Architettura RenderingNG

Chris Harrelson
Chris Harrelson

Qui scoprirai come sono impostati i componenti di RenderingNG e come avviene il passaggio della pipeline di rendering.

Partendo dal livello più alto, le attività del rendering sono:

  1. Esegui il rendering dei contenuti in pixel sullo schermo.
  2. Applica animazioni a effetti visivi sui contenuti da uno stato all'altro.
  3. Scorri in risposta a un input.
  4. Indirizza gli input in modo efficiente nelle posizioni giuste in modo che gli script degli sviluppatori e altri sottosistemi possano rispondere.

I contenuti da visualizzare sono una struttura di frame per ogni scheda del browser, oltre all'interfaccia del browser. e un flusso 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 insieme 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 di iframe ricorrenti.

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

Componenti dell'architettura

In RenderingNG, queste attività sono suddivise logicamente in più fasi e componenti di codice. I componenti finiscono in vari processi della CPU, thread e componenti secondari al loro interno. Ognuna gioca un ruolo importante per raggiungere affidabilità, prestazioni scalabili ed estensibilità di 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 contrassegnate da un colore per mostrare quale thread o processo eseguono. In alcuni casi, le fasi possono essere eseguite in più posizioni, a seconda della circostanza, ed è per questo che alcune hanno due colori. le fasi verdi sono il thread principale del processo di rendering; il giallo è il compositore 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 il codice che esegue un'attività ben definita all'interno del rendering. Gli artefatti sono strutture di dati che sono input o output delle fasi.

Le fasi sono:

  1. Animazione:modifica gli stili calcolati e gli alberi di proprietà nel tempo in base a tempistiche dichiarative.
  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 l'albero di frammenti immutabile.
  4. Pre-colorazione: calcola gli alberi delle proprietà e annulla tutti gli elenchi di visualizzazione e i riquadri di texture GPU esistenti, a seconda dei casi.
  5. Scorri: aggiorna l'offset di scorrimento dei documenti e degli elementi DOM scorrevoli, modificando le strutture delle proprietà.
  6. Paint: calcola un elenco di visualizzazione che descrive come rasterare i riquadri di texture GPU dal DOM.
  7. Commit: copia le strutture delle proprietà e l'elenco di visualizzazione nel thread del compositore.
  8. Sovrapporre l'elenco: suddividi l'elenco di visualizzazione in un elenco di livelli composti per la rasterizzazione e l'animazione indipendenti.
  9. Raster, decodifica e colora i worklet:trasforma, rispettivamente, gli elenchi di visualizzazione, le immagini codificate e il codice del worklet di colorazione in riquadro di texture GPU.
  10. Attiva: crea un frame composito che rappresenti come disegnare e posizionare i riquadri GPU sullo schermo, insieme a eventuali effetti visivi.
  11. Aggregate:combina i frame del compositore da tutti i frame del compositore visibili in un unico frame del compositore globale.
  12. Disegna: esegue il frame del compositore aggregato sulla GPU per creare pixel sullo schermo.

Se non sono necessarie, le fasi della pipeline di rendering possono essere ignorate. Ad esempio, le animazioni di effetti visivi e lo scorrimento possono saltare il layout, pre-colorare e colorare. Questo è il motivo per cui l'animazione e lo scorrimento sono contrassegnati con punti gialli e verdi nel diagramma. Se è possibile saltare layout, pre-paint e colorazione per gli effetti visivi, possono essere eseguiti interamente sul thread del compositore e saltano il thread principale.

Il rendering dell'interfaccia utente del browser non è illustrato direttamente qui, ma può essere considerato come una versione semplificata di questa stessa pipeline (in effetti la sua implementazione condivide gran parte del codice). Il video (anche non raffigurato direttamente) generalmente esegue il rendering con codice indipendente che decodifica i frame in riquadri di texture GPU che vengono quindi collegati ai frame del compositore e nel passaggio di disegno.

Struttura dei processi e dei thread

Processi CPU

L'utilizzo di più processi CPU consente di isolare le prestazioni e la sicurezza tra i siti e dallo stato del browser, nonché a livello di stabilità e sicurezza dall'hardware GPU.

Diagramma delle varie parti dei processi della CPU

  • Il processo di rendering esegue il rendering, l'animazione, lo scorrimento e il routing dell'input per una singola combinazione di sito e scheda. Esistono diversi processi di rendering.
  • Il processo del browser esegue il rendering, l'animazione e il routing dell'input per l'interfaccia utente del browser (inclusi la barra degli indirizzi, i titoli delle schede e le icone) e instrada tutti gli input rimanenti al processo di rendering appropriato. Esiste un solo processo del browser.
  • Il processo di visualizzazione aggrega la composizione da più processi di rendering oltre al processo del browser. Rafforza e disegna usando la GPU. Esiste un solo processo di Viz.

Siti diversi finiscono sempre in processi di rendering diversi.

In genere, più schede del browser o finestre dello stesso sito vengono associate a processi di rendering diversi, a meno che le schede non siano correlate, ad esempio una per aprire l'altra. Con una forte pressione di memoria su desktop, Chromium potrebbe inserire più schede dello stesso sito nello stesso processo di rendering anche se non correlate.

All'interno di un'unica scheda del browser, i frame di siti diversi si trovano sempre in processi di rendering diversi l'uno dall'altro, ma i frame dello stesso sito si trovano sempre nello stesso processo di rendering. Dal punto di vista del rendering, l'importante vantaggio di più processi di rendering è che le schede e gli iframe tra siti realizzano un isolamento delle prestazioni l'uno dall'altro. Inoltre, le origini possono attivare un isolamento ancora maggiore.

Esiste esattamente un processo Viz per tutto Chromium, dato che di solito è disponibile solo una GPU e uno schermo.

Separare Viz in un proprio processo è un vantaggio per la stabilità nonostante i bug nei driver GPU o nell'hardware. È inoltre utile per l'isolamento di sicurezza, il che è importante per le API GPU come Vulkan e per la sicurezza in generale.

Poiché il browser può avere molte schede e finestre e tutte hanno pixel di interfaccia utente del browser, potresti chiederti perché esiste esattamente un solo processo. Il motivo è che solo una di queste è attiva alla volta. Infatti, le schede del browser non visibili sono per lo più disattivate e perdono tutta la memoria GPU. Tuttavia, le complesse funzionalità di rendering dell'interfaccia utente del browser vengono implementate sempre più spesso anche nei processi di rendering (note come WebUI). Ciò non è dovuto a 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 se utilizzati in un componente WebView (questo non vale in generale per Chromium su Android, ma solo per WebView). In WebView, anche il processo del browser viene condiviso con l'app di incorporamento e WebView ha un solo processo di rendering.

A volte esiste anche una procedura di utilità per la decodifica dei contenuti video protetti. Questo processo non è descritto nei diagrammi precedenti.

Thread

I thread aiutano a ottenere l'isolamento delle prestazioni e la reattività nonostante le attività lente, il caricamento in contemporanea della pipeline e il buffering multiplo.

Diagramma del processo di rendering.

  • Il thread principale esegue gli script, il loop di eventi di rendering, il ciclo di vita del documento, il test degli hit, l'invio degli eventi dello script e l'analisi di HTML, CSS e altri formati di dati.
    • Gli aiutanti dei thread principali svolgono attività come la creazione di bitmap e BLOB di immagini che richiedono la codifica o la decodifica.
    • Esegui lo script dei web worker 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, la colorazione dei worklet e le attività di raster.
    • Gli aiutanti dei thread del compositore coordinano le attività di raster di Viz ed eseguono attività di decodifica delle immagini, colorazione dei worklet e raster di fallback.
  • I thread di contenuti multimediali, demuxer o output audio 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 del thread principale da quello del compositore è di fondamentale importanza 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 potrebbero finire nello stesso processo. Tuttavia, le prestazioni sono isolate dal lavoro svolto in varie API del browser. Ad esempio, la generazione di bitmap e BLOB delle immagini nell'API Canvas viene eseguita in un thread helper del thread principale.

Allo stesso modo, esiste un solo thread del compositore per processo di rendering. Di solito non è un problema se ne esiste solo una, perché tutte le operazioni molto costose sul thread del compositore sono delegate ai thread worker del compositore o al processo di Viz e questo lavoro può essere fatto in parallelo con il routing dell'input, lo scorrimento o l'animazione. I thread worker del compositore coordinano le attività eseguite nel processo Viz, ma l'accelerazione delle GPU ovunque può non riuscire per motivi al di fuori del controllo di Chromium, come bug dei driver. In queste situazioni il thread di lavoro svolgerà il lavoro in modalità di riserva sulla CPU.

Il numero di thread worker del compositore dipende dalle capacità del dispositivo. Ad esempio, i computer desktop solitamente usano più thread, poiché hanno più core della CPU e hanno meno limiti di batteria rispetto ai dispositivi mobili. Questo è un esempio di scalabilità verso l'alto e verso il basso.

L'architettura di thread del processo di rendering è un'applicazione di tre diversi pattern di ottimizzazione:

  • Thread helper: invia attività secondarie di lunga durata a thread aggiuntivi per mantenere il thread principale reattivo ad altre richieste simultanee. I thread di supporto per il thread principale e di supporto per la composizione sono ottimi esempi di questa tecnica.
  • Buffer multiplo: mostra i contenuti sottoposti a rendering in precedenza 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ù posizioni contemporaneamente. In questo modo, scorrimento e animazione possono essere veloci: anche se è in corso un aggiornamento del rendering del thread principale, le funzionalità di scorrimento e animazione possono essere eseguite in parallelo.

Processo del browser

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

  • Il thread di rendering e composizione risponde all'input nell'interfaccia utente del browser, instrada altri input al corretto processo di rendering, definisce e visualizza l'interfaccia utente del browser.
  • Gli aiutanti per il rendering e la composizione dei thread eseguono attività di decodifica delle immagini e il raster o la decodifica di fallback.

I thread di rendering e composizione del processo del browser sono simili al codice e alla funzionalità di un processo di rendering, ad eccezione del 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 isolare le prestazioni dalle attività lunghe del thread principale, poiché non ce n'è uno solo per progettazione.

Processo di visualizzazione

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

  • Il thread principale GPU esegue il raster degli elenchi di visualizzazione e dei frame video nei riquadri di texture GPU, e traccia i frame del compositore sullo schermo.
  • Il thread del compositore display aggrega e ottimizza la composizione di ogni processo di rendering, più il processo del browser, in un unico frame del compositore per la presentazione sullo schermo.

Raster e disegno in genere avvengono sullo stesso thread, perché entrambi si basano sulle risorse GPU ed è difficile utilizzare in modo affidabile la GPU in multi-thread (l'accesso multi-thread alla GPU più semplice è uno dei motivi per lo sviluppo del nuovo standard Vulkan). Su Android WebView, è disponibile un thread di rendering separato a livello di sistema operativo per disegnare per via del modo in cui i componenti WebView sono incorporati in un'app nativa. Altre piattaforme avranno probabilmente questo thread in futuro.

Il compositore display si trova su un thread diverso perché deve essere sempre reattivo e non bloccarsi su qualsiasi possibile origine di rallentamento sul thread principale della GPU. Una causa del rallentamento nel thread principale della GPU sono le chiamate a codice non Chromium, ad esempio driver GPU specifici del fornitore, che potrebbero essere lenti in modi difficili da prevedere.

Struttura componente

All'interno di ogni thread principale o del compositore del processo di rendering, sono presenti componenti software logici che interagiscono tra loro in modi strutturati.

Componenti del thread principale del processo di rendering

Diagramma del renderer Blink.

Nel renderer di Blink:

  • Il frammento ad albero di 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.
  • L'esecutore del ciclo di vita del documento esegue i passaggi della pipeline di rendering fino al passaggio di commit incluso.
  • Il componente hit test e invio degli eventi di input esegue i test degli hit per scoprire quale elemento DOM viene scelto come target di un evento, oltre a eseguire gli algoritmi di invio degli eventi di input e i comportamenti predefiniti.

Lo scheduler e runner del loop degli eventi di rendering decidono cosa eseguire nel loop degli eventi e quando. Pianifica il rendering in modo che avvenga a una cadenza corrispondente a quella del display del dispositivo.

Un diagramma dell'albero di frame.

I frammenti di frame di frame locali sono un po' complicati. Ricorda che una struttura di frame è la pagina principale e i suoi iframe secondari, in modo ricorsivo. Un frame è locale per un processo di rendering se quest'ultimo viene sottoposto a rendering, altrimenti è remoto.

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

Un frammento ad albero di frame locale è un componente connesso dello stesso colore in un albero di frame. Nell'immagine sono presenti quattro strutture di frame locali: due per il sito A, uno per il sito B e uno per il sito C. Ogni struttura di frame locale riceve il proprio componente di rendering Blink. Il renderer Blink di un frame Tree locale potrebbe trovarsi o meno nello stesso processo di rendering di altri frame Tree locali. Dipende dal modo in cui vengono selezionati i processi di rendering, come descritto in precedenza.

Struttura dei thread del compositore del processo di rendering

Diagramma che mostra i componenti del compositore del processo di rendering.

I componenti del compositore del processo di rendering includono:

  • Un gestore di dati che gestisce un elenco di livelli compositi, elenchi di visualizzazione e strutture di proprietà.
  • Un runner del ciclo di vita che esegue le fasi di animazione, scorrimento, composito, raster e decodifica e attivazione della pipeline di rendering. Ricorda che l'animazione e lo scorrimento possono verificarsi sia nel thread principale che nel compositore.
  • Un gestore di input e hit test esegue l'elaborazione degli input e gli hit test alla risoluzione di livelli compositi, per determinare se i gesti di scorrimento possono essere eseguiti sul thread del compositore e quali test di hit del processo di rendering devono scegliere come target.

Architettura di esempio nella 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>

Il processo, il thread e la struttura dei componenti per queste schede hanno il seguente aspetto:

Diagramma della procedura per le schede.

Vediamo un esempio per ognuna delle quattro attività principali del rendering. Promemoria:

  1. Esegui il rendering dei contenuti in pixel sullo schermo.
  2. Applicare animazioni a effetti visivi sui contenuti da uno stato all'altro.
  3. Scorri in risposta a un input.
  4. Indirizza in modo efficiente l'input nei punti 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 dello sviluppatore modifica il DOM nel processo di rendering per foo.com.
  2. Il renderer Blink comunica al compositore che è necessario eseguire il rendering.
  3. Il compositore comunica a Viz che è necessario eseguire il rendering.
  4. Viz segnala l'inizio del rendering al compositore.
  5. Il compositore inoltra il segnale di avvio al renderer Blink.
  6. Il runner del loop di eventi del thread principale esegue il ciclo di vita del documento.
  7. Il thread principale invia il risultato al thread del compositore.
  8. L'esecutore del loop di eventi del compositore esegue il ciclo di vita della composizione.
  9. Tutte le attività raster vengono inviate a Viz per il raster (spesso ce ne sono più di una).
  10. Visualizzazione dei contenuti raster sulla GPU.
  11. Viz conferma il completamento dell'attività di raster. Nota: spesso Chromium non attende il completamento del raster, invece utilizza un elemento chiamato token di sincronizzazione che deve essere risolto dalle attività di raster prima dell'esecuzione del passaggio 15.
  12. Un frame del compositore viene inviato a Viz.
  13. Viz aggrega i frame del compositore per il processo di rendering foo.com, il processo di rendering dell'iframe bar.com e l'interfaccia utente del browser.
  14. Visualizzazione pianifica una estrazione.
  15. Viz disegna il frame del compositore aggregato sullo schermo.

Per animare una transizione Transform CSS nella scheda due:

  1. Il thread del compositore per il processo di rendering bar.com attiva un'animazione nel loop di eventi del compositore mutando le strutture delle proprietà esistenti. Viene quindi eseguito di nuovo il ciclo di vita del compositore. (potrebbero verificarsi attività raster e decodificate, ma non sono rappresentate qui.)
  2. Un frame del compositore viene inviato a Viz.
  3. Viz aggrega i frame del compositore per il processo di rendering foo.com, il processo di rendering bar.com e l'interfaccia utente del browser.
  4. Visualizzazione pianifica una estrazione.
  5. Viz disegna il frame del compositore aggregato sullo schermo.

Per scorrere la pagina web nella scheda tre:

  1. Una sequenza di eventi input (mouse, tocco o tastiera) arriva nel processo del browser.
  2. Ogni evento viene instradato al thread del compositore del processo di rendering di baz.com.
  3. Il compositore determina se il thread principale deve conoscere l'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 nell'evento.
  6. Il thread principale restituisce se preventDefault è stato chiamato al compositore.
  7. In caso contrario, l'evento di input viene inviato nuovamente 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 in quel punto e il thread del compositore per il processo di rendering bar.com attiva un'animazione nel suo loop di eventi del compositore. Questa operazione modifica l'offset di scorrimento nelle strutture delle proprietà ed esegue nuovamente il ciclo di vita del compositore. Inoltre, indica al thread principale di attivare un evento scroll (non mostrato qui).
  11. Un frame del compositore viene inviato a Viz.
  12. Viz aggrega i frame del compositore per il processo di rendering foo.com, il processo di rendering bar.com e l'interfaccia utente del browser.
  13. Visualizzazione pianifica una estrazione.
  14. Viz disegna il frame del compositore aggregato sullo schermo.

Per indirizzare un evento click su un link ipertestuale nell'iframe #due nella scheda uno:

  1. Nel processo del browser si verifica un evento input (mouse, tocco o tastiera). Esegue un test hit approssimativo per determinare che il processo di rendering dell'iframe bar.com debba ricevere il clic e inviarlo lì.
  2. Il thread del compositore per bar.com instrada l'evento click al thread principale per bar.com e pianifica un'attività di loop di eventi di rendering per elaborarlo.
  3. Il processore di eventi di input per i principali test di hit dei thread di bar.com al fine di determinare su quale elemento DOM nell'iframe è stato fatto clic e attiva un evento click che gli script devono osservare. Se sente l'assenza di preventDefault, apre il link ipertestuale.
  4. Al caricamento della pagina di destinazione del link ipertestuale, viene visualizzato il nuovo stato, con passaggi simili all'esempio precedente "DOM Render modificato". (Queste modifiche successive non sono descritte qui.)

Conclusione

Può essere necessario molto tempo per ricordare e rendere interno il funzionamento del rendering.

L'aspetto più importante è che la pipeline di rendering, attraverso un'attenta modularizzazione e un'attenzione ai dettagli, è stata suddivisa in una serie di componenti autonomi. Questi componenti sono stati quindi suddivisi tra processi e thread paralleli per massimizzare le prestazioni scalabili e le opportunità di estensibilità.

Ogni componente gioca un ruolo fondamentale per migliorare le prestazioni e le funzionalità delle moderne app web.

Continua a leggere per scoprire le strutture chiave dei dati, importante per il renderingNG quanto i componenti di codice.


Illustrazioni di Una Kravets.