From a37265b8ac1f60affb72c114dc2d29911d5b76d8 Mon Sep 17 00:00:00 2001 From: Andy Date: Sun, 15 Mar 2026 21:04:39 +0300 Subject: [PATCH] fix(core): per-stage resource limit validation in CreateBindGroupLayout Prevents wgpu-native abort when Vello compute requests 9 storage buffers on devices with limit 8. Error returned gracefully, enabling SDF fallback. --- CHANGELOG.md | 10 +++++++++- core/validate.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0839ed..c346af3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/core/validate.go b/core/validate.go index 252bb7a..f51fcca 100644 --- a/core/validate.go +++ b/core/validate.go @@ -1,6 +1,7 @@ package core import ( + "fmt" "math/bits" "github.com/gogpu/gputypes" @@ -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{ @@ -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