A menudo me encuentro copiando código entre varios sombreadores. Esto incluye ciertos cálculos o datos compartidos entre todos los sombreadores en una sola tubería, y cálculos comunes que todos mis sombreadores de vértices necesitan (o cualquier otra etapa).
Por supuesto, esa es una práctica horrible: si necesito cambiar el código en cualquier lugar, necesito asegurarme de cambiarlo en cualquier otro lugar.
¿Existe una mejor práctica aceptada para mantener SECO ? ¿Las personas simplemente anteponen un único archivo común a todos sus sombreadores? ¿Escriben su propio preprocesador rudimentario de estilo C que analiza las #include
directivas? Si hay patrones aceptados en la industria, me gustaría seguirlos.
Respuestas:
Hay muchos enfoques, pero ninguno es perfecto.
Es posible compartir código mediante la
glAttachShader
combinación de sombreadores, pero esto no permite compartir cosas como declaraciones de estructura o#define
constantes -d. Funciona para compartir funciones.A algunas personas les gusta usar el conjunto de cadenas que se pasan
glShaderSource
como una forma de anteponer definiciones comunes antes de su código, pero esto tiene algunas desventajas:#version
, debido a la siguiente declaración en la especificación GLSL:Debido a esta declaración,
glShaderSource
no se puede usar para anteponer texto antes de las#version
declaraciones. Esto significa que la#version
línea debe incluirse en susglShaderSource
argumentos, lo que significa que su interfaz de compilador GLSL necesita saber de alguna manera qué versión de GLSL se espera que se use. Además, al no especificar a#version
, el compilador GLSL usará GLSL versión 1.10 de forma predeterminada. Si desea permitir que los autores de sombreadores especifiquen#version
dentro del script de una manera estándar, entonces debe insertar#include
-s de alguna manera después de la#version
declaración. Esto podría hacerse analizando explícitamente el sombreador GLSL para encontrar la#version
cadena (si está presente) y hacer sus inclusiones después de ella, pero teniendo acceso a un#include
La directiva podría ser preferible para controlar más fácilmente cuando esas inclusiones deben hacerse. Por otro lado, dado que GLSL ignora los comentarios antes de la#version
línea, puede agregar metadatos para incluirlos dentro de los comentarios en la parte superior de su archivo (qué asco).La pregunta ahora es: ¿hay una solución estándar para
#include
, o necesita rodar su propia extensión de preprocesador?Existe la
GL_ARB_shading_language_include
extensión, pero tiene algunos inconvenientes:"/buffers.glsl"
(como se usa en#include "/buffers.glsl"
) corresponde al contenido del archivobuffer.glsl
(que ha cargado previamente)."/"
, como las rutas absolutas de estilo Linux. Esta notación generalmente no es familiar para los programadores de C, y significa que no puede especificar rutas relativas.Un diseño común es implementar su propio
#include
mecanismo, pero esto puede ser complicado ya que también necesita analizar (y evaluar) otras instrucciones del preprocesador#if
para manejar adecuadamente la compilación condicional (como los protectores de encabezado).Si implementa el suyo propio
#include
, también tiene algunas libertades en cómo desea implementarlo:GL_ARB_shading_language_include
).Como simplificación, puede insertar automáticamente protectores de encabezado para cada inclusión en su capa de preprocesamiento, para que su capa de procesador se vea así:
(Gracias a Trent Reed por mostrarme la técnica anterior).
En conclusión , no existe una solución automática, estándar y simple. En una solución futura, podría usar alguna interfaz SPIR-V OpenGL, en cuyo caso el compilador GLSL a SPIR-V podría estar fuera de la API GL. Tener el compilador fuera del tiempo de ejecución de OpenGL simplifica enormemente la implementación de cosas como,
#include
ya que es un lugar más apropiado para interactuar con el sistema de archivos. Creo que el método generalizado actual es simplemente implementar un preprocesador personalizado que funcione de una manera con la que cualquier programador de C debería estar familiarizado.fuente
Por lo general, solo uso el hecho de que glShaderSource (...) acepta una matriz de cadenas como entrada.
Utilizo un archivo de definición de sombreador basado en json, que especifica cómo se compone un sombreador (o un programa para ser más correcto), y allí especifico que el preprocesador define que pueda necesitar, los uniformes que usa, el archivo de sombreadores de vértices / fragmentos, y todos los archivos adicionales de "dependencia". Estas son solo colecciones de funciones que se agregan a la fuente antes que la fuente del sombreador real.
Solo para agregar, AFAIK, el Unreal Engine 4 utiliza una directiva #include que se analiza y agrega todos los archivos relevantes, antes de la compilación, como sugería.
fuente
No creo que haya una convención común, pero si tuviera que adivinar, diría que casi todos implementan alguna forma simple de inclusión textual como un paso de preprocesamiento (una
#include
extensión), porque es muy fácil de hacer. asi que. (En JavaScript / WebGL, puede hacerlo con una expresión regular simple, por ejemplo). La ventaja de esto es que puede realizar el preprocesamiento en un paso fuera de línea para las compilaciones de "lanzamiento", cuando el código del sombreador ya no necesita ser cambiado.De hecho, una indicación de que este enfoque es común es el hecho de que una extensión ARB se introdujo para que:
GL_ARB_shading_language_include
. No estoy seguro de si esto se convirtió en una característica central en este momento o no, pero la extensión se escribió en OpenGL 3.2.fuente
Algunas personas ya han señalado que
glShaderSource
pueden tomar una serie de cadenas.Además de eso, en GLSL la compilación (
glShaderSource
,glCompileShader
) y el enlace (glAttachShader
,glLinkProgram
) del sombreador están separados.Lo he usado en algunos proyectos para dividir los sombreadores entre la parte específica y las partes comunes a la mayoría de los sombreadores, que luego se compila y comparte con todos los programas de sombreadores. Esto funciona y no es difícil de implementar: solo tiene que mantener una lista de dependencias.
Sin embargo, en términos de mantenibilidad, no estoy seguro de que sea una victoria. La observación fue la misma, intentemos factorizar. Si bien de hecho evita la repetición, la sobrecarga de la técnica se siente significativa. Además, el sombreador final es más difícil de extraer: no puede simplemente concatenar las fuentes del sombreador, ya que las declaraciones terminan en un orden que algunos compiladores rechazarán o se duplicarán. Por lo tanto, es más difícil hacer una prueba rápida de sombreado en una herramienta separada.
Al final, esta técnica aborda algunos problemas SECOS, pero está lejos de ser ideal.
En un tema secundario, no estoy seguro de si este enfoque tiene algún impacto en términos de tiempo de compilación; He leído que algunos controladores solo compilan realmente el programa de sombreado al vincular, pero no he medido.
fuente