Chromium Graphics: GPUclient的原理和实现分析之间的同步机制-Part II
摘要:Part I探析GPUclient之间的同步问题,以及Chromium的GL扩展同步点机制的基本原理。本文将源码的角度剖析同步点(SyncPoint)机制的实现方式。
同步点机制的实现主要涉及到是怎样跨进程实现两个GL扩展接口InsertSyncPointCHROMIUM和WaitSyncPointCHROMIUM,以及GPU服务端的同步点等待。
GPUclient
GPUclient将全部的GL命令都封装在GLES2Implementation中。GLES2Implementation将client的GL命令序列化后存放在命令行缓冲区中,client的每一个WebGraphicsContext3DImpl都会创建一个GLES2Implementation实例。调用GLES2Implementation的方法看上去就像直接调用OpenGL方法直接操作GPU设备一样。GPUclient与GPU进程之间的交互封装在GpuCommandBufferProxy类中。这个代理类会向GPU服务端的GpuCommandBufferStub发送IPC消息请求运行某些GPU操作。如发送GpuCommandBufferMsg_AsyncFlush消息提交(Flush)命令缓冲区。
同步点机制首先须要在GLES2Implementation类中实现这两个GL扩展接口,以便同意client代码使用同步点机制。GLES2Implementation::InsertSyncPointCHROMIUM通过GpuCommandBufferProxy向GPU服务端发送一条同步IPC消息GpuCommandBufferMsg_InsertSyncPoint请求注冊一个新的同步点,在调用CommandBufferProxyImpl::InsertSyncPoint之前,GLES2Implementation会将client的全部GL命令通过Flush命令行缓冲区都提交到服务端,例如以下代码:
GLuint GLES2Implementation::InsertSyncPointCHROMIUM() {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glInsertSyncPointCHROMIUM");
helper_->CommandBufferHelper::Flush();
return gpu_control_->InsertSyncPoint();
}
...
uint32 CommandBufferProxyImpl::InsertSyncPoint() {
if (last_state_.error != gpu::error::kNoError)
return 0; uint32 sync_point = 0;
Send(new GpuCommandBufferMsg_InsertSyncPoint(route_id_, true, &sync_point));
return sync_point;
}
同步IPC消息GpuCommandBufferMsg_InsertSyncPoint的參数sync_point返回由GPU服务端统一分配的唯一标识。
而与InsertSyncPointCHROMIUM不同的是。WaitSyncPointCHROMIUM不是通过发送专门的IPC消息给GPU服务端,而是作为一条GL扩展命令加入到CommandBuffer中,GPU服务端处理这条GL命令时再决定怎样运行暂停兴许命令的提交,等待同步点的完毕。
GPU服务端
GPU服务端既能够是一个独立的进程,也能够是一个Browser进程的一个线程,不论是进程还是线程,全部GPU操作了都是在同一个线程上运行,或称为GPU线程。
GPU线程除了向实际的GPU设备提交GL命令之外,还须要管理多个GpuChannel实例,调度它们处理消息的次序。
GpuChannel的消息处理方式
每一个GpuChannel都相应了一个GPUclient(Renderer进程或者Browser进程),GpuChannel::OnMessageReceived收到来自client的IPC消息后。并不会马上对消息进行处理,而是其加入到一个延迟消息队列中,然后再向主消息循环加入一个任务GpuChannel::HandleMessage来处理全部消息队列中全部未处理的消息。
这样的延迟处理IPC消息的方式有两个优点:
- 便于依据IPC消息序列的特定模式优化对多上下文的消息处理。比如,当未处理的消息队列中出现GpuCommandBufferMessage_SetLatencyInfo->GpuCommandBufferMsg_AsyncFlush -> GpuCommmandBufferMsg_Echo消息序列时,说明client发起了一个SwapBuffer操作,要求将backbuffer的内容显示到窗体上。那么此时,能够提前批量处理这三条消息。而不是等待主消息循环的调度。从而有利于提前处理SwapBuffer操作提前显示内容。
- 便于控制多个GpuChannel之间GL命令运行的同步方式,也就是本文所讨论的同步点机制。
IPC消息被缓存后。能够等待遍历消息时决定是马上处理,还是让出机会给其它的GpuChannel
HandleMessage任务中会逐一遍历全部未经处理的IPC消息,依据IPC消息的路由信息,将其交给相应的GpuCommandBufferStub来处理。见例如以下代码:
void GpuChannel::HandleMessage() {
handle_messages_scheduled_ = false;
if (deferred_messages_.empty())
return; bool should_fast_track_ack = false;
// 依据IPC消息的路由信息查找相应的GpuCommandBufferStub实例
IPC::Message* m = deferred_messages_.front();
GpuCommandBufferStub* stub = stubs_.Lookup(m->routing_id()); do {
if (stub) {
if (!stub->IsScheduled()) // 当GpuCommandBufferStub处于“不可调度”状态时。直接返回
return;
if (stub->IsPreempted()) {
OnScheduled();
return;
}
} // 从消息队列中弹出队头消息并交由GpuCommandBufferStub处理
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()) {
// 手工加入一条新的IPC消息给GpuCommandBufferStub,使其仍然有机会去处理那些pending的命令
deferred_messages_.push_front(new GpuCommandBufferMsg_Rescheduled(
stub->route_id()));
message_processed = false;
}
}
}
if (message_processed)
MessageProcessed(); // 此处省略一部分无关代码 } while (should_fast_track_ack); // 假设消息队列不为空,调用OnScheduled继续往主消息循环中加入HandleMessage任务
if (!deferred_messages_.empty()) {
OnScheduled();
}
}
GpuCommandBufferStub类
GpuCommandBufferStub是GPU进程端一个很重要的类。它封装了大部分在GPU服务端用来运行GL命令的功能,包括上下文的创建和初始化,响应client的GPU操作请求。调度运行分析和反序列化命令行缓冲区中的GL命令等工作。 GPUclient请求创建一个新的3D上下文时,实际上是请求一个新的GpuCommandBufferStub实例,因此,对于包括硬件加速canvas的Renderer进程。其相应的GpuChannel会管理多个GpuCommandBufferStub实例,以及依据IPC消息头部信息将IPC消息派发给相应的GpuCommandBufferStub实例上。
同步点的插入和服务端的等待
为了对IO线程上收到的IPC消息进行预处理,每一个GpuChannel都会安装一个执行在IO线程上的消息过滤器,每条来自GPUclient的IPC消息首先要经由消息过滤器的预处理。再转发给GpuChannel做进一步处理。
当GPU服务端收到GpuCommandBufferMsg_InsertSyncPoint消息后。IO线程上的消息过滤器GpuChannelMessageFilter分两步处理:
- 在IO线程调用线程安全的SyncPointManager::GenerateSyncPoint生成同步点的唯一标识,并马上发送给GPUclient;
- 向GPU服务端的主消息循环队列加入一个新的任务InsertSyncPointOnMainThread,即在主线程(而非IO线程)上向GpuChannel加入一个同步点;
static void InsertSyncPointOnMainThread(
base::WeakPtr<GpuChannel> gpu_channel,
scoped_refptr<SyncPointManager> manager,
int32 routing_id,
bool retire,
uint32 sync_point) {
// This function must ensure that the sync point will be retired. Normally
// we'll find the stub based on the routing ID, and associate the sync point
// with it, but if that fails for any reason (channel or stub already
// deleted, invalid routing id), we need to retire the sync point
// immediately.
if (gpu_channel) {
GpuCommandBufferStub* stub = gpu_channel->LookupCommandBuffer(routing_id);
if (stub) {
stub->AddSyncPoint(sync_point);
if (retire) {
GpuCommandBufferMsg_RetireSyncPoint message(routing_id, sync_point);
gpu_channel->OnMessageReceived(message);
}
return;
} else {
gpu_channel->MessageProcessed();
}
}
manager->RetireSyncPoint(sync_point);
}
InsertSyncPointOnMainThread除了调用GpuCommandBufferStub的AddSyncPoint方法向当前上下文加入同步点之外。还会显式调用OnMessageReceived向当前GpuChannel发送一条GpuCommandBufferMsg_RetireSyncPoint消息(稍后再介绍这条消息的用途),这条消息将会加入到GpuChannel的消息队列队尾,那么对于同一时候处理多个GpuChannel的主线程来说,可能会发生下面两种情况(这里如果GpuChannelA调用AddSyncPoint了):
情况I: GpuChannel A在主线程中得到充分的调度机会,也就是说全部未处理的IPC消息都被处理了,包含client在请求插入同步点时发送的GpuCommandBufferMessage_AsyncFlush消息,以及GPU进程主线程上加入同步点之后手工创建的GpuCommandBufferMsg_RetireSyncPoint消息。一旦队尾消息GpuCommandBufferMsg_RetireSyncPoint都得到处理。这就表明,同步点之前的全部GL命令此时都已经提交给GPU驱动了,是时候让这个同步点“退休”了,也就是上述文档中提到的,同步点收到信号将自己主动被删除。此时假设其它GpuChannel试图调用WaitSyncPointCHROMIUM,实际上这是一个空操作但也不会报错。
情况II: GpuChannel A在主线程中没有得到充分的调度机会,同一时候主线程调度了GpuChannel B。当中某个上下文中调用WaitSyncPointCHROMIUM去等待A中创建的同步点。
上面提到,WaitSyncPointCHROMIUm作为一条扩展的GL命令掺入到命令缓冲区中,所以GpuCommandBufferStub在逐条解析并运行命令缓冲区的GL命令时。当遇到WaitSyncPointCHROMIUM命令时,GLES2DecoderImpl::HandleWaitSyncPointCHROMIUM会运行例如以下操作:
- 触发注冊的回调函数GpuCommandBufferStub::OnWaitSyncPoint。该函数依据函数返回值决定当前GpuChannelB中的GpuCommandBufferStub到底是继续解析GL命令,还是停止解析GL命令等待同步点的“退休”;
- OnWaitSyncPoint会首先推断同步点是否已经“退休”,假设是,则返回true表明GpuChannel B的当前GpuCommandBufferStub能够继续GL命令的解析和运行。否则。将当前GpuCommandBufferStub设置为“不可被调度”状态,并请求SyncPointManager为等待的同步点加入一个回调函数GpuCommandBufferStub::OnSyncPointRetired,这个回调函数的作用就是恢复GpuCommandBufferStub为“可调度”状态。
细心的读者可能会发现,这里还有两个问题没有交代清楚:
第一,GpuCommandBufferStub的调度状态是怎样被使用的?
第二,SyncPointManager什么时候会调用GpuCommandBufferStub的回调函数OnSyncPointRetired?
前面提到GpuChannel的消息处理方式。GpuChannel::HandleMessage会逐一遍历全部未被处理的IPC消息,并交给相应的GpuCommandBufferStub来处理。而仅仅有GpuCommandBufferStub处于“可调度”状态时才干处理这个IPC消息,否则直接从HandleMessage方法中返回。因此GpuChannel的消息处理也将停滞不前,也就是说,此时GpuChannelB将停止下来等待同步点的“退休”,直到GpuCommandBufferStub::OnSyncPointRetired回调函数被触发。
一旦主线程上GpuChannel B的消息处理停止了,GpuChannel A则有很多其它的机会得到主线程的调度,GpuChannelA中积压的GpuCommandBufferMsg_AsyncFlush和GpuCommandBufferMsg_RetireSyncPoint消息将会一并被处理。GpuCommandBufferMsg_AsyncFlush消息负责将全部client的GL命令通过GLES2DecoderImpl提交给GPU驱动。而GpuCommandBufferMsg_RetireSyncPoint消息则会触发GpuCommandBufferStub::OnRetireSyncPoint消息处理函数,请求SyncPointManager将给定的同步点删除,在删除同步点时,SyncPointManager将会执行全部与该同步点关联的回调函数,所以此时GpuCommandBufferStub::OnSyncPointRetired被调用,GpuChannelB的消息处理又能够開始了。
须要特别说明的,因为 GpuCommandBufferStub的调度状态而导致GpuChannelB的消息处理停止,仅仅是针对服务端运行GL命令而言的,并没有停止接受来自client的消息,client仍然能够向GpuChannelB发送IPC消息。只是这些消息将会一直会被缓存在消息队列中。直到所等待的同步点“退休”,这就是上述提到的“服务端的等待”。
小结
Chromium为解决多个GPUclient的数据同步问题,引入同步点(SyncPoint)机制的GL扩展,从而同意client能够定制不同上下文GL命令运行的先后次序。同步点机制保证了当前上下文在运行兴许GL命令之前,其等待的同步点之前的全部GL命令都已经提交给GPU设备。同步点的等待是服务端的等待,client代码并不会被堵塞。在实现上,同步点机制依赖于GpuChannel的消息处理机制,当等待的同步点尚未“退休”时。当前的GpuCommandBufferStub它将被设置到“无法调度”状态。直到所有以前的同步点GL命令提交。
版权声明:本文博主原创文章,博客,未经同意不得转载。
Chromium Graphics: GPUclient的原理和实现分析之间的同步机制-Part II的更多相关文章
- Chromium Graphics: GPUclient的原理和实现分析之间的同步机制-Part I
摘要:Chromium于GPU多个流程架构的同意GPUclient这将是这次访问的同时GPU维修,和GPUclient这之间可能存在数据依赖性.因此必须提供一个同步机制,以确保GPU订购业务.本文讨论 ...
- Chromium Graphics: Android L平台上WebView的变化及其对浏览器厂商的影响分析
原创文章.转载请以链接形式注明原始出处为http://blog.csdn.net/hongbomin/article/details/40799167. 摘要:Google近期公布的Android L ...
- MYSQL索引结构原理、性能分析与优化
[转]MYSQL索引结构原理.性能分析与优化 第一部分:基础知识 索引 官方介绍索引是帮助MySQL高效获取数据的数据结构.笔者理解索引相当于一本书的目录,通过目录就知道要的资料在哪里, 不用一页一页 ...
- 【MySQL】排序原理与案例分析
前言 排序是数据库中的一个基本功能,MySQL也不例外.用户通过Order by语句即能达到将指定的结果集排序的目的,其实不仅仅是Order by语句,Group by语句,Distinct语句都会隐 ...
- PHP函数的实现原理及性能分析
前言 在任何语言中,函数都是最基本的组成单元.对于php的函数,它具有哪些特点?函数调用是怎么实现的?php函数的性能如何,有什么使用建议?本文将从原理出发进行分析结合实际的性能测试尝试对这些问题进行 ...
- js手风琴图片切换实现原理及函数分析
关键词: js手风琴 js百叶窗 js百页窗 实现原理解读 使用两层for循环实现, 第一层有三个功能,分别给第个li: 添加索引 预设位置 添加事件 第二层有两个功能,整理图片位置: 鼠标的li,以 ...
- 利用多写Redis实现分布式锁原理与实现分析(转)
利用多写Redis实现分布式锁原理与实现分析 一.关于分布式锁 关于分布式锁,可能绝大部分人都会或多或少涉及到. 我举二个例子:场景一:从前端界面发起一笔支付请求,如果前端没有做防重处理,那么可能 ...
- Camera图像处理原理及实例分析-重要图像概念
Camera图像处理原理及实例分析 作者:刘旭晖 colorant@163.com 转载请注明出处 BLOG:http://blog.csdn.net/colorant/ 主页:http://rg ...
- [置顶] NGINX原理分析之SLAB分配机制
一.基础概述 如果使用伙伴系统分配和释放算法,不仅会造成大量的内存碎片,同时处理效率也比较低.SLAB是一种内存管理机制,其核心思想是预分配.SLAB是将空间按照SIZE对内存进行分类管理的,当申请一 ...
随机推荐
- Hibernate(六)——多对多关联映射
前面几篇文章已经较讲解了三大种关联映射,多对多映射就非常简单了,不过出于对关联映射完整性的考虑,本文还是会简要介绍下多对多关联映射. 1.单向多对多关联映射 情景:一个用户可以有多个角色,比如数据录入 ...
- WM_NCHITTEST有21种取值,常用的有HTCAPTION,HTCLIENT,HTBORDER,HTSYSMENU,HTTRANSPARENT,罗列所有VCL里对其使用的情况
我为了移动一个无标题栏的窗体,使用了WM_NCHITTEST消息,这个消息大概如下: 通常,我们拖动对话框窗口的标题栏来移动窗口,但有时候,我们想通过鼠标在客户区上拖动来移动窗口. 一个容易想到的方案 ...
- ORA-00376:file x cannot be read at this time
之前出现过机房断电情况,重启数据库后发现出现ORA-00376的错误. 通过查询数据文件状态: SQL> select file_id,online_status from dba_data_f ...
- android 在你的UI中显示Bitmap - 开发文档翻译
由于本人英文能力实在有限,不足之初敬请谅解 本博客只要没有注明“转”,那么均为原创,转贴请注明本博客链接链接 Displaying Bitmaps in Your UI 在你的UI中显示Bitmap ...
- android--手机桌面添加网址链接图标(解决方式)
这样的做法最普遍最简单: 1.新建一个android空项目: 2.在drawable文件夹下加入图标文件,如icon.png:在values文件夹下的strings.xml文件里添加名称.如websi ...
- 在VM已安装Android4.4 连接小米手环 网络设置
1.打开一个终端 2.输入su,选择同意 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGl5dW4xMjNneA==/font/5a6L5L2T/fon ...
- poj1651(区间dp)
题目连接:http://poj.org/problem?id=1651 题意:给出一组N个数,每次从中抽出一个数(第一和最后一个不能抽),该次的得分即为抽出的数与相邻两个数的乘积.直到只剩下首尾两个数 ...
- 使用yiic安装开发web应用和解决yiic不是内部命令
使用yii创建应用程序,推荐博客:http://www.cnblogs.com/waitingbar/archive/2013/02/28/2937308.html 把php.exe加入为系统环境变量 ...
- Codeforces Round #269 (Div. 2) A B C
先说C 题目链接:http://codeforces.com/problemset/problem/471/C 题目意思:有 n 张卡,问能做成多少种不同楼层(floor)的 house.注意这 n ...
- cocos2d-x 消类游戏,类似Diamond dash 设计
前几天刚刚在学习cocos2d-x,无聊之下自己做了一个类似Diamond dash的消类游戏,今天放到网上来和大家分享一下.我相信Diamond dash这个游戏大家都玩过,游戏的规则是这样的,有一 ...