Le texture, l'ingordo che divora la tua VRAM
La volta scorsa abbiamo dimezzato i vertici e il modello è un po' ristretto, ma non è diventato un fulmine — perché il vero mangiatore di dimensioni era ancora lì: le texture. In un modello PBR, le texture di solito occupano oltre l'80% della dimensione, ed è la parte che si gonfia di più nella VRAM.
Questo articolo è la cura per il problema dell'«ingordo di VRAM» delle texture. Tre cose: perché PNG/JPG sono colpevoli agli occhi della GPU; come sono fatti i formati di texture nativi della GPU e perché non si possono usare direttamente; e come Basis Universal + KTX2 collegano i tre.
Ripasso: perché il JPG fa esplodere la VRAM
Nell'articolo precedente abbiamo dato una formula:
Uso VRAM = larghezza * altezza * 4 byte (RGBA) * 1.333 (con mipmap)
Una texture 4096×4096, sia che sia un JPG da 1,5 MB o un PNG da 8 MB su disco, diventa ~87 MB nella VRAM. Un solo motivo: la GPU non capisce JPG/PNG.
L'unità di campionamento texture della GPU capisce una cosa sola: date delle coordinate UV, leggere un colore da un blocco di pixel di dimensione fissa. Richiede che la texture sia nella VRAM come «pixel grezzi stesi». Quindi prima di caricare un JPG sulla GPU, il browser deve prima decomprimerlo completamente in pixel RGBA sulla CPU, poi spingere l'intero blocco nella VRAM.
Questo processo ha tre problemi:
- Esplosione della VRAM: i pixel grezzi decompressi sono enormi. 87 MB non è un'esagerazione, è ciò che calcola la formula.
- Blocco dell'upload: spostare un grande blocco di pixel dalla memoria della CPU alla VRAM della GPU è un'operazione lenta che blocca il primo frame.
- Costo di decodifica della CPU: decodificare un'immagine grande costa tempo di per sé, soprattutto su mobile.
Estendendo la metafora della «spugna compressa» della volta scorsa: PNG/JPG è una spugna premuta piatta per un trasporto facile; una volta sulla GPU, la spugna assorbe acqua e torna alla dimensione piena. Il download è diventato più veloce; la VRAM non ha risparmiato nulla.
I formati di texture della GPU: compressi nella VRAM per natura
Visto che la GPU non accetta un PNG precompresso, possiamo mantenere la texture compressa anche dentro la VRAM? La GPU decodifica un singolo blocco di pixel al volo durante il campionamento, quasi senza costo.
È proprio ciò che fanno i formati di texture nativi GPU. Famiglie rappresentative:
| Famiglia di formato | Nome completo | Piattaforme principali | Note |
|---|---|---|---|
| BC1-7 | Block Compression | Desktop (PC, Mac) | Veterano, compressione a blocchi 4×4 per generazione |
| ETC1/2 | Ericsson Texture Compression | Mobile (Android/iOS più vecchi) | Vecchio standard mobile |
| ASTC | Adaptive Scalable Texture Compression | Mobile/VR (dispositivi nuovi) | Flessibile, qualità migliore, regolabile per blocco |
| PVRTC | PowerVR | iOS più vecchi | In fase di sostituzione con ASTC |
Questi formati condividono un tratto: le texture sono memorizzate compresse in piccoli blocchi di 4×4 pixel (block), e la GPU decodifica un piccolo blocco a richiesta durante il campionamento — ciò che esce non è un singolo pixel ma un intero blocco. Il vantaggio è che l'uso della VRAM si riduce di un rapporto fisso indipendentemente dal contenuto.
Confronto:
| PNG/JPG (tradizionale) | Formati nativi GPU | |
|---|---|---|
| Dimensione su disco | Piccola (JPG soprattutto) | Media (compressione a blocchi, bitrate fisso) |
| Uso VRAM | Grande (decompresso a pixel grezzi) | Piccolo (compressione a blocchi, residente) |
| Upload alla GPU | Lento (decodifica CPU + grosso trasferimento) | Veloce (sposta e basta, nessuna decodifica) |
| Velocità di campionamento | Veloce (già pixel grezzi) | Veloce (decodifica hardware in tempo reale) |
I formati GPU sembrano la soluzione perfetta. Allora perché non usarli direttamente?
Il problema: dispositivi diversi riconoscono formati diversi
Questa è la più grande trappola dei formati di texture GPU — frammentazione.
- I PC desktop riconoscono BC1-7, non ASTC
- I telefoni Android riconoscono ETC2/ASTC, per lo più non BC
- iOS (A7+) riconosce ASTC, i più vecchi PVRTC
- WebGPU/WebGL si appoggiano sulle stesse capacità hardware dietro al dispositivo
Se vuoi che una texture «esista come formato nativo GPU su tutti i dispositivi», devi preparare una copia separata per ogni piattaforma. Un prodotto per desktop + Android + iOS ha bisogno di BC + ETC2/ASTC per la stessa texture — tre versioni. Il pacchetto triplica, lo sforzo anche.
Peggio: sul web non sai quale dispositivo apre la pagina l'utente. Pre-generare tutti i formati non è realistico, e il rilevamento a runtime arriva tardi.
Basis Universal: codifica una volta, transcodifica ovunque
Basis Universal (Basis in breve) è nato per risolvere questa frammentazione. La sua idea in una frase:
Prima codifica la texture in un «formato intermedio», poi a runtime transcodificala nel formato nativo corrispondente in base alle capacità GPU del dispositivo attuale.
Flusso di transcodifica (schemativo):
Texture sorgente (PNG/JPG)
│ codifica offline una tantum (lenta, una volta)
▼
Formato intermedio Basis (ETC1S o UASTC)
│ impacchettato in un contenitore KTX2
▼
Pubblica sul web ──┬── GPU desktop ──→ transcodifica runtime → BC1/3/7
├── Android ────→ transcodifica runtime → ETC2
└── iOS/VR ─────→ transcodifica runtime → ASTC
Punti chiave:
- La codifica offline avviene una volta e produce una rappresentazione intermedia compatta
- La transcodifica a runtime è molto veloce (calcolo puro, pochi millisecondi), e transcodifica formati a blocchi — non serve decompressione per pixel
- Ciò che entra nella VRAM dopo la transcodifica è un vero formato nativo GPU, quindi l'uso della VRAM si calcola con i tassi di compressione a blocchi, identico ai formati nativi GPU
Basis offre due modalità di codifica intermedia; il prossimo articolo le sviluppa, ma ricordate i nomi:
- ETC1S: rapporto di compressione estremamente alto, adatto a diffuse/albedo e altre color map
- UASTC: qualità superiore, adatto a normali e altre mappe sensibili alla precisione
KTX2: il contenitore standard per le texture GPU
Rimane una questione ingegneristica: dove mettere i dati Basis codificati, come marcarli e come relazionarli a glTF? La risposta è KTX2.
KTX2 (Khronos Texture 2) non è un altro formato di immagine — è un formato contenitore. Come uno .zip non si cura se contiene documenti o immagini, KTX2 si limita a impacchettare i dati di texture GPU (inclusi quelli codificati Basis) in una struttura standard con metadati (formato, livelli mipmap, spazio colore, ecc.).
In glTF, KTX2 si collega tramite l'estensione KHR_texture_basisu: la texture non è più un file PNG ma un file KTX2 che contiene la codifica Basis. Al caricamento, il motore rileva le capacità del dispositivo e transcodifica nel BC/ETC/ASTC corrispondente.
Districchiamo i tre ruoli — non confondeteli:
| Nome | Ruolo | Analogia |
|---|---|---|
| Basis Universal | Schema di codifica (come comprimere una texture nel formato intermedio) | Una sorta di «algoritmo di compressione» |
| KTX2 | Formato contenitore (come impacchettare i dati codificati) | Una «scatola» |
| KHR_texture_basisu | Estensione glTF (dice al motore che è una texture Basis) | Un'«etichetta» |
Un file KTX2 può contenere codifica Basis (multipiattaforma) o un formato nativo (es. BC7 grezzo). Sul web, al 99% contiene Basis, perché ciò che vogliamo è «codifica una volta, transcodifica ovunque».
Esempio VRAM: una texture 4096 confrontata
Sovrapponendo formula e formati GPU, ecco l'impronta reale di una texture 4096×4096 RGBA sotto diverse opzioni:
| Opzione | Dim. su disco | Uso VRAM (con mipmap) | Velocità upload | Multipiattaforma |
|---|---|---|---|---|
| PNG | ~8 MB | ~87 MB | Lenta (serve decodifica) | ✅ |
| JPG | ~1.5 MB | ~87 MB | Lenta (serve decodifica) | ✅ |
| WebP | ~2 MB | ~87 MB | Lenta (serve decodifica) | ✅ |
| KTX2 (ETC1S) | ~2-3 MB | ~11-14 MB | Veloce | ✅ (transcode) |
| KTX2 (UASTC) | ~6-8 MB | ~22 MB | Veloce | ✅ (transcode) |
Da dove vengono i numeri VRAM: la compressione a blocchi della GPU di solito si conta a 4bpp (4 bit per pixel) o 8bpp. 4096×4096 a 4bpp è circa 8 MB, ×1.333 con mipmap ≈ 11 MB. UASTC è per lo parte transcodificato a 8bpp, quindi circa 22 MB.
L'importante non è il numero esatto di una riga, ma questi due punti:
- I formati tradizionali (PNG/JPG/WebP) hanno un uso VRAM quasi identico — tutti pixel grezzi decompressi, 87 MB. Per quanto piccoli su disco, la VRAM non si risparmia.
- KTX2 abbassa la VRAM a 1/4 o 1/8, e la dimensione su disco è altrettanto competitiva.
Ecco perché VR e web mobile vanno quasi sempre con KTX2 — quante texture da 87 MB stanno nella VRAM da 2 GB di un telefono? A 11 MB ne stanno sette.
Matrice di supporto delle piattaforme: quali GPU riconoscono quali formati
Basis ci scherma dai dettagli, ma capire il mapping sottostante aiuta nella risoluzione dei problemi. Ecco il supporto attuale dei dispositivi principali per i formati nativi:
| Piattaforma / dispositivo | BC1-7 | ETC2 | ASTC | PVRTC |
|---|---|---|---|---|
| PC desktop (D3D11/12, Vulkan, WebGPU) | ✅ | ❌ | Parziale (GPU nuove) | ❌ |
| macOS (Metal) | ✅ (macchine nuove) | ❌ | ✅ | ❌ |
| Android (mainstream) | ❌ | ✅ | ✅ | ❌ |
| iOS (A8+) | ❌ | ✅ | ✅ | ✅ (dispositivi più vecchi) |
| WebGL 2 | In base all'estensione | ✅ | Parziale | ❌ |
| WebGPU | ✅ (desktop) | ✅ | ✅ (a seconda del dispositivo) | ❌ |
Basis rileva queste capacità a runtime e transcodifica la stessa codifica intermedia nella migliore corrispondenza. Ecco perché questo strato Basis sul web è quasi insostituibile — non puoi prevedere il dispositivo dell'utente prima della pubblicazione.
Flusso di upload a confronto: tradizionale vs formati GPU
Per finire, fissiamo la differenza in un diagramma di flusso.
PNG/JPG tradizionale:
File PNG ──download──> memoria CPU ──decodifica CPU (lenta)──> blocco pixel RGBA ──upload (grande, lento)──> VRAM (87 MB)
KTX2 + Basis:
File KTX2 ──download──> memoria CPU ──transcodifica runtime (veloce)──> formato a blocchi GPU ──upload (piccolo, veloce)──> VRAM (11 MB)
Quest'ultimo omette il grande passo di «decodifica CPU per pixel», e i dati caricati sono di un ordine di grandezza inferiori. Primo frame più veloce, meno VRAM — questo è il valore centrale dell'approccio.
Prossimo passo
La teoria è fatta; il prossimo articolo è pratica. Comprimeremo le texture in KTX2 con toktx e gltf-transform, le caricheremo in Three.js / Babylon.js e tratteremo come scegliere ETC1S vs UASTC e come regolare i parametri di compressione.