天天看點

Chromium硬體加速渲染的OpenGL上下文排程過程分析

       Chromium的每一個WebGL端、Render端和Browser端執行個體在GPU程序中都有一個OpenGL上下文。這些OpenGL上下文運作在相同線程中,是以同一時刻隻有一個OpenGL上下文處于運作狀态。這就引發出一個OpenGL上下文排程問題。此外,事情有輕重緩急,OpenGL上下文也有優先級高低之分,優先級高的要保證它的運作時間。本文接下來就分析GPU程序排程運作OpenGL上下文的過程。

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

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

       在前面Chromium硬體加速渲染機制基礎知識簡要介紹和學習計劃一文中提到,GPU程序中的所有OpenGL上下文不僅運作在相同線程中,即運作在GPU程序的GPU線程中,它們還處于同一個共享組中,如圖1所示:

Chromium硬體加速渲染的OpenGL上下文排程過程分析

圖1 OpenGL上下文

       在Chromium中,每一個OpenGL上下文使用一個GLContextEGL對象描述。每一個OpenGL上下文都關聯有一個繪圖表面。對于WebGL端和Render端的OpenGL上下文來說,它關聯的繪圖表面是一個離屏表面。這個離屏表面一般就是用一個Pbuffer描述。在Android平台上,Browser端的OpenGL上下文關聯的繪圖表面是一個SurfaceView。

       當一個OpenGL上下文被排程時,它以及它關聯的繪圖表面就會通過調用EGL函數eglMakeCurrent設定為GPU線程目前使用的OpenGL上下文和繪圖表面。

       從前面Chromium硬體加速渲染的OpenGL上下文建立過程分析一文又可以知道,Chromium為WebGL端、Render端和Browser端建立的OpenGL上下文可能是虛拟的,如圖2所示:

Chromium硬體加速渲染的OpenGL上下文排程過程分析

圖2 虛拟OpenGL上下文

       在Chromium中,每一個虛拟OpenGL上下文都使用一個GLContextVirtual對象描述。每一個虛拟OpenGL上下文都對應有一個真實OpenGL上下文,即一個GLContextEGL對象,并且所有的虛拟OpenGL上下文對應的真實OpenGL上下文都是相同的。虛拟OpenGL上下文也像真實OpenGL上下文一樣,關聯有繪圖表面。對于WebGL端和Render端的虛拟OpenGL上下文來說,它關聯的繪圖表面也是一個使用Pbuffer描述的離屏表面。在Android平台上,Browser端的OpenGL上下文關聯的繪圖表面同樣也是一個SurfaceView。

       當一個虛拟OpenGL上下文被排程時,它對應的真實OpenGL上下文以及它關聯的繪圖表面就會通過調用EGL函數eglMakeCurrent設定為GPU線程目前使用的OpenGL上下文和繪圖表面。由于所有的虛拟OpenGL上下文對應的真實OpenGL上下文都是相同的,是以當一個虛拟OpenGL上下文被排程時,隻需要通過調用EGL函數eglMakeCurrent将其關聯的繪圖表面設定為GPU線程目前使用的繪圖表面即可。

       前面提到,OpenGL上下文有優先級高低之分,具體表現為Browser端的OpenGL上下文優先級比WebGL端和Render端的高。這是因為前者負責合成後者的UI顯示在螢幕中,是以就要保證它的運作時間。在Browser端的OpenGL上下文需要排程運作而GPU線程又被其它OpenGL上下文占有時,Browser端的OpenGL上下文就可以搶占GPU線程。為達到這一目的,Chromium給Browser端與GPU程序建立的GPU通道設定IDLE、WAITING、CHECKING、WOULD_PREEMPT_DESCHEDULED和PREEMPTING五個狀态。這五個狀态的變遷關系如圖3所示:

Chromium硬體加速渲染的OpenGL上下文排程過程分析

圖3 GPU通道狀态變遷圖

       當Browser端的GPU通道處于PREEMPTING狀态時,Browser端的OpenGL上下文就可以要求其它OpenGL上下文停止執行手頭上的任務,以便将GPU線程交出來運作Browser端的OpenGL上下文。

       Browser端的GPU通道開始時處于IDLE狀态。當有未處理IPC消息時,就從IDLE狀态進入WAITING狀态。進入WAITING狀态kPreemptWaitTimeMs毫秒之後,就自動進入CHECKING狀态。kPreemptWaitTimeMs毫秒等于2個kVsyncIntervalMs毫秒,kVsyncIntervalMs定義為17。假設螢幕的重新整理速度是60fps,那麼kVsyncIntervalMs毫秒剛好就是一個Vsync時間,即一次螢幕重新整理時間間隔。

       處于CHECKING狀态期間時,Browser端的GPU通道會不斷檢查最早接收到的未處理IPC消息流逝的時間是否小于2次螢幕重新整理時間間隔。如果小于,那麼就繼續停留在CHECKING狀态。否則的話,就進入WOULD_PREEMPT_DESCHEDULED狀态或者PREEMPTING狀态。圖1所示的stub指的是一個GpuCommandBufferStub對象。從前面Chromium硬體加速渲染的OpenGL上下文建立過程分析一文可以知道,在GPU程序中,一個GpuCommandBufferStub對象描述的就是一個OpenGL上下文。是以,圖1所示的stub指的是一個Browser端OpenGL上下文。

       處于CHECKING狀态期間時,如果最早接收到的未處理IPC消息流逝的時間大于等于2次螢幕重新整理時間間隔,并且沒有任何的Browser端OpenGL上下文自行放棄排程,那麼Browser端的GPU通道就會進入PREEMPTING狀态,表示它要搶占GPU線程,也表示要求目前正在排程的OpenGL上下文放棄占有GPU線程。另一方面,如果這時候至少有一個Browser端OpenGL上下文自行放棄排程,那麼Browser端的GPU通道就會進入WOULD_PREEMPT_DESCHEDULED狀态,表示它現在不急于搶占GPU線程,因為這時候有OpenGL上下文自行放棄了排程,進而使得最早接收到的未處理消息所屬的OpenGL上下文得到排程處理。

       處于WOULD_PREEMPT_DESCHEDULED狀态時,Browser端的GPU通道會繼續檢查是否有Browser端OpenGL上下文自行放棄排程。如果沒有,那麼就進入PREEMPTING狀态,表示要搶占GPU線程。如果有,并且這時候Browser端的GPU通道接收到的IPC消息均已被處理,或者最早接收到的未處理IPC消息的流逝時間小于kStopPreemptThresholdMs毫秒,那麼就進入IDLE狀态。否則的話,就繼續維持WOULD_PREEMPT_DESCHEDULED狀态。kStopPreemptThresholdMs也定義為17,意思是Browser端的GPU通道處于WOULD_PREEMPT_DESCHEDULED狀态時,允許最早接收到的未處理IPC消息延遲一次螢幕重新整理時間間隔再進行處理。

       Browser端的GPU通道處于PREEMPTING狀态的最長時間為kMaxPreemptTimeMs毫秒。kMaxPreemptTimeMs也定義為17,意思是Browser端的GPU通道搶占GPU線程的時間不能超過一個螢幕重新整理時間間隔。如果超過了一個螢幕重新整理時間間隔,那麼就會進入IDLE狀态。

       在處于PREEMPTING狀态期間,如果Browser端的GPU通道接收到的IPC消息均已被處理,或者最早接收到的未處理IPC消息的流逝時間小于kStopPreemptThresholdMs毫秒,那麼Browser端的GPU通道也會進入IDLE狀态。此外,在處于PREEMPTING狀态期間,如果至少有一個Browser端OpenGL上下文自行放棄排程,那麼Browser端的GPU通道就會進入WOULD_PREEMPT_DESCHEDULED狀态。

       注意,在圖3所示的狀态變遷圖中,隻有處于PREEMPTING狀态時,Browser端的GPU通道才會強行搶占GPU線程。這是為了保證Browser端的OpenGL上下文,至少應該需要在兩個螢幕重新整理時間間隔之内,得到一次排程,進而保證網頁UI得到重新整理和及時顯示。WebGL端和Render端的OpenGL上下文就沒有這種待遇,畢竟它們的優先級沒有Browser端的OpenGL上下文高。

       Browser端GPU通道是以什麼方式強行搶占GPU線程的呢?我們通過圖4說明,如下所示:

Chromium硬體加速渲染的OpenGL上下文排程過程分析

圖4 搶占GPU線程

       Browser端GPU通道有一個Preemption Flag。當它處于PREEMPTING狀态時,就會将Preemption Flag設定為True。WebGL端和Render端GPU通道可以通路Browser端GPU通道的Preemption Flag。屬于WebGL端和Render端GPU通道的OpenGL上下文在排程期間,會不斷地檢查Browser端GPU通道的Preemption Flag是否被設定為True。如果被設定為True,那麼它就會中止執行,提前釋放GPU線程。

       WebGL端和Render端GPU通道和Browser端GPU通道是父子關系。其中, WebGL端和Render端GPU通道是兒子,Browser端GPU通道是父親。Chromium規定,兒子GPU通道可以通路父親GPU通道的Preemption Flag。有了前面這些背景知識之後,接下來我們就結合源碼分析OpenGL上下文的排程過程。

       從前面Chromium的GPU程序啟動過程分析一文可以知道,GPU程序通過調用GpuChannel類的成員函數Init建立GPU通道,如下所示:

void GpuChannel::Init(base::MessageLoopProxy* io_message_loop,  
                      base::WaitableEvent* shutdown_event) {  
  ......  
  
  channel_ = IPC::SyncChannel::Create(channel_id_,  
                                      IPC::Channel::MODE_SERVER,  
                                      this,  
                                      io_message_loop,  
                                      false,  
                                      shutdown_event);  
  
  filter_ =  
      new GpuChannelMessageFilter(weak_factory_.GetWeakPtr(),  
                                  gpu_channel_manager_->sync_point_manager(),  
                                  base::MessageLoopProxy::current());  
  ......
  channel_->AddFilter(filter_.get());  
  
  ......  
}  
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

       結合前面Chromium的IPC消息發送、接收和分發機制分析一文可以知道,WebGL端、Render端和Browser端發送過來的GPU消息由GpuChannel類的成員函數OnMessageReceived負責接收。在接收之前,這些GPU消息首先會被GpuChannel類的成員變量filter_指向的一個GpuChannelMessageFilter對象的成員函數OnMessageReceived過濾。

       注意,GpuChannel類的成員函數Init是在GPU線程中執行的,這意味着GpuChannel類的成員函數OnMessageReceived也将會在GPU線程中執行,但是它的成員變量filter_指向的GpuChannelMessageFilter對象的成員函數OnMessageReceived不是在GPU線程執行的,而是在負責接收IPC消息的IO線程中執行的。

       接下來我們先分析GpuChannel類的成員函數OnMessageReceived的實作,後面分析Browser端GPU通道搶占GPU線程的過程時,再分析GpuChannelMessageFilter類的成員函數OnMessageReceived的實作。

       GpuChannel類的成員函數OnMessageReceived的實作如下所示:

bool GpuChannel::OnMessageReceived(const IPC::Message& message) {
  ......

  if (message.type() == GpuCommandBufferMsg_WaitForTokenInRange::ID ||
      message.type() == GpuCommandBufferMsg_WaitForGetOffsetInRange::ID) {
    // Move Wait commands to the head of the queue, so the renderer
    // doesn't have to wait any longer than necessary.
    deferred_messages_.push_front(new IPC::Message(message));
  } else {
    deferred_messages_.push_back(new IPC::Message(message));
  }

  OnScheduled();

  return true;
}
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

       GpuChannel類将接收到的IPC消息儲存在成員變量deferred_messages_描述的一個std::deque中。其中,類型為GpuCommandBufferMsg_WaitForTokenInRange和GpuCommandBufferMsg_WaitForGetOffsetInRange的GPU消息将會儲存在上述std::deque的頭部,而其它GPU消息則儲存在上述std::deque的末部。

       從前面Chromium硬體加速渲染的OpenGL指令執行過程分析和Chromium硬體加速渲染的GPU資料上傳機制分析這篇文章可以知道,當GPU程序接收到類型為GpuCommandBufferMsg_WaitForTokenInRange和GpuCommandBufferMsg_WaitForGetOffsetInRange的IPC消息時,就表示它的Client端,即WebGL端、Render端和Browser端,正在檢查其GPU指令緩沖區的執行情況,需要盡快得到結果,是以就需要将它們放在上述std::deque的頭部,以便盡快得到處理。

       GpuChannel類的成員函數OnMessageReceived将接收到的GPU消息儲存在成員變量deferred_messages_描述的std::deque之後,接下來調用另外一個成員函數OnScheduled對它們進行排程處理,如下所示:

void GpuChannel::OnScheduled() {
  if (handle_messages_scheduled_)
    return;
  // Post a task to handle any deferred messages. The deferred message queue is
  // not emptied here, which ensures that OnMessageReceived will continue to
  // defer newly received messages until the ones in the queue have all been
  // handled by HandleMessage. HandleMessage is invoked as a
  // task to prevent reentrancy.
  base::MessageLoop::current()->PostTask(
      FROM_HERE,
      base::Bind(&GpuChannel::HandleMessage, weak_factory_.GetWeakPtr()));
  handle_messages_scheduled_ = true;
}
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

       GpuChannel類的成員函數OnScheduled通過向GPU線程的消息隊列發送一個Task請求為目前接收到GPU消息的GPU通道執行一次排程。該Task綁定的函數為GpuChannel類的成員函數HandleMessage。

       GpuChannel類的成員變量handle_messages_scheduled_的值等于true時,表示目前接收到GPU消息的GPU通道已經請求過排程了,并且請求的排程還沒有被執行。在這種情況下,就不必再向GPU線程的消息隊列發送Task。

       GpuChannel類的成員函數HandleMessage的實作如下所示:

void GpuChannel::HandleMessage() {
  handle_messages_scheduled_ = false;
  if (deferred_messages_.empty())
    return;

  bool should_fast_track_ack = false;
  IPC::Message* m = deferred_messages_.front();
  GpuCommandBufferStub* stub = stubs_.Lookup(m->routing_id());

  do {
    if (stub) {
      if (!stub->IsScheduled())
        return;
      if (stub->IsPreempted()) {
        OnScheduled();
        return;
      }
    }

    scoped_ptr<IPC::Message> message(m);
    deferred_messages_.pop_front();
    bool message_processed = true;

    currently_processing_message_ = message.get();
    bool result;
    if (message->routing_id() == MSG_ROUTING_CONTROL)
      result = OnControlMessageReceived(*message);
    else
      result = router_.RouteMessage(*message);
    currently_processing_message_ = NULL;

    if (!result) {
      // Respond to sync messages even if router failed to route.
      if (message->is_sync()) {
        IPC::Message* reply = IPC::SyncMessage::GenerateReply(&*message);
        reply->set_reply_error();
        Send(reply);
      }
    } else {
      // If the command buffer becomes unscheduled as a result of handling the
      // message but still has more commands to process, synthesize an IPC
      // message to flush that command buffer.
      if (stub) {
        if (stub->HasUnprocessedCommands()) {
          deferred_messages_.push_front(new GpuCommandBufferMsg_Rescheduled(
              stub->route_id()));
          message_processed = false;
        }
      }
    }
    if (message_processed)
      MessageProcessed();

    // We want the EchoACK following the SwapBuffers to be sent as close as
    // possible, avoiding scheduling other channels in the meantime.
    should_fast_track_ack = false;
    if (!deferred_messages_.empty()) {
      m = deferred_messages_.front();
      stub = stubs_.Lookup(m->routing_id());
      should_fast_track_ack =
          (m->type() == GpuCommandBufferMsg_Echo::ID) &&
          stub && stub->IsScheduled();
    }
  } while (should_fast_track_ack);

  if (!deferred_messages_.empty()) {
    OnScheduled();
  }
}
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。     

       GpuChannel類的成員函數HandleMessage首先檢查成員變量deferred_messages_描述的一個std::deque是否為空。如果為空,那麼就說明沒有未處理的GPU消息,是以就直接傳回。否則的話,就取出頭部的GPU消息m,并且根據這個GPU消息的Routing ID找到負責接收的一個GpuCommandBufferStub對象stub。

       GpuChannel類的成員函數HandleMessage将GPU消息m分發給GpuCommandBufferStub對象stub處理之前,首先檢查GpuCommandBufferStub對象stub是否自行放棄了排程,以及是否被搶占排程。如果GpuCommandBufferStub對象stub自行放棄了排程,那麼調用它的成員函數IsScheduled獲得的傳回值就為false。如果GpuCommandBufferStub對象stub被搶占了排程,那麼調用它的成員函數IsPreempted獲得的傳回值就為true。在前一種情況下,GpuChannel類的成員函數HandleMessage什麼也不做就傳回。在後一種情況下,GpuChannel類的成員函數HandleMessage調用前面分析過的成員函數OnScheduled向GPU線程的消息隊列的末尾發送一個排程用的Task,也就是先将GPU線程讓出來,以便GPU線程執行排在GPU線程消息隊列頭部的Task。根據前面的分析,這時候排在GPU線程消息隊列頭部的Task可能就是用來排程Browser端OpenGL上下文的。

       如果GpuCommandBufferStub對象stub既沒有自行放棄排程,也沒有被搶占排程,那麼接下來GpuChannel類的成員函數HandleMessage就會對GPU消息m進行處理。如果GPU消息m的Routing ID等于MSG_ROUTING_CONTROL,那麼就說明它是一個控制類型的GPU消息,這時候就将它分發給GpuChannel類的成員函數OnControlMessageReceived處理。否則的話,就調用成員變量router_描述的一個MessageRouter對象的成員函數RouteMessage将GPU消息m分發給GpuCommandBufferStub對象stub的成員函數OnMessageReceived處理。

       如果GPU消息m描述的是一個認識的GPU操作,那麼前面調用GpuChannel類的成員函數OnControlMessageReceived或者調用GpuChannel類的成員變量router_描述的MessageRouter對象的成員函數RouteMessage得到的傳回值就為true。這時候GpuChannel類的成員函數HandleMessage會繼續調用GpuCommandBufferStub對象stub的成員函數HasUnprocessedCommands判斷它的GPU指令緩沖區是否還有指令未被處理。如果是的話,就向成員變量deferred_messages_描述的一個std::deque的頭部插入一個類型為GpuCommandBufferMsg_Rescheduled的GPU消息,并且将本地變量message_processed的值設定為false。當上述GpuCommandBufferMsg_Rescheduled消息被處理時,GPU線程就會繼續執行GpuCommandBufferStub對象stub的GPU指令緩沖區中的未處理指令。将本地變量message_processed設定為false,表示後面不要調用GpuChannel類的成員函數MessageProcessed。

      另一方面,如果GPU消息m描述的是一個不認識的GPU操作,那麼前面調用GpuChannel類的成員函數OnControlMessageReceived或者調用GpuChannel類的成員變量router_描述的MessageRouter對象的成員函數RouteMessage得到的傳回值就為false。這時候GpuChannel類的成員函數HandleMessage就會繼續檢查GPU消息m是否是一個同步類型的GPU消息。如果是的話,那麼就向發送該GPU消息的Client端發送一個回複消息,以便該Client端可以結束等待。如果GPU消息m描述的是一個認識的GPU操作,那麼該回複消息就是由負責處理GPU消息m的函數發出的。現在既然沒有函數處理GPU消息m,是以GpuChannel類的成員函數HandleMessage就要負責回複該GPU消息,以便不讓發送該GPU消息的Client端一直等待下去。

       GpuChannel類的成員函數HandleMessage接下來判斷本地變量message_processed的值是否等于true。如果等于true,那麼就代表前面完整地處理完成了一個GPU消息,這時候就調用另外一個成員函數MessageProcessed告知前面提到的成員變量filter_描述的一個GpuChannelMessageFilter對象,目前正在被排程的GPU通道處理完成了一個GPU消息,以便該GpuChannelMessageFilter對象可以更新目前正在被排程的GPU通道的狀态。後面我們分析Browser端OpenGL上下文搶占GPU線程時,再分析GpuChannel類的成員函數MessageProcessed的實作。

       從前面的分析可以知道,本地變量message_processed的值隻有一種情況等于false,那就是GpuCommandBufferStub對象stub的GPU指令緩沖區還有未處理指令。這種情況出現在GpuCommandBufferStub對象stub接收到了一個類型為GpuCommandBufferMsg_AsyncFlush的GPU消息。從前面Chromium硬體加速渲染的OpenGL指令執行過程分析一文可以知道,當一個GpuCommandBufferStub對象接收到一個類型為GpuCommandBufferMsg_AsyncFlush的GPU消息時,就會調用GpuScheduler類的成員函數PutChanged處理該GpuCommandBufferStub對象的GPU指令緩沖區新送出的指令,如下所示:

void GpuScheduler::PutChanged() {  
  ......  
  
  if (!IsScheduled())  
    return;  
  
  while (!parser_->IsEmpty()) {  
    if (IsPreempted())  
      break;  
  
    ......  
  
    error = parser_->ProcessCommand();  
    ......  
  
  }  
  
  ......  
}  
           

      這個函數定義在檔案external/chromium_org/gpu/command_bufferservice/gpu_scheduler.cc中。

      然而,有可能接收到類型為GpuCommandBufferMsg_AsyncFlush的GPU消息的GpuCommandBufferStub對象自行放棄了排程,或者被搶占了排程。前者表現為調用GpuScheduler類的成員函數IsScheduled獲得的傳回值為false,而後者表現為調用GpuScheduler類的成員函數IsPreempted獲得的傳回值為true。在這兩種情況下,上述GpuCommandBufferStub對象的GPU指令緩沖區新送出的指令沒有全部得到處理。這相當于是說接收到的GpuCommandBufferMsg_AsyncFlush消息并沒有得到完整處理。是以,GpuChannel類的成員函數HandleMessage就不會調用成員函數MessageProcessed告知前面提到的成員變量filter_描述的一個GpuChannelMessageFilter對象,目前正在被排程的GPU通道處理完成了一個GPU消息。

       回到GpuChannel類的成員函數HandleMessage中,它接下來檢查成員變量deferred_messages_描述的std::deque是否還有待處理GPU消息。如果有,并且下一個待處理的GPU消息是一個類型為GpuCommandBufferMsg_Echo的GPU消息,并且負責處理該GpuCommandBufferMsg_Echo消息的GpuCommandBufferStub對象沒有自行放棄排程,那麼就需要繼續處理該GpuCommandBufferMsg_Echo消息後才傳回。

       當一個GPU程序的Client端,例如一個Render端,完成自已的網頁UI的繪制後,就會向GPU程序發送一個GPU消息,請求對Browser端OpenGL上下文合成該網頁UI,并且顯示在螢幕中。緊跟在該GPU消息後面的是一個GpuCommandBufferMsg_Echo消息。一個GpuCommandBufferStub對象接收到一個GpuCommandBufferMsg_Echo消息之後,要馬上對該消息進行回複。Client端接收到這個回複消息之後,就可以知道前面已經繪制完成的網頁UI已經被合成顯示在螢幕中了,于是就可以執行一些清理工作,例如回收資源。這些清理操作越快進行越好,是以就要求類型為GpuCommandBufferMsg_Echo的GPU消息要盡快處理。

       如果GpuChannel類的成員函數HandleMessage處理完成GPU消息m後,如果還有待處理的GPU消息,并且這個待處理的GPU消息不是一個類型為GpuCommandBufferMsg_Echo的GPU消息,那麼這個待處理的GPU消息将會在負責處理它的GpuCommandBufferStub對象下一次被排程時才會處理。由此可見,GpuChannel類的成員函數HandleMessage一般情況下隻會處理一個GPU消息。這就是為了讓其它GpuCommandBufferStub對象也有機會處理自己的GPU消息的。

       前面提到,如果目前處理的GPU消息不是一個控制類型為的GPU消息,那麼GpuChannel類的成員函數HandleMessage會将它分發給對應的GpuCommandBufferStub對象的成員函數OnMessageReceived處理,處理過程如下所示:

bool GpuCommandBufferStub::OnMessageReceived(const IPC::Message& message) {
  ......

  if (decoder_.get() && message.type() != GpuCommandBufferMsg_Echo::ID &&
      message.type() != GpuCommandBufferMsg_WaitForTokenInRange::ID &&
      message.type() != GpuCommandBufferMsg_WaitForGetOffsetInRange::ID &&
      message.type() != GpuCommandBufferMsg_RetireSyncPoint::ID &&
      message.type() != GpuCommandBufferMsg_SetLatencyInfo::ID) {
    if (!MakeCurrent())
      return false;
    ......
  }

  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(GpuCommandBufferStub, message)
    ......
    IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_AsyncFlush, OnAsyncFlush);
    ......
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()

  ......

  return handled;
}
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_command_buffer_stub.cc中。

       除了以下五個GPU消息,其餘的GPU消息均要求調用成員GpuCommandBufferStub類的成員函數MakeCurrent将目前正在處理的GpuCommandBufferStub對象描述的OpenGL上下文設定為GPU線程目前激活的OpenGL上下文之後再處理:

       1. GpuCommandBufferMsg_Echo

       2. GpuCommandBufferMsg_WaitForTokenInRange

       3. GpuCommandBufferMsg_WaitForGetOffsetInRange

       4. GpuCommandBufferMsg_RetireSyncPoint

       5. GpuCommandBufferMsg_SetLatencyInfo

       第1個GPU消息的作用可以參考前面的分析。第2個GPU消息是GPU程序的Client端回收共享緩沖區時發送過來的,具體可以參考前面Chromium硬體加速渲染的GPU資料上傳機制分析一文。第3個GPU消息是GPU程序的Client端等待GPU指令緩沖區的指令被處理時發送過來的,具體可以參考前面Chromium硬體加速渲染的OpenGL指令執行過程分析一文。第4個GPU消息與我們後面要分析的Sync Point機制相關。第5個GPU消息是GPU程序的Client端用來告訴GPU程序它的UI繪制完成後多長時間可以用來合成到浏覽器視窗中顯示出來。

       以GpuCommandBufferMsg_AsyncFlush消息為例,GPU線程對它的處理就是執行GPU指令緩沖區新送出的GPU指令,也就是調用相應的OpenGL函數,而調用這些OpenGL函數就必須要在發送GpuCommandBufferMsg_AsyncFlush消息的Client端的OpenGL上下文中進行。是以,GpuCommandBufferStub類的成員函數OnMessageReceived就需要調用成員函數MakeCurrent将發送GpuCommandBufferMsg_AsyncFlush消息的Client端的OpenGL上下文設定為GPU線程目前激活的OpenGL上下文,即切換GPU線程的OpenGL上下文。

       切換GPU線程的OpenGL上下文是OpenGL上下文排程過程的重要一環,是以接下來我們繼續分析GpuCommandBufferStub類的成員函數MakeCurrent的實作,如下所示:

bool GpuCommandBufferStub::MakeCurrent() {
  if (decoder_->MakeCurrent())
    return true;
  ......
  return false;
}
           

      這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_command_buffer_stub.cc中。

      從前面Chromium硬體加速渲染的OpenGL指令執行過程分析一文可以知道,GpuCommandBufferStub類的成員變量decoder_描述的是一個GLES2CmdDecoderImpl對象,這裡調用這個GLES2CmdDecoderImpl對象的成員函數MakeCurrent切換GPU線程的OpenGL上下文,切換過程如下所示:

bool GLES2DecoderImpl::MakeCurrent() {
  ......

  if (!context_->MakeCurrent(surface_.get()) || WasContextLost()) {
    ......

    return false;
  }

  ProcessFinishedAsyncTransfers();

  // Rebind the FBO if it was unbound by the context.
  if (workarounds().unbind_fbo_on_context_switch)
    RestoreFramebufferBindings();

  ......

  return true;
}
           

       這個函數定義在檔案external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。

       GLES2CmdDecoderImpl類的成員函數MakeCurrent主要做了以下三件事情:

       1. 調用成員變量context_描述的一個gfx::GLContext對象的成員函數MakeCurrent切換GPU線程的OpenGL上下文,也就是将目前正在處理的GLES2CmdDecoderImpl對象對應的OpenGL上下文設定為GPU線程目前激活的OpenGL上下文。

       2. 調用成員函數ProcessFinishedAsyncTransfers處理那些已經異步上傳完成了資料的紋理,這個過程可以參考前面Chromium硬體加速渲染的GPU資料上傳機制分析一文。

       3. 有些GPU,例如Vivante和Imagination的GPU,如果一個OpenGL上下文調用OpenGL函數glBindFramebuffer綁定過FBO,那麼當該OpenGL上下文被切換出去後又被切換回來時,之前綁定過的FBO不會自動被綁定,這時候就需要調用成員函數RestoreFramebufferBindings重新綁定之前被綁定的FBO。

       我們主要關注上述的第一件事情。從前面Chromium硬體加速渲染的OpenGL上下文建立過程分析一文可以知道,GLES2CmdDecoderImpl類的成員context_實際描述的要麼是一個GLContextEGL對象,要麼是一個GLContextVirtual對象,分别表示目前正在處理的GLES2CmdDecoderImpl對象對應的OpenGL上下文使用的是真實OpenGL上下文和虛拟OpenGL上下文。

       接下來我們就先分析真實OpenGL上下文的切換過程,即GLContextEGL類的成員函數MakeCurrent的實作,接着再分析虛拟OpenGL上下文的切換過程,即GLContextVirtual類的成員函數MakeCurrent的實作。

       GLContextEGL類的成員函數MakeCurrent的實作如下所示:

bool GLContextEGL::MakeCurrent(GLSurface* surface) {
  .....

  if (!eglMakeCurrent(display_,
                      surface->GetHandle(),
                      surface->GetHandle(),
                      context_)) {
    ......
    return false;
  }

  ......

  return true;
}
           

      這個函數定義在檔案external/chromium_org/ui/gl/gl_context_egl.cc。

      參數surface描述的是一個要切換至的OpenGL上下文的繪圖表面,而GLContextEGL類的成員變量context_描述的就是要切換至的OpenGL上下文。GLContextEGL類的成員函數MakeCurrent通過調用EGL函數eglMakeCurrent即可将context_描述的OpenGL上下文設定為GPU線程目前使用OpenGL上下文,以及将參數surface描述的繪圖表面設定為要切換至的OpenGL上下文的繪圖表面。

      以上就是真實OpenGL上下文的切換過程。這個切換過程主要是通過EGL函數eglMakeCurrent。接下來我們繼續分析虛拟OpenGL上下文的切換過程,即GLContextVirtual類的成員函數MakeCurrent的實作,如下所示:

bool GLContextVirtual::MakeCurrent(gfx::GLSurface* surface) {
  if (decoder_.get())
    return shared_context_->MakeVirtuallyCurrent(this, surface);

  ......
  return false;
}
           

       這個函數定義在檔案external/chromium_org/gpu/command_buffer/service/gl_context_virtual.cc中。

       從前面Chromium硬體加速渲染的OpenGL上下文建立過程分析一文可以知道,每一個虛拟OpenGL上下文都對應有一個真實OpenGL上下文。這個真實OpenGL上下文就是通過GLContextVirtual類的成員變量shared_context_指向的一個GLContextEGL對象描述的。GLContextVirtual類的成員函數MakeCurrent調用這個GLContextEGL對象的成員函數MakeVirtuallyCurrent切換虛拟OpenGL上下文。

       GLContextEGL類的成員函數MakeVirtuallyCurrent是從父類GLContext繼承下來的,是以接下來我們就分析GLContext類的成員函數MakeVirtuallyCurrent的實作。不過在GLContext類的成員函數MakeVirtuallyCurrent的實作之前,我們先分析虛拟OpenGL上下文的初始化過程。

       從前面Chromium硬體加速渲染的OpenGL上下文建立過程分析一文可以知道,虛拟OpenGL上下文的初始化是通過調用GLContextVirtual類的成員函數Initialize實作的,如下所示:

bool GLContextVirtual::Initialize(
    gfx::GLSurface* compatible_surface, gfx::GpuPreference gpu_preference) {
  SetGLStateRestorer(new GLStateRestorerImpl(decoder_));

  ......

  shared_context_->SetupForVirtualization();
  ......
  return true;
}
           

       這個函數定義在檔案external/chromium_org/gpu/command_buffer/service/gl_context_virtual.cc中。

       GLContextVirtual類的成員函數Initialize首先建立一個GLStateRestorerImpl對象,并且通過調用另外一個成員函數SetGLStateRestorer将該GLStateRestorerImpl對象儲存在内部。這個GLStateRestorerImpl對象以後用來儲存目前正在初始化的虛拟OpenGL上下文的狀态。 

       GLContextVirtual類的成員變量shared_context_指向的是一個GLContextEGL對象,這個GLContextEGL對象描述目前正在初始化的虛拟OpenGL上下文對應的真實OpenGL上下文,GLContextVirtual類的成員函數Initialize接下來調用GLContextEGL類的成員函數SetupForVirtualization對該真實OpenGL上下文進行初始化,如下所示:

void GLContext::SetupForVirtualization() {
  if (!virtual_gl_api_) {
    virtual_gl_api_.reset(new VirtualGLApi());
    virtual_gl_api_->Initialize(&g_driver_gl, this);
  }
}
           

       這個函數定義在檔案external/chromium_org/ui/gl/gl_context.cc中。

       GLContextEGL類的成員函數SetupForVirtualization主要是檢查成員變量virtual_gl_api_的值是否等于NULL。如果等于NULL,那麼就建立一個VirtualGLApi對象,并且儲存在成員變量virtual_gl_api_中。這個VirtualGLApi對象以後負責切換虛拟OpenGL上下文。

       有了上面的基礎知識之後,接下來我們就分析GLContext類的成員函數MakeVirtuallyCurrent的實作,如下所示:

bool GLContext::MakeVirtuallyCurrent(
    GLContext* virtual_context, GLSurface* surface) {
  ......
  return virtual_gl_api_->MakeCurrent(virtual_context, surface);
}
           

       這個函數定義在檔案external/chromium_org/ui/gl/gl_context.cc中。

       GLContext類的成員變量virtual_gl_api_描述的是一個VirtualGLApi對象。前面提到,這個VirtualGLApi對象負責切換虛拟OpenGL上下文。這裡調用它的成員函數MakeCurrent切換虛拟OpenGL上下文。

       VirtualGLApi類的成員函數MakeCurrent的實作如下所示:

bool VirtualGLApi::MakeCurrent(GLContext* virtual_context, GLSurface* surface) {
  bool switched_contexts = g_current_gl_context_tls->Get() != this;
  GLSurface* current_surface = GLSurface::GetCurrent();
  if (switched_contexts || surface != current_surface) {
    ......
    if (switched_contexts || !current_surface ||
        !virtual_context->IsCurrent(surface)) {
      if (!real_context_->MakeCurrent(surface)) {
        return false;
      }
    }
  }

  ......

  if (switched_contexts || virtual_context != current_context_) {
    ......

    if (virtual_context->GetGLStateRestorer()->IsInitialized()) {
      virtual_context->GetGLStateRestorer()->RestoreState(
          (current_context_ && !switched_contexts)
              ? current_context_->GetGLStateRestorer()
              : NULL);
    }
    ......
    current_context_ = virtual_context;
  }
  
  ......

  return true;
}
           

       這個函數定義在檔案external/chromium_org/ui/gl/gl_gl_api_implementation.cc中。

       從前面Chromium硬體加速渲染的OpenGL上下文建立過程分析一文可以知道,虛拟OpenGL上下文是按組進行管理的。在同一個組中的虛拟OpenGL上下文對應的真實OpenGL上下文都是相同的。我們将一個虛拟OpenGL上下文切換為GPU線程目前使用的虛拟OpenGL上下文之前,要先将要切換的虛拟OpenGL上下文對應的真實OpenGL上下文切換為GPU線程目前使有的真實OpenGL上下文。

       一個虛拟OpenGL上下文被切換為GPU線程目前使用的虛拟OpenGL上下文之後,它對應的真實OpenGL上下文内部使用的一個VirtualGLApi對象會記錄在全局變量g_current_gl_context_tls中。如果記錄在全局變量g_current_gl_context_tls中的VirtualGLApi對象與目前正在處理的VirtualGLApi對象不一樣,本地變量switched_contexts的值就會等于true。這種情況說明要切換至的虛拟OpenGL上下文對應的真實OpenGL上下文不是GPU線程目前使用的真實OpenGL上下文,是以就需要先将該真實OpenGL上下文切換為GPU線程目前使用的真實OpenGL上下文。這個真實OpenGL上下文就儲存在VirtualGLApi類的成員變量real_context_中,通過調用它的成員函數MakeCurrent,也就是前面分析過的GLContextEGL類的成員函數MakeCurrent,就可以将其設定為GPU線程目前使用的真實OpenGL上下文。

       此外,當要切換至的虛拟OpenGL上下文關聯的繪圖表面不是GPU線程目前使用的繪圖表面時,即使要切換至的虛拟OpenGL上下文對應的真實OpenGL上下文就是GPU線程目前使有的真實OpenGL上下文,也需要調用該真實OpenGL上下文的成員函數MakeCurrent重新設定GPU線程目前使用的繪圖表面。GPU線程目前使用的繪圖表面可以通過調用GLSurface類的靜态成員函數GetCurrent獲得,而要設定的繪圖表面由參數surface指定。

       對切換至的虛拟OpenGL上下文對應的真實OpenGL上下文進行必要的處理後,VirtualGLApi類的成員函數MakeCurrent再檢查GPU線程目前使用的虛拟OpenGL上下文是否發生變化。GPU線程目前使用的虛拟OpenGL上下文記錄在VirtualGLApi類的成員變量current_context_中,參數virtual_context描述的是要切換至的虛拟OpenGL上下文。當這兩者的值不相等時,就說明GPU線程目前使用的虛拟OpenGL上下文發生了變化。在這種情況下,以及在GPU線程目前使用的真實OpenGL上下文發生變化的情況下,都需要執行切換虛拟OpenGL上下文的操作。

       切換虛拟OpenGL上下文實際上就是将要切換至的虛拟OpenGL上下文的狀态設定為GPU線程的目前狀态。前面提到,虛拟OpenGL上下文的狀态記錄在一個GLStateRestorerImpl對象。VirtualGLApi類的成員函數MakeCurrent通過調用參數virtual_context描述的一個GLContextVirtual對象的成員函數GetGLStateRestorer就可以獲得要切換至的虛拟OpenGL上下文的狀态,即一個GLStateRestorerImpl對象。有了這個GLStateRestorerImpl對象之後,就可以調用它的成員函數RestoreState将其描述的狀态設定為GPU線程的目前狀态了。

       GLStateRestorerImpl類的成員函數RestoreState的實作如下所示:

void GLStateRestorerImpl::RestoreState(const gfx::GLStateRestorer* prev_state) {
  DCHECK(decoder_.get());
  const GLStateRestorerImpl* restorer_impl =
      static_cast<const GLStateRestorerImpl*>(prev_state);
  decoder_->RestoreState(
      restorer_impl ? restorer_impl->GetContextState() : NULL);
}
           

       這個函數定義在檔案external/chromium_org/gpu/command_buffer/service/gl_state_restorer_impl.cc中。

       參數prev_state描述的是切換前的GPU線程的狀态,通過調用它的成員函數GetContextState可以獲得這個狀态。有了這個前狀态之後,GLStateRestorerImpl類的成員函數RestoreState就可以調用成員變量decoder_描述的一個GLES2CmdDecoderImpl對象的成員函數RestoreState切換GPU線程的目前狀态。

       GLES2CmdDecoderImpl類的成員函數RestoreState的實作如下所示:

void GLES2DecoderImpl::RestoreState(const ContextState* prev_state) const {
  ......
  RestoreFramebufferBindings();
  state_.RestoreState(prev_state);
}
           

       這個函數定義在檔案external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。

       如果要切換至的虛拟OpenGL上下文以前調用過OpenGL函數glBindFramebuffer綁定過FBO,那麼GLES2CmdDecoderImpl類的成員函數RestoreState就會調用另外一個成員函數RestoreFramebufferBindings重新恢複綁定它們。

       GLES2CmdDecoderImpl類的成員變量state_描述的是一個ContextState對象,GLES2CmdDecoderImpl類的成員函數RestoreState接下來調用這個ContextState對象的成員函數RestoreState切換GPU線程的目前狀态,如下所示:

void ContextState::RestoreState(const ContextState* prev_state) const {
  RestoreAllTextureUnitBindings(prev_state);
  RestoreVertexAttribs();
  RestoreBufferBindings();
  RestoreRenderbufferBindings();
  RestoreProgramBindings();
  RestoreGlobalState(prev_state);
}
           

       這個函數定義在檔案external/chromium_org/gpu/command_buffer/service/context_state.cc中。

       從ContextState類的成員函數RestoreState的實作可以知道,需要切換的GPU線程狀态包括:

       1. 紋理(Texture)——通過成員函數RestoreAllTextureUnitBindings切換。

       2. 頂點數組(VAO)——通過成員函數RestoreVertexAttribs切換。

       3. 頂點緩沖區(VBO、IBO)——通過成員函數RestoreBufferBindings切換。

       4. 渲染緩沖區(RBO)——通過成員函數RestoreRenderbufferBindings切換。

       5. 着色器(Shader)——通過成員函數RestoreProgramBindings切換。

       6. 其他的全局狀态(GL_BLEND、GL_DITHER等ENABLE/DISABLE開關以及Clear Color、Blend Color等參數設定)——通過成員函數RestoreGlobalState切換。

       上述狀态是與真實OpenGL上下文綁定在一起的,并且由GPU驅動進行維護。 在使用真實OpenGL上下文的情況下,上述狀态随着真實OpenGL上下文的切換而得以切換,也就是上述狀态由GPU驅動自動進行切換。虛拟OpenGL上下文是GPU的使用者定義的概念,GPU驅動并不認識,是以在使用虛拟OpenGL上下文的情況下,GPU的使用者必須要自己去維護它們的狀态,實作方式就是自己去調用相關的OpenGL函數去切換相應的狀态。

       接下來我們就以紋理狀态的切換為例,說明在使用虛拟OpenGL上下文的情況下,GPU線程狀态的切換過程,如下所示:

void ContextState::RestoreAllTextureUnitBindings(
    const ContextState* prev_state) const {
  // Restore Texture state.
  for (size_t ii = 0; ii < texture_units.size(); ++ii) {
    RestoreTextureUnitBindings(ii, prev_state);
  }
  RestoreActiveTexture();
}
           

      這個函數定義在檔案external/chromium_org/gpu/command_buffer/service/context_state.cc中。

      ContextState類的成員變量texture_units儲存了一個虛拟OpenGL上下文可以使用的所有紋理單元,每一個紋理單元都通過調用成員函數RestoreTextureUnitBindings進行狀态切換,如下所示:

void ContextState::RestoreTextureUnitBindings(
    GLuint unit, const ContextState* prev_state) const {
  DCHECK_LT(unit, texture_units.size());
  const TextureUnit& texture_unit = texture_units[unit];
  GLuint service_id_2d = Get2dServiceId(texture_unit);
  GLuint service_id_cube = GetCubeServiceId(texture_unit);
  GLuint service_id_oes = GetOesServiceId(texture_unit);
  GLuint service_id_arb = GetArbServiceId(texture_unit);

  bool bind_texture_2d = true;
  bool bind_texture_cube = true;
  bool bind_texture_oes = feature_info_->feature_flags().oes_egl_image_external;
  bool bind_texture_arb = feature_info_->feature_flags().arb_texture_rectangle;

  if (prev_state) {
    const TextureUnit& prev_unit = prev_state->texture_units[unit];
    bind_texture_2d = service_id_2d != Get2dServiceId(prev_unit);
    bind_texture_cube = service_id_cube != GetCubeServiceId(prev_unit);
    bind_texture_oes =
        bind_texture_oes && service_id_oes != GetOesServiceId(prev_unit);
    bind_texture_arb =
        bind_texture_arb && service_id_arb != GetArbServiceId(prev_unit);
  }

  // Early-out if nothing has changed from the previous state.
  if (!bind_texture_2d && !bind_texture_cube
      && !bind_texture_oes && !bind_texture_arb) {
    return;
  }

  glActiveTexture(GL_TEXTURE0 + unit);
  if (bind_texture_2d) {
    glBindTexture(GL_TEXTURE_2D, service_id_2d);
  }
  if (bind_texture_cube) {
    glBindTexture(GL_TEXTURE_CUBE_MAP, service_id_cube);
  }
  if (bind_texture_oes) {
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, service_id_oes);
  }
  if (bind_texture_arb) {
    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, service_id_arb);
  }
}
           

       這個函數定義在檔案external/chromium_org/gpu/command_buffer/service/context_state.cc中。

       一個紋理單元對應有GL_TEXTURE_2D、GL_TEXTURE_CUBE_MAP、GL_TEXTURE_EXTERNAL_OES和GL_TEXTURE_RECTANGLE_ARB四個紋理目标,是以切換一個紋理單元的狀态,所要做的事情有兩件:

       1. 調用OpenGL函數glActiveTexture選擇要切換狀态的紋理單元。

       2. 調用OpenGL函數glBindTexture恢複選擇的紋理單元的上述四個紋理目标。

       每一個紋理目标都通過一個紋理ID描述,這些紋理ID儲存在一個TextureUnit對象中,這個TextureUnit對象可以通過參數unit獲得。

       對一個紋理單元來說,它有可能在切換前的虛拟OpenGL上下文和切換後的虛拟OpenGL上下文中使用了相同的紋理目标。在這種情況下,實際上是不需要調用OpenGL函數glBindTexture來恢複它的紋理目标的,這樣可以降低紋理單元狀态切換帶來的開銷。是以,ContextState類的成員函數RestoreTextureUnitBindings在恢複一個紋理單元的某一個紋理目标之前,首先判斷這個紋理目标在前後兩個虛拟OpenGL上下文中是否發生了變化。隻有發生了變化的紋理目标,才會調用OpenGL函數glBindTexture進行恢複。

       關于紋理單元和紋理目标的關系,以及它們的狀态切換機制,可以參考這篇文章:Differences and relationship between glActiveTexture and glBindTexture。

       回到ContextState類的RestoreAllTextureUnitBindings中,切換了每一個紋理單元的紋理目标之後,接下來它調用成員函數RestoreActiveTexture設定切換後的虛拟OpenGL上下文目前激活的紋理單元,如下所示:

void ContextState::RestoreActiveTexture() const {
  glActiveTexture(GL_TEXTURE0 + active_texture_unit);
}
           

       這個函數定義在檔案external/chromium_org/gpu/command_buffer/service/context_state.cc中。

       一個虛拟OpenGL上下文目前激活的紋理單元記錄在成員變量active_texture_unit中,通過調用OpenGL函數glActiveTexture即可激活它。

       這樣,我們就分析完成了OpenGL上下文的切換過程,包括真實OpenGL上下文和虛拟OpenGL上下文的切換過程。回到前面分析的GpuChannel類的成員函數HandleMessage中,我們繼續分析OpenGL上下文排程過程中涉及到的其它兩個問題:

       1. 一個OpenGL上下文在什麼情況下會自行放棄度排程。

       2. 一個OpenGL上下文在什麼情況下會被搶占排程。

       我們首先分析一個OpenGL上下文在什麼情況下會自行放棄度排程。從前面分析的GpuChannel類的成員函數HandleMessage的實作可以知道,當一個OpenGL上下文自行放棄排程時,調用對應的GpuCommandBufferStub對象的成員函數IsScheduled得到的傳回值等于false。

       GpuCommandBufferStub類的成員函數IsScheduled的實作如下所示:

bool GpuCommandBufferStub::IsScheduled() {
  return (!scheduler_.get() || scheduler_->IsScheduled());
}
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_command_buffer_stub.cc中。

       GpuCommandBufferStub類的成員函數IsScheduled首先判斷成員變量scheduler_是否指向了一個GpuScheduler對象。如果指向了一個GpuScheduler對象,那麼就調用該GpuScheduler對象的成員函數IsScheduled判斷一個OpenGL上下文是否自行放棄了排程。否則的話,就是認為一個OpenGL上下文沒有自行放棄排程,即GpuCommandBufferStub類的成員函數IsScheduled的傳回值為true。

       GpuScheduler類的成員函數IsScheduled的實作如下所示:

bool GpuScheduler::IsScheduled() {
  return unscheduled_count_ == 0;
}
           

       這個函數定義在檔案external/chromium_org/gpu/command_buffer/service/gpu_scheduler.cc中。

       GpuScheduler類的成員變量unscheduled_count_描述的是一個OpenGL上下文請求放棄排程的計數,當它的值不等于0的時候,就表示一個OpenGL上下文自行放棄了排程。那麼GpuScheduler類的成員變量unscheduled_count_的值是什麼時候被設定的呢?

       GpuScheduler類的成員變量unscheduled_count_的值在構造函數中初始化為0,如下所示:

GpuScheduler::GpuScheduler(CommandBufferServiceBase* command_buffer,
                           AsyncAPIInterface* handler,
                           gles2::GLES2Decoder* decoder)
    : ......,
      unscheduled_count_(0),
      rescheduled_count_(0),
      ...... {}
           

       這個函數定義在檔案external/chromium_org/gpu/command_buffer/service/gpu_scheduler.cc中。

       與OpenGL上下文自行放棄排程相關的參數還包括GpuScheduler類的另外一個成員變量rescheduled_count_,它的值也是初始化為0。

       我們可以通過調用GpuScheduler類的成員函數SetScheduled來設定GpuScheduler類的成員變量unscheduled_count_和rescheduled_count_的值,如下所示:

void GpuScheduler::SetScheduled(bool scheduled) {
  ......

  if (scheduled) {
    // If the scheduler was rescheduled after a timeout, ignore the subsequent
    // calls to SetScheduled when they eventually arrive until they are all
    // accounted for.
    if (rescheduled_count_ > 0) {
      --rescheduled_count_;
      return;
    } else {
      --unscheduled_count_;
    }

    ......

    if (unscheduled_count_ == 0) {
      ......

      if (!scheduling_changed_callback_.is_null())
        scheduling_changed_callback_.Run(true);
    }
  } else {
    ++unscheduled_count_;
    if (unscheduled_count_ == 1) {
      ......

#if defined(OS_WIN)
      if (base::win::GetVersion() < base::win::VERSION_VISTA) {
        // When the scheduler transitions from scheduled to unscheduled, post a
        // delayed task that it will force it back into a scheduled state after
        // a timeout. This should only be necessary on pre-Vista.
        base::MessageLoop::current()->PostDelayedTask(
            FROM_HERE,
            base::Bind(&GpuScheduler::RescheduleTimeOut,
                       reschedule_task_factory_.GetWeakPtr()),
            base::TimeDelta::FromMilliseconds(kRescheduleTimeOutDelay));
      }
#endif

      if (!scheduling_changed_callback_.is_null())
        scheduling_changed_callback_.Run(false);
    }
  }
}
           

       這個函數定義在檔案external/chromium_org/gpu/command_buffer/service/gpu_scheduler.cc中。

       GpuScheduler類的成員變量rescheduled_count_主要是用在Vista版本之前的Windows系統上。為了簡單起見,我們首先忽略GpuScheduler類的成員函數SetScheduled對這個成員變量的處理,即假設它的值維持為初始值0。

       GpuScheduler類的成員函數SetScheduled根據參數scheduled的值相應地調整成員變量unscheduled_count_的值:

       1. 當參數scheduled的值等于true時,就将成員變量unscheduled_count_的值減少1。若減少後的值等于0,就說明一個OpenGL上下文從放棄排程狀态進入到了請求排程狀态。這時候如果成員變量scheduling_changed_callback_指向了一個Callback對象,那麼就調用該Callback對象的成員函數Run,并且傳遞一個true參數給它。

       2. 當參數scheduled的值等于false時,就将成員變量unscheduled_count_的值增加1。若增加後的值等于1,就說明一個OpenGL上下文從放棄排程狀态進入到了請求排程狀态。這時候如果成員變量scheduling_changed_callback_指向了一個Callback對象,那麼就調用該Callback對象的成員函數Run,并且傳遞一個false參數給它。

       關于GpuScheduler類的成員變量scheduling_changed_callback_的作用,接下來我們分析OpenGL上下文的搶占排程過程時再分析。

       現在我們再考慮GpuScheduler類的成員變量rescheduled_count_。一個OpenGL上下文進入到了放棄排程狀态後,以後在某個時刻應該恢複為請求排程狀态。為了防止一個OpenGL上下文進入到放棄排程狀态後,很長時間都沒有恢複為請求排程狀态,Chromium會給該OpenGL上下文設定一個恢複為請求排程狀态的逾時時間。當發生逾時時,GpuScheduler類的成員函數RescheduleTimeOut會被調用,如下所示:

void GpuScheduler::RescheduleTimeOut() {
  int new_count = unscheduled_count_ + rescheduled_count_;

  rescheduled_count_ = 0;

  while (unscheduled_count_)
    SetScheduled(true);

  rescheduled_count_ = new_count;
}
           

       這個函數定義在檔案external/chromium_org/gpu/command_buffer/service/gpu_scheduler.cc中。

       GpuScheduler類的成員函數RescheduleTimeOut先将成員變量unscheduled_count_和rescheduled_count_相加後的值記錄在變量new_count中,然後将成員變量rescheduled_count_的值設定為0,目的是為了後面通過不斷調用前面分析過的成員函數SetScheduled将成員變量unscheduled_count_的值設定為0,進而将一個OpenGL上下文從放棄排程狀态切換為請求排程狀态。

       最後,GpuScheduler類的成員函數RescheduleTimeOut又将前面計算出來的new_count值儲存在成員變量rescheduled_count_中,這樣會使得GpuScheduler類的成員函數SetScheduled在恢複請求排程狀态逾時後參數為true的前若幹次調用,僅僅是減少成員變量rescheduled_count_的值,而不會影響成員變量unscheduled_count_的值,因為對成員變量unscheduled_count_的影響已經提前在GpuScheduler類的成員函數RescheduleTimeOut中實施過了。

       上面要表達的意思就是,如果一個OpenGL上下文調用GpuScheduler類的成員函數SetScheduled進入放棄排程狀态,并且在逾時之前,沒有再次調用GpuScheduler類的成員函數SetScheduled進入請求排程狀态,那麼在逾時之後再調用GpuScheduler類的成員函數SetScheduled進入請求排程狀态,那麼該次調用不會導緻該OpenGL上下文的排程狀态發生變化,因為該變化已經在GpuScheduler類的成員函數RescheduleTimeOut實作過了。

       現在,關于OpenGL上下文什麼情況下會自行放棄排程的問題,就變成了GpuScheduler類的成員函數SetScheduled什麼時候會以參數false被調用。這涉及到Chromium硬體加速渲染中的同步點(Sync Point)機制。

       同步點用來在不同的OpenGL上下文中同步資源通路。例如,WebGL端将自己的UI繪制在一個紋理中,然後會将這個紋理以郵箱(Mailbox)的形式交給Browser端OpenGL上下文合成到浏覽器視窗中顯示。Browser端OpenGL上下文在合成WebGL端交給它的紋理之前,必須要保證這個紋理是已經繪制完成了的。實際上就是要保證兩個不同OpenGL上下文的GPU指令緩沖區的指令的執行順序,如圖5所示:

Chromium硬體加速渲染的OpenGL上下文排程過程分析

圖5 Sync Point

       假設WebGL端通過A1、A2、A3和A4指令将UI繪制在一個紋理上,Browser端通過B3指令合成WebGL端生成的紋理,這時候Browser端在GPU指令緩沖區中寫入B3指令之前,先寫入一個WaitSyncPointCHROMIUM指令。

       WebGL端向GPU指令緩沖區寫入A1、A2、A3和A4指令之後,先後向GPU程序中的GPU線程發送一個GpuCommandBufferMsg_AsyncFlush消息和一個GpuCommandBufferMsg_InsertSyncPoint消息。其中,GpuCommandBufferMsg_AsyncFlush消息用來通知GPU線程執行A1、A2、A3和A4指令,GpuCommandBufferMsg_InsertSyncPoint消息用來插入一個Sync Point。這兩個消息被GPU程序中的GPU線程依次處理,這樣可以保證當GpuCommandBufferMsg_InsertSyncPoint消息被處理時,WebGL端已經将UI繪制好在紋理上了。

       假設GPU線程在處理Browser端的GPU指令緩沖區的B3指令時,WebGL端發送給GPU線程的GpuCommandBufferMsg_AsyncFlush消息還沒有被處理,這時候Browser端OpenGL上下文就會自動放棄排程。等到WebGL端發送給GPU線程的GpuCommandBufferMsg_InsertSyncPoint消息被處理之後,Browser端OpenGL上下文就會重新請求排程,進而保證它的B3指令執行時,可以通路到WebGL端已經繪制好了UI的紋理。

       接下來,我們就分析WebGL端向GPU線程請求插入Sync Point和Browser端等待WebGL端插入的Sync Point的過程。

       WebGL端是通過調用GLES2Implementation類的成員函數InsertSyncPointCHROMIUM向GPU線程請求插入Sync Point的,如下所示:

GLuint GLES2Implementation::InsertSyncPointCHROMIUM() {
  ......
  helper_->CommandBufferHelper::Flush();
  return gpu_control_->InsertSyncPoint();
}
           

       這個函數定義在檔案external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。

       GLES2Implementation類的成員函數InsertSyncPointCHROMIUM首先調用成員變量helper_描述的一個GLES2CmdHelper對象從父類CommandBufferHelper繼承下來的成員函數Flush請求GPU程序執行其GPU指令緩沖區的指令,相當于就是執行圖5所示的A1~A4指令。

       在前面Chromium硬體加速渲染的OpenGL指令執行過程分析一文中,我們已經分析過CommandBufferHelper類的成員函數Flush的實作了,它主要就是向GPU程序發送一個GpuCommandBufferMsg_AsyncFlush消息。這個GpuCommandBufferMsg_AsyncFlush消息被封裝一個Task,發送到GPU線程的消息隊列去等待處理,最終被GpuChannel類的成員函數HandleMessage分發給GpuCommandBufferStub類的成員函數OnMessageReceived處理,後者又将該消息分發給另外一個成員函數OnAsyncFlush進一步處理。

       GLES2Implementation類的成員函數InsertSyncPointCHROMIUM接下來又調用成員變量gpu_control_指向的一個CommandBufferProxyImpl對象的成員函數InsertSyncPoint請求GPU程序在目前正在處理的OpenGL上下文中插入一個Sync Point。

       CommandBufferProxyImpl類的成員函數InsertSyncPoint的實作如下所示:

uint32 CommandBufferProxyImpl::InsertSyncPoint() {
  ......

  uint32 sync_point = 0;
  Send(new GpuCommandBufferMsg_InsertSyncPoint(route_id_, &sync_point));
  return sync_point;
}
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/client/command_buffer_proxy_impl.cc中。

       CommandBufferProxyImpl類的成員函數InsertSyncPoint向GPU程序發送一個類型為GpuCommandBufferMsg_InsertSyncPoint的GPU消息。GPU程序在處理這個GPU消息的時候,會生成一個類型為uint32的Sync Point,并且将該Sync Point傳回給調用者。

       從前面分析的GpuChannel類的成員函數Init可以知道,發送給GPU程序的GPU消息首先由注冊在GPU通道中的一個類型為GpuChannelMessageFilter的Message Filter處理,也就是由GpuChannelMessageFilter類的成員函數OnMessageReceived先處理。

      GpuChannelMessageFilter類的成員函數OnMessageReceived會處理類型為GpuCommandBufferMsg_InsertSyncPoint的GPU消息,如下所示:

class GpuChannelMessageFilter : public IPC::MessageFilter {
 public:
  ......

  virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
    ......

    bool handled = false;
    ......

    if (message.type() == GpuCommandBufferMsg_InsertSyncPoint::ID) {
      uint32 sync_point = sync_point_manager_->GenerateSyncPoint();
      IPC::Message* reply = IPC::SyncMessage::GenerateReply(&message);
      GpuCommandBufferMsg_InsertSyncPoint::WriteReplyParams(reply, sync_point);
      Send(reply);
      message_loop_->PostTask(FROM_HERE, base::Bind(
          &GpuChannelMessageFilter::InsertSyncPointOnMainThread,
          gpu_channel_,
          sync_point_manager_,
          message.routing_id(),
          sync_point));
      handled = true;
    }
    return handled;
  }

  ......
};
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

       GpuChannelMessageFilter類的成員函數OnMessageReceived首先調用成員變量sync_point_manager_描述的一個SyncPointManager對象的成員函數GenerateSyncPoint生成一個Sync Point,如下所示:

uint32 SyncPointManager::GenerateSyncPoint() {
  base::AutoLock lock(lock_);
  uint32 sync_point = next_sync_point_++;
  // When an integer overflow occurs, don't return 0.
  if (!sync_point)
    sync_point = next_sync_point_++;

  // Note: wrapping would take days for a buggy/compromized renderer that would
  // insert sync points in a loop, but if that were to happen, better explicitly
  // crash the GPU process than risk worse.
  // For normal operation (at most a few per frame), it would take ~a year to
  // wrap.
  CHECK(sync_point_map_.find(sync_point) == sync_point_map_.end());
  sync_point_map_.insert(std::make_pair(sync_point, ClosureList()));
  return sync_point;
}
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/sync_point_manager.cc中。

       SyncPointManager類的成員函數GenerateSyncPoint主要就是通過遞增成員變量next_sync_point_的值來生成Sync Point,并且在将生成的Sync Point傳回給調用者之前,将其儲存在成員變量sync_point_map_描述的一個Hash Map中。這個Hash Map以Sync Point為Key,儲存了一個Closure List。後面我們會看到這個Closure List的作用。

       回到GpuChannelMessageFilter類的成員函數OnMessageReceived中,調用SyncPointManager類的成員函數GenerateSyncPoint生成了一個Sync Point之後,就向發送GpuCommandBufferMsg_InsertSyncPoint消息的Client端回複一個消息,該回複消息封裝了前面生成的Sync Point。

       GpuChannelMessageFilter類的成員變量message_loop_描述的是GPU線程的消息循環,GpuChannelMessageFilter類的成員函數OnMessageReceived最後向該消息循環使用的消息隊列發送一個Task,該Task綁定了GpuChannelMessageFilter類的成員函數InsertSyncPointOnMainThread,負責處理前面接收到的GpuCommandBufferMsg_InsertSyncPoint消息。這相當于是GPU線程的消息隊列多了一個GpuCommandBufferMsg_InsertSyncPoint消息需要處理。

       根據前面的分析,這裡有一點需要注意,GPU線程在接收到GpuCommandBufferMsg_InsertSyncPoint消息之前,接收到了一個GpuCommandBufferMsg_AsyncFlush消息。該消息也被封裝成一個Task發送到了GPU線程的消息隊列中。這意味着GPU線程先處理GpuCommandBufferMsg_AsyncFlush消息,再處理GpuCommandBufferMsg_InsertSyncPoint消息。是以就可以保證,當GpuChannelMessageFilter類的成員函數InsertSyncPointOnMainThread被調用時,在向目前正在處理的OpenGL上下文插入的Sync Point之前送出的GPU指令均已處理完畢。在我們這個情景中,就相當于是WebGL端已經将UI繪制好在一個紋理中了,即圖5所示的A1~A4指令已處理完畢。

       GpuChannelMessageFilter類的成員函數InsertSyncPointOnMainThread的實作如下所示:

class GpuChannelMessageFilter : public IPC::MessageFilter {
  ......

 private:
  ......

  static void InsertSyncPointOnMainThread(
      base::WeakPtr<GpuChannel> gpu_channel,
      scoped_refptr<SyncPointManager> manager,
      int32 routing_id,
      uint32 sync_point) {
    ......
    if (gpu_channel) {
      GpuCommandBufferStub* stub = gpu_channel->LookupCommandBuffer(routing_id);
      if (stub) {
        stub->AddSyncPoint(sync_point);
        GpuCommandBufferMsg_RetireSyncPoint message(routing_id, sync_point);
        gpu_channel->OnMessageReceived(message);
        return;
      } 
      ......
    }
    ......
  }

  ......
};
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

       參數gpu_channel描述的是接收到GpuCommandBufferMsg_InsertSyncPoint消息的GPU通道。參數routing_id描述的是負責處理GpuCommandBufferMsg_InsertSyncPoint消息的一個GpuCommandBufferStub對象的Routing ID。根據這個Routing ID,就可以在參數gpu_channel描述的GPU通道中找到對應的GpuCommandBufferStub對象。這個GpuCommandBufferStub對象描述的就是發送GpuCommandBufferMsg_InsertSyncPoint消息的Client端的OpenGL上下文。

       找到了目标GpuCommandBufferStub對象之後,GpuChannelMessageFilter類的成員函數InsertSyncPointOnMainThread調用它的成員函數AddSyncPoint,以便将參數sync_point描述的Sync Point交給它處理。

       GpuCommandBufferStub類的成員函數AddSyncPoint的實作如下所示:

void GpuCommandBufferStub::AddSyncPoint(uint32 sync_point) {
  sync_points_.push_back(sync_point);
}
           

      這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_command_buffer_stub.cc中。

      GpuCommandBufferStub類的成員函數AddSyncPoint将參數sync_point描述的Sync Point儲存在成員變量sync_points_描述的一個std::deque中。

      回到GpuChannelMessageFilter類的成員函數InsertSyncPointOnMainThread,它将參數sync_point描述的Sync Point儲存在目标GpuCommandBufferStub對象的成員變量sync_points_描述的一個std::deque中之後,再通過參數gpu_channel描述的GpuChannel對象的成員函數OnMessageReceived向目标GpuCommandBufferStub對象分發一個GpuCommandBufferMsg_RetireSyncPoint消息。

       GpuCommandBufferMsg_RetireSyncPoint消息由GpuCommandBufferStub類的成員函數OnMessageReceived接收,如下所示:

bool GpuCommandBufferStub::OnMessageReceived(const IPC::Message& message) {
  ......

  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(GpuCommandBufferStub, message)
    ......
    IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_RetireSyncPoint,
                        OnRetireSyncPoint)
    ......
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()

  ......

  return handled;
}
           

      這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_command_buffer_stub.cc中。

      從這裡可以看到,GpuCommandBufferStub類的成員函數OnMessageReceived将GpuCommandBufferMsg_RetireSyncPoint消息分發給成員函數OnRetireSyncPoint處理。

      GpuCommandBufferStub類的成員函數OnRetireSyncPoint的實作如下所示:

void GpuCommandBufferStub::OnRetireSyncPoint(uint32 sync_point) {
  DCHECK(!sync_points_.empty() && sync_points_.front() == sync_point);
  sync_points_.pop_front();
  if (context_group_->mailbox_manager()->UsesSync() && MakeCurrent())
    context_group_->mailbox_manager()->PushTextureUpdates();
  GpuChannelManager* manager = channel_->gpu_channel_manager();
  manager->sync_point_manager()->RetireSyncPoint(sync_point);
}
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_command_buffer_stub.cc中。

       從前面的分析可以知道,GpuCommandBufferStub類的成員變量sync_points_儲存了在目前正在處理的OpenGL上下文中插入的Sync Point,并且這些Sync Point都是還沒有被Retired的。一個Sync Point被Retired,是指該Sync Point所插入的OpenGL上下文,在被插入Sync Point之前的所有GPU指令都已經被處理。

       GpuCommandBufferStub類的成員函數OnRetireSyncPoint所做的事情就是Retire參數sync_point描述的Sync Point,注意,插入到一個OpenGL上下文中的Sync Point,是按照插入順序依次被Retired的。

       GpuCommandBufferStub類的成員函數OnRetireSyncPoint是如何Retire一個Sync Point的呢?它首先獲得一個GpuChannelManager對象。從前面Chromium的GPU程序啟動過程分析一文可以知道,這個GpuChannelManager對象在GPU程序中是一個單例。獲得了GPU程序中的GpuChannelManager單例對象之後,再通過它獲得一個SyncPointManager對象。這個SyncPointManager對象與前面分析的GpuChannelMessageFilter類的成員變量sync_point_manager_指向的SyncPointManager對象是相同的。這意味着這個SyncPointManager對象在GPU程序中也是一個單例。獲得了GPU程序中的SyncPointManager單例對象之後,GpuCommandBufferStub類的成員函數OnRetireSyncPoint就調用它的成員函數RetireSyncPoint将參數sync_point描述的Sync Point設定為被Retired的。

       SyncPointManager類的成員函數RetireSyncPoint的實作如下所示:

void SyncPointManager::RetireSyncPoint(uint32 sync_point) {
  ......
  ClosureList list;
  {
    base::AutoLock lock(lock_);
    SyncPointMap::iterator it = sync_point_map_.find(sync_point);
    ......
    list.swap(it->second);
    sync_point_map_.erase(it);
  }
  for (ClosureList::iterator i = list.begin(); i != list.end(); ++i)
    i->Run();
}
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/sync_point_manager.cc中。

       儲存在SyncPointManager類的成員變量sync_point_map_描述的一個Hash Map中的Sync Point都是還沒有被Retired的。一旦一個Sync Point被Retired,它就需要從該Hash Map移除。在移除的時候,如果這個Sync Point關聯的Closure List不為空,那麼儲存在該Closure List中的每一個Closure都會被執行,也就是它們的成員函數Run會被執行。後面我們就會看到,如何将一個Closure添加到一個Sync Point關聯的Closure List中去。

       以上就是SyncPointManager類的成員函數RetireSyncPoint的執行邏輯。回到GpuCommandBufferStub類的成員函數OnRetireSyncPoint中,我們注意以下兩行代碼:

if (context_group_->mailbox_manager()->UsesSync() && MakeCurrent())
    context_group_->mailbox_manager()->PushTextureUpdates();
           

       GpuCommandBufferStub類的成員變量context_group_描述的是一個資源共享組。關于資源共享組,可以參考前面 Chromium硬體加速渲染的OpenGL指令執行過程分析一文。每一個資源共享組都關聯一個MailboxManager對象。這個MailboxManager對象用來實作在Chromium的Mailbox機制時。Chromium的Mailbox機制用來在不同的OpenGL上下文之間傳遞紋理。這一點我們在接下來一篇文章中再詳細分析。

       将一個紋理從一個OpenGL上下文傳遞到另一個OpenGL上下文,需要保證另一個OpenGL上下文能夠通路該紋理。如果這兩個OpenGL上下文是在同一個共享組的,那麼很顯然能保證被傳遞的紋理能夠被另一個OpenGL上下文通路。如果這兩個OpenGL上下文不是在同一個共享組的,那麼另一個OpenGL上下文就不能通路傳遞給它的紋理了。這時候怎麼辦呢?回憶前面Chromium硬體加速渲染的GPU資料上傳機制分析一文,Chromium在執行異步紋理上傳時,是通過EGLImageKHR技術實作在兩個不是在同一個共享組的OpenGL上下文之間共享同一個紋理的。Chromium的Mailbox機制也是通過GLImageKHR技術實作在兩個不是在同一個共享組的OpenGL上下文之間共享同一個紋理的。

       從前面Chromium硬體加速渲染的OpenGL上下文建立過程分析一文可以知道,Chromium中的所有OpenGL上下文都是在同一個共享組的,是以在實作Mailbox機制時,就不需要通過EGLImageKHR技術來實作不同OpenGL上下文之間的紋理共享。但是在基于Chromium實作的WebView,它裡面建立的OpenGL上下文不是在同一個共享組的,是以在實作Mailbox機制時,就需要通過EGLImageKHR技術來實作不同OpenGL上下文之間的紋理共享。

       當需要通過EGLImageKHR技術來實作不同OpenGL上下文之間的紋理共享時,調用上述提到的MailboxManager對象的成員函數UsesSync時,得到的傳回值就為true。這時候需要将目前正在處理的OpenGL上下文設定為GPU線程目前使用的OpenGL上下文,以及将正在傳遞的紋理封裝成一個EGLImageKHR對象。這是通過調用上述提到的MailboxManager對象的成員函數PushTexturesUpdates實作的。将一個紋理封裝成一個EGLImageKHR對象的過程,可以參考前面Chromium硬體加速渲染的GPU資料上傳機制分析一文,這裡就不進行具體分析了。

       以上就是在一個OpenGL上下文中插入Sync Point的過程。在一個OpenGL上下文中插入的Sync Point,會傳遞給另外一個OpenGL上下文。一個OpenGL上下文獲得了一個Sync Point之後,就可以通過調用GLES2Implementation類的成員函數WaitSyncPointCHROMIUM向其GPU指令緩沖區寫入一個gles2::cmds::WaitSyncPointCHROMIUM指令。當該gles2::cmds::WaitSyncPointCHROMIUM指令被執行時,如果它所封裝的Sync Point還沒有被Retired,那麼它所屬的OpenGL上下文就必須進行等待,直到它封裝的Sync Point被Retired為止。等待的形式就是讓OpenGL上下文自動放棄排程。接下來我們就從GLES2Implementation類的成員函數WaitSyncPointCHROMIUM開始,分析一個OpenGL上下文等待一個Sync Point被Retired的過程。

       GLES2Implementation類的成員函數WaitSyncPointCHROMIUM的實作如下所示:

void GLES2Implementation::WaitSyncPointCHROMIUM(GLuint sync_point) {
  ......
  helper_->WaitSyncPointCHROMIUM(sync_point);
  CheckGLError();
}
           

      這個函數定義在檔案external/chromium_org/gpu/command_buffer/client/gles2_implementation_impl_autogen.h中。

      GLES2Implementation類的成員函數WaitSyncPointCHROMIUM調用成員變量helper_描述的一個GLES2CmdHelper對象的成員函數WaitSyncPointCHROMIUM向目前正在處理的OpenGL上下文的GPU指令緩沖區寫入一個gles2::cmds::WaitSyncPointCHROMIUM指令。

      從前面Chromium硬體加速渲染的OpenGL指令執行過程分析一文可以知道,GPU指令緩沖區中的gles2::cmds::WaitSyncPointCHROMIUM指令将會被GLES2DecoderImpl類的成員函數HandleWaitSyncPointCHROMIUM進行處理,處理過程如下所示:

error::Error GLES2DecoderImpl::HandleWaitSyncPointCHROMIUM(
    uint32 immediate_data_size, const cmds::WaitSyncPointCHROMIUM& c) {
  group_->mailbox_manager()->PullTextureUpdates();
  if (wait_sync_point_callback_.is_null())
    return error::kNoError;

  return wait_sync_point_callback_.Run(c.sync_point) ?
      error::kNoError : error::kDeferCommandUntilLater;
}
           

       這個函數定義在檔案external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。

       GLES2DecoderImpl類的成員函數HandleWaitSyncPointCHROMIUM首先獲得成員變量group_描述的資源共享組關聯的一個MailboxManager對象,然後調用這個MailboxManager對象的成員函數PullTexturesUpdates,目的是将那些從其它OpenGL上下文傳遞過來的紋理實作在目前正在處理的OpenGL上下文中共享。

       MailboxManager類的成員函數PullTexturesUpdates所做的事情與前面提到的它的另外一個成員函數PushTexturesUpdates剛好相反,它根據後者建立的EGLImageKHR對象在目前正在處理的OpenGL上下文中建立一個紋理,進而實作紋理共享。

       GLES2DecoderImpl類的成員函數HandleWaitSyncPointCHROMIUM接下來檢查成員變量wait_sync_point_callback_是否指向了一個Callback對象。如果指向了一個Callback對象,那麼就調用它的成員函數Run。

       GLES2DecoderImpl類的成員變量wait_sync_point_callback_指向的Callback對象是什麼呢?從前面Chromium硬體加速渲染的OpenGL指令執行過程分析一文可以知道,WebGL端、Render端和Browser端在建立OpenGL上下文的過程中,會向GPU程序發送一個類型為GpuCommandBufferMsg_Initialize的IPC消息。這個IPC消息由GpuCommandBufferStub類的成員函數OnInitialize進行處理。

       GpuCommandBufferStub類的成員函數OnInitialize在處理類型為GpuCommandBufferMsg_Initialize的IPC消息時,會建立一個GLES2DecoderImpl對象,并且調用這個GLES2DecoderImpl對象的成員函數SetWaitSyncPointCallback設定它的成員變量wait_sync_point_callback_,如下所示:

void GpuCommandBufferStub::OnInitialize(
    base::SharedMemoryHandle shared_state_handle,
    IPC::Message* reply_message) {
  ......

  decoder_.reset(::gpu::gles2::GLES2Decoder::Create(context_group_.get()));
  
  ......

  decoder_->SetWaitSyncPointCallback(
      base::Bind(&GpuCommandBufferStub::OnWaitSyncPoint,
                 base::Unretained(this)));
  
  ......
}
           

      這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_command_buffer_stub.cc中。

      GLES2DecoderImpl對象的成員函數SetWaitSyncPointCallback的實作如下所示:

void GLES2DecoderImpl::SetWaitSyncPointCallback(
    const WaitSyncPointCallback& callback) {
  wait_sync_point_callback_ = callback;
}
           

       這個函數定義在檔案external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc。

       這意味着GLES2DecoderImpl對象的成員變量wait_sync_point_callback_指向的Callback對象綁定的函數是GpuCommandBufferStub類的成員函數OnWaitSyncPoint,是以GLES2DecoderImpl類的成員函數HandleWaitSyncPointCHROMIUM在處理gles2::cmds::WaitSyncPointCHROMIUM指令時,會調用到GpuCommandBufferStub類的成員函數OnWaitSyncPoint。

       GpuCommandBufferStub類的成員函數OnWaitSyncPoint的實作如下所示:

bool GpuCommandBufferStub::OnWaitSyncPoint(uint32 sync_point) {
  ......

  GpuChannelManager* manager = channel_->gpu_channel_manager();
  if (manager->sync_point_manager()->IsSyncPointRetired(sync_point))
    return true;

  ......

  scheduler_->SetScheduled(false);
  ......
  manager->sync_point_manager()->AddSyncPointCallback(
      sync_point,
      base::Bind(&GpuCommandBufferStub::OnSyncPointRetired,
                 this->AsWeakPtr()));
  return scheduler_->IsScheduled();
}
           

      這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_command_buffer_stub.cc中。

      根據前面的分析,GpuCommandBufferStub類的成員函數OnWaitSyncPoint所要做的事情就是讓目前正在處理的OpenGL上下文等待參數sync_point描述的Sync Point被Retired。是以,GpuCommandBufferStub類的成員函數OnWaitSyncPoint首先獲得GPU程序中的一個SyncPointManager單例對象,并且調用這個SyncPointManager單例對象的成員函數IsSyncPointRetired判斷參數sync_point描述的Sync Point是否已經被Retired,如果已經被Retired,那麼目前正在處理的OpenGL上下文就不用等待了,也就是不用自動放棄排程了。

      SyncPointManager類的成員函數IsSyncPointRetired的實作如下所示:

bool SyncPointManager::IsSyncPointRetired(uint32 sync_point) {
  ......
  {
    base::AutoLock lock(lock_);
    SyncPointMap::iterator it = sync_point_map_.find(sync_point);
    return it == sync_point_map_.end();
  }
}
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/sync_point_manager.cc中。

       當參數sync_point描述的Sync Point不在成員變量sync_point_map_描述的一個Hash Map時,SyncPointManager類的成員函數IsSyncPointRetired就認為它已經被Retired了。

       回到GpuCommandBufferStub類的成員函數OnWaitSyncPoint中,如果參數sync_point描述的Sync Point還沒有被Retired,那麼它就會調用成員變量scheduler_描述的一個GpuScheduler對象的成員函數SetScheduled将目前正在處理的OpenGL上下文設定為自動放棄排程狀态。從前面的分析可以知道,當一個OpenGL上下文自動放棄排程時,它就不能夠處理Client端發送過來的GPU消息。

       GpuCommandBufferStub類的成員函數OnWaitSyncPoint最後還會調用前面獲得的SyncPointManager單例對象的成員函數AddSyncPointCallback為參數sync_point描述的Sync Point增加一個Callback對象,該Callback對象綁定的函數為GpuCommandBufferStub類的成員函數OnSyncPointRetired。

       SyncPointManager類的成員函數AddSyncPointCallback的實作如下所示:

void SyncPointManager::AddSyncPointCallback(uint32 sync_point,
                                            const base::Closure& callback) {
  ......
  {
    base::AutoLock lock(lock_);
    SyncPointMap::iterator it = sync_point_map_.find(sync_point);
    if (it != sync_point_map_.end()) {
      it->second.push_back(callback);
      return;
    }
  }
  callback.Run();
} 
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/sync_point_manager.cc中。

       SyncPointManager類的成員函數AddSyncPointCallback首先根據參數sync_point在成員變量sync_point_map_描述的一個Hash Map中查找一個對應的Sync Point。如果存在,那麼就将參數callback描述的Closure對象儲存在與該Sync Point關聯的一個Closure List中。否則的話,就直接調用參數callback描述的Closure對象的成員函數Run,表示參數參數sync_point描述的Sync Point已經被Retired了。

       假設這時候參數sync_point描述的Sync Point還沒有被Retired,那麼根據前面分析的SyncPointManager類的成員函數RetireSyncPoint,當該Sync Point被Retired時,儲存在與它關聯的Closure List中的Closure對象,就會被執行,也就是會調用GpuCommandBufferStub類的成員函數OnSyncPointRetired。

       GpuCommandBufferStub類的成員函數OnSyncPointRetired的實作如下所示:

void GpuCommandBufferStub::OnSyncPointRetired() {
  ......
  scheduler_->SetScheduled(true);
}
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_command_buffer_stub.cc中。

       GpuCommandBufferStub類的成員函數OnSyncPointRetired所做的事情就是将目前正在處理的OpenGL上下文設定為請求排程狀态,使得它可以處理Client端發送過來的GPU消息。

       這樣,我們就分析完成一個OpenGL上下文自行放棄度排程以及重新請求排程的過程。接下來我們繼續分析OpenGL上下文被搶占排程的過程,就是Render端OpenGL上下文被

Browser端OpenGL上下文搶占排程的過程。

       我們從Render端OpenGL上下文的繪圖表面的建立過程開始分析。從前面Chromium硬體加速渲染的OpenGL上下文繪圖表面建立過程分析一文可以知道,Render端OpenGL上下文的繪圖表面句柄是通過調用RenderWidgetHostViewAndroid類的成員函數GetCompositingSurface建立的,如下所示:

gfx::GLSurfaceHandle RenderWidgetHostViewAndroid::GetCompositingSurface() {
  gfx::GLSurfaceHandle handle =
      gfx::GLSurfaceHandle(gfx::kNullPluginWindow, gfx::NATIVE_TRANSPORT);
  if (CompositorImpl::IsInitialized()) {
    handle.parent_client_id =
        ImageTransportFactoryAndroid::GetInstance()->GetChannelID();
  }
  return handle;
}
           

       這個函數定義在檔案external/chromium_org/content/browser/renderer_host/render_widget_host_view_android.cc中。

       RenderWidgetHostViewAndroid類的成員函數GetCompositingSurface傳回的繪圖表面句柄用一個gfx::GLSurfaceHandle對象描述,這個gfx::GLSurfaceHandle對象的成員變量parent_client_id被設定為Browser程序中的一個ImageTransportFactoryAndroid單例對象的成員函數GetChannelID的傳回值。

       ImageTransportFactoryAndroid類的成員函數GetChannelID的實作如下所示:

class CmdBufferImageTransportFactory : public ImageTransportFactoryAndroid {
 public:
  ......

  virtual uint32 GetChannelID() OVERRIDE {
    return BrowserGpuChannelHostFactory::instance()->GetGpuChannelId();
  }

 ......
};
           

       這個函數定義在檔案external/chromium_org/content/browser/renderer_host/image_transport_factory_android.cc中。

       ImageTransportFactoryAndroid類的成員函數GetChannelID通過調用Browser程序中的一個BrowserGpuChannelHostFactory單例對象的成員函數GetGpuChannelId的獲得一個GPU通道ID,然後将這個ID傳回給調用者。

       BrowserGpuChannelHostFactory類的成員函數GetGpuChannelId的實作如下所示:

class CONTENT_EXPORT BrowserGpuChannelHostFactory
    : public GpuChannelHostFactory,
      public GpuMemoryBufferFactoryHost {
 public:
  ......

  int GetGpuChannelId() { return gpu_client_id_; }

  ......

 private:
  ......

  const int gpu_client_id_;

  ......
};
           

       這個函數定義在檔案external/chromium_org/content/browser/gpu/browser_gpu_channel_host_factory.h中。

       BrowserGpuChannelHostFactory類的成員函數GetGpuChannelId傳回的是成員變量gpu_client_id_的值。從前面Chromium的GPU程序啟動過程分析一文可以知道,BrowserGpuChannelHostFactory類的成員變量gpu_client_id_描述的是Browser程序與GPU程序之間的GPU通道的ID。這也意味着用來描述Render端OpenGL上下文的繪圖表面句柄的gfx::GLSurfaceHandle對象的成員變量parent_client_id指向的是Browser程序與GPU程序之間的GPU通道的ID。

       從前面Chromium硬體加速渲染的OpenGL上下文建立過程分析一文可以知道,前面通過調用RenderWidgetHostViewAndroid類的成員函數GetCompositingSurface建立的gfx::GLSurfaceHandle對象将會傳遞給GPU程序,用來建立Render端OpenGL上下文的繪圖表面,如下所示:

scoped_refptr<gfx::GLSurface> ImageTransportSurface::CreateNativeSurface(  
    GpuChannelManager* manager,  
    GpuCommandBufferStub* stub,  
    const gfx::GLSurfaceHandle& handle) {  
  if (handle.transport_type == gfx::NATIVE_TRANSPORT) {  
    return scoped_refptr<gfx::GLSurface>(  
        new ImageTransportSurfaceAndroid(manager,  
                                         stub,  
                                         manager->GetDefaultOffscreenSurface(),  
                                         handle.parent_client_id));  
  }  

  ......
}
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/image_transport_surface_android.cc中。

       參數handle描述的gfx::GLSurfaceHandle對象即為前面調用RenderWidgetHostViewAndroid類的成員函數GetCompositingSurface建立的gfx::GLSurfaceHandle對象,它的成員變量transport_type的值等于gfx::NATIVE_TRANSPORT,是以ImageTransportSurface類的成員函數CreateNativeSurface建立一個ImageTransportSurfaceAndroid對象來描述Render端OpenGL上下文的繪圖表面。

       在調用ImageTransportSurfaceAndroid類的構造函數建立ImageTransportSurfaceAndroid對象的時候,第三個參數指定為參數handle描述的gfx::GLSurfaceHandle對象的成員變量parent_client_id。這個參數将會儲存在ImageTransportSurfaceAndroid類的成員變量parent_client_id_中,如下所示:

ImageTransportSurfaceAndroid::ImageTransportSurfaceAndroid(
    GpuChannelManager* manager,
    GpuCommandBufferStub* stub,
    gfx::GLSurface* surface,
    uint32 parent_client_id)
    : PassThroughImageTransportSurface(manager, stub, surface),
      parent_client_id_(parent_client_id) {}
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/image_transport_surface_android.cc中。

       從前面Chromium硬體加速渲染的OpenGL上下文建立過程分析一文還可以知道,通過ImageTransportSurface類的成員函數CreateNativeSurface建立出來的ImageTransportSurfaceAndroid對象接下來會被始化,這是通過調用它的成員函數Initialize進行的,如下所示:

bool ImageTransportSurfaceAndroid::Initialize() {
  ......

  GpuChannel* parent_channel =
      GetHelper()->manager()->LookupChannel(parent_client_id_);
  if (parent_channel) {
    const CommandLine* command_line = CommandLine::ForCurrentProcess();
    if (command_line->HasSwitch(switches::kUIPrioritizeInGpuProcess))
      GetHelper()->SetPreemptByFlag(parent_channel->GetPreemptionFlag());
  }

  return true;
}
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/image_transport_surface_android.cc中。

       ImageTransportSurfaceAndroid類的成員函數GetHelper傳回的是一個ImageTransportHelper對象,調用這個ImageTransportHelper對象的成員函數manager獲得的是GPU程序中的一個GpuChannelManager單例對象。有了這個GpuChannelManager單例對象之後,就可以調用它的成員函數LookupChannel獲得與ImageTransportSurfaceAndroid類的成員變量parent_client_id_對應的一個GpuChannel對象。從前面的分析可以知道,這個GpuChannel對象描述的就是Browser程序與GPU程序之間建立的GPU通道。

       ImageTransportSurfaceAndroid類的成員函數Initialize會檢查GPU程序的啟動參數是否包含有一個switches::kUIPrioritizeInGpuProcess選項。如果包含有,就意味着要優先執行Browser端OpenGL上下文的GPU指令,以便可以快速地将網頁UI顯示在螢幕中。這也意味着Browser端OpenGL上下文可以搶占排程。

       我們假設GPU程序的啟動參數包含有switches::kUIPrioritizeInGpuProcess選項,這時候 ImageTransportSurfaceAndroid類的成員函數Initialize做了兩件事情:

       1. 調用用來描述Browser端GPU通道的GpuChannel對象的成員函數GetPreemptionFlag獲得一個PreemptionFlag對象,這個PreemptionFlag對象用來描述Browser端OpenGL上下文是否需要搶占排程。

       2.  調用ImageTransportSurfaceAndroid類的成員函數GetHelper獲得一個ImageTransportHelper對象,并且以前面獲得的PreemptionFlag對象為參數,調用這個ImageTransportHelper對象的成員函數SetPreemptByFlag。

       接下來我們就分别分析GpuChannel類的成員函數GetPreemptionFlag和ImageTransportHelper類的成員函數SetPreemptByFlag的實作。

       GpuChannel類的成員函數GetPreemptionFlag的實作如下所示:

gpu::PreemptionFlag* GpuChannel::GetPreemptionFlag() {
  if (!preempting_flag_.get()) {
    preempting_flag_ = new gpu::PreemptionFlag;
    io_message_loop_->PostTask(
        FROM_HERE, base::Bind(
            &GpuChannelMessageFilter::SetPreemptingFlagAndSchedulingState,
            filter_, preempting_flag_, num_stubs_descheduled_ > 0));
  }
  return preempting_flag_.get();
}
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

       GpuChannel類的成員函數GetPreemptionFlag傳回的是成員變量preempting_flag_描述的一個PreemptionFlag對象。如果這個PreemptionFlag對象還沒有建立,那麼就會先建立,并且在建立出來之後,向GPU程序的IO線程的消息隊列發送一個Task,這個Task綁定的是GpuChannel類的成員變量filter_描述的一個GpuChannelMessageFilter對象的成員函數SetPreemptingFlagAndSchedulingState。

       這意味着接下來GpuChannelMessageFilter類的成員函數SetPreemptingFlagAndSchedulingState會在GPU程序的IO線程中調用,它的實作如下所示:

class GpuChannelMessageFilter : public IPC::MessageFilter {
 public:
  ......

  void SetPreemptingFlagAndSchedulingState(
      gpu::PreemptionFlag* preempting_flag,
      bool a_stub_is_descheduled) {
    preempting_flag_ = preempting_flag;
    a_stub_is_descheduled_ = a_stub_is_descheduled;
  }

  ......

 private:
  ......

  scoped_refptr<gpu::PreemptionFlag> preempting_flag_;

  ......

  bool a_stub_is_descheduled_;
};
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

       參數preempt_flag來自GpuChannel類的成員變量preempting_flag_,另外一個參數a_stub_is_descheduled表示是否有Browser端OpenGL上下文自行放棄排程。這兩個參數分别儲存在GpuChannelMessageFilter類的成員變量preempting_flag_和a_stub_is_descheduled_中。

       這裡有一點需要注意,目前正在處理的GpuChannelMessageFilter對象是一個Message Filter。這個Message Filter是用來過濾通過Browser端GPU通道發送過來的GPU消息的,也就是用來過濾與Browser端OpenGL上下文相關的GPU消息。後面我們還會看到,這個Message Filter還用來決定Browser端OpenGL上下文是否需要搶占排程。

       接下來我們繼續分析ImageTransportHelper類的成員函數SetPreemptByFlag的實作,如下所示:

void ImageTransportHelper::SetPreemptByFlag(
    scoped_refptr<gpu::PreemptionFlag> preemption_flag) {
  stub_->channel()->SetPreemptByFlag(preemption_flag);
}
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/image_transport_surface.cc中。

       目前正在處理的ImageTransportHelper對象的成員變量stub_指向的是一個GpuCommandBufferStub對象。這個GpuCommandBufferStub對象描述的是一個Render端OpenGL上下文。調用這個GpuCommandBufferStub對象的成員函數channel獲得的是一個GpuChannel對象。這個GpuChannel對象描述的是Render端GPU通道。ImageTransportHelper類的成員函數SetPreemptByFlag調用這個GpuChannel對象的成員函數SetPreemptByFlag,以便将參數preempting_flag描述的PreemptionFlag對象交給它處理。

       GpuChannel類的成員函數SetPreemptByFlag的實作如下所示:

void GpuChannel::SetPreemptByFlag(
    scoped_refptr<gpu::PreemptionFlag> preempted_flag) {
  preempted_flag_ = preempted_flag;

  for (StubMap::Iterator<GpuCommandBufferStub> it(&stubs_);
       !it.IsAtEnd(); it.Advance()) {
    it.GetCurrentValue()->SetPreemptByFlag(preempted_flag_);
  }
}
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

       GpuChannel類的成員函數SetPreemptByFlag首先将參數preempting_flag描述的PreemptionFlag對象儲存在成員變量preempting_flag_,接下來又依次調用儲存在成員變量stubs_中的每一個GpuCommandBufferStub對象的成員函數SetPreemptByFlag,以便将參數preempting_flag描述的PreemptionFlag對象交給它們處理。

       注意,目前正在處理的GpuChannel對象描述的是Render端GPU通道,相應地,儲存在它的成員變量stubs_中的每一個GpuCommandBufferStub對象描述的都是一個Render端OpenGL上下文。

       GpuCommandBufferStub類的成員函數SetPreemptByFlag的實作如下所示:

void GpuCommandBufferStub::SetPreemptByFlag(
    scoped_refptr<gpu::PreemptionFlag> flag) {
  preemption_flag_ = flag;
  if (scheduler_)
    scheduler_->SetPreemptByFlag(preemption_flag_);
}
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_command_buffer_stub.cc中。

       GpuCommandBufferStub類的成員函數SetPreemptByFlag首先将參數flag描述的PreemptionFlag對象儲存在成員變量preempting_flag_,接下來又調用成員變量scheduler_描述的一個GpuScheduler對象的成員函數SetPreemptByFlag,以便将參數flag描述的PreemptionFlag對象交給它處理。

       GpuScheduler類的成員函數SetPreemptByFlag的實作如下所示:

class GPU_EXPORT GpuScheduler
    : NON_EXPORTED_BASE(public CommandBufferEngine),
      public base::SupportsWeakPtr<GpuScheduler> {
 public:
  ......

  void SetPreemptByFlag(scoped_refptr<PreemptionFlag> flag) {
    preemption_flag_ = flag;
  }

  ......

 private:
  ......

  scoped_refptr<PreemptionFlag> preemption_flag_;
  
  ......
};
           

      這個函數定義在檔案external/chromium_org/gpu/command_buffer/service/gpu_scheduler.h中。

      GpuScheduler類的成員函數SetPreemptByFlag将參數flag描述的PreemptionFlag對象儲存在成員變量preemption_flag_中。

      從前面的分析可以知道,GpuChannel類的成員函數HandleMessage負責将接收到的GPU消息分發給相應的GpuComandBufferStub對象處理。不過在分發之前,會先判斷目标GpuComandBufferStub對象描述的OpenGL上下文是否被搶占排程,這是通過調用目标GpuComandBufferStub對象的成員函數IsPreempted實作的。如果被搶占排程,那麼GpuChannel類的成員函數HandleMessage就暫時不會将GPU消息分發給目标GpuComandBufferStub對象處理。在我們這個情景中,目标GpuComandBufferStub對象描述的OpenGL上下文即為Render端OpenGL上下文。

      GpuComandBufferStub類的成員函數IsPreempted的實作如下所示:      

class GpuCommandBufferStub
    : public GpuMemoryManagerClient,
      public IPC::Listener,
      public IPC::Sender,
      public base::SupportsWeakPtr<GpuCommandBufferStub> {
 public:
  ......

  bool IsPreempted() const {
    return scheduler_.get() && scheduler_->IsPreempted();
  }

  ......

 private: 
  ......

  scoped_ptr<gpu::GpuScheduler> scheduler_;

  ......
};
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_command_buffer_stub.h中。

       GpuComandBufferStub類的成員函數IsPreempted調用成員變量scheduler_描述的一個GpuScheduler對象的成員函數IsPreempted判斷目前正在處理的OpenGL上下文是否被搶占排程。

       GpuScheduler類的成員函數IsPreempted的實作如下所示:

bool GpuScheduler::IsPreempted() {
  if (!preemption_flag_.get())
    return false;

  ......

  return preemption_flag_->IsSet();
}
           

       這個函數定義在檔案external/chromium_org/gpu/command_buffer/service/gpu_scheduler.cc中。

       如果GpuScheduler類的成員變量preemption_flag_沒有指向一個PreemptionFlag對象,那麼GpuScheduler類的成員函數IsPreempted的傳回值就為false,表示目前正在處理的OpenGL上下文沒有被搶占。

       隻有當GpuScheduler類的成員變量preemption_flag_指向了一個PreemptionFlag對象的時候,目前正在處理的OpenGL上下文才可能被搶占排程,這取決于成員變量preemption_flag_指向的PreemptionFlag對象的成員函數IsSet的傳回值。當這個傳回值為true的時候,就表示正在處理的OpenGL上下文被搶占排程。

       PreemptionFlag類的定義如下所示:

class PreemptionFlag
    : public base::RefCountedThreadSafe<PreemptionFlag> {
 public:
  PreemptionFlag() : flag_(0) {}

  bool IsSet() { return !base::AtomicRefCountIsZero(&flag_); }
  void Set() { base::AtomicRefCountInc(&flag_); }
  void Reset() { base::subtle::NoBarrier_Store(&flag_, 0); }

 private:
  base::AtomicRefCount flag_;

  ......
};
           

       這個類定義在檔案external/chromium_org/gpu/command_buffer/service/gpu_scheduler.h中。

       PreemptionFlag類有一個類型為AtomicRefCount的成員變量flag_。當它的值不等于0的時候,PreemptionFlag類的成員函數IsSet的傳回值就為true。否則的話,PreemptionFlag類的成員函數IsSet的傳回值就為false。

       PreemptionFlag類的成員變量flag_的值被初始化為0,以後每一次調用PreemptionFlag類的成員函數Set,将會使它的值增加1。另外,調用PreemptionFlag類的成員函數Reset之後,可以将這個成員變量的值重新設定為0。

       現在,我們需要知道在什麼情況下,Render端OpenGL上下文使用的GpuScheduler對象的成員變量preemption_flag_描述的PreemptionFlag對象的成員函數Set什麼時候會被調用。從前面的分析可以知道,這個PreemptionFlag對象就是用來描述Browser端GPU通道的一個GpuChannel對象的成員變量preempting_flag_指向的PreemptionFlag對象。

       從前面的分析還可以知道,用來描述Browser端GPU通道的一個GpuChannel對象的成員變量preempting_flag_指向的PreemptionFlag對象還被一個GpuChannelMessageFilter對象的成員變量preempting_flag_引用。這個GpuChannelMessageFilter對象是用來過濾與Browser端OpenGL上下文相關的GPU消息的。在過濾的過程,就有可能會調用其成員變量preempting_flag_引用的PreemptionFlag對象的成員函數Set。

       接下來我們就分析GpuChannelMessageFilter類是過濾與Browser端OpenGL上下文相關的GPU消息的過程。不過在分析這個過程之前,我們首先分析GpuChannelMessageFilter類的一個成員變量preemption_state_,如下所示:

class GpuChannelMessageFilter : public IPC::MessageFilter {
 public:
  GpuChannelMessageFilter(base::WeakPtr<GpuChannel> gpu_channel,
                          scoped_refptr<SyncPointManager> sync_point_manager,
                          scoped_refptr<base::MessageLoopProxy> message_loop)
      : preemption_state_(IDLE),
        ...... {}

 ......

 private:
  enum PreemptionState {
    // Either there's no other channel to preempt, there are no messages
    // pending processing, or we just finished preempting and have to wait
    // before preempting again.
    IDLE,
    // We are waiting kPreemptWaitTimeMs before checking if we should preempt.
    WAITING,
    // We can preempt whenever any IPC processing takes more than
    // kPreemptWaitTimeMs.
    CHECKING,
    // We are currently preempting (i.e. no stub is descheduled).
    PREEMPTING,
    // We would like to preempt, but some stub is descheduled.
    WOULD_PREEMPT_DESCHEDULED,
  };

  PreemptionState preemption_state_;

  ......
};
           

      這個成員變量定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

      GpuChannelMessageFilter類的成員變量preemption_state_用來描述Browser端GPU通道的狀态,一共有五個:

      1. IDLE:空閑狀态,或者是因為沒有GPU消息需要處理,或者因為剛完成了一次搶占。

      2. WAITING:等待狀态,等待進入檢查狀态。

      3. CHECKING:檢查狀态,檢查是否需要進入搶占狀态。

      4. PREEMPTING:搶占狀态。

      5. WOULD_PREEMPT_DESCHEDULED:在等待進入搶占狀态或者正在搶占狀态期間,有其它的Browser端OpenGL上下文自動放棄排程。

      關于這五個狀态的遷移,參見前面的圖3。從GpuChannelMessageFilter類的構造函數可以知道,它的成員變量preemption_state_開始時處于IDLE狀态。

      有三種情況會觸發Browser端GPU通道從IDLE狀态開始,遷移至其它狀态。第一種情況是接收到了新的GPU消息,如下所示:

class GpuChannelMessageFilter : public IPC::MessageFilter {
 public:
  ......

  virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
    ......

    bool handled = false;
    ......

    if (!handled) {
      messages_forwarded_to_channel_++;
      if (preempting_flag_.get())
        pending_messages_.push(PendingMessage(messages_forwarded_to_channel_));
      UpdatePreemptionState();
    }

    ......

    return handled;
  }

  ......

 private:
  ......

  scoped_refptr<gpu::PreemptionFlag> preempting_flag_;

  std::queue<pendingmessage> pending_messages_;

  ......
};
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

       在成員變量preempting_flag_指向了一個PreemptingFlag對象的情況下,GpuChannelMessageFilter類的成員函數OnMessageReceived建立一個PendingMessage對象來描述接收的GPU消息,并且給這個PendingMessage對象賦予一個序号,然後儲存在成員變量pending_messages_描述的一個std::queue中。對于非Browser端GPU通道來說,它用來過濾GPU消息的GpuChannelMessageFilter對象的成員變量preempting_flag_沒有指向一個PreemptingFlag對象,是以它們的狀态會保持為IDLE,不會發生搶占排程的情況。

       GpuChannelMessageFilter類的成員函數OnMessageReceived接下來會調用另外一個成員函數UpdatePreemptionState更新Browser端GPU通道的狀态。

       第二種情況是Browser端OpenGL上下文自動放棄排程和重新請求排程時。前面分析GpuScheduler類的成員函數SetScheduled時提到,當一個OpenGL上下文自動放棄排程和重新請求排程時,會執行GpuScheduler類的成員變量scheduling_changed_callback_描述的一個Callback對象。這個Callback對象是通過調用GpuScheduler類的成員函數SetSchedulingChangedCallback設定的,如下所示:

void GpuScheduler::SetSchedulingChangedCallback(
    const SchedulingChangedCallback& callback) {
  scheduling_changed_callback_ = callback;
}
           

      這個函數定義在檔案external/chromium_org/gpu/command_buffer/service/gpu_scheduler.cc中。

      那麼GpuScheduler類的成員函數SetSchedulingChangedCallback是什麼時候被誰調用的呢?在前面Chromium硬體加速渲染的OpenGL上下文建立過程分析一文中提到,OpenGL上下文是由GpuCommandBufferStub類的成員函數OnInitialize進行初始化的。在初始化OpenGL上下文的過程中,GpuCommandBufferStub類的成員函數OnInitialize就會調用成員變量scheduler_描述的一個GpuScheduler對象的成員函數SetSchedulingChangedCallback,以便給該GpuScheduler對象的成員變量scheduling_changed_callback_描述的一個Callback對象,如下所示:

void GpuCommandBufferStub::OnInitialize(
    base::SharedMemoryHandle shared_state_handle,
    IPC::Message* reply_message) {
  ......

  scheduler_->SetSchedulingChangedCallback(
      base::Bind(&GpuChannel::StubSchedulingChanged,
                 base::Unretained(channel_)));

  ......
}
           

      這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_command_buffer_stub.cc中。

      從這裡就可以看到,GpuScheduler類的成員變量scheduling_changed_callback_描述的Callback對象綁定的函數是其成員變量channel_描述的一個GpuChannel對象的成員函數StubSchedulingChanged。

      這意味着,當一個OpenGL上下文自動放棄排程和重新請求排程時,會調用描述它所使用的GPU通道的一個GpuChannel對象的成員函數StubSchedulingChanged,以便通知該GPU通道,它其中的一個OpenGL上下文排程狀态發生了變化。

      GpuChannel類的成員函數StubSchedulingChanged的實作如下所示:

void GpuChannel::StubSchedulingChanged(bool scheduled) {
  bool a_stub_was_descheduled = num_stubs_descheduled_ > 0;
  if (scheduled) {
    num_stubs_descheduled_--;
    OnScheduled();
  } else {
    num_stubs_descheduled_++;
  }
  DCHECK_LE(num_stubs_descheduled_, stubs_.size());
  bool a_stub_is_descheduled = num_stubs_descheduled_ > 0;

  if (a_stub_is_descheduled != a_stub_was_descheduled) {
    if (preempting_flag_.get()) {
      io_message_loop_->PostTask(
          FROM_HERE,
          base::Bind(&GpuChannelMessageFilter::UpdateStubSchedulingState,
                     filter_,
                     a_stub_is_descheduled));
    }
  }
}
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

       GpuChannel類的成員變量num_stubs_descheduled_描述的是目前正在處理的GPU通道,有多少個OpenGL上下文自動放棄了排程,它是根據GpuChannel類的成員函數StubSchedulingChanged的調用次數及其參數scheduled計算出來的。

       每當一個GPU通道自動放棄排程的OpenGL上下文的個數發生變化時,GpuChannel類的成員函數StubSchedulingChanged就會向GPU程序的IO線程的消息隊列發送一個Task,這個Task綁定的函數是用來過濾該GPU通道消息的一個GpuChannelMessageFilter對象的成員函數UpdateStubSchedulingState。

       GpuChannelMessageFilter類的成員函數UpdateStubSchedulingState的實作如下所示:

class GpuChannelMessageFilter : public IPC::MessageFilter {
 public:
  ......

  void UpdateStubSchedulingState(bool a_stub_is_descheduled) {
    a_stub_is_descheduled_ = a_stub_is_descheduled;
    UpdatePreemptionState();
  }

  ......

 private:
  ......

  bool a_stub_is_descheduled_;
};
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

       GpuChannelMessageFilter類的成員函數UpdateStubSchedulingState将參數a_stub_is_descheduled的值儲存在成員變量a_stub_is_descheduled_,是以當GpuChannelMessageFilter類的成員變量a_stub_is_descheduled_等于true的時候,就表示有一個OpenGL上下文自動放棄了排程。

       從這裡就可以看到,與前面分析的GpuChannelMessageFilter類的成員函數OnMeesageReceived一樣,GpuChannelMessageFilter類的成員函數UpdateStubSchedulingState也會調用成員函數UpdatePreemptionState來更新目前正在處理的GPU通道的狀态。

       第三種情況是用來描述OpenGL上下文的GpuCommandBufferStub類通過成員函數OnMessageReceived處理了一個GPU消息時。從前面分析的GpuChannel類的成員函數HandleMessage可以知道,這時候GpuChannel類的成員函數MessageProcessed會被調用,調用過程如下所示:

void GpuChannel::MessageProcessed() {
  messages_processed_++;
  if (preempting_flag_.get()) {
    io_message_loop_->PostTask(
        FROM_HERE,
        base::Bind(&GpuChannelMessageFilter::MessageProcessed,
                   filter_,
                   messages_processed_));
  }
}
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

       GpuChannel類的成員函數MessageProcessed首先增加成員變量messages_processed_的值,接着向GPU程序的IO線程的消息隊列發送一個Task,這個Task綁定的函數為其成員變量filter_描述的一個GpuChannelMessageFilter對象的成員函數MessageProcessed。GpuChannel類的成員變量messages_processed_描述的是目前正在處理的GPU通道已經處理的GPU消息的個數。

       GpuChannelMessageFilter類的成員函數MessageProcessed的實作如下所示:

class GpuChannelMessageFilter : public IPC::MessageFilter {
 public:
  ......

  void MessageProcessed(uint64 messages_processed) {
    while (!pending_messages_.empty() &&
           pending_messages_.front().message_number <= messages_processed)
      pending_messages_.pop();
    UpdatePreemptionState();
  }

  ......
};
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

       前面提到,GpuChannelMessageFilter類的成員變量pending_messages_描述的一個std::queue儲存的每一個PendingMessage對象都代表了一個接收到的但還未處理的GPU消息,并且每一個PendingMessage對象都設定有一個序号。當一個PendingMessage對象的序号小于等于參數messages_processed的值時,就表示該PendingMessage對象代表的GPU消息已經被處理,是以就需要從GpuChannelMessageFilter類的成員變量pending_messages_描述的一個std::queue中移除。

       最後,GpuChannelMessageFilter類的成員函數MessageProcessed也像前面描述的兩種情況一樣,調用另外一個成員函數UpdatePreemptionState更新目前正在處理的GPU通道的狀态。

       GpuChannelMessageFilter類的成員函數UpdatePreemptionState的實作如下所示:

class GpuChannelMessageFilter : public IPC::MessageFilter {
 ......

 private:
  ......

  void UpdatePreemptionState() {
    switch (preemption_state_) {
      case IDLE:
        if (preempting_flag_.get() && !pending_messages_.empty())
          TransitionToWaiting();
        break;
      case WAITING:
        // A timer will transition us to CHECKING.
        DCHECK(timer_.IsRunning());
        break;
      case CHECKING:
        if (!pending_messages_.empty()) {
          base::TimeDelta time_elapsed =
              base::TimeTicks::Now() - pending_messages_.front().time_received;
          if (time_elapsed.InMilliseconds() < kPreemptWaitTimeMs) {
            // Schedule another check for when the IPC may go long.
            timer_.Start(
                FROM_HERE,
                base::TimeDelta::FromMilliseconds(kPreemptWaitTimeMs) -
                    time_elapsed,
                this, &GpuChannelMessageFilter::UpdatePreemptionState);
          } else {
            if (a_stub_is_descheduled_)
              TransitionToWouldPreemptDescheduled();
            else
              TransitionToPreempting();
          }
        }
        break;
      case PREEMPTING:
        // A TransitionToIdle() timer should always be running in this state.
        DCHECK(timer_.IsRunning());
        if (a_stub_is_descheduled_)
          TransitionToWouldPreemptDescheduled();
        else
          TransitionToIdleIfCaughtUp();
        break;
      case WOULD_PREEMPT_DESCHEDULED:
        // A TransitionToIdle() timer should never be running in this state.
        DCHECK(!timer_.IsRunning());
        if (!a_stub_is_descheduled_)
          TransitionToPreempting();
        else
          TransitionToIdleIfCaughtUp();
        break;
      default:
        NOTREACHED();
    }
  }

  ......
};
           

      這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

      從這裡可以看到,一個GPU通道可以從初始的IDLE狀态遷移到其它狀态,需要滿足兩個條件:

      1. 用來過濾GPU消息的GpuChannelMessageFilter對象的成員變量preemption_state_指向了一個PreemptionFlag對象。

      2. 接收到了GPU消息。

      第1個條件并不是所有的GPU通道都能滿足的。前面分析ImageTransportSurfaceAndroid類的成員函數Initialize提到,當GPU程序的啟動參數包含有switches::kUIPrioritizeInGpuProcess選項時,用來描述Browser端GPU通道的一個GpuChannel對象的成員函數GetPreemptionFlag會被調用,如下所示:

bool ImageTransportSurfaceAndroid::Initialize() {
  ......

  GpuChannel* parent_channel =
      GetHelper()->manager()->LookupChannel(parent_client_id_);
  if (parent_channel) {
    const CommandLine* command_line = CommandLine::ForCurrentProcess();
    if (command_line->HasSwitch(switches::kUIPrioritizeInGpuProcess))
      GetHelper()->SetPreemptByFlag(parent_channel->GetPreemptionFlag());
  }

  return true;
}
           

      這個函數定義在檔案external/chromium_org/content/common/gpu/image_transport_surface_android.cc中。

      GpuChannel類的成員函數GetPreemptionFlag在調用的時候,會觸發建立一個PreemptionFlag對象,如下所示:

gpu::PreemptionFlag* GpuChannel::GetPreemptionFlag() {
  if (!preempting_flag_.get()) {
    preempting_flag_ = new gpu::PreemptionFlag;
    io_message_loop_->PostTask(
        FROM_HERE, base::Bind(
            &GpuChannelMessageFilter::SetPreemptingFlagAndSchedulingState,
            filter_, preempting_flag_, num_stubs_descheduled_ > 0));
  }
  return preempting_flag_.get();
}
           

     這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

     GpuChannel類的成員函數GetPreemptionFlag會将建立的PreemptionFlag對象傳遞給其成員變量filter_描述的一個GpuChannelMessageFilter對象,如下所示:

class GpuChannelMessageFilter : public IPC::MessageFilter {
 public:
  ......

  void SetPreemptingFlagAndSchedulingState(
      gpu::PreemptionFlag* preempting_flag,
      bool a_stub_is_descheduled) {
    preempting_flag_ = preempting_flag;
    a_stub_is_descheduled_ = a_stub_is_descheduled;
  }

  ......
};
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

       由于用來描述非Browser端GPU通道的GpuChannel對象的成員函數GetPreemptionFlag不會被調用,是以就會導緻用來過濾它們的GPU消息的GpuChannelMessageFilter對象的成員變量preempting_flag_沒有指向一個PreemptionFlag對象,于是非Browser端GPU通道的狀态就會保持為IDLE不變。正是通過這種方式,使得非Browser端OpenGL上下文不會發生搶占排程的情況,而Browser端OpenGL上下文卻可以搶占排程。

       接下來,我們就分析Browser端GPU通道的狀态遷移過程。首先是從IDLE狀态遷移到WAITING狀态,這是通過調用GpuChannelMessageFilter類的成員函數TransitionToWaiting實作的,如下所示:

class GpuChannelMessageFilter : public IPC::MessageFilter {
 ......

 private:
  ......

  void TransitionToWaiting() {
    DCHECK_EQ(preemption_state_, IDLE);
    DCHECK(!timer_.IsRunning());

    preemption_state_ = WAITING;
    timer_.Start(
        FROM_HERE,
        base::TimeDelta::FromMilliseconds(kPreemptWaitTimeMs),
        this, &GpuChannelMessageFilter::TransitionToChecking);
  }

  ......
};
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

       Browser端GPU通道進入WAITING狀态後,會啟用一個定時器,該定時器在kPreemptWaitTimeMs毫秒之後,會調用GpuChannelMessageFilter類的成員函數TransitionToChecking。

       kPreemptWaitTimeMs是一個常量,它的定義如下所示:

// Number of milliseconds between successive vsync. Many GL commands block
// on vsync, so thresholds for preemption should be multiples of this.
const int64 kVsyncIntervalMs = 17;

// Amount of time that we will wait for an IPC to be processed before
// preempting. After a preemption, we must wait this long before triggering
// another preemption.
const int64 kPreemptWaitTimeMs = 2 * kVsyncIntervalMs;
           

      這兩個常量定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

      kPreemptWaitTimeMs剛好就定義為2個kVsyncIntervalMs的大小,一個kVsyncIntervalMs是17毫秒,等于60fps螢幕重新整理頻率的一個VSync信号時間間隔,也就是說,kPreemptWaitTimeMs等于2個VSync信号時間間隔。

      GpuChannelMessageFilter類的成員函數TransitionToChecking的實作如下所示:

class GpuChannelMessageFilter : public IPC::MessageFilter {
 ......

 private:
  ......

  void TransitionToChecking() {
    DCHECK_EQ(preemption_state_, WAITING);
    DCHECK(!timer_.IsRunning());

    preemption_state_ = CHECKING;
    max_preemption_time_ = base::TimeDelta::FromMilliseconds(kMaxPreemptTimeMs);
    UpdatePreemptionState();
  }

  ......
};
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

       GpuChannelMessageFilter類的成員函數TransitionToChecking将Browser端GPU通道的狀态設定為CHECKING,并且将成員變量max_preemption_time_的值設定為kMaxPreemptTimeMs毫秒,用來描述Browser端GPU通道最多可搶占排程的時間。

       kMaxPreemptTimeMs也是一個常量,它的定義如下所示:

const int64 kMaxPreemptTimeMs = kVsyncIntervalMs;
           

       這個常量定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

       kMaxPreemptTimeMs定義為1個kVsyncIntervalMs的大小,也就是1個Vsync信号時間間隔。這意味着Browser端OpenGL上下文最多可搶占其它OpenGL上下文一個Vsync信号時間間隔長度。

       Browser端GPU通道從CHECKING狀态可以遷移至PREEMPTING狀态或者WOULD_PREEMPT_DESCHEDULED狀态,也有可能會維持CHECKING不變,如下所示:

switch (preemption_state_) {
      ......
      case CHECKING:
        if (!pending_messages_.empty()) {
          base::TimeDelta time_elapsed =
              base::TimeTicks::Now() - pending_messages_.front().time_received;
          if (time_elapsed.InMilliseconds() < kPreemptWaitTimeMs) {
            // Schedule another check for when the IPC may go long.
            timer_.Start(
                FROM_HERE,
                base::TimeDelta::FromMilliseconds(kPreemptWaitTimeMs) -
                    time_elapsed,
                this, &GpuChannelMessageFilter::UpdatePreemptionState);
          } else {
            if (a_stub_is_descheduled_)
              TransitionToWouldPreemptDescheduled();
            else
              TransitionToPreempting();
          }
        }
        break;
      ......
    }
           

      這個代碼片斷定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

      在以下兩種情況下,CHECKING狀态會維持CHECKING狀态不變:

      1. 此時沒有未處理GPU消息。

      2. 最早接收到的還未處理GPU消息的流逝時間time_elapsed小于kPreemptWaitTimeMs毫秒,即1個螢幕重新整理時間間隔。

      在第2種情況下,會啟動一個定時器,在(kPreemptWaitTimeMs - time_elapsed)毫秒後,會重新調用GpuChannelMessageFilter類的成員函數UpdatePreemptionState檢查是否需要更新狀态。

      在最早接收到的未處理GPU消息的流逝時間time_elapsed大于等于kPreemptWaitTimeMs毫秒的情況下,CHECKING狀态會向PREEMPTING或者WOULD_PREEMPT_DESCHEDULED狀态遷移,取決于此時有沒有Browser端OpenGL上下文自動放棄排程,即取決于此時GpuChannelMessageFilter類的成員變量a_stub_is_descheduled_的值。

      如果GpuChannelMessageFilter類的成員變量a_stub_is_descheduled_的值等于true,那麼就調用成員函數TransitionToWouldPreemptDescheduled遷移至WOULD_PREEMPT_DESCHEDULED狀态。否則的話,就調用成員函數TransitionToPreempting遷移至PREEMPTING狀态。

      GpuChannelMessageFilter類的成員函數TransitionToWouldPreemptDescheduled的實作如下所示:

class GpuChannelMessageFilter : public IPC::MessageFilter {
 ......

 private:
  ......

  void TransitionToWouldPreemptDescheduled() {
    DCHECK(preemption_state_ == CHECKING ||
           preemption_state_ == PREEMPTING);
    DCHECK(a_stub_is_descheduled_);

    if (preemption_state_ == CHECKING) {
      // Stop any pending state update checks that we may have queued
      // while CHECKING.
      timer_.Stop();
    } else {
      // Stop any TransitionToIdle() timers that we may have queued
      // while PREEMPTING.
      timer_.Stop();
      max_preemption_time_ = timer_.desired_run_time() - base::TimeTicks::Now();
      if (max_preemption_time_ < base::TimeDelta()) {
        TransitionToIdle();
        return;
      }
    }

    preemption_state_ = WOULD_PREEMPT_DESCHEDULED;
    preempting_flag_->Reset();
    TRACE_COUNTER_ID1("gpu", "GpuChannel::Preempting", this, 0);

    UpdatePreemptionState();
  }

  ......
};
           

       這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

       後面我們會看到,Browser端GPU通道從PREEMPTING狀态遷移至WOULD_PREEMPT_DESCHEDULED狀态也是調用GpuChannelMessageFilter類的成員函數TransitionToWouldPreemptDescheduled進行的。

       如果是從CHECKING狀态遷移至WOULD_PREEMPT_DESCHEDULED狀态,那麼根據前面的分析,目前可能正啟動着一個定時器,這個定時器負責再次調用GpuChannelMessageFilter類的成員函數UpdatePreemptionState更新Browser端GPU通道。這時候GpuChannelMessageFilter類的成員函數TransitionToWouldPreemptDescheduled首先停止該定時器,然後将Browser端GPU通道的狀态個修改為WOULD_PREEMPT_DESCHEDULED。

       如果是從PREEMPTING狀态遷移至WOULD_PREEMPT_DESCHEDULED狀态,那麼這時候也會啟動着一個定時器,該定時器負責将Browser端GPU通道從PREEMPTING狀态遷移至IDLE狀态,是以GpuChannelMessageFilter類的成員函數TransitionToWouldPreemptDescheduled也是首先停止該定時器,然後計算剩餘的可搶占排程的時間長度。這個時間長度是Browser端GPU通道從WOULD_PREEMPT_DESCHEDULED狀态遷移至PREEMPTING狀态時,可用的搶占排程時間長度。

       前面分析GpuChannelMessageFilter類的成員函數TransitionToChecking時提到,Browser端GPU通道的最大可搶占排程時間長度設定為kMaxPreemptTimeMs毫秒。假設Browser端GPU通道的狀态變化過程為PREEMPTING->WOULD_PREEMPT_DESCHEDULED->PREEMPTING,那麼第一次進入PREEMPTING狀态的可用搶占排程時間長度為kMaxPreemptTimeMs毫秒,第二次進入PREEMPTING狀态的可用搶占排程時間長度為第一次進入PREEMPTING狀态時剩餘的搶占排程時間。如果剩餘的搶占排程時間已經小于0,那麼就沒有必要第二次進入PREEMPTING狀态了。是以,這時候GpuChannelMessageFilter類的成員函數TransitionToWouldPreemptDescheduled調用另外一個成員函數TransitionToIdle把Browser端GPU通道的狀态設定為IDLE。

       如果前面所述的剩餘的搶占排程時間大于0,那麼GpuChannelMessageFilter類的成員函數TransitionToWouldPreemptDescheduled将Browser端GPU通道的狀态修改為WOULD_PREEMPT_DESCHEDULED,并且調用成員變量preempting_flag_描述的一個PreemptionFlag對象的成員函數Reset,用來取消Browser端GPU通道之前設定的搶占排程标志。如果此時是從PREEMPTING狀态遷移至WOULD_PREEMPT_DESCHEDULED狀态,那麼Browser端GPU通道設定的搶占排程标志之前就是設定了搶占排程标志的。

       GpuChannelMessageFilter類的成員函數TransitionToPreempting的實作如下所示:

class GpuChannelMessageFilter : public IPC::MessageFilter {
 ......

 private:
  ......

  void TransitionToPreempting() {
    DCHECK(preemption_state_ == CHECKING ||
           preemption_state_ == WOULD_PREEMPT_DESCHEDULED);
    DCHECK(!a_stub_is_descheduled_);

    // Stop any pending state update checks that we may have queued
    // while CHECKING.
    if (preemption_state_ == CHECKING)
      timer_.Stop();

    preemption_state_ = PREEMPTING;
    preempting_flag_->Set();
    TRACE_COUNTER_ID1("gpu", "GpuChannel::Preempting", this, 1);

    timer_.Start(
       FROM_HERE,
       max_preemption_time_,
       this, &GpuChannelMessageFilter::TransitionToIdle);

    UpdatePreemptionState();
  }

  ......
};
           

      這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

      後面我們會看到,Browser端GPU通道從WOULD_PREEMPT_DESCHEDULED狀态遷移至PREEMPTING狀态也是調用GpuChannelMessageFilter類的成員函數TransitionToPreempting進行的。

      如果是從CHECKING狀态遷移至PREEMPTING狀态,那麼根據前面的分析,目前可能正啟動着一個定時器,這個定時器負責再次調用GpuChannelMessageFilter類的成員函數UpdatePreemptionState更新Browser端GPU通道。這時候GpuChannelMessageFilter類的成員函數TransitionToPreempting首先停止該定時器,然後将Browser端GPU通道的狀态修改為PREEMPTING。

       将Browser端GPU通道的狀态個修改為PREEMPTING之後,GpuChannelMessageFilter類的成員函數TransitionToPreempting還有兩件重要的事情要做:

       1. 調用成員變量preempting_flag_描述的一個PreemptionFlag對象的成員函數Set,給Browser端GPU通道設定搶占排程标志。這樣就會導緻正在排程的OpenGL上下文停止排程,以便将GPU線程釋放出來給Browser端OpenGL上下文使用,正如前面分析的GpuChannel類的成員函數HandleMessage和GpuScheduler類的成員函數PutChanged所示。

       2. 啟用一個定時器,這個定時器會在max_preemption_time_毫秒後逾時,逾時之後Browser端GPU通道的狀态通過GpuChannelMessageFilter類的成員函數TransitionToIdle重新進入IDLE狀态。結合前面的分析,我們就可以知道,這個定時器将Browser端OpenGL上下文的最大搶占排程時間長度限制為kMaxPreemptTimeMs毫秒,即一個螢幕重新整理時間間隔,用來避免其他OpenGL上下文被無限搶占排程。

       Browser端GPU通道從WOULD_PREEMPT_DESCHEDULED狀态可以遷移至PREEMPTING狀态或者IDLE狀态,如下所示:

switch (preemption_state_) {
      ......
      case WOULD_PREEMPT_DESCHEDULED:
        // A TransitionToIdle() timer should never be running in this state.
        DCHECK(!timer_.IsRunning());
        if (!a_stub_is_descheduled_)
          TransitionToPreempting();
        else
          TransitionToIdleIfCaughtUp();
        break;
      ......
    }
           

       這個代碼片斷定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

       如果此時沒有Browser端OpenGL上下文自動放棄排程,即GpuChannelMessageFilter類的成員變量a_stub_is_descheduled_的值等于false,那麼Browser端GPU通道就會調用前面分析過的GpuChannelMessageFilter類的成員函數TransitionToPreempting從WOULD_PREEMPT_DESCHEDULED狀态可以遷移至PREEMPTING狀态。否則的話,就會調用GpuChannelMessageFilter類的成員函數TransitionToIdleIfCaughtUp檢查是否需要從WOULD_PREEMPT_DESCHEDULED狀态可以遷移至IDLE狀态。

       GpuChannelMessageFilter類的成員函數TransitionToIdleIfCaughtUp的實作如下所示:

class GpuChannelMessageFilter : public IPC::MessageFilter {
 ......

 private:
  ......

  void TransitionToIdleIfCaughtUp() {
    DCHECK(preemption_state_ == PREEMPTING ||
           preemption_state_ == WOULD_PREEMPT_DESCHEDULED);
    if (pending_messages_.empty()) {
      TransitionToIdle();
    } else {
      base::TimeDelta time_elapsed =
          base::TimeTicks::Now() - pending_messages_.front().time_received;
      if (time_elapsed.InMilliseconds() < kStopPreemptThresholdMs)
        TransitionToIdle();
    }
  }

  ......
};
           

      這個函數定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

      後面我們會看到,Browser端GPU通道從PREEMPTING狀态遷移至IDLE狀态也是調用GpuChannelMessageFilter類的成員函數TransitionToIdleIfCaughtUp進行的。

      在兩種情況下,Browser端GPU通道會從WOULD_PREEMPT_DESCHEDULED和PREEMPTING狀态遷移至IDLE狀态:

      1. 此時沒有未處理的GPU消息。

      2. 最早接收到的還未處理GPU消息的流逝時間time_elapsed小于kStopPreemptThresholdMs毫秒。

      kStopPreemptThresholdMs是一個常量,它的定義如下所示:

const int64 kStopPreemptThresholdMs = kVsyncIntervalMs;
           

      這個常量定義在檔案external/chromium_org/content/common/gpu/gpu_channel.cc中。

      這意味着Browser端GPU通道處于WOULD_PREEMPT_DESCHEDULED和PREEMPTING狀态時,如果最早接收到的還未處理GPU消息的流逝時間time_elapsed小于1個螢幕重新整理時間間隔,那麼Browser端GPU通道就會放棄搶占排程。

      這樣,我們就分析完成Browser端OpenGL上下文搶占排程過程了。至此,我們也分析完成Chromium硬體加速渲染的OpenGL上下文排程過程了,主要涉及到三個關鍵過程:

      1. OpenGL上下文切換,主要是通過EGL函數eglMakeCurrent實作的。

      2. OpenGL上下文自動放棄排程,這主要是等待Sync Point引發的。

      3. OpenGL上下文搶占排程,發生在Browser端OpenGL上下文搶占排程其它OpenGL上下文上。

      在接下來的一篇文章中,我們分析Browser端合成WebGL端和Render端UI的過程。那時候我們就可以看到,Browser端在合成WebGL端UI的過程中,會用到這篇文章分析的Sync Point機制。敬請關注!更多的資訊也可以關注老羅的新浪微網誌:http://weibo.com/shengyangluo。