Compute Shader Backend Differences
May 15, 2026 · View on GitHub
This document describes compute shader support across wgpu's HAL backends.
Support Matrix
| Feature | Vulkan | DX12 | Metal | GLES | Software | Browser | Noop |
|---|---|---|---|---|---|---|---|
| Compute shaders | Yes | Yes | Yes | Partial | Yes (interpreted) | Yes | No |
| Storage buffers | Yes | Yes | Yes | Yes | Yes (interpreted) | Yes | No |
| Timestamp queries | Yes | Yes | Stub | Stub | No | No | No |
| Indirect dispatch | Yes | Yes | Yes | Yes | No | Yes | No |
| Buffer mapping (GPU->CPU) | Yes | Yes | Yes | Yes | Yes | Yes | No |
| Max workgroup size X | 1024+ | 1024 | 1024 | 1024 | 1024 | Browser | N/A |
| Max workgroup invocations | 1024+ | 1024 | 1024 | 1024 | 1024 | Browser | N/A |
Vulkan
Status: Full compute support.
Vulkan provides the most complete compute shader implementation:
- Shader compilation: WGSL -> SPIR-V (via
gogpu/naga). - Timestamp queries: Fully implemented using
vkCmdWriteTimestampat the beginning and end of compute passes. Results are copied to a buffer viavkCmdCopyQueryPoolResults. - Memory barriers: Automatic global memory barrier inserted at compute pass end (
End()) to ensure shader writes are visible to subsequent commands. - Workgroup size limits: Depends on the physical device. Typical minimum guaranteed by Vulkan spec: 128 invocations per workgroup. Most desktop GPUs support 1024.
- Synchronization: Use
Fence+Device.Wait()to synchronize CPU with GPU completion. - Timestamp period:
Queue.GetTimestampPeriod()returns the nanoseconds-per-tick value for the GPU's timestamp counter.
Vulkan-Specific Notes
- Query pools are reset automatically before each timestamp write.
- The
ResolveQuerySetmethod inserts a pipeline barrier before copying results to ensure timestamps are finalized. VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BITflags ensure results are complete 64-bit values.
DirectX 12
Status: Compute shaders work. Timestamp queries supported.
- Shader compilation: WGSL -> HLSL -> DXBC via FXC (default), or WGSL -> DXIL direct via
gogpu/naga/dxil(GOGPU_DX12_DXIL=1, SM 6.0+, no external dependencies). - Timestamp queries: Fully implemented using
ID3D12Device::CreateQueryHeapwithD3D12_QUERY_TYPE_TIMESTAMPandID3D12GraphicsCommandList::EndQuery+ResolveQueryData. Begin/end-of-pass timestamps written automatically. - Workgroup size limits: Maximum 1024 invocations per workgroup (D3D12 spec).
- Indirect dispatch:
DispatchIndirectviaExecuteIndirectwith pre-createdID3D12CommandSignature(dispatch args: 3 × uint32 = 12 bytes). Also supportsDrawIndirect(16B) andDrawIndexedIndirect(20B). - Root signature: Compute pipelines use a separate root signature from graphics pipelines. Descriptor heaps are bound lazily on first
SetBindGroupcall.
DX12-Specific Notes
- Descriptor heaps must be bound before setting descriptor tables. The
ComputePassEncodertracks this withdescriptorHeapsSet. - No explicit compute pass begin/end is needed at the D3D12 API level.
Metal
Status: Compute shaders work. Timestamp queries are stubbed.
- Shader compilation: WGSL -> MSL (via
gogpu/naga). - Timestamp queries:
CreateQuerySetcurrently returnsErrTimestampsNotSupported. Metal supports counter sample buffers (MTLCounterSampleBuffer) for GPU timestamps on Apple Silicon. Implementation is planned. - Workgroup size limits: Maximum 1024 invocations per workgroup on Apple Silicon. Older Intel Macs may have lower limits.
- Compute encoder: Metal uses
MTLComputeCommandEncoder(created fromMTLCommandBuffer). The encoder isRetain-ed to survive autorelease pool drains.
Metal-Specific Notes
GetTimestampPeriod()returns 1.0 (Metal timestamps are already in nanoseconds).- Bind group resources are bound directly via
setBuffer:offset:atIndex:on the compute encoder.
OpenGL ES (GLES)
Status: Partial compute support. Requires OpenGL ES 3.1+.
- Shader compilation: WGSL -> GLSL (via
gogpu/naga). - Timestamp queries:
CreateQuerySetcurrently returnsErrTimestampsNotSupported. GLES supports timer queries viaGL_EXT_disjoint_timer_queryextension (not universally available). Implementation is planned. - Compute support flag: Check
DownlevelFlagsComputeShadersin adapter capabilities. - GLES 3.0: No compute shader support (only GLES 3.1+).
- GLES 3.1+: Compute shaders supported via
glDispatchCompute.
GLES-Specific Notes
- Compute commands are deferred:
BeginComputePassrecords commands into a list that is replayed onSubmit. - The
glMemoryBarriercall is needed after dispatch to ensure writes are visible. - Some drivers (especially Mesa llvmpipe on CI) may have limited compute support.
Software Backend
Status: Compute shaders supported via SPIR-V interpreter (v0.27.0+).
The software backend executes compute shaders on CPU through a built-in SPIR-V interpreter. Designed for shader debugging, CI/CD testing, and GPU-less environments — not for production workloads (interpreted, ~100× slower than JIT software renderers).
- Shader compilation: WGSL → SPIR-V (via
gogpu/naga), then interpreted instruction-by-instruction. - Storage buffers: Full read/write support. Buffer writes from compute shader are immediately visible.
- Atomics: OpAtomicIAdd, OpAtomicISub, OpAtomicExchange, OpAtomicCompareExchange, OpAtomicSMin/SMax/UMin/UMax.
- Workgroup shared memory: Per-workgroup allocation, zeroed at dispatch start.
- Barriers: OpControlBarrier accepted (sequential execution, no-op for synchronization).
- Shader debugger: DebugContext with breakpoints, JSON trace, watch variables. Zero overhead when disabled.
- Workgroup size: Read from OpExecutionMode LocalSize in SPIR-V.
CreateQuerySetreturns an error (timestamps not supported).DispatchIndirectlogs a warning (not yet implemented).
Verified
wgpu/examples/software-test/ — 256-element scaled-copy compute shader, all values match.
gogpu/examples/particles/ — 4096-particle orbital simulation (compute + instanced render).
Browser WebGPU Backend
Status: Full compute support via browser WebGPU API.
The browser backend delegates compute operations directly to the browser's WebGPU implementation via syscall/js. No shader compilation in Go — WGSL is passed as a string to createShaderModule().
- Shader compilation: Browser handles WGSL compilation internally.
- Storage buffers: Full support via browser
GPUBuffer. - Timestamp queries: Not yet wired (browser support varies).
- Indirect dispatch:
dispatchWorkgroupsIndirectviasyscall/js. - Buffer mapping:
mapAsync+getMappedRange→js.CopyBytesToGo. - Workgroup limits: Determined by browser/GPU combination.
Browser-Specific Notes
- All compute passes are recorded and submitted like native —
beginComputePass,setPipeline,setBindGroup,dispatchWorkgroups,end,queue.submit. - Data transfer uses
js.CopyBytesToJS/js.CopyBytesToGofor crossing the JS/WASM boundary. - Build:
GOOS=js GOARCH=wasm go build.
Noop Backend
Status: Compute shaders are not supported.
The noop backend is a testing backend that accepts all API calls without executing anything. All compute pass operations are no-ops.
CreateQuerySetreturnsErrTimestampsNotSupported.- Useful for unit testing command encoder state machines without requiring a GPU.
Workgroup Size Recommendations
| Data Size | Recommended @workgroup_size | Notes |
|---|---|---|
| < 1K elements | 64 | Small batches, low overhead |
| 1K - 100K | 64 or 128 | Good balance for most GPUs |
| 100K - 1M | 256 | Better occupancy on discrete GPUs |
| > 1M | 256 | Consider multiple dispatches |
The optimal workgroup size depends on the GPU architecture, register usage, and shared memory requirements. When in doubt, use 64 -- it works well across all backends.
Memory Considerations
Buffer Alignment
- Uniform buffers: 256-byte alignment required on most backends.
- Storage buffers: No strict alignment, but 16-byte alignment is recommended.
- Copy operations: Source/destination offsets may need alignment (check
Alignments.BufferCopyOffset).
GPU Memory Limits
Large compute workloads may exhaust GPU memory. Monitor for ErrDeviceOutOfMemory errors from buffer and pipeline creation.
Staging Buffers
To read compute results back to the CPU:
- Create the output buffer with
BufferUsageStorage | BufferUsageCopySrc. - Create a staging (readback) buffer with
BufferUsageMapRead | BufferUsageCopyDst. - Use
CopyBufferToBufferto copy from output to staging inside a command encoder. - Submit the command buffer.
- Use
Buffer.Map(ctx, MapModeRead, 0, size)to map the staging buffer (blocks until GPU completes, or returnsctx.Err()on cancellation). - Use
Buffer.MappedRange(offset, size)to access the bytes. - Call
Buffer.Unmap()when done.
This two-step process is required because GPU-optimal memory is typically not directly accessible by the CPU.
For non-blocking readback (game loops, frame-budgeted compute), use Buffer.MapAsync + Device.Poll(PollPoll) instead. See COMPUTE-SHADERS.md for full examples, and dev/research/ADR-BUFFER-MAPPING-API.md for design rationale.