Life of a Pixel:前端代碼如何經過瀏覽器演化爲屏幕顯示的像素

Life of a Pixel 原本是 Chromium 團隊在入職培訓時的培訓資料,其目的是爲了讓新入職的同事可以從大致上快速的瞭解 Chromium 的架構,而不是糾結於代碼邏輯。如今該團隊正式將其發佈,也是爲了對於此感興趣的工程師可以快速的瞭解項目,參與項目的開發協做。本視頻的內容,從宏觀上來講,就是本演講的題目 Life of a Pixel,直譯就是一個像素點的一輩子,表示該演講做者但願觀衆可以在視頻結束後瞭解,前端的工程師所完成的代碼,是如何經過瀏覽器,變爲一個又一個的像素點,以及像素點是如何更新和毀滅的。前端

大致流程

web content (代碼) ➡️ magic (渲染) ➡️pixels (像素)python

  • HTML(Hyper-Text Markup Language, 頁面結構與內容)
  • CSS(Cascading Style Sheets, 頁面樣式)
  • JS(JavaScript, 負責頁面結構與內容和頁面樣式的更新)

瀏覽器真正渲染的內容在紅框內,以外的都是非渲染的部分。渲染的引擎能夠看作是一個黑箱,在 Chromium 中,咱們把它稱爲 Blink。git

同時,在渲染時,咱們須要調用圖像處理的底層 API 去進行渲染,對於此,有官方的統一標準就是 openGL,可是,對於 Windows,可能還須要轉化爲 DirectX。對於此,團隊正在開發一個新項目名爲 Vulkan,爲了進行統一化。固然,這種底層的 API 並不能讀懂咱們的 HTML 和 CSS,它們只能作一些簡單得圖像繪製,諸如畫一些多邊形這種操做。web

因此,咱們再梳理一遍流程。總的來講就是咱們要將 web content 轉化爲對於的圖像處理的 API,在電腦屏幕上進行繪製。在這個過程當中,爲了更好地將已經渲染的圖像更新,咱們要設計一種數據結構,可以幫助咱們更新這個頁面的結構與內容和頁面樣式。這些更新就包括咱們熟知的 JavaScript API,用戶在輸入框輸入,異步加載,動畫,卷軸移動,頁面縮放。後端

初始渲染

parse(解析)

DOM

HTML 的結構,是一種自然的語義化的繼承式的結構。語義化是標籤所帶來的,集成式是樹狀結構所帶來的。咱們能夠將 HTML 看作輸入進行解析,成爲一顆咱們熟知的 DOM 樹。很好的詮釋了父子間,兄弟間的關係。咱們也能夠很直觀的從 JavaScript 所暴露出的 DOM API 中發現。瀏覽器

Style

CSS 由於是 HTML 的裝飾,因此天然而然的也是要依附到 DOM 對象上。依附的過程,也就是 CSS 選擇的過程。可是因爲 HTML 的樹狀結構,CSS 有時須要寫的十分複雜以去高效的匹配到相對應的 DOM 元素。

在 CSS 解析時,解析器會將每個選擇器所選擇的 CSS 屬性名和屬性值保存,做爲 map,同時視頻中說起,CSS 屬性名是由 C++ 進行生成的,該 C++ 文件在構建時由 python 腳本自動生成。下一步,稱爲重計算(recalc),對於全部產生的屬性,咱們會計算它們的疊加和,做爲每個 DOM 元素的每個屬性的值,這個值咱們也稱爲計算值。也就是最終渲染的結構。這個屬性值咱們可使用 Chrome 的 API 或者是 JavaScript DOM API 都可以獲得。數據結構

Layout

經過上面得到的計算屬性,咱們就能夠肯定每個元素在視圖上佔據的肯定位置。這裏舉一個簡單的例子。就是每個元素是由上向下依次排列的,每個元素的高度由字體的大小和間距所決定。可是,實際狀況每每可能比較複雜。

好比一個元素的內容超出了邊界。那麼該元素會呈現可滾動的特性,這時頁面須要實時計算顯示的區域。再好比表格佈局,浮動,文本分列,flex 佈局,writing-mode等屬性,都會帶來佈局的複雜程度。因此,咱們須要一個更加完善的數據結構去存儲這樣一些狀態,使得每一次迭代都可以變得高效和純粹。

這裏引入了新的數據結構,也是對於以前重計算所獲得的 Render Tree 的進一步封裝,將以前的複雜狀況進一步考慮,獲得最終每個元素在視圖上的最終位置,即 Layout Tree。須要注意的是,不是每個 DOM 元素都對應有一個 Layout Object,好比對於一個 display 屬性設置爲 none 的元素。同時,也不是每個 Layout Object 都對應於一個 DOM 元素,好比僞元素。

基於 Layout Tree,咱們就能夠處理 overflow 等一系列複雜狀況。但還有一個個問題是,這種數據結構沒有將輸入的計算屬性和輸出的視圖位置分離開,因此這裏說起了一個正在開發的新項目名爲 LayoutNG 就是爲了解決這個問題。架構

Paint

獲得了上個步驟的 Layout 對象也就意味着咱們能夠真正的繪製像素了。但注意,這裏的繪製也僅僅是語義上的,並無落實到屏幕上。咱們會再一次將 Layout 對象轉化爲一個一個矩形和其對於的顏色,而且將其按堆棧的形式顯示,並不是是 DOM 的出現順序。而且每一次渲染都是根據某一種屬性,好比 PPT 中簡化的幾種,背景,浮動,前景,輪廓。在示例中雖然帶有 blue class 的元素在 green class 以後,但是 green class 中的文字卻顯示在最前,緣由也就是 foreground 屬性在視圖的位置上,要先顯示。

Raster

這裏咱們獲得了元素最終在視圖的顯示信息,咱們就須要真正的進行預渲染。在這一步,咱們會將屏幕上每個像素點的顏色生成爲一個 32 位的二進制碼,分別表示 4 中顏色,同時咱們能夠利用 GPU 進行渲染加速,這裏還要說起的是使用 Google 自主開發的 skia 項目進行圖像處理渲染,經過最底層的 OpenGL。同時 skia 做爲一個單獨的項目,也支持了其餘的大型項目,好比安卓系統。

gpu

最後須要注意的是真正再經過 skia 調用的 openGL API 會經過 CommandBuffer 調用 GPU 進程,進行真實的渲染。也就是說,真實的渲染是獨立出去的,當這一部分進程宕機時咱們能夠快速的重啓。同時以前也提到過,這裏咱們會將 openGL 轉化爲 DirectX 在 Windows 平臺上,經過 Angle 這個庫。固然,Vulkan 的開發就是爲了統一,同時,開發者也在嘗試着將 skia 調用這個模塊也放入 GPU 的進程。

總結

咱們假設初次渲染已經完成,可是,對於前端的快速發展,大量的邏輯已經由後端轉往前端實現,DOM 的更新變得異常頻繁。簡單地說,咱們須要在原有 DOM 上作適量的改動從新渲染。爲了避免從新將上圖的整個流程所有再次進行,這裏咱們就須要將其中的某些狀態保留,提升更新效率。異步

更新渲染

引入

在更新渲染時,有時咱們會縮放頁面,區域滾動,或者是有動畫。在這類型的狀況下,若是渲染速率低於60幀,那麼人眼看到會變得有些卡頓。佈局

因此咱們要儘量判斷出在上節提到的每個步驟中,有哪些元素是須要改變的,哪些不須要是能夠從新利用的,作到效率的優化。這也是在技術實現中也被考慮到的地方。

可是,實際狀況是,有時一個大的區域所有改變,那麼咱們不得不對這個大的區域進行所有從新渲染,好比區域滾動。

還要注意的是 JavaScript 的設計是單線程的,也就意味着在渲染時,加入有 JS 腳本的執行,就會阻塞當前的渲染。

解決方案 compositing

基於以前提到的種種問題,Chromium 團隊提出了 compositing 這種解決方案。目的就是優化性能。有點相似於 Photoshop,簡單得說,有兩點:

  1. 頁面分紅獨立的層,每一層之間的渲染是獨立的
  2. 單獨使用一個線程(impl)去渲染層

在咱們進行動畫,滾動,縮放等操做時,瀏覽器會監聽用戶的輸入行爲,在 impl 線程上進行工做,使得主線程執行 JavaScript,互不干擾,可是假如 impl 線程發現這個事件沒法處理,則仍是會交還給主線程。

在實現層這個概念時仍是會借鑑初次渲染的數據結構,也就是樹,稱爲 Layer Tree。它是命名在 cc(Chromium compositor)下,主要數據信息由以前的 Layout Tree 繼承而來。注意,這裏還有一個 PaintLayer Tree, 相似於一箇中間狀態,將一個 Layout Object 進行分層,而且賦予其功能,例如對子元素進行裁切或者是施加別的效果。

天然而然,咱們將會在 layout 和 paint 這兩個階段中加入 compositing update 去加快大區域從新渲染,得到 layer tree。須要注意的是,如今團隊中正在進行一個工程,稱爲 slimming paint,將 layer tree 的創建放在 paint 階段後,目的是爲了將每一層 layer 的創建變得更加獨立,而且創建屬性樹,提取出獨立或者公共的屬性,儘量地將其放到真正像素級渲染以前。當 impl 線程的 paint 階段結束後,就能夠通知主線程進行同步,有點相似於使用 git 在不一樣分支上合併代碼。

在 raster 以前還有一步優化,對於大面積滾動視圖,沒有必要一開始將全部的內容所有變換成 bitmaps,咱們只須要將視窗中的先進行轉化,在這裏有一個 tiling manager,它負責將區域分塊,就像地板上的瓦塊同樣,隨着滾動區域的變化,將相鄰區域的瓦塊優先渲染。

全部主要的階段已經大致介紹完畢。歡迎補充和加深!

感謝張冀韜同窗將演講內容梳理成文章並於掘金首發。

相關文章
相關標籤/搜索