Quick Flow
| 항목 | 의미 |
|---|---|
| Compute shader | 화면에 직접 그리지 않는 GPU 병렬 계산 shader |
| Dispatch | workgroup 수를 지정해 실행 |
| Workgroup | 함께 스케줄되는 실행 단위 |
| Storage buffer | 읽고 쓰는 대용량 데이터 버퍼 |
| Barrier | 같은 dispatch 안의 메모리 순서 조정 |
CPU dispatch
-> workgroups
-> many shader invocations
-> buffer 또는 texture에 결과 기록
-> 다음 pass에서 사용@group(0) @binding(0) var<storage, read_write> values: array<f32>;
@compute @workgroup_size(256)
fn addOne(@builtin(global_invocation_id) id: vec3<u32>) {
if (id.x >= arrayLength(&values)) { return; }
values[id.x] = values[id.x] + 1.0;
}구조
Compute shader는 vertex shader처럼 정점을 입력으로 받거나 fragment shader처럼 화면 샘플에 묶이지 않습니다. 개발자가 정한 grid 크기만큼 실행되고, 각 invocation은 자신의 index를 기준으로 buffer나 texture를 읽고 씁니다.
dispatch(64, 1, 1)
workgroup_size(256)
-> 최대 64 * 256개의 병렬 작업 후보이 구조는 화면에 그리는 작업보다 데이터 병렬 계산에 잘 맞습니다. particle update, culling, prefix sum, image processing, tiled light list 생성처럼 같은 계산을 많은 원소에 반복하는 작업이 대표적입니다.
WebGPU 예시의 @workgroup_size(256)은 한 workgroup 안의 invocation 수입니다. 원소가 count개이면 CPU는 보통 ceil(count / 256) workgroup을 dispatch하고, shader는 마지막 group의 범위 밖 index를 반드시 막습니다. workgroup 크기는 하드웨어 제한, register/shared memory 사용량, 접근 패턴에 따라 달라지므로 256이 보편적인 정답은 아닙니다.
그래픽 pass와의 차이
그래픽 pass는 pipeline 상태, vertex input, rasterization, depth/blend 같은 고정 기능 단계와 연결됩니다. Compute pass는 이런 단계 없이 thread-like invocation이 직접 메모리에 접근합니다. 그래서 더 자유롭지만, 메모리 충돌과 동기화 책임도 커집니다.
graphics pass
-> geometry를 화면 샘플로 변환
compute pass
-> 데이터 배열을 병렬로 갱신선택 기준
| 상황 | 판단 |
|---|---|
| 화면 픽셀마다 색을 계산 | fragment shader |
| 정점 위치를 변환 | vertex shader |
| 큰 배열을 병렬 갱신 | compute shader |
| 렌더링 전 객체 후보를 줄임 | compute culling |
| CPU에서 반복문이 병목 | GPU 병렬화 검토 |
| 작업량이 작고 readback 필요 | CPU가 더 단순할 수 있음 |
Compute shader는 GPU에서 끝나는 데이터 흐름에 특히 적합합니다. 계산 결과를 바로 CPU로 읽어 오면 동기화와 readback 비용 때문에 장점이 줄어듭니다.
같은 workgroup이 공유 메모리를 함께 쓸 때는 해당 workgroup 안에서만 barrier가 의미 있습니다. workgroup 사이 순서나 다음 render pass의 가시성은 workgroupBarrier() 하나로 해결되지 않으며, compute pass 종료 뒤의 resource usage와 command/pass 경계로 관리합니다.
주의할 점
Compute shader는 자동으로 빠른 선택지가 아닙니다. 메모리 접근 패턴이 흩어져 있거나 thread 간 충돌이 많으면 ALU보다 bandwidth와 synchronization이 병목이 됩니다.
또 dispatch 결과를 다음 render pass에서 읽으려면 resource state, barrier, pass 의존성을 명확히 관리해야 합니다. 결과가 가끔 깨지는 문제는 계산식보다 동기화 누락에서 나오는 경우가 많습니다.
동일 storage resource에 여러 invocation이 같은 위치를 쓰면 결과 순서는 정의되지 않을 수 있습니다. 원자 연산, prefix sum처럼 충돌을 피하는 알고리즘, 분리된 output buffer 중 하나를 선택하지 않고 단순 대입으로 해결하려 하면 프레임마다 값이 달라질 수 있습니다.
참고 링크
1 sources