Почему чертеж OpenGL терпит неудачу, когда нулевой атрибут вершины атрибута отключен?

У меня возникли серьезные проблемы с получением вершинного шейдера моего ядра под управлением ядра OpenGL 3.3 на драйвере ATI:

#version 150

uniform mat4 graph_matrix, view_matrix, proj_matrix;
uniform bool align_origin;

attribute vec2 graph_position;
attribute vec2 screen_position;
attribute vec2 texcoord0;
attribute vec4 color;
varying vec2 texcoord0_px;
varying vec4 color_px;

void main() {
    // Pick the position or the annotation position
    vec2 pos = graph_position;

    // Transform the coordinates
    pos = vec2(graph_matrix * vec4(pos, 0.0, 1.0));

    if( align_origin )
        pos = floor(pos + vec2(0.5, 0.5)) + vec2(0.5, 0.5);

    gl_Position = proj_matrix * view_matrix * vec4(pos + screen_position, 0.0, 1.0);
    texcoord0_px = texcoord0;
    color_px = color;
}

Я использовал glVertexAttrib4f, чтобы указать атрибут цвета, и отключил массив атрибутов. В соответствии со стр. 33 3.3 основной спецификации, которая должна работать:

Если массив, соответствующий общему атрибуту, требуемому вершинным шейдером, не включен, то соответствующий элемент берется из текущего общего состояния атрибута (см. раздел 2.7).

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

Значительный поиск дал эту страницу советов относительно WebGL, в которых было сказано следующее:

Всегда активировать массив атрибутов вершины. Если вы рисуете с отключенным атрибутом вершины 0, вы заставите браузер выполнять сложную эмуляцию при работе на настольном OpenGL (например, на Mac OSX). Это связано с тем, что в настольном OpenGL ничего не получается, если атрибут вершины 0 не включен в массив. Вы можете использовать bindAttribLocation(), чтобы заставить атрибут вершин использовать местоположение 0 и использовать enableVertexAttribArray(), чтобы сделать его включенным массивом.

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

Я не могу найти никакого упоминания об этом правиле в любом месте и, конечно же, не на аппаратном обеспечении ATI. Кто-нибудь знает, откуда это правило? Или это ошибка в реализации, которую пользователи Mozilla заметили и предупредили?

Ответ 1

tl; dr: это ошибка драйвера. Core OpenGL 3.3 должен позволить вам не использовать атрибут 0, но профиль совместимости не работает, и некоторые драйверы не реализуют этот переключатель правильно. Просто не забудьте использовать атрибут 0, чтобы избежать каких-либо проблем.

Фактическое содержимое:

Дайте небольшой урок истории о том, как появилась спецификация OpenGL.

В самые древние дни OpenGL существовал ровно один способ рендеринга: немедленный режим (т.е. glBegin/glVertex/glColor/glEtc/glEnd). Отображаемые списки существовали, но они всегда определялись как простое пересылку захваченных команд. Таким образом, хотя реализации фактически не выполняли все эти вызовы функций, реализации все равно будут вести себя так, как если бы они были.

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

Вершинные массивы на основе буфера были определены таким же образом, хотя с немного усложненным языком, вытаскивая из хранилища сервера вместо клиентского хранилища.

Затем произошло что-то: ARB_vertex_program (а не ARB_vertex_shader. Я говорю о программах сборки).

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

Режим Immedate работает следующим образом:

glBegin(...);
glTexCoord(...);
glColor(...);
glVertex(...);
glTexCoord(...);
glColor(...);
glVertex(...);
glTexCoord(...);
glColor(...);
glVertex(...);
glEnd();

Каждый раз, когда вы вызываете glVertex, это приводит к тому, что все текущее состояние атрибута используется для одной вершины. Все остальные функции немедленного режима просто устанавливают значения в контексте; эта функция фактически отправляет вершину в OpenGL для обработки. Это очень важно в непосредственном режиме. И так как каждая вершина должна иметь место в землях фиксированной функции, имеет смысл использовать эту функцию, чтобы решить, когда должна обрабатываться вершина.

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

По соглашению они привязали это значение к атрибуту 0. Поэтому всякая немедленная рендеринг режима должна использовать либо атрибут 0, либо glVertex для отправки вершины.

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

Затем появился OpenGL 3.0. Они не рекомендовали немедленный режим. Устаревшее не означает удаление; спецификация все еще имела эти функции в ней, и все рендеринг массива вершин все еще определялся в терминах их.

OpenGL 3.1 фактически вырвал старый материал. И это создавало проблему языка. В конце концов, каждая команда рисования массива всегда определялась в терминах немедленного режима. Но как только немедленный режим больше не существует... как вы его определяете?

Поэтому им пришлось придумать новый язык для ядра OpenGL 3.1+. При этом они удалили бессмысленное ограничение на необходимость использования атрибута 0.

Но профиль совместимости не сделал.

Поэтому правила OpenGL 3.2+ - это. Если у вас есть основной профиль OpenGL, вам не нужно использовать атрибут 0. Если у вас есть профиль OpenGL совместимости, вы должны использовать атрибут 0 (или glVertex). Об этом говорится в спецификации.

Но это не то, что реализуют реализации.

В общем, NVIDIA никогда не заботилась о правиле "должен использовать атрибут 0" и просто делает это так, как вы ожидали бы, даже в профилях совместимости. Таким образом, нарушив букву спецификации. AMD, как правило, чаще придерживается спецификации. Однако они забыли реализовать правильное поведение ядра. Таким образом, NVIDIA тоже допускает совместимость, а AMD слишком ограничительна для ядра.

Чтобы обойти эти ошибки драйверов, просто всегда используйте атрибут 0.

Кстати, если вам интересно, NVIDIA выиграла. В OpenGL 4.3 профиль совместимости использует ту же формулировку для своих команд рендеринга массивов, что и ядро. Таким образом, вы можете не использовать атрибут 0 как для ядра, так и для совместимости.