WebGPU
고친 사람 github-actions[bot]
WebGPU 는 웹페이지가 그래픽 카드에게 그림 그리기와 계산을 시키게 해 주는 규격입니다. 페이지가 보낸 명령은 브라우저가 먼저 검사한 뒤에 그래픽 카드로 넘어갑니다. 그리기 설정은 미리 한 덩이로 묶어 둡니다. 그릴 때는 그 덩이를 고릅니다. 그림과 상관없는 계산만 맡기는 길도 따로 있습니다.
쉽고 빠른 이해
WebGPU 는 웹페이지의 자바스크립트가 그래픽 카드에게 일을 시킬 때 부르는 함수들을 정해 둔 문서입니다. 물체가 아주 많은 3D 장면을 그리거나 페이지 안에서 기계 학습 모델을 돌리는 일이 여기에 듭니다.
이게 나오기 전에 웹에서 그래픽 카드를 쓰는 길은 WebGL 이었습니다. WebGL 은 오래된 그래픽스 설계를 물려받아 요즘 그래픽 카드와 잘 맞지 않습니다. 그림이 아닌 계산을 시키기도 무척 번거롭습니다.
어떻게 도나:
- 그래픽 카드 하나를 대표하는 객체를 받습니다. 거기서 명령을 보낼 연결을 엽니다
- 그래픽 카드에서 돌 프로그램과 그리기 설정을 미리 한 덩이로 묶어 둡니다
- 명령을 모아 두었다가 한 번에 제출합니다. 브라우저가 검사한 뒤 그래픽 카드로 넘깁니다
- 그린 결과는 페이지에 끼운 캔버스에서 받아 화면으로 내보냅니다
대가도 있습니다. 잘못 부른 함수가 대개 곧바로 예외를 던지지 않습니다. 오류는 나중에 따로 알려 오므로, 받으려면 미리 준비해 둬야 합니다.
상세
GPU(Graphics Processing Unit, 그래픽 처리 장치)는 그림 그리기와 대량 계산을 맡는 처리 장치입니다. 앞에서 「그래픽 카드」라고 부른 것이 이 GPU 입니다. 같은 계산을 아주 많은 데이터에 한꺼번에 거는 데 맞춰져 있습니다. 화면 픽셀 하나하나의 색을 정하는 일이 그런 계산입니다.
WebGPU 는 웹페이지가 이 GPU 에 그리기와 계산을 시키는 API(Application Programming Interface, 응용 프로그램 인터페이스)를 정한 규격입니다. API 는 프로그램이 다른 소프트웨어의 기능을 부를 때 쓰는 함수와 객체의 목록입니다. 자바스크립트에서 부를 이름과 넘길 값, 부른 뒤에 일어날 일을 이 규격이 적어 둡니다.
WebGPU 는 설치하는 프로그램이 아니라 문서입니다. 문서는 W3C(World Wide Web Consortium, 월드 와이드 웹 컨소시엄)의 GPU for the Web 작업반이 냅니다. 그 함수를 돌리는 코드는 각 브라우저가 만들어 넣습니다.
아래 소절은 객체를 얻는 순서, 셰이더 언어, 자원 묶음, 파이프라인, 명령 제출, 그린 결과를 화면에 올리는 길, 계산 전용 경로, 검사와 타임라인을 차례로 봅니다.
어댑터와 디바이스
WebGPU 는 GPU 를 객체 두 겹으로 다룹니다. 바깥 겹이 어댑터(GPUAdapter)입니다. 어댑터는 이
기계에서 쓸 수 있는 WebGPU 구현 하나를 가리킵니다. 그 구현이 무엇을 할 수 있는지는 기능과 한도로
알려 줍니다. 기능은 쓸 수 있는지 없는지가 갈리는 항목입니다. 한도는 maxBufferSize 처럼 수로 정해진
상한입니다.
어댑터가 늘 하드웨어 GPU 인 것은 아닙니다. 내장 GPU 나 외장 GPU 위의 하드웨어 가속 구현일 수 있습니다. 소프트웨어 구현일 수도 있습니다.
안쪽 겹이 디바이스(GPUDevice)입니다. 디바이스는 어댑터와 맺은 논리적 연결입니다. 디바이스를 쥔
코드는 자기가 그 어댑터의 유일한 사용자인 것처럼 움직일 수 있습니다. 한 페이지 안의 여러 부분이
각자 디바이스를 하나씩 가져도 됩니다.
디바이스는 나머지 객체를 만드는 출발점이기도 합니다. 버퍼·텍스처·셰이더 모듈·파이프라인이 전부 디바이스에서 만들어집니다. 디바이스가 그 객체들의 주인입니다. 디바이스가 못 쓰게 되면 거기서 만든 객체도 함께 못 쓰게 됩니다. 이 이름들은 아래 소절에서 하나씩 풉니다.
시작점은 navigator.gpu 입니다. 브라우저 창과 웹 워커 양쪽에서 보이는 이 객체가 WebGPU 의
입구입니다. 웹 워커는 페이지 뒤에서 따로 도는 자바스크립트 실행 단위입니다.
아무 조건 없이 어댑터를 요청하는 한 줄은 이렇습니다.
const gpuAdapter = await navigator.gpu.requestAdapter();
어댑터를 받았으면 requestDevice() 로 디바이스를 요청합니다. 요청이 성공하면 그 어댑터는
만료됩니다. 디바이스를 새로 받으려면 requestAdapter() 부터 다시 불러야 합니다.
아래 그림은 지금까지 나온 이름이 서로 무엇의 아래에 달리는지만 보입니다.
flowchart TD
A["페이지의 자바스크립트"] --> B["어댑터 · 쓸 수 있는 구현 하나"]
B --> C["디바이스 · 어댑터와 맺은 연결"]
subgraph D["디바이스가 만들고 소유하는 객체"]
E["버퍼 · 텍스처 · 샘플러"]
F["셰이더 모듈"]
G["파이프라인 · 바인드 그룹"]
H["커맨드 인코더"]
end
C --> D
C --> Q["큐 · 명령을 실행한다"]
페이지가 만드는 객체는 전부 디바이스 아래에 달립니다. 디바이스에는 명령을 실행할 주 큐도 딸려 있습니다.
셰이더와 WGSL
셰이더는 GPU 에서 돌도록 만든 특수한 프로그램입니다. WebGPU 의 셰이더는 WGSL(WebGPU Shading Language, WebGPU 셰이딩 언어)로 씁니다. WGSL 은 WebGPU 와 짝을 이루는 규격입니다. 다만 문서는 따로 나옵니다.
자바스크립트는 WGSL 소스를 문자열로 넘깁니다. createShaderModule() 이 그 문자열을 받아 셰이더
모듈(GPUShaderModule)을 만듭니다. 이때 구현은 셰이더를 플랫폼 전용 셰이더로 옮기기 전에 먼저
검사해야 합니다.
WebGPU 는 GPU 에 일을 명령 단위로 보냅니다. 그 명령 가운데 WGSL 과 맞물리는 것이 둘입니다. 그리기 명령은 렌더 파이프라인을 실행합니다. 디스패치 명령은 컴퓨트 파이프라인을 실행합니다. 파이프라인이 무엇인지는 아래 「파이프라인」 소절이, 명령을 어디에 적는지는 「명령 인코딩과 큐 제출」 소절이 다룹니다. 여기서 중요한 것은 하나입니다. WGSL 로 쓰는 것은 두 파이프라인 안에서 도는 셰이더입니다. 명령 자체는 자바스크립트가 적습니다.
GPU 가 불러 들어가는 함수를 진입점이라고 합니다. 진입점은 함수 앞에 셰이더 단계 속성을 붙여
표시합니다. @vertex 는 정점 셰이더, @fragment 는 프래그먼트 셰이더, @compute 는 컴퓨트
셰이더입니다.
정점 셰이더는 점 하나마다 한 번 돌아 그 점을 화면 어디에 둘지 정합니다. 프래그먼트 셰이더는 삼각형이 덮은 픽셀 후보마다 한 번 돌아 무슨 색을 낼지 정합니다. 컴퓨트 셰이더는 그림과 상관없이 데이터를 읽고 써서 계산만 합니다.
바인드 그룹
셰이더가 읽고 쓰는 자원은 GPU 메모리에 따로 둡니다. 버퍼(GPUBuffer)는 GPU 연산에 쓰는 메모리
한 덩어리입니다. 바이트마다 버퍼 시작점에서 몇 번째인지로 가리킵니다. 다만 연산에 따라 정렬 제약이
붙습니다.
텍스처(GPUTexture)는 이미지처럼 가로세로로 놓인 데이터를 담는 자원입니다. 텍스처도 GPU 메모리에
있습니다. 샘플러(GPUSampler)는 GPU 가 그 텍스처를 어떻게 읽을지 정하는 자원입니다. WGSL 쪽에서는
텍스처와 샘플러와 좌표를 함께 textureSample() 에 넘겨 그 좌표의 색을 읽습니다.
자원을 셰이더에 이어 주는 것을 바인딩이라고 합니다. 자원은 셰이더에 하나씩 꽂지 않고 묶어서
건넵니다. 그 묶음이 바인드 그룹(GPUBindGroup)입니다. 바인드 그룹은 한 그룹으로 묶을 자원들과,
셰이더 단계마다 그 자원을 어떻게 쓰는지를 정합니다.
묶음의 모양은 바인드 그룹 레이아웃(GPUBindGroupLayout)이 먼저 정합니다. 레이아웃이 바인딩 번호와
자원 종류를 적습니다. 바인드 그룹은 그 번호에 실제 버퍼·텍스처·샘플러를 넣습니다. WGSL 쪽에서는
@group(1) @binding(0) 처럼 그룹 번호와 바인딩 번호로 그 자원을 가리킵니다.
flowchart TD
subgraph L["바인드 그룹 레이아웃 · 종류만 적는다"]
L0["바인딩 0 · 버퍼"]
L1["바인딩 1 · 텍스처"]
L2["바인딩 2 · 샘플러"]
end
subgraph G["바인드 그룹 · 실제 자원을 넣는다"]
G0["바인딩 0 · 유니폼 버퍼"]
G1["바인딩 1 · 텍스처"]
G2["바인딩 2 · 샘플러"]
end
L -->|"이 모양대로 채운다"| G
L -->|"묶어 담는다"| P["파이프라인 레이아웃"]
W["WGSL 변수 · @group @binding"] -.->|"같은 번호로 찾는다"| G
같은 바인딩 번호가 세 군데에서 맞물립니다. 레이아웃이 그 번호의 종류를 정합니다. 바인드 그룹이 그 번호에 자원을 넣습니다. 셰이더는 그 번호로 자원을 찾습니다.
파이프라인
파이프라인은 GPU 하드웨어와 드라이버, 브라우저가 함께 해내는 처리 전체를 객체 하나로 묶은 것입니다. 바인딩과 정점 버퍼로 들어온 데이터를 받아 결과를 냅니다. 그리기라면 결과는 그림이 담길 텍스처에 칠해지는 색입니다.
안에는 셰이더로 짜는 단계와 고정 기능 설정이 차례로 들어 있습니다. 고정 기능은 셰이더로 짜지 않고 값만 골라 주는 단계입니다. 새로 그린 색을 이미 있는 색과 섞는 블렌딩 방식이 그 예입니다.
종류는 둘입니다. 렌더 파이프라인(GPURenderPipeline)은 정점 셰이더와 프래그먼트 셰이더 단계를
맡습니다. 컴퓨트 파이프라인(GPUComputePipeline)은 컴퓨트 셰이더 단계를 맡습니다.
렌더 파이프라인이 받는 입력은 넷입니다. 첫째가 바인딩입니다. 앞 소절에서 본 바인드 그룹이 그 입구입니다. 둘째가 정점 버퍼와 인덱스 버퍼입니다.
정점 버퍼는 점들의 좌표 같은 값을 담습니다. 인덱스 버퍼는 그 점을 어떤 순서로 이을지를 담습니다.
셋째와 넷째가 첨부입니다. 첨부는 그린 결과를 써 넣는 텍스처입니다. 색 첨부에는 색이 담깁니다. 깊이·스텐실 첨부는 있을 때만 씁니다 — 깊이와 스텐실이 각각 어떤 값인지는 이 판의 자료에 없습니다(미확인).
파이프라인을 만들 때 이 넷을 각각 어느 설명으로 적어 넘기는지가 아래 표입니다.
| 입력 | 무엇이 기술하나 |
|---|---|
| 바인딩 | 파이프라인 레이아웃(GPUPipelineLayout) |
| 정점 버퍼와 인덱스 버퍼 | GPUVertexState |
| 색 첨부 | GPUColorTargetState |
| 깊이·스텐실 첨부 (있을 때만) | GPUDepthStencilState |
이 처리 전체를 객체 하나로 만드는 까닭 가운데 하나는 이렇습니다. 드라이버는 대상 플랫폼에 따라 고정 기능 설정 일부를 셰이더 코드로 바꿔, 사용자가 준 셰이더와 이어 붙일 수 있습니다. 그렇게 이어 붙이려면 양쪽을 미리 다 알아야 하므로, 설정이 흩어져 있으면 할 수 없는 일입니다.
만든 파이프라인은 명령 하나(setPipeline())로 바꿔 끼웁니다. 파이프라인 객체에 안 든 상태도
있습니다. 그 상태는 명령을 적는 동안 beginRenderPass() 나 setBlendConstant() 같은 명령으로
정합니다. 명령을 어디에 적는지는 바로 다음 소절에서 봅니다.
명령 인코딩과 큐 제출
GPU 쪽에서만 일어나는 연산은 부르는 즉시 GPU 로 가지 않습니다. 먼저 커맨드 인코더
(GPUCommandEncoder)에 적힙니다. 인코더에서 finish() 를 부르면 적힌 명령이 커맨드 버퍼
(GPUCommandBuffer)로 나옵니다. 그 뒤로 그 인코더는 더 쓸 수 없습니다.
커맨드 버퍼는 디바이스의 큐(GPUQueue)에 submit() 으로 제출합니다. 제출하면 GPU 가 그 명령을
실행하도록 일정이 잡힙니다. 한 번 제출한 커맨드 버퍼는 다시 못 씁니다. 같은 그리기 명령을 여러 번
제출해 다시 쓰려면 렌더 번들(GPURenderBundle)을 씁니다.
const commandEncoder = gpuDevice.createCommandEncoder();
commandEncoder.clearBuffer(buffer);
const commandBuffer = commandEncoder.finish();
gpuDevice.queue.submit([commandBuffer]);
위 코드는 인코더를 만들어 버퍼를 비우는 명령 하나를 적습니다. 그다음 커맨드 버퍼로 마감해 큐에
제출합니다. 그리기 명령은 인코더에서 연 렌더 패스 인코더(GPURenderPassEncoder) 안에 적습니다.
계산 명령은 컴퓨트 패스 인코더(GPUComputePassEncoder) 안에 적습니다.
sequenceDiagram
participant 스크립트 as 자바스크립트
participant 인코더 as 커맨드 인코더
participant 큐
participant GPU
스크립트->>인코더: 명령을 적는다
스크립트->>인코더: finish()
인코더-->>스크립트: 커맨드 버퍼
Note over 스크립트,인코더: 여기까지는 GPU 로 안 건너간다
스크립트->>큐: submit([커맨드 버퍼])
큐->>GPU: 실행 일정을 잡는다
이렇게 모았다가 내는 설계는 네이티브 GPU API 도 씁니다. 네이티브 GPU API 는 운영체제가 자기 기계의 GPU 를 쓰라고 내놓은 API 입니다. Direct3D 12 · Metal · Vulkan 이 그런 API 입니다. 이득은 둘입니다. 명령을 적는 일은 다른 상태에 매이지 않습니다. 그래서 적는 일과 그 검사를 여러 CPU(Central Processing Unit, 중앙 처리 장치) 스레드에 나눠 맡길 수 있습니다. 그리고 일을 큰 덩어리로 한 번에 넘기므로 GPU 드라이버가 일을 하드웨어에 나눠 배치할 때 더 넓게 보고 최적화할 수 있습니다.
화면에 올리기
그린 결과가 저절로 페이지에 보이지는 않습니다. 이 소절은 디바이스와 캔버스 요소(<canvas>)를
잇는 길과, 그 캔버스에 그린 텍스처가 화면으로 넘어가는 때를 봅니다.
WebGPU 는 캔버스 없이 시작합니다. 디바이스를 얻는 일과 캔버스를 얻는 일이 떨어져 있어서, 디바이스 하나에 캔버스를 0개 이상 붙여 프레임마다 그 가운데 몇 개든 그릴 수 있습니다. 기본으로 딸려 오는 그리기 버퍼 같은 것은 없습니다. 어느 캔버스 컨텍스트든 어느 디바이스와도 짝지을 수 있어서, 디바이스를 갈아 끼우는 일도 쉽습니다.
캔버스 쪽 입구는 getContext('webgpu') 입니다. 이 호출이 캔버스 컨텍스트(GPUCanvasContext)를
돌려줍니다. 2D 나 WebGL 과 달리 getContext() 의 둘째 인자는 무시됩니다. 설정은 컨텍스트의
configure() 로 합니다. 캔버스를 갈아 끼우지 않고 설정만 바꿀 수 있게 하려고 이렇게 갈랐습니다.
configure() 에 반드시 적는 것이 디바이스와 형식입니다. 디바이스는 이 캔버스가 내줄 텍스처와 짝이
맞을 디바이스입니다. 형식은 "bgra8unorm" · "rgba8unorm" · "rgba16float" 셋 가운데 하나여야
합니다. 그 밖의 값을 주면 TypeError 가 납니다.
이 기계의 화면에 어느 형식이 알맞은지는 navigator.gpu.getPreferredCanvasFormat() 이 알려 줍니다. 돌려주는 값은 "rgba8unorm" 이나
"bgra8unorm" 둘 중 하나입니다.
그릴 텍스처는 getCurrentTexture() 로 받습니다. 이 텍스처가 캔버스 컨텍스트가 다음에 문서로 합성할
텍스처입니다. 캔버스 안쪽에는 드로잉 버퍼라는 이미지가 있습니다. 받은 텍스처는 그 버퍼에 쓸 수 있게 얹힌
뷰입니다.
configure() 에 usage 를 따로 적지 않으면 기본값이 RENDER_ATTACHMENT 라서, 받은
텍스처를 렌더 패스의 색 첨부로 쓸 수 있습니다.
화면에 올라가는 때는 이벤트 루프가 그 문서나 전용 워커의 렌더링을 갱신하는 단계입니다. 그 단계에서 현재 텍스처가 만료됩니다. 드로잉 버퍼는 그때까지 쓰인 내용으로 내보내집니다. 드로잉 버퍼를 읽을 때 구현은 앞서 제출한 작업이 그 버퍼에 쓰기를 모두 마쳤는지 보장해야 합니다.
그 뒤 드로잉 버퍼는 읽기 전용이 되어 브라우저의 합성기와 페이지가 나눠 씁니다. 페이지 쪽에서 drawImage() 나 toDataURL()
로 캔버스를 읽는 것도 이 버퍼를 읽는 일입니다.
sequenceDiagram
participant 페이지 as 페이지의 자바스크립트
participant 컨텍스트 as 캔버스 컨텍스트
participant 큐
participant 합성기 as 브라우저 합성기
페이지->>컨텍스트: getCurrentTexture()
컨텍스트-->>페이지: 드로잉 버퍼에 쓰는 텍스처
페이지->>큐: 그 텍스처를 색 첨부로 쓴 명령을 제출한다
Note over 페이지,컨텍스트: 렌더링을 갱신하는 단계에서 텍스처가 만료된다
컨텍스트->>합성기: 드로잉 버퍼를 내보낸다
그래서 텍스처는 프레임마다 다시 받습니다. 만료되기 전에는 getCurrentTexture() 가 같은 객체를
계속 돌려줍니다. 만료를 부르는 것은 렌더링 갱신 말고도 configure() 와 캔버스 크기 변경입니다.
캔버스 크기를 바꾸면 드로잉 버퍼를 갈아 내용이 날아갑니다.
계산 전용 경로
그림과 상관없는 계산은 컴퓨트 파이프라인으로 돌립니다. 계산을 출발시키는 것은 디스패치 명령입니다. 디스패치 명령은 컴퓨트 파이프라인을 실행하는 명령입니다. 이 명령은 컴퓨트 패스 인코더에 적습니다. 한 번의 디스패치가 작업 그룹을 몇 벌 돌리는지는 이 판의 자료에 없습니다(미확인).
컴퓨트 파이프라인의 입력과 출력은 전부 바인딩에 들어 있습니다. 출력은 "storage" 타입 버퍼
바인딩이나, 쓰기를 허용한 스토리지 텍스처 바인딩입니다.
컴퓨트 셰이더 진입점에는 @workgroup_size 를 반드시 적습니다. 작업 그룹은 GPU 에서 함께 도는
셰이더 호출의 묶음입니다. 이 값이 그 묶음의 크기입니다. 컴퓨트 진입점은 값을 돌려주지 않습니다.
결과는 바인딩된 스토리지 버퍼나 스토리지 텍스처에 씁니다.
결과를 자바스크립트에서 보려면 버퍼를 매핑합니다. 매핑할 수 있게 만든 버퍼는 그 메모리를
자바스크립트의 ArrayBuffer 로 내줍니다. 매핑을 비동기로 요청하는 호출이 mapAsync() 입니다.
검사와 타임라인
WebGPU 는 웹에 열린 API 라서 보안 요구가 웹의 다른 기능과 같습니다. 이 요구는 타협할 수 없습니다. 그래서 기본 방침은 모든 명령을 GPU 에 닿기 전에 엄격하게 검사하는 것입니다. 페이지가 자기 데이터만 다루게 하려는 것입니다.
네이티브 GPU API 는 명령마다 올바른 사용법을 정해 둡니다. 그 규칙을 어겼을 때의 결과는 대개 보장하지 않습니다. 이것을 미정의 동작이라고 합니다. 공격자는 미정의 동작을 이용해 남의 메모리를 읽거나 드라이버가 임의의 코드를 실행하게 만들 수 있습니다.
그래서 WebGPU 는 어떤 입력이 와도 일어날 일을 정해 둡니다. 구현은 사용자 입력을 전부 검사하고
올바른 작업만 드라이버로 넘겨야 합니다. 예를 들어 copyBufferToBuffer() 의 원본과 대상에 같은
버퍼의 겹치는 구간을 주면 인코더가 오류를 냅니다. 다른 일은 아무것도 일어나지 않습니다.
WebGPU 는 동작을 타임라인 셋으로 나눠 적습니다. 타임라인은 연산이 일어나는 순서와, 어느 연산이 어느 상태를 볼 수 있는지를 정합니다.
| 타임라인 | 무엇이 여기서 일어나나 |
|---|---|
| 콘텐츠 타임라인 | 웹 스크립트의 실행. 이 규격의 메서드를 부르는 일 전부 |
| 디바이스 타임라인 | 브라우저가 내는 GPU 디바이스 연산. 어댑터·디바이스·자원·상태 객체 만들기. 별도 운영체제 프로세스에 있을 수 있다 |
| 큐 타임라인 | GPU 연산 장치에서의 실행. 그리기·복사·계산 작업 |
flowchart TD
subgraph P1["콘텐츠 타임라인"]
S["자바스크립트가 메서드를 부른다"]
end
subgraph P2["디바이스 타임라인"]
V["검사하고 객체를 만든다"]
end
subgraph P3["큐 타임라인"]
X["GPU 가 그리기·복사·계산을 실행한다"]
end
S -->|"비동기로 넘긴다"| V
V -->|"올바른 작업만"| X
이 모형은 브라우저 엔진의 다중 프로세스 구조를 본뜬 것입니다. 대개 콘텐츠 프로세스와 GPU 프로세스로 나뉩니다. 다만 구현이 반드시 여러 프로세스나 여러 스레드를 써야 하는 것은 아닙니다.
GPU 프로세스를 두는 브라우저에서는 이 프로세스가 GPU 드라이버를 품습니다. 나머지 브라우저와는 비동기 IPC(Inter-Process Communication, 프로세스 간 통신)로 이야기합니다.
이 프로세스는 콘텐츠 프로세스보다 격리가 약합니다. 대개 여러 오리진(origin, 스킴·호스트·포트 셋으로 가르는 웹의 보안 단위)이 함께 씁니다. 그래서 들어온 메시지를 전부 검사해야 합니다. 보낸 콘텐츠 프로세스가 이미 장악당했을 수 있습니다. 그 메시지를 믿고 넘기면 다른 콘텐츠 프로세스가 쓰는 GPU 메모리를 들여다보게 될 수 있습니다.
WebGPU 의 검사 규칙은 대부분 안전을 지키려고 있는 것입니다. 그래서 검사는 GPU 프로세스에서 일어나야 합니다.
flowchart TD
subgraph CA["콘텐츠 프로세스 · 오리진 A"]
HA["GPUBuffer 핸들 · 150바이트쯤"]
end
subgraph CB["콘텐츠 프로세스 · 오리진 B"]
HB["GPUBuffer 핸들"]
end
subgraph GP["GPU 프로세스 · 격리가 약하다"]
V["들어온 메시지를 전부 검사한다"]
DR["GPU 드라이버"]
R["진짜 버퍼 · GPU 메모리 1GB"]
V -->|"올바른 것만"| DR
DR -->|"드라이버 객체는 여기에만 산다"| R
end
HA -->|"비동기 IPC"| V
HB -->|"비동기 IPC"| V
HA -.->|"가리킨다"| R
이 구조 탓에 콘텐츠 쪽의 WebGPU 객체는 대개 GPU 프로세스에 있는 진짜 객체를 가리키는 핸들일
뿐입니다. 콘텐츠 프로세스에서는 150바이트쯤인 GPUBuffer 하나가 GPU 메모리 1GB 를 쥐고 있을 수도
있습니다.
자바스크립트는 나중에 끝날 작업의 결과를 프로미스라는 객체로 받습니다. 그런데 객체를 만드는 WebGPU 호출은 프로미스를 안 돌려줍니다. 그래도 안에서는 비동기로 돕니다. 이것도 같은 까닭입니다. 객체는 콘텐츠 타임라인에서 곧바로 돌려받습니다. 검사는 거의 전부 디바이스 타임라인에서 나중에 돕니다. 그러니 오류도 호출한 자리에서 바로 안 옵니다.
출처 문서
정본은 W3C 의 GPU for the Web 작업반(Working Group)이 내는 「WebGPU」입니다. 판 번호가 없습니다. 날짜로 판을 가릅니다. 이번에 본 판은 2026년 9월 15일자 후보 권고 초안(Candidate Recommendation Draft, CRD)입니다.
| 항목 | 값 |
|---|---|
| 문서 | WebGPU |
| 이번 판 | W3C Candidate Recommendation Draft, 15 September 2026 — https://www.w3.org/TR/2026/CRD-webgpu-20260915/ |
| 앞 판 | Candidate Recommendation Draft, 1 September 2026 |
| 최신 발행판 주소 | https://www.w3.org/TR/webgpu/ |
| 편집자 초안 | https://gpuweb.github.io/gpuweb/ |
| 낸 곳 | GPU for the Web Working Group |
| 편집자 | Kai Ninomiya · Brandon Jones · Jim Blandy |
| 시험 묶음 | WebGPU CTS(Conformance Test Suite, 적합성 시험 묶음) |
상태 절은 이 문서를 W3C 권고 절차(Recommendation track)에 오른 후보 권고 초안이라고 적습니다. 후보 권고(Candidate Recommendation, CR)로 발행된 것이 W3C 와 회원들의 승인을 뜻하지는 않습니다. 후보 권고 초안은 앞선 후보 권고에서 바뀐 것 가운데 작업반이 다음 후보 권고 스냅숏에 넣으려는 것을 모은 판입니다. 문서는 언제든 고쳐진다고 적습니다. 일부는 아직 작업 중이라고도 적습니다.
작업반은 기능마다 요즘 GPU 시스템 API 위에서 도는, 배포된 브라우저 둘 이상의 구현을 보일 것으로 내다본다고 적습니다. 여기서 GPU 시스템 API 는 상세에서 네이티브 GPU API 라고 부른 그것입니다. 그 실적은 시험 묶음으로 구현 보고서를 만들어 보입니다. W3C 발행 이력 표에는 권고 (Recommendation) 줄이 아직 없습니다.
timeline
2021 : 첫 공개 작업 초안 (5월 18일)
2024 : 마지막 작업 초안 (12월 14일) : 후보 권고 스냅숏 (12월 19일)
2025 : 후보 권고 초안이 이어진다 (1월 16일부터)
2026 : 이번에 본 판 (9월 15일)
그림은 W3C 발행 이력에서 상태가 바뀐 판만 골랐습니다. 2021년과 2024년 사이에 작업 초안이 여럿 더 있습니다. 2025년 1월부터는 후보 권고 초안만 이어집니다.
이웃 문서
| 문서 | 낸 곳 | 상태 | 이 편과의 관계 |
|---|---|---|---|
| WebGPU Shading Language (WGSL) | GPU for the Web Working Group | Candidate Recommendation Draft, 21 September 2026 | 셰이더 언어. 별도 규격이다. 편집자 Alan Baker · Mehmet Oguz Derin · David Neto |
| WebGPU Explainer | GPU for the Web Community Group | Draft Community Group Report, 23 September 2026 | 같은 이름을 쓰는 커뮤니티 그룹이 쓴 해설. 스스로 W3C 표준도 아니고 표준 절차에도 없다고 적는다 |
해설은 규범이 아닙니다. 해설 안에도 미완성 표시가 남아 있습니다. §3.4 Device Loss 끝에는 「Finish this explainer」가 적혀 있습니다. §3.8 Pipelines 는 제목만 있습니다.
요구 강도
WebGPU 의 적합성 절은 요구를 RFC(Request for Comments) 2119 낱말로 적는다고 밝힙니다.
| 낱말 | RFC 2119 의 뜻 |
|---|---|
| MUST | 규격의 절대 요구다 |
| SHOULD | 특정 사정에서 따르지 않을 타당한 이유가 있을 수 있다. 다만 그 결과를 다 따져 보고 골라야 한다 |
| MAY | 정말로 선택이다 |
다만 이 문서는 읽기 쉽게 하려고 그 낱말들을 대문자로 쓰지 않습니다. RFC 8174 는 대문자일 때만 특별한 뜻을 갖는다고 RFC 2119 를 갱신했습니다. 그러나 WebGPU 는 RFC 2119 만 인용합니다. 그리고 소문자 must·should·may 를 RFC 2119 대로 읽으라고 적습니다. 그래서 WebGPU 문서의 소문자 must 는 RFC 2119 의 MUST 와 같은 무게입니다.
규범이 아닌 것도 정해 둡니다. 비규범이라고 표시한 절, 예제, 노트(Note)는 규범이 아닙니다. §1 Introduction 과 §2 Malicious use considerations 는 스스로 비규범이라고 밝힙니다. 알고리즘으로 적은 요구는 결과만 같으면 어떤 방식으로 구현해도 됩니다.
아래 표는 이 편이 본문에서 쓴 주요 요구를 강도와 절 번호, 규범 여부로 가른 것입니다.
| 대목 | 강도 | 절 | 규범 |
|---|---|---|---|
| 오류는 발생 조건과 하위 종류를 밝힌 연산에서만 낸다 | must | §22.2 | 규범 |
requestDevice() 는 이미 쓴 어댑터로 부르면 안 된다 |
must | §4.3 | 규범 |
| 부작용을 남기는 실패가 나면 디바이스를 잃게 한다 | should | §3.5.2 | 규범 |
| 시스템 상태가 바뀌면 앞서 내준 어댑터를 만료시킨다 | should | §4.2 | 규범 |
| 캔버스 형식은 지원 형식 셋 안이어야 한다 | must | §21.4 | 규범 |
| 드로잉 버퍼를 읽을 때 앞서 제출한 쓰기가 끝났음을 보장한다 | must | §21.2 | 규범 |
잡히지 않은 오류를 uncapturederror 이벤트로 알린다 |
브라우저가 고를 때 · may | §22.3 · §22.4 | 규범 |
| 잡히지 않은 오류를 개발자 콘솔에 경고로 보인다 | should | §22.3 의 Note | 비규범 |
| 앱이 부르지 않은 손실에 개발자 경고를 낸다 | should consider | §3.5.2 의 Note | 비규범 |
| 앱이 쓰는 GPU 메모리를 제한하고 워치독을 둔다 | may | §2.1.9 | 비규범 |
구현에 맡긴 대목
어댑터를 내줄지, 내준다면 어떤 능력으로 내줄지는 구현이 정합니다(§4.2). 디바이스 손실 정보의
message 값도 구현이 정합니다. 노트는 애플리케이션이 그 값을 파싱하지 말라고 적습니다(§3.5.2). 잡히지 않은 오류
이벤트를 낼지, 몇 개까지 낼지도 브라우저가 고릅니다(§22.3 · §22.4). 타임라인이 여러 프로세스에
나뉘는지도 구현에 맡겨져 있습니다(§3.4.1). 현재 텍스처를 자동으로 만료시키는 작업을 둘지, 둔다면
언제 돌릴지도 구현에 맡겨져 있습니다(§21.2).
본문 대목과 근거 절
| 본문 대목 | 근거 |
|---|---|
| 상세 · 어댑터와 디바이스 | WebGPU §1 · §3.5.2 · §4.1 · §4.2 · §4.3 · §4.4 · §5.1 · 해설 §3.1 |
| 상세 · 셰이더와 WGSL | WebGPU §2.1.2 · §9.1 · WGSL §1 · §1.1 · §13.2 |
| 상세 · 바인드 그룹 | WebGPU §1 · §5.1 · §8.2 · §8.4 · WGSL §1 |
| 상세 · 파이프라인 | WebGPU §1 · §10 · §10.2 · §10.3 |
| 상세 · 명령 인코딩과 큐 제출 | WebGPU §12 · §13.2 · §13.7 · §19.2 · 해설 §1.2 · §3.7 |
| 상세 · 화면에 올리기 | WebGPU §4.2 · §21.1 · §21.2 · §21.3 · §21.4 · §21.4.2 · 해설 §3.10 · §3.10.1 · §3.10.2 · §3.10.3 |
| 상세 · 계산 전용 경로 | WebGPU §5.1 · §10.2 · §22 · WGSL §1.1 · §13.2 · §13.2.1 |
| 상세 · 검사와 타임라인 | WebGPU §2.1 · §2.1.1 · §3.2.1 · §3.4.1 · 해설 §2.1 |
| 배경 | 해설 §1 · §1.1 · §1.2 · §1.3 · WebGPU §1 · 상태 절 |
| 실패 · 디바이스 손실이 일어나는 예 | 해설 §3.4 (비규범 · 미완성). WebGPU 본문은 원인을 나열하지 않고 손실을 일으키는 규칙만 둔다(§3.5.2 · §4.3 · §4.4) |
| 실패 · 그 밖 | WebGPU §3.2.1 · §3.5.2 · §4.2 · §4.3 · §4.5 · §5.1 · §22 · §22.1 ~ §22.4 · 해설 §3.1.1 · §3.2.1 · §3.3 |
예시
이 절은 WebGPU 를 부르는 코드 넷을 봅니다. 어댑터와 디바이스 요청, 버퍼 하나, 바인드 그룹, 셰이더 진입점입니다. 코드마다 그 값이 무엇을 정하는지를 아래에 적습니다.
어댑터와 디바이스 요청
오류 처리까지 넣은 요청 코드입니다.
let gpuDevice = null;
async function initializeWebGPU() {
// Check to ensure the user agent supports WebGPU.
if (!('gpu' in navigator)) {
console.error("User agent doesn't support WebGPU.");
return false;
}
// Request an adapter.
const gpuAdapter = await navigator.gpu.requestAdapter();
// requestAdapter may resolve with null if no suitable adapters are found.
if (!gpuAdapter) {
console.error('No WebGPU adapters found.');
return false;
}
// Request a device.
// Note that the promise will reject if invalid options are passed to the optional
// dictionary. To avoid the promise rejecting always check any features and limits
// against the adapters features and limits prior to calling requestDevice().
gpuDevice = await gpuAdapter.requestDevice();
// requestDevice will never return null, but if a valid device request can't be
// fulfilled for some reason it may resolve to a device which has already been lost.
// Additionally, devices can be lost at any time after creation for a variety of reasons
// (ie: browser resource management, driver updates), so it's a good idea to always
// handle lost devices gracefully.
gpuDevice.lost.then((info) => {
console.error(`WebGPU device was lost: ${info.message}`);
gpuDevice = null;
// Many causes for lost devices are transient, so applications should try getting a
// new device once a previous one has been lost unless the loss was caused by the
// application intentionally destroying the device. Note that any WebGPU resources
// created with the previous device (buffers, textures, etc) will need to be
// re-created with the new one.
if (info.reason != 'destroyed') {
initializeWebGPU();
}
});
onWebGPUInitialized();
return true;
}
function onWebGPUInitialized() {
// Begin creating WebGPU resources here...
}
initializeWebGPU();
코드는 실패를 세 겹으로 받습니다. 먼저 navigator 에 gpu 가 있는지 봐서 브라우저가 WebGPU 를
지원하는지 확인합니다. 다음으로 requestAdapter() 가 null 을 돌려주면 멈춥니다.
마지막으로 lost 프로미스에 다시 시작하는 절차를 걸어 둡니다. 손실 사유가 'destroyed' 가
아니면 initializeWebGPU() 를 다시 불러 어댑터부터 새로 받습니다. 앞 디바이스로 만든 버퍼와
텍스처는 새 디바이스로 다시 만들어야 합니다.
128바이트 유니폼 버퍼
값을 써 넣을 수 있는 128바이트짜리 유니폼 버퍼를 만듭니다. 유니폼 버퍼는 바인드 그룹에 꽂아 셰이더가 읽는 버퍼입니다. 바로 아래 소절의 바인딩 0 이 이 버퍼입니다.
const buffer = gpuDevice.createBuffer({
size: 128,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
});
size 는 바이트 수입니다. usage 는 이 버퍼를 어디에 쓸지를 플래그로 겹쳐 적습니다.
UNIFORM 과 COPY_DST 두 플래그가 「유니폼 버퍼로 쓰고, 값을 써 넣을 수 있다」를 함께 적습니다. usage 가 0 이면 검사 오류가 납니다. 버퍼는 무효가 됩니다.
버퍼·텍스처·샘플러를 묶는 바인드 그룹
유니폼 버퍼, 텍스처, 샘플러를 바인딩 0·1·2 에 두는 레이아웃을 만듭니다. 그다음 그 레이아웃으로 바인드 그룹과 파이프라인 레이아웃을 만듭니다.
const bindGroupLayout = gpuDevice.createBindGroupLayout({
entries: [{
binding: 0,
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
buffer: {}
}, {
binding: 1,
visibility: GPUShaderStage.FRAGMENT,
texture: {}
}, {
binding: 2,
visibility: GPUShaderStage.FRAGMENT,
sampler: {}
}]
});
const bindGroup = gpuDevice.createBindGroup({
layout: bindGroupLayout,
entries: [{
binding: 0,
resource: { buffer: buffer },
}, {
binding: 1,
resource: texture
}, {
binding: 2,
resource: sampler
}]
});
const pipelineLayout = gpuDevice.createPipelineLayout({
bindGroupLayouts: [bindGroupLayout]
});
visibility 가 바인딩마다 어느 셰이더 단계에서 보이는지를 정합니다. 바인딩 0 의 버퍼는 정점과
프래그먼트 두 단계에서 보입니다. 텍스처와 샘플러는 프래그먼트 단계에서만 보입니다. 레이아웃은
바인딩마다 자원 종류만 적습니다. 바인드 그룹이 같은 바인딩 번호에 실제 자원을 넣습니다.
WGSL 진입점 셋
셰이더 단계마다 진입점을 하나씩 선언합니다.
@vertex
fn vert_main() -> @builtin(position) vec4<f32> {
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
}
@fragment
fn frag_main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
return vec4<f32>(coord_in.x, coord_in.y, 0.0, 1.0);
}
@compute @workgroup_size(1)
fn comp_main() { }
vert_main 은 점의 위치를 돌려줍니다. frag_main 은 들어온 픽셀 좌표의 x·y 를 색의 빨강·초록
값으로 써서 돌려줍니다. comp_main 은 작업 그룹 크기 1 로 선언했습니다. 돌려주는 값은 없습니다.
파이프라인을 만들 때 이 함수 이름이 진입점 이름이 됩니다. WebGPU 쪽 GPUProgrammableStage 의
entryPoint 에 "vert_main" 같은 이름을 적어 고릅니다.
배경
브라우저에서 GPU 를 쓰는 길은 WebGL 이 먼저였습니다. WebGL 1.0 과 2.0 은 각각 OpenGL ES(OpenGL for Embedded Systems, 임베디드 시스템용 OpenGL) 2.0 과 3.0 의 API 를 자바스크립트로 옮긴 것입니다. 뿌리는 1992년에 나온 OpenGL 1.0 까지 올라갑니다. 이 내력 덕분에 쌓인 지식이 많습니다. 옮기기도 쉬웠습니다. 대신 요즘 GPU 의 설계와 맞지 않아 CPU 쪽과 GPU 쪽 모두에서 성능 문제를 냈습니다.
그림이 아닌 계산은 더 막혀 있었습니다. WebGL 은 주로 그림을 그리는 API 라서, 다른 계산에 쓰려면 큰 수고를 들여 용도를 바꿔야 했습니다. 범용 계산을 더하려던 WebGL 2.0 Compute 는 네이티브 GPU API 와 어긋나 무척 어려웠습니다. 그 기여자들은 WebGPU 쪽으로 힘을 옮겼습니다.
그래서 「WebGL 3」으로 고쳐 잇지 않고 새 규격을 세웠습니다. WebGPU 는 2014년 이후의 네이티브 GPU API 에 효율적으로 대응하도록 처음부터 설계됐습니다. WebGL 과는 관계가 없습니다. 대상은 Direct3D 12 · Metal · Vulkan 위의 구현입니다. 화면 안팎의 요즘 그래픽스 렌더링, GPU 에서 효율적으로 도는 범용 계산, 사람이 손으로 쓸 셰이더 언어, 브라우저 다중 프로세스 구조에서의 보안이 목표에 듭니다.
WebGL 2 로는 안 풀리던 쓰임의 예입니다.
| 쓰임 | WebGL 2 에서 걸리던 점 |
|---|---|
| CAD(Computer-Aided Design, 컴퓨터 지원 설계) 모델처럼 물체가 아주 많은 장면 | WebGPU 의 그리기 명령은 하나하나가 WebGL 의 것보다 싸다 |
| 사실적인 장면을 그리는 고급 알고리즘 | 요즘 렌더링 기법과 최적화 다수가 범용 계산을 못 해서 WebGL 2 에서 안 돈다 |
| GPU 에서 기계 학습 모델 실행 | WebGL 로도 GPGPU(General-Purpose computing on GPU, GPU 범용 계산)는 되지만 효율이 떨어지고 훨씬 어렵다 |
목표 밖과 안 쓰는 때
위 표의 쓰임 셋은 모두 「WebGL 2 가 받지 못하는」 것으로 묶여 있습니다. 거꾸로 WebGPU 가 답이 아닌 때도 목표 밖 목록에서 읽힙니다.
| 이런 때 | 무엇이 걸리나 |
|---|---|
브라우저에 navigator.gpu 가 없다 |
부를 입구 자체가 없습니다 |
| 구현이 어댑터를 안 내준다 | requestAdapter() 가 null 로 이행합니다. 내줄지 말지는 구현이 고릅니다 |
| 범용 계산을 못 하는 옛 휴대전화 GPU 나 더 옛 데스크톱 GPU 위에서 돈다 | 그 하드웨어를 드러내는 것은 목표 밖입니다 |
| DSP(Digital Signal Processor, 디지털 신호 처리기)나 기계 학습 전용 하드웨어를 쓰려 한다 | 프로그래밍이 아예 안 되거나 훨씬 덜 유연한 하드웨어도 목표 밖입니다 |
| 네이티브 GPU API 의 기능을 빠짐없이 쓰려 한다 | 한 벤더의 GPU 에만 있거나 너무 틈새인 기능은 목표 밖입니다 |
| WebGL 코드와 WebGPU 코드를 넓게 섞어 쓰려 한다 | 섞어 쓰기는 목표가 아닙니다 |
| CSS(Cascading Style Sheets, 종속형 시트) Houdini 처럼 페이지 렌더링 흐름에 밀착시키려 한다 | 밀착도 목표가 아닙니다. 다른 웹 기능과는 이미지를 주고받는 선까지만 엮습니다 |
| 규격이 더 굳기를 기다리는 쪽이 낫다 | 정본이 아직 후보 권고 초안이라 언제든 고쳐지고 일부는 작업 중입니다 |
실패
이 절은 WebGPU 호출이 깨지는 조건과 그때 무엇이 돌아오는지를 봅니다. 대상은 어댑터·디바이스 요청, 객체 만들기, 그리고 디바이스 손실입니다. 먼저 조건과 결과를 한 표로 모읍니다.
표에서 프로미스가 값을 내놓고 끝나는 것(resolve)을 이행, 실패로 끝나는 것(reject)을 거부라고 적습니다.
| 조건 | 돌아오는 것 |
|---|---|
| 브라우저가 WebGPU 를 지원하지 않는다 | navigator 에 gpu 가 없다 |
| 구현이 어댑터를 내줄 수 없거나, 내주지 않기로 골랐다 | requestAdapter() 가 거부하지 않고 null 로 이행한다 |
| 요청한 기능·한도가 어댑터 능력을 넘는다 | requestDevice() 프로미스가 거부된다 |
이미 쓴("consumed") 어댑터로 requestDevice() 를 불렀다 |
프로미스가 OperationError 로 거부된다 |
만료된("expired") 어댑터로 requestDevice() 를 불렀다 · 그 밖에 요청을 채울 수 없다 |
이미 잃은 디바이스로 이행한다 |
| 객체를 만들 때 넘긴 설명(descriptor)이 올바르지 않다 | 무효 객체 + GPUValidationError |
| 자원을 할당할 메모리가 모자란다 | 무효 객체 + GPUOutOfMemoryError |
| 검사는 통과했는데 시스템이나 구현 사정으로 실패했다 | GPUInternalError |
| 디바이스를 잃었다 | lost 프로미스가 GPUDeviceLostInfo 로 이행한다 |
위 두 행이 갈리는 기준은 어댑터의 내부 상태입니다. requestDevice() 가 성공하면 그 어댑터는
"expired" 가 되므로, 한 번 쓴 어댑터로 다시 요청하면 아래 행이 걸립니다. 어댑터가 "consumed"
로 가는 길은 이 판의 자료에 없습니다(미확인).
예외 대신 무효 객체
객체를 만드는 호출은 대부분 예외를 던지지 않습니다. 디바이스 타임라인에서 난 오류는 대부분
그 디바이스에 생기는 GPUError 로 따로 알려집니다. 호출 자체는 객체를 바로 돌려줍니다.
만들 수 없었던 객체는 처음부터 무효입니다. 넘긴 설명이 성립하는 객체를 가리키지 않을 때, 자원을 할당할 메모리가 모자랄 때 이렇게 됩니다. 무효 객체는 나중에 유효해지지 않습니다.
무효 객체로 다른 객체를 만들면 그 객체도 무효가 됩니다. 이것을 전염되는 무효성이라고 부릅니다.
무효 객체를 쓴 명령이 들어간 인코더도 무효가 됩니다. 그 인코더의 finish() 결과도 무효입니다.
다음 코드는 그렇게 번지는 모습을 보입니다. 첫 줄의 버퍼는 너무 커서 OOM(Out Of Memory, 메모리 부족)이 납니다.
// The size of the buffer is too big, this causes an OOM and srcBuffer is invalid.
const srcBuffer = device.createBuffer({
size: BIG_NUMBER,
usage: GPUBufferUsage.COPY_SRC
});
const dstBuffer = ...;
// The encoder starts as a valid object.
const encoder = device.createCommandEncoder();
// Special case: an invalid object is used when encoding commands, so the encoder
// becomes invalid.
encoder.copyBufferToBuffer(srcBuffer, 0, dstBuffer, 0, 4);
// Since the encoder is invalid, encoder.finish() is invalid and returns
// an invalid object.
const commands = encoder.finish();
// The command references an invalid object so it becomes a no-op.
device.queue.submit([commands]);
메모리 부족으로 srcBuffer 가 무효가 됩니다. 그 버퍼를 복사 원본으로 쓴 인코더가 무효가 됩니다.
마지막 submit() 은 아무 일도 하지 않는 호출로 끝납니다. 자바스크립트 쪽에서는 어느 줄도 예외를
던지지 않습니다.
오류 세 종류
GPUError 의 하위 종류는 셋입니다. 오류는 발생 조건과 하위 종류를 알고리즘에 밝혀 둔 연산에서만
나야 합니다.
| 종류 | 뜻 | 다시 하면 |
|---|---|---|
GPUValidationError |
연산이 검사 요구를 다 채우지 못했다. 언제나 애플리케이션의 오류다 | 같은 기능과 한도를 쓰는 한 어느 디바이스에서나 똑같이 실패할 것으로 기대된다 |
GPUOutOfMemoryError |
연산을 끝낼 빈 메모리가 모자랐다 | 텍스처 크기를 줄이는 식으로 요구량을 낮추거나, 다른 자원의 메모리를 먼저 풀면 성공할 수 있다 |
GPUInternalError |
검사는 다 통과했는데 시스템이나 구현 사정으로 실패했다. 지원 한도로 다 잡히지 않는 구현 능력을 넘은 경우가 그 예다 | 다른 디바이스나 다른 상황에서는 성공할 수 있다 |
나중 판에서 하위 종류가 늘 수 있습니다. 그러면 이름으로 종류를 가르는 코드는 새 종류를 못 알아봅니다.
그래서 애플리케이션은 되도록 오류의 message 만 쓰는 쪽을 권합니다(should). 종류를 가를 때는
instanceof 로 가르라고 권합니다. message 는 개발자가 디버깅에 쓰라는 글이지
최종 사용자에게 보여 줄 글이 아닙니다.
오류 범위와 uncapturederror
오류를 받으려면 오류 범위를 씁니다. pushErrorScope(filter) 로 범위를 엽니다. popErrorScope() 로
닫습니다. 필터는 "validation" · "out-of-memory" · "internal" 가운데 하나입니다. 닫을 때 그 범위가
잡은 오류가 돌아옵니다. 잡은 오류가 없으면 null 이 돌아옵니다.
범위는 스택으로 쌓입니다. 연산에서 난 오류는 그때 스택 맨 위의 범위로 갑니다. 범위 하나는 잡은 오류 하나만 간직합니다. 나머지는 조용히 버립니다. 필터가 안 맞아 못 잡은 오류는 스택 아래 범위로 내려갑니다.
스택 바닥까지 내려간 오류는 어느 범위에도 안 잡힌 오류입니다. 브라우저가 고르면 디바이스에
uncapturederror 이벤트가 납니다. 이벤트를 낸 뒤에는 브라우저가 개발자 콘솔 경고 같은 방법으로 그
오류를 개발자에게 보이기를 권합니다(should). 이벤트에서 preventDefault() 를 부르면 그 경고가 꺼집니다.
flowchart TD
E["연산에서 오류가 난다"] --> T["스택 맨 위 범위"]
T -->|"필터가 맞다"| C["그 범위가 잡는다 · popErrorScope 로 돌아온다"]
T -->|"필터가 안 맞다"| N["아래 범위로 내려간다"]
N -->|"바닥까지 갔다"| U["브라우저가 고르면 uncapturederror 이벤트"]
U --> W["콘솔 경고를 권한다 · preventDefault 면 끈다"]
uncapturederror 이벤트는 원격 측정과 예상 못 한 오류 보고에 쓰라고 둔 것입니다. 잡히지 않은
오류마다 꼭 난다는 보장이 없습니다. 내는 개수를 제한할 수도 있습니다. 정상 동작 중에 날 수 있는,
알고 있는 오류는 오류 범위로 받으라고 권합니다.
이 설계는 WebGL 의 getError 와 견주면 보입니다. getError 는 동기라서 왕복이 생깁니다. 앞서 낸
작업도 다 끝나야 합니다. 상태가 하나라서 라이브러리나 브라우저 확장처럼 상관없는 코드의 오류가 섞입니다.
WebGPU 는 오류를 비동기로 돌려줍니다. 범위 스택으로 구성 요소마다 자기 오류만 잡게 합니다.
동기 예외로 오류를 알리는 길은 다중 프로세스 구현에서 감당할 수 없을 만큼 비쌉니다. 개발 중에 동기 검사가 필요하면 구현이 개발자 도구에 「WebGPU 오류에서 멈춤」 같은 선택지를 두라고 권합니다. 이 선택지는 검사하는 호출마다 프로세스 사이 왕복을 하므로 쓰면 매우 느려집니다.
디바이스 손실
디바이스를 더 쓸 수 없게 되는 일을 디바이스 손실이라고 합니다. 디바이스의 lost 프로미스는
디바이스가 살아 있는 동안 대기 상태로 있다가, 손실되는 순간 GPUDeviceLostInfo 로 이행합니다. 이
정보의 reason 은 "unknown" 이나 "destroyed" 입니다.
손실을 부르는 규칙은 넷입니다.
| 일어나는 때 | reason |
|---|---|
애플리케이션이 destroy() 를 불렀다 |
"destroyed" |
| 브라우저가 디바이스 접근을 거둬야 한다 | "unknown" |
| 부작용을 남길 수 있는 실패가 났다 (should) | 사유 값이 따로 정해져 있지 않다 |
만료된 어댑터로 requestDevice() 를 불렀다 |
"unknown" |
셋째 행에서 reason 이 두 값 가운데 어느 것이 되는지는 이 판의 자료에 없습니다(미확인).
손실은 WebGPU 호출로도, 페이지 바깥의 사건으로도 일어납니다. 그 예로는 회복할 수 없는 메모리 부족, GPU 프로세스의 비정상 종료, 오래 걸린 작업 탓의 GPU 재설정, 다른 애플리케이션이 일으킨 GPU 재설정이 있습니다. 전력을 아끼려 외장 GPU 를 끈 경우와 외부 GPU 를 뽑은 경우도 여기에 듭니다.
메모리 부족이 언제나 손실은 아닙니다. 버퍼를 할당하다 부작용 없이 실패하면 디바이스는 살아 있습니다.
GPUOutOfMemoryError 와 무효 버퍼만 남습니다. 손실의 예로 든 것은 회복할 수 없는 메모리 부족입니다.
손실된 디바이스에서는 오류가 더 나오지 않습니다. popErrorScope() 와 uncapturederror 가 오류
보고를 멈춥니다. 객체가 유효한지도 더는 알 수 없습니다. 대부분의 연산은 성공한 것처럼 보입니다.
예외는 mapAsync() 로, 매핑할 올바른 데이터를 줄 수 없어 오류를 냅니다.
stateDiagram-v2
state "쓸 수 있음" as OK
state "잃음" as LOST
[*] --> OK: requestDevice 가 디바이스를 만든다
[*] --> LOST: 만료된 어댑터로 요청했다
OK --> LOST: destroy() · 브라우저가 접근을 거둔다 · 부작용 있는 실패
note right of LOST: 들어오는 순간 lost 프로미스가 이행한다
다시 살리려면 어댑터부터 새로 받습니다. 앞 디바이스로 만든 버퍼와 텍스처는 새 디바이스로 다시
만들어야 합니다. 손실 원인 다수는 잠깐 지나가는 것이라, destroy() 로 스스로 잃은 경우가 아니면
새 디바이스를 받아 보라고 권합니다. 손실 뒤에는 검사 오류가 안 납니다. 프로미스 대부분도 정상으로
이행합니다. 그래서 이 처리를 안 해 두면 손실을 알아챌 길이 좁습니다.
관련 항목
WebGPU 가 속하는 상위 분류
그래픽스 · 렌더링 · 그래픽스 API · 웹 API · API · GPU
WebGPU 와 같은 역할을 두고 겨루는 규격
WebGL · WebGL 2 · OpenGL · OpenGL ES · Vulkan · Direct3D · Metal
WebGPU 를 정하고 구현하는 주체
W3C · GPU for the Web · 브라우저 · 그래픽 드라이버 · Dawn · wgpu
WebGPU 를 정의하는 표준·문서
RFC 2119 · RFC 8174 · W3C 권고 절차 · 후보 권고 · 적합성
WebGPU 셰이더를 적는 언어와 그 이웃 언어
WGSL · GLSL · HLSL · SPIR-V · 셰이더
WebGPU 를 부르는 브라우저 쪽 환경
자바스크립트 · canvas 요소 · OffscreenCanvas · 웹 워커 · ArrayBuffer · 프로미스
WebGPU 가 만들어 다루는 GPU 쪽 객체
어댑터 · 디바이스 · GPU 버퍼 · 텍스처 · 텍스처 뷰 · 샘플러 · 바인드 그룹 · 바인드 그룹 레이아웃 · 파이프라인 레이아웃 · 셰이더 모듈 · 렌더 파이프라인 · 컴퓨트 파이프라인 · 렌더 번들 · 쿼리 셋
WebGPU 로 그리기가 거치는 처리 단계
그래픽스 파이프라인 · 정점 셰이더 · 래스터화 · 프래그먼트 셰이더 · 깊이 테스트 · 블렌딩 · 프레임버퍼
WebGPU 가 명령을 모아 보내는 장치
커맨드 인코더 · 커맨드 버퍼 · 커맨드 큐 · 렌더 패스 · 컴퓨트 패스
WebGPU 를 안전하게 돌리는 브라우저 구조
GPU 프로세스 · 샌드박스 · IPC · 미정의 동작 · 다중 프로세스 브라우저
WebGPU 가 실패를 알리는 장치
디바이스 손실 · 검증 오류 · 오류 범위 · 무효 객체 · 메모리 부족
WebGPU 를 그림 밖에서 쓰는 분야
GPGPU · 컴퓨트 셰이더 · 작업 그룹 · 기계 학습 · WebNN · 병렬 계산
WebGPU 가 동작을 나눠 적는 타임라인
콘텐츠 타임라인 · 디바이스 타임라인 · 큐 타임라인
WebGPU 위에서 장면을 다루는 라이브러리와 모델 포맷
3D 라이브러리 · Three.js · Babylon.js · glTF
다른 이름: Web GPU · WebGPU API