Вероятно, не будет шоком то, что не все WebGL программы работают на всех устройствах или браузерах.
Вот список большинства проблем, с которыми вы можете столкнуться, с верхушки моей головы
Топовый GPU, вероятно, работает в 100 раз быстрее, чем низкоуровневый GPU. Единственный способ обойти это, который я знаю, это либо целиться низко, либо давать пользователю опции, как делают большинство Desktop PC приложений, где они могут выбрать производительность или качество.
Аналогично топовый GPU может иметь от 12 до 24 гигабайт оперативной памяти, тогда как низкоуровневый GPU вероятно имеет меньше 1 гигабайта. (Я старый, поэтому для меня удивительно, что низкоуровневый = 1 гигабайт, поскольку я начал программировать на машинах с 16k до 64k памяти 😜)
WebGL имеет различные минимально поддерживаемые функции, но ваше локальное устройство может поддерживать больше, чем этот минимум, что означает, что оно не сработает на других устройствах, которые поддерживают меньше.
Примеры включают:
Максимальный размер текстуры
2048 или 4096 кажутся разумными ограничениями. По крайней мере, по состоянию на 2020 год выглядит, что 99% устройств поддерживают 4096, но только 50% поддерживают > 4096.
Примечание: максимальный размер текстуры - это максимальное измерение, которое GPU может обработать. Это не означает, что у GPU достаточно памяти для этого измерения в квадрате (для 2D текстуры) или в кубе (для 3D текстуры). Например, некоторые GPU имеют максимальный размер 16384. Но 3D текстура 16384 с каждой стороны потребовала бы 16 терабайт памяти!!!
Максимальное количество вершинных атрибутов в одной программе
В WebGL1 минимально поддерживается 8. В WebGL2 это 16. Если вы используете больше, чем это, то ваш код не сработает на машине только с минимумом
Максимальное количество uniform векторов
Они указаны отдельно для вершинных шейдеров и фрагментных шейдеров.
В WebGL1 это 128 для вершинных шейдеров и 16 для фрагментных шейдеров В WebGL2 это 256 для вершинных шейдеров и 224 для фрагментных шейдеров
Обратите внимание, что uniforms могут быть “упакованы”, поэтому число выше - это сколько vec4
можно использовать. Теоретически вы могли бы иметь в 4 раза больше float
uniforms.
но есть алгоритм, который их помещает. Вы можете представить пространство как
массив с 4 столбцами, одна строка для каждого из максимальных uniform векторов выше.
+-+-+-+-+
| | | | | <- один vec4
| | | | | |
| | | | | |
| | | | | V
| | | | | максимальные uniform векторы строк
| | | | |
| | | | |
| | | | |
...
Сначала выделяются vec4
с mat4
как 4 vec4
. Затем vec3
помещаются
в оставшееся пространство. Затем vec2
с последующими float
. Так что представьте, что у нас было 1
mat4
, 2 vec3
, 2 vec2
и 3 float
+-+-+-+-+
|m|m|m|m| <- mat4 занимает 4 строки
|m|m|m|m|
|m|m|m|m|
|m|m|m|m|
|3|3|3| | <- 2 vec3 занимают 2 строки
|3|3|3| |
|2|2|2|2| <- 2 vec2 могут втиснуться в 1 строку
|f|f|f| | <- 3 float помещаются в одну строку
...
Далее, массив uniforms всегда вертикальный, поэтому, например, если максимально
разрешенные uniform векторы - 16, то вы не можете иметь 17-элементный float
массив
и на самом деле, если у вас был один vec4
, это заняло бы целую строку, поэтому осталось
только 15 строк, что означает, что самый большой массив, который вы можете иметь, будет 15
элементов.
Мой совет, однако, не рассчитывайте на идеальную упаковку. Хотя спецификация говорит, что алгоритм выше требуется для прохождения, слишком много комбинаций для тестирования, что все драйверы проходят. Просто будьте в курсе, если вы приближаетесь к лимиту.
примечание: varyings и attributes не могут быть упакованы.
Максимальные varying векторы.
WebGL1 минимум 8. WebGL2 это 16.
Если вы используете больше, ваш код не будет работать на машине только с минимумом.
Максимальные texture units
Здесь 3 значения.
WebGL1 | WebGL2 | |
---|---|---|
мин texture units которые существуют | 8 | 32 |
мин texture units на которые может ссылаться вершинный шейдер | 0! | 16 |
мин texture units на которые может ссылаться фрагментный шейдер | 8 | 16 |
Важно отметить 0 для вершинного шейдера в WebGL1. Обратите внимание, что это, вероятно, не конец света. По-видимому, ~97% всех устройств поддерживают по крайней мере 4. Тем не менее, вы можете захотеть проверить, чтобы вы могли либо сказать пользователю, что ваше приложение не будет работать для них, либо вы можете откатиться к каким-то другим шейдерам.
Есть и другие ограничения. Чтобы их посмотреть, вы вызываете gl.getParameter
с
следующими значениями.
MAX_TEXTURE_SIZE | макс размер текстуры |
MAX_VERTEX_ATTRIBS | количество атрибутов которые вы можете иметь |
MAX_VERTEX_UNIFORM_VECTORS | количество vec4 uniforms которые может иметь вершинный шейдер |
MAX_VARYING_VECTORS | количество varyings которые у вас есть |
MAX_COMBINED_TEXTURE_IMAGE_UNITS | количество texture units которые существуют |
MAX_VERTEX_TEXTURE_IMAGE_UNITS | количество texture units на которые может ссылаться вершинный шейдер |
MAX_TEXTURE_IMAGE_UNITS | количество texture units на которые может ссылаться фрагментный шейдер |
MAX_FRAGMENT_UNIFORM_VECTORS | количество vec4 uniforms которые может иметь фрагментный шейдер |
MAX_CUBE_MAP_TEXTURE_SIZE | макс размер cubemap |
MAX_RENDERBUFFER_SIZE | макс размер renderbuffer |
MAX_VIEWPORT_DIMS | макс размер viewport |
Это не весь список. Например, максимальный размер точки и максимальная толщина линии, но вы должны в основном предполагать, что максимальная толщина линии 1.0 и что POINTS полезны только для простых демо, где вам все равно на проблемы с обрезкой.
WebGL2 добавляет еще несколько. Несколько общих:
MAX_3D_TEXTURE_SIZE | макс размер 3D текстуры |
MAX_DRAW_BUFFERS | количество color attachments которые вы можете иметь |
MAX_ARRAY_TEXTURE_LAYERS | макс слоев в 2D texture array |
MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS | количество varyings которые вы можете выводить в отдельные буферы при использовании transform feedback |
MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS | количество varyings которые вы можете выводить при отправке их всех в один буфер |
MAX_COMBINED_UNIFORM_BLOCKS | количество uniform blocks которые вы можете использовать в целом |
MAX_VERTEX_UNIFORM_BLOCKS | количество uniform blocks которые может использовать вершинный шейдер |
MAX_FRAGMENT_UNIFORM_BLOCKS | количество uniform blocks которые может использовать фрагментный шейдер |
Несколько действительно старых мобильных устройств используют 16-битные буферы глубины. В противном случае, AFAICT 99% устройств используют 24-битный буфер глубины, поэтому вам, вероятно, не нужно беспокоиться об этом.
Только определенные format/type комбинации гарантированно работают. Другие комбинации опциональны. Это покрыто в этой статье.
Framebuffers могут иметь 1 или более attachments текстур и renderbuffers.
В WebGL1 только 3 комбинации attachments гарантированно работают.
RGBA
, type = UNSIGNED_BYTE
texture как COLOR_ATTACHMENT0
RGBA
, type = UNSIGNED_BYTE
texture как COLOR_ATTACHMENT0
и
format = DEPTH_COMPONENT
renderbuffer прикрепленный как DEPTH_ATTACHMENT
RGBA
, type = UNSIGNED_BYTE
texture как COLOR_ATTACHMENT0
и
format = DEPTH_STENCIL
renderbuffer прикрепленный как DEPTH_STENCIL_ATTACHMENT
Все другие комбинации зависят от реализации, которую вы проверяете, вызывая
gl.checkFramebufferStatus
и видя, вернул ли он FRAMEBUFFER_COMPLETE
.
WebGL2 гарантирует возможность записи во многие другие форматы, но все еще имеет ограничение в том, что любая комбинация может потерпеть неудачу! Ваша лучшая ставка может быть, если все color attachments одного формата, если вы прикрепляете больше 1.
Многие функции WebGL1 и WebGL2 опциональны. Вся суть наличия
API под названием getExtension
в том, что он может потерпеть неудачу, если расширение не существует,
и поэтому вы должны проверять эту неудачу и не слепо предполагать, что это
сработает.
Вероятно, наиболее часто отсутствующее расширение в WebGL1 и WebGL2 - это
OES_texture_float_linear
, что является способностью фильтровать floating point
текстуру, что означает способность поддерживать установку TEXTURE_MIN_FILTER
и
TEXTURE_MAX_FILTER
на что-либо, кроме NEAREST
. Многие мобильные устройства не
поддерживают это.
В WebGL1 другое часто отсутствующее расширение - это WEBGL_draw_buffers
, что является
способностью прикреплять больше 1 color attachment к framebuffer, все еще около
70% для десктопа и почти ни одного для смартфонов (это кажется неправильным).
В основном любое устройство, которое может запускать WebGL2, должно также поддерживать
WEBGL_draw_buffers
в WebGL1, но все же, это, по-видимому, все еще проблема. Если вы
нуждаетесь рендерить в несколько текстур одновременно, вероятно, вашей странице нужен
высокоуровневый GPU в целом. Тем не менее, вы должны проверить, поддерживает ли устройство пользователя это, и
если нет, предоставить дружелюбное объяснение.
Для WebGL1 следующие 3 расширения кажутся почти универсально поддерживаемыми, поэтому, хотя вы можете захотеть предупредить пользователя, что ваша страница не будет работать, если они отсутствуют, вероятно, что у этого пользователя крайне старое устройство, которое все равно не собиралось хорошо запускать вашу страницу.
Это ANGLE_instance_arrays
(способность использовать instanced drawing),
OES_vertex_array_object
(способность хранить все состояние атрибута в объекте, чтобы вы могли поменять все
это состояние одним вызовом функции. Смотрите это), и OES_element_index_uint
(способность использовать UNSIGNED_INT
32 битные индексы с drawElements
).
Полу-общая ошибка - не искать локации атрибутов. Например, у вас есть вершинный шейдер вроде
attribute vec4 position;
attribute vec2 texcoord;
uniform mat4 matrix;
varying vec2 v_texcoord;
void main() {
gl_Position = matrix * position;
v_texcoord = texcoord;
}
Ваш код предполагает, что position
будет атрибутом 0 и texcoord
будет
атрибутом 1, но это не гарантировано. Поэтому он работает для вас, но не срабатывает для кого-то
другого. Часто это может быть ошибкой в том, что вы не делали это намеренно, но
через ошибку в коде вещи работают, когда локации одним способом, но не
другим.
Есть 3 решения.
Всегда искать локации.
Назначать локации, вызывая gl.bindAttribLocation
перед вызовом gl.linkProgram
Только WebGL2, устанавливать локации в шейдере как в
#version 300 es
layout(location = 0) vec4 position;
latout(location = 1) vec2 texcoord;
...
Решение 2 кажется наиболее D.R.Y., тогда как решение 3 кажется наиболее W.E.T., если только вы не генерируете ваши текстуры во время выполнения.
Несколько GLSL функций имеют неопределенное поведение. Например, pow(x, y)
является
неопределенным, если x < 0
. Есть более длинный список в низу статьи о
spot lighting.
В 2020 году самая большая проблема здесь в том, что если вы используете mediump
или lowp
в ваших шейдерах,
то на десктопе GPU действительно будет использовать highp
, но на мобильных они действительно будут
mediump
и или lowp
, и поэтому вы не заметите никаких проблем при разработке на десктопе.
Смотрите эту статью для более подробной информации.
POINTS
и LINES
в WebGL могут иметь максимальный размер 1, и на самом деле для LINES
это теперь наиболее общее ограничение. Далее, обрезаются ли точки, когда их
центр находится вне viewport, определяется реализацией. Смотрите низ
этой статьи.
Аналогично, обрезает ли viewport только вершины или также пиксели, является неопределенным. Scissor всегда обрезает пиксели, поэтому включайте scissor test и устанавливайте размер scissor, если вы устанавливаете viewport меньше, чем то, что вы рисуете, и вы рисуете LINES или POINTS.