WebGL에서 WebGPU까지

프랑수아 보포르
프랑수아 보포르

WebGL 개발자라면 최신 그래픽 API의 향상된 기능을 웹에 제공하는 WebGL의 뒤를 잇는 WebGPU를 사용하기 시작하겠다는 두려움과 감동을 느낄 수 있습니다.

WebGL과 WebGPU가 많은 핵심 개념을 공유한다는 사실은 안심이 됩니다. 두 API 모두 GPU에서 셰이더라는 작은 프로그램을 실행할 수 있습니다. WebGL은 꼭짓점 셰이더와 프래그먼트 셰이더를 지원하고 WebGPU는 컴퓨팅 셰이더도 지원합니다. WebGL은 OpenGL Shading Language (GLSL)를 사용하고 WebGPU는 WGSL (WebGPU Shading Language)을 사용합니다. 두 언어는 서로 다르지만 기본 개념은 대부분 동일합니다.

이를 염두에 두고 이 문서에서는 시작하는 데 도움이 되도록 WebGL과 WebGPU의 몇 가지 차이점을 중점적으로 설명합니다.

전역 상태

WebGL에는 많은 전역 상태가 있습니다. 일부 설정은 바인딩되는 텍스처와 버퍼와 같은 모든 렌더링 작업에 적용됩니다. 다양한 API 함수를 호출하여 이 전역 상태를 설정하며, 상태를 변경할 때까지 계속 적용됩니다. WebGL의 전역 상태는 전역 설정 변경을 잊어버리기 쉽기 때문에 오류의 주요 원인입니다. 또한 전역 상태는 코드 공유를 어렵게 만듭니다. 개발자가 코드의 다른 부분에 영향을 미치는 방식으로 전역 상태를 실수로 변경하지 않도록 주의해야 하기 때문입니다.

WebGPU는 스테이트리스(Stateless) API이며 전역 상태를 유지하지 않습니다. 대신 파이프라인 개념을 사용하여 WebGL에서 전역이었던 모든 렌더링 상태를 캡슐화합니다. 파이프라인에는 사용할 혼합, 토폴로지, 속성과 같은 정보가 포함됩니다. 파이프라인은 변경할 수 없습니다. 일부 설정을 변경하려면 다른 파이프라인을 만들어야 합니다. 또한 WebGPU는 명령어 인코더를 사용하여 명령어를 함께 일괄 처리하고 기록된 순서대로 실행합니다. 이는 예컨대 객체에 대한 단일 패스로 애플리케이션이 각 광의 그림자 맵에 하나씩 여러 개의 명령 스트림을 기록할 수 있는 그림자 매핑에 유용합니다.

요약하자면, WebGL의 글로벌 상태 모델로 인해 강력하고 구성 가능한 라이브러리와 애플리케이션을 만드는 것이 어렵고 취약하기 때문에 WebGPU는 개발자가 GPU로 명령어를 전송하는 동안 추적해야 하는 상태의 양을 크게 줄였습니다.

더 이상 동기화하지 않음

GPU에서는 일반적으로 명령어를 전송하고 동시에 기다리는 것이 비효율적입니다. 파이프라인이 플러시되고 버블이 발생할 수 있기 때문입니다. 이는 자바스크립트와 별도의 프로세스에서 GPU 드라이버가 실행되는 다중 프로세스 아키텍처를 사용하는 WebGPU와 WebGL에서 특히 그렇습니다.

예를 들어 WebGL에서 gl.getError()를 호출하려면 자바스크립트 프로세스에서 GPU 프로세스로, 그리고 이 프로세스 간에 동기식 IPC가 필요합니다. 이로 인해 두 프로세스가 통신할 때 CPU 측에 버블이 발생할 수 있습니다.

이러한 버블을 피하기 위해 WebGPU는 완전히 비동기식으로 설계되었습니다. 오류 모델 및 기타 모든 작업은 비동기식으로 발생합니다. 예를 들어 텍스처를 생성하면 텍스처가 실제로 오류이더라도 작업이 즉시 성공하는 것처럼 보입니다. 비동기식으로만 오류를 찾을 수 있습니다. 이러한 설계는 프로세스 간 통신을 버블 없이 유지하고 애플리케이션의 안정적인 성능을 제공합니다.

컴퓨팅 셰이더

컴퓨팅 셰이더는 GPU에서 실행되어 범용 계산을 수행하는 프로그램입니다. 이 기능은 WebGL이 아닌 WebGPU에서만 사용할 수 있습니다.

꼭짓점 및 프래그먼트 셰이더와 달리 셰이더는 그래픽 처리에 제한되지 않으며 머신러닝, 물리 시뮬레이션, 과학 컴퓨팅과 같은 다양한 작업에 사용할 수 있습니다. 컴퓨팅 셰이더는 수백 또는 수천 개의 스레드에 의해 병렬로 실행되므로 대규모 데이터 세트를 처리하는 데 매우 효율적입니다. GPU 컴퓨팅에 관한 자세한 내용과 WebGPU에 관한 이 포괄적인 도움말에서 자세한 내용을 알아보세요.

동영상 프레임 처리

JavaScript 및 WebAssembly를 사용하여 동영상 프레임을 처리하면 GPU 메모리에서 CPU 메모리로 데이터를 복사하는 비용과 작업자 및 CPU 스레드로 달성할 수 있는 제한된 병렬 처리라는 몇 가지 단점이 있습니다. WebGPU에는 이러한 제한사항이 없으므로 WebCodecs API와의 긴밀한 통합 덕분에 동영상 프레임 처리에 적합합니다.

다음 코드 스니펫은 VideoFrame을 WebGPU에서 외부 텍스처로 가져와 처리하는 방법을 보여줍니다. 이 데모를 사용해 보세요.

// Init WebGPU device and pipeline...
// Configure canvas context...
// Feed camera stream to video...

(function render() {
  const videoFrame = new VideoFrame(video);
  applyFilter(videoFrame);
  requestAnimationFrame(render);
})();

function applyFilter(videoFrame) {
  const texture = device.importExternalTexture({ source: videoFrame });
  const bindgroup = device.createBindGroup({
    layout: pipeline.getBindGroupLayout(0),
    entries: [{ binding: 0, resource: texture }],
  });
  // Finally, submit commands to GPU
}

애플리케이션 이식성 기본

WebGPU에서 limits를 요청해야 합니다. 기본적으로 requestDevice()은 실제 기기의 하드웨어 기능과 일치하지 않을 수 있는 GPUDevice 기기를 반환합니다. 오히려 모든 GPU의 합리적이고 최소 공통분모를 반환합니다. WebGPU는 개발자에게 기기 이용 한도를 요청하도록 함으로써 최대한 많은 기기에서 애플리케이션을 실행할 수 있도록 합니다.

캔버스 처리

사용자가 WebGL 컨텍스트를 생성하고 알파, 안티앨리어스, colorSpace, 깊이, keepDrawingBuffer 또는 스텐실과 같은 컨텍스트 속성을 제공하면 WebGL이 자동으로 캔버스를 관리합니다.

반면 WebGPU를 사용하려면 사용자가 캔버스를 직접 관리해야 합니다. 예를 들어 WebGPU에서 앤티앨리어싱을 수행하려면 멀티샘플 텍스처를 생성하고 여기에 렌더링해야 합니다. 그런 다음 멀티샘플 텍스처를 일반 텍스처로 리졸브하고 캔버스에 해당 텍스처를 그립니다. 이러한 수동 관리를 통해 단일 GPUDevice 객체에서 원하는 만큼 캔버스에 출력할 수 있습니다. 반면에 WebGL은 캔버스당 하나의 컨텍스트만 만들 수 있습니다.

WebGPU 다중 캔버스 데모를 확인하세요.

참고로 현재 브라우저에서는 페이지당 WebGL 캔버스 수가 제한되어 있습니다. 이 글을 작성하는 시점 기준으로 Chrome과 Safari는 최대 16개의 WebGL 캔버스만 동시에 사용할 수 있었으며, Firefox는 최대 200개의 WebGL 캔버스를 만들 수 있습니다. 반면, 페이지당 WebGPU 캔버스의 수에는 제한이 없습니다.

Safari, Chrome, Firefox 브라우저에서 최대 WebGL 캔버스 수를 표시할 수 있는 스크린샷
Safari, Chrome, Firefox의 최대 WebGL 캔버스 수 (왼쪽에서 오른쪽) - 데모

유용한 오류 메시지

WebGPU는 API에서 반환되는 모든 메시지에 대한 호출 스택을 제공합니다. 즉, 코드에서 오류가 발생한 위치를 빠르게 확인할 수 있으므로 오류를 디버깅하고 수정하는 데 유용합니다.

WebGPU 오류 메시지는 호출 스택을 제공할 뿐만 아니라 이해하고 실행하기도 쉽습니다. 오류 메시지에는 일반적으로 오류에 대한 설명과 오류 해결 방법에 대한 제안이 포함됩니다.

WebGPU를 사용하면 각 WebGPU 객체에 맞춤 label를 제공할 수도 있습니다. 이 라벨은 브라우저에서 GPUError 메시지, 콘솔 경고, 브라우저 개발자 도구에서 사용됩니다.

이름에서 색인으로

WebGL에서는 많은 것이 이름으로 연결됩니다. 예를 들어 GLSL에서 myUniform라는 균일 변수를 선언하고 gl.getUniformLocation(program, 'myUniform')를 사용하여 그 위치를 가져올 수 있습니다. 균일한 변수의 이름을 잘못 입력하면 오류가 발생할 때 유용합니다.

반면, WebGPU에서는 모든 것이 바이트 오프셋 또는 색인 (위치라고도 함)으로 완전히 연결됩니다. WGSL과 자바스크립트에서 코드의 위치를 동기화하는 것은 개발자의 책임입니다.

밉맵 생성

WebGL에서는 텍스처 수준 0 mip를 만든 다음 gl.generateMipmap()를 호출할 수 있습니다. 그러면 WebGL이 다른 모든 mip 수준을 생성합니다.

WebGPU에서는 밉맵을 직접 생성해야 합니다. 이를 위한 기본 제공 함수는 없습니다. 이 결정에 대해 자세히 알아보려면 사양 토론을 참조하세요. webgpu-utils와 같은 편리한 라이브러리를 사용하여 밉맵을 생성하거나 직접 만드는 방법을 알아볼 수 있습니다.

스토리지 버퍼 및 스토리지 텍스처

균일한 버퍼는 WebGL과 WebGPU에서 모두 지원되며 제한된 크기의 상수 매개변수를 셰이더에 전달할 수 있습니다. 균일 버퍼와 매우 유사한 스토리지 버퍼는 WebGPU에서만 지원되며 균일 버퍼보다 더 강력하고 유연합니다.

  • 셰이더에 전달되는 저장소 버퍼 데이터는 균일 버퍼보다 훨씬 클 수 있습니다. 사양에 따르면 균일한 버퍼 결합의 크기는 최대 64KB (maxUniformBufferBindingSize 참고)이지만 스토리지 버퍼 결합의 최대 크기는 WebGPU에서 최소 128MB입니다 (maxStorageBufferBindingSize 참고).

  • 저장소 버퍼는 쓰기가 가능하고 일부 원자적 작업을 지원하지만 균일한 버퍼는 읽기 전용입니다. 이를 통해 새로운 알고리즘 클래스를 구현할 수 있습니다.

  • 저장소 버퍼 결합은 보다 유연한 알고리즘을 위해 런타임 크기의 배열을 지원하는 반면, 균일한 버퍼 배열 크기는 셰이더에 제공해야 합니다.

스토리지 텍스처는 WebGPU에서만 지원되며 스토리지 버퍼가 무엇인지 균일 버퍼에 텍스처링하는 역할을 합니다. 일반 텍스처보다 유연하여 임의 액세스 쓰기 및 읽기도 지원합니다.

버퍼 및 텍스처 변경사항

WebGL에서 버퍼나 텍스처를 만들고 예를 들어 언제든지 gl.bufferData()gl.texImage2D()를 사용하여 크기를 변경할 수 있습니다.

WebGPU에서 버퍼와 텍스처는 변경할 수 없습니다. 즉, 템플릿을 만든 후에는 크기, 용도 또는 형식을 변경할 수 없습니다. 콘텐츠만 변경할 수 있습니다.

공백 규칙 차이

WebGL에서 Z 클립 공간 범위는 -1~1입니다. WebGPU에서 Z 클립 공간 범위는 0~1입니다. 즉, z 값이 0인 객체는 카메라에 가장 가깝고 z 값이 1인 객체는 가장 먼 거리에 있습니다.

WebGL 및 WebGPU의 Z 클립 공간 범위 그림
WebGL 및 WebGPU의 Z 클립 공간 범위입니다.

WebGL은 OpenGL 규칙을 사용합니다. 여기서 Y축은 위쪽이고 Z축은 뷰어를 향합니다. WebGPU는 Y축이 아래쪽에 있고 Z축이 화면 외부에 있는 메탈 규칙을 사용합니다. Y축 방향은 프레임 버퍼 좌표, 표시 영역 좌표, 프래그먼트/픽셀 좌표에서 아래쪽입니다. 클립 공간에서 Y축 방향은 WebGL에서처럼 여전히 위쪽에 있습니다.

감사의 말씀

이 글을 검토해 주신 코렌틴 월레즈, 그레그 타바레스, 스티븐 화이트, 켄 러셀, 레이첼 앤드류님께 감사드립니다.

또한 WebGPU와 WebGL의 차이점을 자세히 살펴보려면 WebGPUFundamentals.org를 사용하는 것이 좋습니다.