Не так давно я привел перевод одной из статей по SSBO (shader_storage_buffer_object), сегодня я решил продолжить рассмотрение нововведений, появившихся в OpenGL 4.2-4.3, в частности - продолжить знакомство с уже знакомым нам SSBO и clear_buffer_object, а так же рассмотреть появившееся в OpenGL 4.2 расширение shader_image_load_store.
Оба этих расширения предоставляют альтернативный вариант рендеринга в текстуру, но возникает закономерный вопрос о производительности этих подходов. В этой статье я разберу каждый из вариантов и сделаю оценку их производительности.
Для написания демки я набросал небольшой фреймвок, в частности вынес туда создание объекта буфера, создание шейдера и прочие мелочи. Так как работа с SSBO ничем не отличается от использования других объектов буфера (в том числе и VBO), то на этом я не буду останавливаться. Создание шейдера для OpenGL 4.3 тоже ничем не отличается от создания шейдера для OpenGL 1.4, потому эту часть я тоже пропущу и сосредоточусь на нововведениях.
В частности первое нововведение это возможность очистки объекта буфера, причем не просто очистки, но и заливки буфера произвольными данными. Эта возможность появилась в OpenGL 4.3 и описана в расширении clear_buffer_object. В частности нас интересует команда:
Простой пример, если мы хотим очистить буфер SSBO, предназначенный для хранения цвета пикселя в формате RGBA8, то нам достаточно выполнить следующие действия:
Теперь собственно разберем что значит "альтернативные" методы и какие они есть. Углубляться в историю и рассматривать всякие "pBuffer" я не буду, как и команды glGetImage/glReadPixels. Сейчас нас будет интересовать только производительность относительно FBO.
С появлением в OpenGL 4.2 возможности не только читать из текстуры но и писать в нее, посредством расширения shader_image_load_store, мы получили первый альтернативный метод, так как теперь, вместо того чтоб выводить результат в стандартный буфер кадра через gl_FragColor, мы можем перенаправить вывод сразу в текстуру или несколько текстур. Соответствующий шейдер (в демке он идет под номером "Shader4") будет иметь вид:
Что мы здесь делаем - ну во-первых мы не делаем запись в буфер кадра, вместо этого мы прикрепили к шейдеру текстуру "Image" и разрешили запись в нее, после чего просто сохраняем в нее текстурные координаты посредством функции "imageStore".
Так как меня интересовала не только производительность записи в буфер/текстуру, но и эффективность чтения из такой текстуры (а так же для контроля корректности записи), то я написал еще один шейдер(Shader5):
Здесь я уже в качестве вывода использую стандартный буфер кадра, связанный с переменной "FragColor", в которую пишутся данные из текстуры "Image".
Вторым альтернативным способом рендеринга в текстуру является прямая запись в SSBO. Ниже приведен код соответствующего шейдера для записи в SSBO (Shader2):
и шейдер на чтение из SSBO с выводом в буфер кадра(Shader3):
Что здесь происходит, ну во-первых, вместо использования текстуры мы уже используем буфер SSBO "Colors", для его объявления служит ключевое слово "buffer", а объявляется он по тем же правилам что и блок юниформ, с тем отличием, что мы можем использовать при объявлении буфера открытые массивы. Так как массив "Colors" у нас одномерный, то мне пришлось пересчитывать координаты фрагмента в смешение в буфере.
Второе замечание - массив объявлен как "uint", что как раз подходит для хранения формата RGBA8. Чтоб работать с таким форматом, в расширении shading_language_packing, были введены функции "packUnorm4x8" и "unpackUnorm4x8", позволяющие запаковать в uint вектор из 4-х флоатов.
Для рендеринга как в FBO так и в стандартный буфер кадра, используется такой шейдер (Shader1):
Для оценки так же осуществляется вывод текстурированного скринквада в стандартный буфер кадра (Shader6):
Во всех случаях использовался одинаковый вершинный шейдер скринквада (Shader.vert):
Ну и теперь переходим к тестированию. Как вы уже могли понять из приведенных выше шейдеров, выводится скринквад с градиентом, соответствующим распределению текстурных координат:
Результаты теста можно видеть на этом же скриншоте. Расшифровка результатов:
Первая строка: "Clearing Buffers" - результаты по времени очистки буферов через "glClear" (76.26мкс) и "glClearBufferData"(246.34мкс) соответственно.
Вторая строка:
FragColor = 127.9мкс - время вывода скринквада с прямой записью цвета в "FragColor" (Shader1);
SSBO Fill = 286.98мкс - время записи в буфер SSBO (Shader2);
SSBO Render = 363.58мкс - время вывода скринквада с чтением из SSBO (Shader3);
Третья строка:
ImageStore = 529.89мкс - время записи в текстуру через ImageStore (Shader4);
ImageLoad = 330.98мкс - время вывода скринква с текстурированием через ImageLoad (Shader5);
FBO = 169.50 мкс - время рендеринга в текстуру через FBO (Shader1)$
Четвертая строка:
SimpleTexturing = 236.74мкс - для сравнения был выведен обычный текстурированный скринквад.
Выводы
Что можно сказать по результатам - ну во-первых мы проигрываем по скорости очистки буферов, к счастью это нужно делать один раз на сцену, а если предполагается полная перерисовка экрана, то и вообще можно обойтись без нее.
С рендерингом в текстуру все сложнее. Возьмем за основу время рендеринга в FBO и скринквада с результатом: 170мкс+237мкс = 407мкс, это у нас будет как эталонное значение.
Используя рендеринг в SSBO суммарное время у нас получается 650мкс, что в полтора раза выше чем при обычном рендеринге в FBO.
Воспользовавшись расширением ImageLoadStore нас ждет наибольшее разочарование, так как суммарное время операции составляет целых 860мкс, что в два раза выше чем у FBO и на 30% выше чем у SSBO.
Однако, нужно помнить что все это время измерено в микросекундах, в переводе на "FPS" вы получите 2500 фпс у FBO, 1500 фпс у SSBO и 1160 фпс у ImageLoadStore.
Отдельно стоит отметить результат "ImageLoad" и "SSBO Render". Не смотря на то, что запись в текстурный буфер два раза медленнее записи в SSBO, по чтению текстурный буфер выигрывает, так как там меньше операций (в SSBO нам пришлось вычислять смещение и распаковывать формат пикселя). Это тонко намекает что тест очень чувствителен к количеству операций и в реальных условиях разница будет не столь существенной.
Возможно чуть позже я добавлю еще одну демку, где существенно увеличу нагрузку как на вершинный так и фрагментный шейдер.
Пока можно с уверенностью сказать что альтернатива FBO есть :)
Демку с исходниками можно взять здесь.
Для запуска демки требуется драйвера с поддержкой OpenGL 4.3, на момент написания этой статьи существует только бета-версия драйверов и только у NVidia, кторые можно взять здесь:
https://developer.nvidia.com/opengl-driver
или ищите 310 версию в бета-драйверах NVidia:
http://www.nvidia.ru/Download/Find.aspx?lang=ru
На момент написания статьи последняя версия была 310.70.
Оба этих расширения предоставляют альтернативный вариант рендеринга в текстуру, но возникает закономерный вопрос о производительности этих подходов. В этой статье я разберу каждый из вариантов и сделаю оценку их производительности.
Для написания демки я набросал небольшой фреймвок, в частности вынес туда создание объекта буфера, создание шейдера и прочие мелочи. Так как работа с SSBO ничем не отличается от использования других объектов буфера (в том числе и VBO), то на этом я не буду останавливаться. Создание шейдера для OpenGL 4.3 тоже ничем не отличается от создания шейдера для OpenGL 1.4, потому эту часть я тоже пропущу и сосредоточусь на нововведениях.
В частности первое нововведение это возможность очистки объекта буфера, причем не просто очистки, но и заливки буфера произвольными данными. Эта возможность появилась в OpenGL 4.3 и описана в расширении clear_buffer_object. В частности нас интересует команда:
Ее формат достаточно простой - указываем какой буфер нужно очистить (буфер должен быть предварительно забинден к соответствующему таргету), указываем внутренний формат данных, формат пикселя и тип данных (все аналогично как и для текстуры), и передаем ссылку на область памяти, содержащую данные для заливки в буфер.void ClearBufferData(enum target, enum internalformat, enum format, enum type, const void * data);
Простой пример, если мы хотим очистить буфер SSBO, предназначенный для хранения цвета пикселя в формате RGBA8, то нам достаточно выполнить следующие действия:
constДля того, чтоб оценить производительность этой команды, я воспользовался прецизионным таймером (ARB_timer_query), сделав замер времени очистки вьюпорта (только буфер цвета) через стандартный "glClear" и очистку буфера, аналогичного формата и размера через "glClearBufferData". Результат получился таким:
bRGBA: array[0..3] of byte = (255,255,255,255);
...
glBindBuffer(GL_SHADER_STORAGE_BUFFER, BuffId);
glClearBufferData(GL_SHADER_STORAGE_BUFFER, GL_RGBA8,
GL_RGBA, GL_UNSIGNED_BYTE, @bRGBA[0]);
glClear - 77usРезультат получился не в пользу "новинки", проиграв обычной очистке в 3.2 раза. Тем не менее, в дальнейшем нам придется использовать данный метод, если хотим использовать альтернативные методы рендеринга в текстуру.
glClearBufferData - 246us
Теперь собственно разберем что значит "альтернативные" методы и какие они есть. Углубляться в историю и рассматривать всякие "pBuffer" я не буду, как и команды glGetImage/glReadPixels. Сейчас нас будет интересовать только производительность относительно FBO.
С появлением в OpenGL 4.2 возможности не только читать из текстуры но и писать в нее, посредством расширения shader_image_load_store, мы получили первый альтернативный метод, так как теперь, вместо того чтоб выводить результат в стандартный буфер кадра через gl_FragColor, мы можем перенаправить вывод сразу в текстуру или несколько текстур. Соответствующий шейдер (в демке он идет под номером "Shader4") будет иметь вид:
#version 420 in vec4 gl_FragCoord; in vec2 TexCoord; layout(binding = 0, rgba8) writeonly uniform image2D Image; void main() { imageStore(Image, ivec2(gl_FragCoord.xy), vec4(TexCoord,0.0,1.0)); }
Что мы здесь делаем - ну во-первых мы не делаем запись в буфер кадра, вместо этого мы прикрепили к шейдеру текстуру "Image" и разрешили запись в нее, после чего просто сохраняем в нее текстурные координаты посредством функции "imageStore".
Так как меня интересовала не только производительность записи в буфер/текстуру, но и эффективность чтения из такой текстуры (а так же для контроля корректности записи), то я написал еще один шейдер(Shader5):
#version 420 in vec4 gl_FragCoord; in vec2 TexCoord; layout(location = 0) out vec4 FragColor; layout(binding = 0, rgba8) readonly uniform image2D Image; void main() { FragColor = imageLoad(Image, ivec2(gl_FragCoord.xy)); }
Здесь я уже в качестве вывода использую стандартный буфер кадра, связанный с переменной "FragColor", в которую пишутся данные из текстуры "Image".
Вторым альтернативным способом рендеринга в текстуру является прямая запись в SSBO. Ниже приведен код соответствующего шейдера для записи в SSBO (Shader2):
#version 430 in vec2 TexCoord; layout(binding = 2) buffer Col { uint Colors[];}; uniform float Width; void main() { uint index = uint(gl_FragCoord.y*Width+gl_FragCoord.x); Colors[index] = packUnorm4x8(vec4(TexCoord.xy, 0.0, 1.0)); }
и шейдер на чтение из SSBO с выводом в буфер кадра(Shader3):
#version 430 layout(location = 0) out vec4 FragColor; layout(binding = 2) buffer Col { uint Colors[];}; uniform float Width; void main() { uint index = uint(gl_FragCoord.y*Width+gl_FragCoord.x); FragColor = unpackUnorm4x8(Colors[index]); }
Что здесь происходит, ну во-первых, вместо использования текстуры мы уже используем буфер SSBO "Colors", для его объявления служит ключевое слово "buffer", а объявляется он по тем же правилам что и блок юниформ, с тем отличием, что мы можем использовать при объявлении буфера открытые массивы. Так как массив "Colors" у нас одномерный, то мне пришлось пересчитывать координаты фрагмента в смешение в буфере.
Второе замечание - массив объявлен как "uint", что как раз подходит для хранения формата RGBA8. Чтоб работать с таким форматом, в расширении shading_language_packing, были введены функции "packUnorm4x8" и "unpackUnorm4x8", позволяющие запаковать в uint вектор из 4-х флоатов.
Для рендеринга как в FBO так и в стандартный буфер кадра, используется такой шейдер (Shader1):
#version 330 in vec2 TexCoord; layout(location = 0) out vec4 FragColor; void main() { FragColor = vec4(TexCoord,0.0,1.0); }
Для оценки так же осуществляется вывод текстурированного скринквада в стандартный буфер кадра (Shader6):
#version 330 in vec2 TexCoord; layout(location = 0) out vec4 FragColor; uniform sampler2D Image; void main() { FragColor = texture(Image, TexCoord.xy); }
Во всех случаях использовался одинаковый вершинный шейдер скринквада (Shader.vert):
#version 420 layout(location = 0) in vec3 in_Position; layout(location = 1) in vec2 in_TexCoord; out vec2 TexCoord; void main(void) { gl_Position = vec4(in_Position, 1.0); TexCoord = in_TexCoord; }
Ну и теперь переходим к тестированию. Как вы уже могли понять из приведенных выше шейдеров, выводится скринквад с градиентом, соответствующим распределению текстурных координат:
Результаты теста можно видеть на этом же скриншоте. Расшифровка результатов:
Первая строка: "Clearing Buffers" - результаты по времени очистки буферов через "glClear" (76.26мкс) и "glClearBufferData"(246.34мкс) соответственно.
Вторая строка:
FragColor = 127.9мкс - время вывода скринквада с прямой записью цвета в "FragColor" (Shader1);
SSBO Fill = 286.98мкс - время записи в буфер SSBO (Shader2);
SSBO Render = 363.58мкс - время вывода скринквада с чтением из SSBO (Shader3);
Третья строка:
ImageStore = 529.89мкс - время записи в текстуру через ImageStore (Shader4);
ImageLoad = 330.98мкс - время вывода скринква с текстурированием через ImageLoad (Shader5);
FBO = 169.50 мкс - время рендеринга в текстуру через FBO (Shader1)$
Четвертая строка:
SimpleTexturing = 236.74мкс - для сравнения был выведен обычный текстурированный скринквад.
Выводы
Что можно сказать по результатам - ну во-первых мы проигрываем по скорости очистки буферов, к счастью это нужно делать один раз на сцену, а если предполагается полная перерисовка экрана, то и вообще можно обойтись без нее.
С рендерингом в текстуру все сложнее. Возьмем за основу время рендеринга в FBO и скринквада с результатом: 170мкс+237мкс = 407мкс, это у нас будет как эталонное значение.
Используя рендеринг в SSBO суммарное время у нас получается 650мкс, что в полтора раза выше чем при обычном рендеринге в FBO.
Воспользовавшись расширением ImageLoadStore нас ждет наибольшее разочарование, так как суммарное время операции составляет целых 860мкс, что в два раза выше чем у FBO и на 30% выше чем у SSBO.
Однако, нужно помнить что все это время измерено в микросекундах, в переводе на "FPS" вы получите 2500 фпс у FBO, 1500 фпс у SSBO и 1160 фпс у ImageLoadStore.
Отдельно стоит отметить результат "ImageLoad" и "SSBO Render". Не смотря на то, что запись в текстурный буфер два раза медленнее записи в SSBO, по чтению текстурный буфер выигрывает, так как там меньше операций (в SSBO нам пришлось вычислять смещение и распаковывать формат пикселя). Это тонко намекает что тест очень чувствителен к количеству операций и в реальных условиях разница будет не столь существенной.
Возможно чуть позже я добавлю еще одну демку, где существенно увеличу нагрузку как на вершинный так и фрагментный шейдер.
Пока можно с уверенностью сказать что альтернатива FBO есть :)
Демку с исходниками можно взять здесь.
Для запуска демки требуется драйвера с поддержкой OpenGL 4.3, на момент написания этой статьи существует только бета-версия драйверов и только у NVidia, кторые можно взять здесь:
https://developer.nvidia.com/opengl-driver
или ищите 310 версию в бета-драйверах NVidia:
http://www.nvidia.ru/Download/Find.aspx?lang=ru
На момент написания статьи последняя версия была 310.70.
Комментариев нет:
Отправить комментарий