Использование спецификатора layout в GLSL

Спецификатор layout позволяет прямо в теле шейдера задать некоторые свойства, облегчая его использование. Здесь будет дано описание некоторых наиболее полезных вариантов использования этого спецификатора.

В общем случае данный спецификатор имеет следующий вид:

layout ( qualifier1, qualifier2 = value, ... ) variable_declaration;

Порядок описателей внутри layout обычно не играет никакой роли. Используемые после знака равенства значения должны быть целочисленными литералами. Для OpenGL 4.4 допускается использование целочисленных констант времени компиляции.

Задание индекса для атрибутов вершины

При помощи спецификатора layout можно задать расположение (location) для атрибута вершины прямо в вершинном шейдере. Ниже приводится пример такого описания, атрибуту position присваивается расположение по индексу 2.

layout ( location = 2 ) in vec3 position;

Использование данного спецификатора позволяет отказаться от вызовов функциий glBindAttribLocation и glgetAttribLocation. Если атрибут является массивом, то его элементы будут занимать подряд идущие положения начиная с заданного. Так в следующем примере элементы массива values получат положения 2, 3, 4 и 5.

layout ( location = 2 ) in vec4 values [4];

Задание привязки выходных значений фрагментного шейдера к подключениям фреймбуфера при MRT

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

layout ( location = 1 ) out vec4 color2;

Это позволяет избежать вызовов glBindFragDataLocation. Обратите внимание, что вызовы glBindFragDataLocation имеют меньший приоритет чем значения, задаваемые спецификатором layout.

Если мы используем двойной альфа-блендинг (dual source blending), то можно добавить в layout еще и дополнительный индекс для выводимого цвета.

layout ( location = 0, index = 1 ) out vec4 color;
layout ( location = 0, index = 0 ) out vec4 color2;

Задание привязки входных и выходных значений для независимо линкуемых программ

При использовании раздельной линковки программ (через расширение ARB_separate_shader_objects) для задания соответствия выходных значений одного шейдера входным значениям следующего шейдера используются целочисленные индексы, задаваемые с помощью спецификатора layout.

layout ( location = 0 ) out vec4 color;
layout ( location = 1 ) out vec2 texCoord;

При этом в разных шейдерах переменные могут иметь разные имена - соответствие устанавливается при помощи задаваемых индексов. Так следующие описания входных значений соответствуют приведенным выше описаниям выходных значений.

layout ( location = 0 ) in vec4 diffColor;
layout ( location = 1 ) in vec2 tCoord;

Задание расположения атомарных индексов

Расширение ARB_shader_atomic_counters вводит атомарные счетчики в шейдерах. Каждый такой счетчик расположен внутри некоторого VBO. Привязка счетчика к буферу и конкретному положению внутри буфера задается только через спецификатор layout, других способов нет.

В спецификаторе layout задаются один или оба параметра. Первый из них - binding - задает привязку счетчика к конкретной точке привязки буфера типа GL_ATOMIC_COUNTER_BUFFER. Второй - offset - является необязательным и задает смещение внутри буфера. Если смещение для счетчика не задано явно, то оно будет на 4 больше, чем смещение предыдущего счетчика в этом буфере.

layout ( binding = 0, offset = 12 ) uniform atomic_uint c1;
layout ( binding = 1 ) uniform atomic_uint c2;

Задание положение uniform-переменной

Расширение ARB_explicit_uniform_location позволяет явно задать положение (location) для uniform-переменной внутри шейдера. При этом пропадает необходимость использовать функцию glGetUniformLocation. Каждому базовому типу соответствует ровно одно положение. Для элементов массивов и структур их элементы (поля) будут получать положения по возрастанию, начиная с заданного.

layout ( location = 0 ) uniform mat4 mv;
layout ( location = 1 ) uniform mat4 m [10];

struct Light 
{
    vec4    pos;
    vec4    color;
};

latyout ( location = 11 ) uniform Light light;

Задание захватываемых переменных для transform feedback

При помощи спецификатора layout можно задать какие именно выходные переменные вершинного шейдера должны быть записаны во время transform feedback (преобразования обратной связи). Для этого расширение ARB_enhanced_layout вводит ряд специальных параметорв в спецификатор layout.

Так каждая выходная переменная, объявленная с использоваинем xfb_offsetавтоматически будет захвачена. При этом объявлении захват переменных полностью "перебивает" команду gLTransformFeedbackVaryings.

layout ( xfb_offset = 0 ) out vec4 position;

Также можно явно задать и точку привязки буфера (buffer binding index) в который будет осуществляться запись при помощи xfb_buffer.

layout ( xfb_buffer = 0, xfb_offset = 40 ) out float x;

Обратите внимание, что задание буфера без задания смещения не имеет вообще никакого эффекта и просто игнорируется.

Кроме буфера и смещения в буфере также можно задать и шаг (stride) - расстояние в байтах между записями, соответствующими последовательно записываемыми вершинами. Для этого используется xfb_stride.

layout ( xfb_buffer = 1, xfb_stride = 32 ) out;

Данная команда задает буфер 1 используемым по умолчанию и для него задает шаг в 32 байта.

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

Управление координатами в окне

Встроенная переменная gl_FragCoord содержит координаты текущего фрагмента в системе координат кона. По умолчанию в OpenGKL принято, что начало координат в системе координат окна расположено в нижнем левом углу. Однако можно изменить это поведение, объявив переменную gl_FragCoord с соответствующим спецификатором layout как показано ниже.

layout ( origin_upper_right ) in vec4 gl_FragCoord;

Кроме того в OpenGL по умолчнию центр фрагмента имеет дробную часть , равную 0.5. Можно сдвинуть экранные координаты на полпиксела при помощи следующего объявления.

layout ( pixel_center_integer ) in vec4 gl_FragCoord;

Управление ранним тестом глубины

Согласно спецификации OpenGL выполняются после выполнения фрагментного шейдера, т.е. если значение глубины будет изменено в фрагментном шейдере, то тест глубины все равно отработает корректно.

ОРднако это приводит к тому, что зачастую фрагментный шейдер работает впустую, т.е. выполняется для тех фрагментов, которые затем будут отброшены тестом глубины. Поэтому многие реализации OpenGL умеют в определенных случаях выполнять эти тесты (и, соответственно, отбрасывать фрагменты) до выполнения фрагментного шейдера, что увеличивает быстродействие.

Но при использовании атомарных операций и записи в изображение (расширение ARB_shader_image_load_store) отбрасывание фрагментов до выполнения фрагментного шейдера может привести к некорректным результатам.

Поэтому расширение ARB_shader_image_load_store вводит способ явно включить выполнение тестов до выполнения фрагментного шейдера (но при этом каждая запись в gl_FragDepth в фрагментном шейдер будет просто игнорироваться). Для этого служит следующая директива.

layout ( early_fragment_tests ) in;

Другим способом разрешения выполнения теста глубины до выполнения фрагментного шейдера является явное задание того, как именно в шейдере изменится значение глубины (по сравнению со значением, полученным при растеризации).

Для этого расширение ARB_conservative_depth позволяет явно указать характер изменение глубины в шедйер при помощи спецификатора layoutю В этом спецификаторе можно задать один из следующих описателей - depth_any, depth_greater, depth_less и depth_unchanged.

layout ( depth_greater ) out float gl_FragDepth;

Описатель depth_any означает, что глубина может измениться в шейдере в любую сторону. Описатель depth_greater означает, что окончательное значение, записанное в gl_FragDepth будет больше или равно значения глубины, полученного при растеризации. Описатель depth_less означает, что окончательное записанное значение будет меньше или равно чем значение, полученное при растеризации. И наконец, depth_unchanged означает, что хотя компилятор правильно обработает любое изменение gl_FragDepth, все остальные части OpenGL будут считать, что значение gl_FragCoord.

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