chrome 硬件渲染(GPU Accelerated Compositing in Chrome)
原文鏈接
http://www.chromium.org/developers/design-documents/gpu-accelerated-compositing-in-chrome
chrome 中集成了webkit,這篇文章對webkit 硬件渲染過程有詳細(xì)的介紹,很好。
簡介
這篇文檔講解chrome硬件加速合成的實(shí)現(xiàn)細(xì)節(jié)和背景。
介紹
通常來講,網(wǎng)頁瀏覽器完全依賴CPU來渲染網(wǎng)頁內(nèi)容。
隨著高性能的GPU日益成為設(shè)備(即使是最小的設(shè)備)的一部分,以及富媒體(video,3D圖形游戲)在網(wǎng)絡(luò)體驗(yàn)中扮演著越來越重要的角色,
人們的注意力開始轉(zhuǎn)向?qū)ふ乙恍┓椒▉砀浞掷玫讓佑布哪芰?#xff0c;以提供更好的體驗(yàn)以及更少的電量消耗。
很明顯,如果使GPU直接參與網(wǎng)頁內(nèi)容的合成會(huì)帶來非常顯著地提速。這樣做最大的好處是可以省去不必要的大量數(shù)據(jù)拷貝過程,尤其是將內(nèi)存中的video數(shù)據(jù)拷貝到系統(tǒng)內(nèi)存的過程。
這種加速有兩個(gè)最明顯的用武之地,一個(gè)是使用硬件解碼的Video元素,一個(gè)是WebGL canvas, 這兩個(gè)元素都把它們的結(jié)果數(shù)據(jù)放在一個(gè)GPU無法快速訪問的內(nèi)存區(qū)域。
將包含網(wǎng)頁內(nèi)容的各個(gè)layer的合成交給GPU來做還有另外一些好處。
就涉及大量像素處理的繪制和合成操作而言,GPU要遠(yuǎn)比CPU有效率(不管是速度還是繪制能力),這是因?yàn)獒槍@些類型的工作負(fù)載,GPU有做專門的設(shè)計(jì)。
將這些操作交給GPU完成,可以實(shí)現(xiàn)GPU和CPU并行運(yùn)行,從而創(chuàng)造出一個(gè)高效率的圖形處理管線。
第一部分:webkit 渲染基礎(chǔ)及軟件渲染路徑
webkit 渲染引擎的源碼非常的繁雜,而且注釋和說明性文檔實(shí)在是太少了。
為了了解GPU加速在Chrome中是怎么工作的,必須先了解webkit 實(shí)現(xiàn)頁面渲染的基礎(chǔ)組件。
我們先簡要回顧一下GPU加入以前的工作流程,再分析GPU加入后的工作流程,以此來理解GPU是怎樣起作用的。
Nodes and DOM tree
網(wǎng)頁內(nèi)容在webkit 內(nèi)部以Node為節(jié)點(diǎn)的樹形結(jié)構(gòu)存儲,稱為DOM tree。
網(wǎng)頁中的每一個(gè)HTML 元素,包括元素之間的text都和一個(gè)Node相關(guān)聯(lián)。
DOM tree的最頂層Node 永遠(yuǎn)是Document Node.
Frome Nodes to RenderObjects
Dom tree中每一個(gè)可視化的Node 節(jié)點(diǎn)都對應(yīng)著一個(gè)RenderObject.RenderObject 也存儲在一棵對應(yīng)的樹結(jié)構(gòu)中,稱為Render tree.
RenderObject 知道如何在一個(gè)display surface上顯示(繪制) Node 節(jié)點(diǎn)的內(nèi)容。
它通過調(diào)用GraphicsContext提供的繪制接口來完成顯示(繪制)過程。
GraphicsContext最終負(fù)責(zé)將像素寫入一塊bitmap,這塊bitmap會(huì)被顯示在屏幕上。
在Chrome中,GraphicsContext 封裝了Skia, 2D圖形庫,對GraphicsContext的大多數(shù)調(diào)用都轉(zhuǎn)變成對SkCanvas或SkPlatformCanvas的接口調(diào)用。
在軟件渲染的情況下,整個(gè)網(wǎng)頁只有一個(gè)GraphicsContext,所有的RenderObjects都繪制在同一個(gè)GraphicsContext上.
From? RenderObjects to RenderLayers
每一個(gè)RenderObject 都關(guān)聯(lián)著一個(gè)RenderLayer.這種關(guān)聯(lián)是通過祖先RenderObject 節(jié)點(diǎn)直接或間接地建立的。
分享同一坐標(biāo)系的RenderObject(比如被同一CSS transform屬性影響的元素)必然位于同一RenderLayer.
正是由于RenderLayer的存在,網(wǎng)頁上的元素才可以按照正確的順序合成,從而恰當(dāng)?shù)娘@示有交疊的內(nèi)容,和半透明元素等效果。
像在RenderBoxModelObject::requiresLayer() 及RenderBoxModelObject的子類中重實(shí)現(xiàn)的requiresLayer()中定義的那樣,有很多種條件可以
觸發(fā)一個(gè)特定的RenderObject創(chuàng)建一個(gè)RenderLayer.
通常來講,滿足下列條件之一時(shí),RenderObject就會(huì)創(chuàng)建RenderLayer:
1.網(wǎng)頁的root節(jié)點(diǎn);
2.有明確的CSS position屬性(relative,absolute,transform)
3.元素是透明的
4.overflow, alpha mask,或者reflection
5.有css filter(濾鏡) 屬性
6.有2D加速Context或者3D(webGL)context的 canvas 元素對應(yīng)的RenderObject.
7.video元素對應(yīng)的RenderObject
需要注意的是RenderObject和RenderLayer之間并不是一一對應(yīng)的。?
RenderObject 或者與它所創(chuàng)建的RenderLayer相關(guān)聯(lián)(如果它創(chuàng)建了的話),或者與它的第一個(gè)擁有RenderLayer的祖先RenderObject創(chuàng)建的RenderLayer相關(guān)聯(lián)。
RenderLayer 也會(huì)形成一個(gè)樹型層次結(jié)構(gòu)。這個(gè)樹結(jié)構(gòu)的根節(jié)點(diǎn)是與網(wǎng)頁的根元素相對應(yīng)的RenderLayer.每一個(gè)RenderLayer 節(jié)點(diǎn)的后代都是
包含在父親RenderLayer內(nèi)的可視化的RenderLayer.
每一個(gè)RenderLayer的子節(jié)點(diǎn)都被存儲在兩個(gè)按升序排列的有序表中。
negZOrderList 有序表中存儲的子節(jié)點(diǎn)是z-index值為負(fù)的子RenderLayer,所以這些RenderLayer在當(dāng)前RenderLayer的下面;
posZOrderList有序表中存儲的子節(jié)點(diǎn)是z-index值為正的子RenderLayer,所以這些RenderLayer在當(dāng)前RenderLayer的上面;
Putting it Together: many trees
簡言之,在概念上存在著三顆平行的樹結(jié)構(gòu),分別負(fù)責(zé)渲染過程中的不同目的:
DOM Tree 是我們的基礎(chǔ)模型;
RenderObject Tree是由DOM Tree的可視化節(jié)點(diǎn)一對一映射而來的,RenderObject知道如何渲染它所對應(yīng)的Dom Node.
RenderLayer Tree 由RenderObject 映射的RenderLayers組成。這種映射關(guān)系是多對一的,因?yàn)槊恳粋€(gè)RenderObject或者與它所擁有(創(chuàng)建)的RenderLayer相關(guān)聯(lián),
或者與它的第一個(gè)擁有RenderLayer的祖先RenderObject的RenderLayer相關(guān)聯(lián)。
RenderLayer Tree負(fù)責(zé)維護(hù)RenderLayers 之間的Z-ordering 順序。
圖 render trees.png
the software rendering path
從根本上說,webkit 通過從根節(jié)點(diǎn)開始遍歷RenderLayer樹結(jié)構(gòu)來渲染頁面。
Webkit 代碼庫中包含兩種完全不同的渲染路徑。軟件渲染路徑和硬件加速渲染路徑。
軟件渲染路徑是傳統(tǒng)的渲染方式。
在軟件渲染方式下,按照從后向前順序繪制各層RenderLayer來渲染頁面
從根節(jié)點(diǎn)開始遞歸遍歷RenderLayer層次結(jié)構(gòu)。遍歷過程中要完成大量工作。
這些工作是在RenderLayer::paintLayer()中完成的,包括以下一些基本步驟(為了更明晰,簡要列在這里):
a.判斷當(dāng)前RenderLayer是否與damage rect 有交集;
b.調(diào)用paintLayer()遞歸繪制有序表negZOrderList中的各層RenderLayer;
c.請求與當(dāng)前RenderLayer相關(guān)聯(lián)的那些RenderObject 繪制它們自己;
d.這個(gè)繪制過程是通過遍歷以創(chuàng)建當(dāng)前RenderLayer的RenderObject為開始節(jié)點(diǎn)的Render Tree進(jìn)行的。當(dāng)遇到一個(gè)RenderObject所關(guān)聯(lián)的RenderLayer與當(dāng)前RenderLayer不同時(shí),遍歷過程便結(jié)束了。
e.調(diào)用paintLayer()遞歸繪制有序表posZOrderList中的各層RenderLayer;
在軟件渲染方式下,RenderObjects 調(diào)用一個(gè)共享的GraphicsContext(Chrome中是Skia)提供的繪制接口將自己繪制到一塊目標(biāo)bitmap上。
注意,GraphicsContext 本身并沒有Layers概念,但是為了正確繪制半透明的RenderLayer.
這里有一個(gè)警告:半透明的層在繪制與它自身相關(guān)聯(lián)的RenderObject之前會(huì)先調(diào)用 GraphicsContext::beginTransparencyLayer()。
在Skia的實(shí)現(xiàn)中,beginTransparencyLayer()這個(gè)調(diào)用會(huì)使接下來的所有的繪制命令都繪在一塊單獨(dú)的Bitmap上。
當(dāng)前RenderLayer繪制完成后,這塊單獨(dú)的Bitmap會(huì)和之前的那塊Bitmap合成。當(dāng)與當(dāng)前的半透明的RenderLayer相關(guān)聯(lián)的RenderObject都繪制完時(shí)候,需要相應(yīng)的調(diào)用GraphicsContext::endTransparencyLayer()。
From WebKit to the Screen
圖:software rendering architecture
當(dāng)所有的RenderLayer都繪制到一塊共享的Bitmap上后,就需要將這塊Bitmap顯示到屏幕上。
在Chrome中,這塊Bitmap位于一塊共享內(nèi)存中,對這塊Bitmap的控制通過IPC方式交給Browser進(jìn)程。
Browser進(jìn)程負(fù)責(zé)調(diào)用系統(tǒng)的窗口API將這塊Bitmap繪制到合適的窗口中。
第二部分: 硬件基礎(chǔ)
回憶Webkit 代碼庫中包含兩種渲染路徑,一種是軟件渲染路徑,一種是硬件加速渲染路徑。
如果你已經(jīng)了解了軟件渲染方式,我們可以開始分析硬件加速渲染方式與軟件渲染方式的區(qū)別。
正如名字所提示的,硬件加速方式就是利用GPU在合成一些RenderLayer的內(nèi)容時(shí)的加速功能。
硬件加速的相關(guān)代碼由編譯時(shí)的宏開關(guān)ACCELERATED_COMPOSITING 控制。
當(dāng)至少有一個(gè)RenderLayer要求使用硬件加速時(shí),或者當(dāng)標(biāo)志 --forced-compositing-mode打開時(shí),?
Chrome就會(huì)使用硬件加速渲染路徑。這個(gè)標(biāo)志在android和ChromeOS的Chrome中默認(rèn)都是打開的。
蘋果和大多數(shù)iOS上的Safari都采用硬件加速的渲染方式,并且大量運(yùn)用了蘋果的CoreAnimation API.
Introducing the compositor
在硬件加速渲染方式下,一些RenderLayer(不是所有)擁有自己的backing surface(擁有單獨(dú)的backing surface的renderlayer叫做compositing layers).
擁有Backing surface的RenderLayer在繪制時(shí)將它們自身繪制到自己的backing surface上,而不是網(wǎng)頁公共的那塊bitmap上。
隨后的合成過程會(huì)將所有的backing surface合成到目標(biāo)Bitmap上。
我們?nèi)匀皇菑腞enderLayer Tree開始,以一塊單獨(dú)的Bitmap結(jié)束,但是這種分成兩個(gè)階段的方式,允許合成器在per-compositing-layer的基礎(chǔ)上做一些額外的工作。
例如,在合成每個(gè)compositing layer之前,合成器負(fù)責(zé)對這個(gè)compositing layer所對應(yīng)的bitmap做一些必要的變換(由css 的 transform 屬性值指定)。
而且,由于這些Layer的繪制和合成分開了,這些Layer中的一個(gè)如果失效,只會(huì)引起這一個(gè)Layer中內(nèi)容的重繪,然后再將這個(gè)layer重新合成即可。
相反地,在軟件渲染中,如果任何一個(gè)Layer的內(nèi)容失效會(huì)引起在它上面和下面的所有Layer的重繪。這些都是CPU的不必要的負(fù)擔(dān)。
More Trees: From RenderLayers to GraphicsLayers
在軟件渲染方式下,整個(gè)網(wǎng)頁只有一個(gè)GraphicsContext。
在硬件加速合成的方式下,每個(gè)compositing layer 都需要一個(gè)單獨(dú)的GraphicsContext,以便每個(gè)Compositing layer 都可以繪制在自己單獨(dú)的
bitmap上。
前面已經(jīng)講過,有一組平行的概念上的樹結(jié)構(gòu),每一個(gè)都比前一個(gè)更稀疏,而且每一個(gè)都對前一個(gè)的一個(gè)子樹負(fù)責(zé),這組樹結(jié)依次為:DOM Tree;RenderObject Tree,和RenderLayer Tree.
在介紹硬件合成時(shí),我們要再加一個(gè)概念上的樹: GraphicsLayer tree。每一個(gè)RenderTree或者擁有自己的GraphicsLayer(如果這個(gè)RenderLayer是compositing Layer的話),或者是使用它的第一個(gè)擁有GraphicsLayer的祖先節(jié)點(diǎn)的GraphicsLayer.
RenderLayer與GraphicsLayer的關(guān)系類似于RenderObject與RenderLayer之間的關(guān)系。每個(gè)GraphicsLayer都擁有一個(gè)GraphicsContext,與這個(gè)GraphicsLayer相對應(yīng)的每個(gè)RenderLayer都繪制到這個(gè)GraphicsContext.上。
理論上講,每一個(gè)RenderLayer都可以將自己繪制到一個(gè)單獨(dú)的backing surface上以避免不必要的重繪。
但是在實(shí)際中,這種做法會(huì)導(dǎo)致內(nèi)存的大量浪費(fèi)。在當(dāng)前的Webkit 實(shí)現(xiàn)中,只有滿足以下條件之一,RenderLayer才會(huì)擁有它自己的composingLayers.(見RenderLayerCompositor::requiresCompositingLayer()):
layer 有3D或者CSS transform 屬性值;
layer是硬解碼的video 元素使用的;
layer是擁有3D context或2D加速context的Canvas標(biāo)簽使用的;
layer是一個(gè)合成的插件使用的;
layer使用了動(dòng)畫表示它的透明度,或者Layer使用了動(dòng)畫形式的webkit 變換;
layer 使用了加速的CSS 濾鏡;
擁有compositing layer后代的layer
渲染在Compositing layer之上的layer
這意味著擁有需要合成的RenderLayer的網(wǎng)頁總是通過合成器來渲染。其他網(wǎng)頁可能需要也可能不需要合成器來渲染,這取決于標(biāo)志 --forced-compositing- mode 的狀態(tài)。
the code?
與合成器相關(guān)的代碼在WebCore中。由USE(ACCELERATED_COMPOSITING)控制。一部分代碼是所有平臺共享的,一部分是Chrome平臺專用的。
webkit的代碼結(jié)構(gòu)允許把chrome平臺相關(guān)的合成器的實(shí)現(xiàn)放在平臺相關(guān)的源文件中(platform/graphics/chromium),不需要修改webkit的核心代碼。
同樣的,依據(jù)Skia圖形庫實(shí)現(xiàn)的GraphicsContext我們也放在了平臺相關(guān)的源文件中。
GPU 在哪?
GPU是怎樣發(fā)揮作用的?為了節(jié)省耗時(shí)的內(nèi)存?zhèn)魉?#xff0c;加入了加速的合成器。瀏覽器的Tab區(qū)域的最終渲染都由GPU直接控制。
這種模式與當(dāng)前的render進(jìn)程將一塊含有網(wǎng)頁內(nèi)容的Bitmap通過IPC和共享內(nèi)存?zhèn)鹘o瀏覽器進(jìn)程去顯示的模式是很不相同的。
在含有硬件加速的架構(gòu)中,硬件加速Layer(比如,需要合成的 Layer,即那些擁有backing surface的Layer)和網(wǎng)頁其余內(nèi)容的合成過程是通過調(diào)用平臺相關(guān)的3D API在GPU上完成的。
實(shí)現(xiàn)這些API的最終代碼被封裝成一個(gè)庫,這個(gè)庫運(yùn)行在渲染進(jìn)程中,叫做合成器。
合成器庫利用GPU合成網(wǎng)頁上的矩形區(qū)域(例如所有的compositing layers)到一塊單獨(dú)的bitmap中。這塊bitmap中的內(nèi)容就是最終要顯示的網(wǎng)頁內(nèi)容。
架構(gòu)插曲: GPU 進(jìn)程
在我們進(jìn)一步分析合成器生成的GPU 命令之前,有必要先了解一下Render進(jìn)程是怎樣向GPU發(fā)送命令的。
在chrome平臺的多進(jìn)程模型中,我們有一個(gè)專門的進(jìn)程負(fù)責(zé)這項(xiàng)工作:GPU進(jìn)程。GPU進(jìn)程的存在主要是出于安全方面的考慮。
受限于沙箱性質(zhì),渲染進(jìn)程(webkit 與合成器都運(yùn)行在此進(jìn)程)不能直接調(diào)用OS提供的3D API(windows平臺是Direct3D,其余平臺是OpenGL).
出于這個(gè)原因,我們需要一個(gè)單獨(dú)的進(jìn)程做渲染工作。這個(gè)進(jìn)程就是GPU.GPU進(jìn)程是專門用來訪問系統(tǒng)的3D API.它以client-server模式工作:
客戶端(運(yùn)行渲染進(jìn)程的代碼),并不直接調(diào)用系統(tǒng)的3D API,而是將這些命令序列化后放在一個(gè)環(huán)形Buffer(命令Buffer)中,這塊環(huán)形Buffer位于client進(jìn)程與server進(jìn)程之間的一塊共享內(nèi)存中。
服務(wù)端(GPU進(jìn)程,運(yùn)行在一個(gè)可以直接訪問平臺的3D API的受限較少的砂箱中)將序列化的命令從共享內(nèi)存中取出,解析,然后調(diào)用合適的
圖形命令,結(jié)果數(shù)據(jù)直接輸出給窗口。
圖 GPU Process
GPU 進(jìn)程接收到的命令的樣式類似于GL ES 2.0 API(比如一個(gè)命令對應(yīng)于glClear,一個(gè)命令對應(yīng)于glDrawArrays).由于大部分GL 命令都沒有返回值,
所以client 和server之間可以近乎異步的工作。這使性能負(fù)載可以保持在非常低的程度。
客戶端和服務(wù)端的所有同步,比如客戶端需要通知服務(wù)端有額外的工作需要做,都由IPC機(jī)制來控制。
還有一點(diǎn)需要注意的是,共享內(nèi)存除了存儲命令外,還用來在客戶和服務(wù)器之間傳送更大的源數(shù)據(jù),比如bitmap for textures,vertext
這里略掉一部分沒有翻譯
第三部分:硬件渲染模式下,利用合成器渲染
合成器的實(shí)現(xiàn)建立在GL ES 2.0的客戶端庫之上,這個(gè)庫將圖形接口調(diào)用代理給GPU 進(jìn)程(用上文解釋的方式)。
當(dāng)一個(gè)網(wǎng)頁使用合成器渲染時(shí),它的所有像素都是通過GPU 進(jìn)程直接繪制到窗口上。
合成器維護(hù)著GraphicsLayers的一個(gè)層次結(jié)構(gòu),這個(gè)層次結(jié)構(gòu)是通過遍歷RenderLayer Tree以及隨著頁面變化的更新建立的。
除了WebGL和Video Layers,每一個(gè)GraphicsLayer的內(nèi)容都是先繪制到一塊系統(tǒng)內(nèi)存的Bitmap上的(與軟件渲染方式中的過程相同):
每一個(gè)RenderLayer都請求與它相關(guān)聯(lián)的RenderObject 繪制自身到與當(dāng)前RenderLayer相關(guān)聯(lián)的GraphicsLayer的GraphicsContext上,即繪制在了
這個(gè)GraphicContext相關(guān)聯(lián)位于系統(tǒng)共享內(nèi)存中的一塊Bitmap上。這塊Bitmap隨后會(huì)傳給GPU 進(jìn)程(利用GPU進(jìn)程一節(jié)中介紹的資源傳送機(jī)制),
接下來,GPU 進(jìn)程就把這塊Bitmap作為一個(gè)textue上傳給GPU.
合成器跟蹤從最近一次被繪制后有變化的GraphicsLayer,只更新與變化的GraphicsLayer相對于的texture.
當(dāng)所有的texture都上傳給GPU之后,渲染頁面內(nèi)容就變成了深度優(yōu)先遍歷GraphicsLayer層次結(jié)構(gòu)并發(fā)送GL 命令為之前上傳以texture上傳給GPU的GraphicsLayer 繪制texture quad.
A texture quad 是屏幕上的用給定的Texture填充的四邊形(在我們的例子里,就是GraphicsLayer的內(nèi)容)。
需要注意的是,深度優(yōu)先遍歷需要確保GraphicsLayer的正確的z-ordering順序。與每一個(gè)GraphicsLayer相關(guān)聯(lián)的RenderLayer的Z-ordering序是在較早的時(shí)候RenderObject被光柵化到texture中時(shí)予以保證的。
圖: compositing with the gpu process
the code?
chrome 平臺的合成器代碼的實(shí)現(xiàn)在webcore下的目錄 platform/graphics/chromium中。
合成器的邏輯大部分在LayerRendererChromium.cpp文件中。
各種composited layer的實(shí)現(xiàn)分別在文件 {Content|Video|Image} LayerChromium.cpp中。
第四部分:優(yōu)化!平滑渲染
現(xiàn)在我們已經(jīng)大致知道怎樣用合成器渲染一個(gè)頁面:網(wǎng)頁內(nèi)容被分布在各個(gè)Layerzhong ,Layer被光柵化到textures中,textures被上傳給GPU.
合成器告訴GPU將所有的textures合成為最終的屏幕圖像。
接下來理解怎么在一秒鐘內(nèi)做60次這些事情,以確保動(dòng)畫,滾動(dòng),以及其他的頁面交互能平滑進(jìn)行。
為了解釋這些,我們需要介紹與渲染技術(shù)的優(yōu)化相關(guān)的一些概念。
Damage:
目前為止,我們只介紹了如何渲染整個(gè)頁面。這只是Webkit在頁面加載完后第一次渲染頁面時(shí)做的事情。
但是,更典型的情況是,用戶與頁面交互時(shí)只有部分頁面內(nèi)容發(fā)生了變化,這時(shí)就需要進(jìn)入damage rect.
當(dāng)網(wǎng)頁內(nèi)容的可視化部分發(fā)生變化時(shí)(比如 js 改變了css style,css 動(dòng)畫在運(yùn)行,或者用戶滾動(dòng)了viewport),webkit 會(huì)跟蹤網(wǎng)頁中需要更新
的部分。跟蹤的結(jié)果是用一個(gè)damage rectangle 記錄需要重繪的網(wǎng)頁區(qū)域。
當(dāng)繪制時(shí),我們遍歷RenderLayer只繪制與damage rectangle 有交集的Layer,跳過與damage rectangle完全沒有重疊的Layers.
這就避免了網(wǎng)頁中任何一部分變化時(shí)都需要重繪整個(gè)網(wǎng)頁,明顯會(huì)優(yōu)化性能。
總結(jié)
以上是生活随笔為你收集整理的chrome 硬件渲染(GPU Accelerated Compositing in Chrome)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: webkit入门准备
- 下一篇: 在Ubuntu 12.04 64bit上