모든 WebGL 프로그램이 모든 기기 혹은 브라우저에서 작동하지 않아도 충격으로 다가오지 않을 겁니다. WebGL2의 경우, 적어도 2020년 7월 현재, 사파리에서 지원되지 않습니다.
다음은 제 머릿속에 있는 발생 가능한 대부분의 문제입니다.
고사양 GPU는 저사양 GPU보다 100배 더 빠르게 돌아갈 수 있습니다. 제가 아는 유일한 방법은 저사양을 기준으로 하거나, 대부분의 데스크탑 PC 앱처럼 성능이나 품질을 선택할 수 있는 옵션을 사용자에게 제공하는 겁니다.
비슷하게 고사양 GPU는 12에서 24기가의 램을 가질 수 있는 반면 저사양 GPU는 1기가보다 적을 수 있습니다. (제가 나이가 많아서 16k에서 64k의 메모리를 가진 컴퓨터로 프로그래밍을 시작했기 때문에 저사양 = 1기가인 것이 저에겐 놀랍습니다 😜)
WebGL은 다양한 최소 지원 기능이 있지만 로컬 장치에서는 최소 범위보다 많이 지원할 수 있으며 이는 더 적게 지원하는 다른 기기에서 실패한다는 걸 의미합니다.
예제:
허용되는 최대 텍스처 크기
2048이나 4096은 합리적인 제한으로 보입니다. 적어도 2020년 현재 99%의 기기들이 4096를 지원하지만 50%만이 4096 이상을 지원합니다.
참고: 최대 텍스처 크기는 GPU가 처리할 수 있는 최대 크기입니다. 이건 GPU가 해당 크기를 제곱(2D 텍스처용)하거나 세제곱(3D 텍스처용)하는데 충분한 메모리가 있다는 뜻이 아닌데요. 예를 들어 일부 GPU는 16384의 최대 크기를 가집니다. 하지만 각 면이 16384인 3D 텍스처는 16TB의 메모리를 필요로 합니다!!!
단일 프로그램의 정점 속성 최대 개수
WebGL1에서 지원되는 최소값은 8입니다. WebGL2는 16입니다. 그 이상을 사용하고 있다면 최소값만 지원하는 컴퓨터에서 코드가 실패할 겁니다.
유니폼 벡터의 최대 개수
정점 셰이더와 프래그먼트 셰이더에 각각 따로 지정됩니다.
WebGL1에서는 정점 셰이더에 128개 그리고 프래그먼트 셰이더에 16개입니다. WebGL2에서는 정점 셰이더에 256개 그리고 프래그먼트 셰이더에 224개입니다.
참고로 유니폼은 "패킹"될 수 있음으로 위 숫자는 사용할 수 있는 vec4
의 개수인데요.
이론적으로는 float
유니폼 개수의 4배를 가질 수 있습니다.
하지만 그것들을 끼워 맞추는 알고리즘이 있는데요.
위의 최대 유니폼 벡터 각각에 대해 한 행, 4개의 열이 있는 배열로 공간을 상상할 수 있습니다.
+-+-+-+-+
| | | | | <- vec4 1개
| | | | | |
| | | | | |
| | | | | V
| | | | | 최대 유니폼 벡터 행
| | | | |
| | | | |
| | | | |
...
먼저 vec4
는 mat4
가 4개의 vec4
로 할당됩니다.
다음으로 vec3
는 공간 왼쪽에 넣습니다.
그런 다음 vec2
에 이어 float
가 따라옵니다.
그래서 mat4
1개, vec3
2개, vec2
2개, float
3개가 있다고 상상해 보면
+-+-+-+-+
|m|m|m|m| <- mat4는 4개의 행을 가짐
|m|m|m|m|
|m|m|m|m|
|m|m|m|m|
|3|3|3| | <- vec3 2개는 행 2개를 가짐
|3|3|3| |
|2|2|2|2| <- vec2 2개는 행 1개로 압축 가능
|f|f|f| | <- float 3개는 행 1개에 들어감
...
게다가 유니폼 배열은 항상 수직이므로, 예를 들어 허용되는 유니폼 벡터 최대값이 16개면 요소 17개의 float
배열을 가질 수 없고, 실제로 전체 행을 차지하는 하나의 vec4
가 있는 경우 15개의 행만 남으며, 이는 가질 수 있는 가장 큰 배열이 15개의 요소가 된다는 걸 의미합니다.
제 조언은 완벽한 패킹을 기대하지 말라는 겁니다. 비록 명세서에는 위 알고리즘을 통과해야 한다고 나와 있지만 모든 드라이버를 통과하는지 테스트하기엔 조합이 너무 많습니다. 그냥 한도에 가까워지고 있는지 확인해주세요.
참고: Varying과 속성은 패킹할 수 없습니다.
Varying 벡터 최대값
WebGL1의 최소값은 8입니다. WebGL2는 16입니다.
그 이상을 사용한다면 최소값만 지원하는 컴퓨터에서 코드가 실패할 겁니다.
텍스처 유닛 최대값
여기에는 3가지 값이 있습니다.
WebGL1 | WebGL2 | |
---|---|---|
존재하는 텍스처 유닛 최소값 | 8 | 32 |
정점 셰이더가 참조할 수 있는 텍스처 유닛 최소값 | 0! | 16 |
프래그먼트 셰이더가 참조할 수 있는 텍스처 유닛 최소값 | 8 | 16 |
WebGL1에서 정점 셰이더에 대한 값이 0임을 주의해야 합니다. 참고로 너무 낙담하지 않아도 괜찮은데요. 모든 장치의 약 97%가 최소 4개를 지원합니다. 그래도, 앱이 동작하지 않을 수 있다고 사용자에게 알릴 수 있는지 혹은 다른 셰이더로 폴백할 수 있는지 확인하는 게 좋습니다.
다른 제한도 있는데요.
이를 조회하기 위해 다음의 값으로 gl.getParameter
를 호출합니다.
MAX_TEXTURE_SIZE | 텍스처 최대 크기 |
MAX_VERTEX_ATTRIBS | 가질 수 있는 속성 개수 |
MAX_VERTEX_UNIFORM_VECTORS | 정점 셰이더가 가질 수 있는 vec4 유니폼 개수 |
MAX_VARYING_VECTORS | 가지고 있는 varying 개수 |
MAX_COMBINED_TEXTURE_IMAGE_UNITS | 존재하는 텍스처 유닛 개수 |
MAX_VERTEX_TEXTURE_IMAGE_UNITS | 정점 셰이더가 참조할 수 있는 텍스처 유닛 개수 |
MAX_TEXTURE_IMAGE_UNITS | 프래그먼트 셰이더가 참조할 수 있는 텍스처 유닛 개수 |
MAX_FRAGMENT_UNIFORM_VECTORS | 프래그먼트 셰이더가 가질 수 있는 vec4 유니폼 개수 |
MAX_CUBE_MAP_TEXTURE_SIZE | 큐브맵 최대 크기 |
MAX_RENDERBUFFER_SIZE | 렌더 버퍼 최대 크기 |
MAX_VIEWPORT_DIMS | 뷰포트 최대 크기 |
이건 전체 목록이 아닌데요. 최대 점 크기와 최대 선 두께 등이 있지만 기본적으로 최대 선 두께는 1.0이고 POINTS는 클리핑 문제를 신경쓰지 않아도 되는 간단한 데모에서만 유용하다고 가정해야 합니다.
WebGL2는 몇 가지 더 추가하는데요. 일반적인 것들은 다음과 같습니다.
MAX_3D_TEXTURE_SIZE | 3D 텍스처 최대 크기 |
MAX_DRAW_BUFFERS | 가질 수 있는 색상 attachment 개수 |
MAX_ARRAY_TEXTURE_LAYERS | 2D 텍스처 배열의 최대 레이어 |
MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS | 변환 피드백을 사용할 때 별도의 버퍼로 출력할 수 있는 varying 개수 |
MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS | 모든 걸 단일 버퍼로 보낼 때 출력할 수 있는 varying 개수 |
MAX_COMBINED_UNIFORM_BLOCKS | 종합적으로 사용할 수 있는 유니폼 블록 개수 |
MAX_VERTEX_UNIFORM_BLOCKS | 정점 셰이더가 사용할 수 있는 유니폼 블록 개수 |
MAX_FRAGMENT_UNIFORM_BLOCKS | 프래그먼트 셰이더가 사용할 수 있는 유니폼 블록 개수 |
정말 오래된 일부 기기들은 16비트 깊이 버퍼를 사용합니다. 그렇지 않은, 99%의 기기들은 24비트 깊이 버퍼를 사용하므로 걱정하지 않아도 됩니다.
특정 포맷/타입 조합만 작동이 보장됩니다. 다른 조합들은 선택적인데요. 이 내용은 이 글에서 다룹니다.
프레임 버퍼는 텍스처와 렌더 버퍼의 attachment를 하나 이상 가질 수 있습니다.
WebGL1에서는 3개의 attachment 조합만 동작이 보장됩니다.
RGBA
, 타입 = UNSIGNED_BYTE
텍스처는 COLOR_ATTACHMENT0
로 첨부RGBA
, 타입 = UNSIGNED_BYTE
텍스처는 COLOR_ATTACHMENT0
로 그리고 포맷 = DEPTH_COMPONENT
렌더 버퍼는 DEPTH_ATTACHMENT
로 첨부RGBA
, 타입 = UNSIGNED_BYTE
텍스처는 COLOR_ATTACHMENT0
로 그리고 포맷 = DEPTH_STENCIL
렌더 버퍼는 DEPTH_STENCIL_ATTACHMENT
로 첨부다른 모든 조합은 gl.checkFramebufferStatus
를 호출하여 FRAMEBUFFER_COMPLETE
를 반환했는지 확인하는 구현에 따라 달라집니다.
WebGL2는 더 많은 포맷을 쓸 수 있도록 보장하지만 어떤 조합이 실패할 수 있다는 한계는 여전한데요. 1개보다 많이 첨부한다면 모든 색상 attachment가 같은 포맷일 경우가 가장 안전한 방법일 수 있습니다.
WebGL1과 WebGL2의 많은 기능들이 선택적인데요.
getExtension
라는 API가 갖는 요점은 확장이 존재하지 않으면 실패할 수 있으므로 실패를 확인하고 맹목적으로 성공할 것이라 가정하지 말아야 합니다.
아마 WebGL1과 WebGL2에서 가장 흔하게 누락되는 확장은 부동 소수점 텍스처를 필터링할 수 있는 OES_texture_float_linear
인데, 이는 TEXTURE_MIN_FILTER
와 TEXTURE_MAX_FILTER
를 NEAREST
를 제외한 모든 항목으로 설정하도록 지원하는 기능을 의미합니다.
많은 모바일 기기들이 이걸 지원하지 않습니다.
WebGL1에서 종종 누락되는 또 다른 확장은 2개 이상의 색상 attachment를 프레임 버퍼로 첨부할 수 있는 기능인 WEBGL_draw_buffers
이며 여전히 데스크탑의 경우 70% 정도이고 스마트폰의 경우 거의 없습니다.
기본적으로 WebGL2를 실행할 수 있는 모든 기기는 WebGL1에서 WEBGL_draw_buffers
도 지원해야 하지만 여전히 문제가 있는데요.
여러 텍스처를 한 번에 렌더링해야 한다면 고사양 GPU로도 시간이 필요할 수 있습니다.
그래도 사용자 기기가 지원하는지 확인하고, 지원하지 않는다면 친절한 설명을 제공해야 합니다.
WebGL1의 경우 다음의 3개의 확장이 거의 보편적으로 지원되는 것처럼 보이므로 사용자에게 이들이 누락되면 페이지가 작동하지 않을 것이라 경고하고 싶을 수 있지만 사용자가 페이지를 제대로 실행하지 못 할만큼 아주 오래된 기기를 가지고 있을 수 있습니다.
ANGLE_instance_arrays
(instanced drawing에 사용하는 기능), OES_vertex_array_object
(단일 함수 호출로 모든 상태를 바꿀 수 있도록 모든 속성 상태를 객체에 저장하는 기능), OES_element_index_uint
(drawElements
로 UNSIGNED_INT
32비트 인덱스를 사용하는 기능)
버그는 속성 위치를 찾지 못하는 겁니다. 예를 들어 이런 정점 셰이더가 있다면
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.linkProgram
호출 전에 gl.bindAttribLocation
을 호출하여 위치 할당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
일 경우는 정의되지 않았습니다.
스포트라이트에 대한 글의 하단에 더 긴 목록이 있습니다
2020년 여기서 가장 큰 문제는 셰이더에서 mediump
나 lowp
를 사용하면 데스크탑의 GPU는 highp
를 사용하지만 모바일은 mediump
나 lowp
가 되는 것이므로 데스크탑으로 개발할 때는 어떤 문제도 알아차리지 못 할 겁니다.
자세한 내용은 이 글을 봐주세요.
WebGL의 POINTS
와 LINES
는 최대 크기 1을 가질 수 있고 현재 가장 일반적인 제한인 LINES
의 경우입니다.
또한 중심이 뷰포트 외부에 있을 때 포인트의 클리핑 여부는 구현에 정의됩니다.
이 글의 하단을 봐주세요.
마찬가지로, 뷰포트가 정점만 클리핑을 하는지 혹은 픽셀도 클리핑을 하는지 여부는 정의되지 않았습니다. 시저는 항상 픽셀 클리핑하기 때문에 시저 테스트를 켜고, 그리려는 것들과 그리고 있는 LINES나 POINTS보다 뷰포트를 작게 설정했다면 시저 크기를 설정하세요.
사파리는 다른 최신 브라우저들보다 많은 WebGL 버그를 가지고 있으며 애플은 이들을 고치는 데 전혀 관심이 없는 걸로 보입니다.
몇 년간 고쳐지지 않은 버그의 일부 목록
toDataURL(그리고 toBlob)이 premultipledAlpha = false인 경우 거꾸로 결과를 반환하는 버그 (4년 전)
현재 iOS에서 preserveDrawingBuffer = true일 때 잘못된 이중 버퍼 버그 (4년 전)
OES_texture_float
구현이 non-ArrayBufferView 엔트리 포인트를 지원해야 하는 버그 (10년 전)
readPixels가 RGBA/UNSIGNED_BYTE에 대해 INVALID_ENUM를 생성하는 버그 (3년 전)
WebGL 캔버스와 레이어 변환에 대한 변경 사항이 항상 동기화되지 않는 버그 (3년 전)
알파 채널이 없는 PNG 텍스처가 잘못된 rgb 색상을 가지는 버그 (4년 전)
사파리가 일반적인 속성 없이 쓰는 경우를 처리하지 않는 버그 (1년 전)
또한 사파리에는 WebGL2를 활성화하는 옵션이 있지만 #version 300 es
셰이더를 허용하는 게 전부입니다.
다른 80개 이상의 모든 WebGL2 api 함수는 적어도 2020년 7월 기준 구현되어 있지 않습니다.
Source를 보고 "not implemented"를 검색해보세요.