В OpenGL 4.3 появился давно ожидаемый новый тип буфера - Shader Storage Buffer Object (сокращенно SSBO), являющийся по существу массивом для произвольного чтения/записи в шейдере, исключающий необходимость использовать для этих целей текстурные буферы совместно с расширением Image Load Store. Вкратце об этом буфере написано в этой статье, здесь я привожу ее перевод с некоторыми дополнениями (взятыми из спецификации).
Shader Storage Buffer Object (SSBO) - это объект буфера (Buffer Object), который используется для хранения и извлечения данных в GLSL.
Этот тип буфера объявлен в расширении ARB_shader_storage_buffer_object которое вошло в ядро OpenGL 4.3.
SSBO во многом похож на Uniform Buffer Objects. В шейдере блоки определяются практически идентично блокам юниформ, с тем отличием что SSBO связано со своим точкам бинда, а UBO со своими.
Основные различия между ними:
Говоря о функциональности, SSBO можно рассматривать как более удобный интерфейс доступа к текстурным буферам (Buffer Textures) чем через предоставляемый Image Load Store.
Атомарные операции (Atomic operations).
Существуют специальные атомарные функции, которые могут быть применены к переменным буфера (и для разделяемых (shared) переменных в вычислительном шейдере). Эти функции принимают только целочисленные типы (uint и int), но они могут быть членами структур, массивов и векторов.
Все атомарные функции возвращают значение до выполнения операции.
Вместо "nint" может быть int или uint.
Список доступных атомарных функций:
Пару важных дополнений к указанной выше статье, взятые из спецификации:
1. Что происходит при попытке записи за пределы размера SSBO? В спецификации дан такой ответ: "Для SSBO используются те же правила что и для блоков юниформ, которые не предоставляют никаких проверок выхода за границы диапазона. Если активный блок юниформ не подкреплен буфером с достаточным объемом, то результат работы шейдера будет неопределенным и может привести к прерыванию работы GL".
2. В GLSL 430 появился новый квалификатор "std430", в отличии от старого "std140", у которого выравнивание по границе границе 16 байт, "std430" позволяет более плотно упаковывать данные, так для "float", "int", и "uint" выравнивание будет только на границу в 4 байта. Однако, "vec3" все так же выравнивается на границу в 16 байт.
Квалификтор "std430" может быть применим только для блоков SSBO.
3. Узнать максимально допустимые размеры SSBO для каждого типа шейдера можно воспользовавшись функцией "glGetInteger64v" с одним из параметров:
"void ShaderStorageBlockBinding(uint program, uint storageBlockIndex, uint storageBlockBinding);"
Или с использованием старых функций "BindBuffer, BufferData, BufferSubData, MapBuffer, UnmapBuffer, GetBufferSubData, GetBufferPointerv", указав в качестве назначения буфера (Target) константу:
В этом примере, при растеризации примитивов, в буфер записывается список координат фрагментов (x,y) и их цвет. Соответствующий фрагментный шейдер представлен ниже:
Код приложения:
Shader Storage Buffer Object (SSBO) - это объект буфера (Buffer Object), который используется для хранения и извлечения данных в GLSL.
Этот тип буфера объявлен в расширении ARB_shader_storage_buffer_object которое вошло в ядро OpenGL 4.3.
SSBO во многом похож на Uniform Buffer Objects. В шейдере блоки определяются практически идентично блокам юниформ, с тем отличием что SSBO связано со своим точкам бинда, а UBO со своими.
Основные различия между ними:
- SSBO обычно значительно больше. Максимальный размер буферов зависит от реализации, при этом наименьший максимальный размер для UBO составляет 16Кб, в то время как максимальный размер SSBO уже как минимум 16Мб.
- SSBO доступно для записи, даже атомарно, в то время как UBO является юниформой. SSBO имеет тот же тип доступа к памяти как и операции Image Load Store, потому для гарантированного чтения/записи нуждаются в соответствующих "барьерах" (memory barriers).
- У SSBO размер может варьироваться от 0 до размера буфера, в то время как размер UBO фиксирован. Это означает, что у вас в SSBO может быть массив произвольной длины. Фактический размер массива определяется размером прикрепленного буфера и может быть запрошен в шейдере через функцию "length".
Говоря о функциональности, SSBO можно рассматривать как более удобный интерфейс доступа к текстурным буферам (Buffer Textures) чем через предоставляемый Image Load Store.
Атомарные операции (Atomic operations).
Существуют специальные атомарные функции, которые могут быть применены к переменным буфера (и для разделяемых (shared) переменных в вычислительном шейдере). Эти функции принимают только целочисленные типы (uint и int), но они могут быть членами структур, массивов и векторов.
Все атомарные функции возвращают значение до выполнения операции.
Вместо "nint" может быть int или uint.
Список доступных атомарных функций:
За исключением последней, назначение всех функций понятно из названия. Последняя функция (atomicCompSwap) меняет значение mem с data, если compare равно текущему значению mem.nint atomicAdd(inout nint mem, nint data) nint atomicMin(inout nint mem, nint data) nint atomicMax(inout nint mem, nint data) nint atomicAnd (inout nint mem, nint data) nint atomicOr(inout nint mem, nint data) nint atomicXor(inout nint mem, nint data) nint atomicExchange(inout nint mem, nint data) nint atomicCompSwap(inout nint mem, nint compare, nint data)
Пару важных дополнений к указанной выше статье, взятые из спецификации:
1. Что происходит при попытке записи за пределы размера SSBO? В спецификации дан такой ответ: "Для SSBO используются те же правила что и для блоков юниформ, которые не предоставляют никаких проверок выхода за границы диапазона. Если активный блок юниформ не подкреплен буфером с достаточным объемом, то результат работы шейдера будет неопределенным и может привести к прерыванию работы GL".
2. В GLSL 430 появился новый квалификатор "std430", в отличии от старого "std140", у которого выравнивание по границе границе 16 байт, "std430" позволяет более плотно упаковывать данные, так для "float", "int", и "uint" выравнивание будет только на границу в 4 байта. Однако, "vec3" все так же выравнивается на границу в 16 байт.
Квалификтор "std430" может быть применим только для блоков SSBO.
3. Узнать максимально допустимые размеры SSBO для каждого типа шейдера можно воспользовавшись функцией "glGetInteger64v" с одним из параметров:
MAX_VERTEX_SHADER_STORAGE_BLOCKS 0x90D6 MAX_GEOMETRY_SHADER_STORAGE_BLOCKS 0x90D7 MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS 0x90D8 MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS 0x90D9 MAX_FRAGMENT_SHADER_STORAGE_BLOCKS 0x90DA MAX_COMPUTE_SHADER_STORAGE_BLOCKS 0x90DB MAX_COMBINED_SHADER_STORAGE_BLOCKS 0x90DC MAX_SHADER_STORAGE_BUFFER_BINDINGS 0x90DD MAX_SHADER_STORAGE_BLOCK_SIZE 0x90DE
4. Бинд SSBO возможен с использованием новой функции:
"void ShaderStorageBlockBinding(uint program, uint storageBlockIndex, uint storageBlockBinding);"
Или с использованием старых функций "BindBuffer, BufferData, BufferSubData, MapBuffer, UnmapBuffer, GetBufferSubData, GetBufferPointerv", указав в качестве назначения буфера (Target) константу:
SHADER_STORAGE_BUFFER 0x90D2
Пример использования SSBO (пример взят из из спецификации).В этом примере, при растеризации примитивов, в буфер записывается список координат фрагментов (x,y) и их цвет. Соответствующий фрагментный шейдер представлен ниже:
#extension GL_ARB_shader_storage_buffer_object : require // Use an atomic counter to keep a running count of the number of // fragments recorded in the shader storage buffer. layout(binding=0, offset=0) uniform atomic_uint fragmentCounter; // Keep a uniform with the number of fragments that can be recorded in // the buffer. uniform uint maxFragmentCount; // Structure with the per-fragment information to record. struct FragmentData { ivec2 position; vec4 color; }; // Shader storage block holding an array <fragments> declared without // a fixed size. Application code should determine how many fragments // it wants to record and allocate a buffer appropriately. With the // "std140" layout, each FragmentData record will take 32B. With other // layouts, the stride of the array is implementation-dependent. The // "binding=2" layout qualifier says that the block <Fragments> should // be associated with shader storage buffer binding point #2. layout(std140, binding=2) buffer Fragments { FragmentData fragments[]; }; in vec4 color; void main() { uint fragmentNumber = atomicCounterIncrement(fragmentCounter); if (fragmentNumber < maxFragmentCount) { fragments[fragmentNumber].position = ivec2(gl_FragCoord.xy); fragments[fragmentNumber].color = color; } }
Код приложения:
#define NFRAGMENTS 100000 #define FRAGMENT_SIZE 32 // known due to "std140" usage GLuint fragmentBuffer, counterBuffer; // Generate, bind, and specify the data store to hold fragments. The // NULL pointer in BufferData says that the intial buffer contents are // undefined. They will be filled in by the fragment shader code. glGenBuffers(1, &fragmentBuffer); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, fragmentBuffer); glBufferData(GL_SHADER_STORAGE_BUFFER, NFRAGMENTS*FRAGMENT_SIZE, NULL, GL_DYNAMIC_DRAW); // Generate, bind, and specify the data store for the atomic counter. glGenBuffers(1, &counterBuffer); glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, counterBuffer); glBufferData(GL_ATOMIC_COUNTER_BUFFER, sizeof(GLuint), NULL, GL_DYNAMIC_DRAW); // Reset the atomic counter to zero, then draw stuff. This will record // values into the shader storage buffer as fragments are generated. GLuint zero = 0; glBufferSubData(GL_ATOMIC_COUNTER_BUFFER, 0, sizeof(GLuint), &zero); glUseProgram(program); glDrawElements(GL_TRIANGLES, ...); // You could inspect the contents with a call such as: void *ptr = glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY); ... glUnmapBuffer(GL_SHADER_STORAGE_BUFFER); // You could also use the storage buffer contents for vertex pulling. // The glMemoryBarrier() command ensures that the data writes to the // storage buffer complete prior to vertex pulling. glMemoryBarrier(GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT); glBindBuffer(GL_ARRAY_BUFFER, fragmentBuffer); glVertexAttribIPointer(0, 2, GL_INT, GL_FALSE, FRAGMENT_SIZE, (void*)0); glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, FRAGMENT_SIZE, (void*)16); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glDrawArrays(GL_POINTS, ...);
Получается, что теперь в шейдере можно одновременно применять к объекту некий эффект и тут же записывать результат в буфер, который дальше можно будет использовать для создания пост-эффектов? Можно ли параллельно автивировать аттачмент FBO и вести запись в 2 буфера(хотя пока слабо представляю, зачем мне такое нужно :) )? Или лучше не забивать гвозди микроскопом и использовать его именно для хранения промежуточных данных? Пока на ум прходят только мысли о записи информации о техиках освещения в light pre-pass рендере, да и те довольно невнятные...
ОтветитьУдалитьДа, почитай следующую статью:
ОтветитьУдалитьhttp://vbomesh.blogspot.com/2012/12/opengl-43-fbo.html
Там как раз рассматривается вариант полной замены FBO на SSBO.
SSBO это простой массив, что ты туда будешь класть - твое личное дело. Это может быть простой счетчик, это могут быть "корзины" для расчета гистограмм, это может быть массив частиц, со всеми их свойствами, это могут быть данные G-Buffer'а для отложенного освещения и т.д. и т.п.
Полностью все возможности SSBO раскрываются совместно с вычислительными шейдерами, где этот буфер выступает в роли обычного массива.
Относительно FBO, при использовании в качестве текстуры, оно конечно немного медленнее, но это с лихвой окупается предоставляемыми возможностями.