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

上传人:b****6 文档编号:6620612 上传时间:2023-01-08 格式:DOCX 页数:76 大小:372.40KB
下载 相关 举报
Chromium硬件加速渲染的OpenGL上下文调度过程分析.docx_第1页
第1页 / 共76页
Chromium硬件加速渲染的OpenGL上下文调度过程分析.docx_第2页
第2页 / 共76页
Chromium硬件加速渲染的OpenGL上下文调度过程分析.docx_第3页
第3页 / 共76页
Chromium硬件加速渲染的OpenGL上下文调度过程分析.docx_第4页
第4页 / 共76页
Chromium硬件加速渲染的OpenGL上下文调度过程分析.docx_第5页
第5页 / 共76页
点击查看更多>>
下载资源
资源描述

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

《Chromium硬件加速渲染的OpenGL上下文调度过程分析.docx》由会员分享,可在线阅读,更多相关《Chromium硬件加速渲染的OpenGL上下文调度过程分析.docx(76页珍藏版)》请在冰豆网上搜索。

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

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

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

Chromium的每一个WebGL端、Render端和Browser端实例在GPU进程中都有一个OpenGL上下文。

这些OpenGL上下文运行在相同线程中,因此同一时刻只有一个OpenGL上下文处于运行状态。

这就引发出一个OpenGL上下文调度问题。

此外,事情有轻重缓急,OpenGL上下文也有优先级高低之分,优先级高的要保证它的运行时间。

本文接下来就分析GPU进程调度运行OpenGL上下文的过程。

在前面一文中提到,GPU进程中的所有OpenGL上下文不仅运行在相同线程中,即运行在GPU进程的GPU线程中,它们还处于同一个共享组中,如图1所示:

在Chromium中,每一个OpenGL上下文使用一个GLContextEGL对象描述。

每一个OpenGL上下文都关联有一个绘图表面。

对于WebGL端和Render端的OpenGL上下文来说,它关联的绘图表面是一个离屏表面。

这个离屏表面一般就是用一个Pbuffer描述。

在Android平台上,Browser端的OpenGL上下文关联的绘图表面是一个SurfaceView。

当一个OpenGL上下文被调度时,它以及它关联的绘图表面就会通过调用EGL函数eglMakeCurrent设置为GPU线程当前使用的OpenGL上下文和绘图表面。

从前面一文又可以知道,Chromium为WebGL端、Render端和Browser端创建的OpenGL上下文可能是虚拟的,如图2所示:

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

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

Browser端GPU通道有一个PreemptionFlag。

当它处于PREEMPTING状态时,就会将PreemptionFlag设置为True。

WebGL端和Render端GPU通道可以访问Browser端GPU通道的PreemptionFlag。

属于WebGL端和Render端GPU通道的OpenGL上下文在调度期间,会不断地检查Browser端GPU通道的PreemptionFlag是否被设置为True。

如果被设置为True,那么它就会中止执行,提前释放GPU线程。

WebGL端和Render端GPU通道和Browser端GPU通道是父子关系。

其中,WebGL端和Render端GPU通道是儿子,Browser端GPU通道是父亲。

Chromium规定,儿子GPU通道可以访问父亲GPU通道的PreemptionFlag。

有了前面这些背景知识之后,接下来我们就结合源码分析OpenGL上下文的调度过程。

从前面一文可以知道,GPU进程通过调用GpuChannel类的成员函数Init创建GPU通道,如下所示:

[cpp]viewplaincopy

voidGpuChannel:

:

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_=

newGpuChannelMessageFilter(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中。

结合前面一文可以知道,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的实现如下所示:

[cpp]viewplaincopy

boolGpuChannel:

:

OnMessageReceived(constIPC:

:

Message&message){

......

if(message.type()==GpuCommandBufferMsg_WaitForTokenInRange:

:

ID||

message.type()==GpuCommandBufferMsg_WaitForGetOffsetInRange:

:

ID){

//MoveWaitcommandstotheheadofthequeue,sotherenderer

//doesn'thavetowaitanylongerthannecessary.

deferred_messages_.push_front(newIPC:

:

Message(message));

}else{

deferred_messages_.push_back(newIPC:

:

Message(message));

}

OnScheduled();

returntrue;

}

这个函数定义在文件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的末部。

从前面和这篇文章可以知道,当GPU进程接收到类型为GpuCommandBufferMsg_WaitForTokenInRange和GpuCommandBufferMsg_WaitForGetOffsetInRange的IPC消息时,就表示它的Client端,即WebGL端、Render端和Browser端,正在检查其GPU命令缓冲区的执行情况,需要尽快得到结果,因此就需要将它们放在上述std:

:

deque的头部,以便尽快得到处理。

GpuChannel类的成员函数OnMessageReceived将接收到的GPU消息保存在成员变量deferred_messages_描述的std:

:

deque之后,接下来调用另外一个成员函数OnScheduled对它们进行调度处理,如下所示:

[cpp]viewplaincopy

voidGpuChannel:

:

OnScheduled(){

if(handle_messages_scheduled_)

return;

//Postatasktohandleanydeferredmessages.Thedeferredmessagequeueis

//notemptiedhere,whichensuresthatOnMessageReceivedwillcontinueto

//defernewlyreceivedmessagesuntiltheonesinthequeuehaveallbeen

//handledbyHandleMessage.HandleMessageisinvokedasa

//tasktopreventreentrancy.

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的实现如下所示:

[cpp]viewplaincopy

voidGpuChannel:

:

HandleMessage(){

handle_messages_scheduled_=false;

if(deferred_messages_.empty())

return;

boolshould_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

:

Message>message(m);

deferred_messages_.pop_front();

boolmessage_processed=true;

currently_processing_message_=message.get();

boolresult;

if(message->routing_id()==MSG_ROUTING_CONTROL)

result=OnControlMessageReceived(*message);

else

result=router_.RouteMessage(*message);

currently_processing_message_=NULL;

if(!

result){

//Respondtosyncmessagesevenifrouterfailedtoroute.

if(message->is_sync()){

IPC:

:

Message*reply=IPC:

:

SyncMessage:

:

GenerateReply(&*message);

reply->set_reply_error();

Send(reply);

}

}else{

//Ifthecommandbufferbecomesunscheduledasaresultofhandlingthe

//messagebutstillhasmorecommandstoprocess,synthesizeanIPC

//messagetoflushthatcommandbuffer.

if(stub){

if(stub->HasUnprocessedCommands()){

deferred_messages_.push_front(newGpuCommandBufferMsg_Rescheduled(

stub->route_id()));

message_processed=false;

}

}

}

if(message_processed)

MessageProcessed();

//WewanttheEchoACKfollowingtheSwapBufferstobesentascloseas

//possible,avoidingschedulingotherchannelsinthemeantime.

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消息的RoutingID找到负责接收的一个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既没有自行放弃调度,也没有被抢占调度,那么接下来GpuChan

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 党团工作 > 入党转正申请

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1