Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Chromium網頁Layer Tree繪制過程分析

Chromium網頁Layer Tree繪制過程分析

編輯:關於Android編程

網頁繪圖表面創建完成之後,調度器就會請求繪制CC Layer Tree,這樣網頁在加載完成之後就能快速顯示出來。通過CC Layer Tree可以依次找到Graphics Layer Tree、Render Layer Tree和Render Object Tree。有了Render Object Tree之後,就可以執行具體的繪制工作了。接下來我們就分析網頁CC Layer Tree的繪制過程。

CC Layer Tree是在Main線程中進行繪制的。這個繪制操作對應於網頁渲染過程中的第2個步驟ACTION_SEND_BEGIN_MAIN_FRAME,如下所示:

\

圖1 CC Layer Tree的繪制時機

Main線程實際上並沒有對CC Layer Tree執行真正的繪制,它只是收集將每一個Layer的繪制命令收集起來。這些繪制命令在對網頁分塊進行光柵化時才會被執行,也就是在圖1所示的第4個操作ACTION_MANAGE_TILES中執行。

CC Layer Tree中的每一個Layer都是按照分塊進行繪制的。每一個分塊的繪制命令都收集在一個SkPicture中。這個SkPicture就類似於Android應用程序UI硬件加速渲染過程形成的Display List。關於Android應用程序UI硬件加速渲染的詳細分析,可以參考前面Android應用程序UI硬件加速渲染技術簡要介紹和學習計劃一文。

Layer分塊並不是簡單的區域劃分。簡單的區域劃分在網頁縮放過程中會有問題。我們通過圖2來說明這個問題,以及針對這個問題的解決方案,如下所示:

\

圖2 Layer分塊機制

在圖2中,左上角的圖顯示的是對一個Layer進行的簡單的區域劃分,也就是將Layer劃分為四分塊,每一塊都緊挨著的,沒有發生重疊。當我們對網頁進行縮放的時候,是通過對每一個Layer中的每一個分塊進行縮放實現的。

問題就出現在分塊的邊界上,如圖2右上角的圖所示。圖2右上角的圖對Layer的四個分塊分別執行一個縮小的操作。我們的目的是對整個網頁執行一個縮小的操作。縮小後的網頁的每一個點都是通過原來網頁的若干個相鄰點計算出來的。例如,我們要將網頁縮小為原來的1/4,那麼原網頁每相鄰的4個點都會通過加權平均合成1個點。但是對網頁進行分塊之後,分塊邊界的點就會失去原來與它相鄰的但是在另外一個分塊的點的信息,這樣就會導致這些邊界點無法進行正確的縮放計算。

針對上述問題的解決方案是將Layer劃分成相互重疊的區域。重疊的區域多大才合適的呢?這與網頁的最小縮放因子有關。假設網頁最小可以縮小原來的1/16,那麼重疊的區域就至少需要15個點。有了這額外的15個字,原來邊界上的點就可以找到它所有的相鄰的點進行加權平均計算,從而得到正確的縮小後的點。圖2下面的三個圖顯示的就是左上角的Layer的分塊情況,原本Layer只需要劃分為四個分塊,現在被劃分為六個分塊。為了清楚地看到這個六個分塊的重疊情況,我們用了三個圖來顯示這些分塊,每一個圖顯示其中的兩塊,並且用不同的顏色標記。

有了上面描述的簡單背景知識之後 ,接下來我們結合源碼詳細分析CC Layer Tree的繪制過程。我們從調度器調度執行ACTION_SEND_BEGIN_MAIN_FRAME操作說起這個繪制過程。

從前面Chromium網頁渲染調度器(Scheduler)實現分析一文可以知道,當調度器調用SchedulerStateMachine類的成員函數NextAction詢問狀態機下一步要執行的操作時,SchedulerStateMachine類的成員函數NextAction會調用另外一個成員函數ShouldSendBeginMainFrame。當SchedulerStateMachine類的成員函數ShouldSendBeginMainFrame返回值等於true的時候,狀態機就會提示調度器接下來需要執行ACTION_SEND_BEGIN_MAIN_FRAME操作。

SchedulerStateMachine類的成員函數NextAction的實現如下所示:

 

SchedulerStateMachine::Action SchedulerStateMachine::NextAction() const {
  ......
  if (ShouldSendBeginMainFrame())
    return ACTION_SEND_BEGIN_MAIN_FRAME;
  ......
  return ACTION_NONE;
}
這個函數定義在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。

 

接下來我們就繼續分析SchedulerStateMachine類的成員函數ShouldSendBeginMainFrame什麼情況下會返回true,它的實現如下所示:

 

bool SchedulerStateMachine::ShouldSendBeginMainFrame() const {
  if (!needs_commit_)
    return false;

  // Only send BeginMainFrame when there isn't another commit pending already.
  if (commit_state_ != COMMIT_STATE_IDLE)
    return false;

  // Don't send BeginMainFrame early if we are prioritizing the active tree
  // because of smoothness_takes_priority.
  if (smoothness_takes_priority_ &&
      (has_pending_tree_ || active_tree_needs_first_draw_)) {
    return false;
  }

  // We do not need commits if we are not visible.
  if (!visible_)
    return false;

  // We want to start the first commit after we get a new output surface ASAP.
  if (output_surface_state_ == OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT)
    return true;

  // We should not send BeginMainFrame while we are in
  // BEGIN_IMPL_FRAME_STATE_IDLE since we might have new
  // user input arriving soon.
  // TODO(brianderson): Allow sending BeginMainFrame while idle when the main
  // thread isn't consuming user input.
  if (begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_IDLE &&
      BeginFrameNeeded())
    return false;

  // We need a new commit for the forced redraw. This honors the
  // single commit per interval because the result will be swapped to screen.
  if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_COMMIT)
    return true;

  // After this point, we only start a commit once per frame.
  if (HasSentBeginMainFrameThisFrame())
    return false;

  // We shouldn't normally accept commits if there isn't an OutputSurface.
  if (!HasInitializedOutputSurface())
    return false;

  // SwapAck throttle the BeginMainFrames unless we just swapped.
  // TODO(brianderson): Remove this restriction to improve throughput.
  bool just_swapped_in_deadline =
      begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE &&
      HasSwappedThisFrame();
  if (pending_swaps_ >= max_pending_swaps_ && !just_swapped_in_deadline)
    return false;

  if (skip_begin_main_frame_to_reduce_latency_)
    return false;

  return true;
}

這個函數定義在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。

SchedulerStateMachine類的成員函數ShouldSendBeginMainFrame返回true需要滿足三個個必要條件:

1. 網頁的CC Layer Tree在當前幀中發生了新的變化。這時候SchedulerStateMachine類的成員變量needs_commit_的值等於true。

2. 網頁的CC Layer Tree在上一幀中的變化已經同步到CC Pending Layer Tree去了。這時候狀態機的CommitState狀態等於COMMIT_STATE_IDLE。

3. 網頁當前是可見的。這時候SchedulerStateMachine類的成員變量visible_的值等於true。

如果SchedulerStateMachine類的成員變量smoothness_takes_priority_的值等於true,那麼就表示CC Active Layer的渲染優先級比CC Layer Tree的繪制優先級高。在這種情況下,SchedulerStateMachine類的成員函數ShouldSendBeginMainFrame返回true還需要滿足第四個必要條件:

4. 上一個CC Pending Layer Tree已經被激活為CC Active Layer Tree,並且這個CC Active Layer Tree在激活後已經至少被渲染過一次了。這時候SchedulerStateMachine類的成員變量has_pending_tree_和active_tree_needs_first_draw_的值均等於false。

當SchedulerStateMachine類的成員變量has_pending_tree_的值等於true時,就表明在上一個CC Pending Layer Tree還未被激活,也就是它的光柵化操作還未完成。這時候如果又去繪制新的CC Layer Tree,那麼就會進一步拖延到上一個CC Pending Layer Tree的光柵化操作,從而導致這個CC Pending Layer Tree不能盡快地激活為CC Active Layer Tree進行渲染。這就違反了CC Active Layer的渲染優先級比CC Layer Tree的繪制優先級高的原則。

當SchedulerStateMachine類的成員變量active_tree_needs_first_draw_的值等於true時,就表明上一個CC Pending Layer Tree剛剛激活為CC Active Layer Tree,但是這個CC Active Layer Tree還未被渲染過。這時候如果又去繪制新的CC Layer Tree,那麼就會就會進一步拖延剛剛激活的CC Active Layer Tree的渲染時間。這同樣是違反了CC Active Layer的渲染優先級比CC Layer Tree的繪制優先級高的原則。

滿足了上述4個必要條件之後,SchedulerStateMachine類的成員函數ShouldSendBeginMainFrame在三種情況下會返回true。

第一種情況是網頁的繪圖表面剛剛創建和初始化完成。從前面Chromium網頁繪圖表面(Output Surface)創建過程分析一文可以知道,這時候狀態機的OutputSurfaceState狀態為OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT。這種情況一般發生在網頁加載完成時。這時候最重要的事情就是盡快得到一個CC Active Layer Tree,以便Compositor線程有東西可渲染。由於CC Layer Tree繪制完成後,才可以得到CC Active Layer Tree,因此這種情況就要求馬上對CC Layer Tree進行繪制。

第二種情況是狀態機的ForcedRedrawOnTimeoutState狀態等於FORCED_REDRAW_STATE_WAITING_FOR_COMMIT。從前面Chromium網頁渲染調度器(Scheduler)實現分析一文可以知道,這時候Compositor線程正在等待Main線程將CC Layer Tree的變化同步到新的CC Pending Layer Tree中去,以便可以執行下一幀的渲染操作。因此這時候就要求馬上對CC Layer Tree進行繪制。

在第二種情況中,如果狀態機的BeginImplFrameState狀態等於BEGIN_IMPL_FRAME_STATE_IDLE,那麼還需要滿足一個額外條件,SchedulerStateMachine類的成員函數ShouldSendBeginMainFrame的返回值才會等於true。這個額外條件就是當前不是處於動畫過程中,並且當前的CC Active Layer Tree也不要求重新渲染。如果能滿足這個額外條件,那麼從前面Chromium網頁渲染調度器(Scheduler)實現分析一文可以知道,這時候調用SchedulerStateMachine類的成員函數BeginFrameNeeded得到的返回值就會等於false。這一點應該怎麼理解呢?

調度器原則上不會在狀態機的BeginImplFrameState狀態等於BEGIN_IMPL_FRAME_STATE_IDLE時,請求Main線程對CC Layer Tree進行繪制。從前面Chromium網頁渲染調度器(Scheduler)實現分析一文可以知道,從上一個BEGIN_IMPL_FRAME操作執行完成至下一個VSync信號到來之前的這段時間裡,狀態機的BeginImplFrameState狀態等於BEGIN_IMPL_FRAME_STATE_IDLE。這時候如果去繪制CC Layer Tree,那麼就會導致這個段時間裡的用戶輸入要等到下一次繪制CC Layer Tree時才會得到反應,也就是再下一個VSync信號到來時才會得到反應。這會讓用戶覺得網頁對用戶的輸入響應得不夠快。相反,如果將CC Layer Tree的繪制延遲到下一個VSync信號到來時再執行,那麼在上述時間段內發生的用戶輸入,就可以在接下來的CC Layer Tree得到反應,也就是在下一個VSync信號到來得到反應,而不必等待再一個VSync信號的到來。

不過調度器有時候也會放寬要求,允許在狀態機的BeginImplFrameState狀態等於BEGIN_IMPL_FRAME_STATE_IDLE時,讓Main線程對CC Layer Tree進行繪制,前提是當前不是處於動畫過程中,並且當前的CC Active Layer Tree也不要求重新渲染。網頁在顯示動畫時,往往用戶也正在輸入。典型的情景就是用戶正在滑動網頁,這時候Chromium需要根據用戶的輸入滾動網頁,也就是執行網頁的滾動動畫。反過來,如果網頁當前不需要顯示動畫,那麼很大程度上也表明當前沒有用戶輸入。這時候馬上對CC Layer Tree進行繪制,既可以提前對CC Layer Tree進行繪制,又不會引起上述的用戶輸入響應問題。

第三種情況需要滿足以下四個條件:

1. 在當前幀時間裡,調度器還沒有執行過ACTION_SEND_BEGIN_MAIN_FRAME操作。這時候調用SchedulerStateMachine類的成員函數HasSentBeginMainFrameThisFrame得到的返回值等於false。這個條件表明每一個VSync周期只允許執行一次ACTION_SEND_BEGIN_MAIN_FRAME操作,也就是一個VSync周期只允許繪制一次CC Layer Tree。

2.網頁的繪圖表面已經創建和初始化完成,也就是網頁的繪圖表面是一個有效的繪圖表面。這時候調用SchedulerStateMachine類的成員函數HasInitializedOutputSurface得到的返回值等於true。如果網頁的繪圖表面還沒有創建和初始化完成,或者已經創建和初始化完成,但是失效了,那麼此時繪制CC Layer Tree是沒有意義的,因為繪制出來了也無法顯示。這時候優先級最高的事情是為網頁創建新的繪圖表面。

3. 未完成的swapBuffers操作的個數小於預設的閥值。這個預設的閥值保存在SchedulerStateMachine類的成員變量max_pending_swaps_中。同時,未完成的swapBuffers操作的個數記錄在SchedulerStateMachine類的另外一個成員變量pending_swaps_中。Compositor線程每次渲完成CC Active Layer Tree之後,都會執行一個swapBuffers操作,並且將SchedulerStateMachine類的成員變量pending_swaps_的值增加1。這個操作就是請求Browser進程對已經渲染好的網頁內容進行合成。當Browser進程合成好網頁的內容後,它就會通知Render進程將SchedulerStateMachine類的成員變量pending_swaps_的值減少1。當未完成的swapBuffers操作超過預設閥值時,就說明Browser進程太忙了,跟不上Render進程渲染網頁的速度,因此這時候就要求Render進程慢下來。在這裡就表現為暫停繪制CC Layer Tree。

4. Main線程當前不是處於高延時模式。這時候SchedulerStateMachine類的成員變量skip_begin_main_frame_to_reduce_latency_等於false。當Main線程當前處於高延時模式時,要降低Main線程繪制CC Layer Tree的頻率,否則延時就會進一步加大。關於Main線程的高延時模式判斷,可以參考前面Chromium網頁渲染調度器(Scheduler)實現分析一文。

其中,第3個條件又可以進一步放寬,就是即使未完成的swapBuffers操作的個數大於等於預設的閥值,SchedulerStateMachine類的成員函數ShouldSendBeginMainFrame也可以返回true。前提條件是此時有一個之前提交的swapBuffers操作在當前的BEGIN_IMPL_FRAME操作執行期間剛剛執行完成。這時候實際上是需要將SchedulerStateMachine類的成員變量pending_swaps_的值減少1的,但是還沒有來得減,因此就允許Compositor再提交一個新的swapBuffers操作,以抵消剛剛完成的wapBuffers操作。提交新的swapBuffers操作就意味要先繪制新的CC Layer Tree,因此這時候就允許SchedulerStateMachine類的成員函數ShouldSendBeginMainFrame返回true。

回到SchedulerStateMachine類的成員函數NextAction中,當它調用成員函數ShouldSendBeginMainFrame得到的返回值等於true,它就會返回一個ACTION_SEND_BEGIN_MAIN_FRAME給Scheduler類的成員函數ProcessScheduledActions。這時候Scheduler類的成員函數ProcessScheduledActions就會請求Main線程對CC Layer Tree進行繪制,如下所示:

 

void Scheduler::ProcessScheduledActions() {
  ......

  SchedulerStateMachine::Action action;
  do {
    action = state_machine_.NextAction();
    ......
    state_machine_.UpdateState(action);
    ......
    switch (action) {
      ......
      case SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME:
        client_->ScheduledActionSendBeginMainFrame();
        break;
      ......
    }
  } while (action != SchedulerStateMachine::ACTION_NONE);

  SetupNextBeginFrameIfNeeded();
  ......

  if (state_machine_.ShouldTriggerBeginImplFrameDeadlineEarly()) {
    ......
    ScheduleBeginImplFrameDeadline(base::TimeTicks());
  }
}
這個函數定義在文件external/chromium_org/cc/scheduler/scheduler.cc中。

 

Scheduler類的成員函數ProcessScheduledActions的詳細分析可以參考前面Chromium網頁渲染調度器(Scheduler)實現分析一文。這時候Scheduler類的成員函數ProcessScheduledActions首先調用SchedulerStateMachine類的成員函數UpdateState更新狀態機的狀態,接著再調用成員變量client_指向的一個ThreadProxy對象的成員函數ScheduledActionSendBeginMainFrame請求Main線程繪制CC Layer Tree。

SchedulerStateMachine類的成員函數UpdateState的實現如下所示:

 

void SchedulerStateMachine::UpdateState(Action action) {
  switch (action) {
    ......

    case ACTION_SEND_BEGIN_MAIN_FRAME:
      ......
      commit_state_ = COMMIT_STATE_BEGIN_MAIN_FRAME_SENT;
      needs_commit_ = false;
      last_frame_number_begin_main_frame_sent_ =
          current_frame_number_;
      return;

      ......
  }
}
這個函數定義在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。

 

SchedulerStateMachine類的成員函數UpdateState這時候做三件事情:

1. 將狀態機的CommitState狀態設置為COMMIT_STATE_BEGIN_MAIN_FRAME_SENT,表示調度器正在請求Main線程繪制CC Layer Tree。等到Main線程繪制完成CC Layer Tree之後,就需要將它同步到CC Pending Layer Tree去。

2. 將成員變量needs_commit_的值設置為false,表示此前CC Layer Tree發生的變化已經得到處理。在此之後如果CC Layer Tree又再發生了變化,成員變量needs_commit_的值才會再次設置為true。

3. 將成員變量last_frame_number_begin_main_frame_sent_的值設置為成員變量current_frame_number_,表示當前繪制的CC Layer Tree是屬於哪一幀。以後通過這兩個成員變量就可以判斷上一次執行的BEGIN_MAIN_FRAME操作是否在當前幀內。

ThreadProxy類的成員函數ScheduledActionSendBeginMainFrame的實現如下所示:

 

void ThreadProxy::ScheduledActionSendBeginMainFrame() {
  ......

  scoped_ptr begin_main_frame_state(
      new BeginMainFrameAndCommitState);
  ......

  Proxy::MainThreadTaskRunner()->PostTask(
      FROM_HERE,
      base::Bind(&ThreadProxy::BeginMainFrame,
                 main_thread_weak_ptr_,
                 base::Passed(&begin_main_frame_state)));
  ......
}
這個函數定義在文件external/chromium_org/cc/trees/thread_proxy.cc中。

ThreadProxy類的成員函數ScheduledActionSendBeginMainFrame向Main線程的消息隊列發送一個Task,這個Task綁定的函數是ThreadProxy類的成員函數BeginMainFrame。因此,接下來ThreadProxy類的成員函數BeginMainFrame會在Main線程中執行,執行過程如下所示:

 

void ThreadProxy::BeginMainFrame(
    scoped_ptr begin_main_frame_state) {
  ......

  if (!layer_tree_host()->visible()) {
    ......
    bool did_handle = false;
    Proxy::ImplThreadTaskRunner()->PostTask(
        FROM_HERE,
        base::Bind(&ThreadProxy::BeginMainFrameAbortedOnImplThread,
                   impl_thread_weak_ptr_,
                   did_handle));
    return;
  }

  if (layer_tree_host()->output_surface_lost()) {
    ......
    bool did_handle = false;
    Proxy::ImplThreadTaskRunner()->PostTask(
        FROM_HERE,
        base::Bind(&ThreadProxy::BeginMainFrameAbortedOnImplThread,
                   impl_thread_weak_ptr_,
                   did_handle));
    return;
  }

  ......

  layer_tree_host()->AnimateLayers(
      begin_main_frame_state->monotonic_frame_begin_time);

  ......

  if (begin_main_frame_state->evicted_ui_resources)
    layer_tree_host()->RecreateUIResources();

  layer_tree_host()->Layout();

  ......

  bool can_cancel_this_commit =
      main().can_cancel_commit && !begin_main_frame_state->evicted_ui_resources;
  ......

  scoped_ptr queue =
      make_scoped_ptr(new ResourceUpdateQueue);
  bool updated = layer_tree_host()->UpdateLayers(queue.get());
  
  ......

  if (!updated && can_cancel_this_commit) {
    ......
    bool did_handle = true;
    Proxy::ImplThreadTaskRunner()->PostTask(
        FROM_HERE,
        base::Bind(&ThreadProxy::BeginMainFrameAbortedOnImplThread,
                   impl_thread_weak_ptr_,
                   did_handle));

    .......
    return;
  }


  {
    ......

    CompletionEvent completion;
    Proxy::ImplThreadTaskRunner()->PostTask(
        FROM_HERE,
        base::Bind(&ThreadProxy::StartCommitOnImplThread,
                   impl_thread_weak_ptr_,
                   &completion,
                   queue.release()));
    completion.Wait();

    ......
  }

  ......
}
這個函數定義在文件external/chromium_org/cc/trees/thread_proxy.cc中。

ThreadProxy類在內部保存了一個LayerTreeHost對象,通過調用成員函數layer_tree_host可以獲得這個LayerTreeHost對象。這個LayerTreeHost對象就是用來管理網頁的CC Layer Tree的,因此調用LayerTreeHost類的成員函數可以對CC Layer Tree執行相應的操作。

ThreadProxy類的成員函數BeginMainFrame主要是做三件事情:

1. 計算CC Layer Tree的動畫。這是通過調用LayerTreeHost類的成員函數AnimateLayers實現的。

2. 計算CC Layer Tree的布局。這是通過調用LayerTreeHost類的成員函數Layout實現的。

3. 繪制CC Layer Tree。這是通過調用LayerTreeHost類的成員函數UpdateLayers實現的。

不過,在做這三件事情之前,ThreadProxy類的成員函數BeginMainFrame會先檢查網頁當前是否可見的,以及它的繪圖表面是否是有效的。如果網頁當前不可見,或者網頁的繪圖表面已經失效,那麼ThreadProxy類的成員函數BeginMainFrame就不會無用功了。它直接向Compositor線程的消息隊列發送一個Task,這個Task綁定了ThreadProxy類的成員函數BeginMainFrameAbortedOnImplThread。當ThreadProxy類的成員函數BeginMainFrameAbortedOnImplThread被調用的時候,就會告訴調度器,它之前調度的ACTION_SEND_BEGIN_MAIN_FRAME操作還沒有執行就被取消了。

第三件事情執行完成後,LayerTreeHost類的成員函數UpdateLayers的返回值表示CC Layer Tree是否有發生變化。如果CC Layer Tree沒有發生變化,並且接下來允許不執ACTION_COMMIT操作,那麼ThreadProxy類的成員函數BeginMainFrame就不會請求Compositor線程將CC Layer Tree的內容同步到一個新的CC Pending Layer Tree中去,它會直接向Compositor線程的消息隊列發送一個Task,這個Task也是綁定了ThreadProxy類的成員函數BeginMainFrameAbortedOnImplThread。當ThreadProxy類的成員函數BeginMainFrameAbortedOnImplThread被調用的時候,就會告訴調度器,它之前調度的ACTION_SEND_BEGIN_MAIN_FRAME操作已經執行但是接下來的ACTION_COMMIT操作被取消了。

一般來說,ACTION_COMMIT操作是緊跟在ACTION_SEND_BEGIN_MAIN_FRAME操作後面執行的。但是當ACTION_SEND_BEGIN_MAIN_FRAME操作不是由CC Layer Tree的變化觸發的時候,就可能會允許不在ACTION_SEND_BEGIN_MAIN_FRAME操作之後執行一個ACTION_COMMIT操作。這裡說可能,是因為還需要滿足另外一個條件,才真的允許不在ACTION_SEND_BEGIN_MAIN_FRAME操作之後執行一個ACTION_COMMIT操作。這個條件就是網頁正在使用的資源沒有被回收。當網頁從可見變為不可見的時候(用戶切換Tab的時候就會發生這種情況),網頁正在使用的資源就會被回收。等到網頁重新變為可見的時候,之前回收的資源需要重新創建。這些資源的重建工作是在計算CC Layer Tree的布局之前做的,也就是通過調用LayerTreeHost類的成員函數RecreateUIResources完成的。這些資源重新創建之後,需要通過一個ACTION_COMMIT操作才能渲染出來。因此,在這種情況下,就要求在ACTION_SEND_BEGIN_MAIN_FRAME操作之後緊接著執行一個ACTION_COMMIT操作。

調用ThreadProxy類的成員函數main可以獲得一個MainThreadOnly對象。當這個MainThreadOnly對象的成員變量can_cancel_commit等於false的時候,就表示ACTION_SEND_BEGIN_MAIN_FRAME操作是由CC Layer Tree的變化觸發的,因此這時候不允許不在ACTION_SEND_BEGIN_MAIN_FRAME操作之後執行一個ACTION_COMMIT操作。此外,當參數begin_main_frame_state指向的BeginMainFrameAndCommitState對象的成員變量evicted_ui_resources的值等於true的時候,就表示網頁正在使用的資源被回收了,因此這時候也允許不在ACTION_SEND_BEGIN_MAIN_FRAME操作之後執行一個ACTION_COMMIT操作。

如果LayerTreeHost類的成員函數UpdateLayers的返回值表示CC Layer Tree發生了變化,或者沒有發生變化,但是要求接下來執行一個ACTION_COMMIT操作,那麼ThreadProxy類的成員函數BeginMainFrame接下來就會向Compositor線程的消息隊列發送一個Task,這個Task綁定了ThreadProxy類的成員函數StartCommitOnImplThread。因此,接下來ThreadProxy類的成員函數StartCommitOnImplThread就會在Compositor線程中執行。

ThreadProxy類的成員函數StartCommitOnImplThread主要是做兩件事情。第一件事情是處理網頁中的圖片資源,也就是將它們作為紋理上傳到GPU中去,以便後面可以進行渲染。這些需要當作紋理上傳到GPU去的圖片資源是在繪制CC Layer Tree的時候收集的。收集後會保存在一個隊列中,然後這個隊列會傳遞給ThreadProxy類的成員函數StartCommitOnImplThread處理。第二件事情是將CC Layer Tree同步到一個新的CC Pending Layer Tree中去。本文只分析第一件事情,第二件事情在接下來一篇文章再詳細分析。

注意,ThreadProxy類的成員函數BeginMainFrame是在Main線程執行的,當它請求Compositor線程執行ThreadProxy類的成員函數StartCommitOnImplThread時,Main線程會通過一個CompletionEvent對象進入等待狀態。等到ThreadProxy類的成員函數StartCommitOnImplThread將CC Layer Tree同步到一個新的CC Pending Layer Tree去之後,Compositor線程才會通過上述CompletionEvent對象喚醒Main線程。在網頁的渲染過程中,就只有這一個環節需要Main線程和Compositor線程串行執行。在其它時候,Main線程和Compositor線程都是可以並行執行的。

接下來我們首先分析ThreadProxy類的成員函數BeginMainFrameAbortedOnImplThread的實現,接著再分析LayerTreeHost類的成員函數AnimateLayers、Layout和UpdateLayers的實現,最後分析ThreadProxy類的成員函數StartCommitOnImplThread的實現。

ThreadProxy類的成員函數BeginMainFrameAbortedOnImplThread的實現如下所示:

 

void ThreadProxy::BeginMainFrameAbortedOnImplThread(bool did_handle) {
  ......
  impl().scheduler->BeginMainFrameAborted(did_handle);
}
這個函數定義在文件external/chromium_org/cc/trees/thread_proxy.cc中。

 

ThreadProxy類的成員函數BeginMainFrameAbortedOnImplThread調用Scheduler類的成員函數BeginMainFrameAborted通知調度器它之前請求執行的ACTION_SEND_BEGIN_MAIN_FRAME操作被取消了。

Scheduler類的成員函數BeginMainFrameAborted的實現如下所示:

 

void Scheduler::BeginMainFrameAborted(bool did_handle) {
  ......
  state_machine_.BeginMainFrameAborted(did_handle);
  ProcessScheduledActions();
}
這個函數定義在文件external/chromium_org/cc/scheduler/scheduler.cc中。

 

Scheduler類的成員函數BeginMainFrameAborted會調用SchedulerStateMachine類的成員函數BeginMainFrameAborted修改狀態機的狀態。由於狀態機的狀態發生變化之後,有可能需要觸發新的操作,因此Scheduler類的成員函數BeginMainFrameAborted就會調用另外一個成員函數ProcessScheduledActions進行檢查是否需要觸發新的操作。

SchedulerStateMachine類的成員函數BeginMainFrameAborted的實現如下所示:

 

void SchedulerStateMachine::BeginMainFrameAborted(bool did_handle) {
  DCHECK_EQ(commit_state_, COMMIT_STATE_BEGIN_MAIN_FRAME_SENT);
  if (did_handle) {
    bool commit_was_aborted = true;
    UpdateStateOnCommit(commit_was_aborted);
  } else {
    commit_state_ = COMMIT_STATE_IDLE;
    SetNeedsCommit();
  }
}
這個函數定義在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。

 

SchedulerStateMachine類的成員函數BeginMainFrameAborted主要是修改狀態機的CommitState狀態。

從前面的分析可以知道,當參數did_handle的值等於false的時候,就表示之前調度的ACTION_SEND_BEGIN_MAIN_FRAME操作還沒開始執行就被取消了。在這種情況下,SchedulerStateMachine類的成員函數BeginMainFrameAborted將狀態機的ommitState狀態恢復為COMMIT_STATE_IDLE,並且調用另外一個成員函數SetNeedsCommit將成員變量needs_commit_的值設置為true。這樣就可以下一個VSync信號到來時,重新執行一個ACTION_SEND_BEGIN_MAIN_FRAME操作。

為什麼要在下一個VSync信號到來時,重新執行一個ACTION_SEND_BEGIN_MAIN_FRAME操作呢?從前面的分析可以知道,參數did_handle的值等於false時,有可能是因為網頁從可見變為不可見引起的。在下一個VSync信號到來時,有可能網頁會從不可見變為可見,因此這時候就需要重新執行一個ACTION_SEND_BEGIN_MAIN_FRAME操作,以便處理CC Layer Tree之前發生的變化。

當參數did_handle的值等於true的時候,就表示不必在ACTION_SEND_BEGIN_MAIN_FRAME操作之後執行一個ACTION_COMIIT操作,這時候SchedulerStateMachine類的成員函數BeginMainFrameAborted就會調用另外一個成員函數UpdateStateOnCommit修改狀態機的狀態,並且傳遞參數false給它。

注意,狀態機在調度執行ACTION_COMIIT操作的時候,也會調用SchedulerStateMachine類的成員函數UpdateStateOnCommit修改狀態機的狀態,不過這時候傳遞的參數為true。這一點我們在接下來一篇文章分析CC Layer Tree和CC Pending Layer Tree的同步過程時就會看到。

SchedulerStateMachine類的成員函數UpdateStateOnCommit的實現如下所示:

 

void SchedulerStateMachine::UpdateStateOnCommit(bool commit_was_aborted) {
  commit_count_++;

  if (commit_was_aborted || settings_.main_frame_before_activation_enabled) {
    commit_state_ = COMMIT_STATE_IDLE;
  } else if (settings_.main_frame_before_draw_enabled) {
    commit_state_ = settings_.impl_side_painting
                        ? COMMIT_STATE_WAITING_FOR_ACTIVATION
                        : COMMIT_STATE_IDLE;
  } else {
    commit_state_ = COMMIT_STATE_WAITING_FOR_FIRST_DRAW;
  }

  // If we are impl-side-painting but the commit was aborted, then we behave
  // mostly as if we are not impl-side-painting since there is no pending tree.
  has_pending_tree_ = settings_.impl_side_painting && !commit_was_aborted;

  // Update state related to forced draws.
  if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_COMMIT) {
    forced_redraw_state_ = has_pending_tree_
                               ? FORCED_REDRAW_STATE_WAITING_FOR_ACTIVATION
                               : FORCED_REDRAW_STATE_WAITING_FOR_DRAW;
  }

  // Update the output surface state.
  DCHECK_NE(output_surface_state_, OUTPUT_SURFACE_WAITING_FOR_FIRST_ACTIVATION);
  if (output_surface_state_ == OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT) {
    if (has_pending_tree_) {
      output_surface_state_ = OUTPUT_SURFACE_WAITING_FOR_FIRST_ACTIVATION;
    } else {
      output_surface_state_ = OUTPUT_SURFACE_ACTIVE;
      needs_redraw_ = true;
    }
  }

  // Update state if we have a new active tree to draw, or if the active tree
  // was unchanged but we need to do a forced draw.
  if (!has_pending_tree_ &&
      (!commit_was_aborted ||
       forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_DRAW)) {
    needs_redraw_ = true;
    active_tree_needs_first_draw_ = true;
  }

  // This post-commit work is common to both completed and aborted commits.
  pending_tree_is_ready_for_activation_ = false;

  if (continuous_painting_)
    needs_commit_ = true;
}
這個函數定義在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。

 

SchedulerStateMachine類的成員函數UpdateStateOnCommit不單止會修改為狀態機的CommitState狀態,還可能會修改ForcedRedrawOnTimeoutState狀態和OutputSurfaceState狀態,以及其它的狀態。

我們首先看CommitState狀態的修改邏輯:

1. 如果參數commit_was_aborted等於true,也就是接下來不需要執行一個ACTION_COMMIT操作,這時候狀態機的CommitState狀態就會被恢復為COMMIT_STATE_IDLE。注意,在此之前,CommitState狀態為COMMIT_STATE_BEGIN_MAIN_FRAME_SENT。另外,如果SchedulerStateMachine類的成員變量settings_描述的一個SchedulerSettings對象的成員變量main_frame_before_activation_enabled等於true,狀態機的CommitState狀態就會被恢復為COMMIT_STATE_IDLE。這種情況表示允許在下一個CC Pending Layer Tree激活為CC Active Layer Tree之前,執行另外一個BEGIN_MAIN_FRAME操作。由於接下來不會執行ACTION_COMMIT操作,也就是不會有新的CC Pending Layer Tree,於是就不存在CC Pending Layer激活的問題,因此就可以將狀態機的CommitState狀態就會被恢復為COMMIT_STATE_IDLE。

2. 如果不執行第1點所示的邏輯,並且SchedulerStateMachine類的成員變量settings_描述的一個SchedulerSettings對象的成員變量main_frame_before_draw_enabled的值等於true(這表示允許在新激活的CC Active Layer Tree被渲染之前,執行一個另外一個BEGIN_MAIN_FRAME操作),那麼狀態機的CommitState狀態可能會被設置為COMMIT_STATE_IDLE。為什麼只是可能呢?這是因為如果網頁的分塊還沒有被光柵化,那麼就要等到網頁的分塊光柵化完成之後,才會得到一個激活的CC Active Layer Tree。也就是現在還輪不到執行Active Layer Tree的渲染操作。在這種情況下,是禁止執行一個另外一個BEGIN_MAIN_FRAME操作的,也就是不能將狀態機的CommitState狀態設置為COMMIT_STATE_IDLE。另一方面,如果網頁的分塊已經被光柵化,那麼接下來的操作就是對Active Layer Tree進行渲染了,因此這時候就可以執行另外一個BEGIN_MAIN_FRAME操作,也就是可以將狀態機的CommitState狀態設置為COMMIT_STATE_IDLE。注意,現在剛剛執行完成的工作是在Main線程中繪制CC Layer Tree。當SchedulerStateMachine類的成員變量settings_描述的一個SchedulerSettings對象的成員變量impl_side_painting的值等於false的時候,表示網頁分塊的光柵化操作在Main線程繪制CC Layer Tree的時候就順帶完成了,因此這種情況就可以將狀態機的CommitState狀態設置為COMMIT_STATE_IDLE,否則的話,要將狀態機的CommitState狀態設置為COMMIT_STATE_WAITING_FOR_ACTIVATION。

3. 如果不執行第1點和第2點的邏輯,那麼這時候狀態機的CommitState狀態就會被設置為COMMIT_STATE_WAITING_FOR_FIRST_DRAW。這樣設置有兩個含義。一個含義表示接下來不能執行另外一個BEGIN_MAIN_FRAME操作,另一個含義表示狀態機正在等待CC Active Layer Tree被激活後的第一次渲染。

我們接下來看ForcedRedrawOnTimeoutState狀態的的修改邏輯。注意,在當前ForcedRedrawOnTimeoutState狀態等於FORCED_REDRAW_STATE_WAITING_FOR_COMMIT的時候,才需要對它進行修改。這是因為當ForcedRedrawOnTimeoutState狀態等於FORCED_REDRAW_STATE_WAITING_FOR_COMMIT時,表示狀態機正在等待調度器調度一個ACTION_COMMIT操作。既然現在這個ACTION_COMMIT已經被調度了,因此就可以遷移狀態機的ForcedRedrawOnTimeoutState狀態了:

1. 如果前一個CC Pending Layer Tree還未完成光柵化操作,那麼狀態機的ForcedRedrawOnTimeoutState狀態被設置為FORCED_REDRAW_STATE_WAITING_FOR_ACTIVATION,表示狀態機正在等這個CC Pending Layer Tree完成光柵化操作。

2. 如果前一個CC Pending Layer Tree已經完成光柵化操作,也就是它已經激活為一個新的CC Active Layer Tree,那麼狀態機的ForcedRedrawOnTimeoutState狀態被設置為FORCED_REDRAW_STATE_WAITING_FOR_DRAW,表示狀態機正在等待渲染新激活的CC Active Layer Tree。

我們接下來繼續看OutputSurfaceState狀態的修改邏輯。注意,在當前OutputSurfaceState狀態等於OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT的時修,才需要對它進行修改。這是因為當OutputSurfaceState狀態等於OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT時,表示狀態機正在等待調度器調度一個ACTION_COMMIT操作。既然現在這個ACTION_COMMIT已經被調度了,因此就可以遷移狀態機的OutputSurfaceState狀態了:

1.如果前一個CC Pending Layer Tree還未完成光柵化操作,那麼狀態機的OutputSurfaceState狀態被設置為OUTPUT_SURFACE_WAITING_FOR_FIRST_ACTIVATION,表示狀態機正在等這個CC Pending Layer Tree完成光柵化操作。

2. 如果前一個CC Pending Layer Tree已經完成光柵化操作,也就是它已經激活為一個新的CC Active Layer Tree,那麼狀態機的OutputSurfaceState狀態被設置為OUTPUT_SURFACE_ACTIVE,表示狀態機正在等待渲染新激活的CCActiveLayer Tree。

除了修改狀態機的CommitState狀態,還可能會修改ForcedRedrawOnTimeoutState狀態和OutputSurfaceState狀態,SchedulerStateMachine類的成員函數UpdateStateOnCommit還會做以下四件事情:

1. 檢查當前是否存在一個新的CC Pending Layer Tree。如果存在,就會將成員變量has_pending_tree_的值設置為true。當SchedulerStateMachine類的成員變量settings_描述的一個SchedulerSettings對象的成員變量impl_side_painting的值等於true的時候,就表示接下來可能會產生一個新的CC Pending Layer Tree。但是也有可能產生,因為接下來可能不會執行ACTION_COMMIT操作,取決於參數commit_was_aborted的值。當參數commit_was_aborted的值等於false的時候,就表示接下來會執行一個ACTION_COMMIT操作,因此接下來就一定會產生一個新的CC Pending Layer Tree。

2. 檢查當前是否存在一個新的CC Active Layer Tree。如果存在,就會將成員變量active_tree_needs_first_draw_的值設置為true,表示這個新的CC Active Layer Tree需要執行一次渲染。存在一個新激活的CC Active Layer Tree需要滿足一個必要條件,就是上一個CC Pending Layer Tree已經完成光柵化操作,也就是這時候成員變量has_pending_tree_的值會等於false。滿足這個條件的時候,新激活的CC Active Layer Tree的第一次渲染也許已經執行過了。如果這時候狀態機的ForcedRedrawOnTimeoutState狀態為FORCED_REDRAW_STATE_WAITING_FOR_DRAW,那麼就一定說明新激活的CC Active Layer Tree還沒有被渲染過。否則的話,狀態機的ForcedRedrawOnTimeoutState狀態是不會等於FORCED_REDRAW_STATE_WAITING_FOR_DRAW的。另外,如果這時候參數commit_was_aborted的值等於false,那麼就表示雖然上一個CC Pending Layer Tree已經完成光柵化操作,但是接下來很快又會有一個新的CC Pending Layer Tree等待光柵化。當這個新的CC Pending Layer Tree光柵化操作執行完成後,又會得到一個新激活的CC Active Layer Tree。因此這時候也需要將成員變量active_tree_needs_first_draw_的值設置為true,表示接下來很快就會有一個新的CC Active Layer Tree等待第一次渲染。

3. 將成員變量pending_tree_is_ready_for_activation_的值設置為false,表示下一個CC Pending Layer Tree還沒有光柵化完成,也就是它不可以激活為CC Active Layer Tree。當前只存在兩種情況。一種情況是參數commit_was_aborted的值等於true,表示之前調度的ACTION_SEND_BEGIN_MAIN_FRAME操作還沒開始執行就被取消了。這時候肯定不會產生新的CC Pending Layer Tree,因此無從談起激活它了。另一種情況是參數commit_was_aborted的值等於false,表示接下來馬上就要產生一個新的CC Pending Layer Tree。新產生的CC Pending Layer Tree肯定是還沒有完成光柵化的,因此就不能被激活。

4. 在成員變量continuous_painting_的值等於true的情況下,將另外一個成員變量needs_commit_的值也設置為true,表示不管CC Layer Tree有沒有發生新的變化,在下一個VSync信號到來時,都執行一個ACTION_SEND_BEGIN_MAIN_FRAME操作。

接下來我們分析LayerTreeHost類的成員函數AnimateLayers的實現,以便了解CC Layer Tree的動畫計算過程。

LayerTreeHost類的成員函數AnimateLayers的實現如下所示:

 

void LayerTreeHost::AnimateLayers(base::TimeTicks monotonic_time) {
  if (!settings_.accelerated_animation_enabled ||
      animation_registrar_->active_animation_controllers().empty())
    return;

  ......

  AnimationRegistrar::AnimationControllerMap copy =
      animation_registrar_->active_animation_controllers();
  for (AnimationRegistrar::AnimationControllerMap::iterator iter = copy.begin();
       iter != copy.end();
       ++iter) {
    (*iter).second->Animate(monotonic_time);
    ......
  }
}
這個函數定義在文件external/chromium_org/cc/trees/layer_tree_host.cc中。

LayerTreeHost類的成員變量animation_registrar_指向的是一個AnimationRegistrar對象。這個AnimationRegistrar負責管理CC Layer Tree中的動畫。調用這個AnimationRegistrar對象的成員函數active_animation_controllers可以獲得CC Layer Tree當前激活的動畫控制器,如下所示:

 

class CC_EXPORT AnimationRegistrar {
 public:
  typedef base::hash_map AnimationControllerMap;
  ......

  const AnimationControllerMap& active_animation_controllers() const {
    return active_animation_controllers_;
  }

  ......

 private:
  ......

  AnimationControllerMap active_animation_controllers_;
  AnimationControllerMap all_animation_controllers_;
  
  ......
};
這個函數定義在文件external/chromium_org/cc/animation/animation_registrar.h中。

 

動畫控制器通過類LayerAnimationController描述。CC Layer Tree中的每一個Layer都對應有一個動畫控制器。這些動畫控制器注冊在AnimationRegistrar類的成員變量all_animation_controllers_描述的是一個map中,其中鍵值為對應的Layer的ID。

當一個Layer有動畫需要顯示時,它注冊在AnimationRegistrar類的動畫控制器就會再被保存在到另外一個成員變量active_animation_controllers_描述的一個map中,這樣通過這個成員變量就知道一個Layer當前有哪些動畫是需要執行的。

回到LayerTreeHost類的成員函數AnimateLayers中,它調用當前激活的每一個動畫控制器的成員函數Animate執行CC Layer Tree中的動畫,如下所示:

 

void LayerAnimationController::Animate(base::TimeTicks monotonic_time) {
  ......
  TickAnimations(monotonic_time);
  ......
}
這個函數定義在文件external/chromium_org/cc/animation/layer_animation_controller.cc中。

 

LayerAnimationController類的成員函數Animate主要是調用另外一個成員函數TickAnimations執行注冊在它裡面的動畫,如下所示:

 

void LayerAnimationController::TickAnimations(base::TimeTicks monotonic_time) {
  for (size_t i = 0; i < animations_.size(); ++i) {
    if (animations_[i]->run_state() == Animation::Starting ||
        animations_[i]->run_state() == Animation::Running ||
        animations_[i]->run_state() == Animation::Paused) {
      double trimmed =
          animations_[i]->TrimTimeToCurrentIteration(monotonic_time);

      switch (animations_[i]->target_property()) {
        case Animation::Transform: {
          const TransformAnimationCurve* transform_animation_curve =
              animations_[i]->curve()->ToTransformAnimationCurve();
          ......
          break;
        }

        case Animation::Opacity: {
          const FloatAnimationCurve* float_animation_curve =
              animations_[i]->curve()->ToFloatAnimationCurve();
          ......
          break;
        }

        case Animation::Filter: {
          const FilterAnimationCurve* filter_animation_curve =
              animations_[i]->curve()->ToFilterAnimationCurve();
          ......
          break;
        }

        case Animation::BackgroundColor: {
          // Not yet implemented.
          break;
        }

        case Animation::ScrollOffset: {
          const ScrollOffsetAnimationCurve* scroll_offset_animation_curve =
              animations_[i]->curve()->ToScrollOffsetAnimationCurve();
          ......
          break;
        }

        // Do nothing for sentinel value.
        case Animation::TargetPropertyEnumSize:
          NOTREACHED();
      }
    }
  }
}
這個函數定義在文件external/chromium_org/cc/animation/layer_animation_controller.cc中。

 

注冊在LayerAnimationController類中的動畫保存在其成員變量animations_描述的一個Vector中,每一個動畫都是通過一個Animation對象描述。不同類型的動畫有不同的執行方式。例如,對於類型Animation::Transform的動畫來說,只要是通過調用它內部的一個AnimationCurve對象的成員函數ToTransformAnimationCurve來執行的。

這些動畫是從WebKit裡面注冊到LayerAnimationController類的,接下來我們就分析動畫的注冊過程。

當網頁的DOM Tree中的某一個Element需要創建動畫時,WebKit就會調用WebKit層的Animation類的靜態成員函數create為其創建一個動畫,如下所示:

 

PassRefPtrWillBeRawPtr Animation::create(Element* target, PassRefPtrWillBeRawPtr effect, const Timing& timing, Priority priority, PassOwnPtr eventDelegate)
{
    return adoptRefWillBeNoop(new Animation(target, effect, timing, priority, eventDelegate));
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/animation/Animation.cpp中。

 

其中,參數target描述的就是要執行動畫的Element。這個動畫通過WebKit層的一個Animation對象描述。

WebKit在更新網頁的Graphics Layer Tree的時候,就會將DOM Tree中的動畫注冊到CC模塊中去。從前面Chromium網頁Graphics Layer Tree創建過程分析一文可以知道,網頁的Graphics Layer Tree是在RenderLayerCompositor類的成員函數updateIfNeededRecursive中更新的,如下所示:

 

void RenderLayerCompositor::updateIfNeededRecursive()
{
    ......

    updateIfNeeded();
    
    ......

    DocumentAnimations::startPendingAnimations(m_renderView.document());

    ......
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/rendering/compositing/RenderLayerCompositor.cpp中。

 

RenderLayerCompositor類的成員函數updateIfNeededRecursive是通過調用另外一個成員函數updateIfNeeded更新網頁的Graphics Layer Tree的,接著又會調用DocumentAnimations類的靜態成員函數startPendingAnimations執行網頁的DOM Tree中的動畫。

DocumentAnimations類的靜態成員函數startPendingAnimations的實現如下所示:

 

void DocumentAnimations::startPendingAnimations(Document& document)
{
    ASSERT(document.lifecycle().state() == DocumentLifecycle::CompositingClean);
    if (document.compositorPendingAnimations().startPendingAnimations()) {
        ASSERT(document.view());
        document.view()->scheduleAnimation();
    }
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/animation/DocumentAnimations.cpp中。

 

參數document描述的是網頁的文檔對象,調用這個文檔對象的成員函數compositorPendingAnimations可以獲得一個CompositorPendingAnimations對象,有了這個CompositorPendingAnimations對象之後,就可以調用它的成員函數startPendingAnimations檢查是否有動畫需要執行。一旦需要,CompositorPendingAnimations類的成員函數startPendingAnimations的返回值就等於true,這時候DocumentAnimations類的靜態成員函數startPendingAnimations就會調用另外一個成員函數scheduleAnimation調度執行這些動畫。

接下來我們繼續分析CompositorPendingAnimations類的成員函數startPendingAnimations的實現,以便了解WebKit層的動畫是如何注冊到CC模塊去的,如下所示:

 

bool CompositorPendingAnimations::startPendingAnimations()
{
    bool startedSynchronizedOnCompositor = false;
    for (size_t i = 0; i < m_pending.size(); ++i) {
        if (!m_pending[i]->hasActiveAnimationsOnCompositor() && m_pending[i]->maybeStartAnimationOnCompositor() && !m_pending[i]->hasStartTime())
            startedSynchronizedOnCompositor = true;
    }

    ......
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/animation/CompositorPendingAnimations.cpp中。

 

網頁的DOM Tree中的動畫注冊在CompositorPendingAnimations類的成員變量m_pending描述的一個AnimationPlayer向量中,CompositorPendingAnimations類的成員函數startPendingAnimations依次調用這些AnimationPlayer對象的成員函數maybeStartAnimationOnCompositor檢查它們是否有動畫需要執行。

AnimationPlayer類的成員函數maybeStartAnimationOnCompositor的實現如下所示:

 

bool AnimationPlayer::maybeStartAnimationOnCompositor()
{
    ......

    return toAnimation(m_content.get())->maybeStartAnimationOnCompositor(timeline()->zeroTime() + startTimeInternal() + timeLagInternal());
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/animation/AnimationPlayer.cpp中。

 

AnimationPlayer類是負現播放動畫的,它所要播放的動畫可以通過調用成員函數toAnimation獲得,也就是獲得一個Animation對象。獲得了為個Animation對象後,就可以調用它的成員函數maybeStartAnimationOnCompositor檢查它是否有動畫需要執行,如下所示:

 

bool Animation::maybeStartAnimationOnCompositor(double startTime)
{
    ......
    if (!CompositorAnimations::instance()->canStartAnimationOnCompositor(*m_target))
        return false;
    if (!CompositorAnimations::instance()->startAnimationOnCompositor(*m_target, startTime, specifiedTiming(), *effect(), m_compositorAnimationIds))
        return false;
    ......
    return true;
}

這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/animation/Animation.cpp中。

Animation類的成員函數maybeStartAnimationOnCompositor首先調用WebKit中的一個CompositorAnimations單例對象的成員函數canStartAnimationOnCompositor檢查當前正在處理的Animation對象描述的動畫是否可以執行。如果可以執行,再調用上述CompositorAnimations單例對象的成員函數startAnimationOnCompositor執行該動畫。

CompositorAnimations類的成員函數startAnimationOnCompositor的實現如下所示:

 

bool CompositorAnimations::startAnimationOnCompositor(const Element& element, double startTime, const Timing& timing, const AnimationEffect& effect, Vector& startedAnimationIds)
{
    ......

    const KeyframeEffectModelBase& keyframeEffect = *toKeyframeEffectModelBase(&effect);

    RenderLayer* layer = toRenderBoxModelObject(element.renderer())->layer();
    ......

    Vector > animations;
    CompositorAnimationsImpl::getAnimationOnCompositor(timing, startTime, keyframeEffect, animations);
    ......
    for (size_t i = 0; i < animations.size(); ++i) {
        int id = animations[i]->id();
        if (!layer->compositedLayerMapping()->mainGraphicsLayer()->addAnimation(animations[i].release())) {
            ......
            return false;
        }
        ......
    }
    ......
    return true;
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/animation/CompositorAnimations.cpp中。

 

參數element描述的是要執行動畫的一個Element。這個Element對應的是網頁的DOM Tree中的一個節點。通過調用CompositorAnimations類的成員函數toRenderBoxModelObject可以獲得它在網頁的Render Layer Tree中對應的節點,也就是一個RenderLayer對象。

參數effect描述了上述Element要執行的動畫,每一個動畫會被重新封裝封裝成一個WebAnimation對象。這是通過調用CompositorAnimationsImpl類的靜態成員函數getAnimationOnCompositor實現的,如下所示:

 

void CompositorAnimationsImpl::getAnimationOnCompositor(const Timing& timing, double startTime, const KeyframeEffectModelBase& effect, Vector >& animations)
{
    ......

    PropertySet properties = effect.properties();
    ......
    for (PropertySet::iterator it = properties.begin(); it != properties.end(); ++it) {

        PropertySpecificKeyframeVector values;
        getKeyframeValuesForProperty(&effect, *it, compositorTiming.scaledDuration, compositorTiming.reverse, values);

        blink::WebAnimation::TargetProperty targetProperty;
        OwnPtr curve;
        switch (*it) {
        case CSSPropertyOpacity: {
            targetProperty = blink::WebAnimation::TargetPropertyOpacity;

            blink::WebFloatAnimationCurve* floatCurve = blink::Platform::current()->compositorSupport()->createFloatAnimationCurve();
            addKeyframesToCurve(*floatCurve, values, compositorTiming.reverse);
            curve = adoptPtr(floatCurve);
            break;
        }
        case CSSPropertyWebkitFilter: {
            targetProperty = blink::WebAnimation::TargetPropertyFilter;
            blink::WebFilterAnimationCurve* filterCurve = blink::Platform::current()->compositorSupport()->createFilterAnimationCurve();
            addKeyframesToCurve(*filterCurve, values, compositorTiming.reverse);
            curve = adoptPtr(filterCurve);
            break;
        }
        case CSSPropertyTransform: {
            targetProperty = blink::WebAnimation::TargetPropertyTransform;
            blink::WebTransformAnimationCurve* transformCurve = blink::Platform::current()->compositorSupport()->createTransformAnimationCurve();
            addKeyframesToCurve(*transformCurve, values, compositorTiming.reverse);
            curve = adoptPtr(transformCurve);
            break;
        }
        default:
            ASSERT_NOT_REACHED();
            continue;
        }
        ......

        OwnPtr animation = adoptPtr(blink::Platform::current()->compositorSupport()->createAnimation(*curve, targetProperty));
        ......

        animations.append(animation.release());
    }
    ASSERT(!animations.isEmpty());
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/animation/CompositorAnimations.cpp中。

 

每一個動畫都對應有一個WebAnimationCurve對象。這些WebAnimationCurve對象描述了動畫是如何執行的。不同類型的動畫對應不同的WebAnimationCurve對象。例如,類型為CSSPropertyTransform的動畫對應的WebAnimationCurve對象實際上是一個WebTransformAnimationCurve對象。

動畫對應的WebAnimationCurve對象最終會通過Content層提供的一個WebCompositorSupport接口的成員函數createAnimation封裝在一個WebAnimation對象中。 這些WebAnimation對象最後保存在參數animations描述的一個向量中。

回到CompositorAnimations類的成員函數startAnimationOnCompositor,它獲得了要執行的動畫對應的WebAnimation對象之後,接下來就會將這些WebAnimation對象注冊到它們所關聯的Element所對應的Graphics Layer中去。

這個Graphics Layer是怎麼得到的呢?從前面Chromium網頁加載過程簡要介紹和學習計劃這個系列的文章可以知道,網頁的DOM Tree中的一個Element在Render Object Tree中對應有一個Render Object。Render Object Tree中中的一個Render Object在Render Layer Tree中對應有一個Render Layer。Render Layer Tree中一個Render Layer在Graphics Layer Tree中又對應有一個Graphics Layer。通過這種對應關系,給出DOM Tree中的一個Element,就可以在Graphics Layer Tree中找到一個對應的Graphics Layer。

前面我們已經獲得了要執行動畫的Element所對應的Render Layer,通過調用這個Render Layer的成員函數compositedLayerMapping可以獲得一個Composited Layer Mapping。從前面Chromium網頁Graphics Layer Tree創建過程分析一文可以知道,Render Layer實際上對應的是一個SubGraphics Layer Tree。這個SubGraphics Layer Tree就是通過一個Composited Layer Mapping描述的。屬於一個Render Layer的動畫,需要映射到它對應的SubGraphics Layer Tree中的Main Grapics Layer去執行。這個Main Grapics Layer可以通過調用CompositedLayerMapping類的成員函數mainGraphicsLayer獲得了。

得到了Main Graphics Layer之後,就可以將前面獲得的WebAnimation對象注冊到它裡面去了。這是通過調用GraphicsLayer類的成員函數addAnimation實現的,如下所示:

 

bool GraphicsLayer::addAnimation(PassOwnPtr popAnimation)
{
    OwnPtr animation(popAnimation);
    ......
    return platformLayer()->addAnimation(animation.leakPtr());
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/platform/graphics/GraphicsLayer.cpp中。

 

從前面Chromium網頁Layer Tree創建過程分析一文可以知道,WebKit層中的每一個Graphics Layer在Content層都對應有一個WebLayerImpl對象。這個WebLayerImpl對象可以通過調用GraphicsLayer類的成員函數platformLayer獲得。有了這個WebLayerImpl對象之後,就可以調用它的成員函數addAnimation將參數popAnimation描述的動畫注冊到CC Layer Tree中去。

WebLayerImpl類的成員函數addAnimation的實現如下所示:

 

bool WebLayerImpl::addAnimation(blink::WebAnimation* animation) {
  bool result = layer_->AddAnimation(
      static_cast(animation)->PassAnimation());
  ......
  return result;
}
這個函數定義在文件external/chromium_org/content/renderer/compositor_bindings/web_layer_impl.cc中。

 

從前面Chromium網頁Layer Tree創建過程分析一文可以知道,WebLayerImpl類的成員變量layer_指向的是一個PictrueLayer對象。WebLayerImpl類的成員函數addAnimation將參數animation描述的一個WebAnimation對象注冊到它裡面去。這是通過調用PictrueLayer類的父類Layer的成員函數AddAnimation實現的,如下所示:

 

bool Layer::AddAnimation(scoped_ptr  animation) {
  ......

  layer_animation_controller_->AddAnimation(animation.Pass());
  SetNeedsCommit();
  return true;
}
這個函數定義在文件external/chromium_org/cc/layers/layer.cc中。

 

Layer類的成員變量layer_animation_controller_指向的是一個LayerAnimationController對象。這個LayerAnimationController對象就是我們前面提到的CC Layer Tree中的每一個Layer所對應的動畫控制器。有了這個LayerAnimationController對象之後,就可以調用它的成員函數AddAnimation將參數animation描述的動畫注冊到它裡面去。注冊完成之後,Layer類的成員函數AddAnimation還會調用另外一個成員函數SetNeedsCommit通知CC模塊中的調度器重新繪制CC Layer Tree,因為CC Layer Tree現在有動畫需要執行。

LayerAnimationController類的成員函數AddAnimation的實現如下所示:

 

void LayerAnimationController::AddAnimation(scoped_ptr animation) {
  animations_.push_back(animation.Pass());
  needs_to_start_animations_ = true;
  ......
}
這個函數定義在文件external/chromium_org/cc/animation/layer_animation_controller.cc中。

 

LayerAnimationController類的成員函數AddAnimation將參數animation描述的動畫保存在成員變量animations_描述的一個向量中,並且將另外一個成員變量needs_to_start_animations_的值設置為true,這樣在下一次繪制CC Layer Tree時,我們前面分析的LayerAnimationController類的成員函數TickAnimations就會通過成員變量animations_計算參數animation描述的動畫了。

這樣,我們就分析完成了CC Layer Tree的動畫的計算過程,回到ThreadProxy類的成員函數BeginMainFrame中,接下來我們繼續分析CC Layer Tree的布局的計算過程,也就是LayerTreeHost類的成員函數Layout的實現,如下所示:

 

void LayerTreeHost::Layout() {
  client_->Layout();
}
這個函數定義在文件external/chromium_org/cc/trees/layer_tree_host.cc中。

 

從前面Chromium網頁Layer Tree創建過程分析一文可以知道,LayerTreeHost類的成員變量client_指向的是一個RenderWidgetCompositor對象。LayerTreeHost類的成員函數Layout通過調用這個RenderWidgetCompositor對象的成員函數Layout計算CC Layer Tree的布局,實際上是通過CC Layer Tree對應的Render Object Tree進行計算的,因為後者知道網頁所所有的元素的渲染有關的信息。

RenderWidgetCompositor類的成員函數Layout的實現如下所示:

 

void RenderWidgetCompositor::Layout() {
  widget_->webwidget()->layout();
}
這個函數定義在文件external/chromium_org/content/renderer/gpu/render_widget_compositor.cc中。

 

從前面Chromium網頁Layer Tree創建過程分析一文可以知道,RenderWidgetCompositor類的成員變量widget_指向的是一個RenderViewImpl對象。調用這個RenderViewImpl對象的成員函數webwidget可以獲得一個WebViewImpl對象。有了這個WebViewImpl對象之後,就可以調用它的成員函數layout對網頁元素進行布局了。

WebViewImpl類的成員函數layout的實現如下所示:

 

void WebViewImpl::layout()
{
    ......

    PageWidgetDelegate::layout(m_page.get());
    
    ......
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/web/PageWidgetDelegate.cpp中。

WebViewImpl類的成員函數layout調用PageWidgetDelegate類的靜態成員函數layout計算網頁的布局,如下所示:

 

void PageWidgetDelegate::layout(Page* page)
{
    ......

    page->animator().updateLayoutAndStyleForPainting();
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/web/PageWidgetDelegate.cpp中。

 

PageWidgetDelegate類的靜態成員函數layout首先調用參數page指向的一個Page對象的成員函數獲得一個PageAnimator對象,然後再調用這個PageAnimator對象的成員函數updateLayoutAndStyleForPainting計算網頁的布局,如下所示:

 

void PageAnimator::updateLayoutAndStyleForPainting()
{
    ......

    RefPtr<frameview> view = m_page->deprecatedLocalMainFrame()->view();
    ......

    view->updateLayoutAndStyleForPainting();
}</frameview>
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/page/PageAnimator.cpp中。

 

PageAnimator類的成員函數updateLayoutAndStyleForPainting首先調用成員變量m_page指向的一個Page對象的成員函數deprecatedLocalMainFrame獲得一個LocalFrame對象。這個LocalFrame對象的創建過程可以參考前面Chromium網頁Frame Tree創建過程分析一文,它描述的是一個在當前Render進程中加載的網頁。有了這個LocalFrame對象之後,再調用它的成員函數view獲得一個FrameView對象,最後調用這個FrameView對象的成員函數updateLayoutAndStyleForPainting計算網頁的布局,如下所示:

 

void FrameView::updateLayoutAndStyleForPainting()  
{  
    // Updating layout can run script, which can tear down the FrameView.  
    RefPtr<frameview> protector(this);  
  
    updateLayoutAndStyleIfNeededRecursive();  
  
    if (RenderView* view = renderView()) {  
        ......  
  
        view->compositor()->updateIfNeededRecursive();  
  
        ......  
    }  
  
    ......  
}  </frameview>
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/frame/FrameView.cpp中。

 

FrameView類的成員函數updateLayoutAndStyleForPainting首先調用成員函數updateLayoutAndStyleIfNeededRecursive計算網頁的布局,接下來再調用成員函數renderView獲得一個RenderView對象。從前面Chromium網頁DOM Tree創建過程分析一文可以知道,網頁的DOM Tree的根節點對應的Render Object就是一個RenderView對象。因此,這裡獲得的RenderView對象描述的就是正在加載的網頁的Render Layer Tree的根節點。

得到了描述Render Layer Tree的根節點的RenderView對象之後,FrameView類的成員函數updateLayoutAndStyleForPainting就調用它的成員函數compositor獲得一個RenderLayerCompositor對象,然後調用這個RenderLayerCompositor對象的成員函數updateIfNeededRecursive創建或者更新網頁的Graphics Layer Tree。這個過程可以參考前面Chromium網頁Graphics Layer Tree創建過程分析一文。

接下來我們繼續分析網頁布局的計算過程,也就是FrameView類的成員函數updateLayoutAndStyleIfNeededRecursive的實現,如下所示:

 

void FrameView::updateLayoutAndStyleIfNeededRecursive()
{
    ......

    m_frame->document()->updateRenderTreeIfNeeded();

    if (needsLayout())
        layout();

    ......
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/frame/FrameView.cpp中。

 

FrameView類的成員變量m_frame指向的是一個LocalFrame對象,FrameView類的成員函數updateLayoutAndStyleIfNeededRecursive調用這個LocalFrame對象的成員函數document可以獲得一個Document對象。這個Document對象描述的就是網頁的文檔對象。有了這個Document對象之後,就可以調用它的成員函數updateRenderTreeIfNeeded更新網頁的Render Object Tree。這個過程可以參考前面Chromium網頁Render Object Tree創建過程分析一文。從前面Chromium網頁Render Layer Tree創建過程分析一文又可以知道,在網頁的Render Object Tree的更新過程中,Render Layer Tree也會得到更新。

更新了網頁的Render Object Tree和Render Layer Tree,FrameView類的成員函數updateLayoutAndStyleIfNeededRecursive就調用成員函數needsLayout檢查網頁的布局是否需要重新計算。如果需要的話,就會調用另外一個成員函數layout進行計算,如下所示:

 

void FrameView::layout(bool allowSubtree)
{
    ......

    Document* document = m_frame->document();
    bool inSubtreeLayout = isSubtreeLayout();
    RenderObject* rootForThisLayout = inSubtreeLayout ? m_layoutSubtreeRoot : document->renderView();
    ......

    {
        ......

        performLayout(rootForThisLayout, inSubtreeLayout);

        ......
    }

    ......
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/frame/FrameView.cpp中。

 

當FrameView類的成員變量m_layoutSubtreeRoot的值不等於NULL的時候,它指向一個RenderObject對象,表示以這個RenderObject對象為根節點的一個Sub Render Object Tree需要進行重新布局。另一方面,當這個成員變量的值等於NULL的時候,就表示需要對整個網頁進行重新布局,這時候FrameView類的成員函數layout就會通過成員變量m_frame指向的一個LocalFrame對象可以網頁的Render Object Tree的根節點,也就是一個RenderView對象。

不管是要對Sub Render Object Tree進行重新布局,還是對整個網頁進行重新布局,FrameView類的成員函數layout最後都是通過調用另外一個成員函數performLayout執行具體的布局工作的,如下所示:

 

void FrameView::performLayout(RenderObject* rootForThisLayout, bool inSubtreeLayout)
{
    ......

    rootForThisLayout->layout();

    ......
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/frame/FrameView.cpp中。

 

FrameView類的成員函數layout要做的工作就是調用參數rootForThisLayout指向的RenderObject對象的成員函數layout更新它的布局。這個RenderObject對象又會遞歸計算它的子RenderObject對象的布局,從而完成以參數rootForThisLayout指向的RenderObject對象為根節點的Render Object Tree的布局更新過程。

從前面Chromium網頁Render Object Tree創建過程分析一文可以知道,在Render Object Tree中,每一個節點都對應一個特定類型的Render Object。不過這些Render Object都是從RenderBox類繼承下來的。每一個Render Object在對自己的內容進行布局的過程中,都會通過調用父類RenderBox的成員函數layout對自己的子Render Object進行歸遞布局,如下所示:

 

void RenderBox::layout()
{
    ......

    RenderObject* child = slowFirstChild();
    ......

    while (child) {
        child->layoutIfNeeded();
        ......
        child = child->nextSibling();
    }
    ......
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderBox.cpp中。

 

從這裡我們就可以看到,RenderBox類的成員函數layout通過一個while循環依次調用當前正在處理的Render Object的所有子Render Object的成員函數layoutIfNeeded檢查它們是否需要重新布局,如下所示:

 

class RenderObject : public ImageResourceClient {
    ......
public:
    ......

    /* This function performs a layout only if one is needed. */
    void layoutIfNeeded() { if (needsLayout()) layout(); }

    ......
};
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderObject.h中。

 

如果一個Render Object需要重新布局,那麼它就會調用由其子類重寫的成員函數layout執行具體的布局工作,這是一個遞歸的過程。

所有的Render Object都是按照CSS Box Model來計算自己的布局的,如圖3所示:

\

圖3 CSS Box Model

關於CSS Box Model的詳細描述,可以參考這篇文章:CSS Box Model and Positioning。簡單來說,就是一個CSS Box Model由margin、border、padding和content四部分組成。其中,margin、border和padding又分為top、bottom、left和right四個值。一個Render Object在繪制之前,會先進行Layout。Layout的目的就是確定一個Render Object的CSS Box Model的margin、border和padding值。一旦這些值確定之後,再結合Content值,就可以對一個Render Object進行繪制了。

這樣,我們就分析完成了CC Layer Tree的布局的計算過程,回到ThreadProxy類的成員函數BeginMainFrame中,接下來我們繼續分析CC Layer Tree的繪制過程,也就是LayerTreeHost類的成員函數UpdateLayers的實現,如下所示:

 

bool LayerTreeHost::UpdateLayers(ResourceUpdateQueue* queue) {
  ......

  bool result = UpdateLayers(root_layer(), queue);

  ......

  return result || next_commit_forces_redraw_;
}
這個函數定義在文件external/chromium_org/cc/trees/layer_tree_host.cc中。

LayerTreeHost類的成員函數UpdateLayers首先調用另外一個成員函數root_layer獲得CC Layer Tree的根節點,然後再調用另外一個重載版本的成員函數UpdateLayers對CC Layer Tree進行繪制,如下所示:

 

bool LayerTreeHost::UpdateLayers(Layer* root_layer,
                                 ResourceUpdateQueue* queue) {
  ......

  RenderSurfaceLayerList update_list;
  {
    ......

    bool can_render_to_separate_surface = true;
    ......
    int render_surface_layer_list_id = 0;
    LayerTreeHostCommon::CalcDrawPropsMainInputs inputs(
        root_layer,
        device_viewport_size(),
        gfx::Transform(),
        device_scale_factor_,
        page_scale_factor_,
        page_scale_layer,
        GetRendererCapabilities().max_texture_size,
        settings_.can_use_lcd_text,
        can_render_to_separate_surface,
        settings_.layer_transforms_should_scale_layer_contents,
        &update_list,
        render_surface_layer_list_id);
    LayerTreeHostCommon::CalculateDrawProperties(&inputs);

    ......
  }

  bool did_paint_content = false;
  bool need_more_updates = false;
  PaintLayerContents(
      update_list, queue, &did_paint_content, &need_more_updates);
  if (need_more_updates) {
    ......
    prepaint_callback_.Reset(base::Bind(&LayerTreeHost::TriggerPrepaint,
                                        base::Unretained(this)));
    static base::TimeDelta prepaint_delay =
        base::TimeDelta::FromMilliseconds(100);
    base::MessageLoop::current()->PostDelayedTask(
        FROM_HERE, prepaint_callback_.callback(), prepaint_delay);
  }

  return did_paint_content;
}
這個函數定義在文件external/chromium_org/cc/trees/layer_tree_host.cc中。

LayerTreeHost類的成員函數UpdateLayers主要是做兩件事情。第一件事情是調用LayerTreeHostCommon類靜態成員函數CalculateDrawProperties計算以參數root_layer指向的Layer對象為根節點的CC Layer Tree的每一個Layer的繪圖屬性,例如變換矩陣、背景色等等。此外,LayerTreeHostCommon類靜態成員函數CalculateDrawProperties還會根據上述CC Layer Tree創建一個Render Surface Tree。CC Layer Tree中的節點與Render Surface Tree中的節點是多對一的關系。也就是只有CC Layer Tree的某些節點在Render Surface Tree中才擁有Render Surface。

從前面Chromium網頁Layer Tree創建過程分析一文可以知道,CC Layer Tree與Graphics Layer Tree中的節點是一一對應關系,並且Graphics Layer Tree的每一個Layer代表的都是一個圖層。這個圖層在硬件加速渲染條件下,就是一個FBO。但是,這只是WebKit的一廂情願。到底要不要為Graphics Layer Tree中的Layer分配一個圖層最終是由CC模塊決定的。如果CC模塊決定要為一個Graphics Layer分配一個圖層,那麼就會為它創建一個Render Surface。Render Surface才是真正表示一個圖層。

LayerTreeHostCommon類靜態成員函數CalculateDrawProperties根據CC Layer Tree創建出來的Render Surface Tree雖然在結構上也是一個Tree,不過它的節點是以列表的形式儲存的,也就是儲存在本地變量update_list描述的一個RenderSurfaceLayerList中。

有了上述RenderSurfaceLayerList之後,LayerTreeHost類的成員函數UpdateLayers再調用另外一個成員函數PaintLayerContents地保存在RenderSurfaceLayerList中的Render Surface進行繪制,實際上就是對CC Layer Tree進行繪制。

LayerTreeHost類的成員函數PaintLayerContents的返回值表示它是否對CC Layer Tree執行了繪制工作。如果執行了,這個返回值就會等於true。另外,LayerTreeHost類的成員函數PaintLayerContents的最後一個參數是一個輸出參數。當這個輸出參數的值等於true的時候,就表示CC Layer Tree接下來還需要再繪制一次。在這種情況下,LayerTreeHost類的成員函數UpdateLayers在設置一個定時器,在100毫秒後在Main線程中通過另外一個成員函數TriggerPrepaint觸發一次CC Layer Tree的繪制工作。

接下來,我們首先分析LayerTreeHostCommon類靜態成員函數CalculateDrawProperties的實現,以便了解網頁的Render Surface Tree的創建過程,接著再分析LayerTreeHost類的成員函數PaintLayerContents的實現,以便了解CC Layer Tree的繪制過程。

LayerTreeHostCommon類靜態成員函數CalculateDrawProperties的實現如下所示:

 

void LayerTreeHostCommon::CalculateDrawProperties(
    CalcDrawPropsMainInputs* inputs) {
  LayerList dummy_layer_list;
  SubtreeGlobals globals;
  DataForRecursion data_for_recursion;
  ProcessCalcDrawPropsInputs(*inputs, &globals, &data_for_recursion);

  PreCalculateMetaInformationRecursiveData recursive_data;
  PreCalculateMetaInformation(inputs->root_layer, &recursive_data);
  std::vector > accumulated_surface_state;
  CalculateDrawPropertiesInternal(
      inputs->root_layer,
      globals,
      data_for_recursion,
      inputs->render_surface_layer_list,
      &dummy_layer_list,
      &accumulated_surface_state,
      inputs->current_render_surface_layer_list_id);

  ......
}
這個函數定義在文件external/chromium_org/cc/trees/layer_tree_host_common.cc中。

 

LayerTreeHostCommon類的靜態成員函數CalculateDrawProperties主要是調用另外一個靜態模板成員函數CalculateDrawPropertiesInternal計算CC Layer Tree的繪制屬性以及為其創建Render Surface Tree。其中,模板參數設置為Layer。

LayerTreeHostCommon類的靜態模板成員函數CalculateDrawPropertiesInternal的實現如下所示:

 

template 
static void CalculateDrawPropertiesInternal(
    LayerType* layer,
    const SubtreeGlobals& globals,
    const DataForRecursion& data_from_ancestor,
    typename LayerType::RenderSurfaceListType* render_surface_layer_list,
    typename LayerType::LayerListType* layer_list,
    std::vector >* accumulated_surface_state,
    int current_render_surface_layer_list_id) {
  ......

  bool render_to_separate_surface;
  if (globals.can_render_to_separate_surface) {
    render_to_separate_surface = SubtreeShouldRenderToSeparateSurface(
          layer, combined_transform.Preserves2dAxisAlignment());
  } else {
    render_to_separate_surface = IsRootLayer(layer);
  }
  if (render_to_separate_surface) {
    ......

    typename LayerType::RenderSurfaceType* render_surface =
        CreateOrReuseRenderSurface(layer);
  
    ......

    render_surface_layer_list->push_back(layer);
  } else {
    ......

    layer->ClearRenderSurface();

    ......
  }

  ......

  typename LayerType::LayerListType& descendants =
      (layer->render_surface() ? layer->render_surface()->layer_list()
                               : *layer_list);

  // Any layers that are appended after this point are in the layer's subtree
  // and should be included in the sorting process.
  size_t sorting_start_index = descendants.size();

  if (!LayerShouldBeSkipped(layer, layer_is_drawn)) {
    ......
    descendants.push_back(layer);
  }

  ......

  for (size_t i = 0; i < layer->children().size(); ++i) {
    // If one of layer's children has a scroll parent, then we may have to
    // visit the children out of order. The new order is stored in
    // sorted_children. Otherwise, we'll grab the child directly from the
    // layer's list of children.
    LayerType* child =
        layer_draw_properties.has_child_with_a_scroll_parent
            ? sorted_children[i]
            : LayerTreeHostCommon::get_layer_as_raw_ptr(layer->children(), i);

    ......

    CalculateDrawPropertiesInternal(
        child,
        globals,
        data_for_children,
        render_surface_layer_list,
        &descendants,
        accumulated_surface_state,
        current_render_surface_layer_list_id);
    if (child->render_surface() &&
        !child->render_surface()->layer_list().empty() &&
        !child->render_surface()->content_rect().IsEmpty()) {
      ......
      descendants.push_back(child);
    }

    ......
  }

  ......
}
這個函數定義在文件external/chromium_org/cc/trees/layer_tree_host_common.cc中。

 

LayerTreeHostCommon類的靜態模板成員函數CalculateDrawPropertiesInternal計算CC Layer Tree的過程非常復雜,這裡我們將略過。這不會影響我們理解CC Layer Tree的繪制過程,有興趣的讀者可以自行分析。

有三個參數我們需要重點解釋一下,分別是layer、globals、render_surface_layer_list和layer_list。

參數layer指向的是一個CC Layer Tree中的一個Layer,LayerTreeHostCommon類的靜態模板成員函數CalculateDrawPropertiesInternal要做的事情就是計算以此Layer為根節點的子樹的繪制屬性,以及為該子樹創建的Render Surface Tree。

參數globals指向一個SubtreeGlobals對象,這個SubtreeGlobals對象的成員變量can_render_to_separate_surface來自於前面分析的LayerTreeHost類的成員函數UpdateLayers的本地變量can_render_to_separate_surface,它的值被設置為true,表示允許為CC Layer Tree中的Layer創建單獨的Render Surface。在不允許的情況下,CC Layer Tree中的所有Layer都將繪制在一個Render Surface上,也就是根節點擁有的Render Surface。

參數render_surface_layer_list是一個輸出參數,用來保存最最終得到的Render Surface Tree,這個參數來自於前面分析的LayerTreeHost類的成員函數UpdateLayers的本地變量update_list。

參數layer_list是一個Layer List,這個Layer List保存的是要繪制在當前正在處理的Render Surface的所有Layer。

LayerTreeHostCommon類的靜態模板成員函數CalculateDrawPropertiesInternal為CC Layer Tree創建Render Surface Tree的過程如下所示:

1. 如果允許為CC Layer Tree中的Layer創建單獨的Render Surface,那麼就調用全局函數SubtreeShouldRenderToSeparateSurface判斷是否真的要為參數layer描述的Layer創建Render Surface。如果要創建,那麼就將本地變量render_to_separate_surface設置為true,否則就設置為false。

2. 如果不允許為CC Layer Tree中的Layer創建單獨的Render Surface,那麼就只有CC Layer Tree的根節才允許創建Render Surface,也就是只有當參數layer描述的Layer是CC Layer Tree的根節點時,本地變量render_to_separate_surface才會設置為true。

3. 如果要為參數layer描述的Layer創建Render Surface,那麼就會調用成員函數CreateOrReuseRenderSurface為其創建一個Render Surface。與此同時,參數layer描述的Layer會被保存在參數render_surface_layer_list描述的Render Surface List中。這也意味著保存在render_surface_layer_list描述的Render Surface List中的Layer都是有Render Surface的。

4. 如果不需要為參數layer描述的Layer創建Render Surface,那麼就會調用該Layer的成員函數ClearRenderSurface檢查之前是否為它創建過Render Surface。如果創建過,那麼就將它刪除。

5. 每一個Render Surface都有一個Layer List。這個Layer List保存的Layer都是要繪制宿主Render Surface上的。同時,如果我們為CC Layer Tree中的一個Layer創建了一個Render Surface,那麼該Layer的後代Layer都會保存在該Layer的Render Surface上,直到碰到另外一個具有自己的Render Surface的後代Layer為止。此外,一個Layer要保存在一個Render Surface的Layer List上,還要滿足一個條件,就是以這個Layer為根節點的子樹是可見的。如果不可見,就意味不用繪制,因此就不用保存在Render Surface的Layer List去了。

6. 按照上述方式處理參數layer描述的Layer的所有子Layer,也就是歸遞調用LayerTreeHostCommon類的靜態模板成員函數CalculateDrawPropertiesInternal處理參數layer描述的Layer的所有子Layer。

7. 如果一個子Layer具有自己的Render Surface,並且這個Render Surface的繪制區域不為空,以及它的Layer List不為空,那麼當它被遞歸處理完成後,會添加到前一個Render Surface的Layer List中去。這個Render Surface對應的Layer是子Layer的某個祖先Layer。

我們通過圖4說明Render Surface Tree創建完成後的結構,如下所示:

\

圖4 Render Surface List的構造方式

圖4的左邊是一個CC Layer Tree。其中,第1、3和5個Layer具有Render Surface。因此,得到的Render Surface List就保存了Layer 1、Layer 3和Layer 5。這三個Render Surface分別稱為1、3和5。Render Surface 1的Layer List保存了Layer 1、Layer 2和Layer 3。Render Surface 2的Layer List保存了Layer 3、Layer 4和Layer 5。Render Surface 3的Layer List保存了Layer 5。這表示,Layer 5先繪制在Render Surface 5上,接著Layer 3、Layer 4和Render Surface 5再繪制在Render Surface 3上,最後Layer 1、Layer 2和Render Surface 3繪制在Render Surface 1。最終得到的內容都在Render Surface 1中。因此,也將Render Surface 1稱為Target Render Surface,Render Surface 3和Render Surface 5的Contributing Render Surface。

什麼情況下需要為一個Layer創建的一個Render Surface呢?一般來說,如果一個Layer滿足以下條件之一,就需要為其創建Render Surface:

1. 設置了蒙板。

2. 設置了鏡像。

3. 設置了濾鏡。

4. 設置了透明度以及轉換矩陣。

5. 關聯有Copy Output Request。

6. 具有WebGL Context。

7. 是CC Layer Tree的根節點。

具體的規則可以參考全局函數SubtreeShouldRenderToSeparateSurface的實現。另外,我們也可以調用Layer類的成員函數SetForceRenderSurface將一個Layer強制設置為創建Render Surface。

此外,Copy Output Request是用來拷貝它所關聯的Layer的Render Surface的內容。也就當一個Render Surface渲染完成的時候,如果與它對應的Layer關聯有Copy Output Request,那麼Chromium就將這個Render Surface的內容拷貝出來,並且發送給請求者。我們可以通過調用Layer類的成員函數RequestCopyOfOutput為一個Layer關聯一個Copy Output Request。不過,這些Copy Output Request得到執行還需滿足兩個條件:

1. 它所關聯的Layer的Render Surface是Target Render Surface。

2. 它所關聯的Layer使用的渲染器是直接渲染器(Direct Renderer)。

關於第2點,我們進一步解釋。從前面Chromium硬件加速渲染的UI合成過程分析一文可以知道,Render進程使用委托渲染器(Delegated Renderer)渲染網頁的。委托渲染器實際上並沒有執行實際的渲染操作,它只是要渲染的紋理傳遞給另外一個渲染器渲染,也就是Browser進程使用的渲染器,這是一個直接渲染器。這就意味著,我們無法對網頁的CC Layer Tree的Layer關聯Copy Output Request了,即使了關聯了,也不會得到執行。不過我們卻可以對浏覽器窗口的CC Layer Tree的Layer關聯Copy Output Request。浏覽器窗口的CC Layer Tree有一個Layer,這個Layer描述的是網頁的內容,因此雖然我們無法在Render進程中截取網頁的內容,但是可以在Browser進程中截取的網頁的內容。

回到LayerTreeHost類的成員函數UpdateLayers中,得到了一個Render Surface List之後,接下來它就會調用另外一個成員函數PaintLayerContents根據這個Render Surface List繪制CC Layer Tree,如下所示:

 

void LayerTreeHost::PaintLayerContents(
    const RenderSurfaceLayerList& render_surface_layer_list,
    ResourceUpdateQueue* queue,
    bool* did_paint_content,
    bool* need_more_updates) {
  ......

  // Iterates front-to-back to allow for testing occlusion and performing
  // culling during the tree walk.
  typedef LayerIterator LayerIteratorType;
  LayerIteratorType end = LayerIteratorType::End(&render_surface_layer_list);
  for (LayerIteratorType it =
           LayerIteratorType::Begin(&render_surface_layer_list);
       it != end;
       ++it) {
    ......

    if (it.represents_target_render_surface()) {
      PaintMasksForRenderSurface(
          *it, queue, did_paint_content, need_more_updates);
    } else if (it.represents_itself()) {
      ......
      *did_paint_content |= it->Update(queue, &occlusion_tracker);
      *need_more_updates |= it->NeedMoreUpdates();
      ......
    }

    ......
  }

  ......
}
這個函數定義在文件external/chromium_org/cc/trees/layer_tree_host.cc中。

 

LayerTreeHost類的成員函數PaintLayerContents按照從後到前的順序繪制保存在參數render_surface_layer_list描述的Render Surface List中的Layer,以及Target Render Surface。繪制順序可以參考前面圖4的示例。其中,每一個Layer都是通過調用它的成員函數Update繪制的,Target Render Surface只需要繪制它的蒙板就可以了。每一個Layer繪制完成之後,LayerTreeHost類的成員函數PaintLayerContents都會調用它的另外一個成員函數NeedMoreUpdates判斷它接下來是否需要再進行更新。從前面的分析可以知道,如果需要的話,LayerTreeHost類的成員函數UpdateLayers就會設置一個定時器,在100毫秒後進行更新。

接下來我們主要分析CC Layer Tree中的Layer的繪制過程。從前面Chromium網頁Layer Tree創建過程分析一文可以知道,CC Layer Tree中的每一個Layer都是通過一個PictureLayer對象描述的。因此,接下來我們分析PictureLayer類的成員函數Update的實現,如下所示:

 

bool PictureLayer::Update(ResourceUpdateQueue* queue,
                          const OcclusionTracker* occlusion) {
  ......

  bool updated = Layer::Update(queue, occlusion);

  ......

  gfx::Rect visible_layer_rect = gfx::ScaleToEnclosingRect(
      visible_content_rect(), 1.f / contents_scale_x());

  gfx::Rect layer_rect = gfx::Rect(paint_properties().bounds);

  ......

  pile_->SetTilingRect(layer_rect);

  ......

  pending_invalidation_.Swap(&pile_invalidation_);
  pending_invalidation_.Clear();
  ......

  updated |=
      pile_->UpdateAndExpandInvalidation(client_,
                                         &pile_invalidation_,
                                         SafeOpaqueBackgroundColor(),
                                         contents_opaque(),
                                         client_->FillsBoundsCompletely(),
                                         visible_layer_rect,
                                         update_source_frame_number_,
                                         RecordingMode(),
                                         rendering_stats_instrumentation());

  ......

  return updated;
}

這個函數定義在文件external/chromium_org/cc/layers/layer.cc中。

PictureLayer類的成員函數Update首先調用父類Layer的成員函數Update讓其有機會對當前正在處理的Layer執行一些更新工作(實際上什麼也沒有做),接著再計算當前正在處理的Layer所占據的區域layer_rect和可見區域visible_layer_rect。

PictureLayer類有兩個重要的成員變量pending_invalidation_和pile_。其中,成員變量pending_invalidation_指向的是一個Region對象。這個Region對象描述的是當前正在處理的Layer的待重繪區域。PictureLayer類的成員函數Update在重繪這個區域之前,會先將它值設置到另外一個成員變量pile_invalidation_中去,以表示Layer的當前重繪區域,同時也會將待重繪區域清空。

另外一個成員變量成員變量pile_指向了一個PicturePile對象,這個PicturePile對象負責繪制當前正在處理的Layer的待重繪區域,這是通過調用它的成員函數UpdateAndExpandInvalidation實現的。

Layer的當前重繪區域會傳遞給PicturePile類的成員函數UpdateAndExpandInvalidation。PicturePile類的成員函數UpdateAndExpandInvalidation會使得這個當前重繪區域包含所有進行了重新繪制的分塊。這裡說的重新繪制,包含有三層含義。第一層含義是上一次繪制過,這一次變為不需要繪制。第二層含義是上一次繪制過,這一次需要重新再繪制。第三層含義是上次沒有繪制過,這一次需要進行繪制。

後面將CC Layer Tree同步到新的CC Pending Layer Tree去時,CC Pending Layer Tree會獲得每一個Layer的當前重繪區域。獲得了每一個Layer的當前重繪區域之後,就可以對它所包含的分塊進行光柵化操作了。

在調用PicturePile類的成員函數UpdateAndExpandInvalidation繪制Layer的內容之前,PictureLayer類的成員函數Update會先將Layer所占據的區域layer_rect設置到成員變量pile_指向的PicturePile對象的內部去,這是通過調用PicturePile類的成員函數SetTilingRect實現的。

PicturePile類的成員函數SetTilingRect是從父類PicturePileBase類繼承下來的。在分析PicturePileBase類的成員函數SetTilingRect之前,我們需要分析PicturePileBase類的一個成員變量tiling_,它的定義如下所示:

 

class CC_EXPORT PicturePileBase : public base::RefCounted {
 ......

 protected:
  ......

  TilingData tiling_;
  
  ......
};
這個類定義在文件external/chromium_org/cc/resources/picture_pile_base.h中。

 

從這裡可以看到,PicturePileBase類的成員變量tiling_描述的是一個TilingData對象,這個TilingData對象是負責對Layer進行分塊的,它的定義如下所示:

 

class CC_EXPORT TilingData {
 ......

 private:
  ......

  gfx::Size max_texture_size_;
  gfx::Rect tiling_rect_;
  int border_texels_;

  // These are computed values.
  int num_tiles_x_;
  int num_tiles_y_;
};
這個類定義在文件external/chromium_org/cc/base/tiling_data.h中。

 

TilingData類有五個成員變量,它們的含義分別為:

1.max_texture_size_:表示分塊的大小。

2. tiling_rect_:表示Layer的大小。

3. border_texels_:表示分塊的邊界大小。這個邊界即為圖2所示的分塊重疊區域的長度。

4.num_tiles_x_:表示Layer在x軸方向的分塊個數。

5.num_tiles_y_:表示Layer在y軸方向的分塊個數。

PicturePileBase類的成員變量tiling_描述的TilingData對象是通過調用TilingData類的默認構造函數創建的,如下所示:

 

TilingData::TilingData()
    : border_texels_(0) {
  RecomputeNumTiles();
}
這個函數定義在文件external/chromium_org/cc/base/tiling_data.cc中。

 

TilingData類的默認構造函數先將成員變量border_texels_的值設置為0,接著調用另外一個成員函數RecomputeNumTiles分別計算Layer在x軸和y軸方向的分塊個數。由於現在TilingData類還不知道Layer的大小,因此現在的計算是沒有意義的。

PictureLayer類的成員變量成員變量pile_指向的PicturePile對象在構造的時候,會調用父類PicturePileBase的構造函數設置分塊的大小,如下所示:

 

const int kBasePictureSize = 512;
......

PicturePileBase::PicturePileBase()
    : min_contents_scale_(0),
      ...... {
  tiling_.SetMaxTextureSize(gfx::Size(kBasePictureSize, kBasePictureSize));
  ......
}
這個函數定義在文件external/chromium_org/cc/resources/picture_pile_base.cc中。

 

從這裡可以看到,PicturePileBase的構造函數將成員變量tiling_描述的TilingData對象使用的分塊大小設置為512,這是通過調用TilingData類的成員函數SetMaxTextureSize實現的,如下所示:

 

void TilingData::SetMaxTextureSize(const gfx::Size& max_texture_size) {
  max_texture_size_ = max_texture_size;
  RecomputeNumTiles();
}
這個函數定義在文件external/chromium_org/cc/base/tiling_data.cc中。

 

TilingData類的成員函數SetMaxTextureSize先參數max_texture_size描述的分塊大小保存在成員變量max_texture_size_中,接著再調用成員函數RecomputeNumTiles重新計算Layer在x軸和y軸方向的分塊個數。

從前面Chromium網頁Layer Tree創建過程分析一文可以知道,CC Layer Tree是由Main線程中的一個LayerTreeHost對象管理的。CC模塊會給CC Layer Tree的每一個節點,也就是每一個PictureLayer對象,都關聯負責管理CC Layer Tree的LayerTreeHost對象。這是通過調用PictureLayer類的成員函數SetLayerTreeHost實現的,如下所示:

 

void PictureLayer::SetLayerTreeHost(LayerTreeHost* host) {
  ......
  if (host) {
    pile_->SetMinContentsScale(host->settings().minimum_contents_scale);
    ......
  }
}
這個函數定義在文件external/chromium_org/cc/layers/picture_layer.cc中。

 

PictureLayer類的成員函數SetLayerTreeHost會通過參數host指向的LayerTreeHost對象獲得CC Layer Tree的最小縮放因子minimum_contents_scale。這個最小縮放因子minimum_contents_scale設置為0.0625,也就是1/16。這個最小縮放因子會設置給成員變量pile_指向的PicturePile對象,這是通過調用PicturePile類的成員函數SetMinContentsScale實現的。

PicturePile類的成員函數SetMinContentsScale是從父類PicturePileBase繼承下來的,它的實現如下所示:

 

void PicturePileBase::SetMinContentsScale(float min_contents_scale) {
  ......

  // Picture contents are played back scaled. When the final contents scale is
  // less than 1 (i.e. low res), then multiple recorded pixels will be used
  // to raster one final pixel.  To avoid splitting a final pixel across
  // pictures (which would result in incorrect rasterization due to blending), a
  // buffer margin is added so that any picture can be snapped to integral
  // final pixels.
  //
  // For example, if a 1/4 contents scale is used, then that would be 3 buffer
  // pixels, since that's the minimum number of pixels to add so that resulting
  // content can be snapped to a four pixel aligned grid.
  int buffer_pixels = static_cast(ceil(1 / min_contents_scale) - 1);
  buffer_pixels = std::max(0, buffer_pixels);
  SetBufferPixels(buffer_pixels);
  min_contents_scale_ = min_contents_scale;
}
這個函數定義在文件external/chromium_org/cc/resources/picture_pile_base.cc中。

 

PicturePileBase類的成員函數SetMinContentsScale會將參數min_contents_scale的值保存在成員變量min_contents_scale_中。同時,PicturePileBase類的成員函數SetMinContentsScale也會計算出分塊邊界長度buffer_pixels,然後調用另外一個成員函數SetBufferPixels將它設置給成員變量tiling_描述的TilingData對象,如下所示:

 

void PicturePileBase::SetBufferPixels(int new_buffer_pixels) {
  ......
  tiling_.SetBorderTexels(new_buffer_pixels);
}
這個函數定義在文件external/chromium_org/cc/resources/picture_pile_base.cc中。

 

PicturePileBase類的成員函數SetBufferPixels調用TilingData類的成員函數SetBorderTexels將參數new_buffer_pixels描述的分塊邊界設置給成員變量tiling_描述的TilingData對象,如下所示:

 

void TilingData::SetBorderTexels(int border_texels) {
  border_texels_ = border_texels;
  RecomputeNumTiles();
}

 

這個函數定義在文件external/chromium_org/cc/base/tiling_data.cc中。

TilingData類的成員函數SetBorderTexels先參數border_texels描述的分塊邊界長度保存在成員變量border_texels_中,接著再調用成員函數RecomputeNumTiles重新計算Layer在x軸和y軸方向的分塊個數。

現在我們知道了一個Layer的分塊大小和分塊邊界長度。如果再給出Layer的大小,那麼就可以計算出Layer在x軸和y軸方向的分塊個數了。從前面的分析可以知道,PictureLayer類的成員函數Update計算好一個Layer的大小之後,會將這個大小設置給其成員變量pile_指向的一個PicturePile對象。這是通過調用PicturePile類的成員函數SetTilingRect實現的。

PicturePile類的成員函數SetTilingRect是從父類PicturePileBase繼承下來的,它的實現如下所示:

 

void PicturePileBase::SetTilingRect(const gfx::Rect& new_tiling_rect) {
  ......

  tiling_.SetTilingRect(new_tiling_rect);

  ......
}
這個函數定義在文件external/chromium_org/cc/resources/picture_pile_base.cc中。

 

PicturePileBase類的成員函數SetTilingRect調用TilingData類的成員函數SetTilingRect將參數new_tiling_rect描述的Layer大小設置給成員變量tiling_描述的TilingData對象,如下所示:

 

void TilingData::SetTilingRect(const gfx::Rect& tiling_rect) {
  tiling_rect_ = tiling_rect;
  RecomputeNumTiles();
}
這個函數定義在文件external/chromium_org/cc/base/tiling_data.cc中。

 

TilingData類的成員函數SetTilingRect先參數tiling_rect描述的Layer大小保存在成員變量tiling_rect_中,接著再調用成員函數RecomputeNumTiles重新計算Layer在x軸和y軸方向的分塊個數。

現在我們不僅知道了一個Layer的分塊大小和分塊邊界長度,還知道了這個Layer的大小,於是就可以計算它在x軸和y軸方向上的分塊數了,如下所示:

 

void TilingData::RecomputeNumTiles() {
  num_tiles_x_ = ComputeNumTiles(
      max_texture_size_.width(), tiling_rect_.width(), border_texels_);
  num_tiles_y_ = ComputeNumTiles(
      max_texture_size_.height(), tiling_rect_.height(), border_texels_);
}
這個函數定義在文件external/chromium_org/cc/base/tiling_data.cc中。

 

計算分塊的個數,要同時考慮Layer大小、分塊大小和分塊邊界長度。有了這些數據,就可以調用函數ComputeNumTiles進行計算了,如下所示:

 

static int ComputeNumTiles(int max_texture_size,
                           int total_size,
                           int border_texels) {
  if (max_texture_size - 2 * border_texels <= 0)
    return total_size > 0 && max_texture_size >= total_size ? 1 : 0;

  int num_tiles = std::max(1,
                           1 + (total_size - 1 - 2 * border_texels) /
                           (max_texture_size - 2 * border_texels));
  
  return total_size > 0 ? num_tiles : 0;
}
這個函數定義在文件external/chromium_org/cc/base/tiling_data.cc中。

 

讀者可以參考圖2的示例分析函數ComputeNumTiles計算分塊個數的邏輯,這裡就不詳細分析了。

回到PictureLayer類的成員函數Update中,它計算好當前正在處理的Layer所占據的區域大小和可見區域大小之後,接下來就會調用成員變量pile_指向的PicturePile對象的成員函數UpdateAndExpandInvalidation做兩件事情:

1. 重新繪制位於Layer的Interest Rect內的分塊。

2. 使得Layer的當前重繪區域包含所有執行過重新繪制操作的分塊。

PicturePile對象的成員函數UpdateAndExpandInvalidation的實現比較復雜,我們分段閱讀。

PicturePile類的成員函數UpdateAndExpandInvalidation首先計算出一個Interest Rect,如下所示:

 

const int kPixelDistanceToRecord = 8000;

......

bool PicturePile::UpdateAndExpandInvalidation(
    ContentLayerClient* painter,
    Region* invalidation,
    SkColor background_color,
    bool contents_opaque,
    bool contents_fill_bounds_completely,
    const gfx::Rect& visible_layer_rect,
    int frame_number,
    Picture::RecordingMode recording_mode,
    RenderingStatsInstrumentation* stats_instrumentation) {
  ......

  gfx::Rect interest_rect = visible_layer_rect;
  interest_rect.Inset(
      -kPixelDistanceToRecord,
      -kPixelDistanceToRecord,
      -kPixelDistanceToRecord,
      -kPixelDistanceToRecord);
這個代碼段定義在文件external/chromium_org/cc/resources/picture_pile.cc中。

Interest Rect的面積大於可見區域(Viewport)的面積,但是小於整個Layer Rect的面積。Interest Rect、Visible Rect和Layer Rect的關系如圖5所示:

\

圖5Interest Rect、Visible Rect和Layer Rect的關系

在Interest Rect內的分塊,如果它們處於參數invalidation描述的重繪區域內,那麼就是需要進行重繪的。

從前面的調用過程可以知道,參數invalidation指向的是PictureLayer類的成員變量pile_invalidation_描述的重繪區域,也就是它描述的是Layer的當前重繪區域,PicturePile類的成員函數UpdateAndExpandInvalidation接下來將這個區域對齊到分塊邊界,如下所示:

 

  gfx::Rect interest_rect_over_tiles =
      tiling_.ExpandRectToTileBounds(interest_rect);

  Region invalidation_expanded_to_full_tiles;

  bool invalidated = false;
  for (Region::Iterator i(*invalidation); i.has_rect(); i.next()) {
    gfx::Rect invalid_rect = i.rect();
    
    // Split this inflated invalidation across tile boundaries and apply it
    // to all tiles that it touches.
    bool include_borders = true;
    for (TilingData::Iterator iter(&tiling_, invalid_rect, include_borders);
         iter;
         ++iter) {
      const PictureMapKey& key = iter.index();

      PictureMap::iterator picture_it = picture_map_.find(key);
      if (picture_it == picture_map_.end())
        continue;

      // Inform the grid cell that it has been invalidated in this frame.
      invalidated = picture_it->second.Invalidate(frame_number) || invalidated;
    }

    // Expand invalidation that is outside tiles that intersect the interest
    // rect. These tiles are no longer valid and should be considerered fully
    // invalid, so we can know to not keep around raster tiles that intersect
    // with these recording tiles.
    gfx::Rect invalid_rect_outside_interest_rect_tiles = invalid_rect;
    // TODO(danakj): We should have a Rect-subtract-Rect-to-2-rects operator
    // instead of using Rect::Subtract which gives you the bounding box of the
    // subtraction.
    invalid_rect_outside_interest_rect_tiles.Subtract(interest_rect_over_tiles);
    invalidation_expanded_to_full_tiles.Union(tiling_.ExpandRectToTileBounds(
        invalid_rect_outside_interest_rect_tiles));
  }

  invalidation->Union(invalidation_expanded_to_full_tiles);

 

這個代碼段定義在文件external/chromium_org/cc/resources/picture_pile.cc中。

這個代碼段首先將Interest Rect對齊到分塊邊界,得到新的Interest Rect保存在本地變量interest_rect_over_tiles中。

這個代碼段接下來遍歷Layer的當前重繪區域的每一個Rect。對於每一個Rect,又會遍歷它包含的每一個分塊,並且將這些分塊標記為可能需要重新繪制。PicturePile類有一個成員變量picture_map_,它是從父類PicturePileBase類繼承下來的,描述的是一個Layer上一次繪制的所有分塊。每一個分塊用一個PictureInfo對象描述,並且保存在PicturePileBase類的成員變量picture_map_描述的一個map中,保存的鍵值為分塊的索引號。

將一個分塊標記為需要重新繪制是通過調用與它相對應的一個PictureInfo對象的成員函數Invalidate實現的,如下所示:

 

bool PicturePileBase::PictureInfo::Invalidate(int frame_number) {
  ......

  bool did_invalidate = !!picture_;
  picture_ = NULL;
  return did_invalidate;
}
這個函數定義在文件external/chromium_org/cc/resources/picture_pile_base.cc中。

 

當一個PictureInfo對象的成員變量picture指向了一個Picture對象的時候,就說明與它對應的分塊經過了繪制。另一方面,當一個PictureInfo對象的成員變量picture等於NULL的時候,就說明與它對應的分塊還沒有進行繪制。因引,將一個PictureInfo對象的成員變量picture設置為NULL,就可以將它標記為可能需要重新繪制。

回到前面的代碼片段中,它除了將位於Layer重繪區域的分塊標記為可能需要重新繪制之外,還會做另外一件事情,也就是計算Layer當前重繪區域位於Interest Rect之外的那部分區域,並且會將這些區域對齊到分塊邊界。這些對齊後的區域保存在本地變量invalidation_expanded_to_full_tiles中。結束遍歷後,保存在本地變量invalidation_expanded_to_full_tiles中的區域會被合並到Layer重繪區域中去。這一步做的事情實際上就是將Layer當前重繪區域位於Interest Rect之外的那部分區域擴大至分塊邊界。

總結一下,這個代碼做了兩件事情:

1. 將位於Layer當前重繪區域中的、上次繪制過的分塊標記為可能需要重新繪制。

2. 將Layer當前重繪區域中位於Interest Rect之外的那部分區域擴大至分塊邊界。

PicturePile類的成員函數UpdateAndExpandInvalidation收集那些真正需要重新繪制的分塊,如下所示:

 

  // Make a list of all invalid tiles; we will attempt to
  // cluster these into multiple invalidation regions.
  std::vector invalid_tiles;
  bool include_borders = true;
  for (TilingData::Iterator it(&tiling_, interest_rect, include_borders); it;
       ++it) {
    const PictureMapKey& key = it.index();
    PictureInfo& info = picture_map_[key];

    ......
    int distance_to_visible =
        rect.ManhattanInternalDistance(visible_layer_rect);

    if (info.NeedsRecording(frame_number, distance_to_visible)) {
      gfx::Rect tile = tiling_.TileBounds(key.first, key.second);
      invalid_tiles.push_back(tile);
    } else if (!info.GetPicture()) {
      ......

      // If a tile in the interest rect is not recorded, the entire tile needs
      // to be considered invalid, so that we know not to keep around raster
      // tiles that intersect this recording tile.
      invalidation->Union(tiling_.TileBounds(it.index_x(), it.index_y()));
    }
  }
這個代碼段定義在文件external/chromium_org/cc/resources/picture_pile.cc中。

 

位於Interest Rect內的分塊在滿足以下條件時,就需要進行繪制:

1. 它被標記為可能需要重新繪制,也就是與它對應的一個PictureInfo對象的成員變量picture_的值等於NULL。這意味著這個分塊以前從來沒有被繪制過,或者以前被繪制過,但是現在由於處理Layer的當前重繪區域中,被要求進行重新繪制。

2. 它與Viewport的距離小於預設閥值,這個預設閥值為512。

這兩個條件可以通過調用PictureInfo類的成員函數NeedsRecording判斷是否滿足。如果一個分塊滿足上述兩個條件,那麼它就會保存在本地變量invalid_tiles描述的一個std::vector中。

如果一個分塊不能滿足繪制條件,並且是因為與它對應的一個PictureInfo對象的成員變量picture_的值等於NULL引起的,那麼就需要將它對齊分塊邊界後,記錄在Layer的當前重繪區域中。之所以要這樣做,是因為這些分塊以前可能被繪制過。被繪制過的分塊曾經被執行過光柵化操作,也就是CC模塊為它們分配過紋理資源。現在不需要這些分塊了,就要回收以前分配給它們的紋理資源。因此就需要將它們記錄在Layer的當前重繪區域中,以便以後可以執行資源回收操作。

如果一個分塊不能滿足繪制條件,並且是與它對應的一個PictureInfo對象的成員變量picture_的值不等於NULL,那麼就說明這個分塊位於Interest Rect內,並且不在Layer的當前重繪區域,以前它被繪制過。這類分塊就是屬於沒有發生變化的,並且以前也執行過繪制操作,於是就是復用上一次的繪制結果。

總結一下,這段代碼做了兩件事情:

1. 收集真正需要進行繪制的分塊。這些分塊一定是位於Interest Rect內,同時也可能位於Layer的當前重繪區域內。如果位於Layer的當前重繪區域內,有兩種情況。第一種是之前被繪制過,那麼現在就會被重新繪制。第二種是之前沒有繪制過,那麼現在就會進行繪制。如果不位於Layer的當前重繪區域內,那麼就說明之前沒有繪制過。

2. 將位於Interest Rect內,以前繪制過,但是現在不需要繪制的分塊記錄在Layer的當前重繪區域,以便後面可以回收為這些分塊分配的光柵化資源。

我們通過圖6說明到目前為止,PicturePile類的成員函數UpdateAndExpandInvalidation所完成的事情,如下所示:

\

圖6 繪制分塊收集和重繪區域邊界對齊

第一件事情是將參數invalidation描述的Layer當前重繪區域會被對齊到分塊邊界,也就是將圖6左邊的Invalid rect擴大到分塊邊界,得到圖6右邊所示的Invalid rect。

第二件事情收集需要繪制的分塊。圖6右邊顯示的Draw rect是一個大於Viewport小於Interest rect的區域,它裡面的區域就是需要繪制。但是並不是Draw rect內的所有分塊都是需要繪制的,因為有些之前已經繪制過了。只有那些之前沒有繪制過,或者之前繪制過但是現在位於Invalid rect的分塊才需要繪制。如果假設圖6區域1的分塊之前都是繪制過的,那麼就僅有與Invalid rect重疊的區域2的分塊才是需要重新的繪制的。

第三件事情是那些位於Layer當前重繪區域內的的分塊記錄起來,以便後面可以回收它們的資源,如圖6右邊區域2和區域3包含的分塊。但是區域2和區域3的分塊有點一區別,就是區域2的分塊是需要重新繪制的,也是因為它們要重新繪制,所以才回收舊的資源,後面會給它們分配新的資源,區域3的分塊不是需要重新繪制的,它們的資源回收後也不需要重新分配。

接下來我們分析PicturePile類的成員函數UpdateAndExpandInvalidation的最後一段代碼:

 

  std::vector record_rects;
  ClusterTiles(invalid_tiles, &record_rects);

  ......

  for (std::vector::iterator it = record_rects.begin();
       it != record_rects.end();
       it++) {
    gfx::Rect record_rect = *it;
    record_rect = PadRect(record_rect);

    int repeat_count = std::max(1, slow_down_raster_scale_factor_for_debug_);
    scoped_refptr picture;
    ......

    {
      ......
      for (int i = 0; i < repeat_count; i++) {
        ......
        picture = Picture::Create(record_rect,
                                  painter,
                                  tile_grid_info_,
                                  gather_pixel_refs,
                                  num_raster_threads,
                                  recording_mode);
        ......
      }
      ......
    }

    ......

    bool include_borders = true;
    for (TilingData::Iterator it(&tiling_, record_rect, include_borders); it;
         ++it) {
      const PictureMapKey& key = it.index();
      gfx::Rect tile = PaddedRect(key);
      if (record_rect.Contains(tile)) {
        PictureInfo& info = picture_map_[key];
        info.SetPicture(picture);
        ......
      }
    }
    ......
  }

  has_any_recordings_ = true;
  ......
  return true;
}
這個代碼段定義在文件external/chromium_org/cc/resources/picture_pile.cc中。

 

從前面的分析可以知道,保存在本地變量invalid_tiles描述的一個std::vector中的區域都是需要繪制的。這些區域有可能是相鄰的,因此這段代碼調用函數ClusterTiles將相鄰的區域合並起來形成一個大的區域。經過合並後,得到需要繪制的區域保存在另外一個本地變量record_rects描述的std::vector中。

這段代碼接下來就遍歷保存在本地變量record_rects描述的std::vector中的每一個區域,並且調用Picture類的成員函數Create為它創建一個Picture對象。在創建Picture對象的過程中,也會執行相應的繪制工作。

分析到這裡我們就可以知道,PicturePile類的成員函數UpdateAndExpandInvalidation將網頁分塊的內容繪制在一系列的Picture對象中。其中,一個Picture對象涵蓋多個分塊的內容。每一個分塊關聯的Picture對象都會記錄在與它對應的一個PicutreInfo對象中。這是通過調用PicutreInfo類的成員函數SetPicture實現的,如下所示:

 

void PicturePileBase::PictureInfo::SetPicture(scoped_refptr picture) {
  picture_ = picture;
}
這個函數定義在文件external/chromium_org/cc/resources/picture_pile_base.cc中。

 

接下來我們繼續分析Layer的一個區域的繪制過程,也就是Picture類的成員函數Create的實現,如下所示:

 

scoped_refptr Picture::Create(
    const gfx::Rect& layer_rect,
    ContentLayerClient* client,
    const SkTileGridFactory::TileGridInfo& tile_grid_info,
    bool gather_pixel_refs,
    int num_raster_threads,
    RecordingMode recording_mode) {
  scoped_refptr picture = make_scoped_refptr(new Picture(layer_rect));

  picture->Record(client, tile_grid_info, recording_mode);
  ......

  return picture;
}
這個函數定義在文件external/chromium_org/cc/resources/picture.cc中。

 

參數layer_rect描述的是繪制區域的位置和大小,Picture類的成員函數Create再以它為參數,創建一個Picture對象,然後調用這個Picture對象的成員函數Record對參數layer_rect描述的是區域進行繪制,如下所示:

 

void Picture::Record(ContentLayerClient* painter,
                     const SkTileGridFactory::TileGridInfo& tile_grid_info,
                     RecordingMode recording_mode) {
  ......

  scoped_ptr recording;
  ......

  skia::RefPtr canvas;
  canvas = skia::SharePtr(recorder.beginRecording(
      layer_rect_.width(), layer_rect_.height(), &factory));

  ......

  canvas->save();
  canvas->translate(SkFloatToScalar(-layer_rect_.x()),
                    SkFloatToScalar(-layer_rect_.y()));

  SkRect layer_skrect = SkRect::MakeXYWH(layer_rect_.x(),
                                         layer_rect_.y(),
                                         layer_rect_.width(),
                                         layer_rect_.height());
  canvas->clipRect(layer_skrect);

  gfx::RectF opaque_layer_rect;
  painter->PaintContents(
      canvas.get(), layer_rect_, &opaque_layer_rect, graphics_context_status);

  canvas->restore();
  picture_ = skia::AdoptRef(recorder.endRecording());

  ......
}
這個函數定義在文件external/chromium_org/cc/resources/picture.cc中。

Picture類的成員函數Record首先創建一個類型為SkCanvas的畫布,接著將這個畫布的裁剪區間設置為當前正在處理的Picture對象所描述的區域。

參數painter是從前面分析的PictureLayer類的成員函數Update傳遞進來的,來自於PictureLayer類的成員變量client_。從前面Chromium網頁Layer Tree創建過程分析一文可以知道,PictureLayer類的成員變量client_指向的是一個WebContentLayerImpl對象。Picture類的成員函數Record接下來就調用這個WebContentLayerImpl對象的成員函數PaintContents繪制它所描述的Layer在本地變量canvas描述的一個畫布上。

這裡有兩點需要注意:

1.本地變量canvas描述的畫布是設置為裁剪區間的,並且這個裁剪區間剛好就是當前正在處理的Picture對象所描述的區域,這意味著雖然我們在調用WebContentLayerImpl類的成員函數PaintContents繪制一個Layer的時候,最終得到只是這個Layer的指定區域的內容。

2.WebContentLayerImpl類的成員函數PaintContents是通過調用傳遞給它的畫布的API繪制Layer的內容的,也就是本地變量canvas描述的畫布。這是一個特殊的畫布,在調用它的API的時候,它只是記錄了所調用的API及其調用參數,並沒有真正執行繪制操作,也就是沒有將繪制命令轉化為像素。這一點對WebContentLayerImpl類的成員函數PaintContents是透明的,它只管調用畫布的API繪制自己想要的內容。

繪制完成之後,可以通過畫布關聯的SkRecording對象可以獲得一個SkPicture對象。這個SkPicture對象在內容就記錄了所有的繪制命令。它的作用就類似於Android應用程序UI硬件加速渲染中的Display List。關於Android應用程序UI硬件加速渲染中的Display List,可以參考前面Android應用程序UI硬件加速渲染的Display List渲染過程分析一文。

以後對CC Pending Layer Tree執行光柵化操作時,就需要真正執行記錄在上述SkPicture對象中的繪制命令,這個過程稱為Replay。後面我們分析CC Pending Layer Tree的光柵化過程時就會看到這一點。

接下來,我們繼續分析WebContentLayerImpl類的成員函數PaintContents的實現,以便了解CC Layer Tree的繪制過程,如下所示:

 

void WebContentLayerImpl::PaintContents(
    SkCanvas* canvas,
    const gfx::Rect& clip,
    gfx::RectF* opaque,
    ContentLayerClient::GraphicsContextStatus graphics_context_status) {
  ......

  blink::WebFloatRect web_opaque;
  client_->paintContents(
      canvas,
      clip,
      can_use_lcd_text_,
      web_opaque,
      graphics_context_status == ContentLayerClient::GRAPHICS_CONTEXT_ENABLED
          ? blink::WebContentLayerClient::GraphicsContextEnabled
          : blink::WebContentLayerClient::GraphicsContextDisabled);
  *opaque = web_opaque;
}
這個函數定義在文件external/chromium_org/content/renderer/compositor_bindings/web_content_layer_impl.cc中。

 

從前面Chromium網頁Layer Tree創建過程分析一文可以知道,WebContentLayerImpl類的成員變量client_指向WebKit中的一個OpaqueRectTrackingContentLayerDelegate對象,WebContentLayerImpl類的成員函數PaintContents調用這個OpaqueRectTrackingContentLayerDelegate對象的成員函數paintContents繪制當前正在處理的WebContentLayerImpl對象描述的Layer的內容。

OpaqueRectTrackingContentLayerDelegate類的成員函數paintContents的實現如下所示:

 

void OpaqueRectTrackingContentLayerDelegate::paintContents(
    SkCanvas* canvas, const WebRect& clip, bool canPaintLCDText, WebFloatRect& opaque,
    blink::WebContentLayerClient::GraphicsContextStatus contextStatus)
{
    ......

    GraphicsContext context(canvas,
        contextStatus == blink::WebContentLayerClient::GraphicsContextEnabled ? GraphicsContext::NothingDisabled : GraphicsContext::FullyDisabled);
    ......

    m_painter->paint(context, clip);

    ......
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/platform/graphics/OpaqueRectTrackingContentLayerDelegate.cpp中。

 

OpaqueRectTrackingContentLayerDelegate類的成員函數paintContents首先將參數canvas描述的畫布封裝在一個GraphicsContext對象中。這個GraphicsContext對象提供了一套繪圖API,這些API最終會將繪制命令轉發給參數canvas描述的畫布提供的繪圖API處理。

OpaqueRectTrackingContentLayerDelegate類的成員變量m_painter指向的是一個GraphicsLayer對象。從前面Chromium網頁Graphics Layer Tree創建過程分析一文可以知道,這個GraphicsLayer對象描述的是網頁的Graphics Layer Tree中的一個Layer。OpaqueRectTrackingContentLayerDelegate類的成員函數paintContents調用這個GraphicsLayer對象的成員函數paint就可以繪制它所描述的Layer的內容。

接下來我們繼續分析GraphicsLayer類的成員函數paint的實現,以便了解一個Graphics Layer的繪制過程,如下所示:

 

void GraphicsLayer::paint(GraphicsContext& context, const IntRect& clip)
{
    paintGraphicsLayerContents(context, clip);
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/platform/graphics/GraphicsLayer.cpp中。

 

GraphicsLayer類的成員函數paint調用另外一個成員函數paintGraphicsLayerContents繪制當前正在處理的Graphics Layer的內容,如下所示:

 

void GraphicsLayer::paintGraphicsLayerContents(GraphicsContext& context, const IntRect& clip)
{
    ......
    m_client->paintContents(this, context, m_paintingPhase, clip);
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/rendering/compositing/CompositedLayerMapping.cpp中。

 

GraphicsLayer類的成員變量m_client指向的是一個CompositedLayerMapping對象,GraphicsLayer類的成員函數paintGraphicsLayerContents調用這個CompositedLayerMapping對象的成員函數paintContents繪制當前正在處理的Graphics Layer的內容,如下所示:

 

void CompositedLayerMapping::paintContents(const GraphicsLayer* graphicsLayer, GraphicsContext& context, GraphicsLayerPaintingPhase paintingPhase, const IntRect& clip)
{
    ......

    if (graphicsLayer == m_graphicsLayer.get()
        || graphicsLayer == m_foregroundLayer.get()
        || graphicsLayer == m_backgroundLayer.get()
        || graphicsLayer == m_maskLayer.get()
        || graphicsLayer == m_childClippingMaskLayer.get()
        || graphicsLayer == m_scrollingContentsLayer.get()
        || graphicsLayer == m_scrollingBlockSelectionLayer.get()) {

        GraphicsLayerPaintInfo paintInfo;
        paintInfo.renderLayer = &m_owningLayer;
        paintInfo.compositedBounds = compositedBounds();
        paintInfo.offsetFromRenderer = graphicsLayer->offsetFromRenderer();
        paintInfo.paintingPhase = paintingPhase;
        paintInfo.isBackgroundLayer = (graphicsLayer == m_backgroundLayer);

        // We have to use the same root as for hit testing, because both methods can compute and cache clipRects.
        doPaintTask(paintInfo, &context, clip);
    } else if (graphicsLayer == m_squashingLayer.get()) {
        ASSERT(compositor()->layerSquashingEnabled());
        for (size_t i = 0; i < m_squashedLayers.size(); ++i)
            doPaintTask(m_squashedLayers[i], &context, clip);
    } else if (graphicsLayer == layerForHorizontalScrollbar()) {
        paintScrollbar(m_owningLayer.scrollableArea()->horizontalScrollbar(), context, clip);
    } else if (graphicsLayer == layerForVerticalScrollbar()) {
        paintScrollbar(m_owningLayer.scrollableArea()->verticalScrollbar(), context, clip);
    } else if (graphicsLayer == layerForScrollCorner()) {
        const IntRect& scrollCornerAndResizer = m_owningLayer.scrollableArea()->scrollCornerAndResizerRect();
        context.save();
        context.translate(-scrollCornerAndResizer.x(), -scrollCornerAndResizer.y());
        IntRect transformedClip = clip;
        transformedClip.moveBy(scrollCornerAndResizer.location());
        m_owningLayer.scrollableArea()->paintScrollCorner(&context, IntPoint(), transformedClip);
        m_owningLayer.scrollableArea()->paintResizer(&context, IntPoint(), transformedClip);
        context.restore();
    }

    ......
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/rendering/compositing/CompositedLayerMapping.cpp中。

 

從前面Chromium網頁Graphics Layer Tree創建過程分析一文可以知道,網頁的Render Layer Tree中的一個Render Layer實際上對應的是一個Graphics Layer子樹。這個Graphics Layer子樹由一個CompositedLayerMapping對象管理。

參數graphicsLayer指向的就是上述Graphics Layer子樹中的某一個Graphics Layer。不同的Graphics Layer有不同的繪制方式。例如,對於供述網頁主要內容的Main GraphicsLayer,CompositedLayerMapping類的成員函數paintContents是通過調用另外一個成員函數doPaintTask繪制它的內容的。

接下來我們就繼續分析CompositedLayerMapping類的成員函數doPaintTask的實現,如下所示:

 

void CompositedLayerMapping::doPaintTask(GraphicsLayerPaintInfo& paintInfo, GraphicsContext* context,
    const IntRect& clip) // In the coords of rootLayer.
{
    ......

    if (paintInfo.renderLayer->compositingState() != PaintsIntoGroupedBacking) {
        ......
        LayerPaintingInfo paintingInfo(paintInfo.renderLayer, dirtyRect, PaintBehaviorNormal, paintInfo.renderLayer->subpixelAccumulation());
        paintInfo.renderLayer->paintLayerContents(context, paintingInfo, paintFlags);
        ......
    } else {
        ......
        LayerPaintingInfo paintingInfo(paintInfo.renderLayer, dirtyRect, PaintBehaviorNormal, paintInfo.renderLayer->subpixelAccumulation());
        ......
        paintInfo.renderLayer->paintLayer(context, paintingInfo, paintFlags);
        ......
    }

    .....
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/rendering/compositing/CompositedLayerMapping.cpp中。

 

參數paintInfo描述的GraphicsLayerPaintInfo對象的成員變量renderLayer描述的是當前要繪制的Render Layer。當一個Render Layer擁有自己的Graphics Layer時,它會繪制自己的Backing Store中,否則的話,它與其它的Render Layer一起繪制在別的Backing Store中。

我們假設當前要繪制的Render Layer擁有自己的Graphics Layer,這時候調用它的成員函數compositingState得到的返回值不等於PaintsIntoGroupedBacking,因此接下來CompositedLayerMapping類的成員函數doPaintTask就會調用RenderLayer類的成員函數paintLayerContents對它進行繪制。

RenderLayer類的成員函數paintLayerContents的實現如下所示:

 

void RenderLayer::paintLayerContents(GraphicsContext* context, const LayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags)
{
    ......

    LayerFragments layerFragments;
    if (shouldPaintContent || shouldPaintOutline || isPaintingOverlayScrollbars) {
        // Collect the fragments. This will compute the clip rectangles and paint offsets for each layer fragment, as well as whether or not the content of each
        // fragment should paint.
        collectFragments(layerFragments, localPaintingInfo.rootLayer, localPaintingInfo.paintDirtyRect,
            (paintFlags & PaintLayerTemporaryClipRects) ? TemporaryClipRects : PaintingClipRects, IgnoreOverlayScrollbarSize,
            shouldRespectOverflowClip(paintFlags, renderer()), &offsetFromRoot, localPaintingInfo.subPixelAccumulation);
        updatePaintingInfoForFragments(layerFragments, localPaintingInfo, paintFlags, shouldPaintContent, &offsetFromRoot);
    }

    if (shouldPaintBackground) {
        paintBackgroundForFragments(layerFragments, context, transparencyLayerContext, paintingInfo.paintDirtyRect, haveTransparency,
            localPaintingInfo, paintBehavior, paintingRootForRenderer, paintFlags);
    }

    if (shouldPaintNegZOrderList)
        paintChildren(NegativeZOrderChildren, context, localPaintingInfo, paintFlags);

    if (shouldPaintOwnContents) {
        paintForegroundForFragments(layerFragments, context, transparencyLayerContext, paintingInfo.paintDirtyRect, haveTransparency,
            localPaintingInfo, paintBehavior, paintingRootForRenderer, selectionOnly, forceBlackText, paintFlags);
    }

    if (shouldPaintOutline)
        paintOutlineForFragments(layerFragments, context, localPaintingInfo, paintBehavior, paintingRootForRenderer, paintFlags);

    if (shouldPaintNormalFlowAndPosZOrderLists)
        paintChildren(NormalFlowChildren | PositiveZOrderChildren, context, localPaintingInfo, paintFlags);

    if (shouldPaintOverlayScrollbars)
        paintOverflowControlsForFragments(layerFragments, context, localPaintingInfo, paintFlags);

    ......

    if (shouldPaintMask)
        paintMaskForFragments(layerFragments, context, localPaintingInfo, paintingRootForRenderer, paintFlags);
 
    ......
}

這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderLayer.cpp中。

RenderLayer類的成員函數paintLayerContents首先調用成員函數collectFragments當前正在處理的Render Layer的Fragment。Fragment是CSS3定義的一個規范:CSS3 Fragmentation,WebKit的實現可以參考這篇文章:CSS Fragmentation In WebKit。當一個Render Layer對應的Render Object設置了Fragment相關的屬性之後,這個Render Layer就會以Fragment的方式顯示,也就是它由一系列的Fragment組成。

收集了到了要繪制的Fragment之後,RenderLayer類的成員函數paintLayerContents大概就按照以下順序繪制自己的內容:

1. Background

2. Z-index為負的子Render Layer

3. Foreground

4. Outline

5. Z-index為0和正數的子Render Layer

6. Scollbar

7. Mask

除了子Render Layer的內容是通過調用成員函數paintChildren進行繪制的,其余的內容是通過調用成員函數paintXXXForFragments進行繪制的,也就是說,Render Layer自身的內容是通過調用員函數paintXXXForFragments進行繪制的。

我們以Render Layer的Background的繪制為例,分析Render Layer自有內容的繪制過程。Render Layer的Background是通過調用RenderLayer類的成員函數paintBackgroundForFragments繪制的,它的實現如下所示:

 

void RenderLayer::paintBackgroundForFragments(const LayerFragments& layerFragments, GraphicsContext* context, GraphicsContext* transparencyLayerContext,
    const LayoutRect& transparencyPaintDirtyRect, bool haveTransparency, const LayerPaintingInfo& localPaintingInfo, PaintBehavior paintBehavior,
    RenderObject* paintingRootForRenderer, PaintLayerFlags paintFlags)
{
    for (size_t i = 0; i < layerFragments.size(); ++i) {
        const LayerFragment& fragment = layerFragments.at(i);
        ......

        // Paint the background.
        // FIXME: Eventually we will collect the region from the fragment itself instead of just from the paint info.
        PaintInfo paintInfo(context, pixelSnappedIntRect(fragment.backgroundRect.rect()), PaintPhaseBlockBackground, paintBehavior, paintingRootForRenderer, 0, 0, localPaintingInfo.rootLayer->renderer());
        renderer()->paint(paintInfo, toPoint(fragment.layerBounds.location() - renderBoxLocation() + subPixelAccumulationIfNeeded(localPaintingInfo.subPixelAccumulation, compositingState())));

        ......
    }
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderLayer.cpp中。

 

調用RenderLayer類的成員函數renderer可以獲得一個Render Object。這個Render Object是繪制在當前正在處理的Render Layer上的。這個Render Object對應於Render Object Tree的一個節點,它代表的是網頁中的一個元素,並且知道如何繪制這個元素。

RenderLayer類的成員函數paintBackgroundForFragments遍歷當前正在處理的Render Layer的每一個Fragment。對於每一個Fragment,都調用與當前正在處理的Render Layer對應的一個Render Object的成員函數paint進行繪制。

我們假設當前正在處理的Render Layer對應的一個Render Object是一個Render Block,那麼接下來RenderLayer類的成員函數paintBackgroundForFragments就會調用RenderBlock類的成員函數paint繪制當前正在處理的Render Layer的Fragment,如下所示:

 

void RenderBlock::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
    ......

    {
        ......

        paintObject(paintInfo, adjustedPaintOffset);
    }

    ......
}

這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderBlock.cpp中。

RenderBlock類的成員函數paint主要是調用另外一個成員函數paintObject繪制當前正在處理的Render Block的內容,如下所示:

 

void RenderBlock::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
    PaintPhase paintPhase = paintInfo.phase;

    ......

    // 1. paint background, borders etc
    ......

    // 2. paint contents
    if (paintPhase != PaintPhaseSelfOutline) {
        if (hasColumns())
            paintColumnContents(paintInfo, scrolledOffset);
        else
            paintContents(paintInfo, scrolledOffset);
    }

    // 3. paint selection
    ......

    // 4. paint floats.
    ......

    // 5. paint outline.
    ......

    // 6. paint continuation outlines.
    ......

    // 7. paint caret.
    ......
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderBlock.cpp中。

 

Render Block的內容是分階段繪制的。我們假設現在是繪制內容階段,並且假設當前正在繪制的Render Block沒有設置column屬性。在這種情況下,RenderBlock類的成員函數paint調用另外一個成員函數paintContents繪制它的內容,如下所示:

 

void RenderBlock::paintContents(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
    ......

    if (childrenInline())
        m_lineBoxes.paint(this, paintInfo, paintOffset);
    else {
        PaintPhase newPhase = (paintInfo.phase == PaintPhaseChildOutlines) ? PaintPhaseOutline : paintInfo.phase;
        newPhase = (newPhase == PaintPhaseChildBlockBackgrounds) ? PaintPhaseChildBlockBackground : newPhase;

        // We don't paint our own background, but we do let the kids paint their backgrounds.
        PaintInfo paintInfoForChild(paintInfo);
        paintInfoForChild.phase = newPhase;
        paintInfoForChild.updatePaintingRootForChildren(this);
        paintChildren(paintInfoForChild, paintOffset);
    }
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderBlock.cpp中。

 

Render Block的內容來自於它的子Render Object。我們假設當前正在繪制的Render Block的子Render Object不是以inline方式顯示。在這種情況下,RenderBlock類的成員函數paintContents調用另外一個成員函數paintChildren繪制它的子Render Object的內容,如下所示:

 

void RenderBlock::paintChildren(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
    for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox())
        paintChild(child, paintInfo, paintOffset);
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderBlock.cpp中。

 

RenderBlock類的成員函數paintChildren遍歷它的所有子Render Object,並且分別調用成員函數paintChild繪制這些子Render Object,如下所示:

 

void RenderBlock::paintChild(RenderBox* child, PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
    LayoutPoint childPoint = flipForWritingModeForChild(child, paintOffset);
    if (!child->hasSelfPaintingLayer() && !child->isFloating())
        child->paint(paintInfo, childPoint);
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderBlock.cpp中。

 

當參數child描述的子Render Object不擁有自己的Render Layer,並且它也沒有設置float屬性的時候,RenderBlock類的成員函數paintChild就會調用它的成員函數paint進行繪制。假設這個子Render Object也是一個Render Block,那麼RenderBlock類的成員函數paintChild就會調用前面分析過的成員函數paint對這個子Render Object進行繪制。這是一個遞歸調用過程,直到中間某個Render Object的所有子Render Object都具有自己的Render Layer為止,或者都設置了float屬性。

這樣,一個Render Layer的內容就繪制完成了。回到前面分析的RenderLayer類的成員函數paintLayerContents中,它除了繪制自已的內容,也會繪制它的子Render Layer的內容。這是通過調用RenderLayer類的成員函數paintChildren完成的,如下所示:

 

void RenderLayer::paintChildren(unsigned childrenToVisit, GraphicsContext* context, const LayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags)
{
    ......

    RenderLayerStackingNodeIterator iterator(*m_stackingNode, childrenToVisit);
    while (RenderLayerStackingNode* child = iterator.next()) {
        RenderLayer* childLayer = child->layer();
        ......

        if (!childLayer->isPaginated())
            childLayer->paintLayer(context, paintingInfo, paintFlags);
        else
            paintPaginatedChildLayer(childLayer, context, paintingInfo, paintFlags);
    }
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderLayer.cpp中。

 

RenderLayer類的成員函數paintChildren按照我們在前面Chromium網頁Graphics Layer Tree創建過程分析一文提到的Stacking Context順序繪制當前正在處理的Render Layer的所有子Render Layer。對於設置了分頁的子Render Layer,RenderLayer類的成員函數paintChildren調用另外一個成員函數paintPaginatedChildLayer繪制它們的內容;而對於沒有設置分頁的子Render Layer,RenderLayer類的成員函數paintChildren直接它們的成員函數paintLayer對它們進行繪制。

接下來我們只關注沒有設置分頁的子Render Layer的繪制過程,也就是RenderLayer類的成員函數paintLayer的實現,如下所示:

 

void RenderLayer::paintLayer(GraphicsContext* context, const LayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags)
{
    ......

    // Non self-painting leaf layers don't need to be painted as their renderer() should properly paint itself.
    if (!isSelfPaintingLayer() && !hasSelfPaintingLayerDescendant())
        return;
    ......

    paintLayerContentsAndReflection(context, paintingInfo, paintFlags);
}

 

這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderLayer.cpp中。

從前面的調用過程可以知道,參數context描述的是當前正在處理的Render Layer的父Render Layer的繪圖上下文,因此只有在當前正在處理的Render Layer不具有自己的Graphics Layer的時候,它的內容才會繪制在父Render Layer擁有的Graphics Layer上,也就是參數context描述的繪圖上下文上。

我們假設當前正在處理的Render Layer沒有自己的Graphics Layer,也就是它要繪制在父Render Layer的繪圖上下文上。在這種情況下,RenderLayer類的成員函數paintLayer就會調用另外一個成員函數paintLayerContentsAndReflection繪制它的內容,如下所示:

 

void RenderLayer::paintLayerContentsAndReflection(GraphicsContext* context, const LayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags)
{
    ......

    PaintLayerFlags localPaintFlags = paintFlags & ~(PaintLayerAppliedTransform);

    // Paint the reflection first if we have one.
    if (m_reflectionInfo)
        m_reflectionInfo->paint(context, paintingInfo, localPaintFlags | PaintLayerPaintingReflection);

    localPaintFlags |= PaintLayerPaintingCompositingAllPhases;
    paintLayerContents(context, paintingInfo, localPaintFlags);
}
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderLayer.cpp中。

 

如果當前正在處理的Render Layer設置了box-reflect屬性,那麼RenderLayer類的成員函數paintLayerContentsAndReflection就會先繪制它的倒影,接著再調用前面分析過的成員函數paintLayerContents繪制它自己的內容。這是一個遞歸的繪制過程,直到當前正在處理的Render Layer的所有子Render Layer都有自己的繪圖上下文為止。

這一步執行完成之後,回到前面分析的LayerTreeHost類的成員函數UpdateLayers中,這時候網頁的CC Layer Tree的內容就繪制完成了,不過僅僅是將繪制命令記錄在一系列的Picture對象中。

LayerTreeHost類的成員函數UpdateLayers執行完成後,返回到ThreadProxy類的成員函數BeginMainFrame中,這時候它就會向Compositor線程的消息隊列發送一個Task。這個Task綁定了ThreadProxy類的成員函數StartCommitOnImplThread。這意味著接下來ThreadProxy類的成員函數StartCommitOnImplThread會在Compositor線程中執行。注意,在Compositor線程執行ThreadProxy類的成員函數StartCommitOnImplThread期間,Main線程通過一個Completion Event進入等待狀態。

ThreadProxy類的成員函數StartCommitOnImplThread的實現如下所示:

 

void ThreadProxy::StartCommitOnImplThread(CompletionEvent* completion,
                                          ResourceUpdateQueue* raw_queue) {
  ......

  impl().scheduler->NotifyBeginMainFrameStarted();

  scoped_ptr queue(raw_queue);
  ......

  impl().commit_completion_event = completion;
  impl().current_resource_update_controller = ResourceUpdateController::Create(
      this,
      Proxy::ImplThreadTaskRunner(),
      queue.Pass(),
      impl().layer_tree_host_impl->resource_provider());
  impl().current_resource_update_controller->PerformMoreUpdates(
      impl().scheduler->AnticipatedDrawTime());
}
這個函數定義在文件external/chromium_org/cc/trees/thread_proxy.cc中。

 

在前面Chromium網頁Layer Tree創建過程分析一文中,我們假設Render進程啟用了Impl Side Painting特性。在這種情況下,參數raw_queue描述的一個Resoure Update Queue為空。

如果Render進程沒有啟用Impl Side Painting特性,並且網頁包含了img標簽,那麼CC模塊就會為分別為這些img標簽創建一個Image Layer,並且將要顯示的圖片設置給這些Image Lyer。這些Image Layer在繪制的時候,同樣會對要顯示的圖片進行分塊。得到的每一個圖片分塊就是一個Resource Update。這些Resource Update就保存在參數raw_queue描述的Resoure Update Queue中。

Resoure Update描述的圖片分塊數據是通過調用一個ResourceUpdateController對象的成員函數PerformMoreUpdates上傳到GPU去當紋理渲染的。這個ResourceUpdateController對象可以通過調用ResourceUpdateController類的靜態成員函數Create創建。

即使參數raw_queue描述的Resoure Update Queue為空,ThreadProxy類的成員函數StartCommitOnImplThread也會創建一個ResourceUpdateController對象,並且調用它的成員函數PerformMoreUpdates。但是這個ResourceUpdateController對象的成員函數PerformMoreUpdates在執行的時候,會判斷它的Resoure Update Queue是否為空。如果為空,它就會跳過上述的GPU紋理數據上傳操作。

ThreadProxy類的成員函數StartCommitOnImplThread在調用ResourceUpdateController類的成員函數PerformMoreUpdates之前,還會做兩件事情。第一件事情就調用Scheduler類的成員函數NotifyBeginMainFrameStarted通知調度器修改狀態。第二件事情是將參數completion描述的一個Completion Event保存在內部的一個CompositorThreadOnly對象的成員變量commit_completion_event。後面Compositor線程要通過這個Completion Event喚醒目前正在睡眠的Main線程。

接下來我們先分析Scheduler類的成員函數NotifyBeginMainFrameStarted的實現,以便了解調度器的狀態遷移過程,如下所示:

 

void Scheduler::NotifyBeginMainFrameStarted() {
  ......
  state_machine_.NotifyBeginMainFrameStarted();
}
這個函數定義在文件external/chromium_org/cc/scheduler/scheduler.cc中。

 

Scheduler類的成員函數NotifyBeginMainFrameStarted調用SchedulerStateMachine類的成員函數NotifyBeginMainFrameStarted修改狀態機的CommitState狀態,如下所示:

 

void SchedulerStateMachine::NotifyBeginMainFrameStarted() {
  DCHECK_EQ(commit_state_, COMMIT_STATE_BEGIN_MAIN_FRAME_SENT);
  commit_state_ = COMMIT_STATE_BEGIN_MAIN_FRAME_STARTED;
}
這個函數定義在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。

 

從前面的分析可以知道,Main線程在繪制CC Layer Tree的時候,狀態機的CommitState狀態等於COMMIT_STATE_BEGIN_MAIN_FRAME_SENT。現在CC Layer Tree已經繪制完成了,CommitState狀態就會從COMMIT_STATE_BEGIN_MAIN_FRAME_SENT變遷為COMMIT_STATE_BEGIN_MAIN_FRAME_STARTED。表示Compositor線程正在上傳網頁中的圖片數據到GPU去。

回到hreadProxy類的成員函數StartCommitOnImplThread中,它接下來會調用ResourceUpdateController類的成員函數PerformMoreUpdates上傳網頁中的圖片數據到GPU去,如下所示:

 

void ResourceUpdateController::PerformMoreUpdates(
    base::TimeTicks time_limit) {
  time_limit_ = time_limit;

  ......

  // Post a 0-delay task when no updates were left. When it runs,
  // ReadyToFinalizeTextureUpdates() will be called.
  if (!UpdateMoreTexturesIfEnoughTimeRemaining()) {
    task_posted_ = true;
    task_runner_->PostTask(
        FROM_HERE,
        base::Bind(&ResourceUpdateController::OnTimerFired,
                   weak_factory_.GetWeakPtr()));
  }

  ......
}
這個函數定義在文件external/chromium_org/cc/resources/resource_update_controller.cc中。

 

參數time_limit規定了上傳圖片數據到GPU最多可以使用的時間。一旦超出這個時間,ResourceUpdateController類會通過成員變量task_runner描述的一個SingleThreadTaskRunner向Compositor線程的消息隊列發送一個Task。這個Task綁定了ResourceUpdateController類的成員函數OnTimerFired。

此外,如果沒有圖片數據需要上傳到GPU去,也就是前面提到的Resource Update Queue為空。這時候調用ResourceUpdateController類的成員函數UpdateMoreTexturesIfEnoughTimeRemaining得到的返回值就為false。在這種情況下,ResourceUpdateController類的成員函數PerformMoreUpdates會直接向Compositor線程的消息隊列發送上述的Task。

在我們這個情景中,Resource Update Queue為空。因此,ResourceUpdateController類的成員函數PerformMoreUpdates會馬上向Compositor線程的消息隊列發送上述的Task。這意味著接下來ResourceUpdateController類的成員函數OnTimerFired會在Compositor線程中執行。

ResourceUpdateController類的成員函數OnTimerFired會在Compositor的實現如下所示:

 

void ResourceUpdateController::OnTimerFired() {
  ......
  if (!UpdateMoreTexturesIfEnoughTimeRemaining()) {
    .....
    client_->ReadyToFinalizeTextureUpdates();
  }
}
這個函數定義在文件external/chromium_org/cc/resources/resource_update_controller.cc中。

 

前面提到,Resource Update Queue為空時,調用ResourceUpdateController類的成員函數UpdateMoreTexturesIfEnoughTimeRemaining得到的返回值就為false。因此,ResourceUpdateController類的成員函數OnTimerFired會直接調用成員變量client_指向的一個ThreadProxy對象的成同函數ReadyToFinalizeTextureUpdates,用來通知它現在可以將剛剛繪制好的CC Layer Tree同步到一個新的CC Pending Layer Tree去。

ThreadProxy類的成同函數ReadyToFinalizeTextureUpdates的實現如下所示:

 

void ThreadProxy::ReadyToFinalizeTextureUpdates() {
  DCHECK(IsImplThread());
  impl().scheduler->NotifyReadyToCommit();
}
這個函數定義在文件external/chromium_org/cc/trees/thread_proxy.cc中。

 

ThreadProxy類的成同函數ReadyToFinalizeTextureUpdates調用Scheduler類的成員函數NotifyReadyToCommit修改狀態機的狀態,如下所示:

 

void Scheduler::NotifyReadyToCommit() {
  ......
  state_machine_.NotifyReadyToCommit();
  ProcessScheduledActions();
}
這個函數定義在文件external/chromium_org/cc/scheduler/scheduler.cc中。

 

Scheduler類的成員函數NotifyReadyToCommit首先調用SchedulerStateMachine類的成員函數NotifyReadyToCommit修改狀態機的CommitState狀態,如下所示:

 

void SchedulerStateMachine::NotifyReadyToCommit() {
  DCHECK(commit_state_ == COMMIT_STATE_BEGIN_MAIN_FRAME_STARTED) << *AsValue();
  commit_state_ = COMMIT_STATE_READY_TO_COMMIT;
}
這個函數定義在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。

 

SchedulerStateMachine類的成員函數NotifyReadyToCommit將狀態機的CommitState狀態從COMMIT_STATE_BEGIN_MAIN_FRAME_STARTED遷移為COMMIT_STATE_READY_TO_COMMIT。

回到Scheduler類的成員函數NotifyReadyToCommit中,它將狀態機的CommitState狀態從遷移為COMMIT_STATE_READY_TO_COMMIT後,再調用成員函數ProcessScheduledActions時,就會觸發調度器執行圖1所示的第3個操作ACTION_COMMIT,也就是將剛剛繪制好的CC Layer Tree同步到一個新的CC Pending Layer Tree中去。

至此,我們就分析完成網頁的CC Layer Tree的繪制過程了。這裡我們做一個小結:

1. 在繪制CC Layer Tree之前,CC模塊會先計算CC Layer Tree的動畫和布局。

2. 在繪制CC Layer Tree之後,如果網頁包含有圖片資源,並且Render進程沒有啟用Impl Side Painting特性,那麼CC模塊就會將對這些圖片進行分塊,分塊得到的數據將會上傳到GPU去以紋理方式渲染。

3. CC Layer Tree是以Layer為單位繪制CC Layer Tree的。每一個Layer又被劃分為若干個分塊進行繪制。每一個分塊在繪制的時候,僅僅是繪制命令記錄在了一個Picture對象。

上述三個操作執行完成之後,調度器就會通知Compositor線程將剛剛繪制好的CC Layer Tree同步到一個新的CC Pending Layer Tree中去。這個同步過程我們在接下來一篇文章中就詳細進行分析。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved