天天看點

Chromium網頁Layer Tree繪制過程分析

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

老羅的新浪微網誌:http://weibo.com/shengyangluo,歡迎關注!

《Android系統源代碼情景分析》一書正在進擊的程式員網(http://0xcc0xcd.com)中連載,點選進入!

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

Chromium網頁Layer Tree繪制過程分析

圖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來說明這個問題,以及針對這個問題的解決方案,如下所示:

Chromium網頁Layer Tree繪制過程分析

圖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操作,也就是對CC Layer Tree進行繪制。

       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操作,以抵消剛剛完成的swapBuffers操作。送出新的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<BeginMainFrameAndCommitState> 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<BeginMainFrameAndCommitState> 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<resourceupdatequeue> 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将狀态機的CommitState狀态恢複為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_COMMIIT操作,這時候SchedulerStateMachine類的成員函數BeginMainFrameAborted就會調用另外一個成員函數UpdateStateOnCommit修改狀态機的狀态,并且傳遞參數true給它。

       注意,狀态機在排程執行ACTION_COMMIIT操作的時候,也會調用SchedulerStateMachine類的成員函數UpdateStateOnCommit修改狀态機的狀态,不過這時候傳遞的參數為false。這一點我們在接下來一篇文章分析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,表示狀态機正在等待渲染新激活的CC Active Layer 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<int, LayerAnimationController*> 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> Animation::create(Element* target, PassRefPtrWillBeRawPtr<AnimationEffect> effect, const Timing& timing, Priority priority, PassOwnPtr<EventDelegate> 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<int>& startedAnimationIds)
{
    ......

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

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

    Vector<OwnPtr<blink::WebAnimation> > 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<OwnPtr<blink::WebAnimation> >& 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<blink::WebAnimationCurve> 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<blink::WebAnimation> 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實際上對應的是一個Sub Graphics Layer Tree。這個Sub Graphics Layer Tree就是通過一個Composited Layer Mapping描述的。屬于一個Render Layer的動畫,需要映射到它對應的Sub Graphics Layer Tree中的Main Grapics Layer去執行。這個Main Grapics Layer可以通過調用CompositedLayerMapping類的成員函數mainGraphicsLayer獲得了。

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

bool GraphicsLayer::addAnimation(PassOwnPtr<WebAnimation> popAnimation)
{
    OwnPtr<WebAnimation> 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<WebAnimationImpl*>(animation)->PassAnimation());
  ......
  return result;
}
           

       這個函數定義在檔案external/chromium_org/content/renderer/compositor_bindings/web_layer_impl.cc中。

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

bool Layer::AddAnimation(scoped_ptr <Animation> 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> 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/WebViewImpl.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();
}
           

      這個函數定義在檔案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();  
  
        ......  
    }  
  
    ......  
}  
           

       這個函數定義在檔案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 Object Tree的根節點。

       得到了描述Render Object 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所示:

Chromium網頁Layer Tree繪制過程分析

圖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<Layer> globals;
  DataForRecursion<Layer> data_for_recursion;
  ProcessCalcDrawPropsInputs(*inputs, &globals, &data_for_recursion);

  PreCalculateMetaInformationRecursiveData recursive_data;
  PreCalculateMetaInformation(inputs->root_layer, &recursive_data);
  std::vector<AccumulatedSurfaceState<Layer> > accumulated_surface_state;
  CalculateDrawPropertiesInternal<Layer>(
      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 <typename LayerType>
static void CalculateDrawPropertiesInternal(
    LayerType* layer,
    const SubtreeGlobals<LayerType>& globals,
    const DataForRecursion<LayerType>& data_from_ancestor,
    typename LayerType::RenderSurfaceListType* render_surface_layer_list,
    typename LayerType::LayerListType* layer_list,
    std::vector<AccumulatedSurfaceState<LayerType> >* 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<LayerType>(
        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建立完成後的結構,如下所示:

Chromium網頁Layer 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 3的Layer List儲存了Layer 3、Layer 4和Layer 5。Render Surface 5的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<Layer> 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<Layer>* 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<PicturePileBase> {
 ......

 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<int>(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所示:

Chromium網頁Layer Tree繪制過程分析

圖5 Interest 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<gfx::Rect> 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所完成的事情,如下所示:

Chromium網頁Layer Tree繪制過程分析

圖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<gfx::Rect> record_rects;
  ClusterTiles(invalid_tiles, &record_rects);

  ......

  for (std::vector<gfx::Rect>::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> 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_ = picture;
}
           

       這個函數定義在檔案external/chromium_org/cc/resources/picture_pile_base.cc中。

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

scoped_refptr<Picture> 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> 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<EXPERIMENTAL::SkRecording> recording;
  
   
  ......

  skia::RefPtr<SkCanvas> 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 Graphics Layer,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<ResourceUpdateQueue> 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 Layer。這些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去。

       回到ThreadProxy類的成員函數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中去。這個同步過程我們在接下來一篇文章中就詳細進行分析。敬請關注!更多的資訊也可以關注老羅的新浪微網誌:http://weibo.com/shengyangluo。