Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [0.21.1] - 2026-03-15

### Fixed

- **core: per-stage resource limit validation in CreateBindGroupLayout** — Validates
storage buffer, uniform buffer, sampler, sampled texture, and storage texture counts
per shader stage against device limits before calling HAL. Prevents wgpu-native abort
when Vello compute requests 9 storage buffers on devices with limit 8. Error is now
returned gracefully, enabling fallback to SDF renderer.

## [0.21.0] - 2026-03-15

Expand Down
44 changes: 44 additions & 0 deletions core/validate.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package core

import (
"fmt"
"math/bits"

"github.com/gogpu/gputypes"
Expand Down Expand Up @@ -384,7 +385,9 @@ func ValidateBindGroupLayoutDescriptor(desc *hal.BindGroupLayoutDescriptor, limi
}

// BGL1: Entry binding numbers must be unique.
// Also count per-stage resource usage for limit validation.
seen := make(map[uint32]struct{}, len(desc.Entries))
var storageBuffers, uniformBuffers, samplers, sampledTextures, storageTextures uint32
for _, entry := range desc.Entries {
if _, ok := seen[entry.Binding]; ok {
return &CreateBindGroupLayoutError{
Expand All @@ -394,6 +397,47 @@ func ValidateBindGroupLayoutDescriptor(desc *hal.BindGroupLayoutDescriptor, limi
}
}
seen[entry.Binding] = struct{}{}

// Count resources by type for per-stage limit checks.
if entry.Buffer != nil {
switch entry.Buffer.Type {
case gputypes.BufferBindingTypeStorage, gputypes.BufferBindingTypeReadOnlyStorage:
storageBuffers++
case gputypes.BufferBindingTypeUniform:
uniformBuffers++
}
}
if entry.Sampler != nil {
samplers++
}
if entry.Texture != nil {
sampledTextures++
}
if entry.StorageTexture != nil {
storageTextures++
}
}

// BGL3: Per-stage resource limits.
if limits.MaxStorageBuffersPerShaderStage > 0 && storageBuffers > limits.MaxStorageBuffersPerShaderStage {
return fmt.Errorf("bind group layout %q: %d storage buffers exceeds limit %d",
label, storageBuffers, limits.MaxStorageBuffersPerShaderStage)
}
if limits.MaxUniformBuffersPerShaderStage > 0 && uniformBuffers > limits.MaxUniformBuffersPerShaderStage {
return fmt.Errorf("bind group layout %q: %d uniform buffers exceeds limit %d",
label, uniformBuffers, limits.MaxUniformBuffersPerShaderStage)
}
if limits.MaxSamplersPerShaderStage > 0 && samplers > limits.MaxSamplersPerShaderStage {
return fmt.Errorf("bind group layout %q: %d samplers exceeds limit %d",
label, samplers, limits.MaxSamplersPerShaderStage)
}
if limits.MaxSampledTexturesPerShaderStage > 0 && sampledTextures > limits.MaxSampledTexturesPerShaderStage {
return fmt.Errorf("bind group layout %q: %d sampled textures exceeds limit %d",
label, sampledTextures, limits.MaxSampledTexturesPerShaderStage)
}
if limits.MaxStorageTexturesPerShaderStage > 0 && storageTextures > limits.MaxStorageTexturesPerShaderStage {
return fmt.Errorf("bind group layout %q: %d storage textures exceeds limit %d",
label, storageTextures, limits.MaxStorageTexturesPerShaderStage)
}

return nil
Expand Down
Loading