绘图表面 surface
Chromium硬件加速渲染的OpenGL上下文绘图表面创建过程分析
(17条消息) Chromium网页绘图表面(Output Surface)创建过程分析_罗升阳的博客-CSDN博客
渲染输出 viz:谷歌readme:
https://source.chromium.org/chromium/chromium/src/+/main:components/viz/common/frame_sinks/README.md
https://source.chromium.org/chromium/chromium/src/+/main:components/viz/README.md
https://chromium.googlesource.com/chromium/src/+/HEAD/components/viz/common/frame_sinks/
Window,Surface,Canvas和Bitmap的概念性概述
Bitmap只是像素集合的包装器。 可以将它想象成具有一些其他方便function的像素arrays。
Canvas只是包含所有绘图方法的类。 如果您熟悉它,它类似于AWT / Swing中的Graphics类。 关于如何绘制圆形或框等的所有逻辑都包含在Canvas中。 canvas在Bitmap或开放GL容器上绘制,但没有理由将来可以扩展到其他types的栅格。
SurfaceView是一个包含Surface的视图。 表面类似于位图(它具有像素存储)。 我不知道它是如何实现的,但我想它是一种某种Bitmap包装器,它有与屏幕显示直接相关的东西的额外方法(这就是表面的原因,Bitmap太通用了)。 您可以从Surface获取Canvas,这实际上是将Canvas与底层Bitmap相关联。
你的问题。
1.Canvas附有自己的Bitmap。 Surface有自己的Canvas附加到它。
是的,canvas在Bitmap(或开放的GL面板)上运行。 Surface为您提供了一个Canvas,它可以在Surface上用于其Bitmap样式的像素存储区。
从前面Chromium硬件加速渲染的UI合成过程分析一文可以知道,当Render进程的启动参数设置了“enable-delegated-renderer”选项时,Render进程使用委托渲染器。这个委托渲染器可以通过调用DelegatingRenderer类的静态成员函数Create创建。所谓委托渲染器,就是它委托Browser进程渲染和合成网页的UI。具体来说,就是Compositor线程在渲染网页时,它并没有真的执行渲染操作,而是计算出一系列的Render Pass,然后传递给Browser进程的的渲染器直接合成在浏览器窗口中。Render Pass描述的是网页当前可见的一个分块,它本质上是一个纹理,并且指定了纹理坐标,以及纹理的其它渲染参数,Browser进程根据这些信息就可以将网页的UI渲染出来。与非委托渲染器相比,委托渲染器可以减少一次渲染操作。非委托渲染器的Compositor线程在渲染网页时,会将网页当前可见的分块全部渲染一个纹理上,然后再将这个纹理传递给Browser进程。Browser进程拿到这个纹理后,还要再执行一次渲染操作,也就是合成在浏览窗口上,网页的UI才能显示出来。这过程包含了两次渲染操作,一次发生Render进程,另一次发生在Browser进程。因此非委托渲染器比委托渲染器多执行了一次渲染操作。
如果Render进程的启动参数没有设置“enable-delegated-renderer”选项,但是设置了使用硬件方式渲染网页的UI,那么Render进程就会使用非委托渲染器。这个非委托渲染器可以通过调用GLRenderer类的静态成员函数Create创建。注意,Browser进程也是通过CC模块来渲染浏览器窗口的,也就是它也像Render进程一样,将浏览器窗口抽象成CC Layer Tree、CC Pending Layer Tree和CC Active Layer Tree,这时候网页的UI就是其中的一个Layer。由于Browser进程不能再委托别人渲染自己的UI,因此在采用硬件方式渲染浏览器窗口的情况下,它就只能使用非委托渲染器来渲染浏览器窗口UI。
如果Render进程使用软件方式渲染网页的UI,那么它使用的渲染器通过调用SoftwareRenderer类的静态成员函数Create创建。一般来说,在获取网页的截图时,才会使用软件方式渲染网页的UI。
更多关于委托渲染器和非委托渲染器的知识,可以参考Chromium硬件加速渲染的UI合成过程分析一文。
————————————————
再回到LayerTreeHostImpl类的成员函数InitializeRenderer中,它为网页创建了渲染器之后,接下来判断网页是否使用线程化渲染方式。如果是的话,LayerTreeHostImpl类的成员变量setting_描述的一个LayerTreeSettings对象的成员变量impl_side_painting的值就会等于ture。这时候LayerTreeHostImpl类的成员函数InitializeRenderer就会调用另外一个成员函数CreateAndSetTileManager创建一个分块管理器(Tile Manager)。这是由于线程化渲染方式中,Main线程只是记录了每一个网页分块的绘制命令。在这种情况下,Compositor线程在渲染网页的UI之前,要先通过分块管理器执行每一个分块的绘制命令,以便得到一个图像。这个过程也就是光栅化过程。在非线程化渲染方式中,Main线程直接就将分块光栅化在一个图像中了,也就是它已经做了光栅化的操作了。
————————————————
创建分块管理器一个Resource Pool和Raster Worker Pool。分块管理器的职责是光栅化分块,不同的光栅化方式需要不同类型的Resource Pool和Raster Worker Pool。
CC模块提供了四种光栅化方式。第一种是使用GPU执行光栅化操作,后面三种使用CPU执行光栅化操作。注意,在硬件方式渲染网页UI的情况下,经过CPU光栅化后的分块仍然是通过GPU进行渲染的。
当Render进程设置了"force-gpu-rasterization"和"enable-impl-side-painting"启动选项时,LayerTreeHostImpl类的成员变量use_gpu_rasterization_的值就会等于true,表示要使用GPU光栅化网页分块。但是只有在网页使用硬件方式渲染时,才会真的使用GPU光栅化网页分块。这是因为使用GPU光栅化分块时,分块就直接光栅化在一个纹理中。这个纹理可以被GPU继续渲染出来形成网页的UI。因此,只有在使用硬件方式渲染网页的情况下,使用GPU光栅化分块才有意义。否则的话,光栅化后的分块内容还需要从GPU读取出来再交给CPU渲染。这样效率将会极其低下。
LayerTreeHostImpl类的成员变量output_surface_描述的是网页的绘图表面。从前面的分析可以知道,这个绘图表面是通过一个CompositorOutputSurface对象描述的,也就是LayerTreeHostImpl类的成员变量output_surface_指向的是一个CompositorOutputSurface对象。当调用这个CompositorOutputSurface对象的成员函数context_provider获得一个不为NULL的ContextProvider对象时,就说明网页使用硬件方式渲染UI。
GPU将分块光栅化在一个类型为GL_TEXTURE_2D的纹理上,因此这时候分块管理器需要一个类型为GL_TEXTURE_2D的Resource Pool,也就是一个能够创建类型为GL_TEXTURE_2D的纹理的Resource Pool。与此同时,分块管理器通过一个名称为”Direct“的Raster Worker Pool执行GPU光栅化操作。这个Raster Worker Pool可以通过调用DirectRasterWorkerPool类的静态成员函数Create创建。“Direct”的意思就是直接将分块光栅化在GPU里面,以后可以继续通过GPU进行渲染。
当Render进程设置了“enable-zero-copy”启动选项,并且平台支持GPU和CPU共享内存时,调用LayerTreeHostImpl类的成员函数UseZeroCopyTextureUpload得到的返回值就等于true,这时候将会使用CPU光栅化网页分块。在这种情况下,CPU将分块光栅化一块GPU和CPU都能访问的内存上,这样就可以避免在光栅化操作完成后,将分块内容从CPU拷贝到GPU的过程。因此,这种光栅化方式称为"Zero Copy"光栅化。GPU和CPU都能访问的内存是一种特殊的内存。在Android平台上,这种内存称为Graphics Buffer。CPU光栅化完成后,GPU可以将这个Graphics Buffer当作一个类型为GL_TEXTURE_EXTERNAL_OES的纹理访问。因此,这时候分块管理器需要一个类型为GL_TEXTURE_EXTERNAL_OES的Resource Pool,也就是一个能够创建类型为GL_TEXTURE_EXTERNAL_OES的纹理的Resource Pool。与此同时,分块管理器通过一个名称为”Image“的Raster Worker Pool执行CPU光栅化操作。这个Raster Worker Pool可以通过调用ImageRasterWorkerPool类的静态成员函数Create创建。
当Render进程设置了“enable-one-copy”启动选项,并且平台支持GPU和CPU共享内存时,调用LayerTreeHostImpl类的成员函数UseZeroCopyTextureUpload得到的返回值就等于true,这时候也会使用CPU光栅化网页分块。不过在这种情况下,CPU只是将分块光栅化一块临时的GPU和CPU都能访问的内存上,然后再将这个内存拷贝在一个类型为GL_TEXTURE_2D的纹理上。由于需要执行一次拷贝操作,因此这种光栅化方式称为”One Copy“光栅化。不过,这个拷贝是直接在GPU内完成的,并没有涉及到从CPU读取数据到GPU或者从GPU读取数据到CPU的操作,因此效率不是问题。这时候分块管理器需要两个Resource Pool,一个类型为GL_TEXTURE_EXTERNAL_OES,另一个类型为GL_TEXTURE_2D。其中,前者用来创建临时的GPU和CPU都能访问的内存,后者用来创建类型为GL_TEXTURE_2D的纹理。与此同时,分块管理器通过一个名称为”Image Copy“的Raster Worker Pool执行CPU光栅化操作。这个Raster Worker Pool可以通过调用ImageCopyRasterWorkerPool类的静态成员函数Create创建。
在其余情况下,分块管理器将使用CPU光栅化网页分块,并且是将分块光栅化在一个Pixel Buffer Object(PBO)中。这些PBO最后需要从CPU上传到GPU中,才能被GPU当作纹理访问。在这种情况下,分块管理器需要一个类型为GL_TEXTURE_2D的Resource Pool,用来创建类型为GL_TEXTURE_2D的纹理保存分块光栅化后得到的内容。与此同时,分块管理器通过一个名称为”Pixel Buffer“的Raster Worker Pool执行CPU光栅化操作。这个Raster Worker Pool可以通过调用PixelBufferRasterWorkerPool类的静态成员函数Create创建。
我们注意到,LayerTreeHostImpl类有一个成员变量on_demand_task_graph_runner_,它指向的是一个TaskGraphRunner对象。这个TaskGraphRunner对象也是用来执行光栅化任务的,不过它只用在非委托渲染器中。在非委托渲染方式中,有时候会因为内存限制,使得不能够为所有的网页分块都分配一块独立的内存执行光栅化操作。这时候非委托渲染器会使用一块共用的内存来光栅化那些没有独立内存的分块。这些分块光栅化完成后就会马上渲染,以便将共用的内存释放出来给其它分块使用。因此,对于这些没有独立内存的分块,它们的光栅化过程是比较特殊的,需要通过LayerTreeHostImpl类的成员变量on_demand_task_graph_runner_描述的TaskGraphRunner对象进行。
关于网页分块的光栅化过程,我们在后面的文章中再进行详细分析。
这一步执行完成之后,网页的绘图表面就初始化完成了。回到ThreadProxy类的成员函数InitializeOutputSurfaceOnImplThread中,它接下来就会调用Scheduler类的成员函数DidCreateAndInitializeOutputSurface通知调度器修改状态机的OutputSurfaceState状态,如下所示:
void Scheduler::DidCreateAndInitializeOutputSurface() {
......
state_machine_.DidCreateAndInitializeOutputSurface();
ProcessScheduledActions();
}
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler.cc中。
Scheduler类的成员函数DidCreateAndInitializeOutputSurface调用SchedulerStateMachine类的成员函数DidCreateAndInitializeOutputSurface将状态机的OutputSurfaceState状态修改为OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT,如下所示:
void SchedulerStateMachine::DidCreateAndInitializeOutputSurface() {
DCHECK_EQ(output_surface_state_, OUTPUT_SURFACE_CREATING);
output_surface_state_ = OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT;
......
}
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。
当状态机的OutputSurfaceState状态为OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT时,将会触发调度器尽快执行一个ACTION_SEND_BEGIN_MAIN_FRAME,也就是图1所示的第2个操作。这个操作将会请求Main线程绘制CC Layer Tree的内容。CC Layer Tree的内容绘制好之后,将会被同步到CC Pending Layer Tree中去执行光栅化操作,然后激活为CC Active Layer Tree。这个CC Active Layer Tree经过Compositor渲染后就得到网页的UI。
至此,我们就分析完成网页的绘图表面创建和初始化过程了。有了绘图表面之后,接下来就可以绘制和渲染网页的内容了。在接下来的一篇文章中,我们就继续分析网页的绘制过程,也就是Main线程绘制CC Layer Tree的过程,敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo。
————————————————
版权声明:本文为CSDN博主「罗升阳」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Luoshengyang/article/details/50995124
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2021-09-01 canvaskit默认字体更改
2017-09-01 SpringBoot读取application.properties文件
2016-09-01 wildfly jboss 优化配置
2015-09-01 Jboss7.1 加入realm auth认证 bootsfaces 美化的登录页面