Pembahasan mendalam RenderingNG: BlinkNG

Stefan Zager
Stefan Zager
Chris Harrelson
Chris Harrelson

Blink mengacu pada implementasi platform web Chromium, dan mencakup semua fase rendering sebelum pengomposisian, yang berpuncak pada commit compositor. Anda dapat membaca selengkapnya tentang arsitektur rendering blink di artikel sebelumnya dalam seri ini.

Blink dimulai sebagai fork WebKit, yang merupakan garpu KHTML, yang dibangun pada tahun 1998. Fungsi ini berisi beberapa kode tertua (dan paling penting) di Chromium, dan pada tahun 2014 kode ini benar-benar menunjukkan usianya. Pada tahun itu, kami memulai serangkaian proyek ambisius di bawah tema yang kami sebut BlinkNG, dengan tujuan mengatasi kekurangan lama dalam organisasi dan struktur kode Blink. Artikel ini akan mengeksplorasi BlinkNG dan proyek konstituennya: mengapa kami melakukannya, apa yang mereka capai, prinsip panduan yang membentuk desain mereka, dan peluang untuk peningkatan di masa mendatang yang mereka mampu.

Pipeline rendering sebelum dan sesudah BlinkNG.

Merender pra-NG

Pipeline rendering dalam Blink selalu dibagi secara konseptual menjadi beberapa fase (style, layout, paint, dan seterusnya), tetapi batasan abstraksi bocor. Secara umum, data yang terkait dengan rendering terdiri dari objek yang berumur panjang dan dapat diubah. Objek ini dapat—dan—dimodifikasi kapan saja, serta sering didaur ulang dan digunakan kembali oleh update rendering berturut-turut. Pertanyaan sederhana seperti:

  • Apakah output gaya, tata letak, atau paint perlu diperbarui?
  • Kapan data ini akan mendapatkan nilai "akhir"?
  • Kapan waktu yang tepat untuk mengubah data ini?
  • Kapan objek ini akan dihapus?

Ada banyak contohnya, termasuk:

Style akan menghasilkan ComputedStyle berdasarkan stylesheet; tetapi ComputedStyle tidak dapat diubah; dalam beberapa kasus, akan diubah oleh tahap pipeline berikutnya.

Style akan menghasilkan hierarki LayoutObject, lalu layout akan menganotasi objek tersebut dengan informasi ukuran dan posisi. Dalam beberapa kasus, tata letak bahkan akan memodifikasi struktur hierarki. Tidak ada pemisahan yang jelas antara input dan output tata letak.

Gaya akan menghasilkan struktur data aksesori yang menentukan arah pengomposisian, dan struktur data tersebut dimodifikasi yang diterapkan oleh setiap fase setelah gaya.

Pada tingkat yang lebih rendah, jenis data rendering sebagian besar terdiri dari hierarki khusus (misalnya, hierarki DOM, hierarki gaya, hierarki tata letak, hierarki properti paint); dan fase rendering diimplementasikan sebagai berjalan pohon rekursif. Idealnya, berjalan di pohon harus dimuat: saat memproses node pohon tertentu, kita tidak boleh mengakses informasi apa pun di luar sub-hierarki yang di-root pada node tersebut. Hal itu tidak pernah benar sebelum RenderingNG; hierarki menelusuri informasi yang sering diakses dari ancestor node yang sedang diproses. Hal ini membuat sistem sangat rapuh dan rentan terhadap error. Juga tidak mungkin memulai berjalan pohon dari mana saja kecuali dari akar pohon.

Terakhir, ada banyak jalan miring ke dalam pipeline rendering yang disebar di seluruh kode: tata letak paksa yang dipicu oleh JavaScript, pembaruan parsial yang dipicu selama pemuatan dokumen, pembaruan paksa untuk mempersiapkan penargetan peristiwa, pembaruan terjadwal yang diminta oleh sistem tampilan, dan API khusus yang hanya diekspos ke kode pengujian. Bahkan ada beberapa jalur rekursif dan reentrant ke dalam pipeline rendering (yaitu, melompat ke awal satu tahap dari tengah tahap lain). Masing-masing pada jalan tanjakan ini memiliki perilaku idiosinkratiknya sendiri, dan dalam beberapa kasus, output rendering akan bergantung pada cara update rendering dipicu.

Yang kami ubah

BlinkNG terdiri dari banyak sub-proyek, besar dan kecil, semua dengan tujuan bersama untuk menghilangkan defisit arsitektural yang dijelaskan sebelumnya. Proyek-proyek ini membagikan beberapa prinsip panduan yang dirancang untuk membuat pipeline rendering menjadi lebih mirip dengan pipeline sesungguhnya:

  • Titik masuk seragam: Kita harus selalu memasukkan pipeline di awal.
  • Tahapan fungsional: Setiap tahap harus memiliki input dan output yang didefinisikan dengan baik, dan perilakunya harus fungsional, yaitu, deterministik dan dapat diulang, dan output harus hanya bergantung pada input yang ditentukan.
  • Input konstan: Input tahap apa pun harus secara efektif konstan saat stage berjalan.
  • Output yang tidak dapat diubah: Setelah stage selesai, output-nya seharusnya tidak dapat diubah selama sisa update rendering.
  • Konsistensi titik pemeriksaan: Di akhir setiap tahap, data rendering yang dihasilkan sejauh ini harus berada dalam status konsisten sendiri.
  • Penghapusan duplikat pekerjaan: Hanya menghitung setiap hal sekali.

Daftar lengkap sub-proyek BlinkNG akan membuat pembacaan menjadi melelahkan, tetapi berikut ini adalah beberapa konsekuensi khusus.

Siklus proses dokumen

Class DocumentLifecycle melacak progres kita melalui pipeline rendering. Hal ini memungkinkan kita melakukan pemeriksaan dasar yang menegakkan invarian yang tercantum sebelumnya, seperti:

  • Jika kita memodifikasi properti ComputedStyle, siklus proses dokumen harus kInStyleRecalc.
  • Jika status DocumentLifecycle adalah kStyleClean atau yang lebih baru, NeedsStyleRecalc() harus menampilkan false untuk setiap node yang terpasang.
  • Saat memasuki fase siklus proses paint, status siklus proses harus kPrePaintClean.

Selama mengimplementasikan BlinkNG, kami secara sistematis menghapus jalur kode yang melanggar invarian ini, dan menambahkan lebih banyak pernyataan di seluruh kode untuk memastikan kami tidak mengalami regresi.

Jika Anda pernah menjelajahi lubang kelinci melihat kode rendering tingkat rendah, Anda mungkin perlu bertanya pada diri sendiri, "Bagaimana saya sampai di sini?" Seperti yang disebutkan sebelumnya, ada berbagai titik masuk ke dalam pipeline rendering. Sebelumnya, ini termasuk jalur panggilan rekursif dan reentrant, dan tempat kita memasuki pipeline pada fase perantara, bukan memulai dari awal. Selama BlinkNG, kita menganalisis jalur panggilan ini dan menentukan bahwa semuanya dapat direduksi menjadi dua skenario dasar:

  • Semua data rendering harus diperbarui—misalnya, saat membuat piksel baru untuk tampilan atau melakukan hit test untuk penargetan peristiwa.
  • Kami memerlukan nilai terbaru untuk kueri tertentu yang dapat dijawab tanpa memperbarui semua data rendering. Hal ini mencakup sebagian besar kueri JavaScript, misalnya, node.offsetTop.

Kini hanya ada dua titik masuk ke pipeline rendering, sesuai dengan kedua skenario ini. Jalur kode reentrant telah dihapus atau difaktorkan ulang, dan tidak dapat lagi memasuki pipeline yang dimulai pada fase perantara. Hal ini telah menghilangkan banyak misteri seputar kapan dan bagaimana pembaruan rendering terjadi, sehingga lebih mudah untuk menjelaskan perilaku sistem.

Gaya pipeline, tata letak, dan pra-penggambaran

Secara kolektif, fase rendering sebelum paint bertanggung jawab atas hal-hal berikut:

  • Menjalankan algoritma style cascade untuk menghitung properti gaya akhir untuk node DOM.
  • Menghasilkan hierarki tata letak yang mewakili hierarki kotak dokumen.
  • Menentukan informasi ukuran dan posisi untuk semua kotak.
  • Membulatkan atau mengepaskan geometri subpiksel ke seluruh batas piksel untuk menggambar.
  • Menentukan properti lapisan yang digabungkan (transformasi affine, filter, opasitas, atau apa pun yang dapat dipercepat oleh GPU).
  • Menentukan konten apa yang telah berubah sejak fase menggambar sebelumnya, dan perlu dicat atau dicat ulang (pembatalan validasi cat).

Daftar ini tidak berubah, tetapi sebelum menggunakan BlinkNG, sebagian besar pekerjaan ini dilakukan dengan cara ad hoc, yang tersebar di beberapa fase rendering, dengan banyak fungsi duplikat dan inefisiensi bawaan. Misalnya, fase gaya selalu bertanggung jawab terutama dalam menghitung properti gaya akhir untuk node, tetapi ada beberapa kasus khusus saat kita tidak menentukan nilai properti gaya akhir hingga fase style selesai. Tidak ada poin formal atau yang dapat diberlakukan dalam proses rendering di mana kita dapat mengatakan dengan pasti bahwa informasi gaya itu lengkap dan tidak dapat diubah.

Contoh bagus lainnya dari masalah pra-BlinkNG adalah invalidasi cat. Sebelumnya, pembatalan validasi cat tersebar di semua fase rendering yang mengarah ke paint. Saat memodifikasi kode gaya atau tata letak, sulit untuk mengetahui perubahan apa pada logika pembatalan validasi paint yang diperlukan, dan mudah untuk membuat kesalahan yang menyebabkan bug yang terjadi terlalu atau terlalu dibatalkan validasinya. Anda dapat membaca lebih lanjut seluk-beluk sistem invalidasi Paint lama dalam artikel dari seri artikel yang ditujukan untuk LayoutNG ini.

Mengepaskan geometri tata letak subpiksel ke seluruh batas piksel untuk menggambar adalah contoh di mana kita memiliki beberapa implementasi fungsi yang sama, dan melakukan banyak pekerjaan yang berlebihan. Ada satu jalur kode {i>snapping<i} piksel yang digunakan oleh sistem paint, dan jalur kode yang sepenuhnya terpisah digunakan setiap kali kita membutuhkan perhitungan satu kali dan {i>on-the-fly<i} untuk koordinat yang diikat piksel di luar kode paint. Bisa dibilang, setiap implementasi memiliki bug-nya sendiri, dan hasilnya tidak selalu sesuai. Karena tidak ada caching untuk informasi ini, sistem kadang-kadang akan melakukan komputasi yang sama persis berulang kali—beban yang lain pada performa.

Berikut adalah beberapa proyek signifikan yang menghilangkan defisit arsitektur dari fase rendering sebelum pengecatan.

Project Squad: Menyederhanakan fase gaya

Project ini mengatasi dua defisit utama dalam fase gaya sehingga tidak dapat di-pipeline dengan rapi:

Ada dua output utama dari fase gaya: ComputedStyle, yang berisi hasil menjalankan algoritma kaskade CSS pada hierarki DOM; dan hierarki LayoutObjects, yang menetapkan urutan operasi untuk fase tata letak. Secara konseptual, menjalankan algoritma kaskade harus dilakukan persis sebelum menghasilkan pohon tata letak; tetapi sebelumnya, kedua operasi ini disisipkan. Project Squad berhasil membagi keduanya menjadi beberapa fase yang berbeda dan berurutan.

Sebelumnya, ComputedStyle tidak selalu mendapatkan nilai akhirnya selama penghitungan ulang gaya; ada beberapa situasi saat ComputedStyle diperbarui selama fase pipeline berikutnya. Project Squad berhasil memfaktorkan ulang jalur kode ini, sehingga ComputedStyle tidak pernah diubah setelah fase gaya.

LayoutNG: Pipeline fase tata letak

Project moneter ini—salah satu fondasi RenderingNG—adalah penulisan ulang menyeluruh dari fase rendering tata letak. Kami tidak akan melakukan penilaian terhadap keseluruhan project di sini, tetapi ada beberapa aspek penting untuk keseluruhan project BlinkNG:

  • Sebelumnya, fase tata letak menerima hierarki LayoutObject yang dibuat oleh fase gaya, dan menganotasi hierarki tersebut dengan informasi ukuran dan posisi. Oleh karena itu, tidak ada pemisahan input yang jelas dari output. LayoutNG memperkenalkan hierarki fragmen, yang merupakan output utama yang hanya dapat dibaca untuk tata letak, dan berfungsi sebagai input utama untuk fase rendering berikutnya.
  • LayoutNG menghadirkan properti containment ke tata letak: saat menghitung ukuran dan posisi LayoutObject tertentu, kita tidak lagi melihat ke luar sub-hierarki yang di-root pada objek tersebut. Semua informasi yang diperlukan untuk memperbarui tata letak objek tertentu dihitung sebelumnya dan diberikan sebagai input hanya-baca ke algoritma.
  • Sebelumnya, ada kasus ekstrem ketika algoritma tata letak tidak berfungsi sepenuhnya: hasil algoritma bergantung pada update tata letak terbaru sebelumnya. LayoutNG menyingkirkan kasus-kasus tersebut.

Fase pra-penggambaran

Sebelumnya, tidak ada fase rendering pra-cat formal, hanya ada tas ambil operasi pasca-tata letak. Fase pre-paint berkembang dari pengakuan bahwa ada beberapa fungsi terkait yang dapat diterapkan dengan baik sebagai traversal sistematis dari hierarki tata letak setelah tata letak selesai; yang paling penting:

  • Menerbitkan invalidasi paint: Sangat sulit untuk melakukan pembatalan validasi paint dengan benar selama proses tata letak, jika kami memiliki informasi yang tidak lengkap. Akan jauh lebih mudah untuk melakukan benar, dan bisa menjadi sangat efisien, jika dibagi menjadi dua proses yang berbeda: selama gaya dan tata letak, konten dapat ditandai dengan flag boolean sederhana sebagai "mungkin memerlukan pembatalan cat". Selama berjalan di pohon pra-penggambaran, kami memeriksa tanda ini dan mengeluarkan pembatalan validasi jika diperlukan.
  • Membuat hierarki properti paint: Proses yang dijelaskan secara lebih mendetail lebih lanjut.
  • Menghitung dan merekam lokasi paint yang di-snapped piksel: Hasil yang direkam dapat digunakan oleh fase paint, dan juga oleh kode downstream apa pun yang membutuhkannya, tanpa komputasi yang berlebihan.

Hierarki properti: Geometri yang konsisten

Hierarki properti diperkenalkan di awal RenderingNG untuk menangani kompleksitas scroll, yang di web memiliki struktur yang berbeda dari semua jenis efek visual lainnya. Sebelum hierarki properti, compositor Chromium menggunakan hierarki "lapisan" tunggal untuk mewakili hubungan geometris dari konten gabungan, namun hal itu segera menyatu dengan kompleksitas penuh fitur seperti position:fixed menjadi jelas. Hierarki lapisan menumbuhkan pointer non-lokal tambahan yang menunjukkan "induk scroll" atau "induk klip" dari sebuah lapisan, dan tidak lama kemudian, sangat sulit untuk memahami kodenya.

Hierarki properti memperbaiki masalah ini dengan menampilkan aspek scroll tambahan dan klip konten secara terpisah dari semua efek visual lainnya. Hal ini memungkinkan untuk membuat model struktur visual dan scroll {i>website<i} yang sebenarnya dengan benar. Selanjutnya, "semua" yang harus kita lakukan adalah mengimplementasikan algoritma di atas pohon properti, seperti transformasi ruang-layar dari lapisan yang digabungkan, atau menentukan lapisan mana yang di-scroll dan mana yang tidak.

Bahkan, kita segera melihat bahwa ada banyak tempat lain dalam kode yang memunculkan pertanyaan geometris yang serupa. (Postingan struktur data utama memiliki daftar yang lebih lengkap.) Beberapa dari mereka memiliki implementasi duplikat yang sama dengan kode compositor; semuanya memiliki subset {i>bug<i} yang berbeda; dan tidak ada satu pun dari mereka yang memodelkan struktur situs web yang sebenarnya dengan benar. Solusinya kemudian menjadi jelas: pusatkan semua algoritma geometri di satu tempat dan faktorkan ulang semua kode untuk menggunakannya.

Algoritma ini selanjutnya bergantung pada hierarki properti, itulah sebabnya hierarki properti merupakan struktur data utama–yang digunakan di seluruh pipeline–RenderingNG. Jadi, untuk mencapai tujuan kode geometri terpusat ini, kami perlu memperkenalkan konsep hierarki properti jauh lebih awal dalam pipeline, yaitu pra-penggambaran, dan mengubah semua API yang sekarang bergantung padanya untuk mengharuskan pra-penggambaran dijalankan sebelum dapat dijalankan.

Cerita ini merupakan aspek lain dari pola pemfaktoran ulang BlinkNG: mengidentifikasi komputasi utama, melakukan pemfaktoran ulang untuk menghindari duplikasi, dan membuat tahapan pipeline terdefinisi dengan baik yang menghasilkan struktur data yang memberinya data. Kami menghitung hierarki properti tepat pada titik ketika semua informasi yang diperlukan tersedia; dan kami memastikan bahwa hierarki properti tidak dapat berubah saat tahap rendering berikutnya sedang berjalan.

Komposit setelah cat: Pipeline cat dan pengomposisian

Layerization adalah proses mencari tahu konten DOM mana yang masuk ke dalam lapisan gabungannya sendiri (yang nantinya mewakili tekstur GPU). Sebelum RenderingNG, layerization berjalan sebelum paint, bukan setelahnya (lihat di sini untuk pipeline saat ini–perhatikan perubahan urutan). Pertama-tama kita akan memutuskan bagian DOM mana yang masuk ke dalam lapisan gabungan, dan baru kemudian menggambar daftar tampilan untuk tekstur tersebut. Tentunya, keputusannya bergantung pada faktor-faktor seperti elemen DOM mana yang dianimasikan atau di-scroll, atau memiliki transformasi 3D, dan elemen mana yang digambar di atasnya.

Hal ini menyebabkan masalah besar karena kurang lebih mengharuskan adanya dependensi sirkular dalam kode, yang merupakan masalah besar bagi pipeline rendering. Mari kita lihat alasannya melalui contoh. Misalkan kita perlu invalidate paint (artinya kita perlu menggambar ulang daftar tampilan, lalu melakukan rasternya lagi). Kebutuhan untuk membatalkan validasi bisa berasal dari perubahan di DOM, atau dari gaya atau tata letak yang diubah. Tapi tentu saja, kita hanya ingin membatalkan bagian yang benar-benar telah berubah. Itu berarti menemukan lapisan gabungan mana yang terpengaruh, dan kemudian membatalkan sebagian atau semua daftar tampilan untuk lapisan tersebut.

Ini berarti bahwa pembatalan validasi bergantung pada keputusan DOM, gaya, tata letak, dan layerisasi sebelumnya (masa lalu: arti untuk frame yang dirender sebelumnya). Tetapi {i>layerization<i} saat ini juga bergantung pada semua hal tersebut. Dan karena kita tidak memiliki dua salinan dari semua data {i>layerization<i}, sulit untuk membedakan antara keputusan {i>layerization<i} di masa lalu dan yang akan datang. Jadi kita akhirnya memiliki banyak kode yang memiliki alasan melingkar. Hal ini kadang menyebabkan kode yang tidak logis atau salah, atau bahkan error atau masalah keamanan, jika kami tidak terlalu berhati-hati.

Untuk mengatasi situasi ini, sejak awal kami memperkenalkan konsep objek DisableCompositingQueryAsserts. Sering kali, jika kode mencoba melakukan kueri terhadap keputusan layerisasi di masa lalu, hal ini akan menyebabkan kegagalan pernyataan dan membuat browser error jika berada dalam mode debug. Tindakan ini membantu kami menghindari timbulnya bug baru. Dalam setiap kasus ketika kode secara sah diperlukan untuk mengkueri keputusan layerisasi sebelumnya, kita memasukkan kode untuk mengizinkannya dengan mengalokasikan objek DisableCompositingQueryAsserts.

Rencana kami adalah, dari waktu ke waktu, menghapus semua objek DisableCompositingQueryAssert situs panggilan, lalu mendeklarasikan kode tersebut dengan aman dan benar. Namun, yang kami temukan adalah bahwa sejumlah panggilan pada dasarnya tidak mungkin dihapus selama layerization terjadi sebelum paint. (Kami akhirnya dapat menghapusnya baru-baru ini.) Inilah alasan pertama yang ditemukan untuk project Composite After Paint. Yang kami pelajari adalah, meskipun Anda memiliki fase pipeline yang terdefinisi dengan baik untuk suatu operasi, jika berada di tempat yang salah dalam pipeline, Anda pada akhirnya akan terjebak.

Alasan kedua untuk project Composite After Paint adalah bug Fundamental Compositing. Salah satu cara untuk menyatakan bug ini adalah bahwa elemen DOM bukanlah representasi 1:1 yang baik dari skema layerisasi yang efisien atau lengkap untuk konten laman web. Dan karena pengomposisian terjadi sebelum penggambaran, pengomposisian lebih atau kurang secara inheren bergantung pada elemen DOM, bukan daftar tampilan atau hierarki properti. Hal ini sangat mirip dengan alasan kami memperkenalkan hierarki properti, dan seperti halnya hierarki properti, solusinya terjadi secara langsung jika Anda mengetahui fase pipeline yang tepat, menjalankannya pada waktu yang tepat, dan menyediakannya dengan struktur data kunci yang benar. Dan seperti halnya pohon properti, ini adalah peluang yang baik untuk menjamin bahwa setelah fase pewarnaan selesai, outputnya tidak dapat diubah untuk semua fase pipeline berikutnya.

Manfaat

Seperti yang Anda lihat, pipeline rendering yang terdefinisi dengan baik memberikan manfaat jangka panjang yang sangat besar. Bahkan ada lebih dari yang Anda kira:

  • Keandalan yang jauh lebih baik: Ini cukup mudah. Kode yang lebih sederhana dengan antarmuka yang didefinisikan dengan baik dan dapat dipahami lebih mudah dipahami, ditulis, dan diuji. Hal ini membuatnya lebih dapat diandalkan. Hal ini juga membuat kode menjadi lebih aman dan stabil, dengan lebih sedikit error dan lebih sedikit bug use-after-free.
  • Cakupan pengujian yang diperluas: Selama BlinkNG, kami telah menambahkan banyak pengujian baru ke rangkaian pengujian kami. Hal ini mencakup pengujian unit yang memberikan verifikasi internal yang terfokus; pengujian regresi yang mencegah kita memperkenalkan kembali bug lama yang telah kita perbaiki (begitu banyak!); dan banyak tambahan bagi publik, rangkaian Pengujian Platform Web yang dikelola secara kolektif, yang digunakan oleh semua browser untuk mengukur kesesuaian dengan standar web.
  • Lebih mudah diperluas: Jika sistem dipecah menjadi komponen yang jelas, Anda tidak perlu memahami komponen lain pada tingkat detail apa pun untuk membuat progres pada komponen saat ini. Hal ini memudahkan semua orang untuk menambahkan nilai ke kode rendering tanpa harus menjadi ahli yang mendalam, dan juga memudahkan untuk memahami perilaku seluruh sistem.
  • Performa: Mengoptimalkan algoritma yang ditulis dalam kode spaghetti sudah cukup sulit, tetapi hampir tidak mungkin untuk mencapai hal yang lebih besar seperti scrolling dan animasi berangkai universal atau proses dan thread untuk isolasi situs tanpa pipeline seperti itu. Paralelisme dapat membantu kita meningkatkan performa secara signifikan, tetapi hal ini juga sangat rumit.
  • Hasil dan penanggulangan: Ada beberapa fitur baru yang diwujudkan oleh BlinkNG yang menjalankan pipeline dengan cara baru dan unik. Misalnya, bagaimana jika kita hanya ingin menjalankan pipeline rendering hingga masa berlaku anggaran habis? Atau melewatkan rendering untuk sub-hierarki yang diketahui tidak relevan bagi pengguna saat ini? Itulah yang diaktifkan oleh properti CSS content-visibilitas. Bagaimana dengan membuat gaya komponen bergantung pada tata letaknya? Itu adalah kueri penampung.

Studi kasus: Kueri container

Kueri penampung adalah fitur platform web yang akan sangat dinantikan (ini telah menjadi fitur nomor satu yang paling banyak diminta oleh developer CSS selama bertahun-tahun). Jika sudah sangat bagus, mengapa belum ada? Alasannya adalah penerapan kueri penampung memerlukan pemahaman dan kontrol yang sangat cermat atas hubungan antara kode gaya dan tata letak. Mari kita lihat lebih dekat.

Kueri container memungkinkan gaya yang berlaku pada elemen bergantung pada ukuran ancestor yang sudah ditata. Karena ukuran tata letak dihitung selama tata letak, artinya kita perlu menjalankan penghitungan ulang gaya setelah tata letak; tetapi perhitungan ulang gaya berjalan sebelum tata letak. Paradoks ayam dan telur ini adalah alasan utama kami tidak dapat menerapkan kueri container sebelum BlinkNG.

Bagaimana cara mengatasi masalah ini? Bukankah ini adalah dependensi pipeline mundur, yaitu masalah yang sama dengan yang diselesaikan oleh project seperti Composite After Paint? Lebih buruk lagi, bagaimana jika gaya baru mengubah ukuran ancestor? Bukankah terkadang ini akan menghasilkan loop terus-menerus?

Pada prinsipnya, dependensi melingkar dapat diselesaikan dengan menggunakan properti CSS yang memuat, yang memungkinkan rendering di luar elemen tidak bergantung pada rendering dalam subhierarki elemen. Itu berarti bahwa gaya baru yang diterapkan oleh penampung tidak dapat memengaruhi ukuran penampung, karena kueri penampung memerlukan pembatasan.

Tapi sebenarnya, itu tidak cukup, dan kita perlu memperkenalkan jenis {i>containment<i} yang lebih lemah daripada sekadar pembatasan ukuran. Ini karena biasanya penampung kueri penampung hanya dapat diubah ukurannya dalam satu arah (biasanya memblokir) berdasarkan dimensi inline. Jadi, konsep pembatasan ukuran inline ditambahkan. Tapi seperti yang Anda lihat dari catatan yang sangat panjang di bagian itu, sama sekali tidak jelas untuk waktu yang lama apakah pembatasan ukuran {i>inline<i} dimungkinkan.

Ini adalah satu hal untuk menggambarkan {i>containment<i} dalam bahasa spesifikasi abstrak, dan ini adalah hal lain untuk mengimplementasikannya dengan benar. Ingat bahwa salah satu tujuan BlinkNG adalah untuk menghadirkan prinsip pembatasan ke jalur pohon yang merupakan logika utama rendering: saat melintasi subpohon, tidak ada informasi yang diperlukan dari luar subpohon. Ketika itu terjadi (yah, ini bukan sebuah kebetulan), sekarang jauh lebih rapi dan lebih mudah untuk menerapkan pembatasan CSS jika kode rendering mematuhi prinsip pembatasan.

Masa depan: pengomposisian off-main-thread ... dan seterusnya!

Pipeline rendering yang ditampilkan di sini sebenarnya sedikit lebih cepat dari penerapan RenderingNG saat ini. Ini menunjukkan layerization sebagai berada di luar thread utama, sedangkan saat ini masih berada di thread utama. Namun, ini hanya masalah waktu sebelum proses ini selesai, karena Composite After Paint telah dikirimkan dan layerization setelah paint.

Untuk memahami mengapa hal ini penting, dan ke mana arah mana lagi, kita perlu mempertimbangkan arsitektur mesin rendering dari sudut pandang yang agak lebih tinggi. Salah satu hambatan yang paling tahan lama untuk meningkatkan performa Chromium adalah fakta sederhana bahwa thread utama perender menangani logika aplikasi utama (yaitu, menjalankan skrip) dan melakukan rendering secara massal. Karenanya, thread utama sering kali dipenuhi dengan pekerjaan, dan kemacetan thread utama sering kali menjadi bottleneck di seluruh browser.

Kabar baiknya adalah, seharusnya tidak harus seperti ini! Aspek arsitektur Chromium ini dimulai sejak masa KHTML, saat eksekusi thread tunggal merupakan model pemrograman yang dominan. Pada saat prosesor multi-core menjadi umum di perangkat kelas konsumen, asumsi thread tunggal telah diintegrasikan secara menyeluruh ke dalam Blink (sebelumnya WebKit). Kami telah lama ingin memperkenalkan lebih banyak threading ke dalam mesin rendering, tetapi hal itu tidak mungkin dilakukan pada sistem lama. Salah satu tujuan utama Rendering NG adalah untuk menggali lebih jauh, dan memungkinkan untuk memindahkan pekerjaan rendering, sebagian atau keseluruhan, ke thread (atau thread) lain.

Karena sekarang BlinkNG hampir selesai, kita sudah mulai mempelajari area ini; Non-Blocking Commit adalah upaya pertama untuk mengubah model threading perender. Commpositor commit (atau hanya commit) adalah langkah sinkronisasi antara thread utama dan thread compositor. Selama commit, kami membuat salinan data rendering yang dihasilkan di thread utama, untuk digunakan oleh kode pengomposisian downstream yang berjalan pada thread compositor. Saat sinkronisasi ini terjadi, eksekusi thread utama akan dihentikan saat kode penyalinan berjalan pada thread compositor. Hal ini dilakukan untuk memastikan bahwa thread utama tidak mengubah data rendering saat thread compositor menyalinnya.

Non-Blocking Commit akan menghilangkan kebutuhan thread utama untuk berhenti dan menunggu tahap commit berakhir—thread utama akan terus melakukan tugas sementara commit berjalan secara serentak pada thread compositor. Efek bersih dari Non-Blocking Commit adalah pengurangan waktu yang dikhususkan untuk pekerjaan rendering di thread utama, yang akan mengurangi kemacetan di thread utama, dan meningkatkan performa. Saat tulisan ini dibuat (Maret 2022), kami memiliki prototipe non-Blocking Commit yang berfungsi, dan kami sedang bersiap untuk melakukan analisis mendetail tentang dampaknya terhadap performa.

Menunggu di sayap adalah Penyusunan di luar thread utama, dengan tujuan membuat mesin rendering sesuai dengan ilustrasi dengan memindahkan layerization dari thread utama, dan ke thread pekerja. Seperti Commit Non-Blocking, cara ini akan mengurangi kemacetan pada thread utama dengan mengurangi beban kerja renderingnya. Proyek seperti ini tidak akan pernah mungkin terjadi tanpa peningkatan arsitektur Composite After Paint.

Ada lebih banyak project yang masih dalam proses. Kami akhirnya memiliki fondasi yang memungkinkan eksperimen dengan mendistribusikan ulang pekerjaan rendering, dan kami sangat bersemangat untuk melihat kemungkinannya.