Chromium硬件加速渲染的OpenGL上下文调度过程分析

发表于 5年以前  | 总阅读数:3255 次

Chromium的每一个WebGL端、Render端和Browser端实例在GPU进程中都有一个OpenGL上下文。这些OpenGL上下文运行在相同线程中,因此同一时刻只有一个OpenGL上下文处于运行状态。这就引发出一个OpenGL上下文调度问题。此外,事情有轻重缓急,OpenGL上下文也有优先级高低之分,优先级高的要保证它的运行时间。本文接下来就分析GPU进程调度运行OpenGL上下文的过程。

在前面Chromium硬件加速渲染机制基础知识简要介绍和学习计划一文中提到,GPU进程中的所有OpenGL上下文不仅运行在相同线程中,即运行在GPU进程的GPU线程中,它们还处于同一个共享组中,如图1所示:

图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所示:

图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所示:

图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说明,如下所示:

图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类的另外一个成员变量rescheduledcount,它的值也是初始化为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参数给它。

  1. 当参数scheduled的值等于false时,就将成员变量unscheduled_count_的值增加1。若增加后的值等于1,就说明一个OpenGL上下文从放弃调度状态进入到了请求调度状态。这时候如果成员变量scheduling_changed_callback_指向了一个Callback对象,那么就调用该Callback对象的成员函数Run,并且传递一个false参数给它。

关于GpuScheduler类的成员变量scheduling_changed_callback_的作用,接下来我们分析OpenGL上下文的抢占调度过程时再分析。

现在我们再考虑GpuScheduler类的成员变量rescheduledcount。一个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所示:

图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_pointcallback,如下所示:

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类的成员变量preemptingflag,另外一个参数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对象保存在成员变量preemptingflag,接下来又依次调用保存在成员变量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对象保存在成员变量preemptingflag,接下来又调用成员变量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类的一个成员变量preemptionstate,如下所示:

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_isdescheduled,因此当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

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 目录