理解WebKit和Chromium: Canvas2D及其实现
https://blog.csdn.net/yunchao_he/article/details/41698169
(22条消息) 理解WebKit和Chromium: Canvas2D及其实现_weixin_34081595的博客-CSDN博客
Canvas的‘getContext’方法包含一个参数,该参数用来指定创建上下文对象的类型。对于2d的图形操作,通过传递参数值’2d’,浏览器会返回一个2d的绘图上下文,称为CanvasRenderingContext2D, 它提供了用于绘制2d图形的各种API, 包括基本图形绘制(例如线,矩形,圆弧),文字绘制,图形变换,图片绘制及合成等。
2D和3D是互斥的,不能同时在同一个canvas中操作它们,也就是说,当创建了一个2D的上下文对象后,你不能再为其创建3D的上下文,反之亦然。
W3C定义了2D context标准的草案, 这些接口保存在IDL文件中。 WebKit根据这IDL来直接生成相关的C++类的代码,这些类包括CanvasRenderingContext2D,CanvasPattern, CanvasGradient, ImageData, TextMetrics等, 它们和标准中的接口一一对应,你可以在WebKit/Source/WebCore/html/canvas中找到它们。那么它们被JS中的代如何码调用的呢?答案是生成JavaScript绑定。
让我们简单了解一下这些类的作用:
WebKit端:
HTMLCanvasElement:DOM中的对应着HTML5 的canvas元素,该类包含有关为2D或者3D context服务的相关接口,主要的作用创建JS使用的2D或者3D上下文对象,绘图的平台无关的GraphicsContext对象,后端存储的buffer
GraphicsContext:WebKit的平台无关的上下文类,用于绘制工作,具体调用平台相关的上下文类来实现绘制
PlatformGraphicsContext:平台绘制上下文类,不同的平台有不同的实现,在chromium中是PlatformContextSkia
ImageBuffer:WebKit平台无关后端存储类,不同平台会定义不同的结构,在chromium中会使用SkCanvas
RenderHTMLCanvas:RenderObject的子类,为canvas而设计的
Chromium端:
PlatformContextSkia: Chromium中的PlatformGraphicsContext类
SkCanvas:skia画布,包含所有的绘制状态,使用SkDevice来绘制图形
SkDevice:设备类,包含一个SkBitmap作为后端,利用光栅扫描算法来生成像素值保存于后端存储中,用于软件绘制方案
SkGpuDevice:设备类,包含一个绘制的目标对象,通过GrContext来绘制,其利用硬件加速的GL库来绘制2D图形
GrContext: GPU绘制的上下文类,包含一个平台相关的3D上下文成员
Canvas2DLayerChromium:LayerChromium的子类,包含一个硬件加速的Canvas2D层
其他类:之前介绍过,不再赘述
上图中加速部分相关的GraphicsLayer, CCLayerImpl等,因为在之前介绍过,为方便起见,图中省略了它们。
Chromium中的Canvas2D的绘制操作的实现都是由图形库skia来完成,这里包括软件和硬件加速实现,chromium所要做的就是把WebKit中的调用交给skia来执行并和自己的绘制模型和硬件加速机制集成起来。
那么如何打开或者关闭Canvas2D的硬件加速功能呢? Chromium中提供了两个选项,分别是“--disable-accelerated-2d-canvas”和“--enable-accelerated-2d-canvas”,用户可以通过查看“about://gpu”来确认。
在介绍了基础设施之后,下面来看看具体的软件和硬件加速如何实现Canvas2D的。后面的软件实现和硬件加速实现都基于本章开始的canvas例子来说明。
Canvas 2D的软件实现
这里我们以本章中的Canvas2D的例子来说这个过程。
首先,当执行到JS代码中的canvas.getContext时,WebKit通过V8 JS绑定会调用HTMLCanvasElement.getContext。该函数根据传入的参数来决定创建2D或者3D的上下文对象。 在这里, CanvasRenderingContext2D对象会被创建。此时其他有关的对象例如ImageBuffer,GraphicsContext等不会被创建,直到后面使用到时才会被创建,这是WebKit的做事原则。
其次,当设置fillStyle属性时,WebKit同样通过V8 JS绑定调用CanvasRenderingContext2D.setFillStyle,在这种情形下,2D上下文对象会开始创建相关对象(在软件实现情况下,上图中右侧框中的对象不会被创建,包括ImageBuffer, GraphicsContext, PlatformContextSkia, SkCanvas,SkDevice和 SkBitmap)。
当执行JS的fillRect时,CanvasRenderingContext2D调用GraphicsContext来完成绘制。 这两个类是WebKit的基础类,GraphicsContext需要有不同移植来具体实现绘制工作,在chromium中,这就是PlatformContextSkia。该类是一个转接口,调用skia来绘制。SkCanvas根据之前设定的样式,由SkDevice利用光栅扫描法来计算生成相应的像素值,结果保存在一个SkBitmap中。
最后,当fillRect操作调用完成之后,会安排一个Invalidate相关区域的命令。而后,当该命令被执行了,WebKit会遍历RenderLayer依次绘制RenderObject的内容,当绘制Canvas元素时,会把之前Canvas绘制在SkBitmap的内容绘制到网页的Bitmap上(该bitmap通过共享内存机制会被Browser进程绘制在Tab子窗口中)。下图显示的是当有更新请求时,WebKit遍历RenderLayer树和Render树来绘制网页及Canvas的详细调用过程。
## 观察者是所谓的一套通知的接口而已(HTMLCanvasElement会触发以下的接口,外部关注则重载) class CanvasObserver { public: virtual ~CanvasObserver() { } virtual void canvasChanged(HTMLCanvasElement*, const FloatRect& changedRect) = 0; virtual void canvasResized(HTMLCanvasElement*) = 0; virtual void canvasDestroyed(HTMLCanvasElement*) = 0; }; ## 真正的观察者便重载该接口即可(HTMLCanvasElement通知CSSCavasValue变化) class CanvasObserverProxy : public CanvasObserver { public: CanvasObserverProxy(CSSCanvasValue* ownerValue) : m_ownerValue(ownerValue) { } virtual ~CanvasObserverProxy() { } virtual void canvasChanged(HTMLCanvasElement* canvas, const FloatRect& changedRect) { m_ownerValue->canvasChanged(canvas, changedRect); } virtual void canvasResized(HTMLCanvasElement* canvas) { m_ownerValue->canvasResized(canvas); } virtual void canvasDestroyed(HTMLCanvasElement* canvas) { m_ownerValue->canvasDestroyed(canvas); } private: CSSCanvasValue* m_ownerValue; } ## 监听的CSSCanvasValue的操作 class CSSCanvasValue : public CSSImageGeneratorValue { ## 通知CSSValue的受众RenderObject void CSSCanvasValue::canvasChanged(HTMLCanvasElement*, const FloatRect& changedRect) { IntRect imageChangeRect = enclosingIntRect(changedRect); HashCountedSet<RenderObject*>::const_iterator end = clients().end(); for (HashCountedSet<RenderObject*>::const_iterator curr = clients().begin(); curr != end; ++curr) const_cast<RenderObject*>(curr->key)->imageChanged(static_cast<WrappedImagePtr>(this), &imageChangeRect); } ## 关于样式图片的一个继承和调度的关系 StyleImage=>StyleGeneratedImage=>CSSImageGeneratorValue=>CSSCanvasValue
————————————————
版权声明:本文为CSDN博主「sauphy」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sauphy/article/details/50282515
-----------------------------------------------------------------
多个图形上下文(GraphicsContext,也称为3D Context, GL Context)
众所周知,使用OpenGL/D3D绘图,或者GDI, GTK/Cario, QT, X11, Skia等与绘图相关的库时,有图形上下文(GraphicsContext)这个概念。它和进程上下文有类似之处,只不过它保存绘图有关的信息,比如当前申请的Buffer, Texture, FBO等对象,以及这些对象的属性,如Texture的tile mode(repeat, mirror),以及变换矩阵,投影矩阵,光照信息,线宽,线的颜色,裁剪区大小,alpha混合模式和参数,等等众多相关属性。在Chromium里,由于其多进程构架以及Web内容的复杂性, 它可能包含以下多个GraphicsContext:
首先,网页中的一些特殊元素或组件在Chromium里往往有自己的GraphicsContext,这些特殊元素是WebGL,Canvas2D, Video, Pepper3D等。页面中每出现一个特殊元素,Chromium都会创建一个独立的GraphicsContext。另外,包含其它所有元素的Base Layer(也称Root Layer)也有自己的GraphicsContext。它们都是绘制到off-screen的FBO, 比如PixelBuffe或者Texture上。
其次,RenderCompositor将上述的各个Layer合成,合成过程中也会创建自己的GraphicsContext,它也是绘制到off-screen的FBO。
最终,BrowserCompositor把网页内容和UI合成,也会创建GrapicsContext,这一次合成的结果将绘制到屏幕上。它对应着on-screen Framebuffer。对于最常见的双buffer系统,它会绘制到back buffer, 然后通过swap buffer在屏幕上显示。
虚拟上下文:Virtual Context
对于GPU硬件上不支持多个GraphicsContexts(或者虽然支持,但性能很差)的设备,Chromium会创建虚拟上下文。每个虚拟上下文都维护自己的绘图状态,比如所绑定的FBO, texture,buffer等,以及一些绘制状态和属性。多个虚拟上下文对应一个真实的GraphicsContext。切换虚拟上下文时,则需要重新绑定这些buffer, 以及恢复绘制状态。
所以,虚拟上下文的切换只是逻辑上的切换,并没有真正切换GraphicsContext。这种方式可以在不支持多个图形上下文的设备上,通过虚拟上下文来模拟多个图形上下文。
RenderingContext
实际上,Chromium中为了管理GraphicsContext, 在创建Command Buffer时不仅创建了GraphicsContext和Virtual Context, 还创建了和绘制相关的其它重要的类,比如TransferBufferManager, CommandBufferService, GLES2DecoderImpl, GpuScheduler, GLES2Implementation, GLES2CmdHelper, TransferBuffer等等。这些类和GraphicsContext, Virtual Context一起,构成了Chromium的绘图上下文(RenderingContext)。特别是GLES2DecoderImpl, 它负责解析command buffer客户端发来的Gpu请求,并真正发起GL操作。它的初始化函数gpu::gles2::GLES2DecoderImpl::Initialize里,会创建很多重要的基础设施,比如FBO, 以及需要attach到FBO中的color buffer(Texture 或者RenderBuffer), depth buffer, stencil buffer等等。
这些类和Command Buffer紧密相关,将在下一节详细介绍。读者需要了解的是,Chromium中为了支持多个GraphicsContext,引入了命令缓冲区(command buffer)机制。它需要在多个图形上下文之间进行切换,同步。所以,除了GraphicsContext本身,Chromium的RenderingContext还包含很多其它的基础设施,来实现这一机制。
(注意:GraphicContext和RenderingContext都可以翻译为绘图上下文,在Chromium里,RenderingContext包含GraphicsContext, 还包括VirtualContext, GpuScheduler, GLES2Decoder, GLES2Implementation等等。为避免混淆,除了将GraphicsContext翻译为图形上下文,RenderingContext翻译为绘图上下文以外,将尽量使用英文原文)
Chromium中的具体实现:
1. 创建RenderingContext, GraphicsContext, VirtualContext
以WebGL为例,当JavaScript调用getContext("webgl")时,则会创建GraphicsContext和VirtualContext。具体调用过程是,Render进程的主线程中,当V8执行到
getContext("webgl"); // 或者getContext("experimental-webgl");
时,会通过JS binding到Blink, 调用Blink中相应的API: blink::HTMLCanvasElement::getContext, 它会创建WebGLRenderingContext,后者调用Chromium的content模块,创建RenderingContext, 其中包括GraphicsContext和Virtual Context,其主要的调用序列如下:
blink::HTMLCanvasElement::getContext
blink::WebGLRenderingContext::create
content::RendererBlinkPlatformImpl::createOffscreenGraphicContext3D
content::WebGraphicsContext3DCommandBufferImpl::InitializeOnCurrentThread
content::WebGrapicsContext3DCommandBufferImpl::CreateContext
content::CommandBufferProxyImpl::Initialize
当然,实际创建GraphicsContext需要发起GL操作,比如eglCreateContext。而Chromium里有一个简单原则:凡是GL操作,都是Browser进程的Gpu线程负责。同样地,上述代码本身并没有完成创建RenderingContext的过程,它只是向Browser进程发送创建请求(发送GpuCommandBufferMsg_Initialize消息),Browser进程收到这个请求后,由
content::GpuCommandBufferStub::OnInitialize
负责初始化command buffer, 而GraphicsContext就是在初始化command buffer过程中创建的。具体实现是,通过调用gfx::GLSurface::CreateOffscreenGLSurface或者gfx::GLSurface::CreateViewSurface来创建GLSurface, 然后通过调用gfx::GLContext::CreateGLContext来创建图形上下文。而虚拟上下文则调用gpu::GLContextVirtual::GLContextVirtual来创建。其它和RenderingContext相关的类,也是在Command Buffer初始化时创建的,比如GpuScheduler, GLES2Implementation, GLES2DecoderImpl等等,特别重要的是gpu::gles2::GLES2DecoderImpl::Initialize中申请了off-screen GraphicsContext的FBO, 以及FBO所需要的texture和render buffer。它们位于Browser进程的主线程。
当然,GLSurface和GLContext都是平台相关的,它会调用具体的库来实现,比如egl, CGL等等。它们位于Gpu线程。以Chromium on Android为例,GraphicsContext是通过/src/ui/gl_context_android.cc中的工厂函数CLContext::CreateGLContext,最终调用GLContextEGL::eglCreateContext来创建(/src/ui/gl/gl_context_egl.cc)。而虚拟上下文的创建其实和平台无关,它在Browser进程的主线程就可完成。
整个过程如下:
2. 上下文切换
上下文切换,实际上就是让目标Context成为当前Context。在Chromium里,则是调用gpu::GLContext::MakeCurrent。无论目标Context是平台相关的真实3D Context(具体实现有GLContextEGL, GLContextWGL, GLContextGLX等等), 还是平台无关的Virtual Context, 它们都是GLContext的子类。通过调用gpu::GLContext::MakeCurrent都能动态绑定到具体的类中。具体分析如下:
如果切换的目标Context是真实的GraphicsContext, 则会调用平台相关的MakeCurrent函数,比如GLContextEGL::MakeCurrent。最终调用平台相关的GL操作,比如eglMakeCurrent。真实的3D Context的切换操作比较耗时。具体代码位于/src/ui/gl目录下的相关文件中。
而对于VirtualContext, 则调用GLContextVirtual。它会查看当前的真实3D Context与目标Context是否相同。如果相同,则无需切换真实的GraphicsContext。对于不支持多个GraphicsContext的设备,除第一次调用MakeCurrent会切换到真实的GraphicsContext, 其它情况下都不必切换真实GraphicsContext。切换虚拟上下文的函数是/src/ui/gl/gl_gl_api_implementation.cc中的gfx::VirtualGLApi::MakeCurrent。从GLContextVirtual::MakeCurrent到该函数的调用序列为:
gpu::GLContextVirtual::MakeCurrent
gfx::GLContext::MakeVirtuallyCurrent
gfx::VirtualGLApi::MakeCurrent
如前所述,切换VirtualContext主要是重新绑定buffer,以及通过显式设置状态参数以恢复相关的绘制状态。通常情况下,虚拟上下文的切换是轻量级的。
————————————————
版权声明:本文为CSDN博主「yunchao_he」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yunchao_he/article/details/41698169
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2021-01-05 汉诺塔问题 最简单的图文讲解递归实现
2017-01-05 writing
2015-01-05 微信授权登陆接入第三方App(步骤总结)Android
2015-01-05 修改Android签名证书keystore的密码、别名alias以及别名密码