2017-09-27 13 views
2

Je suis en train de développer un moteur 3D conçu pour prendre en charge l'implémentation d'une API graphique donnée. Je voudrais avoir vos commentaires sur la façon dont je prévois de gérer les fichiers shader:Gestion des fichiers de shader de l'API Multi-Rendering Engine

Je pensais à la création d'une structure qui contient 3 variables de chaîne, le répertoire et le nom de fichier (à la fois vertex et fragment), quelque chose comme ceci:

class ShaderFile : public SerializableAsset 
{ 
    std::string nameID; //Identifier 
    std::string directory; 
    std::string vertexName; 
    std::string fragmentName; 
}; 

L'utilisateur pourrait définir ces variables dans l'éditeur. Ensuite, mon moteur chargeait les fichiers shader comme:

void createShader(RenderAPI api) 
{ 
    ShaderFile shaderFile //Get it from some place 
    std::string vertexPath = shaderFile.directory + shader.vertexName + api.name + api.extension; 
    std::string fragmentPath = shaderFile.directory + shader.fragmentName + api.name + api.extension; 
    //Create shader... 
} 

qui créeraient quelque chose comme: projet/Actif/Shaders/standardVulkan.spv. Est-ce que je pense dans la bonne direction ou est-ce une approche complètement idiote? Tous les commentaires

Répondre

1

Il est une idée intéressante et nous avons effectivement fait exactement cela, mais nous avons découvert des choses sur le chemin qui ne sont pas faciles à traiter:

Si vous jetez un regard plus profond sur l'API Shader de, bien que ils sont proches d'offrir les mêmes capacités sur papier, ils ne supportent souvent pas les fonctionnalités de la même manière et doivent être gérés différemment. Par extension, les shaders le font aussi. L'implémentation du pilote est ici essentielle et diffère parfois considérablement lorsqu'il s'agit de gérer l'état interne (synchronisation et gestion de la mémoire tampon).

Flexibilité

Vous constaterez que OpenGL est flexible dans la façon dont il gère les attributs et les uniformes, où DirectX est plus ciblée sur la réduction telecharge sur le matériel en les liant dans des blocs selon vos configurations de renderpass, généralement sur une base par objet/par image/par passe etc. Bien que vous puissiez également le faire en créant des blocs minuscules, cela donnerait évidemment des performances différentes.

De toute évidence, il existe plusieurs façons de faire des liaisons, ou même des objets de mémoire tampon, ou des shaders, même dans une seule API. De plus, l'obtention de noms de variables de shader et de points de liaison n'est pas très flexible pour interroger DirectX et certains paramètres doivent être définis à partir du code. Dans Vulkan, les attributs de shader de liaison et les uniformes sont encore plus généralisés: vous pouvez configurer complètement les points de liaison comme vous le souhaitez.

Versioning

Un autre sujet est tout ce qui a à voir avec GLSL/HLSL versioning d'ombrage: Vous devrez peut-être écrire différents shaders pour différentes capacités matérielles qui prennent en charge les modèles de shaders inférieurs. Si vous voulez écrire des shaders uniques et n'adoptez pas l'approche uber-shader (et aussi dans une large mesure SI vous utilisez cette approche) cela peut devenir compliqué si elle est trop étroite dans votre conception, et compte tenu du nombre de les permutations pourraient être irréalistes.

Extensions

OpenGL et extensions Vulkan peuvent être 'interrogés' à l'intérieur du shader, tandis que d'autres tels que la mise en DirectX API ont besoin de ce côté du code. Pourtant, dans la même capacité de calcul, vous pouvez avoir des extensions qui ne fonctionnent que sur NVidia, ou sont ARB approuvé mais pas CORE, etc. C'est vraiment très désordonné et dans la plupart des cas application spécifique.

Obsolescence

Une partie considérable d'API sont déconseillés se tout le temps. Cela peut poser problème si votre moteur s'attend à ce que ces fonctionnalités restent en place, en particulier si vous souhaitez gérer plusieurs API prenant en charge cette fonctionnalité.

Compilation & Mise en cache

La plupart des API en prennent désormais en charge une certaine forme de compilation en mode hors connexion qui peut être chargé plus tard. La compilation prend beaucoup de temps, donc la mise en cache est logique. Étant donné que le code du Hardware Shader est compilé uniquement pour le matériel que vous possédez, vous devez effectuer cet exercice pour chaque plate-forme sur laquelle le code doit s'exécuter, soit la première fois que l'application a besoin du shader, soit d'une manière intelligente. pipeline. Votre nom de fichier sera dans ce cas remplacé par un hachage afin que le shader puisse être récupéré du cache. Mais cela signifie que le cache a besoin d'un horodatage pour pouvoir détecter de nouvelles versions du shader source, car si la source du shader doit changer, alors l'entrée du cache doit être reconstruite. etc .. :)

Longue histoire courte

Si vous visez une flexibilité maximale quelle que soit l'API, vous finiriez ajouter une couche inutile dans votre moteur que dans le meilleur des cas doublons simplement les appels sous-jacents . Si vous visez une API généralisée, vous serez rapidement pris au piège dans l'histoire de la version qui n'est pas totalement synchronisée entre les différentes API en termes d'extensions, de dépréciation et de support de l'implémentation du pilote.