Pembahasan mendalam RenderingNG: BlinkNG

Stefan Zager
Stefan Zager
Chris Harrelson
Chris Harrelson

Blink mengacu pada penerapan platform web oleh Chromium, dan meliputi 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 garpu dari WebKit, yang juga merupakan cabang dari KHTML, yang dibuat sejak tahun 1998. Codelab ini berisi beberapa kode terlama (dan paling penting) di Chromium, dan pada tahun 2014, kode ini jelas menunjukkan usianya. Pada tahun itu, kami memulai serangkaian proyek ambisius di bawah spanduk yang kami sebut BlinkNG, dengan tujuan mengatasi kekurangan yang sudah lama ada dalam pengaturan dan struktur kode Blink. Artikel ini akan membahas BlinkNG dan proyek-proyek konstituennya: mengapa kami melakukannya, apa yang mereka capai, prinsip panduan yang membentuk desain mereka, dan peluang untuk perbaikan di masa depan yang mereka mampu.

Pipeline rendering sebelum dan sesudah BlinkNG.

Rendering pra-NG

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

  • Apakah output gaya, tata letak, atau cat perlu diperbarui?
  • Kapan data ini akan mencapai "final" nilai?
  • Kapan waktu yang tepat untuk mengubah data tersebut?
  • Kapan objek ini akan dihapus?

Ada banyak contohnya, antara lain:

Gaya akan menghasilkan ComputedStyle berdasarkan stylesheet; tetapi ComputedStyle tidak dapat diubah; dalam beberapa kasus, hal itu akan dimodifikasi oleh tahap pipeline berikutnya.

Gaya akan menghasilkan pohon LayoutObject, lalu tata letak akan memberi anotasi pada objek tersebut dengan informasi ukuran dan posisi. Dalam beberapa kasus, tata letak bahkan akan mengubah struktur pohon. 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 pada tempatnya oleh setiap fase setelah gaya.

Pada level yang lebih rendah, tipe data rendering sebagian besar terdiri dari pohon khusus (misalnya, hierarki DOM, pohon gaya, hierarki tata letak, pohon properti cat); dan rendering diimplementasikan sebagai jalur pohon rekursif. Idealnya, tree walking harus dimuat: saat memproses node pohon tertentu, kita tidak boleh mengakses informasi apa pun di luar subtree yang di-root pada node tersebut. Hal tersebut tidak pernah terjadi pada pra-RenderingNG; {i>tree walking<i} yang sering mengakses informasi dari ancestor {i>node<i} yang sedang diproses. Hal ini menyebabkan sistem menjadi sangat rapuh dan rentan error. Juga tidak mungkin memulai berjalan pohon dari mana saja kecuali akar pohon.

Terakhir, ada banyak hal yang menunjang dalam pipeline rendering yang disebar di seluruh kode: tata letak paksa yang dipicu oleh JavaScript, update parsial yang dipicu selama pemuatan dokumen, update paksa untuk mempersiapkan penargetan peristiwa, update terjadwal yang diminta oleh sistem tampilan, serta API khusus yang hanya diekspos untuk menguji kode, di antaranya. Bahkan ada beberapa jalur rekursif dan reentrant ke dalam pipeline rendering (yaitu, melompat ke awal satu tahap dari tengah tahap lainnya). Masing-masing on-ramps ini memiliki perilaku uniknya sendiri, dan dalam beberapa kasus, output rendering akan bergantung pada cara pemicuan update rendering.

Hal yang kami ubah

BlinkNG terdiri dari banyak sub-project, besar dan kecil, dengan tujuan bersama untuk meniadakan defisit arsitektur yang dijelaskan sebelumnya. Proyek-proyek ini berbagi beberapa prinsip panduan yang dirancang untuk membuat pipeline rendering lebih sesuai dengan pipeline yang sebenarnya:

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

Daftar lengkap sub-proyek BlinkNG akan menjadi bacaan yang membosankan, tetapi berikut adalah beberapa konsekuensi khususnya.

Siklus proses dokumen

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

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

Selama implementasi BlinkNG, kami secara sistematis menghilangkan jalur kode yang melanggar invarian ini, dan menaburkan lebih banyak pernyataan di seluruh kode untuk memastikan kita tidak mengalami regresi.

Jika Anda pernah melihat-lihat kode rendering tingkat rendah, Anda mungkin bertanya pada diri sendiri, "Bagaimana saya bisa sampai di sini?" Seperti yang telah disebutkan sebelumnya, ada berbagai titik masuk ke dalam pipeline rendering. Sebelumnya, hal ini mencakup jalur panggilan rekursif dan reentrant, serta tempat kita memasuki pipeline pada fase perantara, bukan dimulai dari awal. Dalam BlinkNG, kami menganalisis jalur panggilan ini dan menentukan bahwa semuanya dapat direduksi menjadi dua skenario dasar:

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

Saat ini hanya ada dua titik masuk ke pipeline rendering, yang sesuai dengan kedua skenario tersebut. Jalur kode reentrant telah dihapus atau difaktorkan ulang, dan tidak lagi memungkinkan untuk memasuki pipeline mulai dari fase perantara. Hal ini telah menghilangkan banyak misteri seputar kapan dan bagaimana pembaruan rendering dilakukan, sehingga lebih mudah untuk memikirkan perilaku sistem.

Gaya, tata letak, dan pra-lukisan pipeline

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

  • Menjalankan algoritma gaya menurun untuk menghitung properti gaya akhir untuk node DOM.
  • Menghasilkan pohon 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 melukis.
  • Menentukan properti lapisan yang digabungkan (transformasi affine, filter, opasitas, atau hal lain yang dapat dipercepat oleh GPU).
  • Menentukan konten yang telah berubah sejak fase paint sebelumnya, dan perlu dicat atau dicat ulang (pembatalan validasi cat).

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

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

Pengepasan geometri tata letak subpiksel ke seluruh batas piksel untuk melukis adalah contoh di mana kita memiliki beberapa implementasi fungsionalitas yang sama, dan melakukan banyak pekerjaan yang berlebihan. Ada satu jalur kode pengepasan piksel yang digunakan oleh sistem paint, dan jalur kode yang sepenuhnya terpisah yang digunakan setiap kali kami membutuhkan penghitungan satu kali dan sambil-jalan untuk koordinat yang dipaskan piksel di luar kode cat. Tentu saja, setiap implementasi memiliki {i>bug<i} sendiri, dan hasilnya tidak selalu cocok. Karena tidak ada penyimpanan dalam cache untuk informasi ini, terkadang sistem akan melakukan komputasi yang sama persis berulang kali—gangguan performa yang lain.

Berikut adalah beberapa project signifikan yang menghilangkan defisit arsitektur dari fase rendering sebelum proses menggambar.

Project Squad: Menyusun fase penataan

Project ini mengatasi dua defisit utama dalam fase gaya yang mencegahnya di pipeline dengan rapi:

Ada dua output utama dari fase gaya: ComputedStyle, yang berisi hasil menjalankan algoritma jenjang CSS pada hierarki DOM; dan hierarki LayoutObjects, yang menetapkan urutan operasi untuk fase tata letak. Secara konseptual, menjalankan algoritma bertingkat harus dilakukan persis sebelum menghasilkan struktur 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 pada fase pipeline berikutnya. Project Squad berhasil memfaktorkan ulang jalur kode ini, sehingga ComputedStyle tidak pernah diubah setelah fase gaya.

LayoutNG: Menerapkan pipeline pada fase tata letak

Proyek moneter ini—salah satu landasan RenderingNG—adalah penulisan ulang lengkap dari fase rendering tata letak. Kami tidak akan memberikan keadilan untuk keseluruhan proyek di sini, tetapi ada beberapa aspek penting untuk keseluruhan proyek BlinkNG:

  • Sebelumnya, fase tata letak menerima hierarki LayoutObject yang dibuat oleh fase gaya, dan menganotasi hierarki dengan informasi ukuran dan posisi. Oleh karena itu, tidak ada pemisahan yang jelas antara input dan output. LayoutNG memperkenalkan hierarki fragmen, yang merupakan output utama hanya-baca, dan berfungsi sebagai input utama untuk fase rendering berikutnya.
  • LayoutNG memindahkan properti pembatasan 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 di mana algoritma tata letak tidak sepenuhnya berfungsi: hasil algoritma bergantung pada update tata letak terbaru sebelumnya. LayoutNG menghilangkan kasus-kasus tersebut.

Fase pra-lukisan

Sebelumnya, tidak ada fase rendering pra-cat formal, yang ada hanyalah operasi pasca-tata letak. Fase pre-Paint berkembang dari pengenalan bahwa ada beberapa fungsi terkait yang paling baik diterapkan sebagai traversal sistematis hierarki tata letak setelah tata letak selesai; yang paling penting:

  • Menerbitkan pembatalan paint: Membatalkan validasi paint dengan benar selama proses tata letak akan sangat sulit dilakukan jika kami memiliki informasi yang tidak lengkap. Akan jauh lebih mudah untuk melakukannya dengan benar, dan bisa 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 validasi paint". Selama berjalannya pohon pra-pengecatan, kami memeriksa tanda ini dan mengeluarkan pembatalan jika diperlukan.
  • Membuat hierarki properti paint: Proses yang dijelaskan secara lebih mendetail lebih lanjut.
  • Menghitung dan merekam lokasi cat yang di-snap dengan 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 dalam RenderingNG untuk menangani kerumitan scroll, yang di web memiliki struktur yang berbeda dari semua jenis efek visual lainnya. Sebelum hierarki properti, compositor Chromium menggunakan satu "lapisan" hierarki untuk merepresentasikan hubungan geometris konten yang digabungkan, namun dengan cepat berantakan karena kompleksitas penuh fitur seperti position:fixed menjadi jelas. Hierarki lapisan menambahkan pointer non-lokal tambahan yang menunjukkan "induk scroll" atau "clip parent" lapisan, dan tak 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 model untuk membuat model struktur scroll dan visual yang sebenarnya dari situs. Berikutnya, "semua" yang harus kita lakukan adalah mengimplementasikan algoritma di atas pohon properti, seperti transformasi ruang layar dari lapisan gabungan, atau menentukan lapisan mana yang di-scroll dan yang tidak.

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

Selanjutnya, semua algoritma ini bergantung pada hierarki properti, itulah sebabnya hierarki properti merupakan struktur data kunci–yaitu, yang digunakan di seluruh pipeline RenderingNG. Jadi untuk mencapai tujuan kode geometri terpusat ini, kita perlu memperkenalkan konsep hierarki properti jauh lebih awal dalam pipeline–dalam pra-lukisan–dan mengubah semua API yang sekarang bergantung padanya untuk mengharuskan dijalankannya pra-lukis sebelum dapat dieksekusi.

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

Komposit setelah cat: Cat pipa dan pengomposisian

Layerisasi adalah proses mencari tahu konten DOM mana yang masuk ke lapisan gabungannya sendiri (yang, pada gilirannya, mewakili tekstur GPU). Sebelum RenderingNG, layerisasi dijalankan sebelum paint, bukan setelahnya (lihat di sini untuk pipeline saat ini–perhatikan perubahan urutannya). Pertama-tama, kita akan memutuskan bagian DOM mana yang dimasukkan ke dalam lapisan gabungan, dan baru kemudian menggambar daftar tampilan untuk tekstur tersebut. Secara alami, keputusan ini bergantung pada faktor-faktor seperti elemen DOM mana yang dianimasikan atau di-scroll, atau yang memiliki transformasi 3D, dan elemen mana yang dilukis di atasnya.

Hal ini menyebabkan masalah besar, karena kurang lebihnya diperlukan adanya dependensi sirkular dalam kode, yang merupakan masalah besar bagi pipeline rendering. Mari lihat alasannya melalui contoh. Misalkan kita perlu membatalkan paint (artinya kita harus menggambar ulang daftar tampilan, lalu melakukan raster lagi). Kebutuhan untuk membatalkan validasi bisa berasal dari perubahan dalam DOM, atau dari gaya atau tata letak yang diubah. Namun tentu saja, kita hanya ingin membatalkan bagian yang telah berubah. Hal itu berarti mencari tahu lapisan gabungan mana yang terpengaruh, lalu membatalkan validasi sebagian atau semua daftar tampilan untuk lapisan tersebut.

Ini berarti bahwa pembatalan validasi bergantung pada DOM, gaya, tata letak, dan keputusan layerization sebelumnya (masa lalu: makna untuk frame yang dirender sebelumnya). Tapi layerisasi saat ini juga tergantung pada semua hal itu. Dan karena kita tidak memiliki dua salinan dari semua data pelapisan, sulit untuk membedakan antara keputusan pelapisan pada masa lalu dan masa depan. Jadi kita berakhir dengan banyak kode dengan penalaran sirkular. Hal ini kadang menyebabkan kode yang tidak logis atau salah, atau bahkan error atau masalah keamanan, jika kami tidak terlalu berhati-hati.

Untuk menangani situasi ini, sejak awal kami memperkenalkan konsep objek DisableCompositingQueryAsserts. Sering kali, jika kode mencoba mengkueri keputusan layerisasi sebelumnya, hal ini akan menyebabkan kegagalan pernyataan dan membuat browser error jika berada dalam mode debug. Hal ini membantu kami menghindari munculnya bug baru. Dalam setiap kasus, ketika kode yang secara sah diperlukan untuk mengkueri keputusan layerisasi sebelumnya, kita memasukkan kode untuk mengizinkannya dengan mengalokasikan objek DisableCompositingQueryAsserts.

Rencana kami adalah, seiring waktu, menghapus semua objek DisableCompositingQueryAssert situs panggilan, lalu mendeklarasikan kode tersebut dengan aman dan benar. Tapi apa yang kami temukan adalah bahwa sejumlah panggilan pada dasarnya tidak mungkin dihapus selama layerization terjadi sebelum {i>lukis<i}. (Kami akhirnya dapat menghapusnya baru-baru ini!) Ini adalah alasan pertama ditemukan untuk proyek Composite After Paint. Apa yang kami pelajari adalah bahwa, meskipun Anda memiliki fase {i>pipeline<i} yang terdefinisi dengan baik untuk suatu operasi, jika itu berada di tempat yang salah dalam {i>pipeline<i} Anda pada akhirnya akan terjebak.

Alasan kedua untuk proyek Composite After Paint adalah {i>bug Fundamental Compositing<i}. Salah satu cara untuk menyatakan bug ini adalah bahwa elemen DOM bukan merupakan representasi 1:1 yang baik dari skema layerisasi yang efisien atau lengkap untuk konten halaman web. Dan karena pengomposisian dilakukan sebelum paint, pengomposisian kurang lebih bergantung pada elemen DOM, bukan menampilkan daftar atau hierarki properti. Hal ini sangat mirip dengan alasan kami memperkenalkan hierarki properti, dan seperti halnya hierarki properti, solusi ini langsung cocok jika Anda mengetahui fase pipeline yang tepat, menjalankannya di waktu yang tepat, dan memberinya struktur data kunci yang benar. Seperti halnya hierarki properti, ini adalah peluang bagus untuk menjamin bahwa setelah fase paint selesai, outputnya tidak dapat diubah untuk semua fase pipeline berikutnya.

Manfaat

Seperti yang Anda lihat, pipeline rendering yang didefinisikan dengan baik memberikan manfaat jangka panjang yang sangat besar. Bahkan ada lebih banyak lagi dari yang Anda pikirkan:

  • Keandalan yang sangat ditingkatkan: Yang satu ini cukup sederhana. Kode yang lebih rapi dengan antarmuka yang jelas dan mudah dipahami akan lebih mudah dipahami, ditulis, dan diuji. Hal ini membuatnya lebih andal. Hal ini juga membuat kode lebih aman dan lebih stabil, dengan lebih sedikit error dan lebih sedikit bug use-after-free.
  • Cakupan pengujian yang diperluas: Dalam perjalanan BlinkNG, kami telah menambahkan banyak pengujian baru ke suite kami. Ini mencakup pengujian unit yang memberikan verifikasi terfokus internal; pengujian regresi yang mencegah kami memperkenalkan kembali bug lama yang telah kami perbaiki (sangat banyak!); dan banyak tambahan untuk Web Platform Test suite publik yang dikelola secara kolektif, yang digunakan semua browser untuk mengukur kesesuaian dengan standar web.
  • Lebih mudah untuk diperluas: Jika sistem dibagi menjadi beberapa 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 mempermudah untuk memikirkan perilaku seluruh sistem.
  • Performa: Mengoptimalkan algoritma yang ditulis dalam kode spaghetti cukup sulit, tetapi hampir tidak mungkin untuk mencapai hal yang lebih besar seperti animasi dan scroll dengan thread universal atau proses dan thread untuk isolasi situs tanpa pipeline semacam itu. Paralelisme dapat membantu kami meningkatkan performa dengan luar biasa, tetapi juga sangat rumit.
  • Hasil dan pembatasan: Ada beberapa fitur baru yang dapat diwujudkan oleh BlinkNG yang menjalankan pipeline dengan cara baru dan baru. Misalnya, bagaimana jika kita hanya ingin menjalankan pipeline rendering hingga anggaran habis? Atau melewatkan rendering untuk sub-hierarki yang diketahui tidak relevan bagi pengguna saat ini? Itulah yang dimungkinkan oleh properti CSS visibilitas konten. Bagaimana dengan membuat gaya komponen bergantung pada tata letaknya? Itu adalah kueri penampung.

Studi kasus: Kueri container

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

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

Bagaimana cara mengatasi masalah ini? Bukankah itu dependensi pipeline mundur, yaitu, masalah yang sama dengan proyek seperti Composite After Paint? Lebih buruk lagi, bagaimana jika gaya baru mengubah ukuran ancestor? Bukankah hal ini terkadang akan menyebabkan loop terus-menerus?

Pada prinsipnya, dependensi sirkular dapat diatasi menggunakan properti CSS penampung, yang memungkinkan rendering di luar elemen agar tidak bergantung pada rendering dalam subpohon elemen tersebut. Artinya, 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. Hal ini karena biasanya container kueri container dapat diubah ukurannya hanya dalam satu arah (biasanya blok) berdasarkan dimensi inline-nya. Jadi, konsep pembatasan ukuran inline telah ditambahkan. Namun, seperti yang dapat Anda lihat dari catatan yang sangat panjang di bagian tersebut, sama sekali tidak jelas apakah pembatasan ukuran inline dapat dilakukan untuk waktu yang lama.

Mendeskripsikan {i>containment<i} dalam bahasa spesifikasi abstrak merupakan hal lain, dan mengimplementasikannya dengan benar merupakan hal lain. Ingat bahwa salah satu tujuan BlinkNG adalah untuk menghadirkan prinsip pembatasan ke jalan pohon yang merupakan logika utama rendering: saat melintasi subpohon, tidak ada informasi yang diperlukan dari luar subpohon. Ketika terjadi (yah, kejadian ini jauh lebih bersih dan lebih mudah untuk menerapkan pembatasan CSS jika kode renderingnya mematuhi prinsip pembatasan.

Masa depan: komposisi di luar rangkaian pesan utama ... dan seterusnya!

Pipeline rendering yang ditampilkan di sini sebenarnya sedikit lebih cepat dari implementasi RenderingNG saat ini. Fungsi ini menunjukkan layerization berada di luar thread utama, sedangkan saat ini lapisan tersebut masih berada di thread utama. Namun, ini hanya masalah waktu sebelum ini selesai, setelah Composite After Paint telah diluncurkan dan layerization dilakukan setelah paint.

Untuk memahami alasan pentingnya hal ini, dan ke mana arahnya, kita perlu mempertimbangkan arsitektur mesin rendering dari sudut pandang yang agak lebih tinggi. Salah satu kendala paling tahan lama untuk meningkatkan performa Chromium adalah fakta sederhana bahwa thread utama perender menangani logika aplikasi utama (yaitu menjalankan skrip) dan sebagian besar rendering. Akibatnya, thread utama sering kali dipenuhi oleh pekerjaan, dan kemacetan thread utama sering kali menjadi bottleneck di seluruh browser.

Kabar baiknya adalah tidak harus seperti ini! Aspek arsitektur Chromium ini berawal dari masa KHTML, ketika eksekusi thread tunggal merupakan model pemrograman yang dominan. Pada saat prosesor multi-core menjadi umum di perangkat kelas konsumen, asumsi thread tunggal benar-benar dimasukkan ke dalam Blink (sebelumnya WebKit). Kami ingin memperkenalkan lebih banyak thread ke dalam mesin rendering untuk waktu yang lama, namun hal itu tidak mungkin dilakukan di sistem lama. Salah satu tujuan utama Rendering NG adalah untuk keluar dari lubang ini, dan memungkinkannya untuk memindahkan pekerjaan rendering, sebagian atau keseluruhan, ke thread (atau beberapa thread) lainnya.

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

Commit Non-Blocking akan menghilangkan keharusan thread utama untuk berhenti dan menunggu tahap commit berakhir—thread utama akan terus melakukan pekerjaan saat commit berjalan secara bersamaan di thread compositor. Efek bersih dari Komitmen Non-Pemblokiran akan menjadi pengurangan waktu yang dikhususkan untuk pekerjaan rendering di thread utama, yang akan mengurangi kemacetan di thread utama, dan meningkatkan performa. Saat artikel ini dibuat (Maret 2022), kami memiliki prototipe yang berfungsi untuk Non-Blocking Commit, dan sedang bersiap untuk melakukan analisis mendetail terkait dampaknya terhadap performa.

Menunggu di sayap adalah Pengomposisian thread utama di luar, yang bertujuan membuat mesin rendering sesuai dengan ilustrasi dengan memindahkan lapisanisasi dari thread utama, dan ke thread pekerja. Seperti Non-Blocking Commit, hal ini akan mengurangi kemacetan pada thread utama dengan mengurangi beban kerja rendering-nya. Project seperti ini tidak akan pernah mungkin terjadi tanpa peningkatan arsitektur Composite After Paint.

Dan masih ada lebih banyak proyek yang sedang direncanakan (dimaksudkan dengan ini)! Akhirnya kami memiliki fondasi yang memungkinkan eksperimen dengan mendistribusikan ulang pekerjaan rendering, dan kami sangat bersemangat untuk melihat hal-hal yang dapat dilakukannya!