Il modello di livello
Introduzione
Per la maggior parte degli sviluppatori web, il modello fondamentale di una pagina web è il DOM. Il rendering è il processo, spesso oscuro, che trasforma questa rappresentazione di una pagina in un'immagine sullo schermo. Negli ultimi anni i browser moderni hanno cambiato il modo in cui funziona il rendering per sfruttare le schede grafiche: spesso si parla in modo vago di "accelerazione hardware". Quando si parla di una normale pagina web (ovvero non Canvas2D o WebGL), cosa significa davvero questo termine? Questo articolo spiega il modello di base alla base del rendering con accelerazione hardware dei contenuti web in Chrome.
Avvertenze importanti
Qui parliamo di WebKit e, più specificamente, della porta di Chromium di WebKit. Questo articolo illustra i dettagli di implementazione di Chrome, non le funzionalità della piattaforma web. La piattaforma web e gli standard non codificano questo livello di dettagli di implementazione, pertanto non è garantito che i contenuti di questo articolo si applichino ad altri browser, ma la conoscenza degli aspetti interni può comunque essere utile per il debug e l'ottimizzazione delle prestazioni avanzati.
Inoltre, tieni presente che questo articolo tratta un componente fondamentale dell'architettura di rendering di Chrome che sta cambiando molto rapidamente. Questo articolo tenta di trattare solo gli aspetti che difficilmente cambieranno, ma non è garantito che tutto ciò continuerà a essere valido tra sei mesi.
È importante capire che Chrome ha due percorsi di rendering diversi da un po' di tempo: il percorso con accelerazione hardware e il percorso software precedente. Al momento della stesura di questo articolo, tutte le pagine utilizzano il percorso con accelerazione hardware su Windows, ChromeOS e Chrome per Android. Su Mac e Linux, solo le pagine che richiedono il compositing per alcuni dei loro contenuti utilizzano il percorso accelerato (di seguito sono riportate ulteriori informazioni su cosa richiede il compositing), ma a breve anche tutte le pagine utilizzeranno questo percorso.
Infine, stiamo esaminando il motore di rendering e le sue funzionalità che hanno un grande impatto sulle prestazioni. Quando cerchi di migliorare il rendimento del tuo sito, può essere utile comprendere il modello di livello, ma è anche facile sbagliare: i livelli sono costrutti utili, ma crearne molti può introdurre un overhead in tutta la pila grafica. Ti ho avvisato.
Dal DOM allo schermo
Introduzione ai livelli
Una volta caricata e analizzata, la pagina viene rappresentata nel browser come una struttura familiare a molti sviluppatori web: il DOM. Tuttavia, durante il rendering di una pagina, il browser ha una serie di rappresentazioni intermedie che non sono direttamente esposte agli sviluppatori. La più importante di queste strutture è un livello.
In Chrome esistono in realtà diversi tipi di livelli: RenderLayer, che si occupano dei sottoalberi del DOM, e GraphicsLayer, che si occupano dei sottoalberi dei RenderLayer. Quest'ultimo è quello che ci interessa di più, perché i GraphicsLayer vengono caricati sulla GPU come texture. D'ora in poi dirò semplicemente "livello" per indicare GraphicsLayer.
Piccolo inciso sulla terminologia GPU: che cos'è una texture? Pensala come un'immagine bitmap spostata dalla memoria principale (RAM) alla memoria video (VRAM, sulla GPU). Una volta che è sulla GPU, puoi mapparlo a una geometria mesh. Nei videogiochi o nei programmi CAD, questa tecnica viene utilizzata per dare "pelle" ai modelli 3D scheletrici. Chrome utilizza le texture per trasferire blocchi di contenuti delle pagine web sulla GPU. Le texture possono essere mappate a basso costo a posizioni e trasformazioni diverse applicandole a una mesh rettangolare molto semplice. Questo è il funzionamento del CSS 3D ed è ottimo anche per lo scorrimento veloce. Scopri di più su queste due funzionalità più avanti.
Vediamo un paio di esempi per illustrare il concetto di livello.
Uno strumento molto utile per studiare i livelli in Chrome è l'opzione "Mostra bordi dei livelli compositi" nelle impostazioni (ovvero l'icona a forma di ingranaggio) in Strumenti per gli sviluppatori, sotto la voce "Rendering". Evidenzia in modo molto semplice la posizione dei livelli sullo schermo. Attiviamolo. Questi screenshot ed esempi sono stati tutti tratti dall'ultima versione di Chrome Canary, Chrome 27 al momento della stesura di questo articolo.
Figura 1: una pagina a un livello
<!doctype html>
<html>
<body>
<div>I am a strange root.</div>
</body>
</html>

Questa pagina ha un solo livello. La griglia blu rappresenta le tessere, che puoi considerare come sottounità di un livello che Chrome utilizza per caricare parti di un livello di grandi dimensioni alla volta sulla GPU. Non sono molto importanti qui.
Figura 2: un elemento nel proprio livello
<!doctype html>
<html>
<body>
<div style="transform: rotateY(30deg) rotateX(-30deg); width: 200px;">
I am a strange root.
</div>
</body>
</html>

Se applichiamo una proprietà CSS 3D al <div>
che lo fa ruotare, possiamo vedere come appare quando un elemento ha il proprio livello: nota il bordo arancione che delinea un livello in questa visualizzazione.
Criteri di creazione dei livelli
Quali altri elementi hanno un proprio livello? Le strategie di euristica di Chrome sono state migliorate nel tempo e continueranno a esserlo, ma al momento qualsiasi dei seguenti elementi attiva la creazione del livello:
- Proprietà CSS di trasformazione 3D o prospettiva
- Elementi
<video>
che utilizzano la decodifica video accelerata - Elementi
<canvas>
con un contesto 3D (WebGL) o 2D accelerato - Plug-in compositi (ad es. Flash)
- Elementi con animazione CSS per l'opacità o che utilizzano una trasformazione animata
- Elementi con filtri CSS accelerati
- L'elemento ha un discendente con un livello di composizione (in altre parole, se l'elemento ha un elemento secondario nel proprio livello)
- L'elemento ha un elemento fratello con un indice z inferiore che ha un livello di composizione (in altre parole, viene visualizzato sopra un livello composito)
Implicazioni pratiche: animazione
Possiamo anche spostare i livelli, il che li rende molto utili per l'animazione.
Figura 3: livelli animati
<!doctype html>
<html>
<head>
<style>
div {
animation-duration: 5s;
animation-name: slide;
animation-iteration-count: infinite;
animation-direction: alternate;
width: 200px;
height: 200px;
margin: 100px;
background-color: gray;
}
@keyframes slide {
from {
transform: rotate(0deg);
}
to {
transform: rotate(120deg);
}
}
</style>
</head>
<body>
<div>I am a strange root.</div>
</body>
</html>
Come accennato in precedenza, i livelli sono molto utili per spostarsi tra i contenuti web statici. Nella situazione di base, Chrome dipinge i contenuti di un livello in una bitmap software prima di caricarli sulla GPU come texture. Se questi contenuti non cambiano in futuro, non è necessario ridipingerli. Questo è un bene: la nuova pittura richiede tempo che può essere speso per altre cose, come l'esecuzione di JavaScript, e se la pittura è lunga causa interruzioni o ritardi nelle animazioni.
Ad esempio, guarda questa visualizzazione della cronologia di Dev Tools: non ci sono operazioni di pittura mentre questo livello ruota avanti e indietro.

Non valido. Riverniciatura
Tuttavia, se i contenuti del livello cambiano, il livello deve essere ridisegnato.
Figura 4: livelli di ridipingitura
<!doctype html>
<html>
<head>
<style>
div {
animation-duration: 5s;
animation-name: slide;
animation-iteration-count: infinite;
animation-direction: alternate;
width: 200px;
height: 200px;
margin: 100px;
background-color: gray;
}
@keyframes slide {
from {
transform: rotate(0deg);
}
to {
transform: rotate(120deg);
}
}
</style>
</head>
<body>
<div id="foo">I am a strange root.</div>
<input id="paint" type="button" value="repaint">
<script>
var w = 200;
document.getElementById('paint').onclick = function() {
document.getElementById('foo').style.width = (w++) + 'px';
}
</script>
</body>
</html>
Ogni volta che viene fatto clic sull'elemento di input, l'elemento in rotazione si allarga di 1 pixel. Ciò causa il nuovo layout e la nuova colorazione dell'intero elemento, che in questo caso è un intero livello.
Un buon modo per vedere cosa viene dipinto è utilizzare lo strumento "Mostra rettangoli di pittura" in Strumenti per gli sviluppatori, anche nella sezione "Rendering" delle impostazioni di Strumenti per gli sviluppatori. Dopo averlo attivato, noterai che l'elemento animato e il pulsante lampeggiano entrambi in rosso quando fai clic sul pulsante.

Gli eventi di aggiornamento vengono visualizzati anche nella sequenza temporale di Strumenti per gli sviluppatori. I lettori più attenti potrebbero notare che ci sono due eventi di pittura: uno per il livello e uno per il pulsante stesso, che viene ridisegnato quando passa allo stato premuto o viceversa.

Tieni presente che Chrome non deve sempre ridipingere l'intero livello, ma cerca di ridipingere solo la parte del DOM che è stata invalidata. In questo caso, l'elemento DOM che abbiamo modificato è la dimensione dell'intero livello. In molti altri casi, però, in un livello sono presenti molti elementi DOM.
La domanda successiva ovvia è che cosa causa un'invalidazione e forza una nuova pittura. È difficile rispondere in modo esaustivo perché esistono molti casi limite che possono forzare le invalidazioni. La causa più comune è l'inquinamento del DOM mediante la manipolazione degli stili CSS o il riordinamento. Tony Gentilcore ha scritto un ottimo post del blog sulle cause del relayout, mentre Stoyan Stefanov ha scritto un articolo che tratta la pittura in modo più dettagliato (ma si conclude solo con la pittura, non con queste complicate tecniche di composizione).
Il modo migliore per capire se sta interessando qualcosa a cui stai lavorando è utilizzare gli strumenti della cronologia e dei rettangoli di pittura di DevTools per vedere se stai ridipingendo quando non vorresti, quindi prova a identificare dove hai sporcato il DOM subito prima del nuovo layout/della nuova pittura. Se la pittura è inevitabile, ma sembra richiedere un tempo ingiustificatamente lungo, consulta l'articolo di Eberhard Gräther sulla modalità di pittura continua in Strumenti per gli sviluppatori.
Riepilogo: dal DOM allo schermo
In che modo Chrome trasforma il DOM in un'immagine sullo schermo? A livello concettuale:
- Prende il DOM e lo suddivide in livelli
- Dipinge ciascuno di questi livelli in modo indipendente in bitmap software
- Li carica sulla GPU come texture
- Compone i vari livelli nell'immagine finale della schermata.
Tutto ciò deve avvenire la prima volta che Chrome genera un frame di una pagina web. Tuttavia, per i fotogrammi futuri puoi utilizzare alcune scorciatoie:
- Se alcune proprietà CSS cambiano, non è necessario ridipingere nulla. Chrome può semplicemente ricomporre i livelli esistenti già presenti sulla GPU come texture, ma con proprietà di composizione diverse (ad es. in posizioni diverse, con opacità diverse e così via).
- Se parte di un livello viene invalidata, viene ridipinta e ricaricata. Se i contenuti rimangono invariati, ma gli attributi compositi cambiano (ad esempio, viene tradotto o l'opacità cambia), Chrome può lasciarlo sulla GPU e eseguire nuovamente la composizione per creare un nuovo frame.
Ora dovrebbe essere chiaro che il modello di composizione basato su livelli ha implicazioni profonde sul rendimento del rendering. Il compositing è relativamente economico quando non è necessario dipingere nulla, quindi evitare di ridipingere i livelli è un buon obiettivo generale quando si tenta di eseguire il debug delle prestazioni di rendering. Gli sviluppatori esperti daranno un'occhiata all'elenco degli attivatori di composizione sopra e si renderanno conto che è possibile forzare facilmente la creazione di livelli. Tuttavia, fai attenzione a non crearli senza criterio, perché non sono senza costi: occupano memoria nella RAM di sistema e nella GPU (particolarmente limitata sui dispositivi mobili) e averne molti può introdurre altro overhead nella logica che tiene traccia di quelli visibili. Molti livelli possono anche aumentare il tempo di rasterizzazione se sono di grandi dimensioni e si sovrappongono molto rispetto a prima, causando ciò che a volte viene definito "overdraw". Quindi, utilizza le tue conoscenze con saggezza.
Per il momento è tutto. Continua a seguirci per altri due articoli sulle implicazioni pratiche del modello a livelli.