(🔽 dans le style blog post donc 🔽)
Contraintes de couleur
Pour la suite je réfléchis et hésite beaucoup pour les contraintes. Je pensais à priori limiter l'espace de couleur à 12 bits (4 par composante). 4 bits c'est équivalent à la Sega System 16 (un peu ma référence), et ce qu'aurait dû être la Mega Drive (finalement 3 bits + shadow/highlight qui grattent un "bit" analogique supplémentaire). C'est facile car actuellement tous les pixels passent par la "master palette", vu qu'ils sont indexés, donc je peux la faire soit en 32 bits, soit 16 (4 bits rouge, vert, bleu et 4 alpha).


← exemples de dégradés limités par le nombre de bits par composante. En haut on a 3 (Mega Drive), au milieu 4 (System 16, ma proposition) et en bas 5 (Super Nintendo).
Le but c'est que les graphismes n'utilisent pas trop de dégradés "doux" et paraissent plus "dessinés à la main". D'ailleurs la limite de 16 couleurs par tile/sprite allait déjà dans ce sens, donc pas forcément nécessaire d'avoir les 2… mais on peut tricher en faisant plein de tiles avec de petites gradations (genre 16 tiles de 16 couleurs chacune pour avoir un dégradé 8 bits), sauf que c'est compliqué et que celui qui se fait chier à faire ça pour un écran aura ensuite quelque chose qui clashe avec un reste en couleurs limitées. En plus si le convertisseur permet des couleurs 32 bits il ne fera plus la postérisation à priori donc risque de gaspiller en empaquetant des couleurs très proches.
Je pensais aussi à utiliser un framebuffer 15 bits, histoire de permettre un bit supplémentaire pour les ombres / alpha blending comme la MD et la System 16, mais c'est probablement over-engineeré (si j'écris direct dans le buffer 24 bits du browser ça m'évite une conversion à la fin, mais ça permet de tricher en superposant plein de plans semi-transparents pour obtenir un pseudo 24 bits

).
Ajout des quadrilatères
L'autre truc c'est que je réfléchissais à inclure la possibilité de dessiner des polygones (quadrilatères) non-texturés comme la Sega Model 1, sans gouraud shading (car en couleurs 12 bits ce serait moche… à reconsidérer si on passe en 24, mais du coup le look trop lisse clasherait). L'idée serait d'aider à faire des trucs comme Starfox ou Virtua Racing. Mais il y a un certain nombre de questions avec ça : combien de polygones max ? (voir la sprite limit en bas du post) Ensuite, comment ça s'insère dans la chaîne de rendu ? (§ ci-dessous)
Chaîne de rendu et performances
Concernant la chaîne de rendu, j'hésite vraiment à maintenir le modèle de l'époque, où tu déclares un certain nombre de backgrounds / objets avec une priorité affectée, et le machin se débrouille pour les ordonner. L'autre possibilité c'est de la faire à la moderne, avec une liste de "commandes", genre "dessine un sprite", et l'ordre de dessin détermine le résultat final. C'est un peu plus intuitif, mais moins dans l'esprit, et on perd les avantages de pouvoir gérer les priorités manuellement (là il faudra sûrement une sprite list intermédiaire dans le programme utilisateur, qui va écrire les sprites dans l'ordre à la fin).
L'idée originale devait procéder comme suit, avec le z-testing activé (la valeur Z est la priorité du BG/sprite, entre 0 et 4) :
- Dessiner le backdrop (clear buffer d'une couleur quoi)
- Dessiner tous les sprites du plus proche (en avant) au plus loin
- Dessiner tous les BGs du plus proche au plus loin
- (Dans le design original les tiles devaient avoir un bit de priorité qui inverse la valeur Z pour cette tile de BG)
Ca permettrait de bénéficier du z-buffer pour ne dessiner qu'une seule fois chaque pixel de l'écran, en 3 commandes au GPU (drawcalls).

Ça ne permet pas la transparence, mais ce n'est pas grave, c'est pas très 16-bit de toute façon (et rarement beau si pas fait comme il faut

). Pour ça j'ai simplement rajouté 32 sprites qui supportent la transparence par addition + shadow, dessinés en dernier du plus loin au plus proche. Ils ne pourraient que blender entre eux et au-dessus de tous les BGs/sprites existants.
Mais après test, j'ai remarqué qu'en pratique, il ne sert à rien dans ce cas d'utiliser le z-buffer avec des sprites (un
discard dans le fragment shader désactive l'early depth test, et il va donc quand même rendre tous les pixels, juste ne pas les écrire s'ils sont derrière un pixel existant). De plus, l'alpha blending ne change virtuellement pas les performances*. Résultat ma contrainte ne sert à rien techniquement, et de ce que je comprends, il vaut autant tous dessiner les sprites de l'arrière à l'avant, avec un support de la transparence pour chacun. Par contre après ça devient la foire, et le look 16 bits il finit un peu aux toilettes…**
Avec ce nouveau modèle, la seule chose qu'on veut éviter c'est de switcher constamment entre le dessin d'un sprite ou d'un BG, car ce sont des shaders différents, et le switch est coûteux. En limitant à 4 BGs on "supprime" le problème (max 8 drawcalls). Mais il peut rester avantageux pour limiter l'usage de la transparence de ne pas interfolier les sprites et BGs (donc, dessiner tous les BGs d'un coup, puis tous les sprites, seulement s'ils sont au-dessus d'un pixel de BG/sprite précédent). On aurait alors les BGs qui peuvent être transparents entre eux, et les sprites aussi, au-dessus des BGs composités, mais on ne pourrait pas avoir un BG semi-transparent recouvrant un sprite. Pour éviter que les gens se servent alors des sprites pour tout, on limiterait la transparence aux 32 derniers sprites (et peut être à un seul BG désigné).
Pour comprendre l'importance de l'ordre de dessin, considérez ceci :

L'ordre de priorité est : BG1 (fond), Mario, BG2 (buisson). On dessine dans l'ordre suivant : [BGs opaques], [sprites opaques], [BG transparent], [sprites transparents].
Dans la scène de gauche, ça donne : BG1, Mario, BG2. Notez que grâce au Z-buffer, ça aurait aussi donné ce qu'on voulait même si Mario avait été dessiné avant le BG1, car au moment d'écrire les pixels du BG1, on aurait comparé leur valeur en Z, et on ne les aurait pas écrits car il y avait déjà un objet plus proche (valeur Z plus grande) sur l'écran à cet emplacement, conservant Mario intact.
Dans la scène de droite, Mario et le BG2 sont transparents. On dessine alors BG1 (opaque), BG2 (transparent), Mario (sprite transparent). Le BG2 va se mélanger comme souhaité avec BG1 lorsque dessiné, car il est en avant et dessiné par après. Ensuite vient le tour de Mario : comme il a une valeur de Z le mettant en arrière du BG2 (mais en avant du BG1), les pixels transparents de Mario ne seront écrits sur l'image qu'au-dessus des pixels du BG1, et non ceux du BG2, car BG2 est sensé obscurcir Mario ! Note qu'écrire les pixels de Mario dans ce cas n'aurait pas résolu le problème : il aurait vraiment fallu qu'on dessine le BG2 après Mario, ce qui implique d'ordonner précisément l'ordre de dessin, chose que les machines de l'époque ne faisaient pas.
A titre d'info, la Game Boy Advance aurait eu le même souci. Dans le premier cas si on avait défini BG1 et OBJ (Mario) comme "target" de la transparence et BG2 comme "source", et dans à droite si on avait défini BG1 comme "target" et BG2 et OBJ comme "source". Mais il est impossible d'avoir le rendu qu'on cherche (BG1 opaque, Mario transparent, BG2 transparent par-dessus).
Bref ça paraît pas trop mal, qu'en pensez-vous ? Le souci par contre c'est après où j'insère les quadrilatères dans la chaîne si je le fais, s'ils ont un shader séparé.
* Sauf si… je veux proposer une émulation en software de la machine pour les GPUs n'ayant pas OpenGL 2+. La transparence en soft est super coûteuse.
** Il ne faut pas oublier qu'à l'époque, la transparence ne se faisait pas par objet, mais entre "plans", et pas plus de deux "plans" ne pouvaient être mixés : par exemple on pouvait dire "le BG1 se mélange avec BG2", ou "les OBJets se mélangent avec le BG2" mais pas plus. Alors, si pour un pixel déterminé, le sprite "gagnant" (au-dessus, voire le premier remplissant la "case") était en mode transparent, alors il se voyait mixé avec le BG2, sinon on part sur le circuit de priorité qui va afficher soit l'un soit l'autre en fonction de leur priorité. Mais si 2 objets sont superposés, seul celui le plus à l'avant sera conservé et utilisé dans le calcul de transparence avec BG2. C'est pour ça qu'on ne pouvait pas faire n'importe quoi avec la transparence.
Limite de pixels par image
Je réfléchissais aussi à mettre une "sprite limit", qui ne peut plus être par ligne mais sur l'image entière, pour éviter de tuer les perfs en dessinant des sprites semi-transparents étirés sur l'écran entier. Je pensais à mettre un nombre max de pixels dessinables par frame, et les 2 premiers BGs ne compteraient pas dedans pour encourager les gens à s'en servir correctement (mais donner un avantage à ceux qui veulent désactiver les 2 derniers, 2x plus coûteux qu'un sprite plein écran, pour faire de la 3D avec un max de sprites/polys). En cas de dépassement, les primitives supplémentaires sont ignorées et un warning est affiché dans la console.