Architettura RenderingNG

Chris Harrelson
Chris Harrelson

Qui scoprirai come vengono configurati i componenti di RenderingNG e come la pipeline di rendering li attraversa.

A partire dal livello più alto, le attività di rendering sono:

  1. Eseguire il rendering dei contenuti in pixel sullo schermo.
  2. Anima gli effetti visivi sui contenuti da uno stato all'altro.
  3. Scorri in risposta all'input.
  4. Inoltra l'input in modo efficiente nei posti giusti in modo che gli script per sviluppatori e altri sottosistemi possano rispondere.

I contenuti da visualizzare sono un albero di frame per ogni scheda del browser, oltre all'interfaccia del browser. Inoltre, uno stream di eventi di input non elaborati da touchscreen, mouse, tastiere e altri dispositivi hardware.

Ogni frame include:

  • Stato DOM
  • CSS
  • Canvas
  • Risorse esterne, come immagini, video, caratteri e SVG

Un frame è un documento HTML, oltre al relativo URL. Una pagina web caricata in una scheda del browser ha un frame di primo livello, frame secondari per ogni iframe incluso nel documento di primo livello e i relativi discendenti iframe ricorsivi.

Un effetto visivo è un'operazione grafica applicata a una bitmap, come scorrimento, trasformazione, clip, filtro, opacità o miscelazione.

Componenti dell'architettura

In RenderingNG, queste attività sono suddivise in modo logico in più fasi e componenti di codice. I componenti finiscono in vari processi, thread e subcomponenti della CPU all'interno di questi thread. Ognuno svolge un ruolo importante per garantire affidabilità, prestazioni scalabili e estendibilità per tutti i contenuti web.

Struttura della pipeline di rendering

Diagramma della pipeline di rendering.
Le frecce indicano gli input e gli output di ogni fase. Le fasi sono indicate in base al colore, per mostrare il thread o il processo che eseguono. In alcuni casi, le fasi possono essere eseguite in più posizioni, a seconda delle circostanze, motivo per cui alcune hanno due colori. Le fasi verdi sono il thread principale del processo di rendering; quelle gialle sono i compositori del processo di rendering; le fasi arancioni sono il processo di visualizzazione.

Il rendering procede in una pipeline con una serie di fasi e artefatti creati lungo il percorso. Ogni fase rappresenta un codice che esegue un'attività ben definita all'interno del rendering. Gli elementi sono strutture di dati che rappresentano input o output delle fasi.

Le fasi sono:

  1. Anima:modifica gli stili calcolati e manipola gli alberi di proprietà nel tempo in base a schemi temporali dichiarativi.
  2. Stile:applica CSS al DOM e crea stili calcolati.
  3. Layout:determina le dimensioni e la posizione degli elementi DOM sullo schermo e crea la struttura ad albero dei frammenti immutabili.
  4. Pre-paint: calcola le strutture ad albero delle proprietà e invalidate eventuali elenchi di visualizzazione ed riquadri di texture della GPU esistenti, a seconda dei casi.
  5. Scorrimento:aggiorna l'offset di scorrimento dei documenti e degli elementi DOM scorrevoli modificando le strutture ad albero delle proprietà.
  6. Paint: calcola un elenco di visualizzazione che descrive come rasterizzare i riquadri delle texture della GPU dal DOM.
  7. Commit: copia gli alberi di proprietà e l'elenco di visualizzazione nel thread del compositore.
  8. Esegui la suddivisione in livelli:suddividi l'elenco di visualizzazione in un elenco di livelli compositi per la rasterizzazione e l'animazione indipendenti.
  9. Worklet di rasterizzazione, decodifica e pittura: trasformano rispettivamente le liste di visualizzazione, le immagini codificate e il codice dei worklet di pittura in tessere di texture GPU.
  10. Attiva:crea un frame del compositore che rappresenti come disegnare e posizionare i riquadri della GPU sullo schermo, insieme a eventuali effetti visivi.
  11. Aggregate:combina i frame compositor di tutti i frame compositor visibili in un unico frame compositor globale.
  12. Disegna:esegui il frame del compositore aggregato sulla GPU per creare pixel sullo schermo.

Le fasi della pipeline di rendering possono essere ignorate se non sono necessarie. Ad esempio, le animazioni di effetti visivi e scorrimento possono saltare il layout, la pre-tinta e la tinta. Questo è il motivo per cui l'animazione e lo scorrimento sono contrassegnati da punti gialli e verdi nel diagramma. Se il layout, la pre-tinta e la tinta possono essere ignorati per gli effetti visivi, possono essere eseguiti interamente nel thread del compositore e ignorare il thread principale.

Il rendering dell'interfaccia utente del browser non è mostrato direttamente qui, ma può essere considerato una versione semplificata di questa stessa pipeline (e infatti la sua implementazione condivide gran parte del codice). Il video (anche se non mostrato direttamente) viene generalmente visualizzato con codice indipendente che decodifica i frame in riquadri di texture GPU che vengono poi collegati ai frame del compositore e al passaggio di disegno.

Struttura del processo e del thread

Processi della CPU

L'utilizzo di più processi della CPU consente di ottenere l'isolamento delle prestazioni e della sicurezza tra i siti e dallo stato del browser, nonché l'isolamento della stabilità e della sicurezza dall'hardware della GPU.

Diagramma delle varie parti dei processi della CPU

  • La procedura di rendering esegue il rendering, anima, scorre e inoltra l'input per una singola combinazione di sito e scheda. Esistono diversi processi di rendering.
  • Il processo del browser esegue il rendering, l'animazione e inoltra l'input per l'interfaccia utente del browser (inclusa la barra degli indirizzi, i titoli delle schede e le icone) e inoltra tutto l'input rimanente al processo di rendering appropriato. Esiste un solo processo del browser.
  • Il processo di visualizzazione aggrega il compositing di più processi di rendering più il processo del browser. Esegue la rasterizzazione e il disegno utilizzando la GPU. Esiste un unico processo Viz.

Siti diversi finiscono sempre in processi di rendering diversi.

Più schede o finestre del browser dello stesso sito vengono in genere sottoposte a diversi processi di rendering, a meno che le schede non siano correlate, ad esempio se una apre l'altra. In caso di forte utilizzo della memoria su computer, Chromium potrebbe inserire più schede dello stesso sito nella stessa procedura di rendering anche se non sono correlate.

All'interno di una singola scheda del browser, i frame di siti diversi sono sempre in processi di rendering diversi tra loro, ma i frame dello stesso sito sono sempre nello stesso processo di rendering. Dal punto di vista del rendering, l'importante vantaggio di più processi di rendering è che gli iframe e le schede cross-site ottengono isolamento del rendimento tra loro. Inoltre, le origini possono attivare un isolamento ancora maggiore.

Esiste esattamente un processo Viz per tutto Chromium, poiché in genere esistono solo una GPU e uno schermo su cui disegnare.

Separare Viz in un processo dedicato è utile per la stabilità in caso di bug nei driver o nell'hardware della GPU. È utile anche per l'isolamento della sicurezza, che è importante per le API GPU come Vulkan e per la sicurezza in generale.

Poiché il browser può avere molte schede e finestre, tutte con pixel dell'interfaccia utente del browser da disegnare, potresti chiederti perché esiste esattamente un processo del browser. Il motivo è che solo una di queste è attiva alla volta; infatti, le schede del browser non visibili sono per lo più disattivate e liberano tutta la memoria della GPU. Tuttavia, le funzionalità di rendering dell'interfaccia utente del browser complesse vengono sempre più implementate anche nelle procedure di rendering (note come WebUI). Non per motivi di isolamento delle prestazioni, ma per sfruttare la facilità d'uso del motore di rendering web di Chromium.

Sui dispositivi Android meno recenti, il processo di rendering e del browser vengono condivisi quando vengono utilizzati in un componente WebView (questo non vale per Chromium su Android in generale, ma solo per WebView). In WebView, il processo del browser viene condiviso anche con l'app di incorporamento e WebView ha un solo processo di rendering.

A volte è presente anche un processo di utilità per la decodifica dei contenuti video protetti. Questa procedura non è illustrata nei diagrammi precedenti.

Thread

I thread consentono di ottenere isolamento delle prestazioni e reattività nonostante le attività lente, la parallellizzazione della pipeline e il buffering multiplo.

Diagramma della procedura di rendering.

  • Il thread principale esegue gli script, il loop di eventi di rendering, il ciclo di vita del documento, il test di hit, l'invio di eventi script e l'analisi di HTML, CSS e altri formati di dati.
    • Gli helper del thread principale eseguono attività come la creazione di bitmap e blob di immagini che richiedono la codifica o la decodifica.
    • Worker web esegui lo script e un loop di eventi di rendering per OffscreenCanvas.
  • Il thread del compositore elabora gli eventi di input, esegue lo scorrimento e le animazioni dei contenuti web, calcola la stratificazione ottimale dei contenuti web, e coordina le decodificazioni delle immagini, i worklet di pittura e le attività di rasterizzazione.
    • Gli helper per i thread del compositore coordinano le attività di rasterizzazione di Viz, eseguono attività di decodifica delle immagini, worklet di pittura e rasterizzazione di riserva.
  • I thread di output audio, demuxer o multimediali decodificano, elaborano e sincronizzano gli stream video e audio. Ricorda che il video viene eseguito in parallelo con la pipeline di rendering principale.

La separazione dei thread principali e di compositor è fondamentale per l'isolamento delle prestazioni dell'animazione e dello scorrimento dal lavoro del thread principale.

Esiste un solo thread principale per processo di rendering, anche se più schede o frame dello stesso sito possono finire nello stesso processo. Tuttavia, il rendimento è isolato dal lavoro svolto nelle varie API del browser. Ad esempio, la generazione di bitmap e blob di immagini nell'API Canvas viene eseguita in un thread di supporto del thread principale.

Analogamente, esiste un solo thread del compositore per processo di rendering. In genere non è un problema che ce ne sia solo uno, perché tutte le operazioni molto costose sul thread del compositore vengono delegate ai thread di lavoro del compositore o al processo Viz, e questo lavoro può essere svolto in parallelo con il routing dell'input, lo scorrimento o l'animazione. I thread di lavoro del compositore coordinano le attività eseguite nel processo Viz, ma l'accelerazione GPU ovunque può non riuscire per motivi non controllati da Chromium, ad esempio bug dei driver. In queste situazioni, il thread di lavoro eseguirà il lavoro in una modalità di riserva sulla CPU.

Il numero di thread worker del compositore dipende dalle funzionalità del dispositivo. Ad esempio, i computer in genere utilizzano più thread, poiché hanno più core della CPU e sono meno soggetti a limitazioni della batteria rispetto ai dispositivi mobili. Questo è un esempio di scaling up e scaling down.

L'architettura di threading del processo di rendering è un'applicazione di tre diversi modelli di ottimizzazione:

  • Thread di supporto: invia le attività secondarie che richiedono molto tempo a thread aggiuntivi per mantenere il thread principale reattivo ad altre richieste simultanee. I thread di supporto del thread principale e del compositore sono ottimi esempi di questa tecnica.
  • Buffering multiplo: mostra i contenuti precedentemente visualizzati durante il rendering di nuovi contenuti per nascondere la latenza del rendering. Il thread del compositore utilizza questa tecnica.
  • Parallelizzazione della pipeline:esegui la pipeline di rendering in più punti contemporaneamente. In questo modo, lo scorrimento e l'animazione possono essere rapidi; anche se è in corso un aggiornamento del rendering del thread principale, lo scorrimento e l'animazione possono essere eseguiti in parallelo.

Processo del browser

Un diagramma del processo del browser che mostra la relazione tra il thread di rendering e composizione e l'helper del thread di rendering e composizione.

  • Il thread di rendering e composizione risponde agli input nell'interfaccia utente del browser, indirizza gli altri input al processo di rendering corretto, e gestisce il layout e la visualizzazione dell'interfaccia utente del browser.
  • Gli helper per i thread di rendering e composizione eseguono attività di decodifica delle immagini e raster o decodifica di riserva.

Il thread di rendering e composizione del processo del browser è simile al codice e alla funzionalità di un processo di rendering, tranne per il fatto che il thread principale e il thread del compositore sono combinati in uno solo. In questo caso è necessario un solo thread perché non è necessario l'isolamento del rendimento dalle attività lunghe del thread principale, poiché non ce ne sono per progettazione.

Procedura di visualizzazione

Il processo Viz include il thread principale della GPU e il thread del compositore di visualizzazione.

  • Il thread principale della GPU esegue il raster di elenchi di visualizzazione e frame video in riquadri di texture della GPU e disegna frame del compositore sullo schermo.
  • Il thread del compositore della visualizzazione aggrega e ottimizza il compositing di ogni processo di rendering, oltre al processo del browser, in un unico frame del compositore per la presentazione sullo schermo.

In genere, le operazioni di rasterizzazione e di disegno vengono eseguite nello stesso thread, poiché entrambe si basano sulle risorse della GPU ed è difficile utilizzare in modo affidabile la GPU in multithreading (l'accesso multithreading alla GPU semplificato è uno dei motivi alla base dello sviluppo del nuovo standard Vulkan). In Android WebView è presente un thread di rendering separato a livello di sistema operativo per il disegno a causa del modo in cui le visualizzazioni WebView sono incorporate in un'app nativa. È probabile che altre piattaforme avranno un thread simile in futuro.

Il compositore di visualizzazione si trova in un thread diverso perché deve essere sempre reattivo e non bloccarsi su eventuali fonti di rallentamento nel thread principale della GPU. Una causa del rallentamento del thread principale della GPU è rappresentata dalle chiamate a codice non Chromium, come i driver GPU specifici del fornitore, che potrebbero essere lenti in modi difficili da prevedere.

Struttura del componente

All'interno di ogni thread principale o compositore del processo di rendering, esistono componenti software logici che interagiscono tra loro in modo strutturato.

Componenti del thread principale del processo di rendering

Diagramma del renderer Blink.

Nel Renderer Blink:

  • Il frammento dell'albero dei frame locali rappresenta l'albero dei frame locali e il DOM all'interno dei frame.
  • Il componente API DOM e Canvas contiene le implementazioni di tutte queste API.
  • Il runner del ciclo di vita del documento esegue i passaggi della pipeline di rendering fino al passaggio di commit incluso.
  • Il componente Test di hit e invio di eventi di input esegue test di hit per scoprire quale elemento DOM è scelto come target da un evento ed esegue gli algoritmi di invio degli eventi di input e i comportamenti predefiniti.

Lo scheduler e il runner del loop di eventi di rendering decidono cosa eseguire e quando nel loop di eventi. Pianifica il rendering in modo che venga eseguito a una cadenza corrispondente alla frequenza di aggiornamento del display del dispositivo.

Un diagramma dell'albero di frame.

I frammenti dell'albero del frame locale sono un po' complicati. Ricorda che una struttura ad albero di frame è la pagina principale e i relativi iframe secondari, in modo ricorsivo. Un frame è locale per un processo di rendering se viene visualizzato in quel processo, altrimenti è remoto.

Puoi immaginare di colorare i fotogrammi in base al processo di rendering. Nell'immagine precedente, i cerchi verdi sono tutti i frame di un processo di rendering; quelli arancioni sono in un secondo e quello blu in un terzo.

Un frammento di albero di frame locale è un componente connesso dello stesso colore in un albero di frame. L'immagine contiene quattro alberi di frame locali: due per il sito A, uno per il sito B e uno per il sito C. Ogni albero di frame locale ha il proprio componente di rendering Blink. Il renderer Blink di una struttura ad albero di frame locale può o meno trovarsi nella stessa procedura di rendering di altre strutture ad albero di frame locali. Viene determinato dal modo in cui vengono selezionate le procedure di rendering, come descritto in precedenza.

Struttura del thread del compositore del processo di rendering

Un diagramma che mostra i componenti del compositore del processo di rendering.

I componenti del compositore del processo di rendering includono:

  • Un gestore dei dati che gestisce un elenco di livelli compositi, elenchi di visualizzazione e alberi di proprietà.
  • Un runner del ciclo di vita che esegue le animazioni, lo scorrimento, la composizione, il rasterizzazione, la decodifica e l'attivazione dei passaggi della pipeline di rendering. Ricorda che animate e scroll possono verificarsi sia nel thread principale sia nel compositore.
  • Un handler di test di input e hit esegue l'elaborazione dell'input e i test di hit alla risoluzione dei livelli compositi per determinare se è possibile eseguire gesti di scorrimento nel thread del compositore e quale processo di rendering deve essere scelto come target per i test di hit.

Architettura di esempio in pratica

In questo esempio sono presenti tre schede:

Scheda 1: foo.com

<html>
  <iframe id=one src="foo.com/other-url"></iframe>
  <iframe  id=two src="bar.com"></iframe>
</html>

Scheda 2: bar.com

<html>
 …
</html>

Scheda 3: baz.com html <html> … </html>

La struttura dei processi, dei thread e dei componenti per queste schede è la seguente:

Diagramma della procedura per le schede.

Vediamo un esempio per ognuna delle quattro attività principali di rendering. Un promemoria:

  1. Eseguire il rendering dei contenuti in pixel sullo schermo.
  2. Anima gli effetti visivi sui contenuti da uno stato all'altro.
  3. Scorri in risposta all'input.
  4. Inoltra l'input in modo efficiente nei posti giusti in modo che gli script degli sviluppatori e altri sottosistemi possano rispondere.

Per eseguire il rendering del DOM modificato per la prima scheda:

  1. Uno script sviluppatore modifica il DOM nel processo di rendering per foo.com.
  2. Il motore di rendering Blink comunica al compositore che è necessario un rendering.
  3. Il compositore comunica a Viz che deve essere eseguito un rendering.
  4. Viz segnala l'inizio del rendering al compositore.
  5. Il compositore inoltra l'indicatore di inizio al renderer Blink.
  6. Il programma di esecuzione del loop di eventi del thread principale gestisce il ciclo di vita del documento.
  7. Il thread principale invia il risultato al thread del compositore.
  8. Il runner del loop di eventi del compositore esegue il ciclo di vita del compositing.
  9. Eventuali attività di raster vengono inviate a Viz per raster (spesso ce ne sono più di una).
  10. Viz rasterizza i contenuti sulla GPU.
  11. Viz conferma il completamento dell'attività di rasterizzazione. Nota: spesso Chromium non attende il completamento del raster, ma utilizza un elemento chiamato token di sincronizzazione che deve essere risolto dalle attività di raster prima dell'esecuzione del passaggio 15.
  12. A Viz viene inviato un frame del compositore.
  13. Viz aggrega i frame del compositore per il processo di rendering di foo.com, il processo di rendering dell'iframe di bar.com e l'interfaccia utente del browser.
  14. Viz pianifica un sorteggio.
  15. Viz disegna sullo schermo il frame del compositore aggregato.

Per animate una transizione di trasformazione CSS nella seconda scheda:

  1. Il thread del compositore per il processo di rendering di bar.com esegue il ciclo di eventi del compositore di un'animazione modificando gli alberi di proprietà esistenti. Viene quindi eseguito nuovamente il ciclo di vita del compositore. (Potrebbero verificarsi attività di rasterizzazione e decodifica, ma non sono mostrate qui).
  2. A Viz viene inviato un frame del compositore.
  3. Viz aggrega i frame del compositore per il processo di rendering di foo.com, il processo di rendering di bar.com e l'interfaccia utente del browser.
  4. Viz pianifica un sorteggio.
  5. Viz disegna sullo schermo il frame del compositore aggregato.

Per scorrere la pagina web nella terza scheda:

  1. Una sequenza di eventi input (mouse, tocco o tastiera) arriva al processo del browser.
  2. Ogni evento viene indirizzato al thread del compositore del processo di rendering di baz.com.
  3. Il compositore determina se il thread principale deve essere a conoscenza dell'evento.
  4. L'evento viene inviato, se necessario, al thread principale.
  5. Il thread principale attiva i listener di eventi input (pointerdown, touchstar, pointermove, touchmove o wheel) per verificare se i listener chiameranno preventDefault sull'evento.
  6. Il thread principale restituisce se preventDefault è stato chiamato al compositore.
  7. In caso contrario, l'evento di input viene inviato di nuovo al processo del browser.
  8. Il processo del browser lo converte in un gesto di scorrimento combinandolo con altri eventi recenti.
  9. Il gesto di scorrimento viene inviato di nuovo al thread del compositore del processo di rendering di baz.com,
  10. Lo scorrimento viene applicato lì e il thread del compositore per il processo di rendering di bar.com attiva un'animazione nel suo loop di eventi del compositore. In questo modo, l'offset di scorrimento viene modificato nelle strutture ad albero delle proprietà e il ciclo di vita del compositore viene eseguito di nuovo. Inoltre, indica al thread principale di attivare un evento scroll (non mostrato qui).
  11. A Viz viene inviato un frame del compositore.
  12. Viz aggrega i frame del compositore per il processo di rendering di foo.com, il processo di rendering di bar.com e l'interfaccia utente del browser.
  13. Viz pianifica un sorteggio.
  14. Viz disegna sullo schermo il frame del compositore aggregato.

Per instradare un evento click su un link ipertestuale nell'iframe 2 della scheda 1:

  1. Nel processo del browser viene generato un evento input (mouse, tocco o tastiera). Esegue un test di hit approssimativo per determinare che il processo di rendering dell'iframe di bar.com deve ricevere il clic e lo invia lì.
  2. Il thread del compositore per bar.com inoltra l'evento click al thread principale per bar.com e pianifica un'attività del loop di eventi di rendering per elaborarlo.
  3. L'elaboratore di eventi di input per i test di hit del thread principale di bar.com determina su quale elemento DOM nell'iframe è stato fatto clic e attiva un evento click da osservare per gli script. Non sentendo preventDefault, si apre il link ipertestuale.
  4. Al caricamento della pagina di destinazione del link ipertestuale, viene visualizzato il nuovo stato, con passaggi simili a quelli dell'esempio precedente "Eseguire il rendering del DOM modificato". (Queste modifiche successive non sono mostrate qui).

Concetti chiave

Ricordare e interiorizzare il funzionamento del rendering può richiedere molto tempo.

Il risultato più importante è che la pipeline di rendering, grazie a un'attenta modularizzazione e a un'attenzione ai dettagli, è stata suddivisa in una serie di componenti indipendenti. Questi componenti sono stati poi suddivisi in processi e thread paralleli per massimizzare le prestazioni scalabili e le opportunità di espandibilità.

Ogni componente svolge un ruolo fondamentale per garantire il rendimento e le funzionalità delle moderne app web.

Continua a leggere per saperne di più sulle strutture di dati chiave, che sono importanti per RenderingNG quanto i componenti di codice.


Illustrazioni di Una Kravets.