Chrome渲染優(yōu)化:層模型
引言
對(duì)于大多數(shù)Web開(kāi)發(fā)者而言,Web頁(yè)面的基本模型就是DOM模型。頁(yè)面的渲染過(guò)程常常并不為人所知,大家只是知道它是一個(gè)將頁(yè)面的DOM表示方式轉(zhuǎn)化為在屏幕上顯示的一個(gè)圖片的過(guò)程。近幾年,現(xiàn)代瀏覽器利用圖形卡對(duì)頁(yè)面渲染方式進(jìn)行了改進(jìn):這種改進(jìn)一般都籠統(tǒng)地稱為“硬件加速”。當(dāng)談及普通Web頁(yè)面(也即:非Canvas2D或者WebGL頁(yè)面)時(shí),硬件加速這個(gè)詞到底意味著什么?本文將對(duì)作為Chrome中硬件加速下Web頁(yè)面渲染基礎(chǔ)的基本模型進(jìn)行說(shuō)明。
冗長(zhǎng)的注意事項(xiàng)
我們?cè)谶@里討論的是WebKit,更具體地講,我們討論的是Chromium下的WebKit。 本文所涉及的是Chrome的實(shí)現(xiàn)細(xì)節(jié),而不是Web平臺(tái)的特性。Web平臺(tái)及其標(biāo)準(zhǔn)并沒(méi)有對(duì)這個(gè)層次的實(shí)現(xiàn)細(xì)節(jié)做出詳細(xì)的規(guī)定,所以我們并不能保證本文中的內(nèi)容可以適用于其它的瀏覽器,但是對(duì)瀏覽器內(nèi)部工作機(jī)理的了解定會(huì)有益于對(duì)應(yīng)用進(jìn)行高水平的錯(cuò)誤調(diào)試和性能調(diào)優(yōu)。
另外還要主意,整個(gè)這篇文章討論的都是Chrome渲染架構(gòu)中的一個(gè)核心部分,但這個(gè)結(jié)構(gòu)仍在迅速變化之中。本文試圖僅僅涉及那些不太可能發(fā)生變化的部分,但要是在6個(gè)月后再看這篇文章,那可就不一定是什么情況了。
很重要的是要明白Chrome當(dāng)下有段時(shí)間還會(huì)有兩個(gè)不同的渲染途徑:硬件加速式的渲染和早期軟件式的渲染。直到寫(xiě)這篇文章之時(shí)為止,在Windows、 ChromeOS和Android下的Chrome中,所有的頁(yè)面都走的是硬件加速途徑。在Mac和Linux下, 只有那些部分內(nèi)容需要組合的頁(yè)面才會(huì)走硬件加速途徑(更多關(guān)于什么才需要組合的說(shuō)明請(qǐng)參見(jiàn)下文),但用不了多久,所有頁(yè)面將都會(huì)走硬件加速途徑。
最后要說(shuō)的是,我們?cè)谶@里的內(nèi)容只是對(duì)Chrome的渲染引擎的管窺之見(jiàn),所看到的只是其對(duì)性能影響比較大的一些特性。當(dāng)你想要提高你的網(wǎng)站的性能時(shí),對(duì)層模型有所了解會(huì)非常有幫助作用,但這也容易造成搬起石頭砸自己腳的情況:層這種結(jié)構(gòu)非常有用,但是創(chuàng)建過(guò)多的層會(huì)造成整個(gè)圖形棧開(kāi)銷的增加。到時(shí)候可別說(shuō)我沒(méi)有提前警告過(guò)你哦!
從DOM到屏幕
層的引入
頁(yè)面一旦在裝入并解析完成后,就會(huì)表示為許多Web開(kāi)發(fā)者所熟悉的結(jié)構(gòu):DOM。然而,在頁(yè)面的渲染過(guò)程中,瀏覽器還具有一系列并不直接暴露給開(kāi)發(fā)者的頁(yè)面中間表示方式。這些表示方式中最重要的結(jié)構(gòu)就是層。
在Chrome中實(shí)際上有幾種不同類型的層:掌管DOM子樹(shù)的渲染層(RenderLayer)以及掌管渲染層子樹(shù)的圖形層(GraphicsLayer)。 我們這里最關(guān)心的是后者,因?yàn)樽鳛榧y理上傳到GPU之中的就是圖形層。本文自此就只用“層”來(lái)指代圖形層了。
稍稍離題說(shuō)一下同GPU有關(guān)的幾個(gè)術(shù)語(yǔ):什么是紋理?紋理可以看作是位圖圖像,從主存儲(chǔ)器(也就是RAM)傳遞到視頻存儲(chǔ)器(也就是GPU之上的 VRAM)之中的就是這個(gè)圖像。一旦傳遞到GPU之中后,你就能夠?qū)⒓y理映射到一個(gè)網(wǎng)格幾何結(jié)構(gòu)之上 —— 在視頻游戲或者CAD程序中,這種技術(shù)用來(lái)給框架式的3D模型添加“皮膚”。Chrome采用紋理把頁(yè)面中的內(nèi)容分塊發(fā)送給GPU。紋理能夠以很低的代價(jià)映射到不同的位置,而且還能夠以很低的代價(jià)通過(guò)把它們應(yīng)用到一個(gè)非常簡(jiǎn)單的矩形網(wǎng)格中進(jìn)行變形。這就是3D CSS的實(shí)現(xiàn)原理,而且這么做對(duì)頁(yè)面在屏幕的快速滾動(dòng)也非常好 —— 現(xiàn)在先說(shuō)這些,這兩方面更詳細(xì)的探討情況下文。
下面讓我們用幾個(gè)例子來(lái)說(shuō)明層的概念。
在Chome中研究層時(shí)有一個(gè)非常有用的工具就是Chrome的開(kāi)發(fā)者工具里設(shè)置(也就是那個(gè)小齒輪圖標(biāo))中“渲染(rendering)”小標(biāo)題下的 “顯示層的組合邊界(show composited layer borders)”開(kāi)關(guān)。讓我們把這個(gè)開(kāi)關(guān)打開(kāi)。本文中所有的截屏和例子都來(lái)自最新版的Chrome Canary,在寫(xiě)這篇文章的時(shí)候是Chrome 27。
圖1: 只有一層的頁(yè)面 (將在新窗口中打開(kāi))
- <html>
- <body>
- <div>I am a strange root.</div>
- </body>
- </html>
(譯者注:這里缺了一個(gè)圖,原文中的圖就看不到,可能是需要翻墻?)
在頁(yè)面的基本層中組合層的渲染邊界屏幕截圖
這個(gè)頁(yè)面只有一層。藍(lán)色的柵格表示的是分塊,這些分塊可以看作是比層更低一級(jí)的單位,Chrome以這些分塊為單位,一次向GPU上傳一個(gè)分塊的內(nèi)容。這里它們并不怎么重要。
圖2:有自己的層的元素 (open stand-alone)
- <html>
- <body>
- <div style="-webkit-transform: rotateY(30deg) rotateX(-30deg); width: 200px;">
- I am a strange root.
- </div>
- </body>
- </html>
(譯者注:這里缺了一個(gè)圖,原文中的圖就看不到,可能是需要翻墻?)
旋轉(zhuǎn)后層的渲染邊界的截屏
在<div>上加上讓它旋轉(zhuǎn)一個(gè)角度的3D CSS屬性后,我們就能夠看到一個(gè)元素有自己的層是個(gè)什么樣子:請(qǐng)注意其中的橘色邊界,這個(gè)邊界給出了這個(gè)視圖中層的輪廓。
層的創(chuàng)建準(zhǔn)則
還要其它什么元素會(huì)得到自己的層?Chrome在這方面采用的規(guī)則仍在隨著時(shí)間推移逐漸發(fā)展變化,但在目前下面這些因素都會(huì)引起Chrome創(chuàng)建層:
- 進(jìn)行3D或者透視變換的CSS屬性
- 使用硬件加速視頻解碼的<video>元素
- 具有3D(WebGL)上下文或者硬件加速的2D上下文的<canvas>元素
- 組合型插件(即Flash)
- 具有有CSS透明度動(dòng)畫(huà)或者使用動(dòng)畫(huà)式Webkit變換的元素
- 具有硬件加速的CSS濾鏡的元素
- 子元素中存在具有組合層的元素的元素(換句話說(shuō),就是存在具有自己的層的子元素的元素)
- 同級(jí)元素中有Z索引比其小的元素,而且該Z索引比較小的元素具有組合層(換句話說(shuō)就是在組合層之上進(jìn)行渲染的元素)
實(shí)際意義:動(dòng)畫(huà)
我們還可以將層在頁(yè)面中到處移動(dòng),正好可用于動(dòng)畫(huà)。
圖3: 動(dòng)畫(huà)層(將在新窗口中打開(kāi))
- <html>
- <head>
- <style type="text/css">
- div {
- -webkit-animation-duration: 5s;
- -webkit-animation-name: slide;
- -webkit-animation-iteration-count: infinite;
- -webkit-animation-direction: alternate;
- width: 200px;
- height: 200px;
- margin: 100px;
- background-color: gray;
- }
- @-webkit-keyframes slide {
- from {
- -webkit-transform: rotate(0deg);
- }
- to {
- -webkit-transform: rotate(120deg);
- }
- }
- </style>
- </head>
- <body>
- <div>I am a strange root.</div>
- </body>
- </html>
正如前文所述,層在將Web頁(yè)面中的靜態(tài)內(nèi)容隨處移動(dòng)方面真的很有用。在最基本的情況下,Chrome將層中的內(nèi)容繪制到軟件位圖中,然后再將該位圖作為紋理上載到GPU之中。如果層中的內(nèi)容將來(lái)不會(huì)發(fā)生變化,那就不需要對(duì)其進(jìn)行重繪了。這是個(gè)好事:重繪將會(huì)占用本可以用于干其它事情,比如運(yùn)行 JavaScript的時(shí)間,而且如果繪制過(guò)程太長(zhǎng),動(dòng)畫(huà)還會(huì)出現(xiàn)卡頓現(xiàn)象。
例如,可以在Chrome的開(kāi)發(fā)工具中看一下這個(gè)頁(yè)面的時(shí)間線:但該層在來(lái)回旋轉(zhuǎn)的時(shí)候,并沒(méi)有出現(xiàn)繪制操作。 動(dòng)畫(huà)過(guò)程中的開(kāi)發(fā)者工具時(shí)間線屏幕截圖無(wú)效!重繪
但是如果層中的內(nèi)容發(fā)生了改變,它就需要重繪了。
圖4:層的重繪 (將在新窗口打開(kāi))
- <html>
- <head>
- <style type="text/css">
- div {
- -webkit-animation-duration: 5s;
- -webkit-animation-name: slide;
- -webkit-animation-iteration-count: infinite;
- -webkit-animation-direction: alternate;
- width: 200px;
- height: 200px;
- margin: 100px;
- background-color: gray;
- }
- @-webkit-keyframes slide {
- from {
- -webkit-transform: rotate(0deg);
- }
- to {
- -webkit-transform: rotate(120deg);
- }
- }
- </style>
- </head>
- <body>
- <div id="foo">I am a strange root.</div>
- <input id="paint" type="button" onclick="" value=”repaint”>
- <script>
- var w = 200;
- document.getElementById('paint').onclick = function() {
- document.getElementById('foo').style.width = (w++) + 'px';
- }
- </script>
- </body>
- </html>
每次點(diǎn)擊按鈕后,旋轉(zhuǎn)中的元素的寬度就會(huì)增加1px。這將導(dǎo)致頁(yè)面重新布局,整個(gè)元素都需要重繪,在這個(gè)例子中需要重繪的是整個(gè)層。
#p#
查看Chrome重繪了哪些元素的一個(gè)很好的辦法是使用開(kāi)發(fā)者工具中的“顯示繪制矩形”開(kāi)關(guān),這個(gè)開(kāi)關(guān)同樣也在開(kāi)發(fā)者工具設(shè)置中的“渲染”標(biāo)題下。打開(kāi)該開(kāi)關(guān)后,在點(diǎn)擊按鈕的時(shí)候請(qǐng)注意動(dòng)畫(huà)中的元素和按鈕周圍都會(huì)有一個(gè)紅色的矩形閃現(xiàn)。
顯示繪制矩形檢查框的屏幕截圖
同時(shí)繪制時(shí)間也出現(xiàn)在開(kāi)發(fā)者工具里的時(shí)間線中了。明眼的讀者可能還注意到這里有兩個(gè)繪制事件:一個(gè)是層的,另外一個(gè)是按鈕的。按鈕會(huì)在它的狀態(tài)變成按下?tīng)顟B(tài)和從按下?tīng)顟B(tài)變?yōu)榉前聪聽(tīng)顟B(tài)時(shí)需要進(jìn)行重繪。
開(kāi)發(fā)者工具時(shí)間線中層的重繪的屏幕截圖
請(qǐng)注意Chrome并不總是需要重繪整個(gè)層,它會(huì)盡量地以聰明的方式只繪制DOM中發(fā)生變化的那部分內(nèi)容。在我們的這個(gè)例子中,我們所改變的DOM元素是整個(gè)層的尺寸。但是在很多情況下,在一層中會(huì)有大量的DOM元素。
很顯然接下來(lái)的問(wèn)題是頁(yè)面內(nèi)容失效和強(qiáng)制進(jìn)行重繪是由什么引起的。要全面回答這個(gè)問(wèn)題并不容易,因?yàn)橐饛?qiáng)制進(jìn)行重繪的有大量不太容易區(qū)分的情況。其中最常見(jiàn)的原因是通過(guò)操縱CSS樣式或者引起重新進(jìn)行頁(yè)面布局來(lái)改變DOM的特性。Tony Gentilcore寫(xiě)了一篇很不錯(cuò)的討論引起頁(yè)面重新布局的原因的博文,Stoyan Stefanov有一篇更近詳盡地討論瀏覽器繪制過(guò)程的文章(但這篇文章僅僅止于繪制過(guò)程,并沒(méi)有涉及奇特的組合部分的內(nèi)容)。
找出重繪是否影響到了某些你正在關(guān)注中的元素的最好方式是使用開(kāi)發(fā)者工具中的時(shí)間線以及“顯示繪制矩形”工具,用這兩個(gè)工具能夠看出瀏覽器是否在重繪一些你并不認(rèn)為需要重繪的內(nèi)容,然后找出就在重新布局/重繪前的那個(gè)時(shí)刻之前到底是在哪里改變了DOM結(jié)構(gòu)。如果繪制過(guò)程無(wú)法避免但繪制過(guò)程卻長(zhǎng)的不太合理,請(qǐng)參考一下Eberhard Gräther的文章,這是一篇關(guān)于在開(kāi)發(fā)者工具中如果對(duì)持續(xù)性繪制模式進(jìn)行性能優(yōu)化的文章。
總結(jié):從DOM到屏幕
Chrome是如何將DOM轉(zhuǎn)換為屏幕上的圖像的呢?從概念上講,它:
- 取得DOM并將其分成若干個(gè)層
- 分布將這些層繪制到各自的軟件位圖中
- 將繪制好的位圖作為紋理上載至GPU之中
- 將這些不同的層組合起來(lái)形成屏幕上最后顯示出的圖像
這些步驟在Chrome第一次產(chǎn)生Web頁(yè)面的幀時(shí)都需要執(zhí)行。但是在產(chǎn)生隨后的幀時(shí),就可能會(huì)走一些捷徑:
- 如果有某些CSS屬性發(fā)生了改變,就并不一定要重繪所有的內(nèi)容了。Chrome能夠?qū)⒁呀?jīng)作為紋理保存在GPU之中的層重新組合起來(lái),但只是在重新組合時(shí),使用不同的組合屬性(比如,在不同的位置、以不同的透明度來(lái)組合等等)。
- 如果某一層中的某個(gè)部分的內(nèi)容變成無(wú)效的了,那么該層就需要重繪并在重繪完成后重新上載至GPU中。如果其內(nèi)容仍然不變,只是其組合屬性發(fā)生了變化(比如它的位置或者透明度改變了),Chrome就不會(huì)對(duì)GPU中該層的位圖做任何處理,只是通過(guò)重新進(jìn)行組合來(lái)生成新的幀。
現(xiàn)在大家應(yīng)該弄清楚了,基于層的組合模型對(duì)渲染性能有著非常大的意義。在沒(méi)有需要重新繪制的內(nèi)容時(shí),組合的代價(jià)相對(duì)來(lái)說(shuō)代價(jià)更低一些,所以在調(diào)試渲染性能時(shí),避免層的重繪是一個(gè)非常好的總體目標(biāo)。經(jīng)驗(yàn)老道的開(kāi)發(fā)者會(huì)去看上文提到的組合觸發(fā)的列表,并意識(shí)到很可能會(huì)非常容易的導(dǎo)致瀏覽器創(chuàng)建很多層。但是,一定要小心無(wú)意識(shí)地去創(chuàng)建層,因?yàn)槭褂脤邮怯写鷥r(jià)的:它們會(huì)占用系統(tǒng)RAM以及GPU中的存儲(chǔ)空間(移動(dòng)設(shè)備上的存儲(chǔ)空間特別有限),層太多的話還會(huì)在跟蹤哪些從可見(jiàn)哪些層不可見(jiàn)的算法中引入額外的開(kāi)銷。如果層都很大而且原先不重疊的層突然重疊了起來(lái),那么太多的層就會(huì)增加瀏覽器花在柵格化方面的時(shí)間,這會(huì)導(dǎo)致有時(shí)稱之為“過(guò)度繪制”的情況。所以,一定要明智地運(yùn)用你所學(xué)到的知識(shí)!
暫時(shí)先寫(xiě)到這里了。請(qǐng)你以后再到這里來(lái)查看另外幾篇關(guān)于層模型實(shí)際意義的文章。
其它參考資料
英文原文:Accelerated Rendering in Chrome - The Layer Model
譯文鏈接:http://www.oschina.net/translate/chrome-accelerated-rendering