Передача списка значений во фрагментный шейдер - PullRequest
65 голосов
/ 31 октября 2011

Я хочу отправить список значений в фрагментный шейдер. Это, возможно, большой (несколько тысяч элементов) список с плавающей точкой одинарной точности. Фрагментному шейдеру необходим произвольный доступ к этому списку, и я хочу обновить значения из ЦП в каждом кадре.

Я рассматриваю свои варианты того, как это можно сделать:

  1. Как единообразная переменная типа массива («равномерное число с плавающей запятой x [10];»). Но здесь, похоже, есть ограничения: на моем графическом процессоре отправка более нескольких сотен значений происходит очень медленно, а также мне придется жестко задавать верхний предел в шейдере, когда я бы предпочел изменить его во время выполнения.

  2. Как текстура с высотой 1 и шириной моего списка, затем обновите данные, используя glCopyTexSubImage2D.

  3. Другие методы? В последнее время я не следил за всеми изменениями в спецификации GL, возможно, есть какой-то другой метод, специально разработанный для этой цели?

Ответы [ 4 ]

124 голосов
/ 31 октября 2011

В настоящее время есть 4 способа сделать это: стандартные 1D текстуры, текстуры буферов, однородные буферы и буферы хранения шейдеров.

1D текстуры

При использовании этого метода вы используете glTex(Sub)Image1D, чтобы заполнить одномерную текстуру своими данными. Поскольку ваши данные - это просто массив с плавающей точкой, ваш формат изображения должен быть GL_R32F. Затем вы получаете доступ к нему в шейдере с помощью простого вызова texelFetch. texelFetch принимает координаты текселя (отсюда и название) и отключает всю фильтрацию. Таким образом, вы получите ровно один тексель.

Примечание: texelFetch - 3,0+. Если вы хотите использовать предыдущие версии GL, вам нужно будет передать размер шейдеру и нормализовать координату текстуры вручную.

Основными преимуществами здесь являются совместимость и компактность. Это будет работать на оборудовании GL 2.1 (с использованием нотации). И у вас нет для использования GL_R32F форматов; Вы можете использовать GL_R16F half-float. Или GL_R8, если ваши данные соответствуют нормированному байту. Размер может много значить для общей производительности.

Основным недостатком является ограничение размера. Вы ограничены наличием 1D текстуры с максимальным размером текстуры. На оборудовании класса GL 3.x это будет около 8192, но гарантированно будет не менее 4096.

Объекты однородного буфера

Это работает так, что вы объявляете единообразный блок в своем шейдере:

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

Затем вы получаете доступ к этим данным в шейдере, как массив.

Вернувшись в код на C / C ++ / etc, вы создаете буферный объект и заполняете его данными с плавающей точкой. Затем вы можете связать этот буферный объект с единообразным блоком MyBlock. Более подробную информацию можно найти здесь.

Принципиальными преимуществами этой техники являются скорость и семантика. Скорость объясняется тем, как реализации обрабатывают однородные буферы по сравнению с текстурами. Выборки текстур - это глобальные обращения к памяти. Равномерный доступ к буферу обычно отсутствует; данные унифицированного буфера обычно загружаются в шейдер, когда шейдер инициализируется после его использования при рендеринге. Оттуда это локальный доступ, который намного быстрее.

Семантически, это лучше, потому что это не просто плоский массив. Для ваших конкретных потребностей, если все, что вам нужно, это float[], это не имеет значения. Но если у вас более сложная структура данных, семантика может быть важной. Например, рассмотрим массив источников света. Огни имеют позицию и цвет. Если вы используете текстуру, ваш код для определения положения и цвета для определенного источника света выглядит следующим образом:

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

С унифицированными буферами это выглядит так же, как и любой другой универсальный доступ Вы назвали участников, которых можно назвать position и color. Таким образом, вся семантическая информация есть; легче понять, что происходит.

Для этого есть ограничения по размеру. OpenGL требует, чтобы реализации предоставляли как минимум 16 384 байта для максимального размера однородных блоков. Это означает, что для массивов с плавающей запятой вы получаете только 4096 элементов. Еще раз отметим, что это минимум , требуемый от реализаций; Некоторое оборудование может предложить гораздо большие буферы. Например, AMD предоставляет 65 536 на своем оборудовании класса DX10.

Буферные текстуры

Это своего рода "супер 1D текстура". Они эффективно позволяют получить доступ к объекту буфера из текстурного блока . Хотя они одномерные, они не являются одномерными текстурами.

Вы можете использовать их только с GL 3.0 или выше. И вы можете получить к ним доступ только через функцию texelFetch.

Основным преимуществом здесь является размер. Буферные текстуры могут быть довольно гигантскими. Хотя спецификация в целом консервативна и требует не менее 65 536 байтов для буферных текстур, большинство реализаций GL позволяют им иметь размер мега байтов. Действительно, обычно максимальный размер ограничен доступной памятью графического процессора, а не аппаратными ограничениями.

Кроме того, буферные текстуры хранятся в буферных объектах, а не в более непрозрачных текстурных объектах, таких как 1D текстуры.Это означает, что вы можете использовать некоторые методы потоковой передачи буферных объектов для их обновления.

Основным недостатком здесь является производительность, как и в случае с 1D текстурами.Буферные текстуры, вероятно, не будут медленнее, чем 1D текстуры, но они не будут такими же быстрыми, как UBO.Если вы просто вытаскиваете из них один поплавок, это не должно вызывать беспокойства.Но если вы извлекаете из них много данных, рассмотрите возможность использования UBO.

Объекты буфера хранилища шейдеров

OpenGL 4.3 предоставляет другой способ справиться с этим: буферы хранилища шейдеров .Они очень похожи на однородные буферы;Вы определяете их, используя синтаксис, почти идентичный синтаксису единообразных блоков.Принципиальная разница в том, что вы можете написать им.Очевидно, что это бесполезно для ваших нужд, но есть и другие отличия.

Буферы хранения шейдеров, с концептуальной точки зрения, являются альтернативной формой текстуры буфера.Таким образом, ограничения по размеру для буферов хранения шейдеров на много больше, чем для однородных буферов.Минимум OpenGL для максимального размера UBO составляет 16 КБ.Минимальный OpenGL для максимального размера SSBO составляет 16MB .Так что, если у вас есть аппаратное обеспечение, это интересная альтернатива UBO.

Обязательно объявите их как readonly, так как вы не пишете им.

Потенциальные возможностинедостаток здесь снова производительность по сравнению с UBO.SSBO работают как операция загрузки / сохранения изображения через буферные текстуры.По сути, это (очень хороший) синтаксический сахар для imageBuffer типа изображения.Таким образом, чтение из них, вероятно, будет выполняться со скоростью чтения из readonly imageBuffer.

. На данный момент неясно, является ли чтение через загрузку / сохранение изображения через буферные изображения быстрее или медленнее, чем текстуры буфера.

Другая потенциальная проблема заключается в том, что вы должны соблюдать правила для несинхронного доступа к памяти .Они сложны и могут очень легко сбить вас с толку.

6 голосов
/ 31 октября 2011

Это звучит как хороший пример использования объектов текстурного буфера . Они не имеют ничего общего с обычными текстурами и в основном позволяют получить доступ к памяти буферного объекта в шейдере в виде простого линейного массива. Они похожи на 1D текстуры, но не фильтруются и доступны только по целочисленному индексу, который звучит как то, что вам нужно делать, когда вы называете это списком значений. И они также поддерживают гораздо большие размеры, чем 1D текстуры. Для его обновления вы можете использовать стандартные методы объекта буфера (glBufferData, glMapBuffer, ...).

Но, с другой стороны, им требуется аппаратное обеспечение GL3 / DX10, и я думаю, что они даже стали ядром в OpenGL 3.1. Если ваше оборудование / драйвер не поддерживает его, тогда вашим вторым решением будет выбор метода, но лучше использовать 1D-текстуру, а не 2D-текстуру шириной x 1). В этом случае вы также можете использовать неплоскую 2D текстуру и некоторую магию индекса для поддержки списков, размер которых превышает максимальный размер текстуры.

Но я думаю, что текстурные буферы идеально подходят для вашей проблемы. Для более точного понимания вы можете также обратиться к соответствующей спецификации расширения .

РЕДАКТИРОВАТЬ: В ответ на комментарий Никола о единообразных буферных объектах , вы также можете посмотреть здесь для небольшого сравнения двух. Я по-прежнему склонен к TBO, но на самом деле не могу объяснить, почему, только потому, что я вижу, что это лучше подходит концептуально. Но, может быть, Никол сможет дать ответу более глубокое понимание этого вопроса.

5 голосов
/ 31 октября 2011

Один из способов - использовать унифицированные массивы, как вы упомянули. Еще один способ сделать это - использовать 1D «текстуру». Ищите GL_TEXTURE_1D и glTexImage1D. Я лично предпочитаю этот способ, так как вам не нужно жестко задавать размер массива в коде шейдера, как вы сказали, а в opengl уже есть встроенные функции для загрузки / доступа к 1D-данным на GPU.

1 голос
/ 31 октября 2011

Я бы сказал, что, вероятно, не номер 1. У вас есть ограниченное количество регистров для шейдерной формы, которое зависит от карты.Вы можете запросить GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, чтобы узнать свой лимит.На более новых картах он исчисляется тысячами, например, Quadro FX 5500 имеет 2048, по-видимому.(http://www.nvnews.net/vbulletin/showthread.php?t=85925). Это зависит от того, на каком оборудовании вы хотите, чтобы оно работало, и на том, какую другую униформу вы могли бы также отправить в шейдер.

Номер 2 может работать в зависимости от ваших требований. Извините занеясность здесь, надеюсь, кто-то другой может дать вам более точный ответ, но вы должны четко указать, сколько вызовов текстуры вы делаете в старых моделях карт шейдеров. Это также зависит от того, сколько операций чтения текстуры вы хотите выполнить для фрагмента, вы, вероятно, не захотитеЯ не хочу пытаться читать тысячи элементов на фрагмент, опять же, в зависимости от вашей модели шейдера и требований к производительности. Вы можете упаковать значения в RGBA текстуры, давая вам 4 чтения на вызов текстуры, но с произвольным доступом в качестве требования,это может вам не помочь.

Я не уверен насчет номера 3, но я бы посоветовал, возможно, взглянуть на БПЛА (неупорядоченные виды доступа), хотя я думаю, что это только DirectX, без достойного эквивалента openGL.думаю, что есть расширение nVidia для openGL, но вы снова ограничиваете свойЕсли до довольно строгой минимальной спецификации.

Маловероятно, что передача 1000-х элементов данных в ваш фрагментный шейдер - лучшее решение вашей проблемы ... возможно, если вы дали больше подробностей о том, чего вы пытаетесь достичьВы можете получить альтернативные предложения?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...