Pasar una lista de valores para fragmentar el sombreador

Deseo enviar una lista de valores a un sombreador de fragmentos. Es una lista posiblemente grande (un par de miles de elementos) de flotadores de precisión simple. El sombreador de fragmentos necesita acceso aleatorio a esta lista y quiero actualizar los valores de la CPU en cada cuadro.

Estoy considerando mis opciones sobre cómo se podría hacer esto:

  1. Como una variable uniforme del tipo de matriz (“flotación uniforme x [10];”). Pero parece que hay límites aquí, en mi GPU enviar más de unos cientos de valores es muy lento y también tendría que codificar el límite superior en el sombreador cuando preferiría cambiar eso en tiempo de ejecución.

  2. Como textura con altura 1 y ancho de mi lista, actualice los datos usando glCopyTexSubImage2D.

  3. ¿Otros metodos? No he seguido todos los cambios en la especificación GL recientemente, quizás haya algún otro método específicamente diseñado para este propósito?

Actualmente hay 4 formas de hacerlo: texturas estándar 1D, texturas de búfer, búferes uniformes y búferes de almacenamiento de shader.

Texturas 1D

Con este método, usa glTex(Sub)Image1D para rellenar una textura 1D con sus datos. Dado que sus datos son solo una serie de flotadores, su formato de imagen debe ser GL_R32F . A continuación, acceda a él en el sombreador con una simple llamada texelFetch . texelFetch toma las coordenadas de texel (de ahí el nombre), y apaga todos los filtros. Entonces obtienes exactamente un téxel.

Nota: texelFetch es 3.0+. Si desea utilizar versiones GL anteriores, deberá pasar el tamaño al sombreador y normalizar las coordenadas de textura manualmente.

Las principales ventajas aquí son la compatibilidad y la compacidad. Esto funcionará en el hardware GL 2.1 (usando la notación). Y no tiene que usar los formatos GL_R32F ; podrías usar GL_R16F half-floats. O GL_R8 si sus datos son razonables para un byte normalizado. El tamaño puede significar mucho para el rendimiento general.

La principal desventaja es la limitación de tamaño. Está limitado a tener una textura 1D del tamaño máximo de la textura. En el hardware GL 3.x-class, esto será alrededor de 8.192, pero se garantiza que no será inferior a 4.096.

Objetos de memoria intermedia uniformes

La forma en que esto funciona es que declaras un bloque uniforme en tu sombreador:

 layout(std140) uniform MyBlock { float myDataArray[size]; }; 

A continuación, accede a esos datos en el sombreador como una matriz.

De vuelta en el código C / C ++ / etc, usted crea un objeto buffer y lo llena con datos de coma flotante. Luego, puede asociar ese objeto de memoria intermedia con el bloque uniforme MyBlock . Más detalles se pueden encontrar aquí.

Las principales ventajas de esta técnica son la velocidad y la semántica. La velocidad se debe a la forma en que las implementaciones tratan los buffers uniformes en comparación con las texturas. Las recuperaciones de texturas son accesos globales a la memoria. Los accesos de buffer uniformes generalmente no son; los datos uniformes del búfer generalmente se cargan en el sombreador cuando el sombreador se inicializa después de su uso en el renderizado. A partir de ahí, es un acceso local, que es mucho más rápido.

Semánticamente, esto es mejor porque no es solo una matriz plana. Para sus necesidades específicas, si todo lo que necesita es un float[] , eso no importa. Pero si tiene una estructura de datos más compleja, la semántica puede ser importante. Por ejemplo, considere una variedad de luces. Las luces tienen una posición y un color. Si usa una textura, su código para obtener la posición y el color de una luz en particular se ve así:

 vec4 position = texelFetch(myDataArray, 2*index); vec4 color = texelFetch(myDataArray, 2*index + 1); 

Con buffers uniformes, se ve como cualquier otro acceso uniforme. Ha nombrado miembros que se pueden llamar position y color . Entonces toda la información semántica está allí; es más fácil entender lo que está pasando.

Hay limitaciones de tamaño para esto también. OpenGL requiere que las implementaciones proporcionen al menos 16,384 bytes para el tamaño máximo de bloques uniformes. Lo que significa que, para las matrices flotantes, obtienes solo 4.096 elementos. Tenga en cuenta nuevamente que este es el mínimo requerido de las implementaciones; algunos hardware pueden ofrecer búferes mucho más grandes. AMD proporciona 65.536 en su hardware de clase DX10, por ejemplo.

Texturas de búfer

Estas son una especie de “textura súper 1D”. De hecho, le permiten acceder a un objeto de memoria intermedia desde una unidad de textura . Aunque son unidimensionales, no son texturas 1D.

Solo puedes usarlos desde GL 3.0 o superior. Y solo puede acceder a ellos a través de la función texelFetch .

La principal ventaja aquí es el tamaño. Las texturas del búfer generalmente pueden ser bastante gigantescas. Si bien la especificación es generalmente conservadora, obligando al menos 65,536 bytes para texturas de búfer, la mayoría de las implementaciones de GL les permiten extenderse en mega bytes de tamaño. De hecho, generalmente el tamaño máximo está limitado por la memoria GPU disponible, no por los límites de hardware.

Además, las texturas de búfer se almacenan en objetos de búfer, no en los objetos de textura más opacos como las texturas 1D. Esto significa que puede usar algunas técnicas de transmisión de objetos de búfer para actualizarlas.

La principal desventaja aquí es el rendimiento, al igual que con las texturas 1D. Las texturas del búfer probablemente no serán más lentas que las texturas 1D, pero tampoco serán tan rápidas como las UBO. Si solo estás sacando un flotador de ellos, no debería ser una preocupación. Pero si extrae muchos datos de ellos, considere usar un UBO en su lugar.

Objetos del búfer de almacenamiento Shader

OpenGL 4.3 proporciona otra forma de manejar esto: almacenamientos intermedios de almacenamiento de sombreado . Se parecen mucho a los buffers uniformes; los especifica usando una syntax casi idéntica a la de los bloques uniformes. La principal diferencia es que puedes escribirles. Obviamente, eso no es útil para sus necesidades, pero existen otras diferencias.

Los búfers de almacenamiento Shader son, conceptualmente hablando, una forma alternativa de textura buffer. Por lo tanto, los límites de tamaño para los almacenamientos intermedios de almacenamiento de sombreado son mucho mayores que para los almacenamientos intermedios uniformes. El mínimo de OpenGL para el tamaño máximo de UBO es de 16 KB. El mínimo de OpenGL para el tamaño máximo de SSBO es de 16 MB . Entonces, si tiene el hardware, es una alternativa interesante a los UBO.

Solo asegúrate de declararlos como de readonly , ya que no les estás escribiendo.

La desventaja potencial aquí es el rendimiento nuevamente, en relación con las UBO. Los SSBO funcionan como una operación de carga / almacenamiento de imagen a través de texturas de búfer. Básicamente, es (muy agradable) azúcar sintáctico alrededor de un tipo de imagen imageBuffer . Como tal, las lecturas de estos probablemente se realizarán a la velocidad de las lecturas de un readonly imageBuffer .

Ya sea que la lectura a través de la carga / almacenamiento de imágenes a través de imágenes de búfer sea más rápida o más lenta que las texturas de búfer, no está claro en este momento.

Otro posible problema es que debe cumplir con las reglas para el acceso a la memoria no sincrónico . Estos son complejos y pueden hacerte tropezar muy fácilmente.

Esto suena como un buen caso de uso para objetos de buffer de textura . Estos no tienen mucho que ver con las texturas regulares y básicamente te permiten acceder a la memoria de un objeto buffer en un sombreador como una matriz lineal simple. Son similares a las texturas 1D, pero no se filtran y solo se accede mediante un índice entero, que suena como lo que necesita hacer cuando lo llama una lista de valores. Y también admiten tamaños mucho más grandes que las texturas 1D. Para actualizarlo, puede utilizar los métodos estándar de objetos de almacenamiento intermedio ( glBufferData , glMapBuffer , …).

Pero, por otro lado, requieren hardware GL3 / DX10 para usar e incluso se han convertido en núcleo en OpenGL 3.1, creo. Si su hardware / controlador no lo admite, entonces su segunda solución sería el método de elección, pero mejor utilice una textura 1D que una anchura x 1 textura 2D). En este caso, también puede usar una textura 2D no plana y algo de magia índice para admitir listas mayores que el tamaño máximo de la textura.

Pero los amortiguadores de texturas son la combinación perfecta para su problema, creo. Para obtener una idea más exacta, también puede consultar la especificación de extensión correspondiente.

EDITAR: en respuesta al comentario de Nicol sobre los objetos de buffer uniformes , también puede buscar aquí una pequeña comparación de los dos. Todavía me inclino por TBO, pero realmente no puedo razonar por qué, solo porque lo veo mejor conceptualmente. Pero tal vez Nicol puede proporcionarle a un inversionista más información sobre el asunto.

Una forma sería usar matrices uniformes como las que mencionas. Otra forma de hacerlo es usar una “textura” 1D. Busque GL_TEXTURE_1D y glTexImage1D. Yo personalmente prefiero esta forma, ya que no es necesario codificar el tamaño de la matriz en el código de sombreado como dijiste, y OpenGL ya tiene funciones incorporadas para cargar / acceder a datos 1D en la GPU.

Yo diría que probablemente no sea el número 1 … usted tiene un número limitado de registros para los uniformes del sombreado, que varía según la tarjeta. Puede consultar GL_MAX_FRAGMENT_UNIFORM_COMPONENTS para conocer su límite. En las tarjetas más nuevas se encuentra con miles, por ejemplo, parece que Quadro FX 5500 tiene 2048. (http://www.nvnews.net/vbulletin/showthread.php?t=85925). Depende del hardware en el que desee que se ejecute y de los demás uniformes que desee enviar al sombreador.

El número 2 podría funcionar según sus requisitos. Perdón por la vaguedad que hay aquí, espero que alguien más pueda darte una respuesta más precisa, pero debes ser explícito en la cantidad de llamadas de textura que haces en las tarjetas de modelo de sombreador antiguas. También depende de la cantidad de lecturas de textura que desee hacer por fragmento; probablemente no desee intentar leer 1000 elementos por fragmento, de nuevo, según el modelo de sombreado y los requisitos de rendimiento. Puede empaquetar valores en RGBA de una textura, lo que le da 4 lecturas por llamada de textura, pero con el acceso aleatorio como requisito, es posible que esto no lo ayude.

No estoy seguro acerca del número 3, pero sugiero que tal vez busquen UAV (vistas de acceso desordenadas) aunque creo que solo es DirectX, sin un equivalente decente de OpenGL. Creo que hay una extensión nVidia para OpenGL, pero de nuevo te limitas a una especificación mínima bastante estricta.

Es poco probable que pasar miles de elementos de datos a su sombreador de fragmentos sea la mejor solución para su problema. ¿Tal vez si dio más detalles sobre lo que está tratando de lograr, puede obtener sugerencias alternativas?