天天看點

高性能Web動畫和渲染原理系列(4)“Compositor-Pipeline演講PPT”學習摘要

示例代碼托管在:http://www.github.com/dashnowords/blogs

部落格園位址:《大史住在大前端》原創博文目錄

華為雲社群位址:【你要的前端打怪更新指南】

高性能Web動畫和渲染原理系列(4)“Compositor-Pipeline演講PPT”學習摘要

目錄

  • 摘要
    • 1.合成流水線
    • 2. 預定義UI層
    • 3. paint是什麼意思
    • 4. 分層的優勢和劣勢
    • 5. 視圖屬性及其處理方式
    • 6. Quads
    • 7. Compositor Frame
    • 8. 關于光栅化以及渲染方式
    • 9.【重要】軟體渲染和硬體渲染的差別
附件PPT來自chromium官方網站開發文檔。術語裡的

cc

指的是

Chromium Compositor

一直以來都想了解浏覽器合成層的運作機制,但是相關的中文資料大多比較關注架構和開發技術,這方面的資料實在是太少了,後來在

chromium

官方網站的文檔裡找到了項目組成員malaykeshav在 2019年4月的一份關于浏覽器合成流水線的演講PPT,個人感覺裡面講的非常清楚了,由于沒有找到視訊,有些部分隻能自行了解,本文僅對關鍵資訊做一些筆記,對此感興趣的讀者可以在文章開頭的

github

倉庫或附件中拿到這個PPT自行學習。

合成流水線,就是指浏覽器處理合成層的工作流程,其基本步驟如下:

高性能Web動畫和渲染原理系列(4)“Compositor-Pipeline演講PPT”學習摘要

大緻的流程就是說

Paint

環節會生成一個清單,清單裡登記了頁面元素的繪制指令,接着這個清單需要經過

Raster

光栅化處理,并在

合成幀

中處理紋理,最後的

Draw

環節才是将這些紋理圖展示在浏覽器内容區。

chromium中預定義了一些指定類型的UI層,大緻分為:

  • Not Drawn - 為了處理透明度或濾鏡效果、transform變形或者clip剪裁的非繪制層
  • Solid color layer - 固有顔色層
  • Painted texture layer - Texture紋理會在這個層執行

    paint

    渲染和後續的

    rasterized

    光栅化任務
  • Transferable resource layer - 共享資源層,可能是GPU裡面的Texture紋理也可能未來會發給GPU的位圖
  • Surface layer - 臨時占位層,因為自頂向下周遊layer樹時子樹都還沒處理,需要先占位最後再填充
  • Nine patch layer - 用于實作陰影的層

高性能Web動畫和渲染原理系列(4)“Compositor-Pipeline演講PPT”學習摘要

每個層

layer

是由若幹個

views

組成的,所謂

paint

,就是每個

views

将自己對應圖形的繪制指令添加到層的可展示元素清單

Display Item List

裡,這個清單會被添加到一個延遲執行的光栅化任務中,并最終生成目前層的texture紋理(可以了解為目前層的繪制結果),考慮到傳輸性能以及未來增量更新的需求,光栅化的結果會以

tiles

瓦片形式儲存。在chrome中也可以看到頁面瓦片化拆分的結果:

高性能Web動畫和渲染原理系列(4)“Compositor-Pipeline演講PPT”學習摘要

高性能Web動畫和渲染原理系列(4)“Compositor-Pipeline演講PPT”學習摘要

分層的優勢和劣勢也在此進行了說明,和之前我們主動思考的答案基本一緻(暗爽一下)。

views

中支援的屬性包含

Clip

剪裁,

transform

變換,

effect

效果(如半透明或濾鏡等),

mask

遮罩,通常按照後序周遊的方式自底向上進行周遊處理。

clip

剪裁的處理方式是在父節點和子節點之間插入一個剪裁層,用來将其子樹的渲染結果剪裁到限定的範圍内,然後再向上與父級進行合并;

transform

變換直接作用于父節點,處理到這個節點時其子樹都已經處理完畢,直接将整體應用變形即可;

effect

效果一般直接作用于目前處理的節點,有時也會産生交叉依賴的場景;

PPT第40頁中在介紹

effect

效果處理時描述了兩種不同的透明度處理需求,進而引出了一個

Render Surface

的概念,它相當于一個臨時的層,它的子樹需要先繪制在這個層上,然後再向上與父節點進行合并,螢幕就是是根級的

Render Surface

Layer

周遊處理輸出的結果被稱為

Quads

(從意思上了解好像就是指輸出了很多個矩形方塊),每個

quad

都持有它被繪制到目标緩沖區所需要的資源,根據它持有的資源不同可以分為:

  • Solid Color

    -固定顔色型
  • Texture

    - 紋理型
  • Tile

    - 瓦片型
  • Surface

    - 臨時繪圖表面型
  • Video

    - 視訊幀型
  • Render Pass

    -

    Render Surface

    類型的占位區,

    Render Surface

    子樹處理完後填充到關聯的

    Render Pass

合成層真正的工作要開始了,主角概念

Compositor Frame

(合成幀)登場,它負責将

quads

合并繪制在一起,膠片裡59-62頁非常清楚地展示了合成的過程,最終輸出的結果就是根節點的紋理。

高性能Web動畫和渲染原理系列(4)“Compositor-Pipeline演講PPT”學習摘要

chromium

是多程序架構,

Browser Process

浏覽器程序會對菜單欄等等容器部分的畫面生成合成幀來輸出,每個網頁的

Render Process

渲染程序會對頁面内容生成合成幀來輸出,最終的結果都被共享給

GPU Process

GPU程序進行聚合并生成最終完整的合成表面,接着在

Display Compositor

環節将最後的位圖展示在螢幕上。

膠片裡并沒有描述具體的光栅化的處理過程,但是

layer

輸出的

quads

看起來應該是光栅化以後的結果,推測應該是處理

Display Item List

中的繪圖指令時也和WebGL類似,經過

頂點着色器

片元着色器

的周遊式處理機制,并在過程中自動完成像素插值。

聲明:本節内容是個人了解,僅用作技術交流,不保證對!

軟體渲染和硬體渲染的差別對筆者而言一直非常抽象,隻是知道基本概念。後來在【chromium開發者文檔】(國内可能無法通路)中《Compositor Thread Architecture》這篇合成器線程架構的文章中找到了一些相關描述,也解開了筆者心中一直以來的疑惑,相關部分摘抄如下:

Texture Upload

One challenge with all these textures is that we rasterize them on the main thread of the renderer process, but need to actually get them into the GPU memory. This requires handing information about these textures (and their contents) to the impl thread, then to the GPU process, and once there, into the GL/D3D driver. Done naively, this causes us to copy a single texture over and over again, something we definitely don't want to do.

We have two tricks that we use right now to make this a bit faster. To understand them, an aside on “painting” versus “rasterization.”

  • Painting is the word we use for telling webkit to dump a part of its RenderObject tree to a GraphicsContext. We can pass the painting routine a GraphicsContext implementation that executes the commands as it receives them, or we can pass it a recording context that simply writes down the commands as it receives them.
  • Rasterization is the word we use for actually executing graphics context commands. We typically execute the rasterization commands with the CPU (software rendering) but could also execute them directly with the GPU using Ganesh.
  • Upload: this is us actually taking the contents of a rasterized bitmap in main memory and sending it to the GPU as a texture.With these definitions in mind, we deal with texture upload with the following tricks:
  • Per-tile painting: we pass WebKit paint a recording context that simply records the GraphicsContext operations into an SkPicture data structure. We can then rasterize several texture tiles from that one picture.
  • SHM upload: instead of rasterizing into a void* from the renderer heap, we allocate a shared memory buffer and upload into that instead. The GPU process then issues its glTex operations using that shared memory, avoiding one texture copy.The holy grail of texture upload is “zero copy” upload. With such a scheme, we manage to get a raw pointer inside the renderer process’ sandbox to GPU memory, which we software-rasterize directly into. We can’t yet do this anywhere, but it is something we fantasize about.*

大概翻譯一下,友善英語水準一般的小夥伴了解,GPU處理圖檔的方式是按照Texture進行貼圖的,對此不熟悉的小夥伴可以檢視筆者以前發的有關

Three.js

相關的博文。

紋理上傳:

處理紋理的挑戰之一就是它是在渲染程序(可以了解為單個Tab網頁的程序)的主線程裡進行的,但是最終需要将其放入GPU記憶體。這就需要将紋理資料遞交給合成器線程,然後再交給GPU程序(Chromium架構裡有專門的GPU程序用來專門處理和GPU之間的協作任務),最後再傳遞給底層的

Direct3D

OpenGL

(也就是圖形學的底層技術),如果隻是按照正常流程來處理,就會需要一次又一次來複制生成的紋理資料,這顯然不是我們想要的。

我們現在使用了兩個小方法來使這個流程變得快一點。它們分别作用于

painting

(繪制)和

rasterization

(光栅化)兩個階段。
  • 1号知識點!!!

    Painting

    我們用來告訴webkit為

    RenderObject Tree

    的來生成對應的

    GraphicsContext

    。通過給

    painting routine

    (繪制流程)傳遞一個

    GraphicsContext

    的具體實作來執行這些已經編排好的繪制指令,也可以傳遞一個

    record context

    (記錄上下文)隻是簡單地把繪圖指令都記錄下來。
  • 2号知識點!!!

    Rasterization

    (光栅化)是指

    Graphics context

    關聯的繪圖指令實際被執行的過程。通常我們使用CPU(也就是軟體渲染的方式)來執行光栅化任務,也可以直接使用GPU來渲染(也就是硬體渲染的方式)。
  • 上傳:指在主線程存儲區擷取到光栅化以後的位圖内容然後将它作為紋理上傳給GPU的過程,考慮到上述已經提及的定義,上傳過程是如下來處理的:
    • 瓦片繪制:我們在webkit中使用

      recording context

      來簡單地記錄

      Graphics Context

      的操作指令,将它存儲為SkPicture類型(直接使用軟體光栅化時生成的是SkBitmap類型),随後可以從一張picture裡面光栅化處理得到多個

      紋理瓦片

    • 共享記憶體:在軟體渲染的方式中,光栅化的結果會被存儲在

      renderer

      程序的堆記憶體裡,現在不這樣搞了,我們重新配置設定了一塊共享緩沖區,然後通過它來傳遞相關對象,GPU程序随後在擷取紋理時直接從共享記憶體中擷取就行了,這樣就避免了資料的拷貝。

      總的來說,紋理上傳的過程幾乎是零拷貝的。利用這樣的結構,我們在

      renderer

      程序(也就是網頁的渲染程序)的沙箱環境内也可以擷取到指向GPU 記憶體的指針,而在軟體光栅化的過程中,是直接将位圖結果放在這裡的。
  • Painting: this is the process of asking Layers for their content. This is where we ask webkit to tell us what is on a layer. We might then rasterize that content into a bitmap using software, or we might do something fancier. Painting is a main thread operation.
  • Drawing: this is the process of taking the layer tree and smashing it together with OpenGL onto the screen. Drawing is an impl-thread operation.
  • painting:表示的過程是向Layers對象查詢層内容,也就是讓webkit告訴我們每一層上面到底有什麼。接下來我們就可以使用軟體光栅化的方式将這些内容處理為位圖,也可以做一些更牛的事情,painting是一個主線程行為。
  • drawing:是指将Layer中的内容用OpenGL繪制在螢幕上的過程,它是另一個線程中的操作。

概念比較多沒有基礎的讀者可能了解起來有難度,我嘗試用自己的話複述一下:

【軟體渲染】的模式下,在

paint

時會直接利用

Graphics Context

繪圖上下文将結果繪制出來,在一個

SkBitmap

執行個體中儲存為位圖資訊;【硬體渲染】的模式下,在

paint

時傳入一個

SkPicture

執行個體,将需要執行的繪圖指令儲存在裡面先不執行,然後通過共享記憶體将它傳給GPU程序,借助GPU來最終去執行繪圖指令,生成多個瓦片化的位圖紋理結果(

OpenGL

中頂點着色器向片元着色器傳遞資料時可以自動進行資料插值,完成光栅化的任務)。 純軟體渲染裡嚴格說是沒有合成層概念的,因為最終輸出的隻有一張位圖,按照順序從下往上畫,和畫到一個新層上再把新層貼到已有結果上其實是一樣的。

不管使用哪種途徑,

paint

動作都是得到位圖資料,而最終的

draw

這個動作是借助OpenGL和位圖資料最終把圖形顯示在顯示器上。

是以【硬體渲染】就是渲染程序把要做的事情和需要的資料都寫好,然後打包遞給GPU讓它去幹活。