天天看點

Chrome核心解析 -- 繪制引擎基礎篇:繪圖上下文(RenderingContext, GraphicsContext)

轉載請注明出處:http://blog.csdn.net/yunchao_he/article/details/41698169

多個圖形上下文(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程序的主線程就可完成。

整個過程如下:

Chrome核心解析 -- 繪制引擎基礎篇:繪圖上下文(RenderingContext, GraphicsContext)

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,以及通過顯式設定狀态參數以恢複相關的繪制狀态。通常情況下,虛拟上下文的切換是輕量級的。