목차

WebGLFundamentals.org

Fix, Fork, Contribute

WebGL 크로스 플랫폼 문제

모든 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
       | | | | |   최대 유니폼 벡터 행
       | | | | |
       | | | | |  
       | | | | |
       ...
    

    먼저 vec4mat4가 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가지 값이 있습니다.

    1. 텍스처 유닛 개수
    2. 정점 셰이더가 참조할 수 있는 텍스처 유닛 개수
    3. 프래그먼트 셰이더가 참조할 수 있는 텍스처 유닛 개수
    WebGL1WebGL2
    존재하는 텍스처 유닛 최소값832
    정점 셰이더가 참조할 수 있는 텍스처 유닛 최소값0!16
    프래그먼트 셰이더가 참조할 수 있는 텍스처 유닛 최소값816

    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비트 깊이 버퍼를 사용하므로 걱정하지 않아도 됩니다.

readPixels 포맷/타입 조합

특정 포맷/타입 조합만 작동이 보장됩니다. 다른 조합들은 선택적인데요. 이 내용은 이 글에서 다룹니다.

프레임 버퍼 attachment 조합

프레임 버퍼는 텍스처와 렌더 버퍼의 attachment를 하나 이상 가질 수 있습니다.

WebGL1에서는 3개의 attachment 조합만 동작이 보장됩니다.

  1. 단일 포맷 = RGBA, 타입 = UNSIGNED_BYTE 텍스처는 COLOR_ATTACHMENT0로 첨부
  2. 포맷 = RGBA, 타입 = UNSIGNED_BYTE 텍스처는 COLOR_ATTACHMENT0로 그리고 포맷 = DEPTH_COMPONENT 렌더 버퍼는 DEPTH_ATTACHMENT로 첨부
  3. 포맷 = 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_FILTERTEXTURE_MAX_FILTERNEAREST를 제외한 모든 항목으로 설정하도록 지원하는 기능을 의미합니다. 많은 모바일 기기들이 이걸 지원하지 않습니다.

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(drawElementsUNSIGNED_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가지 해결법이 있습니다.

  1. 항상 위치를 탐색
  2. gl.linkProgram 호출 전에 gl.bindAttribLocation을 호출하여 위치 할당
  3. WebGL2 한정, 다음과 같이 셰이더에서 위치를 설정

    #version 300 es
    layout(location = 0) vec4 position;
    latout(location = 1) vec2 texcoord;
    ...
    

해결법 2가 가장 D.R.Y하게 보이지만, 런타임에 텍스처를 생성하지 않는다면 해결법 3이 가장 W.E.T하게 보입니다.

GLSL 정의되지 않은 동작

여러 GLSL 함수는 정의되지 않은 동작을 가집니다. 예를 들어 pow(x, y)x < 0일 경우는 정의되지 않았습니다. 스포트라이트에 대한 글의 하단에 더 긴 목록이 있습니다

셰이더 정밀도 문제

2020년 여기서 가장 큰 문제는 셰이더에서 mediumplowp를 사용하면 데스크탑의 GPU는 highp를 사용하지만 모바일은 mediumplowp가 되는 것이므로 데스크탑으로 개발할 때는 어떤 문제도 알아차리지 못 할 겁니다.

자세한 내용은 이 글을 봐주세요.

점, 선, 뷰포트, 시저 동작

WebGL의 POINTSLINES는 최대 크기 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"를 검색해보세요.

이슈/버그는? Github에 이슈를 만들어주세요.
코드 블록은 <pre><code>여기에 코드 입력</code></pre>를 사용해주세요
comments powered by Disqus