整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          如何用Vue3和p5.js繪制一個交互式波浪圖

          如何用Vue3和p5.js繪制一個交互式波浪圖

          文由ScriptEcho平臺提供技術支持

          項目地址:傳送門

          基于 p5.js 的動態網格線繪制

          應用場景

          本代碼旨在利用 p5.js JavaScript 庫在 Web 應用程序中創建動態網格線。此功能可用于各種場景,例如:

          • 創建交互式藝術品和可視化
          • 設計交互式數據儀表板
          • 構建網格狀游戲環境

          基本功能

          此代碼使用 p5.js 庫創建了一個動態網格線畫布,其中包含以下功能:

          • 在畫布上繪制水平和垂直網格線
          • 將網格線與畫布中心連接
          • 在網格線交點處放置小方塊

          功能實現步驟及關鍵代碼分析

          1. 加載 p5.js 庫

          let jsUrls=['https://registry.npmmirror.com/p5/1.9.3/files/lib/p5.min.js']
          await Promise.all(jsUrls.map((jsUrl)=> loadJavascript(jsUrl)))
          

          此代碼使用 loadJavascript 函數異步加載 p5.js 庫。jsUrls 數組指定了 p5.js 庫的 URL。

          2. 創建 p5.js 草圖

          const sketch=(s)=> {
            s.setup=()=> {
              s.createCanvas(720, 360)
              s.background(0)
              s.noStroke()
              ...
            }
          }
          

          此代碼創建了一個 p5.js 草圖,它定義了畫布設置和繪制邏輯。s.setup 函數在畫布創建時執行,并負責設置畫布大小、背景顏色和禁用描邊。

          3. 繪制網格線

          let gridSize=35
          
          

          for (let x=gridSize; x <=s.width - gridSize; x +=gridSize) {
          for (let y=gridSize; y <=s.height - gridSize; y +=gridSize) {
          s.noStroke()
          s.fill(255)
          s.rect(x - 1, y - 1, 3, 3)
          s.stroke(255, 50)
          s.line(x, y, s.width / 2, s.height / 2)
          }
          }

          這些嵌套循環負責繪制網格線。內層循環繪制水平網格線,而外層循環繪制垂直網格線。每個網格線交點都放置一個 3x3 像素的白色方塊,并用一條從交點到畫布中心的 50% 不透明度的白色線連接。

          4. 實例化 p5.js 草圖

          new p5(sketch, 'container')
          

          此代碼使用 new p5 函數實例化 p5.js 草圖,并將其附加到具有 ID 為 "container" 的 DOM 元素。

          總結與展望

          開發這段代碼的過程是一個有益的學習經歷。它展示了如何使用 p5.js 庫創建交互式圖形。

          經驗與收獲:

          • 對 p5.js 庫及其繪圖 API 的深入理解
          • 掌握異步腳本加載技術
          • 提高了使用 JavaScript 創建交互式 Web 應用程序的能力

          未來拓展與優化:

          • 添加用戶交互功能,允許用戶更改網格大小或顏色
          • 探索使用 WebSockets 或其他實時通信技術創建多人協作網格繪制應用程序
          • 將此代碼集成到更廣泛的 Web 應用程序中,例如數據可視化工具或游戲環境
          • 更多組件:

          獲取更多Echos

          本文由ScriptEcho平臺提供技術支持

          項目地址:傳送門

          微信搜索ScriptEcho了解更多

          章來自于https://mp.weixin.qq.com/s/9lX8VgMmtXRjFdf7zkOFMQ,@Big前端

          提到瀏覽器不得不說Chrome,Chrome是Google發行的商業產品,而Chromium是一個開源版本的Chrome,兩者很像但是不完全一樣。

          這里嘗試將自己的理解結合下方PPT用最直白的語言記錄最近了解到的瀏覽器的渲染原理知識,方便后續查閱。因為涉及到的知識點非常多且繁雜,如果有表述不到位的地方敬請諒解,錯別字/錯誤理解之類的歡迎聯系我修改。

          為什么要做這件事?近幾年瀏覽器更新挺大的,Chrome/Chromium整體還在不斷演進,越來越頻繁。本文有一些自己的理解如果有誤歡迎聯系

          PPT內容整體來自Chromium開發者,Steve Kobes的演講,PPT地址

          https://docs.google.com/presentation/d/1boPxbgNrTU0ddsc144rcXayGA_WF53k96imRH8Mp34Y/edit#slide=id.ga884fe665f_64_1691


          瀏覽器分層架構

          簡單的說瀏覽器作為應用,底層分別有content,Blink,V8,Skia等等,一層一層像套娃一樣一層引用一層。對比普通應用的項目來說就是不斷用第三方庫和組件來拼湊應用,Chrome也不例外

          • content可以理解為就是除了瀏覽器主進程下的書簽導航之外,網頁內容這一部分,會隨著網頁不同而變化的部分
          • Blink渲染引擎,應該都聽過就是網頁的排版引擎,現存的Chrome/Edge都在用,作為開源項目維護,是在渲染進程里
          • Blink又嵌套了V8 JavaScript engine來執行JS代碼

          渲染

          網頁的渲染可以表示為Content經過rendering最后呈現的過程,即Code -> 可交互的頁面

          content是什么?

          可以看到content就是WebContents對象,C++代碼的一個類。其代表的區域其實是標簽頁頁打開的部分(上圖紅色部分)。而瀏覽器主進程還包含有地址欄、導航按鈕、菜單、擴展,安全提示的小彈窗等等。

          渲染進程render process是一個沙盒,基于安全考慮單獨渲染進程render process掛了不會引起整個瀏覽器掛

          渲染進程render process包含Blink渲染排版引擎和Chromium compositor(上圖中綠色的CC簡寫)

          content還表示網頁內容代碼,有HTML,CSS,JS,圖片等,還有video,canvas,WebAssembly,WebGL等都可能在content區域顯示或者運行

          綜上,content就是網頁代碼最后運行的結果,瀏覽器開發者工具可以看到最后是一個經過處理后的HTML的結構。「而這個HTML在渲染流水線里是一個輸入」

          像素是如何呈現的?

          寫過C/C++代碼的同學知道,我們必須使用操作系統提供的底層API去畫圖,操作系統底層又去調用驅動程序,驅動程序驅動硬件。

          今天大多數平臺上都提供了“OpenGL”的標準化API。在Windows上有一個額外的DirectX轉換。這些庫提供諸如“紋理”和“著色器”之類的低級圖形基元,并允許執行類似“在這些坐標處繪制一個三角形到虛擬像素緩沖區”之類的底層操作。未來計劃用Vulkan替代Skia來做底層圖形化調用。

          所以渲染流水線的整個過程就是將輸入的HTML、CSS、JS轉化為OpenGL調用,最后在屏幕上呈現像素

          瀏覽器渲染的目標

          1. 初次渲染,將網頁內容轉化為底層OpenGL調用去顯示頁面
          2. 更新渲染,在JS運行,用戶輸入,異步請求或者滑動等交互介入后,再次渲染頁面起到交互的目的,而且這里再次渲染需要高效執行,你會想到緩存對嗎?是的每個階段的結果為了提高渲染效率而被緩存下來。還有JS API會查詢一些渲染數據如某個DOM節點的信息

          拆分渲染階段

          把渲染管道分成多個階段的話,可以看出來原來的content內容會被各個階段stage處理為中間數據,最后才呈現為畫面呈現出來。

          DOM

          解析為DOM

          HTML嵌套解析,解析時候解析為數據對象映射反應這里的嵌套模型

          DOM(Document Object Model)是一棵樹,樹有父子,鄰居的關系,而且這棵樹是暴露API給JS調用,JS可以查詢和修改這棵樹。JS引擎V8通過bindings的系統將DOM包裝為DOM API供給Web開發者調用

          文檔可能包含多個DOM樹

          如上圖的示例,自定義元素custom element有shadow tree。ShadowRoot的子元素其實被嵌入到slot元素里了,這跟各大前端框架的slot其實很像。

          其實最后是在遍歷樹后合成視圖,也就是兩棵樹合并為一棵樹

          Style

          style步驟依賴前置的DOM樹解析結果,選擇器是選擇DOM節點集合決定最后應用范圍,最后樣式生效是多個選擇器共同作用的結果,而且樣式間可能互相沖突導致沒有按照預期運行,關于選擇器的優先級感興趣的同學自行查閱

          CSS解析器樣式表StyleSheet構建樣式規則。樣式表可能位于<style>元素、單獨加載的資源的css文件中,也可能由瀏覽器默認提供。樣式規則以各種方式編制索引以實現高效查找。
          屬性類在構建時由Python腳本自動生成,以聲明方式定義了所有樣式屬性,如上圖右上側css_properties.json經過py腳本轉化為.cc文件。

          ComputedStyle

          樣式的重新計算(recalc)從活動樣式表中獲取所有解析的樣式規則,并計算每個DOM元素的每個樣式屬性的最終值。這些內容存儲在一個名為ComputedStyle的對象中,該對象只是樣式屬性到值的映射。可以看到其實每一個DOM節點都對應有一個ComputedStyle對象

          在Chrome瀏覽器里的話,就是對應開發者工具的Computed樣式屬性這一欄。或者是通過getComputedStyle的JSAPI去獲取。

          Layout

          在DOM和Style計算好后開始進入布局Layout階段,比如將DIV解析為一個塊級的LayoutReat區域,用x+y+width+height來表示,布局就是為了計算x,y,width,height這些數據

          默認情況下文檔按照順序排列下去形成了文檔流

          文字和內聯元素則是左右浮動的,而且內聯元素會被行尾打斷(自動換行)。當然也有從右到左的語言,比如阿拉伯語和希伯來語

          布局也包括字體的排列,因為布局需要考慮文本在那里進行換行,Layout使用名為HarfBuzz的開源文本庫來計算每個字形的大小和位置,這決定了文本的總寬度。字體成型必須考慮到排版特征,如字距調整letter-spacing和連字。

          布局可以計算單個元素的多種邊界矩形。例如,當存在溢出時,Layout將同時計算邊界框和布局溢出。如果節點的溢出是可滾動的,Layout還會計算滾動邊界并為滾動條預留空間。最常見的可滾動DOM節點是文檔本身

          表格元素或display:table的樣式需要更復雜的布局,這些元素或樣式指定將內容分成多列,或浮動對象漂浮在一邊,內容在其周圍流動,或者東亞語言的文本垂直排列,而不是水平排列。請注意DOM結構和ComputedStyle值(如“Float:Left”)是布局算法的輸入。「渲染流水線的每個階段都會使用到前面階段的結果」

          通過遍歷DOM樹創建渲染樹LayoutTree,節點一一對應。布局樹中的節點實現布局算法。根據所需的布局行為,LayoutObject有不同的子類。比如LayoutBlockFlow就是塊級Flow的文檔節點。樣式更新階段也構建布局樹。

          在樣式解析最后結束時需要構建布局樹LayoutTree,布局階段遍歷布局樹,對布局樹每個節點LayoutObject執行布局,計算幾何數據、換行符,滾動條等。

          DOM節點跟Layout節點不一定是一一對應

          一般情況下一個DOM節點會有一個LayoutObject,但是有時候LayoutObject是沒有DOM節點與之對應的。
          比如上圖,
          span標簽外部沒有section標簽嵌套,但是LayoutTree會自動創建LayoutBlock的匿名節點與之對應,再比如樣式有display:none的樣式,那么也不會創建對應的LayoutTree

          最后,如果是shadowTree的話,其LayoutObject節點可能會在不同的TreeScope

          布局引擎正在重寫

          如上圖所示,「LayoutNG」代表下一代的布局引擎,2020年布局引擎還在過渡階段,所以有中間形態,如上圖包含了LayoutObjectLayoutNGMixin混合節點。未來所有節點都會變成LayoutNGlayout object

          NG節點的更新主要是因為之前的節點包含了輸入、輸出還有布局算法的信息,也就是說單個節點可以看到整棵樹的狀態(節點有可能需要獲取父節點的寬高數據,但是父節點正在遞歸子節點布局中,實際上還沒確定最后的布局)。

          而新的NG節點對輸入和輸出做了明顯的區分,而且輸出是immutable的,可以緩存結果

          布局結果指向描述物理幾何的片段樹,如圖一個NGLayoutResult對應幾個NGPhysicalFragment,對應右上角的幾個矩形圖形,如果NGLayoutResult沒變化則對應整塊都不會變化。

          舉一個布局例子

          上圖的HTML代碼,會渲染如右下角的例子,對應的DOM樹如左側所示

          DOM樹跟Layout樹很像,節點幾乎是一一對應的,但是注意這里anonymous匿名節點被創建出來,它只有一個塊級子元素。一個布局節點只能擁有塊級元素或者內聯元素其中之一

          上圖的子元素前面兩個其實共享了匿名LayoutNGBlockFlow,也就是說有共同的父節點

          「fragment tree」里我們可以更好的看到文本換行后的繪制結果,以及每個fragment的位置和大小

          Paint

          paint階段只是創建繪制指令paint op,頁面還沒有東西,甚至直到GL調用之前頁面都是沒有呈現任何東西的狀態

          創建繪制指令列表

          繪制paint階段創建繪制指令列表paint ops list

          繪制指令paint op可以理解為在某些坐標用什么顏色畫一個矩形類似的意思,

          每個布局對象LayoutObejct可以有多個顯示項目,對應于其視覺外觀的不同部分,如背景、前景、輪廓等

          樣式可以控制繪制順序

          正確的繪制順序非常重要,這樣當元素重疊時,它們才能正確堆疊。順序可以由樣式控制,而不是完全依靠DOM的先后順序

          繪制分不同階段進行

          每個繪制階段「paint phase」都需單獨遍歷堆疊上下文staking context

          一個元素甚至可能部分位于另一個元素的前面,部分位于另一個元素的后面。這是因為繪制在多個階段中運行,每個繪制階段都對自己的子樹進行遍歷。

          舉一個繪制例子

          如上,一個樣式和DOM節點渲染出來的結果,包含了四個繪制指令paint ops。

          1. document背景色繪制
          2. 塊級元素的背景色繪制
          3. 塊級元素的前景色繪制(包含文本的繪制)

          文本塊的繪制

          文本繪制操作包含文本塊的繪制,其中包含每個字的字符和偏移量以及字體。如圖這些數據都是HarfBuzz計算后得到的

          raster

          中文說的柵格化或者光柵化,本文取PS圖層右鍵的柵格化為譯文。熟悉PS的會知道矢量圖形柵格化后放大圖形會“糊”,但是不做柵格化處理直接放大矢量圖形則不會。原因就是柵格化后只記錄了單像素點的rgba值,放大后本來一個點數據要填滿N個點,圖像就”糊了“

          raster將繪制指令轉化為位圖

          把顯示列表里的繪制操作執行的過程,成為任務,也稱柵格化。比如PS里的合并圖層任務,主要區別就是本來矢量的圖任務后會變成位圖bitmap,后面再縮放就會模糊。

          生成的位圖bitmap中的每個單元格都包含對單個像素的顏色和透明度進行編碼的位。這里用十六進制FFFFFFFF表示一個點的rgba值

          raster還可以對頁面圖片進行解碼

          任務還可以對頁面中嵌入的圖像資源進行解碼。繪制指令引用壓縮數據(JPEG、PNG等),任務調用適當的解碼器將其解壓縮。

          GPU加速柵格化

          GPU還可以運行生成位圖的命令(“加速柵格化”)。請注意,這些像素還沒有出現在屏幕上!
          raster產生的位圖數據存儲在GPU內存中,通常是OpenGL紋理對象引用的GPU內存。

          過去通常是存在內存里再傳給GPU,但是現代GPU可以直接運行著色器shader并在GPU上生成像素,這種情況稱為“加速柵格化”。但是兩個結果都是一致的,最終內存(主存或者GPU內存)里擁有位圖bitmap

          raster通過Skia發出GL調用

          GL調用即OpenGL調用,OpenGL意為"開放圖形庫",可以在不同操作系統、不同編程語言間適配2D,3D矢量圖的渲染。

          raster通過名為Skia的庫發出GL調用。Skia提供了圍繞硬件的抽象層,如路徑和貝塞爾曲線,子像素抗鋸齒以及各種混合疊加模式。

          Skia是開源的,由谷歌維護。跟隨Chrome一起發布,但位于單獨的代碼庫中。它也被其他產品使用,比如Android。Skia的GPU加速代碼路徑構建自己的繪制操作緩沖區,在柵格化結束時刷新。實際上發起GL調用的是Skia的后端,后面會說到

          raster運行在GPU進程中

          回想一下,渲染器進程是一個沙箱環境,因此它不能直接進行系統調用。繪制操作被運送到GPU進程進行任務處理。GPU進程可以發出實際的GL調用。除了獨立于渲染器沙箱之外,在GPU進程中隔離圖形化操作還可以保護我們免受不穩定或不安全的圖形驅動程序的影響。比如GPU進程崩了,瀏覽器可以重啟GPU進程

          繪制請求通過命令緩沖區傳遞給GPU進程

          柵格化的繪制操作通過GPU命令緩沖區command buffer傳輸,渲染進程和GPU進程通過IPC通道發送。命令緩沖區command buffer最初是為序列化的GL圖形命令構建的,類似一個proxy。當前的“進程外”柵格化(即GPU)以不同的方式使用它們,更多是繪制操作的包裝器,就是命令緩沖區command buffer與底層圖形API無關

          不同操作系統調用不同的共享OpenGL庫

          GPU進程中的GL函數指針通過動態查找操作系統底層共享的OpenGL庫進行初始化,Windows上用ANGLE做一個轉化步驟。

          Angel是另一個由Google構建的庫;它的工作是將OpenGL轉換為DirectX,DirectX是微軟在Windows上用于加速圖形的API。調查發現Angle比Windows的OpenGL驅動程序運行更好。

          渲染流水線(簡版)

          至此擁有了整個pipeline,從DOM一直到內存中的像素,牢記渲染不是靜態的,也不是執行一次就完成了,瀏覽器會話期間發生的任何事情都會動態更改渲染的過程。并且整個pipeline從頭開始運行是非常昂貴的,盡可能希望能減少不必要的工作以提高效率

          frames動畫幀

          低于60幀每秒的動畫和滾動看起來會非常卡,渲染器生成動畫幀,每個幀都是內容在特定時間點狀態的完整呈現,多個幀連起來就是看到的動畫,其實動畫只要達到60幀每秒那么看起來就會是連貫的。新的設備甚至要求90或120甚至更高的幀率。

          如果在1/60秒內,約16.66ms還不能渲染完一幀畫面,那么畫面看起來就是斷斷續續很卡的樣子

          流水線各個階段都依賴上一步的結果

          為了提高性能,很簡單的想到了盡可能復用上一階段處理的結果,對于渲染來說既重復使用以前幀的輸出

          重繪

          大塊區域的繪制和柵格化是非常昂貴的,比如在滾動的時候,視口內所有像素都變化了,這個過程稱為重繪repaint

          渲染進程主線程的競爭關系

          渲染進程的主線程的任何事情都會跟JS競爭(互斥關系),意味著其實JS也會阻塞渲染主線程其他任務的執行

          分層與合成線程

          將頁面分解為不同的層便于柵格化raster對不同的層單獨處理,在渲染進程主線程構建層后commit到合成線程compositor thread去,合成線程compositor thread會對每一層進行單獨繪制

          我們可以在瀏覽器開發工具的Layer看到當前頁面的分層,分層的目的是可以對單獨的層進行變換transform和柵格化raster

          試想一下如果有123三層,其中1,2兩層沒變化,第3層旋轉了,那么只要對第三層每幀進行變換就可以得到每一幀的輸出,計算量大大減少

          「所以分層的目的是為了減少計算加速渲染效率,在渲染進程合成器線程執行則是為了不影響渲染主線程的任務執行」

          圖中的impl*即渲染進程的合成線程,因為歷史原因在代碼里都是這樣表示,后面所有表示合成線程都用impl表示

          為什么要分層?

          分層的作用在有動畫時候可以顯著提升性能,如圖所示BBB文本一層的變換不會影響其他層


          動畫是層的移動,頁面滾動是層的移動和裁剪,放大縮小也是層的縮放

          合成線程處理輸入

          當滾動事件沒有觸發JS邏輯時候,即使渲染進程主線程很繁忙,但是瀏覽器進程發出的頁面滾動事件的處理也不會受到影響,因為渲染進程的合成線程compositor thread可以單獨處理滾動事件

          當然如果滾動觸發了JS的邏輯,那么合成線程必須轉發事件到主線程去,滾動事件會進入主線程任務隊列等待處理

          層的提升

          正常情況下一個LayoutView會創建一個PaintLayer,對應一個cc(Chromium Compositor) Layer。
          但是某些樣式屬性也會導致對應的LayoutObject單獨成層,比如transform屬性就類似創建新層的“觸發器”一樣,瀏覽器遇到這個屬性就會單獨創建新層,cc(Chromium Compositor) Layer沒有父子關系,是一個平級的列表,但是還是保留LayerTree的名稱

          滾動容器創建多個層

          滾動容器創建特殊的多個層,比如元素加了overflow:scroll的滾動屬性,那么合成的時候會有5個層,其中4個層都是滾動條scrollbar的層,這些層合并起來稱為CompositedLayerMapping

          合成透明滾動條會禁用子像素抗鋸齒,如上圖左下角所示。而且判斷是否合成滾動條也有判斷邏輯,在安卓和ChromeOS上可以合成所有的滾動條

          合成任務

          如上圖,合成任務包含構建層樹的過程。在布局layout之后,繪制paint任務之前,這個過程也可以稱為「分層和合成任務」,每一層layer都是獨立繪制的,一些屬性節點單獨為層,比如will-change,3D屬性transform之類

          prepaint構建屬性樹

          屬性樹

          渲染進程合成線程繪制的時候,合成線程里的合成器可以將各種屬性應用于其繪制的圖層,如變換矩陣,裁剪,滾動偏移,透明度。這些數據儲存在屬性樹里,可以將這些理解為圖層的屬性(過去也是這么干的)。后面為了解耦這些屬性,讓它們可以脫離層單獨使用,需要引入prepaint的過程

          預繪制prepaint階段遍歷并構建屬性樹

          合成后繪制(CAP)

          CAP是composite after paint的縮寫,它的目標是將屬性和層解耦。即在paint階段只需要paint的信息,而不需要知道層的任何信息,因為這時層還沒有構建

          在過去,變換、裁剪、效果滾動等信息等存儲在層本身上,但CAP要求層的屬性解耦。未來,層layer的合成會在繪制后進行

          commit復制層數據到合成線程

          在繪制paint階段完成后,即繪制指令準備完成后,會進入渲染進程合成線程commit階段

          commit會拷貝層和屬性樹生成副本,這里合成線程的commit會阻塞主線程直到commit完成

          「注意:渲染進程合成線程拿到的是layer副本,用LayerImpl表示」

          tiling分塊平鋪

          整個網頁是非常大的,向下延伸理論上可以無限長(比如新聞類網站的無限滾動)。

          柵格化是繪制之后的步驟,柵格化會將繪制指令轉化為位圖bitmap。試想一下如果在繪制完整個圖層之后再柵格化整個圖層,則成本會很大,但如果只柵格化部分圖層的可見部分成本則會小很多。

          這里tiling是平鋪的意思,類似裝修時候鋪地板用大塊瓷磚平鋪,頁面顯示的做法類似。

          根據視口viewport所在位置的不同,渲染進程合成器線程會選擇靠近視口的圖塊tiles進行渲染,將最后選擇渲染的圖塊傳遞給GPU柵格化線程池里的單個柵格化線程執行柵格化,最后得到柵格化好后的tile圖塊。圖塊大小根據不同設備的分辨率有不同的大小,比如256*256512*512

          「圖塊tiles是柵格化任務的單位,柵格化就是將一塊塊的tiles轉化為位圖bitmap」

          根據分塊tiles去繪制層

          在柵格化所有的圖塊tiles完成后,渲染進程的合成器線程會生成draw quads命令。

          quad類似于在屏幕上特定位置繪制圖塊tile的指令,draw quads就是繪制圖塊們的意思。

          此時的quad是層樹layer tree在拿屬性樹經過一堆變換后的最終結果,每個quad都引用圖塊tile在GPU內存里的柵格化輸出結果。

          多個DrawQuad最后被包裝在「CompositorFrame」里(簡單理解就是一排要鋪上去的瓷磚 :-),這是渲染進程最后的輸出,包含有渲染進程生成的動畫幀,會被傳遞給GPU進程。

          「注意執行到這里還只是數據,這里屏幕還沒有像素呈現」

          activation

          在準備圖塊tiles進行柵格化和draw兩個階段渲染進程的合成線程都會參與,但是渲染進程主線程里的layer數據還在不斷commit過來。實際上合成線程具有兩個樹的拷貝副本

          • 「pending tree」: 負責接收新的commit并轉給柵格化線程池里的柵格化線程執行,完成后進入激活activation階段,同步復制處理好后的layer副本到active tree里
          • 「active tree」: 繪制上一次activation同步復制的layer副本(來自上一個commit)

          這里pending treeactive tree都是層列表和屬性樹的結合,不是真的樹結構,基于習慣沿用樹的叫法

          display(Viz)

          GPU進程的顯示合成器display compositor會將多個進程最后的「CompositorFrame」進行合并顯示,前面說過「CompositorFrame」是每個進程最后的輸出,包裹了DrawQuad列表。

          可以看到這里也有瀏覽器主進程的「CompositorFrame」,導航欄,收藏夾,前進后退這些Content外的渲染是瀏覽器主進程控制的。瀏覽器主進程有自己合成器為瀏覽器UI生成動畫幀,比如標簽條和地址欄的動畫。

          界面可以嵌入其他界面。瀏覽器嵌入渲染器,渲染器可以嵌入其他渲染器用于跨源iframe(也稱為站點隔離,“進程外iframe”或OOPIF)。同源網頁,比如iframe和一個標簽頁可能共用一個渲染進程,而跨源網頁則一定是多個渲染進程。

          地址:https://www.chromium.org/developers/design-documents/oop-iframes


          顯示合成器display compositor在GPU進程中的Viz線程上運行。Viz取Visuals視覺效果的意思。

          顯示合成器display compositor同步傳入的幀,了解嵌入界面之間的依賴關系,做界面聚合。

          Skia

          Viz線程除了做界面聚合還發起圖形調用,最后屏幕上顯示compositor frame的quad。Viz線程是雙緩沖的,分為前置緩沖區和后置緩沖區,這里將數據處理后序列化放到后置緩沖區

          舊模式是GPU主線程解碼器真正發起GL調用,新模式中是交給Skia庫。Skia繪制到一個異步顯示列表里,會一起傳遞到GPU主線程。GPU主線程的Skia后端發起真正的GL調用。

          分離GL調用通過第三方的Skia或者未來準備使用的Vulkan實現與OpenGL解耦

          前后緩沖區

          在大多數平臺上,顯示合成器display compositor的輸出是雙緩沖的,即包含前后兩個緩沖區。圖塊繪制到后臺緩沖區,Viz發出命令交換前后緩沖區使其可見

          也就是說屏幕顯示器這一幀的畫面,是每HZ從前置緩沖區讀取后在屏幕顯示的,后置緩沖區在馬不停歇的繪制,通過前后緩沖區的交換實現新一幀畫面的呈現。在OS X上,使用CoreAnimation做了一些稍微不同的事情

          顯卡的作用?負責將數據寫到后緩沖區,寫完后前后緩沖區互換。通常情況下顯卡的更新頻率和顯示器的刷新頻率是一致的,如果不一致則會發現視覺上的卡頓。大多數設備屏幕的更新頻率是60次/秒,這也就意味著正常情況下要實現流暢的動畫效果,渲染引擎需要每秒更新60張圖片到顯卡的后緩沖區

          至此瀏覽器完成了它的任務,底層驅動通過調用硬件完成繪制。最后,我們的像素出現在屏幕上

          渲染流水線(全版)

          回顧一下整個渲染流水線的過程,從渲染主線程獲取Web內容,構建DOM樹,解析樣式,更新布局,layer分層后合成,生成屬性樹,創建繪制指令列表。

          再到渲染進程合成線程收到渲染主線程commit過來的帶有繪制指令和屬性樹的layer,將layer分塊為圖塊,使用Skia對圖塊進行柵格化,拷貝pending treeactive tree,生成draw quads命令,將quad發送給GPU的Viz線程,最后像素顯示到屏幕上。

          大多數階段是在渲染器進程里執行的,但是raster和display則在GPU進程中執行。

          核心渲染階段DOM,style,layout,paint是在渲染進程主線程的Blink進行的,但是滾動和縮放等交互事件在渲染主線程繁忙時可以在渲染進程合成線程里執行

          渲染進程主線程

          1. 「DOM:」 解析HTML生成DOM樹
          2. 「style:」 解析styleSheet生成ComputedStyle
          3. 「layout:」 生成layout tree,跟DOM樹基本對應,但是display:none的節點不顯示,內聯元素會創建LayoutBlock匿名節點包裹
          4. 「layer分層后合成:」 某些樣式屬性會單獨形成層,如transform會形成單獨的層方便進行圖形變換,滾動元素會多出scrollbar的4層。合成任務在渲染進程的合成線程中執行,與渲染主線程隔離互不影響
          5. 「prepaint:」 為了將屬性與層解耦引入prepaint階段,prepaint階段需要遍歷并構建屬性樹,屬性樹即存儲如變換矩陣,裁剪,滾動偏移,透明度等數據的地方,方便后面paint階段拿屬性樹數據處理
          6. 「paint:」 繪制過程是將LayoutObject轉化為繪制指令paint op,每個LayoutObject會對應多個繪制指令paint ops,比如背景,前景,輪廓等。樣式可以控制繪制的順序。繪制有自己的順序,如背景色在前,其次是浮動元素,前景色,輪廓outline

          渲染進程合成線程

          頁面的滾動等交互會進入渲染進程合成線程compositor thread里處理,這也是渲染進程主線程繁忙時交互也不卡的原因

          1. 「commit」: 渲染進程合成線程將層從渲染主線程拷貝出兩份層和屬性樹副本
          2. 「tiling」: 柵格化整個圖層成本大,渲染進程合成線程將layer分塊后選擇視口相近的圖塊tiles再進行柵格化成本小很多
          3. 「activate」: 合成線程具有兩個樹的副本,pending tree負責將新commit的layer轉到柵格化線程池里的柵格化線程處理好后同步到active tree
          4. 「draw」: 柵格化所有變換后的圖塊之后,生成draw quads命令,包含多個DrawQuad的CompositorFrame,這是渲染進程最后的輸出,此時屏幕還沒有像素出現

          GPU進程

          1. 「raster」: 柵格化是將繪制指令paint op轉化為位圖bitmap的過程,轉化后每個像素點的rgba都確定。柵格化還處理圖片解碼,通過調用不同解碼器解壓縮圖片,GPU可以加速柵格化,通過調用Skia對圖塊進行柵格化
          2. 「Skia」: 封裝OpenGL調用,提供異步顯示列表,最后傳遞到GPU主線程處理,GPU主線程的Skia后臺發起真正的GL調用
          3. 「display」: GPU Viz線程里的顯示合成器display compositor合并多個進程的「CompositorFrame」輸出,并通過Skia發起圖形調用,像素呈現在屏幕上

          參考

          • PPT: Life of a Pixel,https://docs.google.com/presentation/d/1boPxbgNrTU0ddsc144rcXayGA_WF53k96imRH8Mp34Y/edit#slide=id.ga884fe665f_64_1691
          • Youtube: Life of a Pixel,https://www.youtube.com/watch?v=K2QHdgAKP-s
          • 瀏覽器渲染機制(二),https://juejin.cn/post/6920773802624286733

          者: 蜀中亮子

          轉發鏈接:https://mp.weixin.qq.com/s/ugrBaCIWYzGn8nuStp7ohw


          主站蜘蛛池模板: 偷拍激情视频一区二区三区| 国产福利日本一区二区三区| 国产一区二区三区在线免费观看| 日韩A无码AV一区二区三区 | 国内精品视频一区二区三区| 无码AV中文一区二区三区| 亚洲视频在线一区二区| 日韩精品无码Av一区二区| 成人区人妻精品一区二区不卡| 亚洲宅男精品一区在线观看| 精品国产AV一区二区三区| 精品国产一区二区三区久久久狼| 日韩好片一区二区在线看| 无码福利一区二区三区| 无码人妻一区二区三区av| 一区在线观看视频| 久久精品一区二区三区四区 | 国产福利91精品一区二区| 亚洲欧美日韩中文字幕一区二区三区 | 日韩视频一区二区| 少妇无码AV无码一区| 男人的天堂亚洲一区二区三区| 国产综合精品一区二区三区| 韩国精品福利一区二区三区| 国产99久久精品一区二区| 亚洲一区二区影院| 国产剧情国产精品一区| 中文字幕av无码一区二区三区电影| 国产微拍精品一区二区| 欧美人妻一区黄a片| 亚洲AV无码国产精品永久一区| 亚洲宅男精品一区在线观看| 精品久久一区二区三区| 亚洲人AV永久一区二区三区久久| 痴汉中文字幕视频一区| 中文字幕一区精品| 3d动漫精品啪啪一区二区中文| 亚洲国产成人一区二区三区| 亚洲国产视频一区| 天堂一区二区三区在线观看| 国产成人精品亚洲一区 |