Tu peux donner des exemples d'utilisations possibles qui donneraient envie d'utiliser des interfaces ? (hormis l'exemple du noise multi-formes, que je trouve sympa mais p'têt un peu limité)
Il y a deux choses dans mon article: 1) Les functions pointers et 2) les closures.
Pour
1), c'est le cas le plus conventionel:
A chaque fois que tu as du code que tu veux pouvoir moduler en compilant des version plus optimales, ou tu veux modifier le comportement d'un code.
Je prends par exemple le cas du calcul du filtering d'une shadow map: tu peux vouloir utiliser en bout de chaîne du PCF ou du VSM. Dans le cas classique, tu es obligé de faire de la tambouille avec des includes/defines (un peu comme ce qu'explique u2) pour inliner du code:
Typiquement, tu as d'habitude un code suivant:
void BigFunction() {
...
// ... Beaucoup de code
// USE_PCF est une macro: On peut changer le code
// a l'interieur de BigFunction, a condition de faire une double compilation
// de ce fichier en settant la macro USE_PCF
float fliterFactor;
if (USE_PCF)
fliterFactor = FilterPCF(pos, depth);
else
fliterFactor = FilterVSM(pos, depth);
// ... Beaucoup de code
}
Mais tu peux faire ça de manière plus élégante avec les functions pointers (en fait, la c'est même plus, puisque tu peux utiliser tout la richesse des interfaces/classes), sans a avoir bidouiller ton code avec des macros:
void BigFunction(IShadowFilter filter ) {
...
// ... Beaucoup de code
// Utilisation de l'interface pour effectuer le sous calcul.
float fliterFactor = filter.Compute(pos, depth);
// ... Beaucoup de code
}
Tu as d'autres cas d'utilisations: Par exemple, du raymarching avec des fonctions iso interchangeables. L'algo de raymarching, en prenant en compte toutes ses composantes (raymarching, calcul de normale, calcul d'AO...etc.) est très simple mais il devient tout de suite un peu laborieux si tu veux changer la fonction iso facilement (qui se retrouve utilise a plusieurs endroits, pour le calcul du raymarching, pour le calcul de la normale...etc.). Avec les interfaces/classes, tu peux arriver a rendre ton code un peu plus pluggable.
Pour
2), le cas d'utilisation se limite pour ma part a une chose: Lorsque tu veux pouvoir chaîner des appels d'interfaces (A -> B -> C), tout en rendant la chose configurable de l'exterieur (Dans le cas a A->B->C, tu ne veux pas a avoir passer l'instance C par paramètre a A pour qu'il la fasse transiter a B...). C'est le cas de la composition de noise dans mon exemple.
La solution classique serait de définir une variable d'instance de ces classes, mais HLSL n'autorise malheureusement pas les interfaces comme membres d'une classes, donc je suis passé par ce hack de closure qui permet de plugger le code a plusieurs etages (dans l'exemple precedant, c'est comme si IShadowFilter voulait aussi utiliser un code interchangeable, sans que tu ai a passer le code via les parametres de la fonction BigFunction, je sais pas si tu me comprends la!)
Note: Derrière le nom "closure" se cache à la fois les "full closures" qui peuvent sauvegarder l'environnement quelque part pour être appelée quand celui-ci à été détruit, et un "static link" qui permet à une classe d'accéder à cet environnement avec une durée limitée.
Ici c'est un lien statique.
En fait, pas exactement, car on peut faire un peu des deux.

J'ai par exemple utilisé ce code la:
static INoise New(INoise from) {
class LocalNoise : AbsNoise { INoise Next() { return from; } } noise;
return noise;
}
La variable from est locale a New (on pourrait rajouter aussi des variables internes dans New) mais l'instance de INoise a une duree de vie en dehors de la fonction New.
Mais c'est simplement parceque derrière, comme le code d'un shader n'est au final qu'inliné par le compilo, ça revient a la fin a une sorte de "static link"!