Skip to content
Merged
41 changes: 40 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.19.8] - 2026-03-10
## [0.20.0] - 2026-03-10

### Added

- **Core validation layer** (VAL-002) — exhaustive spec-level validation before
HAL calls. 7 validation functions in `core/validate.go` covering 30+ WebGPU
rules for textures, samplers, shaders, pipelines, bind groups, and bind group
layouts. Validates dimensions, limits, multisampling, formats, and usage flags.

- **Typed error types** (VAL-002) — 7 new typed errors with specific error kinds
and context fields: `CreateTextureError` (13 kinds), `CreateSamplerError` (5),
`CreateShaderModuleError` (3), `CreateRenderPipelineError` (8),
`CreateComputePipelineError` (3), `CreateBindGroupLayoutError` (3),
`CreateBindGroupError` (2). All support `errors.As()` for programmatic handling.

- **Deferred nil error detection** (VAL-003) — 10 pass encoder and command encoder
methods that previously silently ignored nil inputs now record deferred errors
following the WebGPU spec pattern. Errors surface at `End()` / `Finish()`:
`RenderPass.SetPipeline`, `SetBindGroup`, `SetVertexBuffer`, `SetIndexBuffer`,
`DrawIndirect`, `DrawIndexedIndirect`, `ComputePass.SetPipeline`, `SetBindGroup`,
`DispatchIndirect`, `CommandEncoder.CopyBufferToBuffer`.

- **Format conversion tests** (COV-001) — 26 new test functions across Metal (20),
Vulkan (4), DX12 (2), and GLES (5 format cases) backends.

### Fixed

- **5 nil panic paths** (VAL-001) — added nil checks in `CreateBindGroup` (nil layout),
`CreatePipelineLayout` (nil bind group layout element), `Queue.Submit` (nil command
buffer), `Surface.Configure` (nil device), `Surface.Present` (nil texture).

- **Metal: CopyDst buffer storage mode** — buffers with `CopyDst` usage were
allocated with `StorageModePrivate` (GPU-only), causing "buffer not mappable"
errors on Apple Silicon when `Queue.WriteBuffer()` tried to write. Now uses
Expand All @@ -26,6 +53,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Metal: zero-length data guard** — `WriteBuffer` and `ReadBuffer` now return
early for empty data slices, preventing a potential panic in the staging path.

### Changed

- **HAL defense-in-depth** (VAL-004) — HAL nil checks now use `"BUG: ..."` prefix
to signal core validation gaps. Removed 6 redundant spec checks (buffer size,
texture dimensions) from Vulkan, Metal, DX12 — core validates these. Added 9
missing nil checks to GLES, Software, and Noop backends.

### Dependencies

- **gputypes v0.2.0 → v0.3.0** — `TextureUsage.ContainsUnknownBits()` method,
used by core validation for texture descriptor validation (VAL-002).

## [0.19.7] - 2026-03-07

### Added
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ cmdBuffer, _ := encoder.Finish()
device.Queue().Submit(cmdBuffer)
```

**Guides:** [Getting Started](docs/compute-shaders.md) | [Backend Differences](docs/compute-backends.md)
**Guides:** [Getting Started](docs/COMPUTE-SHADERS.md) | [Backend Differences](docs/COMPUTE-BACKENDS.md)

Features: WGSL compute shaders, storage/uniform buffers, indirect dispatch, GPU timestamp queries (Vulkan), GPU-to-CPU readback.

Expand Down Expand Up @@ -295,9 +295,9 @@ import _ "github.com/gogpu/wgpu/hal/software"

## Documentation

- **[Compute Shaders Guide](docs/compute-shaders.md)** — Getting started with compute
- **[Compute Backend Differences](docs/compute-backends.md)** — Per-backend capabilities
- **[ARCHITECTURE.md](docs/architecture.md)** — System architecture
- **[Compute Shaders Guide](docs/COMPUTE-SHADERS.md)** — Getting started with compute
- **[Compute Backend Differences](docs/COMPUTE-BACKENDS.md)** — Per-backend capabilities
- **[ARCHITECTURE.md](docs/ARCHITECTURE.md)** — System architecture
- **[ROADMAP.md](ROADMAP.md)** — Development milestones
- **[CHANGELOG.md](CHANGELOG.md)** — Release notes
- **[CONTRIBUTING.md](CONTRIBUTING.md)** — Contribution guidelines
Expand Down
5 changes: 5 additions & 0 deletions computepass.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package wgpu

import (
"fmt"

"github.com/gogpu/wgpu/core"
)

Expand All @@ -18,6 +20,7 @@ type ComputePassEncoder struct {
// SetPipeline sets the active compute pipeline.
func (p *ComputePassEncoder) SetPipeline(pipeline *ComputePipeline) {
if pipeline == nil {
p.encoder.setError(fmt.Errorf("wgpu: ComputePass.SetPipeline: pipeline is nil"))
return
}
raw := p.core.RawPass()
Expand All @@ -29,6 +32,7 @@ func (p *ComputePassEncoder) SetPipeline(pipeline *ComputePipeline) {
// SetBindGroup sets a bind group for the given index.
func (p *ComputePassEncoder) SetBindGroup(index uint32, group *BindGroup, offsets []uint32) {
if group == nil {
p.encoder.setError(fmt.Errorf("wgpu: ComputePass.SetBindGroup: bind group is nil"))
return
}
raw := p.core.RawPass()
Expand All @@ -45,6 +49,7 @@ func (p *ComputePassEncoder) Dispatch(x, y, z uint32) {
// DispatchIndirect dispatches compute work with GPU-generated parameters.
func (p *ComputePassEncoder) DispatchIndirect(buffer *Buffer, offset uint64) {
if buffer == nil {
p.encoder.setError(fmt.Errorf("wgpu: ComputePass.DispatchIndirect: buffer is nil"))
return
}
p.core.DispatchIndirect(buffer.coreBuffer(), offset)
Expand Down
13 changes: 12 additions & 1 deletion core/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,18 @@ func (e *CoreCommandEncoder) MarkConsumed() {
e.status.Store(int32(CommandEncoderStatusConsumed))
}

// setError transitions to error state.
// SetError records a deferred error on this encoder.
//
// The error transitions the encoder to the Error state and will be returned
// by Finish(). This implements the WebGPU deferred error pattern where
// encoding-phase errors are collected and surfaced at Finish() time.
func (e *CoreCommandEncoder) SetError(err error) {
e.mu.Lock()
defer e.mu.Unlock()
e.setError(err)
}

// setError transitions to error state. Caller must hold e.mu.
func (e *CoreCommandEncoder) setError(err error) {
e.error = err
e.status.Store(int32(CommandEncoderStatusError))
Expand Down
Loading
Loading