Код обміну між декількома шейдерами GLSL


30

Я часто знаходжу собі код копіювання між декількома шейдерами. Сюди входять як певні обчислення або дані, що поділяються між усіма шейдерами в одному конвеєрі, так і загальні обчислення, які потрібні всім моїм вершинним шейдерам (або будь-який інший етап).

Звичайно, це жахлива практика: якщо мені потрібно змінити код де завгодно, мені потрібно переконатися, що я його міняю всюди в іншому місці.

Чи є прийнята найкраща практика збереження сухого стану ? Люди просто додають єдиний загальний файл до всіх своїх шейдерів? Вони пишуть власний рудиментарний препроцесор у стилі С, який аналізує #includeдирективи? Якщо в галузі є прийняті моделі, я б хотів їх дотримуватися.


4
Це питання може бути трохи суперечливим, тому що кілька інших сайтів SE не хочуть питань про кращі практики. Це навмисно, щоб побачити, як стоїть ця громада щодо таких питань.
Мартін Ендер

2
Гм, добре мені виглядає. Я б сказав, що ми значною мірою трохи "ширші" / "загальніші" у своїх питаннях, ніж, скажімо, StackOverflow.
Кріс каже, що повернеться до Моніки

2
StackOverflow перейшов від "запитуйте нас" до "не питайте нас, якщо вам не доведеться порадувати".
всередині

Якщо це покликане визначити тематичність, то як щодо пов'язаного мета-питання?
SL Barth - Відновіть Моніку

Відповіді:


18

Є купа підходів, але жоден не ідеальний.

Ділитися кодом можна за допомогою glAttachShaderкомбінування шейдерів, але це не дає можливості ділитися речами, такими як декларації структури або #define-d константи. Це працює для обміну функціями.

Деяким людям подобається використовувати масив рядків, що передаються, glShaderSourceяк спосіб додати загальні визначення перед вашим кодом, але це має деякі недоліки:

  1. Важче контролювати те, що потрібно включити всередину шейдера (для цього вам потрібна окрема система.)
  2. Це означає, що автор шейдера не може вказати GLSL #versionчерез наступне твердження у специфікації GLSL:

#Version директива повинна відбутися в шейдера , перш ніж що -то ще, для коментарів і білого простору , за винятком.

Завдяки цій заяві glShaderSourceне можна використовувати для додавання тексту до #versionдекларацій. Це означає, що #versionрядок потрібно включити у ваші glShaderSourceаргументи, а це означає, що інтерфейс компілятора GLSL повинен якось розповісти, яку версію GLSL очікується використовувати. Крім того, якщо не вказати #versionалімент, компілятор GLSL стане типовим для використання GLSL версії 1.10. Якщо ви хочете дозволити авторам шейдерів задавати #versionстандартний скрипт у межах сценарію, то вам потрібно якось вставити #include-s після #versionзаяви. Це можна зробити, чітко проаналізувавши шейдер GLSL, щоб знайти #versionрядок (якщо такий є) та зробити ваші включення після нього, але маючи доступ до#includeДиректива, можливо, бажано легше контролювати, коли ці включення потрібно зробити. З іншого боку, оскільки GLSL ігнорує коментарі перед #versionрядком, ви можете додати метадані для включення до коментарів у верхній частині вашого файлу (yuck.)

Питання зараз: чи є стандартне рішення для #includeчи потрібно прокрутити власне розширення препроцесора?

Існує GL_ARB_shading_language_includeрозширення, але вона має деякі недоліки:

  1. Він підтримується лише NVIDIA ( http://delphigl.de/glcapsviewer/listreports2.php?listreportsbyextension=GL_ARB_shading_language_include )
  2. Він працює, вказуючи строки включення достроково. Тому перед компілюванням потрібно вказати, що рядок "/buffers.glsl"(як це було використано #include "/buffers.glsl") відповідає вмісту файлу buffer.glsl(який ви раніше завантажили).
  3. Як ви, можливо, помітили у пункті (2), ваші шляхи повинні починатися "/", як і абсолютні контури в стилі Linux. Це позначення, як правило, незнайоме програмістам на C, і означає, що ви не можете вказати відносні шляхи.

Звичайна конструкція полягає в реалізації власного #includeмеханізму, але це може бути складним, оскільки вам також потрібно проаналізувати (і оцінити) інші інструкції препроцесора, як-от #ifдля правильної обробки умовної компіляції (наприклад, заголовки заголовків.)

Якщо ви реалізуєте свою власну #include, у вас також є деякі свободи у тому, як ви хочете її реалізувати:

  • Ви можете передавати рядки раніше часу (як GL_ARB_shading_language_include).
  • Ви можете вказати зворотний виклик включення (це робиться бібліотекою Direct3 D3DCompiler.)
  • Ви можете реалізувати систему, яка завжди читає безпосередньо з файлової системи, як це робиться у типових програмах C.

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

if (#include and not_included_yet) include_file();

(Подяка Тренту Риду за те, що він показав мені вищевказану техніку.)

На закінчення , не існує автоматичного, стандартного та простого рішення. У майбутньому рішенні ви можете використовувати деякий інтерфейс OpenGL SPIR-V, і в цьому випадку компілятор GLSL до SPIR-V може бути поза межами API API. Наявність компілятора за межами виконання OpenGL значно спрощує реалізацію таких речей, як #includeце більш підходяще місце для взаємодії з файловою системою. Я вважаю, що сучасний розповсюджений метод - це просто реалізувати спеціальний препроцесор, який працює таким чином, з яким повинен бути знайомий будь-який програміст C.


Шейдери також можна розділити на модулі за допомогою glslify , хоча він працює лише з node.js.
Андерсон Грін

9

Зазвичай я просто використовую той факт, що glShaderSource (...) приймає масив рядків як його вхід.

Я використовую файл визначення шейдерів на основі json, який визначає, як складається шейдер (або більш коректна програма), і там я вказую, що препроцесор визначає, що мені може знадобитися, уніформу, яку він використовує, файл вершин / фрагментів шейдерів, і всі додаткові файли "залежності". Це лише колекції функцій, які додаються до джерела перед фактичним джерелом шейдера.

Просто додамо, AFAIK, Unreal Engine 4 використовує директиву #include, яка розбирає та додає всі відповідні файли перед компіляцією, як ви запропонували.


4

Я не думаю, що існує загальна конвенція, але якщо я здогадуюсь, я б сказав, що майже кожен реалізує просту форму текстового включення як крок попередньої обробки ( #includeрозширення), тому що це дуже легко зробити так. (У JavaScript / WebGL це можна зробити, наприклад, простим регулярним виразом). Переваги цього полягають у тому, що ви можете виконати попередню обробку в режимі офлайн для побудови "випуску", коли код шейдера більше не потрібно змінювати.

Насправді, ознака того, що цей підхід є загальним є той факт , що розширення АРБ було введено для цього: GL_ARB_shading_language_include. Я не впевнений, чи стало це тепер основною особливістю чи ні, але розширення було написано проти OpenGL 3.2.


2
GL_ARB_shading_language_include не є основною особливістю. Насправді його підтримує лише NVIDIA. ( delphigl.de/glcapsviewer/… )
Ніколас Луї

4

Деякі люди вже вказували на те, що glShaderSourceможуть зайняти масив рядків.

Крім того, у GLSL компіляція ( glShaderSource, glCompileShader) та посилання ( glAttachShader, glLinkProgram) шейдерів є окремими.

Я використовував це в деяких проектах для розподілу шейдерів між конкретною частиною та частинами, загальними для більшості шейдерів, яка потім компілюється та ділиться всіма програмами шейдерів. Це працює і не важко реалізувати: потрібно просто вести список залежностей.

Щодо ремонту, я не впевнений, що це виграш. Спостереження було те саме, спробуємо факторизувати. Хоча це справді уникає повторень, накладні витрати на техніку відчутні. Більше того, остаточний шейдер важче витягнути: ви не можете просто об'єднати джерела шейдерів, оскільки декларації закінчуються в порядку, який деякі компілятори відхилять або дублюються. Таким чином, це ускладнює тест на швидкий шейдер в окремому інструменті.

Врешті-решт, ця методика вирішує деякі проблеми із сухим, але далеко не ідеальною.

Що стосується побічної теми, я не впевнений, чи має такий підхід якийсь вплив на час складання; Я читав, що деякі драйвери дійсно компілюють програму шейдерів на посилання, але я не вимірював.


З мого розуміння, я думаю, що це не вирішує проблему обміну визначеннями структур.
Ніколя Луї Гіллемо

@NicolasLouisGuillemot: так, ти маєш рацію, таким чином ділиться лише кодом інструкцій, а не деклараціями.
Жульєн Герто
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.