Les textures, ce gouffre à mémoire vidéo
Dans l’article précédent, nous avions réduit de moitié le nombre de sommets, le modèle était un peu plus léger, mais pas encore une brindille – car le véritable poids lourd est toujours là : la texture. Sur un modèle PBR, la texture représente souvent plus de 80 % du volume, et c’est elle qui gonfle le plus dans la mémoire vidéo.
Cet article s’attaque spécifiquement au problème du « gouffre à VRAM » des textures. Nous allons voir trois choses : pourquoi PNG/JPG sont intrinsèquement problématiques pour le GPU ; à quoi ressemblent les formats de texture natifs du GPU et pourquoi on ne peut pas les utiliser directement ; et comment la combinaison Basis Universal + KTX2 permet de tout concilier.
Petit rappel : pourquoi le JPG fait exploser la VRAM
Dans l’article précédent, nous avions donné une formule :
Occupation VRAM = largeur × hauteur × 4 octets (RGBA) × 1,333 (avec mipmaps)
Une texture de 4096×4096, qu’elle soit un JPG de 1,5 Mo sur le disque ou un PNG de 8 Mo, occupe environ 87 Mo dans la VRAM. Une seule raison : le GPU ne reconnaît pas le JPG/PNG.
L’unité d’échantillonnage de texture (texture sampler) du GPU ne comprend qu’une chose : étant donné une coordonnée UV, lire la couleur dans un bloc de pixels de taille fixe. Elle exige que la texture soit en mémoire vidéo sous forme de « pixels bruts dépliés ». Le navigateur doit donc d’abord décompresser complètement le JPG en pixels RGBA via le CPU, avant de tout envoyer en bloc dans le GPU.
Ce processus a trois inconvénients majeurs :
- Explosion de la VRAM : les pixels bruts décompressés occupent énormément de place, 87 Mo n’est pas une exagération, c’est un calcul.
- Blocage du chargement : le transfert d’un gros bloc de pixels de la mémoire CPU vers la mémoire GPU est lent et bloque le rendu de la première image.
- Coût de décompression CPU : la décompression d’une grande image prend du temps, surtout sur mobile.
Pour reprendre la métaphore de l’éponge de l’article précédent : le PNG/JPG est comme une éponge compressée, facile à transporter ; une fois sur le GPU, l’éponge absorbe l’eau et reprend sa taille d’origine. Le téléchargement est plus rapide, mais la VRAM n’est pas économisée.
Les formats de texture natifs du GPU : déjà compressés dans la mémoire vidéo
Puisque le GPU n’accepte pas les PNG compressés, ne pourrait-on pas garder la texture compressée aussi dans la VRAM ? Le GPU décompresserait en temps réel un petit bloc de pixels lors de l’échantillonnage, sans surcoût.
C’est exactement ce que font les formats de texture natifs du GPU. Familles représentatives :
| Famille | Nom complet | Plateformes principales | Caractéristiques |
|---|---|---|---|
| BC1-7 | Block Compression | Desktop (PC, Mac) | Ancien, compression par blocs 4×4 |
| ETC1/2 | Ericsson Texture Compression | Mobile (Android, anciens iOS) | Ancien standard mobile |
| ASTC | Adaptive Scalable Texture Compression | Mobile/VR (nouveaux appareils) | Flexible, meilleure qualité, réglable bloc par bloc |
| PVRTC | PowerVR | Anciens iOS | Progressivement remplacé par ASTC |
Point commun de ces formats : la texture est stockée compressée par petits blocs de 4×4 pixels, et le GPU décode à la demande un seul bloc lors de l’échantillonnage. Il ne décode pas un pixel isolé mais un bloc entier. L’avantage est que l’occupation VRAM est réduite dans un rapport fixe, quel que soit le contenu.
Comparaison :
| PNG/JPG (traditionnel) | Format natif GPU | |
|---|---|---|
| Taille disque | Petite (surtout JPG) | Moyenne (compression par bloc, débit constant) |
| Occupation VRAM | Grande (décompressé en pixels bruts) | Petite (compressé par bloc, permanent) |
| Transfert vers GPU | Lent (décompression CPU + gros transfert) | Rapide (copie directe, pas de décompression) |
| Vitesse d’échantillonnage | Rapide (déjà en pixels bruts) | Rapide (décodage matériel en temps réel) |
Les formats GPU semblent être la solution parfaite. Alors pourquoi ne peut-on pas les utiliser directement ?
Le problème : différents appareils reconnaissent différents formats
C’est là le plus gros écueil des formats de texture GPU : la fragmentation.
- Les PC de bureau reconnaissent BC1-7, pas ASTC
- Les téléphones Android reconnaissent ETC2/ASTC, la plupart ne reconnaissent pas BC
- iOS (A7 et après) reconnaît ASTC, les anciens modèles reconnaissent PVRTC
- WebGPU/WebGL utilisent la même capacité matérielle sous-jacente
Pour qu’une texture « existe dans un format natif GPU sur tous les appareils », il faudrait en préparer une version pour chaque plateforme. Un produit destiné au desktop + Android + iOS nécessite trois versions de la même texture : BC + ETC2/ASTC. Le volume triple, le travail aussi.
Pire encore, sur le Web, vous ne savez pas quel appareil l’utilisateur utilise. Générer tous les formats à l’avance n’est pas réaliste, et les détecter à l’exécution est trop tard.
Basis Universal : un encodage, partout, transcodé
Basis Universal (abrégé Basis) a été créé pour résoudre cette fragmentation. Son idée tient en une phrase :
Encoder d’abord la texture dans un « format intermédiaire », puis la transcoder à l’exécution dans le format natif correspondant en fonction des capacités GPU de l’appareil.
Processus de transcodage (schéma) :
Texture source (PNG/JPG)
│ Encodage hors ligne unique (lent, fait une fois)
▼
Format intermédiaire Basis (ETC1S ou UASTC)
│ Empaqueté dans un conteneur KTX2
▼
Publication sur le Web ──┬── GPU desktop ──→ transcodage à l’exécution → BC1/3/7
├── Android ───→ transcodage à l’exécution → ETC2
└── iOS/VR ────→ transcodage à l’exécution → ASTC
Points clés :
- L’encodage hors ligne n’est fait qu’une fois, on obtient une représentation intermédiaire compacte
- Le transcodage à l’exécution est extrêmement rapide (pur calcul, quelques millisecondes), et il transfère des blocs, pas des pixels un par un
- Après transcodage, ce qui est envoyé dans la VRAM est le véritable format natif GPU, l’occupation VRAM est calculée selon la compression par bloc, identique à celle d’un format natif
Basis propose deux modes d’encodage intermédiaire, que nous détaillerons dans le prochain article, mais retenons leurs noms :
- ETC1S : taux de compression très élevé, idéal pour les textures de couleur diffuse/albedo
- UASTC : qualité supérieure, adapté aux textures sensibles à la précision comme les normales
KTX2 : le conteneur standard pour les textures GPU
Il reste un problème d’ingénierie : où mettre les données Basis encodées, comment les marquer, comment les lier à glTF ? La réponse est KTX2.
KTX2 (Khronos Texture 2) n’est pas un nouveau format d’image, mais un format conteneur – comme .zip ne se soucie pas de ce qu’il contient (documents ou images), KTX2 se charge de regrouper les données de texture GPU (y compris celles encodées en Basis) dans une structure standard, avec des métadonnées (format, niveaux de mipmap, espace colorimétrique, etc.).
Dans glTF, KTX2 est intégré via l’extension KHR_texture_basisu : la texture n’est plus un fichier PNG, mais un fichier KTX2 contenant des données Basis. Au chargement, le moteur détecte les capacités de l’appareil et transcode en BC/ETC/ASTC correspondant.
Clarifions les relations entre ces trois termes :
| Nom | Rôle | Analogue |
|---|---|---|
| Basis Universal | Algorithme de compression | Un « algorithme de compression » |
| KTX2 | Format conteneur | Une « boîte d’emballage » |
| KHR_texture_basisu | Extension glTF | Une « étiquette » |
Un fichier KTX2 peut contenir soit des données Basis (multiplateforme), soit un format natif (par exemple, du BC7 directement). Sur le Web, 99 % du temps, il contient du Basis, car nous voulons « un encodage, partout, transcodé ».
Exemple VRAM : comparaison d’une texture 4096
Reprenons la formule précédente et les formats GPU, et regardons l’occupation réelle d’une texture RGBA 4096×4096 selon différentes solutions :
| Solution | Taille disque | Occupation VRAM (avec mipmaps) | Vitesse de transfert | Multiplateforme |
|---|---|---|---|---|
| PNG | ~8 Mo | ~87 Mo | Lente (décompression) | ✅ |
| JPG | ~1,5 Mo | ~87 Mo | Lente (décompression) | ✅ |
| WebP | ~2 Mo | ~87 Mo | Lente (décompression) | ✅ |
| KTX2 (ETC1S) | ~2-3 Mo | ~11-14 Mo | Rapide | ✅ (transcodage) |
| KTX2 (UASTC) | ~6-8 Mo | ~22 Mo | Rapide | ✅ (transcodage) |
Comment les chiffres VRAM sont calculés : la compression par bloc GPU se fait généralement à 4 bpp (bits par pixel) ou 8 bpp. 4096×4096 à 4 bpp donne environ 8 Mo, avec mipmaps multiplier par 1,333 ≈ 11 Mo. UASTC est généralement transcodé à 8 bpp, soit environ 22 Mo.
L’important n’est pas le chiffre exact d’une ligne, mais ces deux points :
- Les formats traditionnels (PNG/JPG/WebP) ont presque la même occupation VRAM – ce sont les pixels bruts décompressés, 87 Mo. Peu importe que le disque soit petit, la VRAM n’est pas économisée.
- KTX2 réduit la VRAM d’un facteur 4 à 8, et la taille disque n’est pas pénalisante non plus.
C’est pourquoi la VR, le Web mobile, etc., adoptent presque obligatoirement KTX2 : combien de textures de 87 Mo peut-on mettre sur un téléphone avec 2 Go de VRAM ? Avec 11 Mo, on peut en mettre 7.
Matrice de support des plateformes : quels GPU reconnaissent quels formats
Bien que Basis nous protège des détails, comprendre la cartographie sous-jacente aide au dépannage. Voici le support des formats natifs sur les appareils courants :
| Plateforme / Appareil | BC1-7 | ETC2 | ASTC | PVRTC |
|---|---|---|---|---|
| PC desktop (D3D11/12, Vulkan, WebGPU) | ✅ | ❌ | Partiel (nouveaux GPU) | ❌ |
| macOS (Metal) | ✅ (nouveaux modèles) | ❌ | ✅ | ❌ |
| Android (principal) | ❌ | ✅ | ✅ | ❌ |
| iOS (A8+) | ❌ | ✅ | ✅ | ✅ (anciens modèles) |
| WebGL 2 | dépend des extensions | ✅ | Partiel | ❌ |
| WebGPU | ✅ (desktop) | ✅ | ✅ (selon appareil) | ❌ |
Basis détecte ces capacités à l’exécution et transforme le même encodage intermédiaire en celui qui convient le mieux. C’est pourquoi cette couche Basis est presque irremplaçable sur le Web – vous ne pouvez pas connaître à l’avance l’appareil de l’utilisateur.
Comparaison des flux de chargement : traditionnel vs format GPU
Terminons par un schéma pour fixer la différence.
Traditionnel PNG/JPG :
Fichier PNG ──téléchargement──> RAM CPU ──décompression CPU (lente)──> bloc de pixels RGBA ──transfert (gros, lent)──> VRAM (87 Mo)
KTX2 + Basis :
Fichier KTX2 ──téléchargement──> RAM CPU ──transcodage à l’exécution (rapide)──> format bloc GPU ──transfert (petit, rapide)──> VRAM (11 Mo)
La seconde solution supprime la grosse étape de « décompression pixel par pixel par le CPU », et la quantité de données transférées est réduite d’un ordre de grandeur. Le rendu de la première image est plus rapide et la VRAM économisée, c’est la valeur fondamentale de cette approche.
Prochaine étape
La théorie est claire. Dans le prochain article, nous passerons à la pratique. Nous utiliserons toktx, gltf-transform pour compresser réellement des textures en KTX2, les charger dans Three.js / Babylon.js, et discuterons du choix entre ETC1S et UASTC, ainsi que des paramètres de compression.