diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d597269..85e29c9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,7 +35,7 @@ jobs: - name: Download wgpu-native shell: bash env: - WGPU_VERSION: "v27.0.4.0" + WGPU_VERSION: "v29.0.0.0" run: | set -e case "${{ matrix.os }}" in @@ -74,9 +74,9 @@ jobs: shell: bash run: | if [ "${{ matrix.os }}" == "windows-latest" ]; then - go test -v ./wgpu/... -run "Mat4|Vec3|StructSizes|CheckInit|WGPUError|Fuzz|NullGuard" + go test -v ./wgpu/... -run "TestABI|Mat4|Vec3|StructSizes|CheckInit|WGPUError|Fuzz|NullGuard" else - CGO_ENABLED=0 go test -v ./wgpu/... -run "Mat4|Vec3|StructSizes|CheckInit|WGPUError|Fuzz|NullGuard" + CGO_ENABLED=0 go test -v ./wgpu/... -run "TestABI|Mat4|Vec3|StructSizes|CheckInit|WGPUError|Fuzz|NullGuard" fi - name: Run fuzz tests (seed corpus only) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b4d5e92..5e89cea 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -56,7 +56,7 @@ jobs: - name: Download wgpu-native shell: bash env: - WGPU_VERSION: "v27.0.4.0" + WGPU_VERSION: "v29.0.0.0" run: | set -e case "${{ matrix.os }}" in @@ -107,9 +107,9 @@ jobs: shell: bash run: | if [ "${{ matrix.os }}" != "windows-latest" ]; then - CGO_ENABLED=0 go test -v -coverprofile=coverage.txt -covermode=atomic ./wgpu/... -run "Mat4|Vec3|StructSizes|CheckInit|WGPUError|Fuzz|NullGuard" + CGO_ENABLED=0 go test -v -coverprofile=coverage.txt -covermode=atomic ./wgpu/... -run "TestABI|Mat4|Vec3|StructSizes|CheckInit|WGPUError|Fuzz|NullGuard" else - go test -v -race -coverprofile=coverage.txt -covermode=atomic ./wgpu/... -run "Mat4|Vec3|StructSizes|CheckInit|WGPUError|Fuzz|NullGuard" + go test -v -race -coverprofile=coverage.txt -covermode=atomic ./wgpu/... -run "TestABI|Mat4|Vec3|StructSizes|CheckInit|WGPUError|Fuzz|NullGuard" fi - name: Upload coverage to Codecov diff --git a/CHANGELOG.md b/CHANGELOG.md index f609def..192157e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,66 @@ 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.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## v0.5.0 (Unreleased) + +### Breaking Changes +- **wgpu-native v29.0.0.0**: Migrated from v27.0.4.0 to v29.0.0.0 with stable webgpu-headers +- **API redesign**: All `Create*` methods now return `(*T, error)` instead of `*T` +- **Method renames**: `GetQueue()` → `Queue()`, `GetSize()` → `Size()`, `GetLimits()` → `Limits()`, etc. +- **Struct layout changes**: `Limits` field order fixed (ABI-breaking), `VertexBufferLayout` gains `nextInChain` +- **Removed types**: `SupportedLimits`, `ChainedStructOut`, `InstanceCapabilities` +- **Enum changes**: `SurfaceGetCurrentTextureStatus` simplified, `InstanceFlag_Default` semantic change +- **gputypes aliases**: Types re-exported for single-import ergonomics +- **Buffer.MapAsync** signature changed: `(mode, offset, size) (*MapPending, error)` — device argument removed, now stored in Buffer +- **Buffer.Unmap()** returns `error` (always nil per WebGPU spec; signature matches gogpu/wgpu) +- **Queue.Submit** returns `(uint64, error)` — submission index via `wgpuQueueSubmitForIndex` extension +- **Adapter.Limits()** returns `Limits` value (not pointer, no error) — cached at creation +- **Device.Limits()** returns `Limits` value (not pointer, no error) — cached at creation +- **BindGroupLayoutEntry** uses pointer sub-layouts (`*BufferBindingLayout`, `*SamplerBindingLayout`, etc.) +- **SamplerDescriptor.MaxAnisotropy** renamed to **Anisotropy** +- **Surface.Configure(device, config) error** — device is now a separate first argument +- **Surface.GetCurrentTexture()** returns `(*SurfaceTexture, bool, error)` — added suboptimal flag +- **Surface.Present(texture ...*SurfaceTexture) error** — takes optional texture argument + +### Added +- 271 enterprise ABI verification tests (`TestABI*`) +- gputypes type aliases and constant re-exports in `wgpu` package +- New v29 API functions: `GetFeatures`, `GetInstanceFeatures`, `BufferReadMappedRange`, etc. +- New enums: `InstanceFeatureName`, `ComponentSwizzle`, `PredefinedColorSpace`, `ToneMappingMode` +- New instance flags: `GPUBasedValidation`, `Debugging`, `AdvancedDebugging`, `WithEnv` +- **`Buffer.Map(ctx, mode, offset, size) error`** — context-aware blocking mapping; drives Device.Poll internally +- **`Buffer.MapAsync(mode, offset, size) (*MapPending, error)`** — truly non-blocking, resolves on next Device.Poll +- **`Buffer.MappedRange(offset, size) (*MappedRange, error)`** — type-safe view over mapped memory +- **`MapPending`** type with `Status() (ready bool, err error)` and `Wait(ctx) error` methods +- **`MappedRange`** type with `Bytes() []byte`, `Len() int`, and `Offset() uint64` methods +- **`Queue.Submit`** returns submission index `uint64` via `wgpuQueueSubmitForIndex` wgpu-native extension +- **`ImageCopyTexture`** — Go-typed descriptor for texture copy/write source (holds `*Texture`) +- **`ImageDataLayout`** — Go-typed buffer layout descriptor for WriteTexture / copy operations +- **`BufferTextureCopy`** — region descriptor combining `ImageCopyTexture` + `ImageDataLayout` + extent +- **`TextureCopy`** — region descriptor for texture-to-texture copies +- **`CommandEncoder.CopyTextureToBuffer(src *Texture, dst *Buffer, regions []BufferTextureCopy)`** — region-based copy + +### Changed +- `convert.go`: Removed 6 identity converters (TextureFormat now matches v29 natively) +- `wgpuAdapterEnumerateFeatures` → `wgpuAdapterGetFeatures` (single-call pattern) +- PushConstants → Immediates rename throughout +- `Buffer` now stores `*Device` reference internally for Poll-driven blocking Map +- All public descriptors use Go-idiomatic types: `string` labels, `bool` flags, pointer sub-structs, slice fields + +### Removed +- `SupportedLimits` wrapper struct +- `ChainedStructOut` type (aliased to `ChainedStruct`) +- `InstanceCapabilities` struct +- DX11 backend support (`InstanceBackendDX11`) +- `SurfaceGetCurrentTextureStatusOutOfMemory`, `SurfaceGetCurrentTextureStatusDeviceLost` + +### Dependencies +- wgpu-native: v27.0.4.0 → v29.0.0.0 +- goffi: v0.5.0 (unchanged) +- gputypes: v0.3.0 (unchanged) + +--- + ## [0.4.3] - 2026-03-29 ### Changed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 47f5315..400640c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -155,7 +155,7 @@ perf: optimize command encoder batch submission - Go 1.25 or later - golangci-lint v2 -- wgpu-native shared libraries (download script provided) +- wgpu-native v29.0.0.0 shared libraries (download script provided) ### Platform Requirements @@ -198,6 +198,20 @@ go test -v ./wgpu/... -run "TestDeviceCreation" go test -bench=. -benchmem ./wgpu/... ``` +Tests are organized into three tiers: + +| Tier | Filter | GPU required | Runs in CI | +|------|--------|-------------|-----------| +| **ABI tests** | `-run "TestABI"` | No | Yes | +| **Safe tests** | `-run "Mat4\|Vec3\|StructSizes\|CheckInit\|WGPUError\|Fuzz\|NullGuard"` | No | Yes | +| **GPU tests** | `-run "TestAdapter\|TestDevice\|TestBuffer\|TestSurface\|TestLeak\|TestErrorScope"` | Yes | No | + +**`abi_test.go`** contains 271 assertions that verify Go wire struct sizes and field offsets match the C ABI defined in wgpu-native v29 `webgpu.h`. These tests catch silent ABI regressions (wrong struct size, missing padding, field reorder) that would cause memory corruption at runtime. Run them after any struct change: + +```bash +go test -v -run "TestABI" ./wgpu/... +``` + ### Running Linter ```bash @@ -262,18 +276,102 @@ webgpu/ └── README.md # Main documentation ``` +## Adding a New FFI Function + +When wrapping a new wgpu-native function, follow this pattern exactly: + +**Step 1: Add the procedure handle** (`wgpu/wgpu.go` or the relevant `*.go` file) + +```go +var procDeviceNewFunction = loadProc("wgpuDeviceNewFunction") +``` + +**Step 2: Add the wire struct** (if the function takes a descriptor) + +The wire struct must match the C struct layout from `webgpu.h` exactly: +```go +// wireNewFunctionDescriptor must match WGPUNewFunctionDescriptor in webgpu.h exactly. +type newFunctionDescriptorWire struct { + NextInChain uintptr // *WGPUChainedStruct — always 8 bytes + Label StringView // {Data uintptr, Length uintptr} — always 16 bytes + SomeField uint32 // matches C uint32_t + _ [4]byte // explicit padding to match C struct alignment +} +``` + +**Step 3: Add the public method** on the appropriate receiver type + +```go +// NewFunction creates a new thing. Returns an error if the device is nil or +// wgpu-native reports a validation error. +func (d *Device) NewFunction(desc *NewFunctionDescriptor) (*NewFunction, error) { + if err := d.checkInit(); err != nil { + return nil, err + } + wire := newFunctionDescriptorWire{ + Label: toStringView(desc.Label), + SomeField: toWGPUSomeEnum(desc.SomeField), // use converter if needed + } + handle := procDeviceNewFunction.callUintptr(d.handle, uintptr(unsafe.Pointer(&wire))) + if handle == 0 { + return nil, fmt.Errorf("wgpu: DeviceNewFunction failed") + } + f := &NewFunction{handle: handle} + trackResource(handle, "NewFunction") + return f, nil +} +``` + +**Step 4: Add Release method** on the new type + +```go +// Release releases the GPU resource. Safe to call on a nil or already-released handle. +func (f *NewFunction) Release() { + if f == nil || f.handle == 0 { + return + } + untrackResource(f.handle) + procNewFunctionRelease.call(f.handle) + f.handle = 0 +} +``` + +**Step 5: Update `abi_test.go`** + +Add size and offset assertions for the new wire struct: +```go +{"newFunctionDescriptorWire", unsafe.Sizeof(newFunctionDescriptorWire{}), expectedSize}, +``` + +Run `go test -run TestABI ./wgpu/...` to verify before pushing. + +**Step 6: Add a test** + +Even a basic null-guard test is required: +```go +func TestNullGuard_NewFunction(t *testing.T) { + var f *NewFunction + f.Release() // must not panic +} +``` + +See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the full explanation of the FFI layer design, wire structs, and the enum conversion rules. + +--- + ## Adding New Features 1. Check if issue exists, if not create one 2. Discuss approach in the issue -3. Create feature branch from `develop` -4. Implement feature with tests -5. Update documentation and examples -6. Run quality checks (`bash scripts/pre-release-check.sh`) -7. Create pull request to `develop` -8. Wait for code review -9. Address feedback -10. Merge when approved +3. Create feature branch from `main` +4. Implement feature with tests (follow the FFI pattern above) +5. Update `abi_test.go` for any new wire structs +6. Update documentation and examples +7. Run quality checks (`bash scripts/pre-release-check.sh`) +8. Create pull request to `main` +9. Wait for code review +10. Address feedback +11. Merge when approved ## Code Style Guidelines @@ -362,6 +460,12 @@ See [STABILITY.md](STABILITY.md) for the API stability policy. When deprecating func OldFunction() { ... } ``` +## Architecture Reference + +For a deep dive into the internal design — wire structs vs public structs, the enum conversion layer, async callback pattern, and testing strategy — see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). + +--- + ## Platform-Specific Notes ### Windows diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 0000000..c9a8064 --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,408 @@ +# Migration Guide: v0.4.x → v0.5.0 + +v0.5.0 is a major breaking release that upgrades wgpu-native from v27 to v29 (stable webgpu-headers) and redesigns the public API for idiomatic Go usage. This guide covers every breaking change with before/after examples. + +See [CHANGELOG.md](CHANGELOG.md) for the full list of changes. + +--- + +## Table of Contents + +- [wgpu-native Binary](#wgpu-native-binary) +- [Error Returns on Create Methods](#error-returns-on-create-methods) +- [Method Renames](#method-renames) +- [Single Import: gputypes Aliases](#single-import-gputypes-aliases) +- [Buffer Mapping API](#buffer-mapping-api) +- [Queue.Submit Returns Index](#queuesubmit-returns-index) +- [Limits Return Value (No Error)](#limits-return-value-no-error) +- [BindGroupLayoutEntry Pointer Sub-Layouts](#bindgrouplayoutentry-pointer-sub-layouts) +- [SamplerDescriptor.Anisotropy Rename](#samplerdescriptoranisotropy-rename) +- [Surface API Changes](#surface-api-changes) +- [Removed Types](#removed-types) +- [Enum Changes](#enum-changes) +- [Quick Checklist](#quick-checklist) + +--- + +## wgpu-native Binary + +Download the new binary from the [wgpu-native releases page](https://github.com/gfx-rs/wgpu-native/releases). + +| Platform | Filename | +|----------|---------| +| Windows x64 | `wgpu-windows-x86_64-msvc-release.zip` → `wgpu_native.dll` | +| Linux x64 | `wgpu-linux-x86_64-release.zip` → `libwgpu_native.so` | +| macOS ARM64 | `wgpu-macos-aarch64-release.zip` → `libwgpu_native.dylib` | + +Required version: **v29.0.0.0**. The v0.5.0 Go bindings are not compatible with v27. + +--- + +## Error Returns on Create Methods + +All `Create*` methods now return `(*T, error)` instead of `*T`. This applies to every resource creation function. + +```go +// Before (v0.4.x) — nil on failure +buffer := device.CreateBuffer(&desc) +if buffer == nil { + log.Fatal("failed to create buffer") +} +defer buffer.Release() + +// After (v0.5.0) — idiomatic error return +buffer, err := device.CreateBuffer(&desc) +if err != nil { + log.Fatal(err) +} +defer buffer.Release() +``` + +Affected methods (all on `*Device` unless noted): + +| Method | v0.4.x return | v0.5.0 return | +|--------|--------------|--------------| +| `CreateBuffer` | `*Buffer` | `(*Buffer, error)` | +| `CreateTexture` | `*Texture` | `(*Texture, error)` | +| `CreateShaderModuleWGSL` | `*ShaderModule` | `(*ShaderModule, error)` | +| `CreateRenderPipeline` | `*RenderPipeline` | `(*RenderPipeline, error)` | +| `CreateComputePipeline` | `*ComputePipeline` | `(*ComputePipeline, error)` | +| `CreateBindGroup` | `*BindGroup` | `(*BindGroup, error)` | +| `CreateBindGroupLayout` | `*BindGroupLayout` | `(*BindGroupLayout, error)` | +| `CreatePipelineLayout` | `*PipelineLayout` | `(*PipelineLayout, error)` | +| `CreateCommandEncoder` | `*CommandEncoder` | `(*CommandEncoder, error)` | +| `CreateSampler` | `*Sampler` | `(*Sampler, error)` | +| `CreateQuerySet` | `*QuerySet` | `(*QuerySet, error)` | +| `Texture.CreateView` | `*TextureView` | `(*TextureView, error)` | +| `CommandEncoder.Finish` | `*CommandBuffer` | `(*CommandBuffer, error)` | + +--- + +## Method Renames + +`Get` prefix removed from accessor methods to follow Go naming conventions: + +```go +// Before (v0.4.x) // After (v0.5.0) +device.GetQueue() → device.Queue() +buffer.GetSize() → buffer.Size() +adapter.GetLimits() → adapter.Limits() +adapter.GetInfo() → adapter.Info() +device.GetLimits() → device.Limits() +device.GetFeatures() → device.Features() +texture.GetWidth() → texture.Width() +texture.GetHeight() → texture.Height() +texture.GetDepthOrArrayLayers() → texture.DepthOrArrayLayers() +texture.GetFormat() → texture.Format() +texture.GetDimension() → texture.Dimension() +texture.GetUsage() → texture.Usage() +``` + +Additionally, `Adapter.EnumerateFeatures()` is now `Adapter.Features()`. The old name is kept as a deprecated alias until v1.0. + +--- + +## Single Import: gputypes Aliases + +v0.5.0 re-exports gputypes types and constants as aliases in the `wgpu` package. A separate `gputypes` import is no longer required. + +```go +// Before (v0.4.x) — two imports required +import ( + "github.com/go-webgpu/webgpu/wgpu" + "github.com/gogpu/gputypes" +) + +surfaceConfig.Format = gputypes.TextureFormatBGRA8Unorm +bufferDesc.Usage = gputypes.BufferUsageVertex | gputypes.BufferUsageCopyDst +``` + +```go +// After (v0.5.0) — single import +import "github.com/go-webgpu/webgpu/wgpu" + +surfaceConfig.Format = wgpu.TextureFormatBGRA8Unorm +bufferDesc.Usage = wgpu.BufferUsageVertex | wgpu.BufferUsageCopyDst +``` + +The direct `gputypes` import still works and produces the same types (they are aliases, not copies). Use the direct import when sharing types with other gogpu ecosystem packages. + +--- + +## Buffer Mapping API + +The buffer mapping API has been redesigned to match gogpu/wgpu and support both blocking and async patterns. + +### Blocking mapping + +```go +// Before (v0.4.x) — MapAsync with device arg, then GetMappedRange unsafe.Pointer +if err := buffer.MapAsync(device, wgpu.MapModeRead, 0, size); err != nil { + return err +} +ptr := buffer.GetMappedRange(0, size) +data := unsafe.Slice((*byte)(ptr), size) +defer buffer.Unmap() + +// After (v0.5.0) — Map blocks until done, MappedRange returns safe type +if err := buffer.Map(ctx, wgpu.MapModeRead, 0, size); err != nil { + return err +} +defer buffer.Unmap() +mr, err := buffer.MappedRange(0, size) +if err != nil { + return err +} +data := mr.Bytes() // []byte, valid until Unmap +``` + +### Truly async mapping + +```go +// Before (v0.4.x) — no truly async variant +if err := buffer.MapAsync(device, wgpu.MapModeRead, 0, size); err != nil { + return err +} + +// After (v0.5.0) — non-blocking: returns *MapPending immediately +pending, err := buffer.MapAsync(wgpu.MapModeRead, 0, size) +if err != nil { + return err +} + +// Option A: poll status manually +for { + ready, err := pending.Status() + if ready { + // err is nil on success + break + } + device.Poll(false) +} + +// Option B: block with context +if err := pending.Wait(ctx); err != nil { + return err +} + +// Then access mapped data as usual +mr, _ := buffer.MappedRange(0, size) +data := mr.Bytes() +``` + +### GetMappedRange still available + +`Buffer.GetMappedRange(offset, size)` is retained for low-level access. `MappedRange` wraps it with type safety and buffer-state validation. + +--- + +## Queue.Submit Returns Index + +`Queue.Submit` now returns the submission index, useful for fence-based synchronization. + +```go +// Before (v0.4.x) +if err := queue.Submit(cmdBuf); err != nil { + return err +} + +// After (v0.5.0) +subIdx, err := queue.Submit(cmdBuf) +if err != nil { + return err +} +// subIdx (uint64) can be used with Device.Poll or future fence APIs +_ = subIdx +``` + +--- + +## Limits Return Value (No Error) + +`Adapter.Limits()` and `Device.Limits()` now return `Limits` directly (cached at creation, no FFI call). + +```go +// Before (v0.4.x) — SupportedLimits wrapper, error return +supported, err := adapter.GetLimits() +if err != nil { + return err +} +maxBuffers := supported.Limits.MaxVertexBuffers + +// After (v0.5.0) — value return, no error, no wrapper +limits := adapter.Limits() +maxBuffers := limits.MaxVertexBuffers +``` + +--- + +## BindGroupLayoutEntry Pointer Sub-Layouts + +Sub-layout fields (`Buffer`, `Sampler`, `Texture`, `StorageTexture`) are now pointers. A nil pointer means "not this binding type". These types are gputypes aliases for cross-project compatibility. + +```go +// Before (v0.4.x) — value types in sub-layouts +entries := []wgpu.BindGroupLayoutEntry{ + { + Binding: 0, + Visibility: wgpu.ShaderStageVertex, + Buffer: wgpu.BufferBindingLayout{ + Type: wgpu.BufferBindingTypeUniform, + }, + }, +} + +// After (v0.5.0) — pointer sub-layouts; nil = "not this type" +entries := []wgpu.BindGroupLayoutEntry{ + { + Binding: 0, + Visibility: wgpu.ShaderStageVertex, + Buffer: &wgpu.BufferBindingLayout{ + Type: wgpu.BufferBindingTypeUniform, + }, + }, +} +``` + +--- + +## SamplerDescriptor.Anisotropy Rename + +`MaxAnisotropy` is renamed to `Anisotropy`. The old name is kept as a deprecated alias. + +```go +// Before (v0.4.x) +sampler, err := device.CreateSampler(&wgpu.SamplerDescriptor{ + MaxAnisotropy: 1, +}) + +// After (v0.5.0) +sampler, err := device.CreateSampler(&wgpu.SamplerDescriptor{ + Anisotropy: 1, +}) +``` + +--- + +## Surface API Changes + +### Configure requires device argument + +```go +// Before (v0.4.x) +surface.Configure(&config) + +// After (v0.5.0) — device is separate first argument, returns error +if err := surface.Configure(device, &config); err != nil { + return err +} +``` + +### GetCurrentTexture returns suboptimal flag + +```go +// Before (v0.4.x) +tex, err := surface.GetCurrentTexture() + +// After (v0.5.0) — added suboptimal bool +tex, suboptimal, err := surface.GetCurrentTexture() +if err != nil { + return err +} +if suboptimal { + // Consider reconfiguring the surface +} +``` + +### Present takes texture argument + +```go +// Before (v0.4.x) +if err := surface.Present(); err != nil { + return err +} + +// After (v0.5.0) — takes the texture returned by GetCurrentTexture +if err := surface.Present(tex); err != nil { + return err +} +``` + +--- + +## Removed Types + +### SupportedLimits + +`SupportedLimits` wrapper struct is removed. `Limits` is now returned directly. + +```go +// Before (v0.4.x) +supported, err := adapter.GetLimits() +maxBuffers := supported.Limits.MaxVertexBuffers + +// After (v0.5.0) +limits, err := adapter.Limits() +maxBuffers := limits.MaxVertexBuffers +``` + +### ChainedStructOut + +`ChainedStructOut` is aliased to `ChainedStruct` (v29 unified them). If your code references `ChainedStructOut`, replace with `ChainedStruct`. + +### InstanceCapabilities + +`InstanceCapabilities` struct is removed. Use `GetInstanceFeatures()` if you need instance-level feature queries. + +--- + +## Enum Changes + +### SurfaceGetCurrentTextureStatus + +Status values simplified. `SurfaceGetCurrentTextureStatusOutOfMemory` and `SurfaceGetCurrentTextureStatusDeviceLost` are removed. Check `WGPUError` from the error return of `CreateTexture` instead. + +### InstanceFlag + +`InstanceFlag_Default` semantic changed in v29. Use explicit flags: + +```go +// Before (v0.4.x) +desc := wgpu.InstanceDescriptor{Flags: wgpu.InstanceFlagDefault} + +// After (v0.5.0) +desc := wgpu.InstanceDescriptor{Flags: wgpu.InstanceFlagNone} +// or with validation: +desc := wgpu.InstanceDescriptor{Flags: wgpu.InstanceFlagDebugging} +``` + +### DX11 Backend Removed + +`InstanceBackendDX11` is removed from `InstanceBackend` flags. wgpu-native v29 uses D3D12 on Windows. + +--- + +## Quick Checklist + +After updating to v0.5.0: + +- [ ] Download wgpu-native v29.0.0.0 for your platform +- [ ] Update Go module: `go get github.com/go-webgpu/webgpu@v0.5.0` +- [ ] Add `err` return handling to all `Create*` calls +- [ ] Replace `GetQueue()` → `Queue()`, `GetLimits()` → `Limits()`, `GetInfo()` → `Info()` +- [ ] Replace `GetSize()` → `Size()` on Buffer +- [ ] Replace `GetWidth()`/`GetHeight()` → `Width()`/`Height()` on Texture +- [ ] Remove separate `gputypes` import if no longer needed +- [ ] Replace `supported.Limits.X` → `limits.X` (SupportedLimits removed, Limits() value return) +- [ ] Update `Adapter.Limits()` / `Device.Limits()` calls: remove error handling (returns value now) +- [ ] Replace `buffer.MapAsync(device, mode, offset, size)` with `buffer.Map(ctx, mode, offset, size)` (blocking) or `buffer.MapAsync(mode, offset, size)` (non-blocking) +- [ ] Replace `unsafe.Pointer` from `GetMappedRange` with `buffer.MappedRange(offset, size)` → `.Bytes()` +- [ ] Update `Queue.Submit` callers: now returns `(uint64, error)` instead of `error` +- [ ] Update `BindGroupLayoutEntry`: sub-layout fields now pointers (`Buffer: &wgpu.BufferBindingLayout{...}`) +- [ ] Rename `SamplerDescriptor.MaxAnisotropy` → `Anisotropy` +- [ ] Update `surface.Configure(&config)` → `surface.Configure(device, &config)` +- [ ] Update `surface.GetCurrentTexture()` callers: now returns `(*SurfaceTexture, bool, error)` +- [ ] Update `surface.Present()` → `surface.Present(tex)` (pass texture returned by GetCurrentTexture) +- [ ] Replace `ChainedStructOut` → `ChainedStruct` +- [ ] Remove any reference to `InstanceBackendDX11` +- [ ] Run `go build ./...` and fix remaining compilation errors diff --git a/README.md b/README.md index fb6b0b9..3945f3b 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,10 @@ Pure Go WebGPU bindings using [goffi](https://github.com/go-webgpu/goffi) + [wgp |---------|--------| | Instance, Adapter, Device | ✅ | | Buffers (vertex, index, uniform, storage) | ✅ | +| Buffer Mapping (Map with context, async MapPending, type-safe MappedRange) | ✅ | +| Queue Submission Index Tracking | ✅ | | Textures, Samplers, Storage Textures | ✅ | +| Region-Based Copy Operations (CopyTextureToBuffer) | ✅ | | Render Pipelines | ✅ | | Compute Pipelines | ✅ | | Depth Buffer | ✅ | @@ -39,7 +42,7 @@ Pure Go WebGPU bindings using [goffi](https://github.com/go-webgpu/goffi) + [wgp ## Requirements - Go 1.25+ -- wgpu-native v27.0.4.0 ([download](https://github.com/gfx-rs/wgpu-native/releases)) +- wgpu-native v29.0.0.0 ([download](https://github.com/gfx-rs/wgpu-native/releases)) ## Installation @@ -56,19 +59,29 @@ export WGPU_NATIVE_PATH=/path/to/libwgpu_native.so ## Type System -This library uses [gputypes](https://github.com/gogpu/gputypes) for WebGPU type definitions, ensuring compatibility with the [gogpu ecosystem](https://github.com/gogpu) and webgpu.h specification. +WebGPU types from [gputypes](https://github.com/gogpu/gputypes) are re-exported as type aliases in the `wgpu` package. A single import is sufficient: + +```go +import "github.com/go-webgpu/webgpu/wgpu" + +// gputypes constants available directly via wgpu package +config.Format = wgpu.TextureFormatBGRA8Unorm +buffer.Usage = wgpu.BufferUsageVertex | wgpu.BufferUsageCopyDst +``` + +If you use multiple gogpu ecosystem packages, importing `gputypes` directly also works and is fully compatible: ```go import ( "github.com/go-webgpu/webgpu/wgpu" - "github.com/gogpu/gputypes" + "github.com/gogpu/gputypes" // optional — for ecosystem interop ) -// Use gputypes for WebGPU enums -config.Format = gputypes.TextureFormatBGRA8Unorm -buffer.Usage = gputypes.BufferUsageVertex | gputypes.BufferUsageCopyDst +config.Format = gputypes.TextureFormatBGRA8Unorm // same underlying type ``` +go-webgpu API is designed to be compatible with gogpu/wgpu, enabling future backend switching within the gogpu ecosystem. + ## Quick Start ```go @@ -132,17 +145,26 @@ cd examples/triangle && go run . ``` ┌─────────────────────────────────────────┐ │ Your Go Application │ +│ (uses wgpu.TextureFormatBGRA8Unorm) │ ├─────────────────────────────────────────┤ -│ go-webgpu (this package) │ -│ - Zero CGO │ -│ - Pure Go FFI via goffi │ +│ go-webgpu/wgpu (this package) │ +│ - Go-idiomatic API │ +│ - gputypes type aliases │ +│ - Error returns on all operations │ ├─────────────────────────────────────────┤ -│ wgpu-native (Rust WebGPU) │ +│ Internal FFI layer │ +│ - Wire structs (C-layout) │ +│ - convert.go (enum translation) │ +│ - goffi (Pure Go FFI) │ +├─────────────────────────────────────────┤ +│ wgpu-native v29 (Rust WebGPU) │ ├─────────────────────────────────────────┤ │ Vulkan / Metal / DX12 / OpenGL │ └─────────────────────────────────────────┘ ``` +For a detailed explanation of the architecture, including why convert.go exists and the FFI wire struct contract, see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). + ## Looking for Pure Go WebGPU? This project uses FFI bindings to wgpu-native. If you're looking for a **100% Pure Go** WebGPU implementation (no native dependencies), check out: @@ -158,8 +180,9 @@ This project uses FFI bindings to wgpu-native. If you're looking for a **100% Pu ## Dependencies -- [goffi](https://github.com/go-webgpu/goffi) — Pure Go FFI for callbacks -- [wgpu-native](https://github.com/gfx-rs/wgpu-native) — Rust WebGPU implementation +- [goffi](https://github.com/go-webgpu/goffi) — Pure Go FFI (callbacks, cross-platform loading) +- [gputypes](https://github.com/gogpu/gputypes) — Shared WebGPU type definitions for the gogpu ecosystem +- [wgpu-native](https://github.com/gfx-rs/wgpu-native) — Rust WebGPU implementation (runtime binary, not a Go dependency) - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) — Platform-specific syscalls ## License diff --git a/ROADMAP.md b/ROADMAP.md index b0aa703..45fb54e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -43,9 +43,9 @@ Enable **GPU-accelerated graphics and compute in pure Go** — no CGO, no comple | Component | Version | Role | |-----------|---------|------| -| [wgpu-native](https://github.com/gfx-rs/wgpu-native) | v27.0.4.0 | WebGPU implementation (Rust) | -| [goffi](https://github.com/go-webgpu/goffi) | v0.3.7 | Zero-CGO FFI layer | -| [gputypes](https://github.com/gogpu/gputypes) | latest | WebGPU type definitions | +| [wgpu-native](https://github.com/gfx-rs/wgpu-native) | v29.0.0.0 | WebGPU implementation (Rust) | +| [goffi](https://github.com/go-webgpu/goffi) | v0.5.0 | Zero-CGO FFI layer | +| [gputypes](https://github.com/gogpu/gputypes) | v0.3.0 | WebGPU type definitions | --- @@ -69,24 +69,35 @@ We use GitHub labels to track feature progress: | Feature | Status | Issue | |---------|--------|-------| -| gputypes integration | `stable` | — | -| wgpu-native v27 compatibility | `stable` | — | -| All 11 examples working | `stable` | — | -| Enum conversion layer | `stable` | — | +| gputypes integration | ✅ `stable` | — | +| wgpu-native v27 compatibility | ✅ `stable` | — | +| wgpu-native v29 migration (stable webgpu-headers) | ✅ `stable` | [#3](https://github.com/go-webgpu/webgpu/issues/3) | +| webgpu-headers upgrade | ✅ `stable` | [#3](https://github.com/go-webgpu/webgpu/issues/3) | +| All 11 examples working | ✅ `stable` | — | +| Enum conversion layer (convert.go) | ✅ `stable` | — | +| Error returns on all Create* methods | ✅ `stable` | — | +| gputypes type aliases (single-import) | ✅ `stable` | — | +| 271 ABI verification tests | ✅ `stable` | — | --- ## Next: Advanced Features -**Focus**: Complete WebGPU API coverage. +**Focus**: Complete WebGPU API coverage and extended wgpu-native capabilities. | Feature | Status | Issue | |---------|--------|-------| -| Storage textures | `exploring` | [#TBD](https://github.com/go-webgpu/webgpu/issues) | -| Texture arrays | `exploring` | [#TBD](https://github.com/go-webgpu/webgpu/issues) | -| Occlusion queries | `exploring` | [#TBD](https://github.com/go-webgpu/webgpu/issues) | -| Pipeline statistics | `exploring` | [#TBD](https://github.com/go-webgpu/webgpu/issues) | -| Multi-draw indirect | `exploring` | [#TBD](https://github.com/go-webgpu/webgpu/issues) | +| Full gogpu/wgpu API parity | ✅ `stable` | — | +| Buffer mapping (Map with context, async MapPending) | ✅ `stable` | — | +| Region-based copy operations (CopyTextureToBuffer) | ✅ `stable` | — | +| Queue submission index (wgpuQueueSubmitForIndex) | ✅ `stable` | — | +| wgpu-native extensions: logging (`wgpuSetLogCallback`) | `exploring` | — | +| wgpu-native extensions: backend selection (`WGPUInstanceExtras`) | `exploring` | — | +| Storage textures | `exploring` | — | +| Texture arrays | `exploring` | — | +| Occlusion queries | `exploring` | — | +| Pipeline statistics | `exploring` | — | +| Multi-draw indirect | `exploring` | — | --- @@ -191,6 +202,14 @@ We track these projects for updates: | Version | Date | Highlights | |---------|------|------------| +| **v0.5.0** | Unreleased | wgpu-native v29, error returns, method renames, gputypes aliases, 271 ABI tests, Buffer.Map/MapAsync/MappedRange, Queue.Submit index, CopyTextureToBuffer regions, gogpu/wgpu API parity | +| **v0.4.3** | 2026-03-29 | goffi v0.5.0 (Windows ARM64/Snapdragon X, FreeBSD amd64) | +| v0.4.2 | 2026-03-04 | goffi v0.4.2 (purego nofakecgo fix) | +| v0.4.1 | 2026-03-02 | goffi v0.4.1 (ABI compliance hotfix) | +| v0.4.0 | 2026-02-27 | Null handle guards, 85 null guard tests, WGPU_NATIVE_PATH env var | +| v0.3.2 | 2026-02-27 | goffi v0.4.0 (crosscall2 integration) | +| v0.3.1 | 2026-02-18 | goffi v0.3.9 (ARM64 callback fix) | +| v0.3.0 | 2026-02-09 | Error scopes, typed errors, resource leak detection, fuzz testing, API stability policy | | **v0.2.0** | 2026-01-29 | gputypes integration, wgpu-native v27, all examples fixed | | v0.1.4 | 2026-01-03 | goffi v0.3.7 (ARM64 Darwin) | | v0.1.3 | 2025-12-29 | goffi v0.3.6 (ARM64 HFA fix) | diff --git a/STABILITY.md b/STABILITY.md index 99568b5..c44db3a 100644 --- a/STABILITY.md +++ b/STABILITY.md @@ -26,14 +26,16 @@ These APIs are unlikely to change and will follow the deprecation policy below: | Category | Examples | |----------|---------| | **Instance lifecycle** | `CreateInstance`, `Instance.Release`, `Instance.ProcessEvents` | -| **Adapter** | `Instance.RequestAdapter`, `Adapter.GetLimits`, `Adapter.GetInfo`, `Adapter.Release` | -| **Device** | `Adapter.RequestDevice`, `Device.GetQueue`, `Device.Release` | -| **Buffer** | `Device.CreateBuffer`, `Buffer.MapAsync`, `Buffer.GetMappedRange`, `Buffer.Unmap`, `Buffer.Release` | -| **Texture** | `Device.CreateTexture`, `Texture.CreateView`, `Texture.Release`, `TextureView.Release` | +| **Adapter** | `Instance.RequestAdapter`, `Adapter.Limits` (value return, cached), `Adapter.Info`, `Adapter.Features`, `Adapter.Release` | +| **Device** | `Adapter.RequestDevice`, `Device.Queue`, `Device.Limits` (value return, cached), `Device.Release` | +| **Buffer** | `Device.CreateBuffer`, `Buffer.Map`, `Buffer.MapAsync`, `Buffer.MappedRange`, `Buffer.GetMappedRange`, `Buffer.Size`, `Buffer.Unmap`, `Buffer.Release` | +| **MapPending** | `MapPending.Status`, `MapPending.Wait`, `MapPending.Release` | +| **MappedRange** | `MappedRange.Bytes`, `MappedRange.Len`, `MappedRange.Offset` | +| **Texture** | `Device.CreateTexture`, `Texture.CreateView`, `Texture.Width`, `Texture.Height`, `Texture.Format`, `Texture.Release`, `TextureView.Release` | | **Shader** | `Device.CreateShaderModuleWGSL`, `ShaderModule.Release` | | **Pipeline** | `Device.CreateRenderPipeline`, `Device.CreateComputePipeline`, `*Pipeline.Release` | | **Bind Group** | `Device.CreateBindGroup`, `Device.CreateBindGroupLayout`, `*.Release` | -| **Command** | `Device.CreateCommandEncoder`, `CommandEncoder.Finish`, `Queue.Submit` | +| **Command** | `Device.CreateCommandEncoder`, `CommandEncoder.Finish`, `Queue.Submit` (returns `(uint64, error)`) | | **Render Pass** | `CommandEncoder.BeginRenderPass`, `RenderPassEncoder.*`, `RenderPassEncoder.End` | | **Compute Pass** | `CommandEncoder.BeginComputePass`, `ComputePassEncoder.*`, `ComputePassEncoder.End` | | **Surface** | `Surface.Configure`, `Surface.GetCurrentTexture`, `Surface.Present`, `Surface.Release` | @@ -47,8 +49,7 @@ These APIs may change in minor versions: | API | Reason | Stability target | |-----|--------|-----------------| -| `Device.GetLimits` | Returns error on some wgpu-native versions | v1.0 | -| `Device.GetFeatures` | Uses newer wgpuDeviceGetFeatures API | v1.0 | +| `Device.Features` | Uses newer wgpuDeviceGetFeatures API | v1.0 | | `Surface.GetCapabilities` | New in v0.3 | v1.0 | | `*Simple` convenience methods | May be renamed or adjusted | v1.0 | | Math helpers (`Mat4`, `Vec3`) | May move to separate package | v1.0 | @@ -93,4 +94,10 @@ This library tracks wgpu-native releases. When wgpu-native makes breaking change 2. If user-facing API must change, it follows the deprecation policy above. 3. The supported wgpu-native version is documented in `go.mod` and README. -Current: **wgpu-native v24.0.3.1** (v27 API) +Current: **wgpu-native v29.0.0.0** (stable webgpu-headers) + +## v0.5.0: Major Breaking Release + +v0.5.0 is a significant milestone: it upgrades from wgpu-native v27 to v29 (stable webgpu-headers), introduces error returns on all `Create*` methods, removes the `Get` prefix from accessor methods, and re-exports gputypes as type aliases for single-import ergonomics. + +For a complete migration guide, see [MIGRATION.md](MIGRATION.md). diff --git a/UPSTREAM.md b/UPSTREAM.md index 7323a6c..624c50b 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -8,8 +8,8 @@ This document tracks upstream dependencies, pinned versions, and compatibility f | Dependency | Version | Commit | Date | |------------|---------|--------|------| -| **wgpu-native** | [v27.0.4.0](https://github.com/gfx-rs/wgpu-native/releases/tag/v27.0.4.0) | [`768f15f`](https://github.com/gfx-rs/wgpu-native/commit/768f15f6ace8e4ec8e8720d5732b29e0b34250a8) | 2025-12-23 | -| **webgpu.h** | wgpu-native bundled | same as above | — | +| **wgpu-native** | [v29.0.0.0](https://github.com/gfx-rs/wgpu-native/releases/tag/v29.0.0.0) | — | 2026-04-10 | +| **webgpu.h** | wgpu-native bundled | [`7d3186c`](https://github.com/webgpu-native/webgpu-headers/commit/7d3186c3dd2c708703524027b46b8703534ab3cc) | stable | | **goffi** | [v0.5.0](https://github.com/go-webgpu/goffi/releases/tag/v0.5.0) | [`ca3231c`](https://github.com/go-webgpu/goffi/commit/ca3231c) | 2026-03-20 | | **gputypes** | [v0.3.0](https://github.com/gogpu/gputypes/releases/tag/v0.3.0) | [`209398e`](https://github.com/gogpu/gputypes/commit/209398e) | 2026-03-20 | @@ -17,6 +17,7 @@ This document tracks upstream dependencies, pinned versions, and compatibility f | go-webgpu | wgpu-native | goffi | gputypes | Go | |-----------|-------------|-------|----------|----| +| v0.5.0 | v29.0.0.0 | v0.5.0 | v0.3.0 | 1.25+ | | v0.4.3 | v27.0.4.0 | v0.5.0 | v0.3.0 | 1.25+ | | v0.4.2 | v27.0.4.0 | v0.4.2 | v0.2.0 | 1.25+ | | v0.4.1 | v27.0.4.0 | v0.4.1 | v0.2.0 | 1.25+ | @@ -110,4 +111,4 @@ Enum values in gputypes follow the webgpu.h specification. When gputypes updates --- -*Last updated: 2026-03-29 (v0.4.3)* +*Last updated: 2026-05-26 (v0.5.0)* diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..0144f64 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,352 @@ +# Architecture + +This document explains the internal design of go-webgpu for contributors. It covers the layered architecture, the FFI contract, the enum translation layer, and the testing strategy. + +--- + +## Table of Contents + +- [High-Level Overview](#high-level-overview) +- [FFI Layer Architecture](#ffi-layer-architecture) +- [Struct Layout Contract](#struct-layout-contract) +- [convert.go — The Enum Translation Layer](#convertgo--the-enum-translation-layer) +- [gputypes Relationship](#gputypes-relationship) +- [Async Callbacks](#async-callbacks) +- [Testing Strategy](#testing-strategy) +- [Ecosystem Context](#ecosystem-context) + +--- + +## High-Level Overview + +``` +Your Go Application + │ uses public API (Go strings, typed values) + ▼ +go-webgpu/wgpu (public package) + ├─ Public structs — Go-idiomatic types, gputypes aliases + ├─ Method receivers — error-returning wrappers + └─ gputypes_aliases.go — re-exports for single-import UX + │ calls + ▼ +Internal FFI layer + ├─ Wire structs — must match C ABI exactly (field order, padding) + ├─ convert.go — translates enum values between gputypes and wgpu-native + ├─ loader*.go — cross-platform dynamic library loading + └─ wgpu.go / procs — syscall procedure handles + │ calls + ▼ +wgpu-native v29 (Rust) — dynamically loaded .dll / .so / .dylib + │ calls + ▼ +Vulkan / Metal / D3D12 / OpenGL +``` + +**Design principle**: the public API is Go-idiomatic. The FFI-level ABI complexity is entirely contained in the internal layer and never surfaces to users. + +--- + +## FFI Layer Architecture + +### Public structs vs Wire structs + +Public structs use Go-friendly types. Wire structs must match C memory layout exactly. + +```go +// Public struct — user creates this +type TextureDescriptor struct { + Label StringView + Usage gputypes.TextureUsage // Go-typed + Dimension gputypes.TextureDimension + Format gputypes.TextureFormat + // ... +} + +// Wire struct — passed directly to wgpu-native +type textureDescriptorWire struct { + NextInChain uintptr + Label StringView + Usage uint64 // CRITICAL: wgpu-native uses uint64 for flags + Dimension uint32 // converted: gputypes value → wgpu-native value + Format uint32 // converted via map in convert.go + // ... +} +``` + +Conversion happens **inside each method** before the FFI call: + +```go +func (d *Device) CreateTexture(desc *TextureDescriptor) (*Texture, error) { + wire := textureDescriptorWire{ + Usage: uint64(desc.Usage), // bitflag: direct cast + Dimension: toWGPUTextureDimension(desc.Dimension), // +1 shift + Format: toWGPUTextureFormat(desc.Format), // lookup table + // ... + } + // Call wgpu-native with &wire, not &desc +} +``` + +The user never sees wire structs. They are unexported and created only at the FFI call site. + +### Procedure handles + +All wgpu-native functions are loaded once at `Init()` time via platform-specific loaders: + +- `loader_windows.go` — `syscall.LazyDLL` / `syscall.LazyProc` +- `loader_unix.go` — goffi `ffi.LoadLibrary` / `ffi.GetSymbol` + +Procedures are package-level variables (`procCreateInstance`, `procDeviceGetQueue`, etc.) used directly in method bodies. + +--- + +## Struct Layout Contract + +**Every wire struct field order, type, and padding must exactly match the C struct in webgpu.h.** + +This is verified at compile time by `abi_test.go` (271 tests) using `unsafe.Sizeof` and `unsafe.Offsetof` assertions. + +Rules: +1. Field order must follow the C struct definition (copy from webgpu.h comments) +2. Enum fields that are `uint32` in C must be `uint32` in Go (not Go enum types) +3. Flag fields that are `uint64` in C must be `uint64` in Go (wgpu-native extends several flag types to 64-bit) +4. Pointer fields are `uintptr` (8 bytes on 64-bit, covers `WGPUSomething*` and `void*`) +5. Booleans are `uint32` (`WGPUBool` = C `uint32_t`) +6. `StringView` is `{Data uintptr, Length uintptr}` — two pointers (16 bytes on 64-bit) + +**Any struct change requires updating both the Go struct and `abi_test.go`.** + +Example of a v29 ABI-breaking change: `Limits` gained `NextInChain uintptr` as its first field and two fields changed position. This would silently corrupt all limit queries if the wire struct was not updated. + +--- + +## convert.go — The Enum Translation Layer + +`convert.go` bridges two independent numbering systems: **gputypes** (Go-idiomatic, WebGPU JS spec) and **wgpu-native v29** (C spec, with some structural differences). + +### Why conversions are needed + +The wgpu-native v29 C header introduces `BindingNotUsed=0` as a sentinel for binding-related enums. gputypes does not have this sentinel and starts from `Undefined=0`, shifting all subsequent values by one position. + +### Enums that need explicit conversion + +| Enum | Reason | Conversion | +|------|--------|-----------| +| `BufferBindingType` | v29 adds `BindingNotUsed=0` | `Undefined=0 → 0`, others `+1` | +| `SamplerBindingType` | same | same | +| `TextureSampleType` | same | same | +| `StorageTextureAccess` | same | same | +| `VertexFormat` | gputypes omits single-component 8/16-bit variants added in v29 | full lookup table | +| `VertexStepMode` | gputypes has `VertexBufferNotUsed=1` removed in v29; values shifted | lookup table | + +Conversion functions follow the naming convention `toWGPU` and return `uint32`. + +### Enums that match exactly — no converter needed + +All bitflag enums match between gputypes and wgpu-native v29 because they use power-of-2 values defined by the WebGPU spec: + +- `TextureFormat` — gputypes v0.3.0 matches v29 exactly, including `R16*/RG16*` Unorm/Snorm variants +- All flag types: `BufferUsage`, `TextureUsage`, `ShaderStage`, `ColorWriteMask`, `MapMode` +- `LoadOp`, `StoreOp`, `BlendFactor`, `BlendOperation` +- `PrimitiveTopology`, `FrontFace`, `CullMode`, `IndexFormat` +- `FilterMode`, `MipmapFilterMode`, `AddressMode`, `CompareFunction`, `StencilOperation` +- `TextureViewDimension`, `TextureDimension`, `TextureAspect` +- `PresentMode`, `CompositeAlphaMode`, `PowerPreference` + +For these enums, a direct `uint32(value)` cast is safe. + +### Adding a new conversion + +When adding support for a new enum, check whether it has a `BindingNotUsed=0` or structural gap. Compare the gputypes values against the webgpu.h C header values before deciding whether a converter is needed. + +--- + +## gputypes Relationship + +`gogpu/gputypes` is the shared type registry for the entire gogpu ecosystem. It defines WebGPU enums and struct types following the WebGPU JavaScript specification numbering. + +go-webgpu re-exports gputypes types and constants as type aliases in `gputypes_aliases.go`: + +```go +// gputypes_aliases.go +type TextureFormat = gputypes.TextureFormat +const TextureFormatBGRA8Unorm = gputypes.TextureFormatBGRA8Unorm +// ... +``` + +This means users can write `wgpu.TextureFormatBGRA8Unorm` with a single import. There is no wrapping or copying — `wgpu.TextureFormat` and `gputypes.TextureFormat` are the same type at the Go type system level. + +**Important**: gputypes values are NOT the same numbers as wgpu-native C values for the enums listed in the conversion table above. `convert.go` is the bridge between these two numbering systems. Never pass a gputypes enum value directly to an FFI function without checking whether a converter exists for that type. + +--- + +## Async Callbacks + +wgpu-native uses callbacks for `RequestAdapter`, `RequestDevice`, `MapAsync`, and error scopes. The Go implementation converts these to synchronous calls using channels: + +1. A `*Request` state struct with a `done chan struct{}` is allocated and registered in a global map +2. The request ID is passed as `Userdata1` to the C callback +3. The callback (registered via `ffi.NewCallback`) looks up the request by ID, writes results, and closes `done` +4. The calling goroutine blocks on `<-req.done` in a select loop that also calls `ProcessEvents()` + +Callback function pointers are created once via `sync.Once` and reused across calls. The global registry is protected by a `sync.Mutex`. + +**Thread safety**: each callback modifies only its own request struct, and deletes the map entry atomically under the lock before writing to the struct, so there is no race between the callback and the waiting goroutine. + +--- + +## Testing Strategy + +Tests are organized into three tiers by GPU requirement: + +### ABI tests (no GPU, always run in CI) + +`abi_test.go` — 271 assertions verifying: +- `unsafe.Sizeof(wireStruct)` matches `C sizeof(WGPUStruct)` +- `unsafe.Offsetof(wireStruct.Field)` matches C field offsets +- Enum constant values match webgpu.h integers +- gputypes type alignment with wgpu-native values for pass-through enums + +These tests have zero external dependencies and catch ABI regressions immediately. + +### Safe tests (no GPU, CI-safe) + +Tests that exercise logic without making FFI calls to wgpu-native: +- `TestMat4*`, `TestVec3*` — math helpers +- `TestStructSizes*` — additional struct size checks +- `TestWGPUError*` — error type assertions +- `TestNullGuard*` — nil/released handle defensive checks +- `Fuzz*` — FFI boundary fuzz targets (seed corpus only in CI) + +Filter: `-run "Mat4|Vec3|StructSizes|CheckInit|WGPUError|Fuzz|NullGuard"` in `.github/workflows/`. + +### GPU tests (local only, skipped in CI) + +Tests that require a real GPU and wgpu-native loaded: +- `TestAdapter*`, `TestDevice*`, `TestBuffer*`, `TestSurface*` +- `TestLeak*`, `TestErrorScope*` + +These require `WGPU_NATIVE_PATH` pointing to a valid wgpu-native binary. GitHub Actions runners have no GPU, so these tests are excluded from CI filter patterns. + +--- + +## Buffer Mapping Architecture + +Buffer mapping in WebGPU is inherently async: the GPU must finish any in-flight work on the buffer before the CPU can access it. go-webgpu provides three access patterns: + +### Map (blocking, context-aware) + +`Buffer.Map(ctx, mode, offset, size) error` — the recommended path for most applications. + +1. Calls `mapAsyncStart` which issues `wgpuBufferMapAsync` with a Go callback registered in `mapRequests` (global map, protected by `sync.Mutex`) +2. The callback (`mapCallbackHandler`) is a C-callable function pointer created once via `ffi.NewCallback` +3. After submitting the request, `Map` kicks an initial `Device.Poll(false)` (for synchronous-complete backends) +4. If not immediately complete, a background goroutine drives `Device.Poll` continuously so the mapping resolves without the caller needing to pump events +5. Blocks on `<-req.done` or `ctx.Done()`, whichever fires first + +### MapAsync (non-blocking) + +`Buffer.MapAsync(mode, offset, size) (*MapPending, error)` — for callers that want to do other work while waiting. + +1. Same `mapAsyncStart` path as Map +2. Returns a `*MapPending` immediately without blocking +3. `MapPending.Status()` performs a non-blocking `select` on `req.done` +4. `MapPending.Wait(ctx)` blocks with context support +5. Caller must drive `Device.Poll` externally until `Status()` returns ready + +### MappedRange (type-safe access) + +After `Map` or `MapAsync` resolves, `Buffer.MappedRange(offset, size) (*MappedRange, error)` wraps `Buffer.GetMappedRange` and validates the buffer state (must be `BufferMapStateMapped`). The returned `MappedRange.Bytes()` returns a `[]byte` backed by the GPU-mapped memory, valid until `Buffer.Unmap()`. + +``` +caller + │ Map(ctx, ...) + ▼ +mapAsyncStart ──► wgpuBufferMapAsync (C FFI) + │ │ + │ C callback ──► mapCallbackHandler (Go) + │ │ closes req.done + ▼ +select req.done / ctx.Done + │ done + ▼ +MappedRange ──► Bytes() ──► []byte view into GPU memory + │ + ▼ +Unmap ──► wgpuBufferUnmap (C FFI) +``` + +--- + +## Limits Caching + +`Adapter.Limits()` and `Device.Limits()` return a cached `Limits` value with no error. Limits are fetched once via `wgpuAdapterGetLimits` / `wgpuDeviceGetLimits` at `RequestAdapter` / `RequestDevice` time and stored inside the `Adapter` / `Device` struct. + +This design has two benefits: +1. **No error handling at call site** — limits are always available once you have a valid Adapter or Device +2. **No FFI overhead on repeated access** — common in render loops that check `MaxUniformBufferBindingSize` or similar + +The cached value is read-only and thread-safe (written once before the struct is returned to the caller). + +--- + +## Wire Struct Pattern Summary + +Every public descriptor type has an unexported `*Wire` counterpart used at the FFI boundary. The pattern is always: + +1. **Public struct** — Go-idiomatic types (`string`, `bool`, `*T`, `[]T`) +2. **Conversion** — inside the method body, construct the wire struct from the public struct +3. **Wire struct** — C-layout types (`uintptr`, `uint32`, `uint64`, `StringView`, `Bool`) +4. **FFI call** — pass `unsafe.Pointer` to the wire struct + +The wire struct is a local variable on the stack; its address is safe to pass to wgpu-native only for the duration of the FFI call (wgpu-native copies all descriptor data synchronously). + +```go +// Public descriptor — what the user writes +type BufferDescriptor struct { + Label string + Usage gputypes.BufferUsage + Size uint64 + MappedAtCreation bool +} + +// Wire struct — matches WGPUBufferDescriptor byte-for-byte +type bufferDescriptorWire struct { + NextInChain uintptr + Label StringView // {Data uintptr, Length uintptr} + Usage gputypes.BufferUsage // uint64 on wgpu-native + Size uint64 + MappedAtCreation Bool // uint32 (WGPUBool) + _pad [4]byte +} +``` + +The 271 ABI tests in `abi_test.go` verify that every wire struct matches the C header at compile time. + +--- + +## Ecosystem Context + +``` +born-ml/born (ML framework) + │ uses gogpu for GPU compute + ▼ +gogpu/gogpu (graphics framework) + │ backend selection + ┌┴──────────────────────────┐ + ▼ ▼ +go-webgpu/webgpu gogpu/wgpu +(FFI → wgpu-native) (Pure Go WebGPU) + │ + ▼ +go-webgpu/goffi (Pure Go FFI) + +Shared: gogpu/gputypes ← WebGPU type definitions used by all +``` + +| Project | Approach | Runtime requirement | +|---------|----------|---------------------| +| go-webgpu/webgpu | FFI to wgpu-native (Rust) | `wgpu_native.dll` / `.so` / `.dylib` | +| gogpu/wgpu | Pure Go WebGPU implementation | None | + +The two implementations share `gputypes` as their type contract. This is what makes gogpu backend switching possible: the same `gputypes.TextureFormat` constant works with both backends, and only the FFI translation (our `convert.go`) changes. diff --git a/examples/adapter_info/main.go b/examples/adapter_info/main.go index 289427a..13ddb69 100644 --- a/examples/adapter_info/main.go +++ b/examples/adapter_info/main.go @@ -6,7 +6,6 @@ import ( "log" "github.com/go-webgpu/webgpu/wgpu" - "github.com/gogpu/gputypes" ) func main() { @@ -19,7 +18,7 @@ func main() { // Request adapter (GPU) adapter, err := instance.RequestAdapter(&wgpu.RequestAdapterOptions{ - PowerPreference: gputypes.PowerPreferenceHighPerformance, + PowerPreference: wgpu.PowerPreferenceHighPerformance, }) if err != nil { log.Fatalf("Failed to request adapter: %v", err) @@ -27,7 +26,7 @@ func main() { defer adapter.Release() // Get adapter information - info, err := adapter.GetInfo() + info, err := adapter.Info() if err != nil { log.Fatalf("Failed to get adapter info: %v", err) } @@ -42,25 +41,22 @@ func main() { fmt.Printf("Vendor ID: 0x%04X\n", info.VendorID) fmt.Printf("Device ID: 0x%04X\n", info.DeviceID) - // Get adapter limits - limits, err := adapter.GetLimits() - if err != nil { - log.Fatalf("Failed to get adapter limits: %v", err) - } + // Get adapter limits (cached at creation, no FFI call) + limits := adapter.Limits() fmt.Println("\n=== Key Adapter Limits ===") - fmt.Printf("Max Texture 2D: %d x %d\n", limits.Limits.MaxTextureDimension2D, limits.Limits.MaxTextureDimension2D) - fmt.Printf("Max Texture 3D: %d x %d x %d\n", limits.Limits.MaxTextureDimension3D, limits.Limits.MaxTextureDimension3D, limits.Limits.MaxTextureDimension3D) - fmt.Printf("Max Bind Groups: %d\n", limits.Limits.MaxBindGroups) - fmt.Printf("Max Buffer Size: %d bytes (%.2f GB)\n", limits.Limits.MaxBufferSize, float64(limits.Limits.MaxBufferSize)/(1024*1024*1024)) - fmt.Printf("Max Uniform Buffer Size: %d bytes (%.2f MB)\n", limits.Limits.MaxUniformBufferBindingSize, float64(limits.Limits.MaxUniformBufferBindingSize)/(1024*1024)) - fmt.Printf("Max Storage Buffer Size: %d bytes (%.2f GB)\n", limits.Limits.MaxStorageBufferBindingSize, float64(limits.Limits.MaxStorageBufferBindingSize)/(1024*1024*1024)) - fmt.Printf("Max Compute Workgroup Size: %d x %d x %d\n", limits.Limits.MaxComputeWorkgroupSizeX, limits.Limits.MaxComputeWorkgroupSizeY, limits.Limits.MaxComputeWorkgroupSizeZ) - fmt.Printf("Max Vertex Buffers: %d\n", limits.Limits.MaxVertexBuffers) - fmt.Printf("Max Color Attachments: %d\n", limits.Limits.MaxColorAttachments) + fmt.Printf("Max Texture 2D: %d x %d\n", limits.MaxTextureDimension2D, limits.MaxTextureDimension2D) + fmt.Printf("Max Texture 3D: %d x %d x %d\n", limits.MaxTextureDimension3D, limits.MaxTextureDimension3D, limits.MaxTextureDimension3D) + fmt.Printf("Max Bind Groups: %d\n", limits.MaxBindGroups) + fmt.Printf("Max Buffer Size: %d bytes (%.2f GB)\n", limits.MaxBufferSize, float64(limits.MaxBufferSize)/(1024*1024*1024)) + fmt.Printf("Max Uniform Buffer Size: %d bytes (%.2f MB)\n", limits.MaxUniformBufferBindingSize, float64(limits.MaxUniformBufferBindingSize)/(1024*1024)) + fmt.Printf("Max Storage Buffer Size: %d bytes (%.2f GB)\n", limits.MaxStorageBufferBindingSize, float64(limits.MaxStorageBufferBindingSize)/(1024*1024*1024)) + fmt.Printf("Max Compute Workgroup Size: %d x %d x %d\n", limits.MaxComputeWorkgroupSizeX, limits.MaxComputeWorkgroupSizeY, limits.MaxComputeWorkgroupSizeZ) + fmt.Printf("Max Vertex Buffers: %d\n", limits.MaxVertexBuffers) + fmt.Printf("Max Color Attachments: %d\n", limits.MaxColorAttachments) // Enumerate features - features := adapter.EnumerateFeatures() + features := adapter.Features() fmt.Println("\n=== Supported Features ===") if len(features) == 0 { fmt.Println("No optional features supported") diff --git a/examples/buffer_introspection/main.go b/examples/buffer_introspection/main.go index 90daa78..8e580c9 100644 --- a/examples/buffer_introspection/main.go +++ b/examples/buffer_introspection/main.go @@ -6,7 +6,6 @@ import ( "log" "github.com/go-webgpu/webgpu/wgpu" - "github.com/gogpu/gputypes" ) func main() { @@ -19,7 +18,7 @@ func main() { // Request adapter adapter, err := instance.RequestAdapter(&wgpu.RequestAdapterOptions{ - PowerPreference: gputypes.PowerPreferenceHighPerformance, + PowerPreference: wgpu.PowerPreferenceHighPerformance, }) if err != nil { log.Fatalf("Failed to request adapter: %v", err) @@ -35,13 +34,13 @@ func main() { // Create a storage buffer bufferSize := uint64(1024 * 1024) // 1MB - buffer := device.CreateBuffer(&wgpu.BufferDescriptor{ - Label: wgpu.EmptyStringView(), - Usage: gputypes.BufferUsageStorage | gputypes.BufferUsageCopySrc | gputypes.BufferUsageCopyDst, + buffer, err := device.CreateBuffer(&wgpu.BufferDescriptor{ + Label: "", + Usage: wgpu.BufferUsageStorage | wgpu.BufferUsageCopySrc | wgpu.BufferUsageCopyDst, Size: bufferSize, }) - if buffer == nil { - log.Fatal("Failed to create buffer") + if err != nil { + log.Fatalf("create buffer: %v", err) } defer buffer.Release() @@ -49,53 +48,62 @@ func main() { fmt.Println("=== Buffer Introspection ===") // Get buffer size - size := buffer.GetSize() + size := buffer.Size() fmt.Printf("Buffer size: %d bytes (%.2f MB)\n", size, float64(size)/(1024*1024)) // Get buffer usage - usage := buffer.GetUsage() + usage := buffer.Usage() fmt.Printf("Buffer usage: %s\n", usageToString(usage)) // Get buffer map state - mapState := buffer.GetMapState() + mapState := buffer.MapState() fmt.Printf("Buffer map state: %s\n", mapStateToString(mapState)) // Create a mappable buffer fmt.Println("\n=== Mappable Buffer Example ===") - mappableBuffer := device.CreateBuffer(&wgpu.BufferDescriptor{ - Label: wgpu.EmptyStringView(), - Usage: gputypes.BufferUsageMapRead | gputypes.BufferUsageCopyDst, + mappableBuffer, err := device.CreateBuffer(&wgpu.BufferDescriptor{ + Label: "", + Usage: wgpu.BufferUsageMapRead | wgpu.BufferUsageCopyDst, Size: 1024, - MappedAtCreation: wgpu.True, + MappedAtCreation: true, }) - if mappableBuffer == nil { - log.Fatal("Failed to create mappable buffer") + if err != nil { + log.Fatalf("create mappable buffer: %v", err) } defer mappableBuffer.Release() // Check state when mapped at creation - mapState = mappableBuffer.GetMapState() + mapState = mappableBuffer.MapState() fmt.Printf("Initial map state (MappedAtCreation): %s\n", mapStateToString(mapState)) // Unmap the buffer mappableBuffer.Unmap() // Check state after unmap - mapState = mappableBuffer.GetMapState() + mapState = mappableBuffer.MapState() fmt.Printf("Map state after Unmap(): %s\n", mapStateToString(mapState)) // Map async fmt.Println("\nMapping buffer asynchronously...") - err = mappableBuffer.MapAsync(device, wgpu.MapModeRead, 0, 1024) - if err != nil { - log.Printf("MapAsync failed: %v", err) + mapPending, mapErr := mappableBuffer.MapAsync(wgpu.MapModeRead, 0, 1024) + if mapErr != nil { + log.Printf("MapAsync failed: %v", mapErr) } else { - mapState = mappableBuffer.GetMapState() + // Drive polling until resolved. + for { + if ready, _ := mapPending.Status(); ready { + break + } + // In a real app, call device.Poll(false) here. + } + mapPending.Release() + + mapState = mappableBuffer.MapState() fmt.Printf("Map state after MapAsync(): %s\n", mapStateToString(mapState)) // Unmap again - mappableBuffer.Unmap() - mapState = mappableBuffer.GetMapState() + mappableBuffer.Unmap() //nolint:errcheck + mapState = mappableBuffer.MapState() fmt.Printf("Map state after final Unmap(): %s\n", mapStateToString(mapState)) } @@ -107,37 +115,37 @@ func main() { fmt.Println("- Debug buffer lifecycle issues") } -func usageToString(usage gputypes.BufferUsage) string { +func usageToString(usage wgpu.BufferUsage) string { var flags []string - if usage&gputypes.BufferUsageMapRead != 0 { + if usage&wgpu.BufferUsageMapRead != 0 { flags = append(flags, "MapRead") } - if usage&gputypes.BufferUsageMapWrite != 0 { + if usage&wgpu.BufferUsageMapWrite != 0 { flags = append(flags, "MapWrite") } - if usage&gputypes.BufferUsageCopySrc != 0 { + if usage&wgpu.BufferUsageCopySrc != 0 { flags = append(flags, "CopySrc") } - if usage&gputypes.BufferUsageCopyDst != 0 { + if usage&wgpu.BufferUsageCopyDst != 0 { flags = append(flags, "CopyDst") } - if usage&gputypes.BufferUsageIndex != 0 { + if usage&wgpu.BufferUsageIndex != 0 { flags = append(flags, "Index") } - if usage&gputypes.BufferUsageVertex != 0 { + if usage&wgpu.BufferUsageVertex != 0 { flags = append(flags, "Vertex") } - if usage&gputypes.BufferUsageUniform != 0 { + if usage&wgpu.BufferUsageUniform != 0 { flags = append(flags, "Uniform") } - if usage&gputypes.BufferUsageStorage != 0 { + if usage&wgpu.BufferUsageStorage != 0 { flags = append(flags, "Storage") } - if usage&gputypes.BufferUsageIndirect != 0 { + if usage&wgpu.BufferUsageIndirect != 0 { flags = append(flags, "Indirect") } - if usage&gputypes.BufferUsageQueryResolve != 0 { + if usage&wgpu.BufferUsageQueryResolve != 0 { flags = append(flags, "QueryResolve") } diff --git a/examples/colored-triangle/main.go b/examples/colored-triangle/main.go index 2b80e07..ff02719 100644 --- a/examples/colored-triangle/main.go +++ b/examples/colored-triangle/main.go @@ -10,7 +10,6 @@ import ( "unsafe" "github.com/go-webgpu/webgpu/wgpu" - "github.com/gogpu/gputypes" "golang.org/x/sys/windows" ) @@ -280,7 +279,7 @@ func (app *App) initWebGPU() error { app.device = device // Get queue - app.queue = device.GetQueue() + app.queue = device.Queue() // Create surface surface, err := inst.CreateSurfaceFromWindowsHWND(uintptr(app.hinstance), uintptr(app.hwnd)) @@ -294,14 +293,13 @@ func (app *App) initWebGPU() error { // configureSurface configures the surface for rendering. func (app *App) configureSurface() error { - app.surface.Configure(&wgpu.SurfaceConfiguration{ - Device: app.device, - Format: gputypes.TextureFormatBGRA8Unorm, - Usage: gputypes.TextureUsageRenderAttachment, + _ = app.surface.Configure(app.device, &wgpu.SurfaceConfiguration{ + Format: wgpu.TextureFormatBGRA8Unorm, + Usage: wgpu.TextureUsageRenderAttachment, Width: app.width, Height: app.height, - AlphaMode: gputypes.CompositeAlphaModeOpaque, - PresentMode: gputypes.PresentModeFifo, + AlphaMode: wgpu.CompositeAlphaModeOpaque, + PresentMode: wgpu.PresentModeFifo, }) app.needsRecreate = false return nil @@ -321,11 +319,11 @@ func (app *App) createVertexBuffer() error { vertexBufferSize := uint64(len(vertices) * 4) // 4 bytes per float32 // Create buffer with MappedAtCreation = true for easy data upload - app.vertexBuffer = app.device.CreateBuffer(&wgpu.BufferDescriptor{ - Label: wgpu.StringView{}, - Usage: gputypes.BufferUsageVertex | gputypes.BufferUsageCopyDst, + app.vertexBuffer, _ = app.device.CreateBuffer(&wgpu.BufferDescriptor{ + Label: "", + Usage: wgpu.BufferUsageVertex | wgpu.BufferUsageCopyDst, Size: vertexBufferSize, - MappedAtCreation: wgpu.True, + MappedAtCreation: true, }) if app.vertexBuffer == nil { @@ -352,7 +350,7 @@ func (app *App) createVertexBuffer() error { // createPipeline creates the render pipeline with vertex buffer layout. func (app *App) createPipeline() error { // Create shader module - shader := app.device.CreateShaderModuleWGSL(shaderSource) + shader, _ := app.device.CreateShaderModuleWGSL(shaderSource) if shader == nil { return fmt.Errorf("failed to create shader module") } @@ -361,19 +359,19 @@ func (app *App) createPipeline() error { // Define vertex attributes attributes := []wgpu.VertexAttribute{ { - Format: gputypes.VertexFormatFloat32x2, // position: vec2f + Format: wgpu.VertexFormatFloat32x2, // position: vec2f Offset: 0, ShaderLocation: 0, }, { - Format: gputypes.VertexFormatFloat32x3, // color: vec3f - Offset: 8, // 2 floats * 4 bytes = 8 bytes offset + Format: wgpu.VertexFormatFloat32x3, // color: vec3f + Offset: 8, // 2 floats * 4 bytes = 8 bytes offset ShaderLocation: 1, }, } // Create render pipeline with vertex buffer layout - pipeline := app.device.CreateRenderPipeline(&wgpu.RenderPipelineDescriptor{ + pipeline, _ := app.device.CreateRenderPipeline(&wgpu.RenderPipelineDescriptor{ Label: "", Layout: nil, // auto layout Vertex: wgpu.VertexState{ @@ -381,15 +379,15 @@ func (app *App) createPipeline() error { EntryPoint: "vs_main", Buffers: []wgpu.VertexBufferLayout{{ ArrayStride: 20, // 5 floats * 4 bytes = 20 bytes per vertex - StepMode: gputypes.VertexStepModeVertex, + StepMode: wgpu.VertexStepModeVertex, AttributeCount: 2, Attributes: &attributes[0], }}, }, Primitive: wgpu.PrimitiveState{ - Topology: gputypes.PrimitiveTopologyTriangleList, - FrontFace: gputypes.FrontFaceCCW, - CullMode: gputypes.CullModeNone, + Topology: wgpu.PrimitiveTopologyTriangleList, + FrontFace: wgpu.FrontFaceCCW, + CullMode: wgpu.CullModeNone, }, Multisample: wgpu.MultisampleState{ Count: 1, @@ -399,8 +397,8 @@ func (app *App) createPipeline() error { Module: shader, EntryPoint: "fs_main", Targets: []wgpu.ColorTargetState{{ - Format: gputypes.TextureFormatBGRA8Unorm, - WriteMask: gputypes.ColorWriteMaskAll, + Format: wgpu.TextureFormatBGRA8Unorm, + WriteMask: wgpu.ColorWriteMaskAll, }}, }, }) @@ -427,7 +425,7 @@ func (app *App) releasePreviousFrame() { // acquireSurfaceTexture gets the current surface texture. func (app *App) acquireSurfaceTexture() error { - surfaceTex, err := app.surface.GetCurrentTexture() + surfaceTex, _, err := app.surface.GetCurrentTexture() if err != nil { // Handle common surface errors if err == wgpu.ErrSurfaceLost || err == wgpu.ErrSurfaceNeedsReconfigure { @@ -439,7 +437,7 @@ func (app *App) acquireSurfaceTexture() error { app.surfaceTex = surfaceTex // Create texture view - view := surfaceTex.Texture.CreateView(nil) + view, _ := surfaceTex.Texture.CreateView(nil) if view == nil { return fmt.Errorf("failed to create texture view") } @@ -449,12 +447,12 @@ func (app *App) acquireSurfaceTexture() error { // renderTriangle encodes the colored triangle rendering commands with vertex buffer. func (app *App) renderTriangle(encoder *wgpu.CommandEncoder, view *wgpu.TextureView) error { - pass := encoder.BeginRenderPass(&wgpu.RenderPassDescriptor{ + pass, _ := encoder.BeginRenderPass(&wgpu.RenderPassDescriptor{ Label: "Colored Triangle Render Pass", ColorAttachments: []wgpu.RenderPassColorAttachment{{ View: view, - LoadOp: gputypes.LoadOpClear, - StoreOp: gputypes.StoreOpStore, + LoadOp: wgpu.LoadOpClear, + StoreOp: wgpu.StoreOpStore, ClearValue: wgpu.Color{ R: 0.1, G: 0.2, @@ -494,7 +492,7 @@ func (app *App) render() error { } // Create command encoder - encoder := app.device.CreateCommandEncoder(nil) + encoder, _ := app.device.CreateCommandEncoder(nil) if encoder == nil { return fmt.Errorf("failed to create command encoder") } @@ -506,15 +504,17 @@ func (app *App) render() error { } // Finish encoding - cmdBuffer := encoder.Finish(nil) + cmdBuffer, _ := encoder.Finish() if cmdBuffer == nil { return fmt.Errorf("failed to finish command encoder") } defer cmdBuffer.Release() // Submit commands and present - app.queue.Submit(cmdBuffer) - app.surface.Present() + if _, err := app.queue.Submit(cmdBuffer); err != nil { + return fmt.Errorf("submit: %w", err) + } + _ = app.surface.Present() return nil } diff --git a/examples/compute/main.go b/examples/compute/main.go index 2a58c34..3463bbf 100644 --- a/examples/compute/main.go +++ b/examples/compute/main.go @@ -9,7 +9,6 @@ import ( "unsafe" "github.com/go-webgpu/webgpu/wgpu" - "github.com/gogpu/gputypes" ) // Compute shader that doubles each element in the array @@ -49,20 +48,20 @@ func main() { } defer device.Release() - queue := device.GetQueue() + queue := device.Queue() defer queue.Release() // Create shader module - shader := device.CreateShaderModuleWGSL(computeShader) - if shader == nil { - log.Fatal("failed to create compute shader") + shader, err := device.CreateShaderModuleWGSL(computeShader) + if err != nil { + log.Fatalf("create compute shader: %v", err) } defer shader.Release() // Create compute pipeline with auto layout - pipeline := device.CreateComputePipelineSimple(nil, shader, "main") - if pipeline == nil { - log.Fatal("failed to create compute pipeline") + pipeline, err := device.CreateComputePipelineSimple(nil, shader, "main") + if err != nil { + log.Fatalf("create compute pipeline: %v", err) } defer pipeline.Release() @@ -79,13 +78,13 @@ func main() { // Create storage buffer bufferSize := uint64(numElements * 4) // 4 bytes per float32 - storageBuffer := device.CreateBuffer(&wgpu.BufferDescriptor{ - Usage: gputypes.BufferUsageStorage | gputypes.BufferUsageCopySrc | gputypes.BufferUsageCopyDst, + storageBuffer, err := device.CreateBuffer(&wgpu.BufferDescriptor{ + Usage: wgpu.BufferUsageStorage | wgpu.BufferUsageCopySrc | wgpu.BufferUsageCopyDst, Size: bufferSize, - MappedAtCreation: wgpu.True, + MappedAtCreation: true, }) - if storageBuffer == nil { - log.Fatal("failed to create storage buffer") + if err != nil { + log.Fatalf("create storage buffer: %v", err) } defer storageBuffer.Release() @@ -98,13 +97,13 @@ func main() { storageBuffer.Unmap() // Create readback buffer for results - readbackBuffer := device.CreateBuffer(&wgpu.BufferDescriptor{ - Usage: gputypes.BufferUsageMapRead | gputypes.BufferUsageCopyDst, + readbackBuffer, err := device.CreateBuffer(&wgpu.BufferDescriptor{ + Usage: wgpu.BufferUsageMapRead | wgpu.BufferUsageCopyDst, Size: bufferSize, - MappedAtCreation: wgpu.False, + MappedAtCreation: false, }) - if readbackBuffer == nil { - log.Fatal("failed to create readback buffer") + if err != nil { + log.Fatalf("create readback buffer: %v", err) } defer readbackBuffer.Release() @@ -116,22 +115,25 @@ func main() { defer bindGroupLayout.Release() // Create bind group - bindGroup := device.CreateBindGroupSimple(bindGroupLayout, []wgpu.BindGroupEntry{ + bindGroup, err := device.CreateBindGroupSimple(bindGroupLayout, []wgpu.BindGroupEntry{ wgpu.BufferBindingEntry(0, storageBuffer, 0, bufferSize), }) - if bindGroup == nil { - log.Fatal("failed to create bind group") + if err != nil { + log.Fatalf("create bind group: %v", err) } defer bindGroup.Release() // Create command encoder - encoder := device.CreateCommandEncoder(nil) - if encoder == nil { - log.Fatal("failed to create command encoder") + encoder, err := device.CreateCommandEncoder(nil) + if err != nil { + log.Fatalf("create command encoder: %v", err) } // Begin compute pass - computePass := encoder.BeginComputePass(nil) + computePass, err := encoder.BeginComputePass(nil) + if err != nil { + log.Fatalf("begin compute pass: %v", err) + } computePass.SetPipeline(pipeline) computePass.SetBindGroup(0, bindGroup, nil) @@ -146,16 +148,32 @@ func main() { encoder.CopyBufferToBuffer(storageBuffer, 0, readbackBuffer, 0, bufferSize) // Submit commands - cmdBuffer := encoder.Finish(nil) + cmdBuffer, err := encoder.Finish() + if err != nil { + log.Fatalf("finish encoder: %v", err) + } encoder.Release() - queue.Submit(cmdBuffer) + if _, err = queue.Submit(cmdBuffer); err != nil { + log.Fatalf("queue submit: %v", err) + } cmdBuffer.Release() // Map readback buffer and read results - err = readbackBuffer.MapAsync(device, wgpu.MapModeRead, 0, bufferSize) + mapPending, err := readbackBuffer.MapAsync(wgpu.MapModeRead, 0, bufferSize) if err != nil { log.Fatalf("MapAsync failed: %v", err) } + // Drive polling until the map resolves. + for { + if ready, werr := mapPending.Status(); ready { + if werr != nil { + log.Fatalf("MapAsync resolved with error: %v", werr) + } + break + } + device.Poll(false) + } + mapPending.Release() resultPtr := readbackBuffer.GetMappedRange(0, bufferSize) if resultPtr != nil { diff --git a/examples/cube/main.go b/examples/cube/main.go index 2526027..4147592 100644 --- a/examples/cube/main.go +++ b/examples/cube/main.go @@ -13,7 +13,6 @@ import ( "unsafe" "github.com/go-webgpu/webgpu/wgpu" - "github.com/gogpu/gputypes" "golang.org/x/sys/windows" ) @@ -311,7 +310,7 @@ func (app *App) initWebGPU() error { app.device = device // Get queue - app.queue = device.GetQueue() + app.queue = device.Queue() // Create surface surface, err := inst.CreateSurfaceFromWindowsHWND(uintptr(app.hinstance), uintptr(app.hwnd)) @@ -325,14 +324,13 @@ func (app *App) initWebGPU() error { // configureSurface configures the surface for rendering. func (app *App) configureSurface() error { - app.surface.Configure(&wgpu.SurfaceConfiguration{ - Device: app.device, - Format: gputypes.TextureFormatBGRA8Unorm, - Usage: gputypes.TextureUsageRenderAttachment, + _ = app.surface.Configure(app.device, &wgpu.SurfaceConfiguration{ + Format: wgpu.TextureFormatBGRA8Unorm, + Usage: wgpu.TextureUsageRenderAttachment, Width: app.width, Height: app.height, - AlphaMode: gputypes.CompositeAlphaModeOpaque, - PresentMode: gputypes.PresentModeFifo, + AlphaMode: wgpu.CompositeAlphaModeOpaque, + PresentMode: wgpu.PresentModeFifo, }) app.needsRecreate = false return nil @@ -340,12 +338,12 @@ func (app *App) configureSurface() error { // createDepthTexture creates the depth texture and view. func (app *App) createDepthTexture() error { - app.depthTexture = app.device.CreateDepthTexture(app.width, app.height, gputypes.TextureFormatDepth24Plus) + app.depthTexture = app.device.CreateDepthTexture(app.width, app.height, wgpu.TextureFormatDepth24Plus) if app.depthTexture == nil { return fmt.Errorf("failed to create depth texture") } - app.depthTextureView = app.depthTexture.CreateView(nil) + app.depthTextureView, _ = app.depthTexture.CreateView(nil) if app.depthTextureView == nil { return fmt.Errorf("failed to create depth texture view") } @@ -427,11 +425,11 @@ func (app *App) createVertexBuffer() error { vertexBufferSize := uint64(len(vertices) * 4) // 4 bytes per float32 // Create buffer with MappedAtCreation = true for easy data upload - app.vertexBuffer = app.device.CreateBuffer(&wgpu.BufferDescriptor{ - Label: wgpu.StringView{}, - Usage: gputypes.BufferUsageVertex | gputypes.BufferUsageCopyDst, + app.vertexBuffer, _ = app.device.CreateBuffer(&wgpu.BufferDescriptor{ + Label: "", + Usage: wgpu.BufferUsageVertex | wgpu.BufferUsageCopyDst, Size: vertexBufferSize, - MappedAtCreation: wgpu.True, + MappedAtCreation: true, }) if app.vertexBuffer == nil { @@ -461,11 +459,11 @@ func (app *App) createUniformBuffer() error { const uniformBufferSize = 64 // Create buffer with Uniform usage and CopyDst for updates - app.uniformBuffer = app.device.CreateBuffer(&wgpu.BufferDescriptor{ - Label: wgpu.StringView{}, - Usage: gputypes.BufferUsageUniform | gputypes.BufferUsageCopyDst, + app.uniformBuffer, _ = app.device.CreateBuffer(&wgpu.BufferDescriptor{ + Label: "", + Usage: wgpu.BufferUsageUniform | wgpu.BufferUsageCopyDst, Size: uniformBufferSize, - MappedAtCreation: wgpu.False, + MappedAtCreation: false, }) if app.uniformBuffer == nil { @@ -480,16 +478,15 @@ func (app *App) createBindGroupLayout() error { entries := []wgpu.BindGroupLayoutEntry{ { Binding: 0, - Visibility: gputypes.ShaderStageVertex, - Buffer: wgpu.BufferBindingLayout{ - Type: gputypes.BufferBindingTypeUniform, - HasDynamicOffset: wgpu.False, - MinBindingSize: 64, // mat4x4f = 64 bytes + Visibility: wgpu.ShaderStageVertex, + Buffer: &wgpu.BufferBindingLayout{ + Type: wgpu.BufferBindingTypeUniform, + MinBindingSize: 64, // mat4x4f = 64 bytes }, }, } - app.bindGroupLayout = app.device.CreateBindGroupLayoutSimple(entries) + app.bindGroupLayout, _ = app.device.CreateBindGroupLayoutSimple(entries) if app.bindGroupLayout == nil { return fmt.Errorf("failed to create bind group layout") } @@ -503,7 +500,7 @@ func (app *App) createBindGroup() error { wgpu.BufferBindingEntry(0, app.uniformBuffer, 0, 64), } - app.bindGroup = app.device.CreateBindGroupSimple(app.bindGroupLayout, entries) + app.bindGroup, _ = app.device.CreateBindGroupSimple(app.bindGroupLayout, entries) if app.bindGroup == nil { return fmt.Errorf("failed to create bind group") } @@ -513,7 +510,7 @@ func (app *App) createBindGroup() error { // createPipelineLayout creates the pipeline layout with bind group layout. func (app *App) createPipelineLayout() (*wgpu.PipelineLayout, error) { - pipelineLayout := app.device.CreatePipelineLayoutSimple([]*wgpu.BindGroupLayout{app.bindGroupLayout}) + pipelineLayout, _ := app.device.CreatePipelineLayoutSimple([]*wgpu.BindGroupLayout{app.bindGroupLayout}) if pipelineLayout == nil { return nil, fmt.Errorf("failed to create pipeline layout") } @@ -524,7 +521,7 @@ func (app *App) createPipelineLayout() (*wgpu.PipelineLayout, error) { // nolint:funlen // Pipeline creation requires many configuration steps func (app *App) createPipeline() error { // Create shader module - shader := app.device.CreateShaderModuleWGSL(shaderSource) + shader, _ := app.device.CreateShaderModuleWGSL(shaderSource) if shader == nil { return fmt.Errorf("failed to create shader module") } @@ -540,19 +537,19 @@ func (app *App) createPipeline() error { // Define vertex attributes attributes := []wgpu.VertexAttribute{ { - Format: gputypes.VertexFormatFloat32x3, // position: vec3f + Format: wgpu.VertexFormatFloat32x3, // position: vec3f Offset: 0, ShaderLocation: 0, }, { - Format: gputypes.VertexFormatFloat32x3, // color: vec3f - Offset: 12, // 3 floats * 4 bytes = 12 bytes offset + Format: wgpu.VertexFormatFloat32x3, // color: vec3f + Offset: 12, // 3 floats * 4 bytes = 12 bytes offset ShaderLocation: 1, }, } // Create render pipeline with vertex buffer layout and depth testing - pipeline := app.device.CreateRenderPipeline(&wgpu.RenderPipelineDescriptor{ + pipeline, _ := app.device.CreateRenderPipeline(&wgpu.RenderPipelineDescriptor{ Label: "", Layout: pipelineLayout, Vertex: wgpu.VertexState{ @@ -560,31 +557,31 @@ func (app *App) createPipeline() error { EntryPoint: "vs_main", Buffers: []wgpu.VertexBufferLayout{{ ArrayStride: 24, // 6 floats * 4 bytes = 24 bytes per vertex - StepMode: gputypes.VertexStepModeVertex, + StepMode: wgpu.VertexStepModeVertex, AttributeCount: 2, Attributes: &attributes[0], }}, }, Primitive: wgpu.PrimitiveState{ - Topology: gputypes.PrimitiveTopologyTriangleList, - FrontFace: gputypes.FrontFaceCCW, - CullMode: gputypes.CullModeBack, // Enable back-face culling for cube + Topology: wgpu.PrimitiveTopologyTriangleList, + FrontFace: wgpu.FrontFaceCCW, + CullMode: wgpu.CullModeBack, // Enable back-face culling for cube }, DepthStencil: &wgpu.DepthStencilState{ - Format: gputypes.TextureFormatDepth24Plus, + Format: wgpu.TextureFormatDepth24Plus, DepthWriteEnabled: true, - DepthCompare: gputypes.CompareFunctionLess, + DepthCompare: wgpu.CompareFunctionLess, StencilFront: wgpu.StencilFaceState{ - Compare: gputypes.CompareFunctionAlways, - FailOp: gputypes.StencilOperationKeep, - DepthFailOp: gputypes.StencilOperationKeep, - PassOp: gputypes.StencilOperationKeep, + Compare: wgpu.CompareFunctionAlways, + FailOp: wgpu.StencilOperationKeep, + DepthFailOp: wgpu.StencilOperationKeep, + PassOp: wgpu.StencilOperationKeep, }, StencilBack: wgpu.StencilFaceState{ - Compare: gputypes.CompareFunctionAlways, - FailOp: gputypes.StencilOperationKeep, - DepthFailOp: gputypes.StencilOperationKeep, - PassOp: gputypes.StencilOperationKeep, + Compare: wgpu.CompareFunctionAlways, + FailOp: wgpu.StencilOperationKeep, + DepthFailOp: wgpu.StencilOperationKeep, + PassOp: wgpu.StencilOperationKeep, }, StencilReadMask: 0xFFFFFFFF, StencilWriteMask: 0xFFFFFFFF, @@ -600,8 +597,8 @@ func (app *App) createPipeline() error { Module: shader, EntryPoint: "fs_main", Targets: []wgpu.ColorTargetState{{ - Format: gputypes.TextureFormatBGRA8Unorm, - WriteMask: gputypes.ColorWriteMaskAll, + Format: wgpu.TextureFormatBGRA8Unorm, + WriteMask: wgpu.ColorWriteMaskAll, }}, }, }) @@ -665,7 +662,7 @@ func (app *App) releasePreviousFrame() { // acquireSurfaceTexture gets the current surface texture. func (app *App) acquireSurfaceTexture() error { - surfaceTex, err := app.surface.GetCurrentTexture() + surfaceTex, _, err := app.surface.GetCurrentTexture() if err != nil { // Handle common surface errors if err == wgpu.ErrSurfaceLost || err == wgpu.ErrSurfaceNeedsReconfigure { @@ -677,7 +674,7 @@ func (app *App) acquireSurfaceTexture() error { app.surfaceTex = surfaceTex // Create texture view - view := surfaceTex.Texture.CreateView(nil) + view, _ := surfaceTex.Texture.CreateView(nil) if view == nil { return fmt.Errorf("failed to create texture view") } @@ -687,12 +684,12 @@ func (app *App) acquireSurfaceTexture() error { // renderCube encodes the rotating cube rendering commands. func (app *App) renderCube(encoder *wgpu.CommandEncoder, view *wgpu.TextureView) error { - pass := encoder.BeginRenderPass(&wgpu.RenderPassDescriptor{ + pass, _ := encoder.BeginRenderPass(&wgpu.RenderPassDescriptor{ Label: "Cube Render Pass", ColorAttachments: []wgpu.RenderPassColorAttachment{{ View: view, - LoadOp: gputypes.LoadOpClear, - StoreOp: gputypes.StoreOpStore, + LoadOp: wgpu.LoadOpClear, + StoreOp: wgpu.StoreOpStore, ClearValue: wgpu.Color{ R: 0.1, G: 0.2, @@ -702,12 +699,12 @@ func (app *App) renderCube(encoder *wgpu.CommandEncoder, view *wgpu.TextureView) }}, DepthStencilAttachment: &wgpu.RenderPassDepthStencilAttachment{ View: app.depthTextureView, - DepthLoadOp: gputypes.LoadOpClear, - DepthStoreOp: gputypes.StoreOpStore, + DepthLoadOp: wgpu.LoadOpClear, + DepthStoreOp: wgpu.StoreOpStore, DepthClearValue: 1.0, DepthReadOnly: false, - StencilLoadOp: gputypes.LoadOpClear, - StencilStoreOp: gputypes.StoreOpStore, + StencilLoadOp: wgpu.LoadOpClear, + StencilStoreOp: wgpu.StoreOpStore, StencilClearValue: 0, StencilReadOnly: false, }, @@ -750,7 +747,7 @@ func (app *App) render() error { } // Create command encoder - encoder := app.device.CreateCommandEncoder(nil) + encoder, _ := app.device.CreateCommandEncoder(nil) if encoder == nil { return fmt.Errorf("failed to create command encoder") } @@ -762,15 +759,17 @@ func (app *App) render() error { } // Finish encoding - cmdBuffer := encoder.Finish(nil) + cmdBuffer, _ := encoder.Finish() if cmdBuffer == nil { return fmt.Errorf("failed to finish command encoder") } defer cmdBuffer.Release() // Submit commands and present - app.queue.Submit(cmdBuffer) - app.surface.Present() + if _, err := app.queue.Submit(cmdBuffer); err != nil { + return fmt.Errorf("submit: %w", err) + } + _ = app.surface.Present() return nil } diff --git a/examples/error_handling/main.go b/examples/error_handling/main.go index 6e78a9a..85203ad 100644 --- a/examples/error_handling/main.go +++ b/examples/error_handling/main.go @@ -37,7 +37,7 @@ func main() { device.PushErrorScope(wgpu.ErrorFilterValidation) // Perform some GPU operations (valid ones) - queue := device.GetQueue() + queue := device.Queue() if queue == nil { log.Fatal("Failed to get queue") } diff --git a/examples/indirect/main.go b/examples/indirect/main.go index 340f038..8d50891 100644 --- a/examples/indirect/main.go +++ b/examples/indirect/main.go @@ -10,7 +10,6 @@ import ( "unsafe" "github.com/go-webgpu/webgpu/wgpu" - "github.com/gogpu/gputypes" "golang.org/x/sys/windows" ) @@ -304,7 +303,7 @@ func (app *App) initWebGPU() error { app.device = device // Get queue - app.queue = device.GetQueue() + app.queue = device.Queue() // Create surface surface, err := inst.CreateSurfaceFromWindowsHWND(uintptr(app.hinstance), uintptr(app.hwnd)) @@ -318,14 +317,13 @@ func (app *App) initWebGPU() error { // configureSurface configures the surface for rendering. func (app *App) configureSurface() error { - app.surface.Configure(&wgpu.SurfaceConfiguration{ - Device: app.device, - Format: gputypes.TextureFormatBGRA8Unorm, - Usage: gputypes.TextureUsageRenderAttachment, + _ = app.surface.Configure(app.device, &wgpu.SurfaceConfiguration{ + Format: wgpu.TextureFormatBGRA8Unorm, + Usage: wgpu.TextureUsageRenderAttachment, Width: app.width, Height: app.height, - AlphaMode: gputypes.CompositeAlphaModeOpaque, - PresentMode: gputypes.PresentModeFifo, + AlphaMode: wgpu.CompositeAlphaModeOpaque, + PresentMode: wgpu.PresentModeFifo, }) app.needsRecreate = false return nil @@ -342,10 +340,10 @@ func (app *App) createBuffers() error { // Create vertex buffer app.vertexBufSize = uint64(len(vertices) * int(unsafe.Sizeof(Vertex{}))) - app.vertexBuffer = app.device.CreateBuffer(&wgpu.BufferDescriptor{ - Usage: gputypes.BufferUsageVertex | gputypes.BufferUsageCopyDst, + app.vertexBuffer, _ = app.device.CreateBuffer(&wgpu.BufferDescriptor{ + Usage: wgpu.BufferUsageVertex | wgpu.BufferUsageCopyDst, Size: app.vertexBufSize, - MappedAtCreation: wgpu.True, + MappedAtCreation: true, }) if app.vertexBuffer == nil { return fmt.Errorf("failed to create vertex buffer") @@ -376,10 +374,10 @@ func (app *App) createBuffers() error { fmt.Println() app.indirectBufSize = uint64(unsafe.Sizeof(indirectArgs)) - app.indirectBuffer = app.device.CreateBuffer(&wgpu.BufferDescriptor{ - Usage: gputypes.BufferUsageIndirect | gputypes.BufferUsageCopyDst, + app.indirectBuffer, _ = app.device.CreateBuffer(&wgpu.BufferDescriptor{ + Usage: wgpu.BufferUsageIndirect | wgpu.BufferUsageCopyDst, Size: app.indirectBufSize, - MappedAtCreation: wgpu.True, + MappedAtCreation: true, }) if app.indirectBuffer == nil { return fmt.Errorf("failed to create indirect buffer") @@ -398,7 +396,7 @@ func (app *App) createBuffers() error { // createPipeline creates the render pipeline. func (app *App) createPipeline() error { // Create shader module - shader := app.device.CreateShaderModuleWGSL(shaderSource) + shader, _ := app.device.CreateShaderModuleWGSL(shaderSource) if shader == nil { return fmt.Errorf("failed to create shader module") } @@ -407,12 +405,12 @@ func (app *App) createPipeline() error { // Vertex attributes vertexAttributes := []wgpu.VertexAttribute{ { - Format: gputypes.VertexFormatFloat32x2, + Format: wgpu.VertexFormatFloat32x2, Offset: 0, ShaderLocation: 0, // position }, { - Format: gputypes.VertexFormatFloat32x3, + Format: wgpu.VertexFormatFloat32x3, Offset: 8, // After position (2 * 4 bytes) ShaderLocation: 1, // color }, @@ -420,22 +418,22 @@ func (app *App) createPipeline() error { vertexBufferLayout := wgpu.VertexBufferLayout{ ArrayStride: uint64(unsafe.Sizeof(Vertex{})), - StepMode: gputypes.VertexStepModeVertex, + StepMode: wgpu.VertexStepModeVertex, AttributeCount: uintptr(len(vertexAttributes)), Attributes: &vertexAttributes[0], } // Create render pipeline - pipeline := app.device.CreateRenderPipeline(&wgpu.RenderPipelineDescriptor{ + pipeline, _ := app.device.CreateRenderPipeline(&wgpu.RenderPipelineDescriptor{ Vertex: wgpu.VertexState{ Module: shader, EntryPoint: "vs_main", Buffers: []wgpu.VertexBufferLayout{vertexBufferLayout}, }, Primitive: wgpu.PrimitiveState{ - Topology: gputypes.PrimitiveTopologyTriangleList, - FrontFace: gputypes.FrontFaceCCW, - CullMode: gputypes.CullModeNone, + Topology: wgpu.PrimitiveTopologyTriangleList, + FrontFace: wgpu.FrontFaceCCW, + CullMode: wgpu.CullModeNone, }, Multisample: wgpu.MultisampleState{ Count: 1, @@ -445,8 +443,8 @@ func (app *App) createPipeline() error { Module: shader, EntryPoint: "fs_main", Targets: []wgpu.ColorTargetState{{ - Format: gputypes.TextureFormatBGRA8Unorm, - WriteMask: gputypes.ColorWriteMaskAll, + Format: wgpu.TextureFormatBGRA8Unorm, + WriteMask: wgpu.ColorWriteMaskAll, }}, }, }) @@ -472,7 +470,7 @@ func (app *App) releasePreviousFrame() { // acquireSurfaceTexture gets the current surface texture. func (app *App) acquireSurfaceTexture() error { - surfaceTex, err := app.surface.GetCurrentTexture() + surfaceTex, _, err := app.surface.GetCurrentTexture() if err != nil { // Handle common surface errors if err == wgpu.ErrSurfaceLost || err == wgpu.ErrSurfaceNeedsReconfigure { @@ -484,7 +482,7 @@ func (app *App) acquireSurfaceTexture() error { app.surfaceTex = surfaceTex // Create texture view - view := surfaceTex.Texture.CreateView(nil) + view, _ := surfaceTex.Texture.CreateView(nil) if view == nil { return fmt.Errorf("failed to create texture view") } @@ -513,19 +511,19 @@ func (app *App) render() error { } // Create command encoder - encoder := app.device.CreateCommandEncoder(nil) + encoder, _ := app.device.CreateCommandEncoder(nil) if encoder == nil { return fmt.Errorf("failed to create command encoder") } defer encoder.Release() // Begin render pass - pass := encoder.BeginRenderPass(&wgpu.RenderPassDescriptor{ + pass, _ := encoder.BeginRenderPass(&wgpu.RenderPassDescriptor{ Label: "Indirect Draw Pass", ColorAttachments: []wgpu.RenderPassColorAttachment{{ View: app.surfaceTexView, - LoadOp: gputypes.LoadOpClear, - StoreOp: gputypes.StoreOpStore, + LoadOp: wgpu.LoadOpClear, + StoreOp: wgpu.StoreOpStore, ClearValue: wgpu.Color{ R: 0.1, G: 0.1, @@ -548,15 +546,17 @@ func (app *App) render() error { pass.End() // Finish encoding - cmdBuffer := encoder.Finish(nil) + cmdBuffer, _ := encoder.Finish() if cmdBuffer == nil { return fmt.Errorf("failed to finish command encoder") } defer cmdBuffer.Release() // Submit commands and present - app.queue.Submit(cmdBuffer) - app.surface.Present() + if _, err := app.queue.Submit(cmdBuffer); err != nil { + return fmt.Errorf("submit: %w", err) + } + _ = app.surface.Present() return nil } diff --git a/examples/instanced/main.go b/examples/instanced/main.go index 404f0db..834518b 100644 --- a/examples/instanced/main.go +++ b/examples/instanced/main.go @@ -10,7 +10,6 @@ import ( "unsafe" "github.com/go-webgpu/webgpu/wgpu" - "github.com/gogpu/gputypes" ) // Vertex data: position (x, y) + color (r, g, b) @@ -111,13 +110,13 @@ func main() { } defer device.Release() - queue := device.GetQueue() + queue := device.Queue() defer queue.Release() // Create shader module - shader := device.CreateShaderModuleWGSL(shaderCode) - if shader == nil { - log.Fatal("failed to create shader module") + shader, err := device.CreateShaderModuleWGSL(shaderCode) + if err != nil { + log.Fatalf("create shader module: %v", err) } defer shader.Release() @@ -130,13 +129,13 @@ func main() { // Create vertex buffer (per-vertex data) vertexBufferSize := uint64(len(triangleVertices)) * uint64(unsafe.Sizeof(triangleVertices[0])) - vertexBuffer := device.CreateBuffer(&wgpu.BufferDescriptor{ - Usage: gputypes.BufferUsageVertex | gputypes.BufferUsageCopyDst, + vertexBuffer, err := device.CreateBuffer(&wgpu.BufferDescriptor{ + Usage: wgpu.BufferUsageVertex | wgpu.BufferUsageCopyDst, Size: vertexBufferSize, - MappedAtCreation: wgpu.True, + MappedAtCreation: true, }) - if vertexBuffer == nil { - log.Fatal("failed to create vertex buffer") + if err != nil { + log.Fatalf("create vertex buffer: %v", err) } defer vertexBuffer.Release() @@ -150,13 +149,13 @@ func main() { // Create instance buffer (per-instance data) instanceBufferSize := uint64(len(instanceData)) * uint64(unsafe.Sizeof(instanceData[0])) - instanceBuffer := device.CreateBuffer(&wgpu.BufferDescriptor{ - Usage: gputypes.BufferUsageVertex | gputypes.BufferUsageCopyDst, + instanceBuffer, err := device.CreateBuffer(&wgpu.BufferDescriptor{ + Usage: wgpu.BufferUsageVertex | wgpu.BufferUsageCopyDst, Size: instanceBufferSize, - MappedAtCreation: wgpu.True, + MappedAtCreation: true, }) - if instanceBuffer == nil { - log.Fatal("failed to create instance buffer") + if err != nil { + log.Fatalf("create instance buffer: %v", err) } defer instanceBuffer.Release() @@ -170,20 +169,20 @@ func main() { // Define vertex attributes for per-vertex buffer vertexAttributes := []wgpu.VertexAttribute{ - {Format: gputypes.VertexFormatFloat32x2, Offset: 0, ShaderLocation: 0}, // position - {Format: gputypes.VertexFormatFloat32x3, Offset: 8, ShaderLocation: 1}, // color + {Format: wgpu.VertexFormatFloat32x2, Offset: 0, ShaderLocation: 0}, // position + {Format: wgpu.VertexFormatFloat32x3, Offset: 8, ShaderLocation: 1}, // color } // Define vertex attributes for per-instance buffer instanceAttributes := []wgpu.VertexAttribute{ - {Format: gputypes.VertexFormatFloat32x2, Offset: 0, ShaderLocation: 2}, // offset - {Format: gputypes.VertexFormatFloat32, Offset: 8, ShaderLocation: 3}, // scale + {Format: wgpu.VertexFormatFloat32x2, Offset: 0, ShaderLocation: 2}, // offset + {Format: wgpu.VertexFormatFloat32, Offset: 8, ShaderLocation: 3}, // scale } // Create render pipeline with two vertex buffer layouts: // - Slot 0: Per-vertex data (position, color) with VertexStepModeVertex // - Slot 1: Per-instance data (offset, scale) with VertexStepModeInstance - pipeline := device.CreateRenderPipeline(&wgpu.RenderPipelineDescriptor{ + pipeline, err := device.CreateRenderPipeline(&wgpu.RenderPipelineDescriptor{ Vertex: wgpu.VertexState{ Module: shader, EntryPoint: "vs_main", @@ -191,14 +190,14 @@ func main() { // Per-vertex buffer (slot 0) { ArrayStride: uint64(unsafe.Sizeof(Vertex{})), - StepMode: gputypes.VertexStepModeVertex, + StepMode: wgpu.VertexStepModeVertex, AttributeCount: uintptr(len(vertexAttributes)), Attributes: &vertexAttributes[0], }, // Per-instance buffer (slot 1) { ArrayStride: uint64(unsafe.Sizeof(InstanceData{})), - StepMode: gputypes.VertexStepModeInstance, // Key: advances per instance, not per vertex + StepMode: wgpu.VertexStepModeInstance, // Key: advances per instance, not per vertex AttributeCount: uintptr(len(instanceAttributes)), Attributes: &instanceAttributes[0], }, @@ -208,55 +207,61 @@ func main() { Module: shader, EntryPoint: "fs_main", Targets: []wgpu.ColorTargetState{ - {Format: gputypes.TextureFormatRGBA8Unorm, WriteMask: gputypes.ColorWriteMaskAll}, + {Format: wgpu.TextureFormatRGBA8Unorm, WriteMask: wgpu.ColorWriteMaskAll}, }, }, Primitive: wgpu.PrimitiveState{ - Topology: gputypes.PrimitiveTopologyTriangleList, - FrontFace: gputypes.FrontFaceCCW, - CullMode: gputypes.CullModeNone, + Topology: wgpu.PrimitiveTopologyTriangleList, + FrontFace: wgpu.FrontFaceCCW, + CullMode: wgpu.CullModeNone, }, Multisample: wgpu.MultisampleState{ Count: 1, Mask: 0xFFFFFFFF, }, }) - if pipeline == nil { - log.Fatal("failed to create render pipeline") + if err != nil { + log.Fatalf("create render pipeline: %v", err) } defer pipeline.Release() // Create output texture - outputTexture := device.CreateTexture(&wgpu.TextureDescriptor{ - Size: gputypes.Extent3D{Width: 256, Height: 256, DepthOrArrayLayers: 1}, - Format: gputypes.TextureFormatRGBA8Unorm, - Usage: gputypes.TextureUsageRenderAttachment | gputypes.TextureUsageCopySrc, + outputTexture, err := device.CreateTexture(&wgpu.TextureDescriptor{ + Size: wgpu.Extent3D{Width: 256, Height: 256, DepthOrArrayLayers: 1}, + Format: wgpu.TextureFormatRGBA8Unorm, + Usage: wgpu.TextureUsageRenderAttachment | wgpu.TextureUsageCopySrc, }) - if outputTexture == nil { - log.Fatal("failed to create output texture") + if err != nil { + log.Fatalf("create output texture: %v", err) } defer outputTexture.Release() - outputView := outputTexture.CreateView(nil) + outputView, err := outputTexture.CreateView(nil) + if err != nil { + log.Fatalf("create texture view: %v", err) + } defer outputView.Release() // Create command encoder - encoder := device.CreateCommandEncoder(nil) - if encoder == nil { - log.Fatal("failed to create command encoder") + encoder, err := device.CreateCommandEncoder(nil) + if err != nil { + log.Fatalf("create command encoder: %v", err) } // Begin render pass - renderPass := encoder.BeginRenderPass(&wgpu.RenderPassDescriptor{ + renderPass, err := encoder.BeginRenderPass(&wgpu.RenderPassDescriptor{ ColorAttachments: []wgpu.RenderPassColorAttachment{ { View: outputView, - LoadOp: gputypes.LoadOpClear, - StoreOp: gputypes.StoreOpStore, + LoadOp: wgpu.LoadOpClear, + StoreOp: wgpu.StoreOpStore, ClearValue: wgpu.Color{R: 0.1, G: 0.1, B: 0.1, A: 1.0}, }, }, }) + if err != nil { + log.Fatalf("begin render pass: %v", err) + } renderPass.SetPipeline(pipeline) renderPass.SetVertexBuffer(0, vertexBuffer, 0, vertexBufferSize) // Per-vertex data @@ -270,10 +275,15 @@ func main() { renderPass.Release() // Submit - cmdBuffer := encoder.Finish(nil) + cmdBuffer, err := encoder.Finish() + if err != nil { + log.Fatalf("finish encoder: %v", err) + } encoder.Release() - queue.Submit(cmdBuffer) + if _, err = queue.Submit(cmdBuffer); err != nil { + log.Fatalf("submit: %v", err) + } cmdBuffer.Release() fmt.Println("Instanced rendering complete!") diff --git a/examples/mrt/main.go b/examples/mrt/main.go index c47ff1c..0da150d 100644 --- a/examples/mrt/main.go +++ b/examples/mrt/main.go @@ -14,7 +14,6 @@ import ( "unsafe" "github.com/go-webgpu/webgpu/wgpu" - "github.com/gogpu/gputypes" "golang.org/x/sys/windows" ) @@ -329,7 +328,7 @@ func (app *App) initWebGPU() error { app.device = device // Get queue - app.queue = device.GetQueue() + app.queue = device.Queue() // Create surface surface, err := inst.CreateSurfaceFromWindowsHWND(uintptr(app.hinstance), uintptr(app.hwnd)) @@ -343,14 +342,13 @@ func (app *App) initWebGPU() error { // configureSurface configures the surface for rendering. func (app *App) configureSurface() error { - app.surface.Configure(&wgpu.SurfaceConfiguration{ - Device: app.device, - Format: gputypes.TextureFormatBGRA8Unorm, - Usage: gputypes.TextureUsageRenderAttachment, + _ = app.surface.Configure(app.device, &wgpu.SurfaceConfiguration{ + Format: wgpu.TextureFormatBGRA8Unorm, + Usage: wgpu.TextureUsageRenderAttachment, Width: app.width, Height: app.height, - AlphaMode: gputypes.CompositeAlphaModeOpaque, - PresentMode: gputypes.PresentModeFifo, + AlphaMode: wgpu.CompositeAlphaModeOpaque, + PresentMode: wgpu.PresentModeFifo, }) app.needsRecreate = false return nil @@ -358,16 +356,16 @@ func (app *App) configureSurface() error { // createExtraTexture creates the second render target for MRT. func (app *App) createExtraTexture() error { - app.extraTexture = app.device.CreateTexture(&wgpu.TextureDescriptor{ - Label: wgpu.StringView{}, - Usage: gputypes.TextureUsageRenderAttachment | gputypes.TextureUsageTextureBinding, - Dimension: gputypes.TextureDimension2D, - Size: gputypes.Extent3D{ + app.extraTexture, _ = app.device.CreateTexture(&wgpu.TextureDescriptor{ + Label: "", + Usage: wgpu.TextureUsageRenderAttachment | wgpu.TextureUsageTextureBinding, + Dimension: wgpu.TextureDimension2D, + Size: wgpu.Extent3D{ Width: app.width, Height: app.height, DepthOrArrayLayers: 1, }, - Format: gputypes.TextureFormatRGBA8Unorm, + Format: wgpu.TextureFormatRGBA8Unorm, MipLevelCount: 1, SampleCount: 1, }) @@ -376,7 +374,7 @@ func (app *App) createExtraTexture() error { return fmt.Errorf("failed to create extra texture") } - app.extraTextureView = app.extraTexture.CreateView(nil) + app.extraTextureView, _ = app.extraTexture.CreateView(nil) if app.extraTextureView == nil { return fmt.Errorf("failed to create extra texture view") } @@ -398,11 +396,11 @@ func (app *App) createVertexBuffer() error { vertexBufferSize := uint64(len(vertices) * 4) // 4 bytes per float32 // Create buffer with MappedAtCreation = true for easy data upload - app.vertexBuffer = app.device.CreateBuffer(&wgpu.BufferDescriptor{ - Label: wgpu.StringView{}, - Usage: gputypes.BufferUsageVertex | gputypes.BufferUsageCopyDst, + app.vertexBuffer, _ = app.device.CreateBuffer(&wgpu.BufferDescriptor{ + Label: "", + Usage: wgpu.BufferUsageVertex | wgpu.BufferUsageCopyDst, Size: vertexBufferSize, - MappedAtCreation: wgpu.True, + MappedAtCreation: true, }) if app.vertexBuffer == nil { @@ -432,11 +430,11 @@ func (app *App) createUniformBuffer() error { const uniformBufferSize = 64 // Create buffer with Uniform usage and CopyDst for updates - app.uniformBuffer = app.device.CreateBuffer(&wgpu.BufferDescriptor{ - Label: wgpu.StringView{}, - Usage: gputypes.BufferUsageUniform | gputypes.BufferUsageCopyDst, + app.uniformBuffer, _ = app.device.CreateBuffer(&wgpu.BufferDescriptor{ + Label: "", + Usage: wgpu.BufferUsageUniform | wgpu.BufferUsageCopyDst, Size: uniformBufferSize, - MappedAtCreation: wgpu.False, + MappedAtCreation: false, }) if app.uniformBuffer == nil { @@ -451,16 +449,15 @@ func (app *App) createBindGroupLayout() error { entries := []wgpu.BindGroupLayoutEntry{ { Binding: 0, - Visibility: gputypes.ShaderStageVertex, - Buffer: wgpu.BufferBindingLayout{ - Type: gputypes.BufferBindingTypeUniform, - HasDynamicOffset: wgpu.False, - MinBindingSize: 64, // mat4x4f = 64 bytes + Visibility: wgpu.ShaderStageVertex, + Buffer: &wgpu.BufferBindingLayout{ + Type: wgpu.BufferBindingTypeUniform, + MinBindingSize: 64, // mat4x4f = 64 bytes }, }, } - app.bindGroupLayout = app.device.CreateBindGroupLayoutSimple(entries) + app.bindGroupLayout, _ = app.device.CreateBindGroupLayoutSimple(entries) if app.bindGroupLayout == nil { return fmt.Errorf("failed to create bind group layout") } @@ -474,7 +471,7 @@ func (app *App) createBindGroup() error { wgpu.BufferBindingEntry(0, app.uniformBuffer, 0, 64), } - app.bindGroup = app.device.CreateBindGroupSimple(app.bindGroupLayout, entries) + app.bindGroup, _ = app.device.CreateBindGroupSimple(app.bindGroupLayout, entries) if app.bindGroup == nil { return fmt.Errorf("failed to create bind group") } @@ -484,7 +481,7 @@ func (app *App) createBindGroup() error { // createPipelineLayout creates the pipeline layout with bind group layout. func (app *App) createPipelineLayout() (*wgpu.PipelineLayout, error) { - pipelineLayout := app.device.CreatePipelineLayoutSimple([]*wgpu.BindGroupLayout{app.bindGroupLayout}) + pipelineLayout, _ := app.device.CreatePipelineLayoutSimple([]*wgpu.BindGroupLayout{app.bindGroupLayout}) if pipelineLayout == nil { return nil, fmt.Errorf("failed to create pipeline layout") } @@ -495,13 +492,13 @@ func (app *App) createPipelineLayout() (*wgpu.PipelineLayout, error) { func getVertexAttributes() []wgpu.VertexAttribute { return []wgpu.VertexAttribute{ { - Format: gputypes.VertexFormatFloat32x2, // position: vec2f + Format: wgpu.VertexFormatFloat32x2, // position: vec2f Offset: 0, ShaderLocation: 0, }, { - Format: gputypes.VertexFormatFloat32x3, // color: vec3f - Offset: 8, // 2 floats * 4 bytes = 8 bytes offset + Format: wgpu.VertexFormatFloat32x3, // color: vec3f + Offset: 8, // 2 floats * 4 bytes = 8 bytes offset ShaderLocation: 1, }, } @@ -521,15 +518,15 @@ func createPipelineDescriptor( EntryPoint: "vs_main", Buffers: []wgpu.VertexBufferLayout{{ ArrayStride: 20, // 5 floats * 4 bytes = 20 bytes per vertex - StepMode: gputypes.VertexStepModeVertex, + StepMode: wgpu.VertexStepModeVertex, AttributeCount: 2, Attributes: attributes, }}, }, Primitive: wgpu.PrimitiveState{ - Topology: gputypes.PrimitiveTopologyTriangleList, - FrontFace: gputypes.FrontFaceCCW, - CullMode: gputypes.CullModeNone, + Topology: wgpu.PrimitiveTopologyTriangleList, + FrontFace: wgpu.FrontFaceCCW, + CullMode: wgpu.CullModeNone, }, Multisample: wgpu.MultisampleState{ Count: 1, @@ -541,12 +538,12 @@ func createPipelineDescriptor( // MRT: Two color targets Targets: []wgpu.ColorTargetState{ { - Format: gputypes.TextureFormatBGRA8Unorm, - WriteMask: gputypes.ColorWriteMaskAll, + Format: wgpu.TextureFormatBGRA8Unorm, + WriteMask: wgpu.ColorWriteMaskAll, }, { - Format: gputypes.TextureFormatRGBA8Unorm, - WriteMask: gputypes.ColorWriteMaskAll, + Format: wgpu.TextureFormatRGBA8Unorm, + WriteMask: wgpu.ColorWriteMaskAll, }, }, }, @@ -556,7 +553,7 @@ func createPipelineDescriptor( // createPipeline creates the render pipeline with MRT support. func (app *App) createPipeline() error { // Create shader module - shader := app.device.CreateShaderModuleWGSL(shaderSource) + shader, _ := app.device.CreateShaderModuleWGSL(shaderSource) if shader == nil { return fmt.Errorf("failed to create shader module") } @@ -574,7 +571,7 @@ func (app *App) createPipeline() error { // Create render pipeline with MRT: two color targets desc := createPipelineDescriptor(pipelineLayout, shader, &attributes[0]) - pipeline := app.device.CreateRenderPipeline(desc) + pipeline, _ := app.device.CreateRenderPipeline(desc) if pipeline == nil { return fmt.Errorf("failed to create render pipeline") @@ -626,7 +623,7 @@ func (app *App) releasePreviousFrame() { // acquireSurfaceTexture gets the current surface texture. func (app *App) acquireSurfaceTexture() error { - surfaceTex, err := app.surface.GetCurrentTexture() + surfaceTex, _, err := app.surface.GetCurrentTexture() if err != nil { // Handle common surface errors if err == wgpu.ErrSurfaceLost || err == wgpu.ErrSurfaceNeedsReconfigure { @@ -638,7 +635,7 @@ func (app *App) acquireSurfaceTexture() error { app.surfaceTex = surfaceTex // Create texture view - view := surfaceTex.Texture.CreateView(nil) + view, _ := surfaceTex.Texture.CreateView(nil) if view == nil { return fmt.Errorf("failed to create texture view") } @@ -648,14 +645,14 @@ func (app *App) acquireSurfaceTexture() error { // renderTriangle encodes the MRT rendering commands. func (app *App) renderTriangle(encoder *wgpu.CommandEncoder, view *wgpu.TextureView) error { - pass := encoder.BeginRenderPass(&wgpu.RenderPassDescriptor{ + pass, _ := encoder.BeginRenderPass(&wgpu.RenderPassDescriptor{ Label: "MRT Render Pass", // MRT: Two color attachments ColorAttachments: []wgpu.RenderPassColorAttachment{ { View: view, - LoadOp: gputypes.LoadOpClear, - StoreOp: gputypes.StoreOpStore, + LoadOp: wgpu.LoadOpClear, + StoreOp: wgpu.StoreOpStore, ClearValue: wgpu.Color{ R: 0.1, G: 0.2, @@ -665,8 +662,8 @@ func (app *App) renderTriangle(encoder *wgpu.CommandEncoder, view *wgpu.TextureV }, { View: app.extraTextureView, - LoadOp: gputypes.LoadOpClear, - StoreOp: gputypes.StoreOpStore, + LoadOp: wgpu.LoadOpClear, + StoreOp: wgpu.StoreOpStore, ClearValue: wgpu.Color{ R: 0.0, G: 0.0, @@ -711,7 +708,7 @@ func (app *App) render() error { } // Create command encoder - encoder := app.device.CreateCommandEncoder(nil) + encoder, _ := app.device.CreateCommandEncoder(nil) if encoder == nil { return fmt.Errorf("failed to create command encoder") } @@ -723,15 +720,17 @@ func (app *App) render() error { } // Finish encoding - cmdBuffer := encoder.Finish(nil) + cmdBuffer, _ := encoder.Finish() if cmdBuffer == nil { return fmt.Errorf("failed to finish command encoder") } defer cmdBuffer.Release() // Submit commands and present - app.queue.Submit(cmdBuffer) - app.surface.Present() + if _, err := app.queue.Submit(cmdBuffer); err != nil { + return fmt.Errorf("submit: %w", err) + } + _ = app.surface.Present() return nil } diff --git a/examples/render_bundle/main.go b/examples/render_bundle/main.go index 0f7faba..52c06b8 100644 --- a/examples/render_bundle/main.go +++ b/examples/render_bundle/main.go @@ -11,7 +11,6 @@ import ( "unsafe" "github.com/go-webgpu/webgpu/wgpu" - "github.com/gogpu/gputypes" "golang.org/x/sys/windows" ) @@ -322,7 +321,7 @@ func (app *App) initWebGPU() error { } app.device = device - app.queue = device.GetQueue() + app.queue = device.Queue() surface, err := inst.CreateSurfaceFromWindowsHWND(uintptr(app.hinstance), uintptr(app.hwnd)) if err != nil { @@ -335,14 +334,13 @@ func (app *App) initWebGPU() error { // configureSurface configures the surface for rendering. func (app *App) configureSurface() error { - app.surface.Configure(&wgpu.SurfaceConfiguration{ - Device: app.device, - Format: gputypes.TextureFormatBGRA8Unorm, - Usage: gputypes.TextureUsageRenderAttachment, + _ = app.surface.Configure(app.device, &wgpu.SurfaceConfiguration{ + Format: wgpu.TextureFormatBGRA8Unorm, + Usage: wgpu.TextureUsageRenderAttachment, Width: app.width, Height: app.height, - AlphaMode: gputypes.CompositeAlphaModeOpaque, - PresentMode: gputypes.PresentModeFifo, + AlphaMode: wgpu.CompositeAlphaModeOpaque, + PresentMode: wgpu.PresentModeFifo, }) app.needsRecreate = false return nil @@ -350,17 +348,17 @@ func (app *App) configureSurface() error { // createPipeline creates the render pipeline. func (app *App) createPipeline() error { - shader := app.device.CreateShaderModuleWGSL(shaderSource) + shader, _ := app.device.CreateShaderModuleWGSL(shaderSource) if shader == nil { return fmt.Errorf("failed to create shader module") } defer shader.Release() - pipeline := app.device.CreateRenderPipelineSimple( + pipeline, _ := app.device.CreateRenderPipelineSimple( nil, shader, "vs_main", shader, "fs_main", - gputypes.TextureFormatBGRA8Unorm, + wgpu.TextureFormatBGRA8Unorm, ) if pipeline == nil { return fmt.Errorf("failed to create render pipeline") @@ -375,11 +373,11 @@ func (app *App) createRenderBundle() error { fmt.Println("Creating RenderBundle with 3 triangles...") // Create render bundle encoder with matching format - colorFormats := []gputypes.TextureFormat{gputypes.TextureFormatBGRA8Unorm} + colorFormats := []wgpu.TextureFormat{wgpu.TextureFormatBGRA8Unorm} bundleEncoder := app.device.CreateRenderBundleEncoderSimple( colorFormats, - gputypes.TextureFormatUndefined, // no depth - 1, // sample count + wgpu.TextureFormatUndefined, // no depth + 1, // sample count ) if bundleEncoder == nil { return fmt.Errorf("failed to create render bundle encoder") @@ -390,7 +388,7 @@ func (app *App) createRenderBundle() error { bundleEncoder.Draw(9, 1, 0, 0) // 9 vertices = 3 triangles // Finish recording - bundle := bundleEncoder.Finish(nil) + bundle := bundleEncoder.Finish() bundleEncoder.Release() if bundle == nil { @@ -416,7 +414,7 @@ func (app *App) releasePreviousFrame() { // acquireSurfaceTexture gets the current surface texture. func (app *App) acquireSurfaceTexture() error { - surfaceTex, err := app.surface.GetCurrentTexture() + surfaceTex, _, err := app.surface.GetCurrentTexture() if err != nil { if err == wgpu.ErrSurfaceLost || err == wgpu.ErrSurfaceNeedsReconfigure { app.needsRecreate = true @@ -426,7 +424,7 @@ func (app *App) acquireSurfaceTexture() error { } app.surfaceTex = surfaceTex - view := surfaceTex.Texture.CreateView(nil) + view, _ := surfaceTex.Texture.CreateView(nil) if view == nil { return fmt.Errorf("failed to create texture view") } @@ -451,19 +449,19 @@ func (app *App) render() error { return nil } - encoder := app.device.CreateCommandEncoder(nil) + encoder, _ := app.device.CreateCommandEncoder(nil) if encoder == nil { return fmt.Errorf("failed to create command encoder") } defer encoder.Release() // Begin render pass - pass := encoder.BeginRenderPass(&wgpu.RenderPassDescriptor{ + pass, _ := encoder.BeginRenderPass(&wgpu.RenderPassDescriptor{ Label: "RenderBundle Pass", ColorAttachments: []wgpu.RenderPassColorAttachment{{ View: app.surfaceTexView, - LoadOp: gputypes.LoadOpClear, - StoreOp: gputypes.StoreOpStore, + LoadOp: wgpu.LoadOpClear, + StoreOp: wgpu.StoreOpStore, ClearValue: wgpu.Color{ R: 0.1, G: 0.1, @@ -483,14 +481,16 @@ func (app *App) render() error { pass.End() - cmdBuffer := encoder.Finish(nil) + cmdBuffer, _ := encoder.Finish() if cmdBuffer == nil { return fmt.Errorf("failed to finish command encoder") } defer cmdBuffer.Release() - app.queue.Submit(cmdBuffer) - app.surface.Present() + if _, err := app.queue.Submit(cmdBuffer); err != nil { + return fmt.Errorf("submit: %w", err) + } + _ = app.surface.Present() return nil } diff --git a/examples/render_debug_markers/main.go b/examples/render_debug_markers/main.go index e960e7d..8b07a2f 100644 --- a/examples/render_debug_markers/main.go +++ b/examples/render_debug_markers/main.go @@ -6,7 +6,6 @@ import ( "log" "github.com/go-webgpu/webgpu/wgpu" - "github.com/gogpu/gputypes" ) func main() { @@ -19,7 +18,7 @@ func main() { // Request adapter adapter, err := instance.RequestAdapter(&wgpu.RequestAdapterOptions{ - PowerPreference: gputypes.PowerPreferenceHighPerformance, + PowerPreference: wgpu.PowerPreferenceHighPerformance, }) if err != nil { log.Fatalf("Failed to request adapter: %v", err) @@ -34,37 +33,37 @@ func main() { defer device.Release() // Create a simple texture for rendering - texture := device.CreateTexture(&wgpu.TextureDescriptor{ - Label: wgpu.EmptyStringView(), - Size: gputypes.Extent3D{ + texture, err := device.CreateTexture(&wgpu.TextureDescriptor{ + Label: "", + Size: wgpu.Extent3D{ Width: 800, Height: 600, DepthOrArrayLayers: 1, }, MipLevelCount: 1, SampleCount: 1, - Dimension: gputypes.TextureDimension2D, - Format: gputypes.TextureFormatRGBA8Unorm, - Usage: gputypes.TextureUsageRenderAttachment, + Dimension: wgpu.TextureDimension2D, + Format: wgpu.TextureFormatRGBA8Unorm, + Usage: wgpu.TextureUsageRenderAttachment, }) - if texture == nil { - log.Fatal("Failed to create texture") + if err != nil { + log.Fatalf("create texture: %v", err) } defer texture.Release() // Create texture view - view := texture.CreateView(nil) - if view == nil { - log.Fatal("Failed to create texture view") + view, err := texture.CreateView(nil) + if err != nil { + log.Fatalf("create texture view: %v", err) } defer view.Release() // Create command encoder - encoder := device.CreateCommandEncoder(&wgpu.CommandEncoderDescriptor{ - Label: wgpu.EmptyStringView(), + encoder, err := device.CreateCommandEncoder(&wgpu.CommandEncoderDescriptor{ + Label: "", }) - if encoder == nil { - log.Fatal("Failed to create command encoder") + if err != nil { + log.Fatalf("create command encoder: %v", err) } defer encoder.Release() @@ -77,113 +76,115 @@ func main() { fmt.Println() // Begin render pass with debug annotations - renderPass := encoder.BeginRenderPass(&wgpu.RenderPassDescriptor{ + renderPass, err := encoder.BeginRenderPass(&wgpu.RenderPassDescriptor{ Label: "Main Render Pass", ColorAttachments: []wgpu.RenderPassColorAttachment{ { View: view, - LoadOp: gputypes.LoadOpClear, + LoadOp: wgpu.LoadOpClear, ClearValue: wgpu.Color{ R: 0.1, G: 0.2, B: 0.3, A: 1.0, }, - StoreOp: gputypes.StoreOpStore, + StoreOp: wgpu.StoreOpStore, }, }, }) - if renderPass == nil { - log.Fatal("Failed to begin render pass") + if err != nil { + log.Fatalf("begin render pass: %v", err) } defer renderPass.Release() // Insert a single debug marker renderPass.InsertDebugMarker("Frame Start") - fmt.Println("✓ Inserted debug marker: 'Frame Start'") + fmt.Println("Inserted debug marker: 'Frame Start'") // Push a debug group for scene rendering renderPass.PushDebugGroup("Scene Rendering") - fmt.Println("✓ Pushed debug group: 'Scene Rendering'") + fmt.Println("Pushed debug group: 'Scene Rendering'") // Nested debug group for geometry renderPass.PushDebugGroup("Geometry Pass") - fmt.Println(" ✓ Pushed nested group: 'Geometry Pass'") + fmt.Println(" Pushed nested group: 'Geometry Pass'") // In a real application, you would draw geometry here renderPass.InsertDebugMarker("Draw Opaque Objects") - fmt.Println(" ✓ Inserted marker: 'Draw Opaque Objects'") + fmt.Println(" Inserted marker: 'Draw Opaque Objects'") renderPass.InsertDebugMarker("Draw Alpha-Tested Objects") - fmt.Println(" ✓ Inserted marker: 'Draw Alpha-Tested Objects'") + fmt.Println(" Inserted marker: 'Draw Alpha-Tested Objects'") // Pop geometry pass renderPass.PopDebugGroup() - fmt.Println(" ✓ Popped debug group: 'Geometry Pass'") + fmt.Println(" Popped debug group: 'Geometry Pass'") // Another nested group for lighting renderPass.PushDebugGroup("Lighting Pass") - fmt.Println(" ✓ Pushed nested group: 'Lighting Pass'") + fmt.Println(" Pushed nested group: 'Lighting Pass'") renderPass.InsertDebugMarker("Compute Shadow Maps") - fmt.Println(" ✓ Inserted marker: 'Compute Shadow Maps'") + fmt.Println(" Inserted marker: 'Compute Shadow Maps'") renderPass.InsertDebugMarker("Apply Lighting") - fmt.Println(" ✓ Inserted marker: 'Apply Lighting'") + fmt.Println(" Inserted marker: 'Apply Lighting'") renderPass.PopDebugGroup() - fmt.Println(" ✓ Popped debug group: 'Lighting Pass'") + fmt.Println(" Popped debug group: 'Lighting Pass'") // Pop scene rendering renderPass.PopDebugGroup() - fmt.Println("✓ Popped debug group: 'Scene Rendering'") + fmt.Println("Popped debug group: 'Scene Rendering'") // Post-processing group renderPass.PushDebugGroup("Post-Processing") - fmt.Println("✓ Pushed debug group: 'Post-Processing'") + fmt.Println("Pushed debug group: 'Post-Processing'") renderPass.InsertDebugMarker("Tone Mapping") - fmt.Println(" ✓ Inserted marker: 'Tone Mapping'") + fmt.Println(" Inserted marker: 'Tone Mapping'") renderPass.InsertDebugMarker("FXAA") - fmt.Println(" ✓ Inserted marker: 'FXAA'") + fmt.Println(" Inserted marker: 'FXAA'") renderPass.PopDebugGroup() - fmt.Println("✓ Popped debug group: 'Post-Processing'") + fmt.Println("Popped debug group: 'Post-Processing'") // Final marker renderPass.InsertDebugMarker("Frame End") - fmt.Println("✓ Inserted debug marker: 'Frame End'") + fmt.Println("Inserted debug marker: 'Frame End'") // End render pass renderPass.End() - fmt.Println("\n✓ Render pass ended") + fmt.Println("\nRender pass ended") // Finish encoding - commandBuffer := encoder.Finish(nil) - if commandBuffer == nil { - log.Fatal("Failed to finish command encoder") + commandBuffer, err := encoder.Finish() + if err != nil { + log.Fatalf("finish encoder: %v", err) } defer commandBuffer.Release() // Submit commands - queue := device.GetQueue() + queue := device.Queue() defer queue.Release() - queue.Submit(commandBuffer) + if _, err = queue.Submit(commandBuffer); err != nil { + log.Fatalf("submit: %v", err) + } - fmt.Println("✓ Commands submitted") + fmt.Println("Commands submitted") fmt.Println("\n=== Debug Marker Hierarchy ===") fmt.Println("Frame Start [marker]") - fmt.Println("└─ Scene Rendering [group]") - fmt.Println(" ├─ Geometry Pass [group]") - fmt.Println(" │ ├─ Draw Opaque Objects [marker]") - fmt.Println(" │ └─ Draw Alpha-Tested Objects [marker]") - fmt.Println(" └─ Lighting Pass [group]") - fmt.Println(" ├─ Compute Shadow Maps [marker]") - fmt.Println(" └─ Apply Lighting [marker]") - fmt.Println("└─ Post-Processing [group]") - fmt.Println(" ├─ Tone Mapping [marker]") - fmt.Println(" └─ FXAA [marker]") + fmt.Println("Scene Rendering [group]") + fmt.Println(" Geometry Pass [group]") + fmt.Println(" Draw Opaque Objects [marker]") + fmt.Println(" Draw Alpha-Tested Objects [marker]") + fmt.Println(" Lighting Pass [group]") + fmt.Println(" Compute Shadow Maps [marker]") + fmt.Println(" Apply Lighting [marker]") + fmt.Println("Post-Processing [group]") + fmt.Println(" Tone Mapping [marker]") + fmt.Println(" FXAA [marker]") fmt.Println("Frame End [marker]") fmt.Println("\nThis hierarchy will be visible in GPU debugging tools!") } diff --git a/examples/rotating-triangle/main.go b/examples/rotating-triangle/main.go index 6b53538..27055fc 100644 --- a/examples/rotating-triangle/main.go +++ b/examples/rotating-triangle/main.go @@ -13,7 +13,6 @@ import ( "unsafe" "github.com/go-webgpu/webgpu/wgpu" - "github.com/gogpu/gputypes" "golang.org/x/sys/windows" ) @@ -309,7 +308,7 @@ func (app *App) initWebGPU() error { app.device = device // Get queue - app.queue = device.GetQueue() + app.queue = device.Queue() // Create surface surface, err := inst.CreateSurfaceFromWindowsHWND(uintptr(app.hinstance), uintptr(app.hwnd)) @@ -323,14 +322,13 @@ func (app *App) initWebGPU() error { // configureSurface configures the surface for rendering. func (app *App) configureSurface() error { - app.surface.Configure(&wgpu.SurfaceConfiguration{ - Device: app.device, - Format: gputypes.TextureFormatBGRA8Unorm, - Usage: gputypes.TextureUsageRenderAttachment, + _ = app.surface.Configure(app.device, &wgpu.SurfaceConfiguration{ + Format: wgpu.TextureFormatBGRA8Unorm, + Usage: wgpu.TextureUsageRenderAttachment, Width: app.width, Height: app.height, - AlphaMode: gputypes.CompositeAlphaModeOpaque, - PresentMode: gputypes.PresentModeFifo, + AlphaMode: wgpu.CompositeAlphaModeOpaque, + PresentMode: wgpu.PresentModeFifo, }) app.needsRecreate = false return nil @@ -350,11 +348,11 @@ func (app *App) createVertexBuffer() error { vertexBufferSize := uint64(len(vertices) * 4) // 4 bytes per float32 // Create buffer with MappedAtCreation = true for easy data upload - app.vertexBuffer = app.device.CreateBuffer(&wgpu.BufferDescriptor{ - Label: wgpu.StringView{}, - Usage: gputypes.BufferUsageVertex | gputypes.BufferUsageCopyDst, + app.vertexBuffer, _ = app.device.CreateBuffer(&wgpu.BufferDescriptor{ + Label: "", + Usage: wgpu.BufferUsageVertex | wgpu.BufferUsageCopyDst, Size: vertexBufferSize, - MappedAtCreation: wgpu.True, + MappedAtCreation: true, }) if app.vertexBuffer == nil { @@ -384,11 +382,11 @@ func (app *App) createUniformBuffer() error { const uniformBufferSize = 64 // Create buffer with Uniform usage and CopyDst for updates - app.uniformBuffer = app.device.CreateBuffer(&wgpu.BufferDescriptor{ - Label: wgpu.StringView{}, - Usage: gputypes.BufferUsageUniform | gputypes.BufferUsageCopyDst, + app.uniformBuffer, _ = app.device.CreateBuffer(&wgpu.BufferDescriptor{ + Label: "", + Usage: wgpu.BufferUsageUniform | wgpu.BufferUsageCopyDst, Size: uniformBufferSize, - MappedAtCreation: wgpu.False, + MappedAtCreation: false, }) if app.uniformBuffer == nil { @@ -403,16 +401,15 @@ func (app *App) createBindGroupLayout() error { entries := []wgpu.BindGroupLayoutEntry{ { Binding: 0, - Visibility: gputypes.ShaderStageVertex, - Buffer: wgpu.BufferBindingLayout{ - Type: gputypes.BufferBindingTypeUniform, - HasDynamicOffset: wgpu.False, - MinBindingSize: 64, // mat4x4f = 64 bytes + Visibility: wgpu.ShaderStageVertex, + Buffer: &wgpu.BufferBindingLayout{ + Type: wgpu.BufferBindingTypeUniform, + MinBindingSize: 64, // mat4x4f = 64 bytes }, }, } - app.bindGroupLayout = app.device.CreateBindGroupLayoutSimple(entries) + app.bindGroupLayout, _ = app.device.CreateBindGroupLayoutSimple(entries) if app.bindGroupLayout == nil { return fmt.Errorf("failed to create bind group layout") } @@ -426,7 +423,7 @@ func (app *App) createBindGroup() error { wgpu.BufferBindingEntry(0, app.uniformBuffer, 0, 64), } - app.bindGroup = app.device.CreateBindGroupSimple(app.bindGroupLayout, entries) + app.bindGroup, _ = app.device.CreateBindGroupSimple(app.bindGroupLayout, entries) if app.bindGroup == nil { return fmt.Errorf("failed to create bind group") } @@ -436,7 +433,7 @@ func (app *App) createBindGroup() error { // createPipelineLayout creates the pipeline layout with bind group layout. func (app *App) createPipelineLayout() (*wgpu.PipelineLayout, error) { - pipelineLayout := app.device.CreatePipelineLayoutSimple([]*wgpu.BindGroupLayout{app.bindGroupLayout}) + pipelineLayout, _ := app.device.CreatePipelineLayoutSimple([]*wgpu.BindGroupLayout{app.bindGroupLayout}) if pipelineLayout == nil { return nil, fmt.Errorf("failed to create pipeline layout") } @@ -446,7 +443,7 @@ func (app *App) createPipelineLayout() (*wgpu.PipelineLayout, error) { // createPipeline creates the render pipeline with vertex buffer layout. func (app *App) createPipeline() error { // Create shader module - shader := app.device.CreateShaderModuleWGSL(shaderSource) + shader, _ := app.device.CreateShaderModuleWGSL(shaderSource) if shader == nil { return fmt.Errorf("failed to create shader module") } @@ -462,19 +459,19 @@ func (app *App) createPipeline() error { // Define vertex attributes attributes := []wgpu.VertexAttribute{ { - Format: gputypes.VertexFormatFloat32x2, // position: vec2f + Format: wgpu.VertexFormatFloat32x2, // position: vec2f Offset: 0, ShaderLocation: 0, }, { - Format: gputypes.VertexFormatFloat32x3, // color: vec3f - Offset: 8, // 2 floats * 4 bytes = 8 bytes offset + Format: wgpu.VertexFormatFloat32x3, // color: vec3f + Offset: 8, // 2 floats * 4 bytes = 8 bytes offset ShaderLocation: 1, }, } // Create render pipeline with vertex buffer layout - pipeline := app.device.CreateRenderPipeline(&wgpu.RenderPipelineDescriptor{ + pipeline, _ := app.device.CreateRenderPipeline(&wgpu.RenderPipelineDescriptor{ Label: "", Layout: pipelineLayout, Vertex: wgpu.VertexState{ @@ -482,15 +479,15 @@ func (app *App) createPipeline() error { EntryPoint: "vs_main", Buffers: []wgpu.VertexBufferLayout{{ ArrayStride: 20, // 5 floats * 4 bytes = 20 bytes per vertex - StepMode: gputypes.VertexStepModeVertex, + StepMode: wgpu.VertexStepModeVertex, AttributeCount: 2, Attributes: &attributes[0], }}, }, Primitive: wgpu.PrimitiveState{ - Topology: gputypes.PrimitiveTopologyTriangleList, - FrontFace: gputypes.FrontFaceCCW, - CullMode: gputypes.CullModeNone, + Topology: wgpu.PrimitiveTopologyTriangleList, + FrontFace: wgpu.FrontFaceCCW, + CullMode: wgpu.CullModeNone, }, Multisample: wgpu.MultisampleState{ Count: 1, @@ -500,8 +497,8 @@ func (app *App) createPipeline() error { Module: shader, EntryPoint: "fs_main", Targets: []wgpu.ColorTargetState{{ - Format: gputypes.TextureFormatBGRA8Unorm, - WriteMask: gputypes.ColorWriteMaskAll, + Format: wgpu.TextureFormatBGRA8Unorm, + WriteMask: wgpu.ColorWriteMaskAll, }}, }, }) @@ -562,7 +559,7 @@ func (app *App) releasePreviousFrame() { // acquireSurfaceTexture gets the current surface texture. func (app *App) acquireSurfaceTexture() error { - surfaceTex, err := app.surface.GetCurrentTexture() + surfaceTex, _, err := app.surface.GetCurrentTexture() if err != nil { // Handle common surface errors if err == wgpu.ErrSurfaceLost || err == wgpu.ErrSurfaceNeedsReconfigure { @@ -574,7 +571,7 @@ func (app *App) acquireSurfaceTexture() error { app.surfaceTex = surfaceTex // Create texture view - view := surfaceTex.Texture.CreateView(nil) + view, _ := surfaceTex.Texture.CreateView(nil) if view == nil { return fmt.Errorf("failed to create texture view") } @@ -584,12 +581,12 @@ func (app *App) acquireSurfaceTexture() error { // renderTriangle encodes the rotating triangle rendering commands. func (app *App) renderTriangle(encoder *wgpu.CommandEncoder, view *wgpu.TextureView) error { - pass := encoder.BeginRenderPass(&wgpu.RenderPassDescriptor{ + pass, _ := encoder.BeginRenderPass(&wgpu.RenderPassDescriptor{ Label: "Rotating Triangle Render Pass", ColorAttachments: []wgpu.RenderPassColorAttachment{{ View: view, - LoadOp: gputypes.LoadOpClear, - StoreOp: gputypes.StoreOpStore, + LoadOp: wgpu.LoadOpClear, + StoreOp: wgpu.StoreOpStore, ClearValue: wgpu.Color{ R: 0.1, G: 0.2, @@ -633,7 +630,7 @@ func (app *App) render() error { } // Create command encoder - encoder := app.device.CreateCommandEncoder(nil) + encoder, _ := app.device.CreateCommandEncoder(nil) if encoder == nil { return fmt.Errorf("failed to create command encoder") } @@ -645,15 +642,17 @@ func (app *App) render() error { } // Finish encoding - cmdBuffer := encoder.Finish(nil) + cmdBuffer, _ := encoder.Finish() if cmdBuffer == nil { return fmt.Errorf("failed to finish command encoder") } defer cmdBuffer.Release() // Submit commands and present - app.queue.Submit(cmdBuffer) - app.surface.Present() + if _, err := app.queue.Submit(cmdBuffer); err != nil { + return fmt.Errorf("submit: %w", err) + } + _ = app.surface.Present() return nil } diff --git a/examples/textured-quad/main.go b/examples/textured-quad/main.go index 24a1776..4541be1 100644 --- a/examples/textured-quad/main.go +++ b/examples/textured-quad/main.go @@ -10,7 +10,6 @@ import ( "unsafe" "github.com/go-webgpu/webgpu/wgpu" - "github.com/gogpu/gputypes" "golang.org/x/sys/windows" ) @@ -305,7 +304,7 @@ func (app *App) initWebGPU() error { app.device = device // Get queue - app.queue = device.GetQueue() + app.queue = device.Queue() // Create surface surface, err := inst.CreateSurfaceFromWindowsHWND(uintptr(app.hinstance), uintptr(app.hwnd)) @@ -319,14 +318,13 @@ func (app *App) initWebGPU() error { // configureSurface configures the surface for rendering. func (app *App) configureSurface() error { - app.surface.Configure(&wgpu.SurfaceConfiguration{ - Device: app.device, - Format: gputypes.TextureFormatBGRA8Unorm, - Usage: gputypes.TextureUsageRenderAttachment, + _ = app.surface.Configure(app.device, &wgpu.SurfaceConfiguration{ + Format: wgpu.TextureFormatBGRA8Unorm, + Usage: wgpu.TextureUsageRenderAttachment, Width: app.width, Height: app.height, - AlphaMode: gputypes.CompositeAlphaModeOpaque, - PresentMode: gputypes.PresentModeFifo, + AlphaMode: wgpu.CompositeAlphaModeOpaque, + PresentMode: wgpu.PresentModeFifo, }) app.needsRecreate = false return nil @@ -368,20 +366,20 @@ func (app *App) createTexture() error { // Create texture textureDesc := wgpu.TextureDescriptor{ - Label: wgpu.EmptyStringView(), - Usage: gputypes.TextureUsageTextureBinding | gputypes.TextureUsageCopyDst, - Dimension: gputypes.TextureDimension2D, - Size: gputypes.Extent3D{ + Label: "", + Usage: wgpu.TextureUsageTextureBinding | wgpu.TextureUsageCopyDst, + Dimension: wgpu.TextureDimension2D, + Size: wgpu.Extent3D{ Width: size, Height: size, DepthOrArrayLayers: 1, }, - Format: gputypes.TextureFormatRGBA8Unorm, + Format: wgpu.TextureFormatRGBA8Unorm, MipLevelCount: 1, SampleCount: 1, } - app.texture = app.device.CreateTexture(&textureDesc) + app.texture, _ = app.device.CreateTexture(&textureDesc) if app.texture == nil { return fmt.Errorf("failed to create texture") } @@ -391,29 +389,29 @@ func (app *App) createTexture() error { const bytesPerRow = size * bytesPerPixel const alignedBytesPerRow = ((bytesPerRow + 255) / 256) * 256 - destInfo := wgpu.TexelCopyTextureInfo{ - Texture: app.texture.Handle(), + destInfo := wgpu.ImageCopyTexture{ + Texture: app.texture, MipLevel: 0, - Origin: gputypes.Origin3D{X: 0, Y: 0, Z: 0}, + Origin: wgpu.Origin3D{X: 0, Y: 0, Z: 0}, Aspect: wgpu.TextureAspectAll, } - layout := wgpu.TexelCopyBufferLayout{ + layout := wgpu.ImageDataLayout{ Offset: 0, BytesPerRow: alignedBytesPerRow, RowsPerImage: size, } - extent := gputypes.Extent3D{ + extent := wgpu.Extent3D{ Width: size, Height: size, DepthOrArrayLayers: 1, } - app.queue.WriteTexture(&destInfo, textureData, &layout, &extent) + _ = app.queue.WriteTexture(&destInfo, textureData, &layout, &extent) // Create texture view - app.textureView = app.texture.CreateView(nil) + app.textureView, _ = app.texture.CreateView(nil) if app.textureView == nil { return fmt.Errorf("failed to create texture view") } @@ -423,7 +421,7 @@ func (app *App) createTexture() error { // createSampler creates a linear sampler. func (app *App) createSampler() error { - app.sampler = app.device.CreateLinearSampler() + app.sampler, _ = app.device.CreateLinearSampler() if app.sampler == nil { return fmt.Errorf("failed to create sampler") } @@ -436,23 +434,22 @@ func (app *App) createBindGroup() error { layoutEntries := []wgpu.BindGroupLayoutEntry{ { Binding: 0, - Visibility: gputypes.ShaderStageFragment, - Sampler: wgpu.SamplerBindingLayout{ - Type: gputypes.SamplerBindingTypeFiltering, + Visibility: wgpu.ShaderStageFragment, + Sampler: &wgpu.SamplerBindingLayout{ + Type: wgpu.SamplerBindingTypeFiltering, }, }, { Binding: 1, - Visibility: gputypes.ShaderStageFragment, - Texture: wgpu.TextureBindingLayout{ - SampleType: gputypes.TextureSampleTypeFloat, - ViewDimension: gputypes.TextureViewDimension2D, - Multisampled: wgpu.False, + Visibility: wgpu.ShaderStageFragment, + Texture: &wgpu.TextureBindingLayout{ + SampleType: wgpu.TextureSampleTypeFloat, + ViewDimension: wgpu.TextureViewDimension2D, }, }, } - app.bindGroupLyt = app.device.CreateBindGroupLayoutSimple(layoutEntries) + app.bindGroupLyt, _ = app.device.CreateBindGroupLayoutSimple(layoutEntries) if app.bindGroupLyt == nil { return fmt.Errorf("failed to create bind group layout") } @@ -463,7 +460,7 @@ func (app *App) createBindGroup() error { wgpu.TextureBindingEntry(1, app.textureView), } - app.bindGroup = app.device.CreateBindGroupSimple(app.bindGroupLyt, entries) + app.bindGroup, _ = app.device.CreateBindGroupSimple(app.bindGroupLyt, entries) if app.bindGroup == nil { return fmt.Errorf("failed to create bind group") } @@ -492,11 +489,11 @@ func (app *App) createBuffers() error { // Create vertex buffer // nolint:gosec // len(vertices) is 16, * 4 = 64 bytes - no overflow risk vertexBufferSize := uint64(len(vertices) * 4) - app.vertexBuffer = app.device.CreateBuffer(&wgpu.BufferDescriptor{ - Label: wgpu.EmptyStringView(), - Usage: gputypes.BufferUsageVertex | gputypes.BufferUsageCopyDst, + app.vertexBuffer, _ = app.device.CreateBuffer(&wgpu.BufferDescriptor{ + Label: "", + Usage: wgpu.BufferUsageVertex | wgpu.BufferUsageCopyDst, Size: vertexBufferSize, - MappedAtCreation: wgpu.True, + MappedAtCreation: true, }) if app.vertexBuffer == nil { return fmt.Errorf("failed to create vertex buffer") @@ -515,11 +512,11 @@ func (app *App) createBuffers() error { // Create index buffer // nolint:gosec // len(indices) is 6, * 2 = 12 bytes - no overflow risk indexBufferSize := uint64(len(indices) * 2) - app.indexBuffer = app.device.CreateBuffer(&wgpu.BufferDescriptor{ - Label: wgpu.EmptyStringView(), - Usage: gputypes.BufferUsageIndex | gputypes.BufferUsageCopyDst, + app.indexBuffer, _ = app.device.CreateBuffer(&wgpu.BufferDescriptor{ + Label: "", + Usage: wgpu.BufferUsageIndex | wgpu.BufferUsageCopyDst, Size: indexBufferSize, - MappedAtCreation: wgpu.True, + MappedAtCreation: true, }) if app.indexBuffer == nil { return fmt.Errorf("failed to create index buffer") @@ -542,14 +539,14 @@ func (app *App) createBuffers() error { // nolint:funlen // FFI descriptor builders with detailed pipeline configuration func (app *App) createPipeline() error { // Create shader module - shader := app.device.CreateShaderModuleWGSL(shaderSource) + shader, _ := app.device.CreateShaderModuleWGSL(shaderSource) if shader == nil { return fmt.Errorf("failed to create shader module") } defer shader.Release() // Create pipeline layout with bind group layout - pipelineLayout := app.device.CreatePipelineLayoutSimple([]*wgpu.BindGroupLayout{app.bindGroupLyt}) + pipelineLayout, _ := app.device.CreatePipelineLayoutSimple([]*wgpu.BindGroupLayout{app.bindGroupLyt}) if pipelineLayout == nil { return fmt.Errorf("failed to create pipeline layout") } @@ -559,19 +556,19 @@ func (app *App) createPipeline() error { // Define vertex attributes attributes := []wgpu.VertexAttribute{ { - Format: gputypes.VertexFormatFloat32x2, // position: vec2f + Format: wgpu.VertexFormatFloat32x2, // position: vec2f Offset: 0, ShaderLocation: 0, }, { - Format: gputypes.VertexFormatFloat32x2, // uv: vec2f - Offset: 8, // 2 floats * 4 bytes = 8 bytes offset + Format: wgpu.VertexFormatFloat32x2, // uv: vec2f + Offset: 8, // 2 floats * 4 bytes = 8 bytes offset ShaderLocation: 1, }, } // Create render pipeline - pipeline := app.device.CreateRenderPipeline(&wgpu.RenderPipelineDescriptor{ + pipeline, _ := app.device.CreateRenderPipeline(&wgpu.RenderPipelineDescriptor{ Label: "", Layout: pipelineLayout, Vertex: wgpu.VertexState{ @@ -579,15 +576,15 @@ func (app *App) createPipeline() error { EntryPoint: "vs_main", Buffers: []wgpu.VertexBufferLayout{{ ArrayStride: 16, // 4 floats * 4 bytes = 16 bytes per vertex - StepMode: gputypes.VertexStepModeVertex, + StepMode: wgpu.VertexStepModeVertex, AttributeCount: 2, Attributes: &attributes[0], }}, }, Primitive: wgpu.PrimitiveState{ - Topology: gputypes.PrimitiveTopologyTriangleList, - FrontFace: gputypes.FrontFaceCCW, - CullMode: gputypes.CullModeNone, + Topology: wgpu.PrimitiveTopologyTriangleList, + FrontFace: wgpu.FrontFaceCCW, + CullMode: wgpu.CullModeNone, }, Multisample: wgpu.MultisampleState{ Count: 1, @@ -597,8 +594,8 @@ func (app *App) createPipeline() error { Module: shader, EntryPoint: "fs_main", Targets: []wgpu.ColorTargetState{{ - Format: gputypes.TextureFormatBGRA8Unorm, - WriteMask: gputypes.ColorWriteMaskAll, + Format: wgpu.TextureFormatBGRA8Unorm, + WriteMask: wgpu.ColorWriteMaskAll, }}, }, }) @@ -625,7 +622,7 @@ func (app *App) releasePreviousFrame() { // acquireSurfaceTexture gets the current surface texture. func (app *App) acquireSurfaceTexture() error { - surfaceTex, err := app.surface.GetCurrentTexture() + surfaceTex, _, err := app.surface.GetCurrentTexture() if err != nil { // Handle common surface errors if err == wgpu.ErrSurfaceLost || err == wgpu.ErrSurfaceNeedsReconfigure { @@ -637,7 +634,7 @@ func (app *App) acquireSurfaceTexture() error { app.surfaceTex = surfaceTex // Create texture view - view := surfaceTex.Texture.CreateView(nil) + view, _ := surfaceTex.Texture.CreateView(nil) if view == nil { return fmt.Errorf("failed to create texture view") } @@ -647,12 +644,12 @@ func (app *App) acquireSurfaceTexture() error { // renderQuad encodes the textured quad rendering commands. func (app *App) renderQuad(encoder *wgpu.CommandEncoder, view *wgpu.TextureView) error { - pass := encoder.BeginRenderPass(&wgpu.RenderPassDescriptor{ + pass, _ := encoder.BeginRenderPass(&wgpu.RenderPassDescriptor{ Label: "Textured Quad Render Pass", ColorAttachments: []wgpu.RenderPassColorAttachment{{ View: view, - LoadOp: gputypes.LoadOpClear, - StoreOp: gputypes.StoreOpStore, + LoadOp: wgpu.LoadOpClear, + StoreOp: wgpu.StoreOpStore, ClearValue: wgpu.Color{ R: 0.05, G: 0.05, @@ -668,8 +665,8 @@ func (app *App) renderQuad(encoder *wgpu.CommandEncoder, view *wgpu.TextureView) pass.SetPipeline(app.pipeline) pass.SetBindGroup(0, app.bindGroup, nil) - pass.SetVertexBuffer(0, app.vertexBuffer, 0, uint64(16*4)) // 16 floats * 4 bytes - pass.SetIndexBuffer(app.indexBuffer, gputypes.IndexFormatUint16, 0, uint64(6*2)) // 6 indices * 2 bytes + pass.SetVertexBuffer(0, app.vertexBuffer, 0, uint64(16*4)) // 16 floats * 4 bytes + pass.SetIndexBuffer(app.indexBuffer, wgpu.IndexFormatUint16, 0, uint64(6*2)) // 6 indices * 2 bytes pass.DrawIndexed(6, 1, 0, 0, 0) pass.End() return nil @@ -693,7 +690,7 @@ func (app *App) render() error { } // Create command encoder - encoder := app.device.CreateCommandEncoder(nil) + encoder, _ := app.device.CreateCommandEncoder(nil) if encoder == nil { return fmt.Errorf("failed to create command encoder") } @@ -705,15 +702,17 @@ func (app *App) render() error { } // Finish encoding - cmdBuffer := encoder.Finish(nil) + cmdBuffer, _ := encoder.Finish() if cmdBuffer == nil { return fmt.Errorf("failed to finish command encoder") } defer cmdBuffer.Release() // Submit commands and present - app.queue.Submit(cmdBuffer) - app.surface.Present() + if _, err := app.queue.Submit(cmdBuffer); err != nil { + return fmt.Errorf("submit: %w", err) + } + _ = app.surface.Present() return nil } diff --git a/examples/timestamp_query/main.go b/examples/timestamp_query/main.go index d0a8dbc..01ab87c 100644 --- a/examples/timestamp_query/main.go +++ b/examples/timestamp_query/main.go @@ -4,13 +4,13 @@ package main import ( + "context" "fmt" "log" "time" "unsafe" "github.com/go-webgpu/webgpu/wgpu" - "github.com/gogpu/gputypes" ) func main() { @@ -52,12 +52,12 @@ func run() error { } defer device.Release() - queue := device.GetQueue() + queue := device.Queue() defer queue.Release() - // Try to create a timestamp query set - // This will fail if TIMESTAMP_QUERY feature is not enabled - querySet := device.CreateQuerySet(&wgpu.QuerySetDescriptor{ + // Try to create a timestamp query set. + // This will fail if TIMESTAMP_QUERY feature is not enabled. + querySet, _ := device.CreateQuerySet(&wgpu.QuerySetDescriptor{ Type: wgpu.QueryTypeTimestamp, Count: 2, }) @@ -82,22 +82,22 @@ func runWithTimestamps(device *wgpu.Device, queue *wgpu.Queue, querySet *wgpu.Qu // Create buffer to resolve query results (2 timestamps * 8 bytes each) queryResultSize := uint64(16) - queryResultBuffer := device.CreateBuffer(&wgpu.BufferDescriptor{ - Usage: gputypes.BufferUsageQueryResolve | gputypes.BufferUsageCopySrc, + queryResultBuffer, err := device.CreateBuffer(&wgpu.BufferDescriptor{ + Usage: wgpu.BufferUsageQueryResolve | wgpu.BufferUsageCopySrc, Size: queryResultSize, }) - if queryResultBuffer == nil { - return fmt.Errorf("failed to create query result buffer") + if err != nil { + return fmt.Errorf("create query result buffer: %w", err) } defer queryResultBuffer.Release() // Create staging buffer for CPU read - stagingBuffer := device.CreateBuffer(&wgpu.BufferDescriptor{ - Usage: gputypes.BufferUsageMapRead | gputypes.BufferUsageCopyDst, + stagingBuffer, err := device.CreateBuffer(&wgpu.BufferDescriptor{ + Usage: wgpu.BufferUsageMapRead | wgpu.BufferUsageCopyDst, Size: queryResultSize, }) - if stagingBuffer == nil { - return fmt.Errorf("failed to create staging buffer") + if err != nil { + return fmt.Errorf("create staging buffer: %w", err) } defer stagingBuffer.Release() @@ -117,15 +117,15 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { } } ` - shader := device.CreateShaderModuleWGSL(shaderCode) - if shader == nil { - return fmt.Errorf("failed to create shader") + shader, err := device.CreateShaderModuleWGSL(shaderCode) + if err != nil { + return fmt.Errorf("create shader: %w", err) } defer shader.Release() - pipeline := device.CreateComputePipelineSimple(nil, shader, "main") - if pipeline == nil { - return fmt.Errorf("failed to create pipeline") + pipeline, err := device.CreateComputePipelineSimple(nil, shader, "main") + if err != nil { + return fmt.Errorf("create pipeline: %w", err) } defer pipeline.Release() @@ -133,12 +133,12 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { const numElements = 1024 * 1024 bufferSize := uint64(numElements * 4) - dataBuffer := device.CreateBuffer(&wgpu.BufferDescriptor{ - Usage: gputypes.BufferUsageStorage, + dataBuffer, err := device.CreateBuffer(&wgpu.BufferDescriptor{ + Usage: wgpu.BufferUsageStorage, Size: bufferSize, }) - if dataBuffer == nil { - return fmt.Errorf("failed to create data buffer") + if err != nil { + return fmt.Errorf("create data buffer: %w", err) } defer dataBuffer.Release() @@ -146,25 +146,28 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { bindGroupLayout := pipeline.GetBindGroupLayout(0) defer bindGroupLayout.Release() - bindGroup := device.CreateBindGroupSimple(bindGroupLayout, []wgpu.BindGroupEntry{ + bindGroup, err := device.CreateBindGroupSimple(bindGroupLayout, []wgpu.BindGroupEntry{ wgpu.BufferBindingEntry(0, dataBuffer, 0, bufferSize), }) - if bindGroup == nil { - return fmt.Errorf("failed to create bind group") + if err != nil { + return fmt.Errorf("create bind group: %w", err) } defer bindGroup.Release() // Record commands with timestamps - encoder := device.CreateCommandEncoder(nil) - if encoder == nil { - return fmt.Errorf("failed to create command encoder") + encoder, err := device.CreateCommandEncoder(nil) + if err != nil { + return fmt.Errorf("create command encoder: %w", err) } // Write start timestamp encoder.WriteTimestamp(querySet, 0) // Execute compute pass - pass := encoder.BeginComputePass(nil) + pass, err := encoder.BeginComputePass(nil) + if err != nil { + return fmt.Errorf("begin compute pass: %w", err) + } pass.SetPipeline(pipeline) pass.SetBindGroup(0, bindGroup, nil) pass.DispatchWorkgroups(numElements/256, 1, 1) @@ -180,19 +183,30 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { // Copy to staging buffer encoder.CopyBufferToBuffer(queryResultBuffer, 0, stagingBuffer, 0, queryResultSize) - cmdBuffer := encoder.Finish(nil) + cmdBuffer, err := encoder.Finish() + if err != nil { + return fmt.Errorf("finish encoder: %w", err) + } encoder.Release() - queue.Submit(cmdBuffer) + if _, err = queue.Submit(cmdBuffer); err != nil { + return fmt.Errorf("submit: %w", err) + } cmdBuffer.Release() // Wait for GPU device.Poll(true) // Map staging buffer - if err := stagingBuffer.MapAsync(device, wgpu.MapModeRead, 0, queryResultSize); err != nil { + mapPending, err := stagingBuffer.MapAsync(wgpu.MapModeRead, 0, queryResultSize) + if err != nil { return fmt.Errorf("map staging buffer: %w", err) } + if werr := mapPending.Wait(context.Background()); werr != nil { + mapPending.Release() + return fmt.Errorf("map staging buffer wait: %w", werr) + } + mapPending.Release() // Read timestamp values ptr := stagingBuffer.GetMappedRange(0, queryResultSize) @@ -206,13 +220,12 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { stagingBuffer.Unmap() - // Calculate elapsed ticks + // Calculate elapsed ticks. // Note: To convert to nanoseconds, you need the timestamp period - // from the adapter (typically 1 ns/tick, but varies by GPU) + // from the adapter (typically 1 ns/tick, but varies by GPU). elapsedTicks := endTimestamp - startTimestamp - // Assume 1 ns/tick (common on most GPUs) - // For accurate conversion, use adapter.GetTimestampPeriod() if available + // Assume 1 ns/tick (common on most GPUs). const assumedPeriodNs = 1.0 elapsedNs := float64(elapsedTicks) * assumedPeriodNs @@ -248,15 +261,15 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { } } ` - shader := device.CreateShaderModuleWGSL(shaderCode) - if shader == nil { - return fmt.Errorf("failed to create shader") + shader, err := device.CreateShaderModuleWGSL(shaderCode) + if err != nil { + return fmt.Errorf("create shader: %w", err) } defer shader.Release() - pipeline := device.CreateComputePipelineSimple(nil, shader, "main") - if pipeline == nil { - return fmt.Errorf("failed to create pipeline") + pipeline, err := device.CreateComputePipelineSimple(nil, shader, "main") + if err != nil { + return fmt.Errorf("create pipeline: %w", err) } defer pipeline.Release() @@ -264,12 +277,12 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { const numElements = 1024 * 1024 bufferSize := uint64(numElements * 4) - buffer := device.CreateBuffer(&wgpu.BufferDescriptor{ - Usage: gputypes.BufferUsageStorage, + buffer, err := device.CreateBuffer(&wgpu.BufferDescriptor{ + Usage: wgpu.BufferUsageStorage, Size: bufferSize, }) - if buffer == nil { - return fmt.Errorf("failed to create buffer") + if err != nil { + return fmt.Errorf("create buffer: %w", err) } defer buffer.Release() @@ -277,33 +290,41 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { bindGroupLayout := pipeline.GetBindGroupLayout(0) defer bindGroupLayout.Release() - bindGroup := device.CreateBindGroupSimple(bindGroupLayout, []wgpu.BindGroupEntry{ + bindGroup, err := device.CreateBindGroupSimple(bindGroupLayout, []wgpu.BindGroupEntry{ wgpu.BufferBindingEntry(0, buffer, 0, bufferSize), }) - if bindGroup == nil { - return fmt.Errorf("failed to create bind group") + if err != nil { + return fmt.Errorf("create bind group: %w", err) } defer bindGroup.Release() // Time the GPU work with CPU timer start := time.Now() - encoder := device.CreateCommandEncoder(nil) - if encoder == nil { - return fmt.Errorf("failed to create command encoder") + encoder, err := device.CreateCommandEncoder(nil) + if err != nil { + return fmt.Errorf("create command encoder: %w", err) } - pass := encoder.BeginComputePass(nil) + pass, err := encoder.BeginComputePass(nil) + if err != nil { + return fmt.Errorf("begin compute pass: %w", err) + } pass.SetPipeline(pipeline) pass.SetBindGroup(0, bindGroup, nil) pass.DispatchWorkgroups(numElements/256, 1, 1) pass.End() pass.Release() - cmdBuffer := encoder.Finish(nil) + cmdBuffer, err := encoder.Finish() + if err != nil { + return fmt.Errorf("finish encoder: %w", err) + } encoder.Release() - queue.Submit(cmdBuffer) + if _, err = queue.Submit(cmdBuffer); err != nil { + return fmt.Errorf("submit: %w", err) + } cmdBuffer.Release() // Wait for GPU to complete diff --git a/examples/triangle/main.go b/examples/triangle/main.go index 0d588e5..14cc5af 100644 --- a/examples/triangle/main.go +++ b/examples/triangle/main.go @@ -10,7 +10,6 @@ import ( "unsafe" "github.com/go-webgpu/webgpu/wgpu" - "github.com/gogpu/gputypes" "golang.org/x/sys/windows" ) @@ -266,7 +265,7 @@ func (app *App) initWebGPU() error { app.device = device // Get queue - app.queue = device.GetQueue() + app.queue = device.Queue() // Create surface surface, err := inst.CreateSurfaceFromWindowsHWND(uintptr(app.hinstance), uintptr(app.hwnd)) @@ -280,14 +279,13 @@ func (app *App) initWebGPU() error { // configureSurface configures the surface for rendering. func (app *App) configureSurface() error { - app.surface.Configure(&wgpu.SurfaceConfiguration{ - Device: app.device, - Format: gputypes.TextureFormatBGRA8Unorm, - Usage: gputypes.TextureUsageRenderAttachment, + _ = app.surface.Configure(app.device, &wgpu.SurfaceConfiguration{ + Format: wgpu.TextureFormatBGRA8Unorm, + Usage: wgpu.TextureUsageRenderAttachment, Width: app.width, Height: app.height, - AlphaMode: gputypes.CompositeAlphaModeOpaque, - PresentMode: gputypes.PresentModeFifo, + AlphaMode: wgpu.CompositeAlphaModeOpaque, + PresentMode: wgpu.PresentModeFifo, }) app.needsRecreate = false return nil @@ -296,21 +294,21 @@ func (app *App) configureSurface() error { // createPipeline creates the render pipeline. func (app *App) createPipeline() error { // Create shader module - shader := app.device.CreateShaderModuleWGSL(shaderSource) - if shader == nil { - return fmt.Errorf("failed to create shader module") + shader, err := app.device.CreateShaderModuleWGSL(shaderSource) + if err != nil { + return fmt.Errorf("create shader module: %w", err) } defer shader.Release() // Create render pipeline - pipeline := app.device.CreateRenderPipelineSimple( + pipeline, err := app.device.CreateRenderPipelineSimple( nil, // auto layout shader, "vs_main", shader, "fs_main", - gputypes.TextureFormatBGRA8Unorm, + wgpu.TextureFormatBGRA8Unorm, ) - if pipeline == nil { - return fmt.Errorf("failed to create render pipeline") + if err != nil { + return fmt.Errorf("create render pipeline: %w", err) } app.pipeline = pipeline @@ -331,7 +329,7 @@ func (app *App) releasePreviousFrame() { // acquireSurfaceTexture gets the current surface texture. func (app *App) acquireSurfaceTexture() error { - surfaceTex, err := app.surface.GetCurrentTexture() + surfaceTex, _, err := app.surface.GetCurrentTexture() if err != nil { // Handle common surface errors if err == wgpu.ErrSurfaceLost || err == wgpu.ErrSurfaceNeedsReconfigure { @@ -343,9 +341,9 @@ func (app *App) acquireSurfaceTexture() error { app.surfaceTex = surfaceTex // Create texture view - view := surfaceTex.Texture.CreateView(nil) - if view == nil { - return fmt.Errorf("failed to create texture view") + view, err := surfaceTex.Texture.CreateView(nil) + if err != nil { + return fmt.Errorf("create texture view: %w", err) } app.surfaceTexView = view return nil @@ -353,12 +351,12 @@ func (app *App) acquireSurfaceTexture() error { // renderTriangle encodes the triangle rendering commands. func (app *App) renderTriangle(encoder *wgpu.CommandEncoder, view *wgpu.TextureView) error { - pass := encoder.BeginRenderPass(&wgpu.RenderPassDescriptor{ + pass, err := encoder.BeginRenderPass(&wgpu.RenderPassDescriptor{ Label: "Triangle Render Pass", ColorAttachments: []wgpu.RenderPassColorAttachment{{ View: view, - LoadOp: gputypes.LoadOpClear, - StoreOp: gputypes.StoreOpStore, + LoadOp: wgpu.LoadOpClear, + StoreOp: wgpu.StoreOpStore, ClearValue: wgpu.Color{ R: 0.1, G: 0.2, @@ -367,8 +365,8 @@ func (app *App) renderTriangle(encoder *wgpu.CommandEncoder, view *wgpu.TextureV }, }}, }) - if pass == nil { - return fmt.Errorf("failed to begin render pass") + if err != nil { + return fmt.Errorf("begin render pass: %w", err) } defer pass.Release() @@ -396,9 +394,9 @@ func (app *App) render() error { } // Create command encoder - encoder := app.device.CreateCommandEncoder(nil) - if encoder == nil { - return fmt.Errorf("failed to create command encoder") + encoder, err := app.device.CreateCommandEncoder(nil) + if err != nil { + return fmt.Errorf("create command encoder: %w", err) } defer encoder.Release() @@ -408,15 +406,17 @@ func (app *App) render() error { } // Finish encoding - cmdBuffer := encoder.Finish(nil) - if cmdBuffer == nil { - return fmt.Errorf("failed to finish command encoder") + cmdBuffer, err := encoder.Finish() + if err != nil { + return fmt.Errorf("finish command encoder: %w", err) } defer cmdBuffer.Release() // Submit commands and present - app.queue.Submit(cmdBuffer) - app.surface.Present() + if _, err = app.queue.Submit(cmdBuffer); err != nil { + return fmt.Errorf("submit: %w", err) + } + _ = app.surface.Present() return nil } diff --git a/scripts/setup-labels.sh b/scripts/setup-labels.sh new file mode 100644 index 0000000..ae1996f --- /dev/null +++ b/scripts/setup-labels.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# Setup GitHub labels for go-webgpu/webgpu +# Run: bash scripts/setup-labels.sh + +set -e + +echo "Creating labels for go-webgpu/webgpu..." + +# Priority +gh label create "priority: critical" --color "b60205" --description "Release blocker, security, data loss" --force +gh label create "priority: high" --color "d93f0b" --description "Important for next release" --force +gh label create "priority: medium" --color "fbca04" --description "Normal priority" --force +gh label create "priority: low" --color "fef2c0" --description "Backlog, nice to have" --force + +# Type +gh label create "type: bug" --color "d73a4a" --description "Something isn't working" --force +gh label create "type: feature" --color "a2eeef" --description "New capability" --force +gh label create "type: enhancement" --color "1d76db" --description "Improve existing feature" --force +gh label create "type: docs" --color "0e8a16" --description "Documentation only" --force +gh label create "type: refactor" --color "5319e7" --description "Code cleanup, no behavior change" --force +gh label create "type: performance" --color "ff7619" --description "Speed/memory improvement" --force +gh label create "type: security" --color "b60205" --description "Security vulnerability" --force +gh label create "type: test" --color "c2e0c6" --description "Test coverage" --force + +# Status +gh label create "status: triage" --color "d4c5f9" --description "Needs initial review" --force +gh label create "status: confirmed" --color "0052cc" --description "Verified, ready for work" --force +gh label create "status: in-progress" --color "fbca04" --description "Actively being worked on" --force +gh label create "status: blocked" --color "e11d21" --description "Waiting on external dependency" --force +gh label create "status: needs-info" --color "E2A1C2" --description "Awaiting reporter response" --force +gh label create "status: review" --color "6f42c1" --description "In code review" --force + +# Resolution +gh label create "~duplicate" --color "cfd3d7" --description "Duplicate of another issue" --force +gh label create "~wontfix" --color "ffffff" --description "Will not implement" --force +gh label create "~invalid" --color "e4e669" --description "Not reproducible or invalid" --force +gh label create "~stale" --color "ededed" --description "Closed due to inactivity" --force + +# Effort +gh label create "effort: 1" --color "c2e0c6" --description "Trivial, < 1 hour" --force +gh label create "effort: 2" --color "bfdadc" --description "Small, 1-4 hours" --force +gh label create "effort: 3" --color "c5def5" --description "Medium, ~1 day" --force +gh label create "effort: 5" --color "d4c5f9" --description "Large, 2-3 days" --force +gh label create "effort: 8" --color "f9d0c4" --description "Very large, ~1 week" --force +gh label create "effort: 13" --color "e99695" --description "Epic, 2+ weeks (split it)" --force + +# Contributor +gh label create "good first issue" --color "7057ff" --description "Good for newcomers" --force +gh label create "help wanted" --color "008672" --description "Extra attention needed" --force +gh label create "mentor available" --color "0e8a16" --description "Maintainer will mentor" --force + +# Area (repo-specific) +gh label create "area: wgpu" --color "0052cc" --description "wgpu/ package (core bindings)" --force +gh label create "area: examples" --color "0052cc" --description "examples/ directory" --force +gh label create "area: ffi" --color "0052cc" --description "goffi integration, FFI issues" --force +gh label create "area: types" --color "0052cc" --description "Type definitions, gputypes" --force +gh label create "area: build" --color "0052cc" --description "Build, CI/CD, releases" --force + +# Platform (repo-specific) +gh label create "os: windows" --color "006b75" --description "Windows-specific" --force +gh label create "os: linux" --color "006b75" --description "Linux-specific" --force +gh label create "os: macos" --color "006b75" --description "macOS-specific" --force +gh label create "upstream" --color "d93f0b" --description "Depends on wgpu-native/goffi" --force + +echo "Done! Created 40 labels." diff --git a/wgpu/abi_test.go b/wgpu/abi_test.go new file mode 100644 index 0000000..2ae4ea3 --- /dev/null +++ b/wgpu/abi_test.go @@ -0,0 +1,944 @@ +package wgpu + +// abi_test.go — ABI validation tests for wgpu-native v29 migration. +// +// These tests verify that Go struct layouts and enum values match +// the C ABI defined in wgpu-native v29 webgpu.h and wgpu.h. +// +// All tests: +// - Require no GPU +// - Use only unsafe package for layout queries +// - Are CI-safe: go test -run "TestStruct|TestEnum|TestGputypes|TestWire" ./wgpu/... +// +// v29 BREAKING CHANGES verified here: +// - WGPULimits gained nextInChain as first field +// - MinUniform/StorageBufferOffsetAlignment moved after MaxStorageBufferBindingSize +// - WGPUStatus Success=0x01 (was 0x00 in v27) +// - WGPUVertexAttribute gained nextInChain (Go wire struct does NOT have it — known gap) +// - WGPUBindGroupLayoutEntry gained bindingArraySize (Go wire struct does NOT have it — known gap) +// - WGPUPassTimestampWrites gained nextInChain + +import ( + "testing" + "unsafe" + + "github.com/gogpu/gputypes" +) + +// ============================================================================= +// TestABIStructSizes — verify Go struct sizes match expected C ABI sizes on 64-bit. +// Expected values derived from wgpu-native v29 webgpu.h and wgpu.h. +// ============================================================================= + +func TestABIStructSizes(t *testing.T) { + // ptr = 8, uint32 = 4, uint64 = 8, float64 = 8, WGPUStringView = 16, + // WGPUBool = uint32 = 4, WGPUFlags = uint64 = 8, enum = uint32 = 4 + + tests := []struct { + name string + got uintptr + expected uintptr + }{ + // Core helper types + {"StringView", unsafe.Sizeof(StringView{}), 16}, + {"ChainedStruct", unsafe.Sizeof(ChainedStruct{}), 16}, + {"Color", unsafe.Sizeof(Color{}), 32}, + + // gputypes pass-through (must match C sizes) + {"gputypes.Extent3D", unsafe.Sizeof(gputypes.Extent3D{}), 12}, + {"gputypes.Origin3D", unsafe.Sizeof(gputypes.Origin3D{}), 12}, + + // Instance-level structs + // instanceDescriptorWire: nextInChain(8)+reqFeatureCount(8)+reqFeatures(8)+reqLimits(8) = 32 + {"instanceDescriptorWire", unsafe.Sizeof(instanceDescriptorWire{}), 32}, + // InstanceLimits: nextInChain(8)+timedWaitAnyMaxCount(8) = 16 + {"InstanceLimits", unsafe.Sizeof(InstanceLimits{}), 16}, + + // Adapter-level structs + // requestAdapterOptionsWire: nextInChain(8)+featureLevel(4)+powerPreference(4)+ + // forceFallbackAdapter(4)+backendType(4)+compatibleSurface(8) = 32 + {"requestAdapterOptionsWire", unsafe.Sizeof(requestAdapterOptionsWire{}), 32}, + + // limitsWire: nextInChain(8) + 14×uint32(56) + 2×uint64(16) + 2×uint32(8) + + // uint32+uint64+9×uint32 + maxImmediateSize(4) = 152 + // Exact layout: see TestStructFieldOffsets for field-level verification. + {"limitsWire", unsafe.Sizeof(limitsWire{}), 152}, + + // AdapterInfo: nextInChain(8)+vendor(16)+architecture(16)+device(16)+ + // description(16)+backendType(4)+adapterType(4)+vendorID(4)+deviceID(4)+ + // subgroupMinSize(4)+subgroupMaxSize(4) = 96 + {"AdapterInfo", unsafe.Sizeof(AdapterInfo{}), 96}, + + // Buffer + // bufferDescriptorWire: nextInChain(8)+label(16)+usage(8)+size(8)+mappedAtCreation(4)+pad(4) = 48 + {"bufferDescriptorWire", unsafe.Sizeof(bufferDescriptorWire{}), 48}, + + // Device-level structs + // QueueDescriptor: nextInChain(8)+label(16) = 24 + {"QueueDescriptor", unsafe.Sizeof(QueueDescriptor{}), 24}, + // DeviceLostCallbackInfo: nextInChain(8)+mode(4)+pad(4)+callback(8)+ud1(8)+ud2(8) = 40 + {"DeviceLostCallbackInfo", unsafe.Sizeof(DeviceLostCallbackInfo{}), 40}, + // UncapturedErrorCallbackInfo: nextInChain(8)+callback(8)+ud1(8)+ud2(8) = 32 + {"UncapturedErrorCallbackInfo", unsafe.Sizeof(UncapturedErrorCallbackInfo{}), 32}, + + // Texture + // samplerDescriptorWire: nextInChain(8)+label(16)+addressModeU(4)+addressModeV(4)+ + // addressModeW(4)+magFilter(4)+minFilter(4)+mipmapFilter(4)+ + // lodMinClamp(4)+lodMaxClamp(4)+compare(4)+maxAnisotropy(2)+pad(2) = 64 + {"samplerDescriptorWire", unsafe.Sizeof(samplerDescriptorWire{}), 64}, + + // Pipeline + // pipelineLayoutDescriptorWire: nextInChain(8)+label(16)+bindGroupLayoutCount(8)+ + // bindGroupLayouts(8)+immediateSize(4)+pad(4) = 48 + {"pipelineLayoutDescriptorWire", unsafe.Sizeof(pipelineLayoutDescriptorWire{}), 48}, + // ProgrammableStageDescriptor: nextInChain(8)+module(8)+entryPoint(16)+ + // constantCount(8)+constants(8) = 48 + {"ProgrammableStageDescriptor", unsafe.Sizeof(ProgrammableStageDescriptor{}), 48}, + // computePipelineDescriptorWire: nextInChain(8)+label(16)+layout(8)+compute(48) = 80 + {"computePipelineDescriptorWire", unsafe.Sizeof(computePipelineDescriptorWire{}), 80}, + + // BindGroup types + // bindGroupEntryWire: nextInChain(8)+binding(4)+pad(4)+buffer(8)+offset(8)+size(8)+ + // sampler(8)+textureView(8) = 56 + {"bindGroupEntryWire", unsafe.Sizeof(bindGroupEntryWire{}), 56}, + + // Render pipeline types + // BlendComponent: operation(4)+srcFactor(4)+dstFactor(4) = 12 + {"BlendComponent", unsafe.Sizeof(BlendComponent{}), 12}, + // BlendState: color(12)+alpha(12) = 24 + {"BlendState", unsafe.Sizeof(BlendState{}), 24}, + + // Native extensions + // NativeLimits: chain(16)+maxImmediateSize(4)+maxNonSamplerBindings(4)+ + // maxBindingArrayElementsPerShaderStage(4)+pad(4) = 32 + {"NativeLimits", unsafe.Sizeof(NativeLimits{}), 32}, + // PipelineLayoutExtras: chain(16)+immediateDataSize(4)+pad(4) = 24 + {"PipelineLayoutExtras", unsafe.Sizeof(PipelineLayoutExtras{}), 24}, + + // Wire structs (FFI-compatible internal structs) + // passTimestampWrites: nextInChain(8)+querySet(8)+beginIndex(4)+endIndex(4) = 24 + {"passTimestampWrites (wire)", unsafe.Sizeof(passTimestampWrites{}), 24}, + // renderPassColorAttachment: nextInChain(8)+view(8)+depthSlice(4)+pad(4)+ + // resolveTarget(8)+loadOp(4)+storeOp(4)+clearValue(32) = 72 + {"renderPassColorAttachment (wire)", unsafe.Sizeof(renderPassColorAttachment{}), 72}, + // vertexBufferLayoutWire: nextInChain(8)+stepMode(4)+pad(4)+arrayStride(8)+ + // attributeCount(8)+attributes(8) = 40 + {"vertexBufferLayoutWire", unsafe.Sizeof(vertexBufferLayoutWire{}), 40}, + // colorTargetStateWire: nextInChain(8)+format(4)+pad(4)+blend(8)+writeMask(8) = 32 + {"colorTargetStateWire (wire)", unsafe.Sizeof(colorTargetStateWire{}), 32}, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + if tt.got != tt.expected { + t.Errorf("sizeof(%s) = %d, want %d (delta: %+d)", + tt.name, tt.got, tt.expected, int(tt.got)-int(tt.expected)) + } + }) + } +} + +// ============================================================================= +// TestABIStructFieldOffsets — verify critical field offsets in FFI structs. +// Especially important for v29 structs where field order changed. +// ============================================================================= + +func TestABIStructFieldOffsets(t *testing.T) { + t.Run("limitsWire", func(t *testing.T) { + // limitsWire is the FFI-compatible C struct for WGPULimits in wgpu-native v29. + // v29 BREAKING: nextInChain added as first field. + // v29 BREAKING: minUniform/StorageBufferOffsetAlignment moved + // after maxStorageBufferBindingSize (were at end in v27). + // v29 NEW: maxImmediateSize added as last field. + // + // Expected layout (all uint32 unless noted): + // offset 0: nextInChain (uintptr/8) + // offset 8: maxTextureDimension1D (uint32) + // offset 12: maxTextureDimension2D (uint32) + // offset 16: maxTextureDimension3D (uint32) + // offset 20: maxTextureArrayLayers (uint32) + // offset 24: maxBindGroups (uint32) + // offset 28: maxBindGroupsPlusVertexBuffers (uint32) + // offset 32: maxBindingsPerBindGroup (uint32) + // offset 36: maxDynamicUniformBuffersPerPipelineLayout (uint32) + // offset 40: maxDynamicStorageBuffersPerPipelineLayout (uint32) + // offset 44: maxSampledTexturesPerShaderStage (uint32) + // offset 48: maxSamplersPerShaderStage (uint32) + // offset 52: maxStorageBuffersPerShaderStage (uint32) + // offset 56: maxStorageTexturesPerShaderStage (uint32) + // offset 60: maxUniformBuffersPerShaderStage (uint32) [last of 14 uint32s] + // --- padding to 8-byte align uint64 --- + // offset 64: maxUniformBufferBindingSize (uint64) + // offset 72: maxStorageBufferBindingSize (uint64) + // --- v29 MOVED here (were at end in v27): --- + // offset 80: minUniformBufferOffsetAlignment (uint32) + // offset 84: minStorageBufferOffsetAlignment (uint32) + // offset 88: maxVertexBuffers (uint32) + // --- padding to 8-byte align uint64 --- + // offset 96: maxBufferSize (uint64) + // offset 104: maxVertexAttributes (uint32) + // offset 108: maxVertexBufferArrayStride (uint32) + // offset 112: maxInterStageShaderVariables (uint32) + // offset 116: maxColorAttachments (uint32) + // offset 120: maxColorAttachmentBytesPerSample (uint32) + // offset 124: maxComputeWorkgroupStorageSize (uint32) + // offset 128: maxComputeInvocationsPerWorkgroup (uint32) + // offset 132: maxComputeWorkgroupSizeX (uint32) + // offset 136: maxComputeWorkgroupSizeY (uint32) + // offset 140: maxComputeWorkgroupSizeZ (uint32) + // offset 144: maxComputeWorkgroupsPerDimension (uint32) + // offset 148: maxImmediateSize (uint32) [NEW v29] + // total: 152 bytes + + var l limitsWire + offsets := []struct { + name string + got uintptr + expected uintptr + }{ + {"NextInChain", unsafe.Offsetof(l.NextInChain), 0}, + {"MaxTextureDimension1D", unsafe.Offsetof(l.MaxTextureDimension1D), 8}, + {"MaxTextureDimension2D", unsafe.Offsetof(l.MaxTextureDimension2D), 12}, + {"MaxTextureDimension3D", unsafe.Offsetof(l.MaxTextureDimension3D), 16}, + {"MaxTextureArrayLayers", unsafe.Offsetof(l.MaxTextureArrayLayers), 20}, + {"MaxBindGroups", unsafe.Offsetof(l.MaxBindGroups), 24}, + {"MaxBindGroupsPlusVertexBuffers", unsafe.Offsetof(l.MaxBindGroupsPlusVertexBuffers), 28}, + {"MaxBindingsPerBindGroup", unsafe.Offsetof(l.MaxBindingsPerBindGroup), 32}, + {"MaxDynamicUniformBuffersPerPipelineLayout", unsafe.Offsetof(l.MaxDynamicUniformBuffersPerPipelineLayout), 36}, + {"MaxDynamicStorageBuffersPerPipelineLayout", unsafe.Offsetof(l.MaxDynamicStorageBuffersPerPipelineLayout), 40}, + {"MaxSampledTexturesPerShaderStage", unsafe.Offsetof(l.MaxSampledTexturesPerShaderStage), 44}, + {"MaxSamplersPerShaderStage", unsafe.Offsetof(l.MaxSamplersPerShaderStage), 48}, + {"MaxStorageBuffersPerShaderStage", unsafe.Offsetof(l.MaxStorageBuffersPerShaderStage), 52}, + {"MaxStorageTexturesPerShaderStage", unsafe.Offsetof(l.MaxStorageTexturesPerShaderStage), 56}, + {"MaxUniformBuffersPerShaderStage", unsafe.Offsetof(l.MaxUniformBuffersPerShaderStage), 60}, + // uint64 fields at 8-byte aligned offsets + {"MaxUniformBufferBindingSize", unsafe.Offsetof(l.MaxUniformBufferBindingSize), 64}, + {"MaxStorageBufferBindingSize", unsafe.Offsetof(l.MaxStorageBufferBindingSize), 72}, + // v29: these two MOVED from end-of-struct to here + {"MinUniformBufferOffsetAlignment", unsafe.Offsetof(l.MinUniformBufferOffsetAlignment), 80}, + {"MinStorageBufferOffsetAlignment", unsafe.Offsetof(l.MinStorageBufferOffsetAlignment), 84}, + {"MaxVertexBuffers", unsafe.Offsetof(l.MaxVertexBuffers), 88}, + {"MaxBufferSize", unsafe.Offsetof(l.MaxBufferSize), 96}, + {"MaxVertexAttributes", unsafe.Offsetof(l.MaxVertexAttributes), 104}, + {"MaxVertexBufferArrayStride", unsafe.Offsetof(l.MaxVertexBufferArrayStride), 108}, + {"MaxInterStageShaderVariables", unsafe.Offsetof(l.MaxInterStageShaderVariables), 112}, + {"MaxColorAttachments", unsafe.Offsetof(l.MaxColorAttachments), 116}, + {"MaxColorAttachmentBytesPerSample", unsafe.Offsetof(l.MaxColorAttachmentBytesPerSample), 120}, + {"MaxComputeWorkgroupStorageSize", unsafe.Offsetof(l.MaxComputeWorkgroupStorageSize), 124}, + {"MaxComputeInvocationsPerWorkgroup", unsafe.Offsetof(l.MaxComputeInvocationsPerWorkgroup), 128}, + {"MaxComputeWorkgroupSizeX", unsafe.Offsetof(l.MaxComputeWorkgroupSizeX), 132}, + {"MaxComputeWorkgroupSizeY", unsafe.Offsetof(l.MaxComputeWorkgroupSizeY), 136}, + {"MaxComputeWorkgroupSizeZ", unsafe.Offsetof(l.MaxComputeWorkgroupSizeZ), 140}, + {"MaxComputeWorkgroupsPerDimension", unsafe.Offsetof(l.MaxComputeWorkgroupsPerDimension), 144}, + // v29 NEW field + {"MaxImmediateSize", unsafe.Offsetof(l.MaxImmediateSize), 148}, + } + for _, o := range offsets { + o := o + t.Run(o.name, func(t *testing.T) { + if o.got != o.expected { + t.Errorf("offsetof(Limits.%s) = %d, want %d", + o.name, o.got, o.expected) + } + }) + } + }) + + t.Run("instanceDescriptorWire", func(t *testing.T) { + // nextInChain(0)+requiredFeatureCount(8)+requiredFeatures(16)+requiredLimits(24) + var d instanceDescriptorWire + offsets := []struct { + name string + got uintptr + expected uintptr + }{ + {"NextInChain", unsafe.Offsetof(d.NextInChain), 0}, + {"RequiredFeatureCount", unsafe.Offsetof(d.RequiredFeatureCount), 8}, + {"RequiredFeatures", unsafe.Offsetof(d.RequiredFeatures), 16}, + {"RequiredLimits", unsafe.Offsetof(d.RequiredLimits), 24}, + } + for _, o := range offsets { + o := o + t.Run(o.name, func(t *testing.T) { + if o.got != o.expected { + t.Errorf("offsetof(instanceDescriptorWire.%s) = %d, want %d", + o.name, o.got, o.expected) + } + }) + } + }) + + t.Run("requestAdapterOptionsWire", func(t *testing.T) { + // nextInChain(0)+featureLevel(8)+powerPreference(12)+ + // forceFallbackAdapter(16)+backendType(20)+compatibleSurface(24) = 32 + var o requestAdapterOptionsWire + offsets := []struct { + name string + got uintptr + expected uintptr + }{ + {"NextInChain", unsafe.Offsetof(o.NextInChain), 0}, + {"FeatureLevel", unsafe.Offsetof(o.FeatureLevel), 8}, + {"PowerPreference", unsafe.Offsetof(o.PowerPreference), 12}, + {"ForceFallbackAdapter", unsafe.Offsetof(o.ForceFallbackAdapter), 16}, + {"BackendType", unsafe.Offsetof(o.BackendType), 20}, + {"CompatibleSurface", unsafe.Offsetof(o.CompatibleSurface), 24}, + } + for _, of := range offsets { + of := of + t.Run(of.name, func(t *testing.T) { + if of.got != of.expected { + t.Errorf("offsetof(requestAdapterOptionsWire.%s) = %d, want %d", + of.name, of.got, of.expected) + } + }) + } + }) + + t.Run("AdapterInfo", func(t *testing.T) { + // nextInChain(0)+vendor(8)+architecture(24)+device(40)+description(56)+ + // backendType(72)+adapterType(76)+vendorID(80)+deviceID(84)+ + // subgroupMinSize(88)+subgroupMaxSize(92) = 96 + var ai AdapterInfo + offsets := []struct { + name string + got uintptr + expected uintptr + }{ + {"NextInChain", unsafe.Offsetof(ai.NextInChain), 0}, + {"Vendor", unsafe.Offsetof(ai.Vendor), 8}, + {"Architecture", unsafe.Offsetof(ai.Architecture), 24}, + {"Device", unsafe.Offsetof(ai.Device), 40}, + {"Description", unsafe.Offsetof(ai.Description), 56}, + {"BackendType", unsafe.Offsetof(ai.BackendType), 72}, + {"AdapterType", unsafe.Offsetof(ai.AdapterType), 76}, + {"VendorID", unsafe.Offsetof(ai.VendorID), 80}, + {"DeviceID", unsafe.Offsetof(ai.DeviceID), 84}, + {"SubgroupMinSize", unsafe.Offsetof(ai.SubgroupMinSize), 88}, + {"SubgroupMaxSize", unsafe.Offsetof(ai.SubgroupMaxSize), 92}, + } + for _, o := range offsets { + o := o + t.Run(o.name, func(t *testing.T) { + if o.got != o.expected { + t.Errorf("offsetof(AdapterInfo.%s) = %d, want %d", + o.name, o.got, o.expected) + } + }) + } + }) + + t.Run("bufferDescriptorWire", func(t *testing.T) { + // nextInChain(0)+label(8)+usage(24)+size(32)+mappedAtCreation(40)+pad(44) = 48 + var bd bufferDescriptorWire + offsets := []struct { + name string + got uintptr + expected uintptr + }{ + {"NextInChain", unsafe.Offsetof(bd.NextInChain), 0}, + {"Label", unsafe.Offsetof(bd.Label), 8}, + {"Usage", unsafe.Offsetof(bd.Usage), 24}, + {"Size", unsafe.Offsetof(bd.Size), 32}, + {"MappedAtCreation", unsafe.Offsetof(bd.MappedAtCreation), 40}, + } + for _, o := range offsets { + o := o + t.Run(o.name, func(t *testing.T) { + if o.got != o.expected { + t.Errorf("offsetof(bufferDescriptorWire.%s) = %d, want %d", + o.name, o.got, o.expected) + } + }) + } + }) + + t.Run("passTimestampWrites_wire", func(t *testing.T) { + // v29 NEW: nextInChain added as first field in WGPUPassTimestampWrites. + // nextInChain(0)+querySet(8)+beginningOfPassWriteIndex(16)+endOfPassWriteIndex(20) = 24 + var ptw passTimestampWrites + offsets := []struct { + name string + got uintptr + expected uintptr + }{ + {"nextInChain", unsafe.Offsetof(ptw.nextInChain), 0}, + {"querySet", unsafe.Offsetof(ptw.querySet), 8}, + {"beginningOfPassWriteIndex", unsafe.Offsetof(ptw.beginningOfPassWriteIndex), 16}, + {"endOfPassWriteIndex", unsafe.Offsetof(ptw.endOfPassWriteIndex), 20}, + } + for _, o := range offsets { + o := o + t.Run(o.name, func(t *testing.T) { + if o.got != o.expected { + t.Errorf("offsetof(passTimestampWrites.%s) = %d, want %d", + o.name, o.got, o.expected) + } + }) + } + }) + + t.Run("renderPassColorAttachment_wire", func(t *testing.T) { + // nextInChain(0)+view(8)+depthSlice(16)+pad(20)+resolveTarget(24)+ + // loadOp(32)+storeOp(36)+clearValue(40) = 72 + var rca renderPassColorAttachment + offsets := []struct { + name string + got uintptr + expected uintptr + }{ + {"nextInChain", unsafe.Offsetof(rca.nextInChain), 0}, + {"view", unsafe.Offsetof(rca.view), 8}, + {"depthSlice", unsafe.Offsetof(rca.depthSlice), 16}, + {"resolveTarget", unsafe.Offsetof(rca.resolveTarget), 24}, + {"loadOp", unsafe.Offsetof(rca.loadOp), 32}, + {"storeOp", unsafe.Offsetof(rca.storeOp), 36}, + {"clearValue", unsafe.Offsetof(rca.clearValue), 40}, + } + for _, o := range offsets { + o := o + t.Run(o.name, func(t *testing.T) { + if o.got != o.expected { + t.Errorf("offsetof(renderPassColorAttachment.%s) = %d, want %d", + o.name, o.got, o.expected) + } + }) + } + }) +} + +// ============================================================================= +// TestABIEnumValues — verify Go enum constants match v29 webgpu.h / wgpu.h values. +// ============================================================================= + +func TestABIEnumValues(t *testing.T) { + t.Run("FeatureLevel", func(t *testing.T) { + // v29 added FeatureLevel; Undefined=0x00, Compatibility=0x01, Core=0x02 + tests := []struct { + name string + got uint32 + expected uint32 + }{ + {"Undefined", uint32(FeatureLevelUndefined), 0x00000000}, + {"Compatibility", uint32(FeatureLevelCompatibility), 0x00000001}, + {"Core", uint32(FeatureLevelCore), 0x00000002}, + } + runEnumTests(t, tests) + }) + + t.Run("FeatureName", func(t *testing.T) { + tests := []struct { + name string + got uint32 + expected uint32 + }{ + {"CoreFeaturesAndLimits", uint32(FeatureNameCoreFeaturesAndLimits), 0x00000001}, + {"DepthClipControl", uint32(FeatureNameDepthClipControl), 0x00000002}, + {"Depth32FloatStencil8", uint32(FeatureNameDepth32FloatStencil8), 0x00000003}, + {"TextureCompressionBC", uint32(FeatureNameTextureCompressionBC), 0x00000004}, + {"TextureCompressionBCSliced3D", uint32(FeatureNameTextureCompressionBCSliced3D), 0x00000005}, + {"TextureCompressionETC2", uint32(FeatureNameTextureCompressionETC2), 0x00000006}, + {"TextureCompressionASTC", uint32(FeatureNameTextureCompressionASTC), 0x00000007}, + {"TextureCompressionASTCSliced3D", uint32(FeatureNameTextureCompressionASTCSliced3D), 0x00000008}, + {"TimestampQuery", uint32(FeatureNameTimestampQuery), 0x00000009}, + {"IndirectFirstInstance", uint32(FeatureNameIndirectFirstInstance), 0x0000000A}, + {"ShaderF16", uint32(FeatureNameShaderF16), 0x0000000B}, + {"RG11B10UfloatRenderable", uint32(FeatureNameRG11B10UfloatRenderable), 0x0000000C}, + {"BGRA8UnormStorage", uint32(FeatureNameBGRA8UnormStorage), 0x0000000D}, + {"Float32Filterable", uint32(FeatureNameFloat32Filterable), 0x0000000E}, + {"Float32Blendable", uint32(FeatureNameFloat32Blendable), 0x0000000F}, + {"ClipDistances", uint32(FeatureNameClipDistances), 0x00000010}, + {"DualSourceBlending", uint32(FeatureNameDualSourceBlending), 0x00000011}, + {"Subgroups", uint32(FeatureNameSubgroups), 0x00000012}, + {"TextureFormatsTier1", uint32(FeatureNameTextureFormatsTier1), 0x00000013}, + {"TextureFormatsTier2", uint32(FeatureNameTextureFormatsTier2), 0x00000014}, + {"PrimitiveIndex", uint32(FeatureNamePrimitiveIndex), 0x00000015}, + {"TextureComponentSwizzle", uint32(FeatureNameTextureComponentSwizzle), 0x00000016}, + } + runEnumTests(t, tests) + }) + + t.Run("BackendType", func(t *testing.T) { + tests := []struct { + name string + got uint32 + expected uint32 + }{ + {"Undefined", uint32(BackendTypeUndefined), 0x00000000}, + {"Null", uint32(BackendTypeNull), 0x00000001}, + {"WebGPU", uint32(BackendTypeWebGPU), 0x00000002}, + {"D3D11", uint32(BackendTypeD3D11), 0x00000003}, + {"D3D12", uint32(BackendTypeD3D12), 0x00000004}, + {"Metal", uint32(BackendTypeMetal), 0x00000005}, + {"Vulkan", uint32(BackendTypeVulkan), 0x00000006}, + {"OpenGL", uint32(BackendTypeOpenGL), 0x00000007}, + {"OpenGLES", uint32(BackendTypeOpenGLES), 0x00000008}, + } + runEnumTests(t, tests) + }) + + t.Run("AdapterType", func(t *testing.T) { + tests := []struct { + name string + got uint32 + expected uint32 + }{ + {"DiscreteGPU", uint32(AdapterTypeDiscreteGPU), 0x00000001}, + {"IntegratedGPU", uint32(AdapterTypeIntegratedGPU), 0x00000002}, + {"CPU", uint32(AdapterTypeCPU), 0x00000003}, + {"Unknown", uint32(AdapterTypeUnknown), 0x00000004}, + } + runEnumTests(t, tests) + }) + + t.Run("SurfaceGetCurrentTextureStatus", func(t *testing.T) { + // v29 BREAKING: OutOfMemory(0x06) and DeviceLost(0x07) removed; + // collapsed to single Error(0x06). Occluded is native extension (0x00030001). + tests := []struct { + name string + got uint32 + expected uint32 + }{ + {"SuccessOptimal", uint32(SurfaceGetCurrentTextureStatusSuccessOptimal), 0x00000001}, + {"SuccessSuboptimal", uint32(SurfaceGetCurrentTextureStatusSuccessSuboptimal), 0x00000002}, + {"Timeout", uint32(SurfaceGetCurrentTextureStatusTimeout), 0x00000003}, + {"Outdated", uint32(SurfaceGetCurrentTextureStatusOutdated), 0x00000004}, + {"Lost", uint32(SurfaceGetCurrentTextureStatusLost), 0x00000005}, + {"Error", uint32(SurfaceGetCurrentTextureStatusError), 0x00000006}, + {"Occluded (native)", uint32(NativeSurfaceGetCurrentTextureStatusOccluded), 0x00030001}, + } + runEnumTests(t, tests) + }) + + t.Run("RequestAdapterStatus", func(t *testing.T) { + // v29: InstanceDropped renamed to CallbackCancelled + tests := []struct { + name string + got uint32 + expected uint32 + }{ + {"Success", uint32(RequestAdapterStatusSuccess), 0x00000001}, + {"CallbackCancelled", uint32(RequestAdapterStatusCallbackCancelled), 0x00000002}, + {"Unavailable", uint32(RequestAdapterStatusUnavailable), 0x00000003}, + {"Error", uint32(RequestAdapterStatusError), 0x00000004}, + } + runEnumTests(t, tests) + }) + + t.Run("RequestDeviceStatus", func(t *testing.T) { + tests := []struct { + name string + got uint32 + expected uint32 + }{ + {"Success", uint32(RequestDeviceStatusSuccess), 0x00000001}, + {"CallbackCancelled", uint32(RequestDeviceStatusCallbackCancelled), 0x00000002}, + {"Error", uint32(RequestDeviceStatusError), 0x00000003}, + } + runEnumTests(t, tests) + }) + + t.Run("BufferMapState", func(t *testing.T) { + tests := []struct { + name string + got uint32 + expected uint32 + }{ + {"Unmapped", uint32(BufferMapStateUnmapped), 0x00000001}, + {"Pending", uint32(BufferMapStatePending), 0x00000002}, + {"Mapped", uint32(BufferMapStateMapped), 0x00000003}, + } + runEnumTests(t, tests) + }) + + t.Run("WGPUStatus", func(t *testing.T) { + // v29 BREAKING: Success changed from 0x00 to 0x01; Error from 0x01 to 0x02. + // Any code comparing against 0 for success is broken after v29. + tests := []struct { + name string + got uint32 + expected uint32 + }{ + {"Success", uint32(WGPUStatusSuccess), 0x00000001}, + {"Error", uint32(WGPUStatusError), 0x00000002}, + } + runEnumTests(t, tests) + }) + + t.Run("SType_standard", func(t *testing.T) { + tests := []struct { + name string + got uint32 + expected uint32 + }{ + {"ShaderSourceSPIRV", uint32(STypeShaderSourceSPIRV), 0x00000001}, + {"ShaderSourceWGSL", uint32(STypeShaderSourceWGSL), 0x00000002}, + {"RenderPassMaxDrawCount", uint32(STypeRenderPassMaxDrawCount), 0x00000003}, + {"SurfaceSourceMetalLayer", uint32(STypeSurfaceSourceMetalLayer), 0x00000004}, + {"SurfaceSourceWindowsHWND", uint32(STypeSurfaceSourceWindowsHWND), 0x00000005}, + {"SurfaceSourceXlibWindow", uint32(STypeSurfaceSourceXlibWindow), 0x00000006}, + {"SurfaceSourceWaylandSurface", uint32(STypeSurfaceSourceWaylandSurface), 0x00000007}, + {"SurfaceSourceAndroidNativeWindow", uint32(STypeSurfaceSourceAndroidNativeWindow), 0x00000008}, + {"SurfaceSourceXCBWindow", uint32(STypeSurfaceSourceXCBWindow), 0x00000009}, + {"SurfaceColorManagement", uint32(STypeSurfaceColorManagement), 0x0000000A}, + } + runEnumTests(t, tests) + }) + + t.Run("SType_native", func(t *testing.T) { + // Native wgpu-native extension STypes in 0x0003XXXX range + tests := []struct { + name string + got uint32 + expected uint32 + }{ + {"DeviceExtras", uint32(STypeDeviceExtras), 0x00030001}, + {"NativeLimits", uint32(STypeNativeLimits), 0x00030002}, + {"PipelineLayoutExtras", uint32(STypePipelineLayoutExtras), 0x00030003}, + {"ShaderSourceGLSL", uint32(STypeShaderSourceGLSL), 0x00030004}, + {"InstanceExtras", uint32(STypeInstanceExtras), 0x00030006}, + {"BindGroupEntryExtras", uint32(STypeBindGroupEntryExtras), 0x00030007}, + {"BindGroupLayoutEntryExtras", uint32(STypeBindGroupLayoutEntryExtras), 0x00030008}, + } + runEnumTests(t, tests) + }) + + t.Run("NativeFeature", func(t *testing.T) { + // Selected subset of NativeFeature values; all in 0x0003XXXX range + tests := []struct { + name string + got uint32 + expected uint32 + }{ + {"Immediates", uint32(NativeFeatureImmediates), 0x00030001}, + {"TextureAdapterSpecificFormatFeatures", uint32(NativeFeatureTextureAdapterSpecificFormatFeatures), 0x00030002}, + {"MultiDrawIndirectCount", uint32(NativeFeatureMultiDrawIndirectCount), 0x00030004}, + {"VertexWritableStorage", uint32(NativeFeatureVertexWritableStorage), 0x00030005}, + {"TextureBindingArray", uint32(NativeFeatureTextureBindingArray), 0x00030006}, + } + runEnumTests(t, tests) + }) + + t.Run("InstanceBackend_bitflags", func(t *testing.T) { + // InstanceBackend is uint64 bitflags + tests := []struct { + name string + got uint64 + expected uint64 + }{ + {"All (zero)", uint64(InstanceBackendAll), 0x00000000}, + {"Vulkan", uint64(InstanceBackendVulkan), 1 << 0}, + {"GL", uint64(InstanceBackendGL), 1 << 1}, + {"Metal", uint64(InstanceBackendMetal), 1 << 2}, + {"DX12", uint64(InstanceBackendDX12), 1 << 3}, + {"BrowserWebGPU", uint64(InstanceBackendBrowserWebGPU), 1 << 5}, + // BREAKING v29: Secondary = GL only (v27 had GL|DX11; DX11 removed) + {"Secondary (GL only)", uint64(InstanceBackendSecondary), 1 << 1}, + {"Primary", uint64(InstanceBackendPrimary), + (1 << 0) | (1 << 2) | (1 << 3) | (1 << 5)}, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + if tt.got != tt.expected { + t.Errorf("InstanceBackend.%s = %#x, want %#x", + tt.name, tt.got, tt.expected) + } + }) + } + }) + + t.Run("InstanceFlag_bitflags", func(t *testing.T) { + // InstanceFlag is uint64 bitflags + // BREAKING v29: Default moved from 0 to 1<<24 + tests := []struct { + name string + got uint64 + expected uint64 + }{ + {"Empty (zero)", uint64(InstanceFlagEmpty), 0x00000000}, + {"Debug", uint64(InstanceFlagDebug), 1 << 0}, + {"Validation", uint64(InstanceFlagValidation), 1 << 1}, + {"DiscardHalLabels", uint64(InstanceFlagDiscardHalLabels), 1 << 2}, + {"AllowUnderlyingNoncompliantAdapter", uint64(InstanceFlagAllowUnderlyingNoncompliantAdapter), 1 << 3}, + {"GPUBasedValidation", uint64(InstanceFlagGPUBasedValidation), 1 << 4}, + {"ValidationIndirectCall", uint64(InstanceFlagValidationIndirectCall), 1 << 5}, + {"AutomaticTimestampNormalization", uint64(InstanceFlagAutomaticTimestampNormalization), 1 << 6}, + // BREAKING: Default=1<<24 in v29 (was 0 in v27) + {"Default", uint64(InstanceFlagDefault), 1 << 24}, + {"Debugging", uint64(InstanceFlagDebugging), 1 << 25}, + {"AdvancedDebugging", uint64(InstanceFlagAdvancedDebugging), 1 << 26}, + {"WithEnv", uint64(InstanceFlagWithEnv), 1 << 27}, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + if tt.got != tt.expected { + t.Errorf("InstanceFlag.%s = %#x, want %#x", + tt.name, tt.got, tt.expected) + } + }) + } + }) +} + +// runEnumTests is a table-driven helper for uint32 enum value checks. +func runEnumTests(t *testing.T, tests []struct { + name string + got uint32 + expected uint32 +}) { + t.Helper() + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + if tt.got != tt.expected { + t.Errorf("= %#08x, want %#08x", tt.got, tt.expected) + } + }) + } +} + +// ============================================================================= +// TestABIGputypesEnumAlignment — verify gputypes enum values pass directly through +// FFI without conversion and match v29 webgpu.h constants. +// ============================================================================= + +func TestABIGputypesEnumAlignment(t *testing.T) { + t.Run("TextureFormat_common", func(t *testing.T) { + // gputypes.TextureFormat values used in textureDescriptorWire (passed directly, no conversion). + // gputypes v0.3.0 uses sequential numbering from the webgpu.h spec where + // R8Unorm=0x01 through the format list; BGRA8Unorm comes later in the sequence. + // These expected values are from gputypes v0.3.0 (github.com/gogpu/gputypes). + // They must remain stable across gputypes versions to avoid silent ABI breaks. + tests := []struct { + name string + got uint32 + expected uint32 + }{ + {"Undefined", uint32(gputypes.TextureFormatUndefined), 0x00000000}, + {"R8Unorm", uint32(gputypes.TextureFormatR8Unorm), 0x00000001}, + {"R8Snorm", uint32(gputypes.TextureFormatR8Snorm), 0x00000002}, + {"R8Uint", uint32(gputypes.TextureFormatR8Uint), 0x00000003}, + {"R8Sint", uint32(gputypes.TextureFormatR8Sint), 0x00000004}, + // gputypes v0.3.0 sequential values (after R-only, RG, RGBA formats): + {"BGRA8Unorm", uint32(gputypes.TextureFormatBGRA8Unorm), 0x0000001b}, + {"BGRA8UnormSrgb", uint32(gputypes.TextureFormatBGRA8UnormSrgb), 0x0000001c}, + {"Depth16Unorm", uint32(gputypes.TextureFormatDepth16Unorm), 0x0000002d}, + {"Depth24Plus", uint32(gputypes.TextureFormatDepth24Plus), 0x0000002e}, + {"Depth32Float", uint32(gputypes.TextureFormatDepth32Float), 0x00000030}, + } + runEnumTests(t, tests) + }) + + t.Run("TextureUsage_bitflags", func(t *testing.T) { + // gputypes.TextureUsage bitflags passed directly as uint64 in wire structs. + // Must match WGPUTextureUsageFlags in webgpu.h v29. + tests := []struct { + name string + got uint64 + expected uint64 + }{ + {"None", uint64(gputypes.TextureUsageNone), 0x0000000000000000}, + {"CopySrc", uint64(gputypes.TextureUsageCopySrc), 0x0000000000000001}, + {"CopyDst", uint64(gputypes.TextureUsageCopyDst), 0x0000000000000002}, + {"TextureBinding", uint64(gputypes.TextureUsageTextureBinding), 0x0000000000000004}, + {"StorageBinding", uint64(gputypes.TextureUsageStorageBinding), 0x0000000000000008}, + {"RenderAttachment", uint64(gputypes.TextureUsageRenderAttachment), 0x0000000000000010}, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + if tt.got != tt.expected { + t.Errorf("TextureUsage.%s = %#x, want %#x", tt.name, tt.got, tt.expected) + } + }) + } + }) + + t.Run("BufferUsage_bitflags", func(t *testing.T) { + // gputypes.BufferUsage bitflags. + // Must match WGPUBufferUsageFlags in webgpu.h v29. + tests := []struct { + name string + got uint64 + expected uint64 + }{ + {"None", uint64(gputypes.BufferUsageNone), 0x0000000000000000}, + {"MapRead", uint64(gputypes.BufferUsageMapRead), 0x0000000000000001}, + {"MapWrite", uint64(gputypes.BufferUsageMapWrite), 0x0000000000000002}, + {"CopySrc", uint64(gputypes.BufferUsageCopySrc), 0x0000000000000004}, + {"CopyDst", uint64(gputypes.BufferUsageCopyDst), 0x0000000000000008}, + {"Index", uint64(gputypes.BufferUsageIndex), 0x0000000000000010}, + {"Vertex", uint64(gputypes.BufferUsageVertex), 0x0000000000000020}, + {"Uniform", uint64(gputypes.BufferUsageUniform), 0x0000000000000040}, + {"Storage", uint64(gputypes.BufferUsageStorage), 0x0000000000000080}, + {"Indirect", uint64(gputypes.BufferUsageIndirect), 0x0000000000000100}, + {"QueryResolve", uint64(gputypes.BufferUsageQueryResolve), 0x0000000000000200}, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + if tt.got != tt.expected { + t.Errorf("BufferUsage.%s = %#x, want %#x", tt.name, tt.got, tt.expected) + } + }) + } + }) + + t.Run("ShaderStage_bitflags", func(t *testing.T) { + // gputypes.ShaderStage bitflags. + // CRITICAL: wgpu-native uses WGPUShaderStageFlags = uint64 (WGPUFlags). + // These values are widened to uint64 when placed in bindGroupLayoutEntryWire. + tests := []struct { + name string + got uint32 + expected uint32 + }{ + {"None", uint32(gputypes.ShaderStageNone), 0x00000000}, + {"Vertex", uint32(gputypes.ShaderStageVertex), 0x00000001}, + {"Fragment", uint32(gputypes.ShaderStageFragment), 0x00000002}, + {"Compute", uint32(gputypes.ShaderStageCompute), 0x00000004}, + } + runEnumTests(t, tests) + }) +} + +// ============================================================================= +// TestABIWireStructAlignment — verify wire struct (FFI-compatible) field offsets. +// Wire structs are internal types; their layout must exactly match C ABI. +// ============================================================================= + +func TestABIWireStructAlignment(t *testing.T) { + t.Run("vertexBufferLayoutWire", func(t *testing.T) { + // v29 BREAKING: nextInChain added as FIRST field in WGPUVertexBufferLayout. + // nextInChain(0)+stepMode(8)+pad(12)+arrayStride(16)+attributeCount(24)+attributes(32) = 40 + var w vertexBufferLayoutWire + offsets := []struct { + name string + got uintptr + expected uintptr + }{ + {"NextInChain", unsafe.Offsetof(w.NextInChain), 0}, + {"StepMode", unsafe.Offsetof(w.StepMode), 8}, + {"ArrayStride", unsafe.Offsetof(w.ArrayStride), 16}, + {"AttributeCount", unsafe.Offsetof(w.AttributeCount), 24}, + {"Attributes", unsafe.Offsetof(w.Attributes), 32}, + } + for _, o := range offsets { + o := o + t.Run(o.name, func(t *testing.T) { + if o.got != o.expected { + t.Errorf("offsetof(vertexBufferLayoutWire.%s) = %d, want %d", + o.name, o.got, o.expected) + } + }) + } + }) + + t.Run("vertexAttributeWire_size", func(t *testing.T) { + // v29 STATUS: WGPUVertexAttribute in C v29 has nextInChain as first field (32 bytes). + // Our vertexAttributeWire does NOT have nextInChain (24 bytes). + // + // This is a KNOWN MIGRATION GAP: + // C v29 WGPUVertexAttribute: + // nextInChain(8)+format(4)+pad(4)+offset(8)+shaderLocation(4)+pad(4) = 32 bytes + // Go vertexAttributeWire (current): + // format(4)+pad(4)+offset(8)+shaderLocation(4)+pad(4) = 24 bytes [MISSING nextInChain] + // + // TODO(v29-migration): Add nextInChain to vertexAttributeWire when upgrading to wgpu-native v29. + // Tracked in: docs/dev/kanban/blocked/0010-webgpu-headers-upgrade.md + const gotSize = unsafe.Sizeof(vertexAttributeWire{}) + const expectedCurrent = uintptr(24) // current Go wire (no nextInChain) + const expectedV29C = uintptr(32) // C v29 target (has nextInChain) + + if gotSize != expectedCurrent { + t.Errorf("sizeof(vertexAttributeWire) = %d, want %d (current Go layout)", + gotSize, expectedCurrent) + } + // Document the gap: once v29 migration is complete, this must be 32. + if gotSize == expectedV29C { + t.Log("vertexAttributeWire already matches C v29 size (32 bytes) — remove migration TODO") + } else { + t.Logf("MIGRATION GAP: vertexAttributeWire is %d bytes, C v29 target is %d bytes (missing nextInChain)", + gotSize, expectedV29C) + } + }) + + t.Run("bindGroupLayoutEntryWire_knownGap", func(t *testing.T) { + // v29 STATUS: WGPUBindGroupLayoutEntry in C v29 has bindingArraySize (uint32) + // between visibility (uint64) and buffer (bufferBindingLayoutWire). + // + // This is a KNOWN MIGRATION GAP: + // C v29 layout after visibility: + // bindingArraySize(4)+pad(4)+buffer(...)+sampler(...)+... + // Go bindGroupLayoutEntryWire (current): + // NO bindingArraySize field between visibility and buffer + // + // Impact: buffer, sampler, texture, storageTexture offsets are all shifted + // by -8 relative to C v29. This will cause incorrect binding when binding arrays + // are used (NativeFeatureTextureBindingArray). + // + // TODO(v29-migration): Add bindingArraySize uint32 + padding after Visibility + // in bindGroupLayoutEntryWire when upgrading to wgpu-native v29. + // Tracked in: docs/dev/kanban/blocked/0010-webgpu-headers-upgrade.md + + var e bindGroupLayoutEntryWire + // Verify current layout is self-consistent (no accidental regressions) + visibilityOffset := unsafe.Offsetof(e.Visibility) + bufferOffset := uintptr(unsafe.Pointer(&e.Buffer)) - uintptr(unsafe.Pointer(&e)) + + // Current: visibility at some offset, buffer directly after (no bindingArraySize gap) + // In C v29: buffer should be at visibility+8+8 = visibility+16 (bindingArraySize+pad) + // Currently buffer is at visibility+8 (just uint64 visibility, no bindingArraySize) + expectedCurrentGap := uintptr(8) // sizeof(Visibility uint64) = 8, buffer follows directly + actualGap := bufferOffset - visibilityOffset + if actualGap != expectedCurrentGap { + t.Errorf("gap(Visibility→Buffer) = %d bytes, want %d (current layout without bindingArraySize)", + actualGap, expectedCurrentGap) + } + t.Logf("MIGRATION GAP: C v29 expects gap(Visibility→Buffer)=16 bytes (bindingArraySize+pad), current Go has %d bytes", + actualGap) + }) + + t.Run("colorTargetStateWire", func(t *testing.T) { + // nextInChain(0)+format(8)+pad(12)+blend(16)+writeMask(24) = 32 + // CRITICAL: writeMask is uint64 (WGPUColorWriteMaskFlags = WGPUFlags = uint64) + var w colorTargetStateWire + offsets := []struct { + name string + got uintptr + expected uintptr + }{ + {"nextInChain", unsafe.Offsetof(w.nextInChain), 0}, + {"format", unsafe.Offsetof(w.format), 8}, + {"blend", unsafe.Offsetof(w.blend), 16}, + {"writeMask", unsafe.Offsetof(w.writeMask), 24}, + } + for _, o := range offsets { + o := o + t.Run(o.name, func(t *testing.T) { + if o.got != o.expected { + t.Errorf("offsetof(colorTargetStateWire.%s) = %d, want %d", + o.name, o.got, o.expected) + } + }) + } + }) + + t.Run("bindGroupLayoutEntryWire_visibility_uint64", func(t *testing.T) { + // CRITICAL: Visibility must be uint64 (WGPUShaderStageFlags = WGPUFlags = uint64 in wgpu-native). + // This is NOT uint32 as in the webgpu.h spec — wgpu-native uses WGPUFlags typedef. + // Verify the Visibility field size via its offset and the next field offset. + var e bindGroupLayoutEntryWire + visibilityOffset := unsafe.Offsetof(e.Visibility) + bufferOffset := uintptr(unsafe.Pointer(&e.Buffer)) - uintptr(unsafe.Pointer(&e)) + visibilitySize := bufferOffset - visibilityOffset + const expectedVisibilitySize = uintptr(8) // must be uint64 = 8 bytes + if visibilitySize != expectedVisibilitySize { + t.Errorf("sizeof(Visibility in bindGroupLayoutEntryWire) = %d, want %d (must be uint64)", + visibilitySize, expectedVisibilitySize) + } + }) +} diff --git a/wgpu/adapter.go b/wgpu/adapter.go index 13e172d..cba6165 100644 --- a/wgpu/adapter.go +++ b/wgpu/adapter.go @@ -25,13 +25,28 @@ type Future struct { } // RequestAdapterOptions configures adapter selection. +// Matches the gogpu/wgpu API for cross-project compatibility. type RequestAdapterOptions struct { + // PowerPreference indicates power consumption preference. + PowerPreference gputypes.PowerPreference + // ForceFallbackAdapter forces the use of a software adapter. + ForceFallbackAdapter bool + // CompatibleSurface, if non-nil, restricts adapter selection to those + // compatible with rendering to the given surface. + CompatibleSurface *Surface +} + +// requestAdapterOptionsWire is the FFI-compatible C-layout struct for wgpuInstanceRequestAdapter. +// v29 layout: nextInChain(8)+featureLevel(4)+powerPreference(4)+ +// +// forceFallbackAdapter(4)+backendType(4)+compatibleSurface(8) = 32 bytes. +type requestAdapterOptionsWire struct { NextInChain uintptr // *ChainedStruct FeatureLevel FeatureLevel PowerPreference gputypes.PowerPreference ForceFallbackAdapter Bool - CompatibilityMode Bool - CompatibleSurface uintptr // WGPUSurface + BackendType BackendType // v29: select specific backend + CompatibleSurface uintptr // WGPUSurface } // RequestAdapterCallbackInfo holds callback configuration. @@ -130,10 +145,20 @@ func (i *Instance) RequestAdapter(options *RequestAdapterOptions) (*Adapter, err adapterRequests[reqID] = req adapterRequestsMu.Unlock() - // Prepare options + // Convert Go-idiomatic options to wire format. var optionsPtr uintptr if options != nil { - optionsPtr = uintptr(unsafe.Pointer(options)) + var surfaceHandle uintptr + if options.CompatibleSurface != nil { + surfaceHandle = options.CompatibleSurface.handle + } + wire := requestAdapterOptionsWire{ + FeatureLevel: FeatureLevelCore, + PowerPreference: options.PowerPreference, + ForceFallbackAdapter: boolToWGPU(options.ForceFallbackAdapter), + CompatibleSurface: surfaceHandle, + } + optionsPtr = uintptr(unsafe.Pointer(&wire)) } // Prepare callback info @@ -165,6 +190,10 @@ func (i *Instance) RequestAdapter(options *RequestAdapterOptions) (*Adapter, err } return nil, &WGPUError{Op: "RequestAdapter", Message: msg} } + // Cache limits at creation time so Limits() returns value without FFI. + if req.adapter != nil { + req.adapter.limits = fetchAdapterLimits(req.adapter.handle) + } return req.adapter, nil default: // Process events to trigger callback @@ -173,6 +202,20 @@ func (i *Instance) RequestAdapter(options *RequestAdapterOptions) (*Adapter, err } } +// fetchAdapterLimits calls wgpuAdapterGetLimits and converts the wire struct to public Limits. +// Returns zero-value Limits on failure (non-fatal: limits remain valid defaults). +func fetchAdapterLimits(handle uintptr) Limits { + var wire limitsWire + status, _, _ := procAdapterGetLimits.Call( + handle, + uintptr(unsafe.Pointer(&wire)), + ) + if WGPUStatus(status) != WGPUStatusSuccess { + return Limits{} + } + return limitsFromWire(&wire) +} + // Release releases the adapter resources. func (a *Adapter) Release() { if a.handle != 0 { @@ -182,10 +225,88 @@ func (a *Adapter) Release() { } } -// Limits describes resource limits for an adapter or device. -// This corresponds to WGPULimits in webgpu.h (no nextInChain field). -// Used inside SupportedLimits which wraps it with nextInChain for FFI calls. +// Limits describes GPU resource limits for an adapter or device. +// +// This type matches the gogpu/wgpu API for cross-project compatibility. +// Limits are cached at creation time (RequestAdapter/RequestDevice) and +// returned by value — no FFI call is made on each access. +// +// Note: wgpu-native-specific fields (MaxImmediateSize, MaxNonSamplerBindings) +// are not exposed here. Use NativeLimits for native extensions. type Limits struct { + // MaxTextureDimension1D is the maximum 1D texture dimension. + MaxTextureDimension1D uint32 + // MaxTextureDimension2D is the maximum 2D texture dimension. + MaxTextureDimension2D uint32 + // MaxTextureDimension3D is the maximum 3D texture dimension. + MaxTextureDimension3D uint32 + // MaxTextureArrayLayers is the maximum texture array layer count. + MaxTextureArrayLayers uint32 + // MaxBindGroups is the maximum number of bind groups. + MaxBindGroups uint32 + // MaxBindGroupsPlusVertexBuffers is the max bind groups + vertex buffers. + MaxBindGroupsPlusVertexBuffers uint32 + // MaxBindingsPerBindGroup is the max bindings per bind group. + MaxBindingsPerBindGroup uint32 + // MaxDynamicUniformBuffersPerPipelineLayout is the max dynamic uniform buffers. + MaxDynamicUniformBuffersPerPipelineLayout uint32 + // MaxDynamicStorageBuffersPerPipelineLayout is the max dynamic storage buffers. + MaxDynamicStorageBuffersPerPipelineLayout uint32 + // MaxSampledTexturesPerShaderStage is the max sampled textures per shader stage. + MaxSampledTexturesPerShaderStage uint32 + // MaxSamplersPerShaderStage is the max samplers per shader stage. + MaxSamplersPerShaderStage uint32 + // MaxStorageBuffersPerShaderStage is the max storage buffers per shader stage. + MaxStorageBuffersPerShaderStage uint32 + // MaxStorageTexturesPerShaderStage is the max storage textures per shader stage. + MaxStorageTexturesPerShaderStage uint32 + // MaxUniformBuffersPerShaderStage is the max uniform buffers per shader stage. + MaxUniformBuffersPerShaderStage uint32 + // MaxUniformBufferBindingSize is the max uniform buffer binding size in bytes. + MaxUniformBufferBindingSize uint64 + // MaxStorageBufferBindingSize is the max storage buffer binding size in bytes. + MaxStorageBufferBindingSize uint64 + // MinUniformBufferOffsetAlignment is the minimum uniform buffer offset alignment. + MinUniformBufferOffsetAlignment uint32 + // MinStorageBufferOffsetAlignment is the minimum storage buffer offset alignment. + MinStorageBufferOffsetAlignment uint32 + // MaxVertexBuffers is the max vertex buffers in a pipeline. + MaxVertexBuffers uint32 + // MaxBufferSize is the max buffer size in bytes. + MaxBufferSize uint64 + // MaxVertexAttributes is the max vertex attributes in a pipeline. + MaxVertexAttributes uint32 + // MaxVertexBufferArrayStride is the max vertex buffer array stride. + MaxVertexBufferArrayStride uint32 + // MaxInterStageShaderVariables is the max inter-stage shader variables. + MaxInterStageShaderVariables uint32 + // MaxColorAttachments is the max color attachments in a render pass. + MaxColorAttachments uint32 + // MaxColorAttachmentBytesPerSample is the max bytes per sample for color attachments. + MaxColorAttachmentBytesPerSample uint32 + // MaxComputeWorkgroupStorageSize is the max compute workgroup storage in bytes. + MaxComputeWorkgroupStorageSize uint32 + // MaxComputeInvocationsPerWorkgroup is the max compute invocations per workgroup. + MaxComputeInvocationsPerWorkgroup uint32 + // MaxComputeWorkgroupSizeX is the max compute workgroup size in X dimension. + MaxComputeWorkgroupSizeX uint32 + // MaxComputeWorkgroupSizeY is the max compute workgroup size in Y dimension. + MaxComputeWorkgroupSizeY uint32 + // MaxComputeWorkgroupSizeZ is the max compute workgroup size in Z dimension. + MaxComputeWorkgroupSizeZ uint32 + // MaxComputeWorkgroupsPerDimension is the max compute workgroups per dimension. + MaxComputeWorkgroupsPerDimension uint32 +} + +// limitsWire is the FFI-compatible C-layout struct for wgpu-native v29 WGPULimits. +// IMPORTANT: Field order must exactly match C struct layout for correct ABI. +// v29 BREAKING changes vs v27: +// - NextInChain added as FIRST field (was absent in v27 Limits) +// - MinUniformBufferOffsetAlignment and MinStorageBufferOffsetAlignment moved +// from end to after MaxStorageBufferBindingSize (before MaxVertexBuffers) +// - MaxImmediateSize added as LAST field (new in v29) +type limitsWire struct { + NextInChain uintptr // *ChainedStruct — NEW in v29 MaxTextureDimension1D uint32 MaxTextureDimension2D uint32 MaxTextureDimension3D uint32 @@ -202,6 +323,8 @@ type Limits struct { MaxUniformBuffersPerShaderStage uint32 MaxUniformBufferBindingSize uint64 MaxStorageBufferBindingSize uint64 + MinUniformBufferOffsetAlignment uint32 // MOVED: now after MaxStorageBufferBindingSize + MinStorageBufferOffsetAlignment uint32 // MOVED: now after MinUniformBufferOffsetAlignment MaxVertexBuffers uint32 MaxBufferSize uint64 MaxVertexAttributes uint32 @@ -215,34 +338,69 @@ type Limits struct { MaxComputeWorkgroupSizeY uint32 MaxComputeWorkgroupSizeZ uint32 MaxComputeWorkgroupsPerDimension uint32 - MinUniformBufferOffsetAlignment uint32 - MinStorageBufferOffsetAlignment uint32 + MaxImmediateSize uint32 // NEW in v29 (push constants replacement) } -// SupportedLimits contains adapter limits. -type SupportedLimits struct { - NextInChain uintptr // *ChainedStructOut - Limits Limits +// limitsFromWire converts a limitsWire (FFI struct with NextInChain) to public Limits. +func limitsFromWire(w *limitsWire) Limits { + return Limits{ + MaxTextureDimension1D: w.MaxTextureDimension1D, + MaxTextureDimension2D: w.MaxTextureDimension2D, + MaxTextureDimension3D: w.MaxTextureDimension3D, + MaxTextureArrayLayers: w.MaxTextureArrayLayers, + MaxBindGroups: w.MaxBindGroups, + MaxBindGroupsPlusVertexBuffers: w.MaxBindGroupsPlusVertexBuffers, + MaxBindingsPerBindGroup: w.MaxBindingsPerBindGroup, + MaxDynamicUniformBuffersPerPipelineLayout: w.MaxDynamicUniformBuffersPerPipelineLayout, + MaxDynamicStorageBuffersPerPipelineLayout: w.MaxDynamicStorageBuffersPerPipelineLayout, + MaxSampledTexturesPerShaderStage: w.MaxSampledTexturesPerShaderStage, + MaxSamplersPerShaderStage: w.MaxSamplersPerShaderStage, + MaxStorageBuffersPerShaderStage: w.MaxStorageBuffersPerShaderStage, + MaxStorageTexturesPerShaderStage: w.MaxStorageTexturesPerShaderStage, + MaxUniformBuffersPerShaderStage: w.MaxUniformBuffersPerShaderStage, + MaxUniformBufferBindingSize: w.MaxUniformBufferBindingSize, + MaxStorageBufferBindingSize: w.MaxStorageBufferBindingSize, + MinUniformBufferOffsetAlignment: w.MinUniformBufferOffsetAlignment, + MinStorageBufferOffsetAlignment: w.MinStorageBufferOffsetAlignment, + MaxVertexBuffers: w.MaxVertexBuffers, + MaxBufferSize: w.MaxBufferSize, + MaxVertexAttributes: w.MaxVertexAttributes, + MaxVertexBufferArrayStride: w.MaxVertexBufferArrayStride, + MaxInterStageShaderVariables: w.MaxInterStageShaderVariables, + MaxColorAttachments: w.MaxColorAttachments, + MaxColorAttachmentBytesPerSample: w.MaxColorAttachmentBytesPerSample, + MaxComputeWorkgroupStorageSize: w.MaxComputeWorkgroupStorageSize, + MaxComputeInvocationsPerWorkgroup: w.MaxComputeInvocationsPerWorkgroup, + MaxComputeWorkgroupSizeX: w.MaxComputeWorkgroupSizeX, + MaxComputeWorkgroupSizeY: w.MaxComputeWorkgroupSizeY, + MaxComputeWorkgroupSizeZ: w.MaxComputeWorkgroupSizeZ, + MaxComputeWorkgroupsPerDimension: w.MaxComputeWorkgroupsPerDimension, + } } // SupportedFeatures contains features supported by adapter or device. -// This is the wire format for wgpuAdapterGetFeatures/wgpuDeviceGetFeatures. +// This is the wire format for wgpuAdapterGetFeatures/wgpuDeviceGetFeatures (v29 single-call API). +// Call SupportedFeaturesFreeMembers after use to release C-allocated memory. type SupportedFeatures struct { FeatureCount uintptr // size_t - Features uintptr // *FeatureName + Features uintptr // *FeatureName (C-allocated, must free with SupportedFeaturesFreeMembers) } // AdapterInfo contains information about the adapter. +// v29: NextInChain type changed from *ChainedStructOut to *ChainedStruct. +// v29: SubgroupMinSize and SubgroupMaxSize fields added. type AdapterInfo struct { - NextInChain uintptr // *ChainedStructOut - Vendor StringView - Architecture StringView - Device StringView - Description StringView - BackendType BackendType - AdapterType AdapterType - VendorID uint32 - DeviceID uint32 + NextInChain uintptr // *ChainedStruct (was *ChainedStructOut in v27) + Vendor StringView + Architecture StringView + Device StringView + Description StringView + BackendType BackendType + AdapterType AdapterType + VendorID uint32 + DeviceID uint32 + SubgroupMinSize uint32 // NEW in v29 + SubgroupMaxSize uint32 // NEW in v29 } // AdapterInfoGo is the Go-friendly version of AdapterInfo with actual strings. @@ -257,57 +415,59 @@ type AdapterInfoGo struct { DeviceID uint32 } -// GetLimits retrieves the limits of this adapter. -// Returns nil if the adapter is nil or if the operation fails. -func (a *Adapter) GetLimits() (*SupportedLimits, error) { - if err := checkInit(); err != nil { - return nil, err - } +// Limits returns the resource limits of this adapter. +// +// Limits are cached at adapter creation time and returned by value. +// No FFI call is made. Returns zero-value Limits if the adapter is nil. +// This matches the gogpu/wgpu API signature for cross-project compatibility. +func (a *Adapter) Limits() Limits { if a == nil || a.handle == 0 { - return nil, &WGPUError{Op: "Adapter.GetLimits", Message: "adapter is nil"} - } - - limits := &SupportedLimits{} - status, _, _ := procAdapterGetLimits.Call( - a.handle, - uintptr(unsafe.Pointer(limits)), - ) - - if WGPUStatus(status) != WGPUStatusSuccess { - return nil, &WGPUError{Op: "Adapter.GetLimits", Message: "operation failed"} + return Limits{} } - - return limits, nil + return a.limits } -// EnumerateFeatures retrieves all features supported by this adapter. -// Returns a slice of FeatureName values. -func (a *Adapter) EnumerateFeatures() []FeatureName { +// Features retrieves all features supported by this adapter. +// v29: Uses single-call wgpuAdapterGetFeatures with SupportedFeatures struct (replaces two-call EnumerateFeatures). +// The returned slice is copied from C memory; the underlying C allocation is freed automatically. +func (a *Adapter) Features() []FeatureName { mustInit() if a == nil || a.handle == 0 { return nil } - // First call: get count - count, _, _ := procAdapterEnumerateFeatures.Call( + // Single call: wgpu fills WGPUSupportedFeatures with C-allocated array + var sf SupportedFeatures + procAdapterGetFeatures.Call( //nolint:errcheck a.handle, - 0, // null pointer to get count + uintptr(unsafe.Pointer(&sf)), ) - if count == 0 { + if sf.FeatureCount == 0 || sf.Features == 0 { return nil } - // Second call: get features + // Copy features from C memory to Go slice + count := int(sf.FeatureCount) features := make([]FeatureName, count) - procAdapterEnumerateFeatures.Call( //nolint:errcheck - a.handle, - uintptr(unsafe.Pointer(&features[0])), - ) + for i := range features { + // Each FeatureName is uint32 (4 bytes) + ptr := (*FeatureName)(ptrFromUintptr(sf.Features + uintptr(i)*4)) + features[i] = *ptr + } + + // Free C-allocated memory + procSupportedFeaturesFreeMembers.Call(uintptr(unsafe.Pointer(&sf))) //nolint:errcheck return features } +// EnumerateFeatures is a deprecated alias for Features. +// Deprecated: Use Features instead. This method was renamed in wgpu-native v29. +func (a *Adapter) EnumerateFeatures() []FeatureName { + return a.Features() +} + // HasFeature checks if the adapter supports a specific feature. func (a *Adapter) HasFeature(feature FeatureName) bool { mustInit() @@ -323,15 +483,15 @@ func (a *Adapter) HasFeature(feature FeatureName) bool { return Bool(result) == True } -// GetInfo retrieves information about this adapter. +// Info retrieves information about this adapter. // The returned AdapterInfoGo contains Go strings copied from C memory. // Returns nil if the adapter is nil or if the operation fails. -func (a *Adapter) GetInfo() (*AdapterInfoGo, error) { +func (a *Adapter) Info() (*AdapterInfoGo, error) { if err := checkInit(); err != nil { return nil, err } if a == nil || a.handle == 0 { - return nil, &WGPUError{Op: "Adapter.GetInfo", Message: "adapter is nil"} + return nil, &WGPUError{Op: "Adapter.Info", Message: "adapter is nil"} } // Get native adapter info @@ -342,7 +502,7 @@ func (a *Adapter) GetInfo() (*AdapterInfoGo, error) { ) if WGPUStatus(status) != WGPUStatusSuccess { - return nil, &WGPUError{Op: "Adapter.GetInfo", Message: "operation failed"} + return nil, &WGPUError{Op: "Adapter.Info", Message: "operation failed"} } // Convert StringViews to Go strings diff --git a/wgpu/adapter_test.go b/wgpu/adapter_test.go index 0f1234d..a7c9ccd 100644 --- a/wgpu/adapter_test.go +++ b/wgpu/adapter_test.go @@ -35,7 +35,6 @@ func TestRequestAdapterWithOptions(t *testing.T) { defer inst.Release() options := &RequestAdapterOptions{ - FeatureLevel: FeatureLevelCore, PowerPreference: gputypes.PowerPreferenceHighPerformance, } diff --git a/wgpu/bindgroup.go b/wgpu/bindgroup.go index a39b96f..1e8cd7d 100644 --- a/wgpu/bindgroup.go +++ b/wgpu/bindgroup.go @@ -7,52 +7,52 @@ import ( ) // BufferBindingLayout describes buffer binding properties. -type BufferBindingLayout struct { - NextInChain uintptr // *ChainedStruct - Type gputypes.BufferBindingType - HasDynamicOffset Bool - MinBindingSize uint64 -} +// +// This type matches gputypes.BufferBindingLayout for cross-project compatibility. +// Used as a pointer field in BindGroupLayoutEntry; nil means "not a buffer binding". +type BufferBindingLayout = gputypes.BufferBindingLayout // SamplerBindingLayout describes sampler binding properties. -type SamplerBindingLayout struct { - NextInChain uintptr // *ChainedStruct - Type gputypes.SamplerBindingType -} +// +// This type matches gputypes.SamplerBindingLayout for cross-project compatibility. +// Used as a pointer field in BindGroupLayoutEntry; nil means "not a sampler binding". +type SamplerBindingLayout = gputypes.SamplerBindingLayout // TextureBindingLayout describes texture binding properties. -type TextureBindingLayout struct { - NextInChain uintptr // *ChainedStruct - SampleType gputypes.TextureSampleType - ViewDimension gputypes.TextureViewDimension - Multisampled Bool -} +// +// This type matches gputypes.TextureBindingLayout for cross-project compatibility. +// Used as a pointer field in BindGroupLayoutEntry; nil means "not a texture binding". +type TextureBindingLayout = gputypes.TextureBindingLayout // StorageTextureBindingLayout describes storage texture binding properties. -type StorageTextureBindingLayout struct { - NextInChain uintptr // *ChainedStruct - Access gputypes.StorageTextureAccess - Format gputypes.TextureFormat - ViewDimension gputypes.TextureViewDimension -} +// +// This type matches gputypes.StorageTextureBindingLayout for cross-project compatibility. +// Used as a pointer field in BindGroupLayoutEntry; nil means "not a storage texture binding". +type StorageTextureBindingLayout = gputypes.StorageTextureBindingLayout // BindGroupLayoutEntry describes a single binding in a bind group layout. +// +// Exactly one of Buffer, Sampler, Texture, or StorageTexture must be non-nil. +// This matches the gogpu/wgpu API for cross-project compatibility. type BindGroupLayoutEntry struct { - NextInChain uintptr // *ChainedStruct - Binding uint32 - Visibility gputypes.ShaderStage - Buffer BufferBindingLayout - Sampler SamplerBindingLayout - Texture TextureBindingLayout - StorageTexture StorageTextureBindingLayout + // Binding is the binding number (must match @binding in shader). + Binding uint32 + // Visibility specifies which shader stages can access this binding. + Visibility gputypes.ShaderStage + // Buffer describes a buffer binding (nil if not a buffer binding). + Buffer *BufferBindingLayout + // Sampler describes a sampler binding (nil if not a sampler binding). + Sampler *SamplerBindingLayout + // Texture describes a texture binding (nil if not a texture binding). + Texture *TextureBindingLayout + // StorageTexture describes a storage texture binding (nil if not a storage texture binding). + StorageTexture *StorageTextureBindingLayout } // BindGroupLayoutDescriptor describes a bind group layout. type BindGroupLayoutDescriptor struct { - NextInChain uintptr // *ChainedStruct - Label StringView - EntryCount uintptr // size_t - Entries uintptr // *BindGroupLayoutEntry + Label string + Entries []BindGroupLayoutEntry } // ============================================================================= @@ -110,34 +110,39 @@ type bindGroupLayoutEntryWire struct { } // toWire converts a BindGroupLayoutEntry to its wire representation. +// Nil sub-layout pointers produce zero-value wire structs (BindingNotUsed sentinel). func (e *BindGroupLayoutEntry) toWire() bindGroupLayoutEntryWire { - return bindGroupLayoutEntryWire{ - NextInChain: e.NextInChain, - Binding: e.Binding, - Visibility: uint64(e.Visibility), // widen uint32 to uint64 - Buffer: bufferBindingLayoutWire{ - NextInChain: e.Buffer.NextInChain, + wire := bindGroupLayoutEntryWire{ + Binding: e.Binding, + Visibility: uint64(e.Visibility), // widen uint32 to uint64 + } + if e.Buffer != nil { + wire.Buffer = bufferBindingLayoutWire{ Type: toWGPUBufferBindingType(e.Buffer.Type), - HasDynamicOffset: e.Buffer.HasDynamicOffset, + HasDynamicOffset: boolToWGPU(e.Buffer.HasDynamicOffset), MinBindingSize: e.Buffer.MinBindingSize, - }, - Sampler: samplerBindingLayoutWire{ - NextInChain: e.Sampler.NextInChain, - Type: toWGPUSamplerBindingType(e.Sampler.Type), - }, - Texture: textureBindingLayoutWire{ - NextInChain: e.Texture.NextInChain, + } + } + if e.Sampler != nil { + wire.Sampler = samplerBindingLayoutWire{ + Type: toWGPUSamplerBindingType(e.Sampler.Type), + } + } + if e.Texture != nil { + wire.Texture = textureBindingLayoutWire{ SampleType: toWGPUTextureSampleType(e.Texture.SampleType), - ViewDimension: toWGPUTextureViewDimension(e.Texture.ViewDimension), - Multisampled: e.Texture.Multisampled, - }, - StorageTexture: storageTextureBindingLayoutWire{ - NextInChain: e.StorageTexture.NextInChain, + ViewDimension: uint32(e.Texture.ViewDimension), + Multisampled: boolToWGPU(e.Texture.Multisampled), + } + } + if e.StorageTexture != nil { + wire.StorageTexture = storageTextureBindingLayoutWire{ Access: toWGPUStorageTextureAccess(e.StorageTexture.Access), - Format: toWGPUTextureFormat(e.StorageTexture.Format), - ViewDimension: toWGPUTextureViewDimension(e.StorageTexture.ViewDimension), - }, + Format: uint32(e.StorageTexture.Format), + ViewDimension: uint32(e.StorageTexture.ViewDimension), + } } + return wire } // bindGroupLayoutDescriptorWire is the FFI-compatible descriptor. @@ -149,9 +154,23 @@ type bindGroupLayoutDescriptorWire struct { } // BindGroupEntry describes a single binding in a bind group. +// Exactly one of Buffer, Sampler, or TextureView must be non-nil. type BindGroupEntry struct { + Binding uint32 + Buffer *Buffer // For buffer bindings (nil if not used) + Offset uint64 // Buffer offset (ignored for non-buffer bindings) + Size uint64 // Buffer binding size; 0 = whole buffer + Sampler *Sampler // For sampler bindings (nil if not used) + TextureView *TextureView // For texture view bindings (nil if not used) +} + +// bindGroupEntryWire is the FFI-compatible C-layout struct for wgpu-native. +// CRITICAL: layout must match WGPUBindGroupEntry exactly. +// nextInChain(8)+binding(4)+pad(4)+buffer(8)+offset(8)+size(8)+sampler(8)+textureView(8) = 56 bytes. +type bindGroupEntryWire struct { NextInChain uintptr // *ChainedStruct Binding uint32 + _pad [4]byte // padding for FFI alignment Buffer uintptr // WGPUBuffer (nullable) Offset uint64 Size uint64 @@ -159,35 +178,64 @@ type BindGroupEntry struct { TextureView uintptr // WGPUTextureView (nullable) } +// toWire converts a BindGroupEntry to its FFI wire representation. +func (e *BindGroupEntry) toWire() bindGroupEntryWire { + wire := bindGroupEntryWire{ + Binding: e.Binding, + Offset: e.Offset, + Size: e.Size, + } + if e.Buffer != nil { + wire.Buffer = e.Buffer.handle + } + if e.Sampler != nil { + wire.Sampler = e.Sampler.handle + } + if e.TextureView != nil { + wire.TextureView = e.TextureView.handle + } + return wire +} + // BindGroupDescriptor describes a bind group. type BindGroupDescriptor struct { + Label string + Layout *BindGroupLayout + Entries []BindGroupEntry +} + +// bindGroupDescriptorWire is the FFI-compatible C-layout struct for wgpu-native. +type bindGroupDescriptorWire struct { NextInChain uintptr // *ChainedStruct Label StringView Layout uintptr // WGPUBindGroupLayout EntryCount uintptr // size_t - Entries uintptr // *BindGroupEntry + Entries uintptr // *bindGroupEntryWire } // CreateBindGroupLayout creates a bind group layout. // Entries are converted from gputypes to wgpu-native enum values before FFI call. -func (d *Device) CreateBindGroupLayout(desc *BindGroupLayoutDescriptor) *BindGroupLayout { - mustInit() - if d == nil || d.handle == 0 || desc == nil { - return nil +// Returns an error if the FFI call fails or the device/descriptor is nil. +func (d *Device) CreateBindGroupLayout(desc *BindGroupLayoutDescriptor) (*BindGroupLayout, error) { + if err := checkInit(); err != nil { + return nil, err + } + if d == nil || d.handle == 0 { + return nil, &WGPUError{Op: "CreateBindGroupLayout", Message: "device is nil or released"} + } + if desc == nil { + return nil, &WGPUError{Op: "CreateBindGroupLayout", Message: "descriptor is nil"} } - // If there are entries, we need to convert them to wire format var wireDesc bindGroupLayoutDescriptorWire - wireDesc.NextInChain = desc.NextInChain - wireDesc.Label = desc.Label - wireDesc.EntryCount = desc.EntryCount - - if desc.EntryCount > 0 && desc.Entries != 0 { - // Convert entries to wire format - entries := unsafe.Slice((*BindGroupLayoutEntry)(ptrFromUintptr(desc.Entries)), desc.EntryCount) - wireEntries := make([]bindGroupLayoutEntryWire, len(entries)) - for i := range entries { - wireEntries[i] = entries[i].toWire() + wireDesc.Label = stringToStringView(desc.Label) + wireDesc.EntryCount = uintptr(len(desc.Entries)) + + var wireEntries []bindGroupLayoutEntryWire + if len(desc.Entries) > 0 { + wireEntries = make([]bindGroupLayoutEntryWire, len(desc.Entries)) + for i := range desc.Entries { + wireEntries[i] = desc.Entries[i].toWire() } wireDesc.Entries = uintptr(unsafe.Pointer(&wireEntries[0])) } @@ -197,40 +245,18 @@ func (d *Device) CreateBindGroupLayout(desc *BindGroupLayoutDescriptor) *BindGro uintptr(unsafe.Pointer(&wireDesc)), ) if handle == 0 { - return nil + return nil, &WGPUError{Op: "CreateBindGroupLayout", Message: "wgpu returned null handle"} } trackResource(handle, "BindGroupLayout") - return &BindGroupLayout{handle: handle} + return &BindGroupLayout{handle: handle}, nil } // CreateBindGroupLayoutSimple creates a bind group layout with the given entries. -func (d *Device) CreateBindGroupLayoutSimple(entries []BindGroupLayoutEntry) *BindGroupLayout { - mustInit() - if d == nil || d.handle == 0 || len(entries) == 0 { - return nil - } - - // Convert entries to wire format - wireEntries := make([]bindGroupLayoutEntryWire, len(entries)) - for i := range entries { - wireEntries[i] = entries[i].toWire() - } - - wireDesc := bindGroupLayoutDescriptorWire{ - Label: EmptyStringView(), - EntryCount: uintptr(len(entries)), - Entries: uintptr(unsafe.Pointer(&wireEntries[0])), - } - - handle, _, _ := procDeviceCreateBindGroupLayout.Call( - d.handle, - uintptr(unsafe.Pointer(&wireDesc)), - ) - if handle == 0 { - return nil - } - trackResource(handle, "BindGroupLayout") - return &BindGroupLayout{handle: handle} +// Returns an error if the FFI call fails or the device is nil. +func (d *Device) CreateBindGroupLayoutSimple(entries []BindGroupLayoutEntry) (*BindGroupLayout, error) { + return d.CreateBindGroupLayout(&BindGroupLayoutDescriptor{ + Entries: entries, + }) } // Release releases the bind group layout. @@ -246,35 +272,57 @@ func (bgl *BindGroupLayout) Release() { func (bgl *BindGroupLayout) Handle() uintptr { return bgl.handle } // CreateBindGroup creates a bind group. -func (d *Device) CreateBindGroup(desc *BindGroupDescriptor) *BindGroup { - mustInit() - if d == nil || d.handle == 0 || desc == nil { - return nil +// Returns an error if the FFI call fails or the device/descriptor is nil. +func (d *Device) CreateBindGroup(desc *BindGroupDescriptor) (*BindGroup, error) { + if err := checkInit(); err != nil { + return nil, err + } + if d == nil || d.handle == 0 { + return nil, &WGPUError{Op: "CreateBindGroup", Message: "device is nil or released"} + } + if desc == nil { + return nil, &WGPUError{Op: "CreateBindGroup", Message: "descriptor is nil"} + } + if desc.Layout == nil { + return nil, &WGPUError{Op: "CreateBindGroup", Message: "layout is nil"} + } + + // Convert Go-idiomatic entries to FFI wire entries + var wireEntries []bindGroupEntryWire + var wireEntriesPtr uintptr + if len(desc.Entries) > 0 { + wireEntries = make([]bindGroupEntryWire, len(desc.Entries)) + for i := range desc.Entries { + wireEntries[i] = desc.Entries[i].toWire() + } + wireEntriesPtr = uintptr(unsafe.Pointer(&wireEntries[0])) + } + + wire := bindGroupDescriptorWire{ + Label: stringToStringView(desc.Label), + Layout: desc.Layout.handle, + EntryCount: uintptr(len(desc.Entries)), + Entries: wireEntriesPtr, } + handle, _, _ := procDeviceCreateBindGroup.Call( d.handle, - uintptr(unsafe.Pointer(desc)), + uintptr(unsafe.Pointer(&wire)), ) if handle == 0 { - return nil + return nil, &WGPUError{Op: "CreateBindGroup", Message: "wgpu returned null handle"} } trackResource(handle, "BindGroup") - return &BindGroup{handle: handle} + return &BindGroup{handle: handle}, nil } -// CreateBindGroupSimple creates a bind group with buffer entries. -func (d *Device) CreateBindGroupSimple(layout *BindGroupLayout, entries []BindGroupEntry) *BindGroup { - mustInit() - if d == nil || d.handle == 0 || layout == nil || len(entries) == 0 { - return nil - } - desc := BindGroupDescriptor{ - Label: EmptyStringView(), - Layout: layout.handle, - EntryCount: uintptr(len(entries)), - Entries: uintptr(unsafe.Pointer(&entries[0])), - } - return d.CreateBindGroup(&desc) +// CreateBindGroupSimple creates a bind group with the given entries. +// Returns an error if the FFI call fails or the device/layout is nil. +func (d *Device) CreateBindGroupSimple(layout *BindGroupLayout, entries []BindGroupEntry) (*BindGroup, error) { + return d.CreateBindGroup(&BindGroupDescriptor{ + Layout: layout, + Entries: entries, + }) } // Release releases the bind group. @@ -293,7 +341,7 @@ func (bg *BindGroup) Handle() uintptr { return bg.handle } func BufferBindingEntry(binding uint32, buffer *Buffer, offset, size uint64) BindGroupEntry { return BindGroupEntry{ Binding: binding, - Buffer: buffer.handle, + Buffer: buffer, Offset: offset, Size: size, } @@ -303,7 +351,7 @@ func BufferBindingEntry(binding uint32, buffer *Buffer, offset, size uint64) Bin func TextureBindingEntry(binding uint32, textureView *TextureView) BindGroupEntry { return BindGroupEntry{ Binding: binding, - TextureView: textureView.handle, + TextureView: textureView, } } @@ -311,6 +359,6 @@ func TextureBindingEntry(binding uint32, textureView *TextureView) BindGroupEntr func SamplerBindingEntry(binding uint32, sampler *Sampler) BindGroupEntry { return BindGroupEntry{ Binding: binding, - Sampler: sampler.handle, + Sampler: sampler, } } diff --git a/wgpu/bindgroup_test.go b/wgpu/bindgroup_test.go index a05b871..b6f46fe 100644 --- a/wgpu/bindgroup_test.go +++ b/wgpu/bindgroup_test.go @@ -30,15 +30,15 @@ func TestCreateBindGroupLayout(t *testing.T) { { Binding: 0, Visibility: gputypes.ShaderStageCompute, - Buffer: BufferBindingLayout{ + Buffer: &BufferBindingLayout{ Type: gputypes.BufferBindingTypeStorage, MinBindingSize: 0, }, }, } - layout := device.CreateBindGroupLayoutSimple(entries) - if layout == nil { - t.Fatal("CreateBindGroupLayoutSimple returned nil") + layout, err := device.CreateBindGroupLayoutSimple(entries) + if err != nil { + t.Fatalf("CreateBindGroupLayoutSimple failed: %v", err) } defer layout.Release() @@ -70,14 +70,14 @@ func TestCreateBindGroup(t *testing.T) { // Create buffer bufferDesc := &BufferDescriptor{ - Label: EmptyStringView(), + Label: "", Usage: gputypes.BufferUsageStorage | gputypes.BufferUsageCopyDst, Size: 256, - MappedAtCreation: False, + MappedAtCreation: false, } - buffer := device.CreateBuffer(bufferDesc) - if buffer == nil { - t.Fatal("CreateBuffer returned nil") + buffer, err := device.CreateBuffer(bufferDesc) + if err != nil { + t.Fatalf("CreateBuffer failed: %v", err) } defer buffer.Release() @@ -86,15 +86,15 @@ func TestCreateBindGroup(t *testing.T) { { Binding: 0, Visibility: gputypes.ShaderStageCompute, - Buffer: BufferBindingLayout{ + Buffer: &BufferBindingLayout{ Type: gputypes.BufferBindingTypeStorage, MinBindingSize: 0, }, }, } - layout := device.CreateBindGroupLayoutSimple(layoutEntries) - if layout == nil { - t.Fatal("CreateBindGroupLayoutSimple returned nil") + layout, err := device.CreateBindGroupLayoutSimple(layoutEntries) + if err != nil { + t.Fatalf("CreateBindGroupLayoutSimple failed: %v", err) } defer layout.Release() @@ -103,9 +103,9 @@ func TestCreateBindGroup(t *testing.T) { entries := []BindGroupEntry{ BufferBindingEntry(0, buffer, 0, 256), } - bindGroup := device.CreateBindGroupSimple(layout, entries) - if bindGroup == nil { - t.Fatal("CreateBindGroupSimple returned nil") + bindGroup, err := device.CreateBindGroupSimple(layout, entries) + if err != nil { + t.Fatalf("CreateBindGroupSimple failed: %v", err) } defer bindGroup.Release() @@ -136,25 +136,25 @@ func TestBindGroupWithMultipleBindings(t *testing.T) { defer device.Release() // Create buffers - inputBuffer := device.CreateBuffer(&BufferDescriptor{ - Label: EmptyStringView(), + inputBuffer, err := device.CreateBuffer(&BufferDescriptor{ + Label: "", Usage: gputypes.BufferUsageStorage | gputypes.BufferUsageCopyDst, Size: 256, - MappedAtCreation: False, + MappedAtCreation: false, }) - if inputBuffer == nil { - t.Fatal("CreateBuffer (input) returned nil") + if err != nil { + t.Fatalf("CreateBuffer (input) failed: %v", err) } defer inputBuffer.Release() - outputBuffer := device.CreateBuffer(&BufferDescriptor{ - Label: EmptyStringView(), + outputBuffer, err := device.CreateBuffer(&BufferDescriptor{ + Label: "", Usage: gputypes.BufferUsageStorage | gputypes.BufferUsageCopySrc, Size: 256, - MappedAtCreation: False, + MappedAtCreation: false, }) - if outputBuffer == nil { - t.Fatal("CreateBuffer (output) returned nil") + if err != nil { + t.Fatalf("CreateBuffer (output) failed: %v", err) } defer outputBuffer.Release() @@ -163,7 +163,7 @@ func TestBindGroupWithMultipleBindings(t *testing.T) { { Binding: 0, Visibility: gputypes.ShaderStageCompute, - Buffer: BufferBindingLayout{ + Buffer: &BufferBindingLayout{ Type: gputypes.BufferBindingTypeReadOnlyStorage, MinBindingSize: 0, }, @@ -171,15 +171,15 @@ func TestBindGroupWithMultipleBindings(t *testing.T) { { Binding: 1, Visibility: gputypes.ShaderStageCompute, - Buffer: BufferBindingLayout{ + Buffer: &BufferBindingLayout{ Type: gputypes.BufferBindingTypeStorage, MinBindingSize: 0, }, }, } - layout := device.CreateBindGroupLayoutSimple(layoutEntries) - if layout == nil { - t.Fatal("CreateBindGroupLayoutSimple returned nil") + layout, err := device.CreateBindGroupLayoutSimple(layoutEntries) + if err != nil { + t.Fatalf("CreateBindGroupLayoutSimple failed: %v", err) } defer layout.Release() @@ -189,9 +189,9 @@ func TestBindGroupWithMultipleBindings(t *testing.T) { BufferBindingEntry(0, inputBuffer, 0, 256), BufferBindingEntry(1, outputBuffer, 0, 256), } - bindGroup := device.CreateBindGroupSimple(layout, entries) - if bindGroup == nil { - t.Fatal("CreateBindGroupSimple returned nil") + bindGroup, err := device.CreateBindGroupSimple(layout, entries) + if err != nil { + t.Fatalf("CreateBindGroupSimple failed: %v", err) } defer bindGroup.Release() diff --git a/wgpu/buffer.go b/wgpu/buffer.go index e42a35c..e862e56 100644 --- a/wgpu/buffer.go +++ b/wgpu/buffer.go @@ -1,6 +1,7 @@ package wgpu import ( + "context" "sync" "unsafe" @@ -26,14 +27,15 @@ type MapAsyncStatus uint32 const ( // MapAsyncStatusSuccess indicates the buffer was successfully mapped. MapAsyncStatusSuccess MapAsyncStatus = 0x00000001 - // MapAsyncStatusInstanceDropped indicates the instance was dropped before completion. - MapAsyncStatusInstanceDropped MapAsyncStatus = 0x00000002 + // MapAsyncStatusCallbackCancelled indicates the callback was cancelled. + MapAsyncStatusCallbackCancelled MapAsyncStatus = 0x00000002 // MapAsyncStatusError indicates a mapping error occurred. MapAsyncStatusError MapAsyncStatus = 0x00000003 // MapAsyncStatusAborted indicates the mapping was aborted (e.g., buffer destroyed). MapAsyncStatusAborted MapAsyncStatus = 0x00000004 - // MapAsyncStatusUnknown indicates an unknown mapping error. - MapAsyncStatusUnknown MapAsyncStatus = 0x00000005 + + // Deprecated: use MapAsyncStatusCallbackCancelled. + MapAsyncStatusInstanceDropped = MapAsyncStatusCallbackCancelled ) // BufferMapCallbackInfo holds callback configuration for MapAsync. @@ -98,30 +100,53 @@ func initMapCallback() { mapCallbackPtr = ffi.NewCallback(mapCallbackHandler) } -// BufferDescriptor describes a buffer to create. +// BufferDescriptor describes a GPU buffer to create. type BufferDescriptor struct { + Label string // Buffer label for debugging + Usage gputypes.BufferUsage // How the buffer will be used + Size uint64 // Size in bytes + MappedAtCreation bool // If true, buffer is mapped when created +} + +// bufferDescriptorWire is the FFI-compatible C-layout struct for wgpu-native. +// CRITICAL: layout must match WGPUBufferDescriptor exactly. +// nextInChain(8)+label(16)+usage(8)+size(8)+mappedAtCreation(4)+pad(4) = 48 bytes. +type bufferDescriptorWire struct { NextInChain uintptr // *ChainedStruct Label StringView // Buffer label for debugging Usage gputypes.BufferUsage // How the buffer will be used Size uint64 // Size in bytes MappedAtCreation Bool // If true, buffer is mapped when created + _pad [4]byte //nolint:unused // padding for FFI alignment } // CreateBuffer creates a new GPU buffer. -func (d *Device) CreateBuffer(desc *BufferDescriptor) *Buffer { - mustInit() - if d == nil || d.handle == 0 || desc == nil { - return nil +// Returns an error if the FFI call fails or the device/descriptor is nil. +func (d *Device) CreateBuffer(desc *BufferDescriptor) (*Buffer, error) { + if err := checkInit(); err != nil { + return nil, err + } + if d == nil || d.handle == 0 { + return nil, &WGPUError{Op: "CreateBuffer", Message: "device is nil or released"} + } + if desc == nil { + return nil, &WGPUError{Op: "CreateBuffer", Message: "descriptor is nil"} + } + wire := bufferDescriptorWire{ + Label: stringToStringView(desc.Label), + Usage: desc.Usage, + Size: desc.Size, + MappedAtCreation: boolToWGPU(desc.MappedAtCreation), } handle, _, _ := procDeviceCreateBuffer.Call( d.handle, - uintptr(unsafe.Pointer(desc)), + uintptr(unsafe.Pointer(&wire)), ) if handle == 0 { - return nil + return nil, &WGPUError{Op: "CreateBuffer", Message: "wgpu returned null handle"} } trackResource(handle, "Buffer") - return &Buffer{handle: handle} + return &Buffer{handle: handle, device: d}, nil } // GetMappedRange returns a pointer to the mapped buffer data. @@ -146,16 +171,19 @@ func (b *Buffer) GetMappedRange(offset, size uint64) unsafe.Pointer { // Unmap unmaps the buffer, making the mapped memory inaccessible. // For buffers created with MappedAtCreation, this commits the data to the GPU. -func (b *Buffer) Unmap() { +// Returns nil on success. Matches gogpu/wgpu Buffer.Unmap() error signature. +func (b *Buffer) Unmap() error { mustInit() if b == nil || b.handle == 0 { - return + return nil } procBufferUnmap.Call(b.handle) //nolint:errcheck + // wgpu-native returns void for wgpuBufferUnmap; always nil per WebGPU spec. + return nil } -// GetSize returns the size of the buffer in bytes. -func (b *Buffer) GetSize() uint64 { +// Size returns the size of the buffer in bytes. +func (b *Buffer) Size() uint64 { mustInit() if b == nil || b.handle == 0 { return 0 @@ -164,73 +192,14 @@ func (b *Buffer) GetSize() uint64 { return uint64(size) } -// MapAsync maps a buffer for reading or writing. -// This is a synchronous wrapper that blocks until the mapping is complete. -// The device parameter is used to poll for completion. -// After MapAsync succeeds, use GetMappedRange to access the data. -// Call Unmap when done to release the mapping. -func (b *Buffer) MapAsync(device *Device, mode MapMode, offset, size uint64) error { - if err := checkInit(); err != nil { - return err - } - if b == nil || b.handle == 0 { - return &WGPUError{Op: "Buffer.MapAsync", Message: "buffer is nil or released"} - } - if device == nil || device.handle == 0 { - return &WGPUError{Op: "Buffer.MapAsync", Message: "device is nil or released"} - } - - // Initialize callback once - mapCallbackOnce.Do(initMapCallback) - - // Create request state - req := &mapRequest{ - done: make(chan struct{}), - } - - // Register request - mapRequestsMu.Lock() - mapRequestID++ - reqID := mapRequestID - mapRequests[reqID] = req - mapRequestsMu.Unlock() - - // Prepare callback info - callbackInfo := BufferMapCallbackInfo{ - NextInChain: 0, - Mode: CallbackModeAllowProcessEvents, - Callback: mapCallbackPtr, - Userdata1: reqID, - Userdata2: 0, - } - - // Call wgpuBufferMapAsync - procBufferMapAsync.Call( //nolint:errcheck - b.handle, - uintptr(mode), - uintptr(offset), - uintptr(size), - uintptr(unsafe.Pointer(&callbackInfo)), - ) - - // Poll device until callback fires - for { - select { - case <-req.done: - // Callback completed - if req.status != MapAsyncStatusSuccess { - msg := req.message - if msg == "" { - msg = "buffer map failed" - } - return &WGPUError{Op: "Buffer.MapAsync", Message: msg} - } - return nil - default: - // Poll device to process callbacks - device.Poll(false) - } +// MapAsyncBlocking maps a buffer for reading or writing, blocking until complete. +// Deprecated: Use [Buffer.Map] for blocking mapping or [Buffer.MapAsync] for non-blocking. +// This method is retained for backward compatibility. +func (b *Buffer) MapAsyncBlocking(device *Device, mode MapMode, offset, size uint64) error { + if device != nil && b.device == nil { + b.device = device } + return b.Map(context.Background(), mode, offset, size) } // Destroy destroys the buffer, making it invalid. @@ -251,11 +220,12 @@ func (b *Buffer) Release() { } // WriteBuffer writes data to a buffer. -// This is a convenience method that stages data for upload to the GPU. -func (q *Queue) WriteBuffer(buffer *Buffer, offset uint64, data []byte) { +// Returns nil on success. In this FFI implementation errors are surfaced through +// the Device uncaptured-error callback; the signature matches gogpu/wgpu for API compatibility. +func (q *Queue) WriteBuffer(buffer *Buffer, offset uint64, data []byte) error { mustInit() if q == nil || q.handle == 0 || buffer == nil || buffer.handle == 0 || len(data) == 0 { - return + return nil } procQueueWriteBuffer.Call( //nolint:errcheck q.handle, @@ -264,6 +234,7 @@ func (q *Queue) WriteBuffer(buffer *Buffer, offset uint64, data []byte) { uintptr(unsafe.Pointer(&data[0])), uintptr(len(data)), ) + return nil } // WriteBufferTyped writes typed data to a buffer. @@ -282,8 +253,8 @@ func (q *Queue) WriteBufferRaw(buffer *Buffer, offset uint64, data unsafe.Pointe ) } -// GetUsage returns the usage flags of this buffer. -func (b *Buffer) GetUsage() gputypes.BufferUsage { +// Usage returns the usage flags of this buffer. +func (b *Buffer) Usage() gputypes.BufferUsage { mustInit() if b == nil || b.handle == 0 { return gputypes.BufferUsageNone @@ -292,8 +263,8 @@ func (b *Buffer) GetUsage() gputypes.BufferUsage { return gputypes.BufferUsage(usage) } -// GetMapState returns the current mapping state of this buffer. -func (b *Buffer) GetMapState() BufferMapState { +// MapState returns the current mapping state of this buffer. +func (b *Buffer) MapState() BufferMapState { mustInit() if b == nil || b.handle == 0 { return BufferMapStateUnmapped diff --git a/wgpu/buffer_test.go b/wgpu/buffer_test.go index 138e20e..c183a73 100644 --- a/wgpu/buffer_test.go +++ b/wgpu/buffer_test.go @@ -28,14 +28,14 @@ func TestCreateBuffer(t *testing.T) { t.Log("Creating buffer...") desc := &BufferDescriptor{ - Label: EmptyStringView(), + Label: "", Usage: gputypes.BufferUsageCopyDst | gputypes.BufferUsageMapRead, Size: 256, - MappedAtCreation: False, + MappedAtCreation: false, } - buffer := device.CreateBuffer(desc) - if buffer == nil { - t.Fatal("CreateBuffer returned nil") + buffer, err := device.CreateBuffer(desc) + if err != nil { + t.Fatalf("CreateBuffer failed: %v", err) } defer buffer.Release() @@ -45,8 +45,8 @@ func TestCreateBuffer(t *testing.T) { t.Logf("Buffer created: handle=%#x", buffer.Handle()) - // Test GetSize - size := buffer.GetSize() + // Test Size + size := buffer.Size() if size != 256 { t.Errorf("Buffer size = %d, want 256", size) } @@ -74,14 +74,14 @@ func TestBufferMappedAtCreation(t *testing.T) { t.Log("Creating buffer with MappedAtCreation...") desc := &BufferDescriptor{ - Label: EmptyStringView(), + Label: "", Usage: gputypes.BufferUsageCopySrc, Size: 64, - MappedAtCreation: True, + MappedAtCreation: true, } - buffer := device.CreateBuffer(desc) - if buffer == nil { - t.Fatal("CreateBuffer returned nil") + buffer, err := device.CreateBuffer(desc) + if err != nil { + t.Fatalf("CreateBuffer failed: %v", err) } defer buffer.Release() @@ -123,7 +123,7 @@ func TestQueueWriteBuffer(t *testing.T) { } defer device.Release() - queue := device.GetQueue() + queue := device.Queue() if queue == nil { t.Fatal("GetQueue returned nil") } @@ -131,14 +131,14 @@ func TestQueueWriteBuffer(t *testing.T) { t.Log("Creating buffer for WriteBuffer test...") desc := &BufferDescriptor{ - Label: EmptyStringView(), + Label: "", Usage: gputypes.BufferUsageCopyDst, Size: 128, - MappedAtCreation: False, + MappedAtCreation: false, } - buffer := device.CreateBuffer(desc) - if buffer == nil { - t.Fatal("CreateBuffer returned nil") + buffer, err := device.CreateBuffer(desc) + if err != nil { + t.Fatalf("CreateBuffer failed: %v", err) } defer buffer.Release() @@ -172,7 +172,7 @@ func TestQueueWriteBufferRaw(t *testing.T) { } defer device.Release() - queue := device.GetQueue() + queue := device.Queue() if queue == nil { t.Fatal("GetQueue returned nil") } @@ -180,14 +180,14 @@ func TestQueueWriteBufferRaw(t *testing.T) { t.Log("Creating buffer for WriteBufferRaw test...") desc := &BufferDescriptor{ - Label: EmptyStringView(), + Label: "", Usage: gputypes.BufferUsageCopyDst, Size: 128, - MappedAtCreation: False, + MappedAtCreation: false, } - buffer := device.CreateBuffer(desc) - if buffer == nil { - t.Fatal("CreateBuffer returned nil") + buffer, err := device.CreateBuffer(desc) + if err != nil { + t.Fatalf("CreateBuffer failed: %v", err) } defer buffer.Release() diff --git a/wgpu/command.go b/wgpu/command.go index 8df97b4..993d90e 100644 --- a/wgpu/command.go +++ b/wgpu/command.go @@ -6,10 +6,16 @@ import ( "github.com/gogpu/gputypes" ) -// CommandEncoderDescriptor describes a command encoder. +// CommandEncoderDescriptor describes a command encoder to create. type CommandEncoderDescriptor struct { - NextInChain uintptr // *ChainedStruct - Label StringView + Label string +} + +// commandEncoderDescriptorWire is the FFI-compatible C-layout struct for wgpu-native. +// nextInChain(8)+label(16) = 24 bytes. +type commandEncoderDescriptorWire struct { + NextInChain uintptr // *ChainedStruct + Label StringView // 16 bytes } // CommandBufferDescriptor describes a command buffer. @@ -18,53 +24,97 @@ type CommandBufferDescriptor struct { Label StringView } -// ComputePassDescriptor describes a compute pass. +// ComputePassTimestampWrites is a deprecated alias for PassTimestampWrites. +// Deprecated: Use PassTimestampWrites. Renamed in wgpu-native v29. +type ComputePassTimestampWrites = PassTimestampWrites + +// computePassDescriptorWire is the native FFI structure for ComputePassDescriptor. +// v29: timestampWrites field is *WGPUPassTimestampWrites (unified, not separate ComputePassTimestampWrites). +type computePassDescriptorWire struct { + nextInChain uintptr // 8 bytes + label StringView // 16 bytes + timestampWrites uintptr // 8 bytes (*passTimestampWrites, nullable) +} + +// ComputePassDescriptor describes a compute pass (user-facing API). type ComputePassDescriptor struct { - NextInChain uintptr // *ChainedStruct - Label StringView - TimestampWrites uintptr // *ComputePassTimestampWrites (nullable) + Label string + TimestampWrites *PassTimestampWrites // optional; use PassTimestampWrites (was ComputePassTimestampWrites) } // CreateCommandEncoder creates a command encoder. -func (d *Device) CreateCommandEncoder(desc *CommandEncoderDescriptor) *CommandEncoder { - mustInit() +// Returns an error if the FFI call fails or the device is nil. +func (d *Device) CreateCommandEncoder(desc *CommandEncoderDescriptor) (*CommandEncoder, error) { + if err := checkInit(); err != nil { + return nil, err + } if d == nil || d.handle == 0 { - return nil + return nil, &WGPUError{Op: "CreateCommandEncoder", Message: "device is nil or released"} } var descPtr uintptr if desc != nil { - descPtr = uintptr(unsafe.Pointer(desc)) + wire := commandEncoderDescriptorWire{ + Label: stringToStringView(desc.Label), + } + descPtr = uintptr(unsafe.Pointer(&wire)) } handle, _, _ := procDeviceCreateCommandEncoder.Call( d.handle, descPtr, ) if handle == 0 { - return nil + return nil, &WGPUError{Op: "CreateCommandEncoder", Message: "wgpu returned null handle"} } trackResource(handle, "CommandEncoder") - return &CommandEncoder{handle: handle} + return &CommandEncoder{handle: handle}, nil } // BeginComputePass begins a compute pass. -func (enc *CommandEncoder) BeginComputePass(desc *ComputePassDescriptor) *ComputePassEncoder { - mustInit() +// Returns an error if the FFI call fails or the encoder is nil. +func (enc *CommandEncoder) BeginComputePass(desc *ComputePassDescriptor) (*ComputePassEncoder, error) { + if err := checkInit(); err != nil { + return nil, err + } if enc == nil || enc.handle == 0 { - return nil + return nil, &WGPUError{Op: "BeginComputePass", Message: "encoder is nil or released"} } + + var wireDesc computePassDescriptorWire + var wireTimestamp passTimestampWrites var descPtr uintptr + if desc != nil { - descPtr = uintptr(unsafe.Pointer(desc)) + wireDesc.nextInChain = 0 + if desc.Label != "" { + labelBytes := []byte(desc.Label) + wireDesc.label = StringView{ + Data: uintptr(unsafe.Pointer(&labelBytes[0])), + Length: uintptr(len(labelBytes)), + } + } else { + wireDesc.label = EmptyStringView() + } + if desc.TimestampWrites != nil { + wireTimestamp = passTimestampWrites{ + nextInChain: 0, + querySet: desc.TimestampWrites.QuerySet.handle, + beginningOfPassWriteIndex: desc.TimestampWrites.BeginningOfPassWriteIndex, + endOfPassWriteIndex: desc.TimestampWrites.EndOfPassWriteIndex, + } + wireDesc.timestampWrites = uintptr(unsafe.Pointer(&wireTimestamp)) + } + descPtr = uintptr(unsafe.Pointer(&wireDesc)) } + handle, _, _ := procCommandEncoderBeginComputePass.Call( enc.handle, descPtr, ) if handle == 0 { - return nil + return nil, &WGPUError{Op: "BeginComputePass", Message: "wgpu returned null handle"} } trackResource(handle, "ComputePassEncoder") - return &ComputePassEncoder{handle: handle} + return &ComputePassEncoder{handle: handle}, nil } // CopyBufferToBuffer copies data between buffers. @@ -150,7 +200,7 @@ func (enc *CommandEncoder) PopDebugGroup() { procCommandEncoderPopDebugGroup.Call(enc.handle) //nolint:errcheck } -// CopyBufferToTexture copies data from a buffer to a texture. +// CopyBufferToTexture copies data from a buffer to a texture using low-level wire types. // Errors are reported via Device error scopes, not as return values. func (enc *CommandEncoder) CopyBufferToTexture(source *TexelCopyBufferInfo, destination *TexelCopyTextureInfo, copySize *gputypes.Extent3D) { mustInit() @@ -166,8 +216,38 @@ func (enc *CommandEncoder) CopyBufferToTexture(source *TexelCopyBufferInfo, dest } // CopyTextureToBuffer copies data from a texture to a buffer. +// Accepts gogpu/wgpu-compatible types: src *Texture, dst *Buffer, regions []BufferTextureCopy. +// Each region specifies the buffer layout, texture subresource origin, and copy extent. // Errors are reported via Device error scopes, not as return values. -func (enc *CommandEncoder) CopyTextureToBuffer(source *TexelCopyTextureInfo, destination *TexelCopyBufferInfo, copySize *gputypes.Extent3D) { +func (enc *CommandEncoder) CopyTextureToBuffer(src *Texture, dst *Buffer, regions []BufferTextureCopy) { + mustInit() + if enc == nil || enc.handle == 0 || src == nil || dst == nil || len(regions) == 0 { + return + } + for i := range regions { + r := ®ions[i] + srcWire := r.TextureBase.toWire() + dstWire := TexelCopyBufferInfo{ + Layout: TexelCopyBufferLayout{ + Offset: r.BufferLayout.Offset, + BytesPerRow: r.BufferLayout.BytesPerRow, + RowsPerImage: r.BufferLayout.RowsPerImage, + }, + Buffer: dst.handle, + } + size := r.Size + procCommandEncoderCopyTextureToBuffer.Call( //nolint:errcheck + enc.handle, + uintptr(unsafe.Pointer(&srcWire)), + uintptr(unsafe.Pointer(&dstWire)), + uintptr(unsafe.Pointer(&size)), + ) + } +} + +// CopyTextureToBufferRaw copies data from a texture to a buffer using low-level wire types. +// Prefer [CopyTextureToBuffer] for new code. +func (enc *CommandEncoder) CopyTextureToBufferRaw(source *TexelCopyTextureInfo, destination *TexelCopyBufferInfo, copySize *gputypes.Extent3D) { mustInit() if enc == nil || enc.handle == 0 || source == nil || destination == nil || copySize == nil { return @@ -181,8 +261,31 @@ func (enc *CommandEncoder) CopyTextureToBuffer(source *TexelCopyTextureInfo, des } // CopyTextureToTexture copies data from one texture to another. +// Accepts gogpu/wgpu-compatible types: src *Texture, dst *Texture, regions []TextureCopy. +// Each region specifies the source and destination subresource origins and copy extent. // Errors are reported via Device error scopes, not as return values. -func (enc *CommandEncoder) CopyTextureToTexture(source *TexelCopyTextureInfo, destination *TexelCopyTextureInfo, copySize *gputypes.Extent3D) { +func (enc *CommandEncoder) CopyTextureToTexture(src, dst *Texture, regions []TextureCopy) { + mustInit() + if enc == nil || enc.handle == 0 || src == nil || dst == nil || len(regions) == 0 { + return + } + for i := range regions { + r := ®ions[i] + srcWire := r.Source.toWire() + dstWire := r.Destination.toWire() + size := r.Size + procCommandEncoderCopyTextureToTexture.Call( //nolint:errcheck + enc.handle, + uintptr(unsafe.Pointer(&srcWire)), + uintptr(unsafe.Pointer(&dstWire)), + uintptr(unsafe.Pointer(&size)), + ) + } +} + +// CopyTextureToTextureRaw copies data from one texture to another using low-level wire types. +// Prefer [CopyTextureToTexture] for new code. +func (enc *CommandEncoder) CopyTextureToTextureRaw(source *TexelCopyTextureInfo, destination *TexelCopyTextureInfo, copySize *gputypes.Extent3D) { mustInit() if enc == nil || enc.handle == 0 || source == nil || destination == nil || copySize == nil { return @@ -196,24 +299,29 @@ func (enc *CommandEncoder) CopyTextureToTexture(source *TexelCopyTextureInfo, de } // Finish finishes recording and returns a command buffer. -func (enc *CommandEncoder) Finish(desc *CommandBufferDescriptor) *CommandBuffer { - mustInit() +// The optional desc argument allows setting a label; pass nothing for defaults. +// This variadic signature matches the gogpu/wgpu API for compatibility. +// Returns an error if the FFI call fails or the encoder is nil. +func (enc *CommandEncoder) Finish(desc ...*CommandBufferDescriptor) (*CommandBuffer, error) { + if err := checkInit(); err != nil { + return nil, err + } if enc == nil || enc.handle == 0 { - return nil + return nil, &WGPUError{Op: "CommandEncoder.Finish", Message: "encoder is nil or released"} } var descPtr uintptr - if desc != nil { - descPtr = uintptr(unsafe.Pointer(desc)) + if len(desc) > 0 && desc[0] != nil { + descPtr = uintptr(unsafe.Pointer(desc[0])) } handle, _, _ := procCommandEncoderFinish.Call( enc.handle, descPtr, ) if handle == 0 { - return nil + return nil, &WGPUError{Op: "CommandEncoder.Finish", Message: "wgpu returned null handle"} } trackResource(handle, "CommandBuffer") - return &CommandBuffer{handle: handle} + return &CommandBuffer{handle: handle}, nil } // Release releases the command encoder. @@ -346,20 +454,28 @@ func (cpe *ComputePassEncoder) Release() { func (cpe *ComputePassEncoder) Handle() uintptr { return cpe.handle } // Submit submits command buffers for execution. -func (q *Queue) Submit(commands ...*CommandBuffer) { +// Returns the submission index (uint64) and nil on success. The submission +// index can be used with Device.Poll to track when work completes. +// Matches gogpu/wgpu Queue.Submit(commands ...*CommandBuffer) (uint64, error). +func (q *Queue) Submit(commands ...*CommandBuffer) (uint64, error) { mustInit() if q == nil || q.handle == 0 || len(commands) == 0 { - return + return 0, nil } handles := make([]uintptr, len(commands)) for i, cmd := range commands { - handles[i] = cmd.handle + if cmd != nil { + handles[i] = cmd.handle + } } - procQueueSubmit.Call( //nolint:errcheck + // wgpuQueueSubmitForIndex is a wgpu-native extension that returns WGPUSubmissionIndex (uint64). + // This enables callers to poll for GPU completion of a specific submission. + submissionIndex, _, _ := procQueueSubmitForIndex.Call( q.handle, uintptr(len(handles)), uintptr(unsafe.Pointer(&handles[0])), ) + return uint64(submissionIndex), nil } // Release releases the command buffer. diff --git a/wgpu/command_new_test.go b/wgpu/command_new_test.go index c14f31a..1da1f3c 100644 --- a/wgpu/command_new_test.go +++ b/wgpu/command_new_test.go @@ -28,21 +28,21 @@ func TestCommandEncoderClearBuffer(t *testing.T) { // Create a buffer bufferDesc := BufferDescriptor{ - Label: EmptyStringView(), + Label: "", Usage: gputypes.BufferUsageCopyDst | gputypes.BufferUsageCopySrc, Size: 256, - MappedAtCreation: False, + MappedAtCreation: false, } - buffer := device.CreateBuffer(&bufferDesc) - if buffer == nil { - t.Fatal("Failed to create buffer") + buffer, err := device.CreateBuffer(&bufferDesc) + if err != nil { + t.Fatal("Failed to create buffer:", err) } defer buffer.Release() // Create command encoder - encoder := device.CreateCommandEncoder(nil) - if encoder == nil { - t.Fatal("Failed to create command encoder") + encoder, err := device.CreateCommandEncoder(nil) + if err != nil { + t.Fatal("Failed to create command encoder:", err) } defer encoder.Release() @@ -50,14 +50,14 @@ func TestCommandEncoderClearBuffer(t *testing.T) { encoder.ClearBuffer(buffer, 0, 256) // Finish command buffer - cmdBuffer := encoder.Finish(nil) - if cmdBuffer == nil { - t.Fatal("Failed to finish command encoder") + cmdBuffer, err := encoder.Finish(nil) + if err != nil { + t.Fatal("Failed to finish command encoder:", err) } defer cmdBuffer.Release() // Submit - queue := device.GetQueue() + queue := device.Queue() if queue == nil { t.Fatal("Failed to get queue") } @@ -88,9 +88,9 @@ func TestCommandEncoderDebugMarkers(t *testing.T) { defer device.Release() // Create command encoder - encoder := device.CreateCommandEncoder(nil) - if encoder == nil { - t.Fatal("Failed to create command encoder") + encoder, err := device.CreateCommandEncoder(nil) + if err != nil { + t.Fatal("Failed to create command encoder:", err) } defer encoder.Release() @@ -108,14 +108,14 @@ func TestCommandEncoderDebugMarkers(t *testing.T) { encoder.PopDebugGroup() // Finish command buffer - cmdBuffer := encoder.Finish(nil) - if cmdBuffer == nil { - t.Fatal("Failed to finish command encoder") + cmdBuffer, err := encoder.Finish(nil) + if err != nil { + t.Fatal("Failed to finish command encoder:", err) } defer cmdBuffer.Release() // Submit - queue := device.GetQueue() + queue := device.Queue() if queue == nil { t.Fatal("Failed to get queue") } @@ -147,7 +147,7 @@ func TestTextureQueryAPIs(t *testing.T) { // Create a texture textureDesc := TextureDescriptor{ - Label: EmptyStringView(), + Label: "", Usage: gputypes.TextureUsageTextureBinding | gputypes.TextureUsageCopyDst, Size: gputypes.Extent3D{ Width: 512, @@ -158,34 +158,34 @@ func TestTextureQueryAPIs(t *testing.T) { MipLevelCount: 3, SampleCount: 1, } - texture := device.CreateTexture(&textureDesc) - if texture == nil { - t.Fatal("Failed to create texture") + texture, err := device.CreateTexture(&textureDesc) + if err != nil { + t.Fatal("Failed to create texture:", err) } defer texture.Release() // Test query methods - width := texture.GetWidth() + width := texture.Width() if width != 512 { t.Errorf("Expected width 512, got %d", width) } - height := texture.GetHeight() + height := texture.Height() if height != 256 { t.Errorf("Expected height 256, got %d", height) } - depthOrLayers := texture.GetDepthOrArrayLayers() + depthOrLayers := texture.DepthOrArrayLayers() if depthOrLayers != 4 { t.Errorf("Expected depth/layers 4, got %d", depthOrLayers) } - mipLevels := texture.GetMipLevelCount() + mipLevels := texture.MipLevelCount() if mipLevels != 3 { t.Errorf("Expected mip levels 3, got %d", mipLevels) } - format := texture.GetFormat() + format := texture.Format() if format != gputypes.TextureFormatRGBA8Unorm { t.Errorf("Expected format RGBA8Unorm, got %d", format) } @@ -196,23 +196,23 @@ func TestTextureQueryAPIsNil(t *testing.T) { var texture *Texture // All methods should return zero values and not panic - if width := texture.GetWidth(); width != 0 { + if width := texture.Width(); width != 0 { t.Errorf("Expected width 0 for nil texture, got %d", width) } - if height := texture.GetHeight(); height != 0 { + if height := texture.Height(); height != 0 { t.Errorf("Expected height 0 for nil texture, got %d", height) } - if depth := texture.GetDepthOrArrayLayers(); depth != 0 { + if depth := texture.DepthOrArrayLayers(); depth != 0 { t.Errorf("Expected depth 0 for nil texture, got %d", depth) } - if mips := texture.GetMipLevelCount(); mips != 0 { + if mips := texture.MipLevelCount(); mips != 0 { t.Errorf("Expected mip levels 0 for nil texture, got %d", mips) } - if format := texture.GetFormat(); format != gputypes.TextureFormatUndefined { + if format := texture.Format(); format != gputypes.TextureFormatUndefined { t.Errorf("Expected format Undefined for nil texture, got %d", format) } } @@ -237,9 +237,9 @@ func TestClearBufferNil(t *testing.T) { } defer device.Release() - encoder := device.CreateCommandEncoder(nil) - if encoder == nil { - t.Fatal("Failed to create command encoder") + encoder, err := device.CreateCommandEncoder(nil) + if err != nil { + t.Fatal("Failed to create command encoder:", err) } defer encoder.Release() @@ -267,9 +267,9 @@ func TestDebugMarkersEmptyStrings(t *testing.T) { } defer device.Release() - encoder := device.CreateCommandEncoder(nil) - if encoder == nil { - t.Fatal("Failed to create command encoder") + encoder, err := device.CreateCommandEncoder(nil) + if err != nil { + t.Fatal("Failed to create command encoder:", err) } defer encoder.Release() diff --git a/wgpu/command_test.go b/wgpu/command_test.go index a206c85..5dcebc7 100644 --- a/wgpu/command_test.go +++ b/wgpu/command_test.go @@ -39,9 +39,9 @@ func TestCreateCommandEncoder(t *testing.T) { defer device.Release() t.Log("Creating command encoder...") - encoder := device.CreateCommandEncoder(nil) - if encoder == nil { - t.Fatal("CreateCommandEncoder returned nil") + encoder, err := device.CreateCommandEncoder(nil) + if err != nil { + t.Fatalf("CreateCommandEncoder failed: %v", err) } defer encoder.Release() @@ -71,15 +71,15 @@ func TestCommandEncoderFinish(t *testing.T) { } defer device.Release() - encoder := device.CreateCommandEncoder(nil) - if encoder == nil { - t.Fatal("CreateCommandEncoder returned nil") + encoder, err := device.CreateCommandEncoder(nil) + if err != nil { + t.Fatalf("CreateCommandEncoder failed: %v", err) } t.Log("Finishing command encoder...") - cmdBuffer := encoder.Finish(nil) - if cmdBuffer == nil { - t.Fatal("Finish returned nil") + cmdBuffer, err := encoder.Finish(nil) + if err != nil { + t.Fatalf("Finish failed: %v", err) } defer cmdBuffer.Release() @@ -105,23 +105,23 @@ func TestComputePassDispatch(t *testing.T) { } defer device.Release() - queue := device.GetQueue() + queue := device.Queue() if queue == nil { - t.Fatal("GetQueue returned nil") + t.Fatal("Queue returned nil") } defer queue.Release() // Create shader - shader := device.CreateShaderModuleWGSL(computeDoubleShader) - if shader == nil { - t.Fatal("CreateShaderModuleWGSL returned nil") + shader, err := device.CreateShaderModuleWGSL(computeDoubleShader) + if err != nil { + t.Fatalf("CreateShaderModuleWGSL failed: %v", err) } defer shader.Release() // Create compute pipeline with auto layout - pipeline := device.CreateComputePipelineSimple(nil, shader, "main") - if pipeline == nil { - t.Fatal("CreateComputePipelineSimple returned nil") + pipeline, err := device.CreateComputePipelineSimple(nil, shader, "main") + if err != nil { + t.Fatalf("CreateComputePipelineSimple failed: %v", err) } defer pipeline.Release() @@ -136,14 +136,14 @@ func TestComputePassDispatch(t *testing.T) { const numElements = 64 bufferSize := uint64(numElements * 4) // 4 bytes per float32 - buffer := device.CreateBuffer(&BufferDescriptor{ - Label: EmptyStringView(), + buffer, err := device.CreateBuffer(&BufferDescriptor{ + Label: "", Usage: gputypes.BufferUsageStorage | gputypes.BufferUsageCopySrc | gputypes.BufferUsageCopyDst, Size: bufferSize, - MappedAtCreation: True, + MappedAtCreation: true, }) - if buffer == nil { - t.Fatal("CreateBuffer returned nil") + if err != nil { + t.Fatalf("CreateBuffer failed: %v", err) } defer buffer.Release() @@ -163,22 +163,22 @@ func TestComputePassDispatch(t *testing.T) { entries := []BindGroupEntry{ BufferBindingEntry(0, buffer, 0, bufferSize), } - bindGroup := device.CreateBindGroupSimple(bindGroupLayout, entries) - if bindGroup == nil { - t.Fatal("CreateBindGroupSimple returned nil") + bindGroup, err := device.CreateBindGroupSimple(bindGroupLayout, entries) + if err != nil { + t.Fatalf("CreateBindGroupSimple failed: %v", err) } defer bindGroup.Release() // Create and execute compute pass t.Log("Creating compute pass...") - encoder := device.CreateCommandEncoder(nil) - if encoder == nil { - t.Fatal("CreateCommandEncoder returned nil") + encoder, err := device.CreateCommandEncoder(nil) + if err != nil { + t.Fatalf("CreateCommandEncoder failed: %v", err) } - pass := encoder.BeginComputePass(nil) - if pass == nil { - t.Fatal("BeginComputePass returned nil") + pass, err := encoder.BeginComputePass(nil) + if err != nil { + t.Fatalf("BeginComputePass failed: %v", err) } pass.SetPipeline(pipeline) @@ -187,9 +187,9 @@ func TestComputePassDispatch(t *testing.T) { pass.End() pass.Release() - cmdBuffer := encoder.Finish(nil) - if cmdBuffer == nil { - t.Fatal("Finish returned nil") + cmdBuffer, err := encoder.Finish(nil) + if err != nil { + t.Fatalf("Finish failed: %v", err) } t.Log("Submitting compute work...") @@ -219,23 +219,23 @@ func TestFullComputeExample(t *testing.T) { } defer device.Release() - queue := device.GetQueue() + queue := device.Queue() if queue == nil { - t.Fatal("GetQueue returned nil") + t.Fatal("Queue returned nil") } defer queue.Release() // Create shader - shader := device.CreateShaderModuleWGSL(computeDoubleShader) - if shader == nil { - t.Fatal("CreateShaderModuleWGSL returned nil") + shader, err := device.CreateShaderModuleWGSL(computeDoubleShader) + if err != nil { + t.Fatalf("CreateShaderModuleWGSL failed: %v", err) } defer shader.Release() // Create compute pipeline - pipeline := device.CreateComputePipelineSimple(nil, shader, "main") - if pipeline == nil { - t.Fatal("CreateComputePipelineSimple returned nil") + pipeline, err := device.CreateComputePipelineSimple(nil, shader, "main") + if err != nil { + t.Fatalf("CreateComputePipelineSimple failed: %v", err) } defer pipeline.Release() @@ -249,14 +249,14 @@ func TestFullComputeExample(t *testing.T) { const numElements = 64 bufferSize := uint64(numElements * 4) - storageBuffer := device.CreateBuffer(&BufferDescriptor{ - Label: EmptyStringView(), + storageBuffer, err := device.CreateBuffer(&BufferDescriptor{ + Label: "", Usage: gputypes.BufferUsageStorage | gputypes.BufferUsageCopySrc | gputypes.BufferUsageCopyDst, Size: bufferSize, - MappedAtCreation: True, + MappedAtCreation: true, }) - if storageBuffer == nil { - t.Fatal("CreateBuffer (storage) returned nil") + if err != nil { + t.Fatalf("CreateBuffer (storage) failed: %v", err) } defer storageBuffer.Release() @@ -269,14 +269,14 @@ func TestFullComputeExample(t *testing.T) { storageBuffer.Unmap() // Create readback buffer - readbackBuffer := device.CreateBuffer(&BufferDescriptor{ - Label: EmptyStringView(), + readbackBuffer, err := device.CreateBuffer(&BufferDescriptor{ + Label: "", Usage: gputypes.BufferUsageMapRead | gputypes.BufferUsageCopyDst, Size: bufferSize, - MappedAtCreation: False, + MappedAtCreation: false, }) - if readbackBuffer == nil { - t.Fatal("CreateBuffer (readback) returned nil") + if err != nil { + t.Fatalf("CreateBuffer (readback) failed: %v", err) } defer readbackBuffer.Release() @@ -284,16 +284,22 @@ func TestFullComputeExample(t *testing.T) { entries := []BindGroupEntry{ BufferBindingEntry(0, storageBuffer, 0, bufferSize), } - bindGroup := device.CreateBindGroupSimple(bindGroupLayout, entries) - if bindGroup == nil { - t.Fatal("CreateBindGroupSimple returned nil") + bindGroup, err := device.CreateBindGroupSimple(bindGroupLayout, entries) + if err != nil { + t.Fatalf("CreateBindGroupSimple failed: %v", err) } defer bindGroup.Release() // Run compute t.Log("Running compute shader...") - encoder := device.CreateCommandEncoder(nil) - pass := encoder.BeginComputePass(nil) + encoder, err := device.CreateCommandEncoder(nil) + if err != nil { + t.Fatalf("CreateCommandEncoder failed: %v", err) + } + pass, err := encoder.BeginComputePass(nil) + if err != nil { + t.Fatalf("BeginComputePass failed: %v", err) + } pass.SetPipeline(pipeline) pass.SetBindGroup(0, bindGroup, nil) pass.DispatchWorkgroups(1, 1, 1) @@ -303,7 +309,10 @@ func TestFullComputeExample(t *testing.T) { // Copy result to readback buffer encoder.CopyBufferToBuffer(storageBuffer, 0, readbackBuffer, 0, bufferSize) - cmdBuffer := encoder.Finish(nil) + cmdBuffer, err := encoder.Finish(nil) + if err != nil { + t.Fatalf("Finish failed: %v", err) + } queue.Submit(cmdBuffer) cmdBuffer.Release() @@ -311,10 +320,21 @@ func TestFullComputeExample(t *testing.T) { // Map the readback buffer and verify results t.Log("Mapping readback buffer...") - err = readbackBuffer.MapAsync(device, MapModeRead, 0, bufferSize) + mapPending, err := readbackBuffer.MapAsync(MapModeRead, 0, bufferSize) if err != nil { t.Fatalf("MapAsync failed: %v", err) } + // Drive polling until resolved (requires GPU — test is GPU-gated). + for { + if ready, werr := mapPending.Status(); ready { + if werr != nil { + t.Fatalf("MapAsync resolved with error: %v", werr) + } + break + } + device.Poll(false) + } + mapPending.Release() resultPtr := readbackBuffer.GetMappedRange(0, bufferSize) if resultPtr == nil { @@ -355,21 +375,21 @@ func TestCopyBufferToBuffer(t *testing.T) { } defer device.Release() - queue := device.GetQueue() + queue := device.Queue() if queue == nil { - t.Fatal("GetQueue returned nil") + t.Fatal("Queue returned nil") } defer queue.Release() // Create source buffer with data - srcBuffer := device.CreateBuffer(&BufferDescriptor{ - Label: EmptyStringView(), + srcBuffer, err := device.CreateBuffer(&BufferDescriptor{ + Label: "", Usage: gputypes.BufferUsageCopySrc | gputypes.BufferUsageCopyDst, Size: 256, - MappedAtCreation: True, + MappedAtCreation: true, }) - if srcBuffer == nil { - t.Fatal("CreateBuffer (src) returned nil") + if err != nil { + t.Fatalf("CreateBuffer (src) failed: %v", err) } defer srcBuffer.Release() @@ -381,22 +401,28 @@ func TestCopyBufferToBuffer(t *testing.T) { srcBuffer.Unmap() // Create destination buffer - dstBuffer := device.CreateBuffer(&BufferDescriptor{ - Label: EmptyStringView(), + dstBuffer, err := device.CreateBuffer(&BufferDescriptor{ + Label: "", Usage: gputypes.BufferUsageCopyDst | gputypes.BufferUsageMapRead, Size: 256, - MappedAtCreation: False, + MappedAtCreation: false, }) - if dstBuffer == nil { - t.Fatal("CreateBuffer (dst) returned nil") + if err != nil { + t.Fatalf("CreateBuffer (dst) failed: %v", err) } defer dstBuffer.Release() // Copy buffer t.Log("Copying buffer...") - encoder := device.CreateCommandEncoder(nil) + encoder, err := device.CreateCommandEncoder(nil) + if err != nil { + t.Fatalf("CreateCommandEncoder failed: %v", err) + } encoder.CopyBufferToBuffer(srcBuffer, 0, dstBuffer, 0, 256) - cmdBuffer := encoder.Finish(nil) + cmdBuffer, err := encoder.Finish(nil) + if err != nil { + t.Fatalf("Finish failed: %v", err) + } queue.Submit(cmdBuffer) cmdBuffer.Release() @@ -422,9 +448,9 @@ func TestQueueSubmitMultiple(t *testing.T) { } defer device.Release() - queue := device.GetQueue() + queue := device.Queue() if queue == nil { - t.Fatal("GetQueue returned nil") + t.Fatal("Queue returned nil") } defer queue.Release() @@ -432,10 +458,13 @@ func TestQueueSubmitMultiple(t *testing.T) { t.Log("Creating multiple command buffers...") var cmdBuffers []*CommandBuffer for i := 0; i < 3; i++ { - encoder := device.CreateCommandEncoder(nil) - cmdBuffer := encoder.Finish(nil) - if cmdBuffer == nil { - t.Fatalf("Finish returned nil for buffer %d", i) + encoder, err := device.CreateCommandEncoder(nil) + if err != nil { + t.Fatalf("CreateCommandEncoder failed for buffer %d: %v", i, err) + } + cmdBuffer, err := encoder.Finish(nil) + if err != nil { + t.Fatalf("Finish failed for buffer %d: %v", i, err) } cmdBuffers = append(cmdBuffers, cmdBuffer) } diff --git a/wgpu/convert.go b/wgpu/convert.go index 978211f..2c85c07 100644 --- a/wgpu/convert.go +++ b/wgpu/convert.go @@ -1,9 +1,35 @@ -// convert.go provides conversion functions between gputypes (webgpu.h spec) -// and wgpu-native internal values. +// convert.go provides conversion functions between gputypes and wgpu-native v29 wire values. // -// Background: gputypes follows an older webgpu.h schema where enums start at 0. -// wgpu-native v24+ uses a newer schema with BindingNotUsed=0, shifting other values by +1. -// TextureFormat also differs due to removal of R16Unorm/R16Snorm from the spec. +// # Why conversions are needed +// +// gputypes follows the WebGPU JS specification numbering where enum values start at +// Undefined=0. wgpu-native v29 introduces BindingNotUsed=0 sentinels for binding-related +// enums, shifting all other values by +1. VertexFormat and VertexStepMode have additional +// structural differences (missing single-component variants, removed VertexBufferNotUsed). +// +// # Enums requiring explicit conversion (converters in this file) +// +// - BufferBindingType: BindingNotUsed=0 in v29 shifts Undefined/Uniform/Storage/ReadOnlyStorage by +1. +// - SamplerBindingType: same +1 shift due to BindingNotUsed=0. +// - TextureSampleType: same +1 shift. +// - StorageTextureAccess: same +1 shift. +// - VertexFormat: gputypes omits single-component 8/16-bit variants (Uint8, Sint8, +// Unorm8, Snorm8, Uint16, Sint16, Unorm16, Snorm16, Float16) added in v29, +// causing a non-trivial numbering gap throughout the enum. +// - VertexStepMode: gputypes has VertexBufferNotUsed=1 (removed in v29); +// v29 maps Vertex=1, Instance=2 instead of gputypes Vertex=2, Instance=3. +// +// # Enums matching v29 exactly — use direct uint32 cast, no converter needed +// +// - TextureFormat (gputypes v0.3.0 matches v29 exactly, including R16*/RG16*/RGBA16* Unorm/Snorm) +// - TextureViewDimension, TextureDimension, TextureAspect +// - LoadOp (Undefined=0, Load=1, Clear=2), StoreOp (Undefined=0, Store=1, Discard=2) +// - BlendFactor values 0x00–0x0D match v29; gputypes lacks Src1* (0x0E–0x11) but those +// are unused via gputypes API so no conversion is needed. +// - BlendOperation, PrimitiveTopology, FrontFace, CullMode +// - All bitflags: BufferUsage, TextureUsage, ShaderStage, ColorWriteMask, MapMode +// - FilterMode, MipmapFilterMode, AddressMode, CompareFunction, StencilOperation +// - IndexFormat, PresentMode, CompositeAlphaMode, PowerPreference package wgpu @@ -12,15 +38,13 @@ import "github.com/gogpu/gputypes" // ============================================================================= // BufferBindingType conversion // gputypes: Undefined=0, Uniform=1, Storage=2, ReadOnlyStorage=3 -// wgpu-native: BindingNotUsed=0, Undefined=1, Uniform=2, Storage=3, ReadOnlyStorage=4 +// wgpu-native v29: BindingNotUsed=0, Undefined=1, Uniform=2, Storage=3, ReadOnlyStorage=4 +// Mapping: 0→0 (BindingNotUsed), others +1 // ============================================================================= func toWGPUBufferBindingType(t gputypes.BufferBindingType) uint32 { - // gputypes: Undefined=0, Uniform=1, Storage=2, ReadOnlyStorage=3 - // wgpu-native: BindingNotUsed=0, Undefined=1, Uniform=2, Storage=3, ReadOnlyStorage=4 - // Keep 0 as 0 (BindingNotUsed), shift others by +1 if t == 0 { - return 0 // BindingNotUsed + return 0 // gputypes Undefined=0 maps to wgpu BindingNotUsed=0 } return uint32(t) + 1 } @@ -28,15 +52,13 @@ func toWGPUBufferBindingType(t gputypes.BufferBindingType) uint32 { // ============================================================================= // SamplerBindingType conversion // gputypes: Undefined=0, Filtering=1, NonFiltering=2, Comparison=3 -// wgpu-native: BindingNotUsed=0, Undefined=1, Filtering=2, NonFiltering=3, Comparison=4 +// wgpu-native v29: BindingNotUsed=0, Undefined=1, Filtering=2, NonFiltering=3, Comparison=4 +// Mapping: 0→0 (BindingNotUsed), others +1 // ============================================================================= func toWGPUSamplerBindingType(t gputypes.SamplerBindingType) uint32 { - // gputypes: Undefined=0, Filtering=1, NonFiltering=2, Comparison=3 - // wgpu-native: BindingNotUsed=0, Undefined=1, Filtering=2, NonFiltering=3, Comparison=4 - // Keep 0 as 0 (BindingNotUsed), shift others by +1 if t == 0 { - return 0 // BindingNotUsed + return 0 // gputypes Undefined=0 maps to wgpu BindingNotUsed=0 } return uint32(t) + 1 } @@ -44,398 +66,99 @@ func toWGPUSamplerBindingType(t gputypes.SamplerBindingType) uint32 { // ============================================================================= // TextureSampleType conversion // gputypes: Undefined=0, Float=1, UnfilterableFloat=2, Depth=3, Sint=4, Uint=5 -// wgpu-native: BindingNotUsed=0, Undefined=1, Float=2, UnfilterableFloat=3, Depth=4, Sint=5, Uint=6 +// wgpu-native v29: BindingNotUsed=0, Undefined=1, Float=2, UnfilterableFloat=3, Depth=4, Sint=5, Uint=6 +// Mapping: 0→0 (BindingNotUsed), others +1 // ============================================================================= func toWGPUTextureSampleType(t gputypes.TextureSampleType) uint32 { - // gputypes: Undefined=0, Float=1, UnfilterableFloat=2, Depth=3, Sint=4, Uint=5 - // wgpu-native: BindingNotUsed=0, Undefined=1, Float=2, UnfilterableFloat=3, Depth=4, Sint=5, Uint=6 - // Keep 0 as 0 (BindingNotUsed), shift others by +1 if t == 0 { - return 0 // BindingNotUsed + return 0 // gputypes Undefined=0 maps to wgpu BindingNotUsed=0 } return uint32(t) + 1 } -// ============================================================================= -// TextureViewDimension conversion -// gputypes: Undefined=0, 1D=1, 2D=2, 2DArray=3, Cube=4, CubeArray=5, 3D=6 -// wgpu-native v27 (bac5208): Undefined=0, 1D=1, 2D=2, 2DArray=3, Cube=4, CubeArray=5, 3D=6 -// Values match! No conversion needed. -// ============================================================================= - -func toWGPUTextureViewDimension(t gputypes.TextureViewDimension) uint32 { - // Values match between gputypes and wgpu-native v27 - no conversion needed - return uint32(t) -} - // ============================================================================= // StorageTextureAccess conversion // gputypes: Undefined=0, WriteOnly=1, ReadOnly=2, ReadWrite=3 -// wgpu-native: BindingNotUsed=0, Undefined=1, WriteOnly=2, ReadOnly=3, ReadWrite=4 +// wgpu-native v29: BindingNotUsed=0, Undefined=1, WriteOnly=2, ReadOnly=3, ReadWrite=4 +// Mapping: 0→0 (BindingNotUsed), others +1 // ============================================================================= func toWGPUStorageTextureAccess(t gputypes.StorageTextureAccess) uint32 { - // gputypes: Undefined=0, WriteOnly=1, ReadOnly=2, ReadWrite=3 - // wgpu-native: BindingNotUsed=0, Undefined=1, WriteOnly=2, ReadOnly=3, ReadWrite=4 - // Keep 0 as 0 (BindingNotUsed), shift others by +1 if t == 0 { - return 0 // BindingNotUsed + return 0 // gputypes Undefined=0 maps to wgpu BindingNotUsed=0 } return uint32(t) + 1 } -// ============================================================================= -// TextureFormat conversion -// gputypes follows older webgpu.h with R16Unorm/R16Snorm/RG16Unorm/RG16Snorm (4 formats). -// wgpu-native v24+ uses newer spec where these were moved to extensions. -// Result: formats after R8Sint are shifted by -2, and after RG8Sint by another -2. -// ============================================================================= - -func toWGPUTextureFormat(f gputypes.TextureFormat) uint32 { - // gputypes values (old spec with R16/RG16 Unorm/Snorm in core): - // 0=Undefined, 1-4=R8*, 5-6=R16Unorm/Snorm, 7-9=R16Uint/Sint/Float, - // 10-13=RG8*, 14-16=R32*, 17-18=RG16Unorm/Snorm, 19-21=RG16Uint/Sint/Float, - // 22-26=RGBA8*, 27-28=BGRA8*, 29=RGB10A2Uint, 30=RGB10A2Unorm, - // 31=RG11B10Ufloat, 32=RGB9E5Ufloat, 33-35=RG32*, 36-37=RGBA16Unorm/Snorm, - // 38-40=RGBA16Uint/Sint/Float, 41-43=RGBA32*, 44=Stencil8, 45=Depth16Unorm, - // 46=Depth24Plus, 47=Depth24PlusStencil8, 48=Depth32Float, 49=Depth32FloatStencil8 - // - // webgpu-headers values (new spec - R16/RG16 Unorm/Snorm moved to extensions): - // 0=Undefined, 1-4=R8*, 5-7=R16Uint/Sint/Float (NO R16Unorm/Snorm!), - // 8-11=RG8*, 12-14=R32*, 15-17=RG16Uint/Sint/Float (NO RG16Unorm/Snorm!), - // 18-22=RGBA8*, 23-24=BGRA8*, 25=RGB10A2Uint, 26=RGB10A2Unorm, - // 27=RG11B10Ufloat, 28=RGB9E5Ufloat, 29-31=RG32*, 32-34=RGBA16Uint/Sint/Float, - // (NO RGBA16Unorm/Snorm!), 35-37=RGBA32*, 38=Stencil8, 39=Depth16Unorm, - // 40=Depth24Plus, 41=Depth24PlusStencil8, 42=Depth32Float, 43=Depth32FloatStencil8 - - // Use a lookup table for common formats - switch f { - case gputypes.TextureFormatUndefined: - return 0 - - // 8-bit R formats (1-4 → 1-4, same) - case gputypes.TextureFormatR8Unorm: - return 1 - case gputypes.TextureFormatR8Snorm: - return 2 - case gputypes.TextureFormatR8Uint: - return 3 - case gputypes.TextureFormatR8Sint: - return 4 - - // R16 formats: gputypes has Unorm/Snorm at 5-6, wgpu-native doesn't - // R16Uint/Sint/Float: gputypes 7-9 → wgpu-native 5-7 - case gputypes.TextureFormatR16Uint: - return 5 - case gputypes.TextureFormatR16Sint: - return 6 - case gputypes.TextureFormatR16Float: - return 7 - - // RG8 formats: gputypes 10-13 → wgpu-native 8-11 - case gputypes.TextureFormatRG8Unorm: - return 8 - case gputypes.TextureFormatRG8Snorm: - return 9 - case gputypes.TextureFormatRG8Uint: - return 10 - case gputypes.TextureFormatRG8Sint: - return 11 - - // R32 formats: gputypes 14-16 → wgpu-native 12-14 - case gputypes.TextureFormatR32Float: - return 12 - case gputypes.TextureFormatR32Uint: - return 13 - case gputypes.TextureFormatR32Sint: - return 14 - - // RG16 formats: gputypes has Unorm/Snorm at 17-18, wgpu-native doesn't - // RG16Uint/Sint/Float: gputypes 19-21 → wgpu-native 15-17 - case gputypes.TextureFormatRG16Uint: - return 15 - case gputypes.TextureFormatRG16Sint: - return 16 - case gputypes.TextureFormatRG16Float: - return 17 - - // RGBA8 formats: gputypes 22-26 → wgpu-native 18-22 - case gputypes.TextureFormatRGBA8Unorm: - return 18 - case gputypes.TextureFormatRGBA8UnormSrgb: - return 19 - case gputypes.TextureFormatRGBA8Snorm: - return 20 - case gputypes.TextureFormatRGBA8Uint: - return 21 - case gputypes.TextureFormatRGBA8Sint: - return 22 - - // BGRA8 formats: gputypes 27-28 → wgpu-native 23-24 - case gputypes.TextureFormatBGRA8Unorm: - return 23 - case gputypes.TextureFormatBGRA8UnormSrgb: - return 24 - - // Packed formats: gputypes 29-32 → wgpu-native 25-28 - case gputypes.TextureFormatRGB10A2Uint: - return 25 - case gputypes.TextureFormatRGB10A2Unorm: - return 26 - case gputypes.TextureFormatRG11B10Ufloat: - return 27 - case gputypes.TextureFormatRGB9E5Ufloat: - return 28 - - // RG32 formats: gputypes 33-35 → wgpu-native 29-31 - case gputypes.TextureFormatRG32Float: - return 29 - case gputypes.TextureFormatRG32Uint: - return 30 - case gputypes.TextureFormatRG32Sint: - return 31 - - // RGBA16 formats: gputypes has Unorm/Snorm at 36-37, wgpu-native doesn't - // RGBA16Uint/Sint/Float: gputypes 38-40 → wgpu-native 32-34 - case gputypes.TextureFormatRGBA16Uint: - return 32 - case gputypes.TextureFormatRGBA16Sint: - return 33 - case gputypes.TextureFormatRGBA16Float: - return 34 - - // RGBA32 formats: gputypes 41-43 → wgpu-native 35-37 - case gputypes.TextureFormatRGBA32Float: - return 35 - case gputypes.TextureFormatRGBA32Uint: - return 36 - case gputypes.TextureFormatRGBA32Sint: - return 37 - - // Depth/Stencil formats: gputypes 44-49 → wgpu-native 38-43 - case gputypes.TextureFormatStencil8: - return 38 - case gputypes.TextureFormatDepth16Unorm: - return 39 - case gputypes.TextureFormatDepth24Plus: - return 40 - case gputypes.TextureFormatDepth24PlusStencil8: - return 41 - case gputypes.TextureFormatDepth32Float: - return 42 - case gputypes.TextureFormatDepth32FloatStencil8: - return 43 - - default: - // For compressed formats and others, try simple mapping - // Compressed formats start at higher values and may need individual mapping - return uint32(f) - } -} - -// fromWGPUTextureFormat converts a wgpu-native TextureFormat value to gputypes. -// This is the reverse of toWGPUTextureFormat. -func fromWGPUTextureFormat(f uint32) gputypes.TextureFormat { - switch f { - case 0: - return gputypes.TextureFormatUndefined - - // 8-bit R formats (1-4 → 1-4, same) - case 1: - return gputypes.TextureFormatR8Unorm - case 2: - return gputypes.TextureFormatR8Snorm - case 3: - return gputypes.TextureFormatR8Uint - case 4: - return gputypes.TextureFormatR8Sint - - // R16 formats: wgpu-native 5-7 → gputypes 7-9 - case 5: - return gputypes.TextureFormatR16Uint - case 6: - return gputypes.TextureFormatR16Sint - case 7: - return gputypes.TextureFormatR16Float - - // RG8 formats: wgpu-native 8-11 → gputypes 10-13 - case 8: - return gputypes.TextureFormatRG8Unorm - case 9: - return gputypes.TextureFormatRG8Snorm - case 10: - return gputypes.TextureFormatRG8Uint - case 11: - return gputypes.TextureFormatRG8Sint - - // R32 formats: wgpu-native 12-14 → gputypes 14-16 - case 12: - return gputypes.TextureFormatR32Float - case 13: - return gputypes.TextureFormatR32Uint - case 14: - return gputypes.TextureFormatR32Sint - - // RG16 formats: wgpu-native 15-17 → gputypes 19-21 - case 15: - return gputypes.TextureFormatRG16Uint - case 16: - return gputypes.TextureFormatRG16Sint - case 17: - return gputypes.TextureFormatRG16Float - - // RGBA8 formats: wgpu-native 18-22 → gputypes 22-26 - case 18: - return gputypes.TextureFormatRGBA8Unorm - case 19: - return gputypes.TextureFormatRGBA8UnormSrgb - case 20: - return gputypes.TextureFormatRGBA8Snorm - case 21: - return gputypes.TextureFormatRGBA8Uint - case 22: - return gputypes.TextureFormatRGBA8Sint - - // BGRA8 formats: wgpu-native 23-24 → gputypes 27-28 - case 23: - return gputypes.TextureFormatBGRA8Unorm - case 24: - return gputypes.TextureFormatBGRA8UnormSrgb - - // Packed formats: wgpu-native 25-28 → gputypes 29-32 - case 25: - return gputypes.TextureFormatRGB10A2Uint - case 26: - return gputypes.TextureFormatRGB10A2Unorm - case 27: - return gputypes.TextureFormatRG11B10Ufloat - case 28: - return gputypes.TextureFormatRGB9E5Ufloat - - // RG32 formats: wgpu-native 29-31 → gputypes 33-35 - case 29: - return gputypes.TextureFormatRG32Float - case 30: - return gputypes.TextureFormatRG32Uint - case 31: - return gputypes.TextureFormatRG32Sint - - // RGBA16 formats: wgpu-native 32-34 → gputypes 38-40 - case 32: - return gputypes.TextureFormatRGBA16Uint - case 33: - return gputypes.TextureFormatRGBA16Sint - case 34: - return gputypes.TextureFormatRGBA16Float - - // RGBA32 formats: wgpu-native 35-37 → gputypes 41-43 - case 35: - return gputypes.TextureFormatRGBA32Float - case 36: - return gputypes.TextureFormatRGBA32Uint - case 37: - return gputypes.TextureFormatRGBA32Sint - - // Depth/Stencil formats: wgpu-native 38-43 → gputypes 44-49 - case 38: - return gputypes.TextureFormatStencil8 - case 39: - return gputypes.TextureFormatDepth16Unorm - case 40: - return gputypes.TextureFormatDepth24Plus - case 41: - return gputypes.TextureFormatDepth24PlusStencil8 - case 42: - return gputypes.TextureFormatDepth32Float - case 43: - return gputypes.TextureFormatDepth32FloatStencil8 - - default: - // For unknown/compressed formats, return as-is - return gputypes.TextureFormat(f) - } -} - -// ============================================================================= -// Types that DON'T need conversion (bitflags or already matching) -// ============================================================================= - -// ShaderStage - bitflags, same values (but needs widening to uint64!) -// BufferUsage - bitflags, same values -// TextureUsage - bitflags, same values -// ColorWriteMask - bitflags, same values - -// ============================================================================= -// Simple enums that may need +1 shift (to be verified) -// ============================================================================= - -// PrimitiveTopology, IndexFormat, FrontFace, CullMode, VertexFormat, VertexStepMode, -// AddressMode, FilterMode, CompareFunction, BlendFactor, BlendOperation, StencilOperation, -// LoadOp, StoreOp, PresentMode, CompositeAlphaMode, TextureDimension -// -// These may or may not have BindingNotUsed/Undefined shift - needs verification. -// For now, we'll add converters as issues are discovered. - -func toWGPULoadOp(op gputypes.LoadOp) uint32 { - // gputypes: Undefined=0, Clear=1, Load=2 - // wgpu-native: Undefined=0, Load=1, Clear=2 (different order!) - switch op { - case gputypes.LoadOpClear: - return 2 // wgpu-native Clear - case gputypes.LoadOpLoad: - return 1 // wgpu-native Load - default: - return 0 // Undefined - } -} - -func toWGPUStoreOp(op gputypes.StoreOp) uint32 { - // gputypes: Undefined=0, Store=1, Discard=2 - // wgpu-native: Undefined=0, Store=1, Discard=2 (same!) - return uint32(op) -} - -// ============================================================================= -// TextureDimension conversion -// gputypes: Undefined=0, 1D=1, 2D=2, 3D=3 -// wgpu-native: Undefined=1, 1D=2, 2D=3, 3D=4 -// ============================================================================= - -func toWGPUTextureDimension(d gputypes.TextureDimension) uint32 { - // gputypes: Undefined=0, 1D=1, 2D=2, 3D=3 - // wgpu-native: Undefined=0, 1D=1, 2D=2, 3D=3 (SAME values!) - // TextureDimension does NOT have BindingNotUsed, so no +1 shift needed - return uint32(d) -} - // ============================================================================= // VertexStepMode conversion -// gputypes: Undefined=0, VertexBufferNotUsed=1, Vertex=2, Instance=3 -// wgpu-native: VertexBufferNotUsed=0, Undefined=1, Vertex=2, Instance=3 -// Note: Undefined and VertexBufferNotUsed are SWAPPED! +// gputypes v0.3.0: Undefined=0, VertexBufferNotUsed=1, Vertex=2, Instance=3 +// wgpu-native v29: Undefined=0, Vertex=1, Instance=2 +// (VertexBufferNotUsed was removed in v29; Undefined is the sentinel for "not used") +// +// Mapping: +// gputypes Undefined(0) → v29 Undefined(0) +// gputypes VertexBufferNotUsed(1) → v29 Undefined(0) [removed, treat as not used] +// gputypes Vertex(2) → v29 Vertex(1) +// gputypes Instance(3) → v29 Instance(2) // ============================================================================= func toWGPUVertexStepMode(m gputypes.VertexStepMode) uint32 { switch m { - case gputypes.VertexStepModeUndefined: - return 1 // wgpu-native Undefined - case gputypes.VertexStepModeVertexBufferNotUsed: - return 0 // wgpu-native VertexBufferNotUsed + case gputypes.VertexStepModeVertex: + return 1 // v29 Vertex + case gputypes.VertexStepModeInstance: + return 2 // v29 Instance default: - // Vertex=2, Instance=3 are the same - return uint32(m) + // VertexStepModeUndefined(0) and VertexStepModeVertexBufferNotUsed(1) + // both map to v29 Undefined(0) — buffer slot not used + return 0 } } // ============================================================================= // VertexFormat conversion -// gputypes has fewer formats (no single-component 8/16-bit). -// wgpu-native has Uint8, Sint8, Unorm8, Snorm8, Uint16, Sint16, Unorm16, Snorm16, Float16 -// which shift all subsequent values. // -// gputypes: Uint8x2=1, Uint8x4=2, Sint8x2=3, Sint8x4=4, Unorm8x2=5, Unorm8x4=6... -// wgpu-native: Uint8=1, Uint8x2=2, Uint8x4=3, Sint8=4, Sint8x2=5, Sint8x4=6... +// gputypes v0.3.0 omits single-component 8-bit and 16-bit variants. +// v29 adds: Uint8(1), Sint8(4), Unorm8(7), Snorm8(10), Uint16(13), Sint16(16), +// Unorm16(19), Snorm16(22), Float16(25), Unorm8x4BGRA(41). +// +// gputypes → v29 mapping (explicit table): +// +// gputypes v29 Format +// 0 0 Undefined +// 1 2 Uint8x2 +// 2 3 Uint8x4 +// 3 5 Sint8x2 +// 4 6 Sint8x4 +// 5 8 Unorm8x2 +// 6 9 Unorm8x4 +// 7 11 Snorm8x2 +// 8 12 Snorm8x4 +// 9 14 Uint16x2 +// 10 15 Uint16x4 +// 11 17 Sint16x2 +// 12 18 Sint16x4 +// 13 20 Unorm16x2 +// 14 21 Unorm16x4 +// 15 23 Snorm16x2 +// 16 24 Snorm16x4 +// 17 26 Float16x2 +// 18 27 Float16x4 +// 19 28 Float32 +// 20 29 Float32x2 +// 21 30 Float32x3 +// 22 31 Float32x4 +// 23 32 Uint32 +// 24 33 Uint32x2 +// 25 34 Uint32x3 +// 26 35 Uint32x4 +// 27 36 Sint32 +// 28 37 Sint32x2 +// 29 38 Sint32x3 +// 30 39 Sint32x4 +// 31 40 Unorm10_10_10_2 (gputypes: Unorm1010102) // ============================================================================= func toWGPUVertexFormat(f gputypes.VertexFormat) uint32 { @@ -443,83 +166,85 @@ func toWGPUVertexFormat(f gputypes.VertexFormat) uint32 { case gputypes.VertexFormatUndefined: return 0 - // 8-bit formats: gputypes lacks single-component + // 8-bit packed formats (gputypes lacks single-component variants) case gputypes.VertexFormatUint8x2: - return 2 // wgpu Uint8x2 + return 2 case gputypes.VertexFormatUint8x4: - return 3 // wgpu Uint8x4 + return 3 case gputypes.VertexFormatSint8x2: - return 5 // wgpu Sint8x2 + return 5 case gputypes.VertexFormatSint8x4: - return 6 // wgpu Sint8x4 + return 6 case gputypes.VertexFormatUnorm8x2: - return 8 // wgpu Unorm8x2 + return 8 case gputypes.VertexFormatUnorm8x4: - return 9 // wgpu Unorm8x4 + return 9 case gputypes.VertexFormatSnorm8x2: - return 11 // wgpu Snorm8x2 + return 11 case gputypes.VertexFormatSnorm8x4: - return 12 // wgpu Snorm8x4 + return 12 - // 16-bit formats: gputypes lacks single-component + // 16-bit packed formats (gputypes lacks single-component variants) case gputypes.VertexFormatUint16x2: - return 14 // wgpu Uint16x2 + return 14 case gputypes.VertexFormatUint16x4: - return 15 // wgpu Uint16x4 + return 15 case gputypes.VertexFormatSint16x2: - return 17 // wgpu Sint16x2 + return 17 case gputypes.VertexFormatSint16x4: - return 18 // wgpu Sint16x4 + return 18 case gputypes.VertexFormatUnorm16x2: - return 20 // wgpu Unorm16x2 + return 20 case gputypes.VertexFormatUnorm16x4: - return 21 // wgpu Unorm16x4 + return 21 case gputypes.VertexFormatSnorm16x2: - return 23 // wgpu Snorm16x2 + return 23 case gputypes.VertexFormatSnorm16x4: - return 24 // wgpu Snorm16x4 + return 24 - // Float16 formats: gputypes lacks single-component + // 16-bit float packed formats (gputypes lacks single-component Float16) case gputypes.VertexFormatFloat16x2: - return 26 // wgpu Float16x2 + return 26 case gputypes.VertexFormatFloat16x4: - return 27 // wgpu Float16x4 + return 27 - // Float32 formats + // 32-bit float formats case gputypes.VertexFormatFloat32: - return 28 // wgpu Float32 + return 28 case gputypes.VertexFormatFloat32x2: - return 29 // wgpu Float32x2 + return 29 case gputypes.VertexFormatFloat32x3: - return 30 // wgpu Float32x3 + return 30 case gputypes.VertexFormatFloat32x4: - return 31 // wgpu Float32x4 + return 31 - // Uint32 formats + // 32-bit unsigned integer formats case gputypes.VertexFormatUint32: - return 32 // wgpu Uint32 + return 32 case gputypes.VertexFormatUint32x2: - return 33 // wgpu Uint32x2 + return 33 case gputypes.VertexFormatUint32x3: - return 34 // wgpu Uint32x3 + return 34 case gputypes.VertexFormatUint32x4: - return 35 // wgpu Uint32x4 + return 35 - // Sint32 formats + // 32-bit signed integer formats case gputypes.VertexFormatSint32: - return 36 // wgpu Sint32 + return 36 case gputypes.VertexFormatSint32x2: - return 37 // wgpu Sint32x2 + return 37 case gputypes.VertexFormatSint32x3: - return 38 // wgpu Sint32x3 + return 38 case gputypes.VertexFormatSint32x4: - return 39 // wgpu Sint32x4 + return 39 - // Packed format + // Packed normalized format case gputypes.VertexFormatUnorm1010102: - return 40 // wgpu Unorm10_10_10_2 + return 40 // v29: Unorm10_10_10_2 default: + // Unknown gputypes format value — pass through as-is. + // This handles future gputypes additions gracefully. return uint32(f) } } diff --git a/wgpu/debug_render_test.go b/wgpu/debug_render_test.go index 37bb0fb..b7a19d8 100644 --- a/wgpu/debug_render_test.go +++ b/wgpu/debug_render_test.go @@ -60,16 +60,17 @@ func TestDebugRenderPipelineBytes(t *testing.T) { defer device.Release() // Check gputypes values + // gputypes v0.3.0 and wgpu-native v29 have identical TextureFormat values. + // BGRA8Unorm = 0x1B = 27 in both gputypes and v29 webgpu.h. t.Logf("gputypes.TextureFormatBGRA8Unorm = %d (0x%X)", gputypes.TextureFormatBGRA8Unorm, gputypes.TextureFormatBGRA8Unorm) t.Logf("gputypes.TextureFormatRG11B10Ufloat = %d (0x%X)", gputypes.TextureFormatRG11B10Ufloat, gputypes.TextureFormatRG11B10Ufloat) - // Verify the conversion - // gputypes BGRA8Unorm = 27, webgpu-headers BGRA8Unorm = 23 - converted := toWGPUTextureFormat(gputypes.TextureFormatBGRA8Unorm) - t.Logf("toWGPUTextureFormat(BGRA8Unorm) = %d (0x%X)", converted, converted) + // Verify the direct cast: gputypes v0.3.0 BGRA8Unorm = 0x1B = 27, v29 BGRA8Unorm = 0x1B = 27 (match) + converted := uint32(gputypes.TextureFormatBGRA8Unorm) + t.Logf("uint32(BGRA8Unorm) = %d (0x%X)", converted, converted) - if converted != 23 { - t.Errorf("toWGPUTextureFormat(BGRA8Unorm) should return 23 (wgpu-native value) but returned %d", converted) + if converted != 27 { + t.Errorf("uint32(BGRA8Unorm) should return 27 (v29 wgpu-native value) but returned %d", converted) } // Manually create the structs to see what's happening @@ -89,9 +90,9 @@ fn fs_main() -> @location(0) vec4 { return vec4(1.0, 0.0, 0.0, 1.0); } ` - shader := device.CreateShaderModuleWGSL(shaderCode) - if shader == nil { - t.Fatal("CreateShaderModuleWGSL returned nil") + shader, err := device.CreateShaderModuleWGSL(shaderCode) + if err != nil { + t.Fatalf("CreateShaderModuleWGSL failed: %v", err) } defer shader.Release() diff --git a/wgpu/descriptors.go b/wgpu/descriptors.go new file mode 100644 index 0000000..b27ccef --- /dev/null +++ b/wgpu/descriptors.go @@ -0,0 +1,93 @@ +package wgpu + +import ( + "unsafe" + + "github.com/gogpu/gputypes" +) + +// ============================================================================= +// Helper functions for public → wire conversion +// ============================================================================= + +// stringToStringView converts a Go string to a wgpu-native StringView. +// The returned StringView points to the string's backing data — the string +// must remain alive for the duration of any FFI call using the result. +func stringToStringView(s string) StringView { + if len(s) == 0 { + return EmptyStringView() + } + b := []byte(s) + return StringView{ + Data: uintptr(unsafe.Pointer(&b[0])), + Length: uintptr(len(b)), + } +} + +// boolToWGPU converts a Go bool to a WebGPU WGPUBool (uint32). +func boolToWGPU(b bool) Bool { + if b { + return True + } + return False +} + +// ============================================================================= +// Shader descriptor +// ============================================================================= + +// ShaderDescriptor is the Go-idiomatic descriptor for creating a shader module. +// Use WGSL to specify WGSL source, or SPIRV for SPIR-V bytecode. +// If both are set, WGSL takes precedence. +// Use Device.CreateShaderModule or Device.CreateShaderModuleFromDescriptor to create from this. +type ShaderDescriptor struct { + Label string + WGSL string // WGSL source code + SPIRV []uint32 // SPIR-V bytecode (alternative to WGSL) +} + +// ShaderModuleDescriptorGo is an alias for ShaderDescriptor. +// Use this name when matching the gogpu/wgpu naming convention. +type ShaderModuleDescriptorGo = ShaderDescriptor + +// ============================================================================= +// Render bundle descriptor +// ============================================================================= + +// RenderBundleEncoderDescriptorGo is kept for backward compatibility. +// Deprecated: Use RenderBundleEncoderDescriptor directly (now Go-idiomatic). +type RenderBundleEncoderDescriptorGo = RenderBundleEncoderDescriptor + +// ============================================================================= +// Legacy types (kept for backward compatibility, may be removed in v0.6.0) +// ============================================================================= + +// BindGroupEntryGo is kept for backward compatibility. +// Deprecated: Use BindGroupEntry directly (now Go-idiomatic). +type BindGroupEntryGo = BindGroupEntry + +// BindGroupDescriptorGo is kept for backward compatibility. +// Deprecated: Use BindGroupDescriptor directly (now Go-idiomatic). +type BindGroupDescriptorGo = BindGroupDescriptor + +// BindGroupLayoutDescriptorGo is kept for backward compatibility. +// Deprecated: Use BindGroupLayoutDescriptor directly (now Go-idiomatic). +type BindGroupLayoutDescriptorGo = BindGroupLayoutDescriptor + +// PipelineLayoutDescriptorGo is kept for backward compatibility. +// Deprecated: Use PipelineLayoutDescriptor directly (now Go-idiomatic). +type PipelineLayoutDescriptorGo = PipelineLayoutDescriptor + +// ComputePipelineDescriptorGo is kept for backward compatibility. +// Deprecated: Use ComputePipelineDescriptor directly (now Go-idiomatic). +type ComputePipelineDescriptorGo = ComputePipelineDescriptor + +// CommandEncoderDescriptorGo is kept for backward compatibility. +// Deprecated: Use CommandEncoderDescriptor directly (now Go-idiomatic). +type CommandEncoderDescriptorGo = CommandEncoderDescriptor + +// ============================================================================= +// Suppressing unused import warning +// ============================================================================= + +var _ = gputypes.TextureFormatUndefined // ensure gputypes import is used diff --git a/wgpu/device.go b/wgpu/device.go index b5c6719..ff4da1d 100644 --- a/wgpu/device.go +++ b/wgpu/device.go @@ -100,11 +100,24 @@ func (a *Adapter) RequestDevice(options *DeviceDescriptor) (*Device, error) { deviceRequests[reqID] = req deviceRequestsMu.Unlock() - // Prepare options + // Convert Go-idiomatic descriptor to wire format. var optionsPtr uintptr + var reqLimitsWire limitsWire // kept alive for the duration of the FFI call if options != nil { - optionsPtr = uintptr(unsafe.Pointer(options)) + wire := deviceDescriptorWire{ + Label: stringToStringView(options.Label), + } + if len(options.RequiredFeatures) > 0 { + wire.RequiredFeatureCount = uintptr(len(options.RequiredFeatures)) + wire.RequiredFeatures = uintptr(unsafe.Pointer(&options.RequiredFeatures[0])) + } + if options.RequiredLimits != nil { + reqLimitsWire = limitsToWire(options.RequiredLimits) + wire.RequiredLimits = uintptr(unsafe.Pointer(&reqLimitsWire)) + } + optionsPtr = uintptr(unsafe.Pointer(&wire)) } + _ = reqLimitsWire // ensure not optimised away before the call below // Prepare callback info callbackInfo := RequestDeviceCallbackInfo{ @@ -123,7 +136,6 @@ func (a *Adapter) RequestDevice(options *DeviceDescriptor) (*Device, error) { ) // Process events until callback fires - // We need an instance to call ProcessEvents - get it from global or use a busy loop for { select { case <-req.done: @@ -135,6 +147,10 @@ func (a *Adapter) RequestDevice(options *DeviceDescriptor) (*Device, error) { } return nil, &WGPUError{Op: "RequestDevice", Message: msg} } + // Cache limits at creation time so Limits() returns value without FFI. + if req.device != nil { + req.device.limits = fetchDeviceLimits(req.device.handle) + } return req.device, nil default: // Brief pause to avoid busy spinning @@ -143,8 +159,22 @@ func (a *Adapter) RequestDevice(options *DeviceDescriptor) (*Device, error) { } } -// GetQueue returns the default queue for the device. -func (d *Device) GetQueue() *Queue { +// fetchDeviceLimits calls wgpuDeviceGetLimits and converts the wire struct to public Limits. +// Returns zero-value Limits on failure (non-fatal: limits remain valid defaults). +func fetchDeviceLimits(handle uintptr) Limits { + var wire limitsWire + status, _, _ := procDeviceGetLimits.Call( + handle, + uintptr(unsafe.Pointer(&wire)), + ) + if WGPUStatus(status) != WGPUStatusSuccess { + return Limits{} + } + return limitsFromWire(&wire) +} + +// Queue returns the default queue for the device. +func (d *Device) Queue() *Queue { mustInit() if d == nil || d.handle == 0 { return nil @@ -192,64 +222,128 @@ func (q *Queue) Release() { } } +// DeviceLostCallbackInfo configures the device-lost callback. +type DeviceLostCallbackInfo struct { + NextInChain uintptr // *ChainedStruct + Mode CallbackMode + Callback uintptr // Function pointer + Userdata1 uintptr + Userdata2 uintptr +} + +// UncapturedErrorCallbackInfo configures the uncaptured-error callback. +type UncapturedErrorCallbackInfo struct { + NextInChain uintptr // *ChainedStruct + Callback uintptr // Function pointer + Userdata1 uintptr + Userdata2 uintptr +} + // DeviceDescriptor configures device creation. -// For now, passing nil uses default settings. +// Matches the gogpu/wgpu API for cross-project compatibility. type DeviceDescriptor struct { + // Label is an optional debug label for the device. + Label string + // RequiredFeatures lists GPU features that the device must support. + RequiredFeatures []FeatureName + // RequiredLimits, if non-nil, specifies minimum resource limits the device must meet. + // Pass nil to use the adapter's default limits. + RequiredLimits *Limits +} + +// limitsToWire converts public Limits to the FFI-compatible limitsWire struct. +// Used when passing required limits to wgpuAdapterRequestDevice. +func limitsToWire(l *Limits) limitsWire { + if l == nil { + return limitsWire{} + } + return limitsWire{ + MaxTextureDimension1D: l.MaxTextureDimension1D, + MaxTextureDimension2D: l.MaxTextureDimension2D, + MaxTextureDimension3D: l.MaxTextureDimension3D, + MaxTextureArrayLayers: l.MaxTextureArrayLayers, + MaxBindGroups: l.MaxBindGroups, + MaxBindGroupsPlusVertexBuffers: l.MaxBindGroupsPlusVertexBuffers, + MaxBindingsPerBindGroup: l.MaxBindingsPerBindGroup, + MaxDynamicUniformBuffersPerPipelineLayout: l.MaxDynamicUniformBuffersPerPipelineLayout, + MaxDynamicStorageBuffersPerPipelineLayout: l.MaxDynamicStorageBuffersPerPipelineLayout, + MaxSampledTexturesPerShaderStage: l.MaxSampledTexturesPerShaderStage, + MaxSamplersPerShaderStage: l.MaxSamplersPerShaderStage, + MaxStorageBuffersPerShaderStage: l.MaxStorageBuffersPerShaderStage, + MaxStorageTexturesPerShaderStage: l.MaxStorageTexturesPerShaderStage, + MaxUniformBuffersPerShaderStage: l.MaxUniformBuffersPerShaderStage, + MaxUniformBufferBindingSize: l.MaxUniformBufferBindingSize, + MaxStorageBufferBindingSize: l.MaxStorageBufferBindingSize, + MinUniformBufferOffsetAlignment: l.MinUniformBufferOffsetAlignment, + MinStorageBufferOffsetAlignment: l.MinStorageBufferOffsetAlignment, + MaxVertexBuffers: l.MaxVertexBuffers, + MaxBufferSize: l.MaxBufferSize, + MaxVertexAttributes: l.MaxVertexAttributes, + MaxVertexBufferArrayStride: l.MaxVertexBufferArrayStride, + MaxInterStageShaderVariables: l.MaxInterStageShaderVariables, + MaxColorAttachments: l.MaxColorAttachments, + MaxColorAttachmentBytesPerSample: l.MaxColorAttachmentBytesPerSample, + MaxComputeWorkgroupStorageSize: l.MaxComputeWorkgroupStorageSize, + MaxComputeInvocationsPerWorkgroup: l.MaxComputeInvocationsPerWorkgroup, + MaxComputeWorkgroupSizeX: l.MaxComputeWorkgroupSizeX, + MaxComputeWorkgroupSizeY: l.MaxComputeWorkgroupSizeY, + MaxComputeWorkgroupSizeZ: l.MaxComputeWorkgroupSizeZ, + MaxComputeWorkgroupsPerDimension: l.MaxComputeWorkgroupsPerDimension, + } +} + +// deviceDescriptorWire is the FFI-compatible C-layout struct for wgpuAdapterRequestDevice. +// v29: Added Label, RequiredFeatureCount, RequiredFeatures, RequiredLimits, +// DefaultQueue, DeviceLostCallbackInfo, UncapturedErrorCallbackInfo fields. +type deviceDescriptorWire struct { + NextInChain uintptr // *ChainedStruct + Label StringView + RequiredFeatureCount uintptr // size_t + RequiredFeatures uintptr // *FeatureName (const) + RequiredLimits uintptr // *Limits (const, nullable) + DefaultQueue QueueDescriptor + DeviceLostCallbackInfo DeviceLostCallbackInfo + UncapturedErrorCallbackInfo UncapturedErrorCallbackInfo +} + +// QueueDescriptor configures queue creation. +type QueueDescriptor struct { NextInChain uintptr // *ChainedStruct - // Additional fields can be added as needed + Label StringView } // CreateDepthTexture creates a depth texture with the specified dimensions and format. // This is a convenience function for creating depth buffers for render passes. +// Returns nil on error (use CreateTexture directly for full error handling). func (d *Device) CreateDepthTexture(width, height uint32, format gputypes.TextureFormat) *Texture { - mustInit() - if d == nil || d.handle == 0 { - return nil - } - desc := TextureDescriptor{ - NextInChain: 0, - Label: EmptyStringView(), - Usage: gputypes.TextureUsageRenderAttachment, - Dimension: gputypes.TextureDimension2D, - Size: gputypes.Extent3D{Width: width, Height: height, DepthOrArrayLayers: 1}, - Format: format, - MipLevelCount: 1, - SampleCount: 1, - ViewFormatCount: 0, - ViewFormats: 0, + Usage: gputypes.TextureUsageRenderAttachment, + Dimension: gputypes.TextureDimension2D, + Size: gputypes.Extent3D{Width: width, Height: height, DepthOrArrayLayers: 1}, + Format: format, + MipLevelCount: 1, + SampleCount: 1, } - return d.CreateTexture(&desc) + t, _ := d.CreateTexture(&desc) + return t } -// GetLimits retrieves the limits of this device. -// Returns the same Limits struct format as Adapter.GetLimits(). -// The FFI call uses SupportedLimits (which wraps Limits with nextInChain). -func (d *Device) GetLimits() (*SupportedLimits, error) { - if err := checkInit(); err != nil { - return nil, err - } +// Limits returns the resource limits of this device. +// +// Limits are cached at device creation time and returned by value. +// No FFI call is made. Returns zero-value Limits if the device is nil. +// This matches the gogpu/wgpu API signature for cross-project compatibility. +func (d *Device) Limits() Limits { if d == nil || d.handle == 0 { - return nil, &WGPUError{Op: "Device.GetLimits", Message: "device is nil"} - } - - limits := &SupportedLimits{} - status, _, _ := procDeviceGetLimits.Call( - d.handle, - uintptr(unsafe.Pointer(limits)), - ) - - if WGPUStatus(status) != WGPUStatusSuccess { - return nil, &WGPUError{Op: "Device.GetLimits", Message: "operation failed"} + return Limits{} } - - return limits, nil + return d.limits } -// GetFeatures retrieves all features enabled on this device. +// Features retrieves all features enabled on this device. // Returns a slice of FeatureName values. -func (d *Device) GetFeatures() []FeatureName { +func (d *Device) Features() []FeatureName { mustInit() if d == nil || d.handle == 0 { return nil @@ -274,6 +368,9 @@ func (d *Device) GetFeatures() []FeatureName { result := make([]FeatureName, supported.FeatureCount) copy(result, features) + // Free C-allocated memory (pass pointer to struct, not individual fields) + procSupportedFeaturesFreeMembers.Call(uintptr(unsafe.Pointer(&supported))) //nolint:errcheck + return result } diff --git a/wgpu/device_test.go b/wgpu/device_test.go index 2ae64e9..524b2ed 100644 --- a/wgpu/device_test.go +++ b/wgpu/device_test.go @@ -51,9 +51,9 @@ func TestDeviceGetQueue(t *testing.T) { defer device.Release() t.Log("Getting queue...") - queue := device.GetQueue() + queue := device.Queue() if queue == nil { - t.Fatal("GetQueue returned nil") + t.Fatal("Queue returned nil") } defer queue.Release() @@ -83,26 +83,15 @@ func TestDeviceGetLimits(t *testing.T) { } defer device.Release() - t.Log("Getting device limits...") - deviceLimits, err := device.GetLimits() - - // NOTE: Currently wgpu-native v27 returns error from wgpuDeviceGetLimits - // See issue #3 - waiting for wgpu-native webgpu-headers update - if err != nil { - t.Logf("Device GetLimits returned expected error (wgpu-native v27 limitation): %v", err) - t.Skip("Skipping test - Device.GetLimits not yet supported in wgpu-native v27") - return - } - - // If we get here, wgpu-native was updated and the function works! - // Verify limits are non-zero - if deviceLimits.Limits.MaxTextureDimension2D == 0 { - t.Error("MaxTextureDimension2D is zero") - } + t.Log("Getting device limits (cached, no FFI call)...") + deviceLimits := device.Limits() + // Limits are cached at RequestDevice time. On wgpu-native v29+ they should be non-zero. + // On older wgpu-native (v27) GetLimits was not fully implemented; limits may be zero — + // that is not a test failure, just log the result. t.Logf("Device limits: MaxTextureDimension2D=%d, MaxBindGroups=%d", - deviceLimits.Limits.MaxTextureDimension2D, - deviceLimits.Limits.MaxBindGroups) + deviceLimits.MaxTextureDimension2D, + deviceLimits.MaxBindGroups) } func TestDeviceGetFeatures(t *testing.T) { @@ -125,7 +114,7 @@ func TestDeviceGetFeatures(t *testing.T) { defer device.Release() t.Log("Getting device features...") - features := device.GetFeatures() + features := device.Features() // Result can be nil (no optional features) - this is ok if features == nil { @@ -166,11 +155,9 @@ func TestDeviceHasFeature(t *testing.T) { func TestDeviceGetLimits_Nil(t *testing.T) { var d *Device - _, err := d.GetLimits() - if err == nil { - t.Error("Expected error for nil device") - } - if err.Error() != "wgpu: Device.GetLimits: device is nil" { - t.Errorf("Unexpected error message: %v", err) + // Nil device returns zero-value Limits (no error, no panic). + limits := d.Limits() + if limits.MaxTextureDimension2D != 0 { + t.Errorf("Expected zero-value Limits for nil device, got MaxTextureDimension2D=%d", limits.MaxTextureDimension2D) } } diff --git a/wgpu/doc.go b/wgpu/doc.go index 6e0dd64..feb407f 100644 --- a/wgpu/doc.go +++ b/wgpu/doc.go @@ -11,8 +11,8 @@ // - [Adapter.RequestDevice] (callback registry protected by mutex) // - [Device.PopErrorScopeAsync] (callback registry protected by mutex) // - [Buffer.MapAsync] (callback registry protected by mutex) -// - Read-only queries: [Adapter.GetLimits], [Adapter.GetInfo], [Adapter.EnumerateFeatures], [Adapter.HasFeature] -// - Read-only queries: [Device.GetLimits], [Device.GetFeatures], [Device.HasFeature] +// - Read-only queries: [Adapter.Limits], [Adapter.Info], [Adapter.Features], [Adapter.HasFeature] +// - Read-only queries: [Device.Limits], [Device.Features], [Device.HasFeature] // - Read-only queries: [Surface.GetCapabilities] // // The following operations are NOT safe for concurrent use on the same object: diff --git a/wgpu/enums.go b/wgpu/enums.go index 85d4fc6..8b54519 100644 --- a/wgpu/enums.go +++ b/wgpu/enums.go @@ -6,12 +6,16 @@ type RequestAdapterStatus uint32 const ( // RequestAdapterStatusSuccess indicates the adapter was successfully obtained. RequestAdapterStatusSuccess RequestAdapterStatus = 0x00000001 - // RequestAdapterStatusInstanceDropped indicates the instance was dropped before completion. - RequestAdapterStatusInstanceDropped RequestAdapterStatus = 0x00000002 + // RequestAdapterStatusCallbackCancelled indicates the operation was cancelled (e.g. instance dropped). + // Renamed from InstanceDropped in v29. + RequestAdapterStatusCallbackCancelled RequestAdapterStatus = 0x00000002 // RequestAdapterStatusUnavailable indicates no suitable adapter is available. RequestAdapterStatusUnavailable RequestAdapterStatus = 0x00000003 // RequestAdapterStatusError indicates an error occurred during adapter request. RequestAdapterStatusError RequestAdapterStatus = 0x00000004 + // RequestAdapterStatusInstanceDropped is a deprecated alias for CallbackCancelled. + // Deprecated: Use RequestAdapterStatusCallbackCancelled. + RequestAdapterStatusInstanceDropped = RequestAdapterStatusCallbackCancelled ) // RequestDeviceStatus is the status returned by RequestDevice callback. @@ -20,18 +24,22 @@ type RequestDeviceStatus uint32 const ( // RequestDeviceStatusSuccess indicates the device was successfully obtained. RequestDeviceStatusSuccess RequestDeviceStatus = 0x00000001 - // RequestDeviceStatusInstanceDropped indicates the instance was dropped before completion. - RequestDeviceStatusInstanceDropped RequestDeviceStatus = 0x00000002 + // RequestDeviceStatusCallbackCancelled indicates the operation was cancelled (e.g. instance dropped). + // Renamed from InstanceDropped in v29. + RequestDeviceStatusCallbackCancelled RequestDeviceStatus = 0x00000002 // RequestDeviceStatusError indicates an error occurred during device request. RequestDeviceStatusError RequestDeviceStatus = 0x00000003 - // RequestDeviceStatusUnknown indicates an unknown error occurred. - RequestDeviceStatusUnknown RequestDeviceStatus = 0x00000004 + // RequestDeviceStatusInstanceDropped is a deprecated alias for CallbackCancelled. + // Deprecated: Use RequestDeviceStatusCallbackCancelled. + RequestDeviceStatusInstanceDropped = RequestDeviceStatusCallbackCancelled ) // FeatureLevel indicates the WebGPU feature level. type FeatureLevel uint32 const ( + // FeatureLevelUndefined indicates no value is passed. Added in v29. + FeatureLevelUndefined FeatureLevel = 0x00000000 // FeatureLevelCompatibility indicates the compatibility feature level (WebGPU compat). FeatureLevelCompatibility FeatureLevel = 0x00000001 // FeatureLevelCore indicates the core feature level (full WebGPU). @@ -58,6 +66,8 @@ const ( STypeShaderSourceSPIRV SType = 0x00000001 // STypeShaderSourceWGSL identifies a WGSL shader source chained struct. STypeShaderSourceWGSL SType = 0x00000002 + // STypeRenderPassMaxDrawCount identifies the max draw count chained struct. + STypeRenderPassMaxDrawCount SType = 0x00000003 // STypeSurfaceSourceMetalLayer identifies a Metal layer surface source (macOS/iOS). STypeSurfaceSourceMetalLayer SType = 0x00000004 @@ -71,9 +81,45 @@ const ( STypeSurfaceSourceAndroidNativeWindow SType = 0x00000008 // STypeSurfaceSourceXCBWindow identifies an XCB window surface source (Linux). STypeSurfaceSourceXCBWindow SType = 0x00000009 - + // STypeSurfaceColorManagement identifies a surface color management chained struct. New in v29. + STypeSurfaceColorManagement SType = 0x0000000A + // STypeRequestAdapterWebXROptions identifies WebXR adapter options. New in v29. + STypeRequestAdapterWebXROptions SType = 0x0000000B + // STypeTextureComponentSwizzleDescriptor identifies a texture component swizzle descriptor. New in v29. + STypeTextureComponentSwizzleDescriptor SType = 0x0000000C + // STypeExternalTextureBindingLayout identifies an external texture binding layout. New in v29. + STypeExternalTextureBindingLayout SType = 0x0000000D + // STypeExternalTextureBindingEntry identifies an external texture binding entry. New in v29. + STypeExternalTextureBindingEntry SType = 0x0000000E + // STypeCompatibilityModeLimits identifies compat-mode limits. New in v29. + STypeCompatibilityModeLimits SType = 0x0000000F + // STypeTextureBindingViewDimension identifies a texture binding view dimension. New in v29. + STypeTextureBindingViewDimension SType = 0x00000010 + + // Native wgpu-native extension STypes (0x0003XXXX range) + + // STypeDeviceExtras identifies wgpu-native device extras chained struct. + STypeDeviceExtras SType = 0x00030001 + // STypeNativeLimits identifies wgpu-native native limits chained struct. + STypeNativeLimits SType = 0x00030002 + // STypePipelineLayoutExtras identifies wgpu-native pipeline layout extras chained struct. + STypePipelineLayoutExtras SType = 0x00030003 + // STypeShaderSourceGLSL identifies a GLSL shader source chained struct (wgpu-native extension). + STypeShaderSourceGLSL SType = 0x00030004 // STypeInstanceExtras identifies wgpu-native instance extras chained struct. STypeInstanceExtras SType = 0x00030006 + // STypeBindGroupEntryExtras identifies wgpu-native bind group entry extras. + STypeBindGroupEntryExtras SType = 0x00030007 + // STypeBindGroupLayoutEntryExtras identifies wgpu-native bind group layout entry extras. + STypeBindGroupLayoutEntryExtras SType = 0x00030008 + // STypeQuerySetDescriptorExtras identifies wgpu-native query set descriptor extras. + STypeQuerySetDescriptorExtras SType = 0x00030009 + // STypeSurfaceConfigurationExtras identifies wgpu-native surface configuration extras. + STypeSurfaceConfigurationExtras SType = 0x0003000A + // STypeSurfaceSourceSwapChainPanel identifies a WinUI SwapChainPanel surface source. + STypeSurfaceSourceSwapChainPanel SType = 0x0003000B + // STypePrimitiveStateExtras identifies wgpu-native primitive state extras. + STypePrimitiveStateExtras SType = 0x0003000C ) // SurfaceGetCurrentTextureStatus describes the result of GetCurrentTexture. @@ -81,21 +127,22 @@ type SurfaceGetCurrentTextureStatus uint32 const ( // SurfaceGetCurrentTextureStatusSuccessOptimal indicates the texture was obtained optimally. - SurfaceGetCurrentTextureStatusSuccessOptimal SurfaceGetCurrentTextureStatus = 0x01 + SurfaceGetCurrentTextureStatusSuccessOptimal SurfaceGetCurrentTextureStatus = 0x00000001 // SurfaceGetCurrentTextureStatusSuccessSuboptimal indicates the texture was obtained but may not be optimal. - SurfaceGetCurrentTextureStatusSuccessSuboptimal SurfaceGetCurrentTextureStatus = 0x02 + SurfaceGetCurrentTextureStatusSuccessSuboptimal SurfaceGetCurrentTextureStatus = 0x00000002 // SurfaceGetCurrentTextureStatusTimeout indicates the operation timed out. - SurfaceGetCurrentTextureStatusTimeout SurfaceGetCurrentTextureStatus = 0x03 + SurfaceGetCurrentTextureStatusTimeout SurfaceGetCurrentTextureStatus = 0x00000003 // SurfaceGetCurrentTextureStatusOutdated indicates the surface needs reconfiguration. - SurfaceGetCurrentTextureStatusOutdated SurfaceGetCurrentTextureStatus = 0x04 + SurfaceGetCurrentTextureStatusOutdated SurfaceGetCurrentTextureStatus = 0x00000004 // SurfaceGetCurrentTextureStatusLost indicates the surface was lost and must be recreated. - SurfaceGetCurrentTextureStatusLost SurfaceGetCurrentTextureStatus = 0x05 - // SurfaceGetCurrentTextureStatusOutOfMemory indicates GPU memory allocation failed. - SurfaceGetCurrentTextureStatusOutOfMemory SurfaceGetCurrentTextureStatus = 0x06 - // SurfaceGetCurrentTextureStatusDeviceLost indicates the GPU device was lost. - SurfaceGetCurrentTextureStatusDeviceLost SurfaceGetCurrentTextureStatus = 0x07 - // SurfaceGetCurrentTextureStatusError indicates an unspecified error occurred. - SurfaceGetCurrentTextureStatusError SurfaceGetCurrentTextureStatus = 0x08 + SurfaceGetCurrentTextureStatusLost SurfaceGetCurrentTextureStatus = 0x00000005 + // SurfaceGetCurrentTextureStatusError indicates a deterministic error (e.g. surface not configured). + // BREAKING: v27 had OutOfMemory=0x06, DeviceLost=0x07, Error=0x08; v29 collapsed to Error=0x06. + SurfaceGetCurrentTextureStatusError SurfaceGetCurrentTextureStatus = 0x00000006 + + // NativeSurfaceGetCurrentTextureStatusOccluded is a wgpu-native extension status. + // Returned on macOS Metal when the window is occluded/minimized. + NativeSurfaceGetCurrentTextureStatusOccluded SurfaceGetCurrentTextureStatus = 0x00030001 ) // TextureAspect describes which aspect of a texture to access. @@ -138,19 +185,187 @@ const ( type FeatureName uint32 const ( + // FeatureNameCoreFeaturesAndLimits indicates core features and limits support. + FeatureNameCoreFeaturesAndLimits FeatureName = 0x00000001 + // FeatureNameDepthClipControl enables depth clip control. + FeatureNameDepthClipControl FeatureName = 0x00000002 + // FeatureNameDepth32FloatStencil8 enables depth32float-stencil8 texture format. + FeatureNameDepth32FloatStencil8 FeatureName = 0x00000003 + // FeatureNameTextureCompressionBC enables BC texture compression. + FeatureNameTextureCompressionBC FeatureName = 0x00000004 + // FeatureNameTextureCompressionBCSliced3D enables sliced 3D BC compression. + FeatureNameTextureCompressionBCSliced3D FeatureName = 0x00000005 + // FeatureNameTextureCompressionETC2 enables ETC2 texture compression. + FeatureNameTextureCompressionETC2 FeatureName = 0x00000006 + // FeatureNameTextureCompressionASTC enables ASTC texture compression. + FeatureNameTextureCompressionASTC FeatureName = 0x00000007 + // FeatureNameTextureCompressionASTCSliced3D enables sliced 3D ASTC compression. + FeatureNameTextureCompressionASTCSliced3D FeatureName = 0x00000008 // FeatureNameTimestampQuery enables timestamp query support. - FeatureNameTimestampQuery FeatureName = 0x00000003 - // Add more features as needed + FeatureNameTimestampQuery FeatureName = 0x00000009 + // FeatureNameIndirectFirstInstance enables indirect first instance. + FeatureNameIndirectFirstInstance FeatureName = 0x0000000A + // FeatureNameShaderF16 enables f16 in shaders. + FeatureNameShaderF16 FeatureName = 0x0000000B + // FeatureNameRG11B10UfloatRenderable enables RG11B10Ufloat as render target. + FeatureNameRG11B10UfloatRenderable FeatureName = 0x0000000C + // FeatureNameBGRA8UnormStorage enables BGRA8Unorm storage textures. + FeatureNameBGRA8UnormStorage FeatureName = 0x0000000D + // FeatureNameFloat32Filterable enables filterable float32 textures. + FeatureNameFloat32Filterable FeatureName = 0x0000000E + // FeatureNameFloat32Blendable enables blendable float32 textures. + FeatureNameFloat32Blendable FeatureName = 0x0000000F + // FeatureNameClipDistances enables clip distances in shaders. + FeatureNameClipDistances FeatureName = 0x00000010 + // FeatureNameDualSourceBlending enables dual source blending. + FeatureNameDualSourceBlending FeatureName = 0x00000011 + // FeatureNameSubgroups enables subgroup operations. + FeatureNameSubgroups FeatureName = 0x00000012 + // FeatureNameTextureFormatsTier1 enables tier 1 texture formats. + FeatureNameTextureFormatsTier1 FeatureName = 0x00000013 + // FeatureNameTextureFormatsTier2 enables tier 2 texture formats. + FeatureNameTextureFormatsTier2 FeatureName = 0x00000014 + // FeatureNamePrimitiveIndex enables primitive index in shaders. + FeatureNamePrimitiveIndex FeatureName = 0x00000015 + // FeatureNameTextureComponentSwizzle enables texture component swizzle. + FeatureNameTextureComponentSwizzle FeatureName = 0x00000016 +) + +// NativeFeature describes a wgpu-native extension feature. +type NativeFeature uint32 + +const ( + // NativeFeatureImmediates enables immediate data (push constants replacement). + // Renamed from PushConstants in v29. + NativeFeatureImmediates NativeFeature = 0x00030001 + // NativeFeaturePushConstants is a deprecated alias for Immediates. + // Deprecated: Use NativeFeatureImmediates. + NativeFeaturePushConstants = NativeFeatureImmediates + // NativeFeatureTextureAdapterSpecificFormatFeatures enables device-specific texture format features. + NativeFeatureTextureAdapterSpecificFormatFeatures NativeFeature = 0x00030002 + // NativeFeatureMultiDrawIndirectCount enables indirect draw count. + NativeFeatureMultiDrawIndirectCount NativeFeature = 0x00030004 + // NativeFeatureVertexWritableStorage enables vertex shader writable storage. + NativeFeatureVertexWritableStorage NativeFeature = 0x00030005 + // NativeFeatureTextureBindingArray enables texture binding arrays. + NativeFeatureTextureBindingArray NativeFeature = 0x00030006 + // NativeFeatureSampledTextureAndStorageBufferArrayNonUniformIndexing enables non-uniform indexing. + NativeFeatureSampledTextureAndStorageBufferArrayNonUniformIndexing NativeFeature = 0x00030007 + // NativeFeaturePipelineStatisticsQuery enables pipeline statistics queries. + NativeFeaturePipelineStatisticsQuery NativeFeature = 0x00030008 + // NativeFeatureStorageResourceBindingArray enables storage resource binding arrays. + NativeFeatureStorageResourceBindingArray NativeFeature = 0x00030009 + // NativeFeaturePartiallyBoundBindingArray enables partially bound binding arrays. + NativeFeaturePartiallyBoundBindingArray NativeFeature = 0x0003000A + // NativeFeatureTextureFormat16bitNorm enables normalized 16-bit texture formats. + NativeFeatureTextureFormat16bitNorm NativeFeature = 0x0003000B + // NativeFeatureTextureCompressionAstcHdr enables ASTC HDR compression. + NativeFeatureTextureCompressionAstcHdr NativeFeature = 0x0003000C + // NativeFeatureMappablePrimaryBuffers enables mappable primary buffers. + NativeFeatureMappablePrimaryBuffers NativeFeature = 0x0003000E + // NativeFeatureBufferBindingArray enables buffer binding arrays. + NativeFeatureBufferBindingArray NativeFeature = 0x0003000F + // NativeFeatureUniformBufferAndStorageTextureArrayNonUniformIndexing enables non-uniform indexing for these types. + NativeFeatureUniformBufferAndStorageTextureArrayNonUniformIndexing NativeFeature = 0x00030010 + // NativeFeaturePolygonModeLine enables polygon line mode. + NativeFeaturePolygonModeLine NativeFeature = 0x00030013 + // NativeFeaturePolygonModePoint enables polygon point mode. + NativeFeaturePolygonModePoint NativeFeature = 0x00030014 + // NativeFeatureConservativeRasterization enables conservative rasterization. + NativeFeatureConservativeRasterization NativeFeature = 0x00030015 + // NativeFeatureSpirvShaderPassthrough enables SPIR-V shader passthrough. + NativeFeatureSpirvShaderPassthrough NativeFeature = 0x00030017 + // NativeFeatureVertexAttribute64bit enables 64-bit vertex attributes. + NativeFeatureVertexAttribute64bit NativeFeature = 0x00030019 + // NativeFeatureTextureFormatNv12 enables NV12 texture format. + NativeFeatureTextureFormatNv12 NativeFeature = 0x0003001A + // NativeFeatureRayQuery enables ray query in shaders. + NativeFeatureRayQuery NativeFeature = 0x0003001C + // NativeFeatureShaderF64 enables f64 in shaders. + NativeFeatureShaderF64 NativeFeature = 0x0003001D + // NativeFeatureShaderI16 enables i16 in shaders. + NativeFeatureShaderI16 NativeFeature = 0x0003001E + // NativeFeatureShaderEarlyDepthTest enables early depth test attribute in shaders. + NativeFeatureShaderEarlyDepthTest NativeFeature = 0x00030020 + // NativeFeatureSubgroup enables subgroup operations in compute/fragment shaders. + NativeFeatureSubgroup NativeFeature = 0x00030021 + // NativeFeatureSubgroupVertex enables subgroup operations in vertex shaders. + NativeFeatureSubgroupVertex NativeFeature = 0x00030022 + // NativeFeatureSubgroupBarrier enables subgroup barrier in compute shaders. + NativeFeatureSubgroupBarrier NativeFeature = 0x00030023 + // NativeFeatureTimestampQueryInsideEncoders enables timestamp queries on command encoders. + NativeFeatureTimestampQueryInsideEncoders NativeFeature = 0x00030024 + // NativeFeatureTimestampQueryInsidePasses enables timestamp queries inside render/compute passes. + NativeFeatureTimestampQueryInsidePasses NativeFeature = 0x00030025 + // NativeFeatureShaderInt64 enables i64/u64 in shaders. + NativeFeatureShaderInt64 NativeFeature = 0x00030026 +) + +// InstanceFeatureName describes features that can be required at instance creation. +// New in v29. +type InstanceFeatureName uint32 + +const ( + // InstanceFeatureNameTimedWaitAny enables wgpuInstanceWaitAny with timeoutNS > 0. + InstanceFeatureNameTimedWaitAny InstanceFeatureName = 0x00000001 + // InstanceFeatureNameShaderSourceSPIRV enables SPIR-V shader sources. + InstanceFeatureNameShaderSourceSPIRV InstanceFeatureName = 0x00000002 + // InstanceFeatureNameMultipleDevicesPerAdapter allows multiple devices per adapter. + InstanceFeatureNameMultipleDevicesPerAdapter InstanceFeatureName = 0x00000003 +) + +// ComponentSwizzle describes texture component swizzle mapping. +// New in v29. +type ComponentSwizzle uint32 + +const ( + // ComponentSwizzleUndefined indicates no value is passed. + ComponentSwizzleUndefined ComponentSwizzle = 0x00000000 + // ComponentSwizzleZero forces the component value to 0. + ComponentSwizzleZero ComponentSwizzle = 0x00000001 + // ComponentSwizzleOne forces the component value to 1. + ComponentSwizzleOne ComponentSwizzle = 0x00000002 + // ComponentSwizzleR takes from the red channel. + ComponentSwizzleR ComponentSwizzle = 0x00000003 + // ComponentSwizzleG takes from the green channel. + ComponentSwizzleG ComponentSwizzle = 0x00000004 + // ComponentSwizzleB takes from the blue channel. + ComponentSwizzleB ComponentSwizzle = 0x00000005 + // ComponentSwizzleA takes from the alpha channel. + ComponentSwizzleA ComponentSwizzle = 0x00000006 +) + +// PredefinedColorSpace describes a color space for surface color management. +// New in v29. +type PredefinedColorSpace uint32 + +const ( + // PredefinedColorSpaceSRGB is the standard sRGB color space. + PredefinedColorSpaceSRGB PredefinedColorSpace = 0x00000001 + // PredefinedColorSpaceDisplayP3 is the Display P3 wide-gamut color space. + PredefinedColorSpaceDisplayP3 PredefinedColorSpace = 0x00000002 +) + +// ToneMappingMode describes tone mapping for HDR surfaces. +// New in v29. +type ToneMappingMode uint32 + +const ( + // ToneMappingModeStandard is standard tone mapping (sRGB). + ToneMappingModeStandard ToneMappingMode = 0x00000001 + // ToneMappingModeExtended is extended (HDR) tone mapping. + ToneMappingModeExtended ToneMappingMode = 0x00000002 ) // WGPUStatus describes the status returned from certain WebGPU operations. +// Note: v29 changed Success from 0x00 to 0x01 and Error from 0x01 to 0x02. type WGPUStatus uint32 const ( // WGPUStatusSuccess indicates the operation completed successfully. - WGPUStatusSuccess WGPUStatus = 0x00000000 + WGPUStatusSuccess WGPUStatus = 0x00000001 // WGPUStatusError indicates the operation failed. - WGPUStatusError WGPUStatus = 0x00000001 + WGPUStatusError WGPUStatus = 0x00000002 ) // BufferMapState describes the mapping state of a buffer. @@ -237,10 +452,18 @@ type PopErrorScopeStatus uint32 const ( // PopErrorScopeStatusSuccess indicates the error scope was successfully popped. PopErrorScopeStatusSuccess PopErrorScopeStatus = 0x00000001 - // PopErrorScopeStatusInstanceDropped indicates the instance was dropped. - PopErrorScopeStatusInstanceDropped PopErrorScopeStatus = 0x00000002 - // PopErrorScopeStatusEmptyStack indicates the error scope stack was empty. - PopErrorScopeStatusEmptyStack PopErrorScopeStatus = 0x00000003 + // PopErrorScopeStatusCallbackCancelled indicates the operation was cancelled (e.g. instance dropped). + // Renamed from InstanceDropped in v29. + PopErrorScopeStatusCallbackCancelled PopErrorScopeStatus = 0x00000002 + // PopErrorScopeStatusError indicates the error scope stack was empty or another error occurred. + // Renamed from EmptyStack in v29. + PopErrorScopeStatusError PopErrorScopeStatus = 0x00000003 + // PopErrorScopeStatusInstanceDropped is a deprecated alias for CallbackCancelled. + // Deprecated: Use PopErrorScopeStatusCallbackCancelled. + PopErrorScopeStatusInstanceDropped = PopErrorScopeStatusCallbackCancelled + // PopErrorScopeStatusEmptyStack is a deprecated alias for Error. + // Deprecated: Use PopErrorScopeStatusError. + PopErrorScopeStatusEmptyStack = PopErrorScopeStatusError ) // DeviceLostReason describes why a device was lost. @@ -251,8 +474,84 @@ const ( DeviceLostReasonUnknown DeviceLostReason = 0x00000001 // DeviceLostReasonDestroyed indicates the device was explicitly destroyed. DeviceLostReasonDestroyed DeviceLostReason = 0x00000002 - // DeviceLostReasonInstanceDropped indicates the instance was dropped. - DeviceLostReasonInstanceDropped DeviceLostReason = 0x00000003 + // DeviceLostReasonCallbackCancelled indicates the operation was cancelled (e.g. instance dropped). + // Renamed from InstanceDropped in v29. + DeviceLostReasonCallbackCancelled DeviceLostReason = 0x00000003 // DeviceLostReasonFailedCreation indicates device creation failed. DeviceLostReasonFailedCreation DeviceLostReason = 0x00000004 + // DeviceLostReasonInstanceDropped is a deprecated alias for CallbackCancelled. + // Deprecated: Use DeviceLostReasonCallbackCancelled. + DeviceLostReasonInstanceDropped = DeviceLostReasonCallbackCancelled +) + +// InstanceBackend is a bitflag selecting which graphics backends to enable. +// Used in InstanceExtras.Backends. +type InstanceBackend uint64 + +const ( + // InstanceBackendAll enables all available backends (default when zero). + InstanceBackendAll InstanceBackend = 0x00000000 + // InstanceBackendVulkan enables the Vulkan backend. + InstanceBackendVulkan InstanceBackend = 1 << 0 + // InstanceBackendGL enables the OpenGL/OpenGL ES backend. + InstanceBackendGL InstanceBackend = 1 << 1 + // InstanceBackendMetal enables the Metal backend (macOS/iOS). + InstanceBackendMetal InstanceBackend = 1 << 2 + // InstanceBackendDX12 enables the Direct3D 12 backend (Windows). + InstanceBackendDX12 InstanceBackend = 1 << 3 + // InstanceBackendBrowserWebGPU enables the browser WebGPU backend (WASM). + InstanceBackendBrowserWebGPU InstanceBackend = 1 << 5 + // InstanceBackendPrimary enables primary tier backends: Vulkan, Metal, DX12, BrowserWebGPU. + InstanceBackendPrimary InstanceBackend = (1 << 0) | (1 << 2) | (1 << 3) | (1 << 5) + // InstanceBackendSecondary enables secondary tier backends: GL only. + // BREAKING: v27 had Secondary = GL | DX11; DX11 was removed in v29. + InstanceBackendSecondary InstanceBackend = (1 << 1) +) + +// InstanceFlag is a bitflag controlling instance debugging and validation behavior. +// Used in InstanceExtras.Flags. +type InstanceFlag uint64 + +const ( + // InstanceFlagEmpty has no flags set. Zero-initialization default. + // BREAKING: v27 had Default=0; v29 renamed to Empty=0, Default moved to 1<<24. + InstanceFlagEmpty InstanceFlag = 0x00000000 + // InstanceFlagDebug generates debug information in shaders and objects. + InstanceFlagDebug InstanceFlag = 1 << 0 + // InstanceFlagValidation enables validation in the backend API. + InstanceFlagValidation InstanceFlag = 1 << 1 + // InstanceFlagDiscardHalLabels suppresses label passing to backend HAL. + InstanceFlagDiscardHalLabels InstanceFlag = 1 << 2 + // InstanceFlagAllowUnderlyingNoncompliantAdapter exposes adapters on non-compliant drivers. + InstanceFlagAllowUnderlyingNoncompliantAdapter InstanceFlag = 1 << 3 + // InstanceFlagGPUBasedValidation enables GPU-based validation (implies Validation). + InstanceFlagGPUBasedValidation InstanceFlag = 1 << 4 + // InstanceFlagValidationIndirectCall validates indirect buffer content before indirect draws. + InstanceFlagValidationIndirectCall InstanceFlag = 1 << 5 + // InstanceFlagAutomaticTimestampNormalization normalizes timestamps to nanoseconds. + InstanceFlagAutomaticTimestampNormalization InstanceFlag = 1 << 6 + // InstanceFlagDefault uses the default flags for the current build configuration. + // BREAKING: v27 had Default=0; v29 Default=1<<24 (debug builds enable Debug+Validation). + InstanceFlagDefault InstanceFlag = 1 << 24 + // InstanceFlagDebugging enables Debug and Validation. + InstanceFlagDebugging InstanceFlag = 1 << 25 + // InstanceFlagAdvancedDebugging enables Debug, Validation, and GPUBasedValidation. + InstanceFlagAdvancedDebugging InstanceFlag = 1 << 26 + // InstanceFlagWithEnv reads flag overrides from environment variables. + InstanceFlagWithEnv InstanceFlag = 1 << 27 +) + +// NativeDisplayHandleType identifies the platform display connection type. +// Used in NativeDisplayHandle.Type. New in v29. +type NativeDisplayHandleType uint32 + +const ( + // NativeDisplayHandleTypeNone indicates no display handle. Default (zero-init). + NativeDisplayHandleTypeNone NativeDisplayHandleType = 0x00000000 + // NativeDisplayHandleTypeXlib is an X11 display via Xlib. + NativeDisplayHandleTypeXlib NativeDisplayHandleType = 0x00000001 + // NativeDisplayHandleTypeXcb is an X11 display via XCB. + NativeDisplayHandleTypeXcb NativeDisplayHandleType = 0x00000002 + // NativeDisplayHandleTypeWayland is a Wayland display connection. + NativeDisplayHandleTypeWayland NativeDisplayHandleType = 0x00000003 ) diff --git a/wgpu/errors_test.go b/wgpu/errors_test.go index 123cb4f..7a394ce 100644 --- a/wgpu/errors_test.go +++ b/wgpu/errors_test.go @@ -46,9 +46,9 @@ func TestErrorScopeNoError(t *testing.T) { device.PushErrorScope(ErrorFilterValidation) // Do some valid operation (no error expected) - queue := device.GetQueue() + queue := device.Queue() if queue == nil { - t.Fatal("GetQueue returned nil") + t.Fatal("Queue returned nil") } defer queue.Release() @@ -88,13 +88,11 @@ func TestErrorScopeValidation(t *testing.T) { // Try to create an invalid buffer (size 0 should be invalid) desc := BufferDescriptor{ - NextInChain: 0, - Label: EmptyStringView(), Usage: gputypes.BufferUsageCopyDst | gputypes.BufferUsageMapRead, Size: 0, // Invalid: size must be > 0 - MappedAtCreation: False, + MappedAtCreation: false, } - buffer := device.CreateBuffer(&desc) + buffer, _ := device.CreateBuffer(&desc) if buffer != nil { defer buffer.Release() } diff --git a/wgpu/fuzz_test.go b/wgpu/fuzz_test.go index 180091b..4fee699 100644 --- a/wgpu/fuzz_test.go +++ b/wgpu/fuzz_test.go @@ -65,74 +65,25 @@ func FuzzToWGPUStorageTextureAccess(f *testing.F) { }) } -func FuzzToWGPUTextureFormat(f *testing.F) { - // Seed with all known format values - for i := uint32(0); i <= 49; i++ { +// FuzzTextureFormatDirectCast verifies that direct uint32 cast of TextureFormat +// never panics. gputypes v0.3.0 and wgpu-native v29 share identical format values. +func FuzzTextureFormatDirectCast(f *testing.F) { + for i := uint32(0); i <= 0x65; i++ { f.Add(i) } - f.Add(uint32(100)) f.Add(uint32(0xFFFFFFFF)) f.Fuzz(func(t *testing.T, v uint32) { - // Must not panic - _ = toWGPUTextureFormat(gputypes.TextureFormat(v)) - }) -} - -func FuzzFromWGPUTextureFormat(f *testing.F) { - for i := uint32(0); i <= 43; i++ { - f.Add(i) - } - f.Add(uint32(100)) - f.Add(uint32(0xFFFFFFFF)) - f.Fuzz(func(t *testing.T, v uint32) { - // Must not panic - _ = fromWGPUTextureFormat(v) - }) -} - -func FuzzTextureFormatRoundTrip(f *testing.F) { - // Seed with all gputypes format values that have valid round-trip - validFormats := []uint32{ - 0, // Undefined - 1, 2, 3, 4, // R8 - 7, 8, 9, // R16 Uint/Sint/Float (skip 5,6 = R16 Unorm/Snorm not in wgpu) - 10, 11, 12, 13, // RG8 - 14, 15, 16, // R32 - 19, 20, 21, // RG16 Uint/Sint/Float (skip 17,18 = RG16 Unorm/Snorm) - 22, 23, 24, 25, 26, // RGBA8 - 27, 28, // BGRA8 - 29, 30, 31, 32, // packed - 33, 34, 35, // RG32 - 38, 39, 40, // RGBA16 Uint/Sint/Float (skip 36,37 = RGBA16 Unorm/Snorm) - 41, 42, 43, // RGBA32 - 44, 45, 46, 47, 48, 49, // depth/stencil - } - for _, v := range validFormats { - f.Add(v) - } - - f.Fuzz(func(t *testing.T, v uint32) { + // Direct cast must be identity: gputypes matches v29 exactly. gf := gputypes.TextureFormat(v) - wf := toWGPUTextureFormat(gf) - back := fromWGPUTextureFormat(wf) - // For known formats, round-trip must be identity - // Skip formats removed from wgpu-native (R16Unorm/Snorm, RG16Unorm/Snorm, RGBA16Unorm/Snorm) - removedFormats := map[uint32]bool{5: true, 6: true, 17: true, 18: true, 36: true, 37: true} - if !removedFormats[v] && v <= 49 { - if back != gf { - t.Errorf("round-trip failed for gputypes format %d: toWGPU=%d, fromWGPU=%d", v, wf, back) - } + wire := uint32(gf) + if wire != v { + t.Errorf("direct cast changed value: input %d, got %d", v, wire) + } + // Round-trip from wire back to gputypes must also be identity. + back := gputypes.TextureFormat(wire) + if back != gf { + t.Errorf("round-trip failed for format %d", v) } - }) -} - -func FuzzToWGPULoadOp(f *testing.F) { - f.Add(uint32(0)) - f.Add(uint32(1)) - f.Add(uint32(2)) - f.Add(uint32(0xFFFFFFFF)) - f.Fuzz(func(t *testing.T, v uint32) { - _ = toWGPULoadOp(gputypes.LoadOp(v)) }) } diff --git a/wgpu/gputypes_aliases.go b/wgpu/gputypes_aliases.go new file mode 100644 index 0000000..b5fdab2 --- /dev/null +++ b/wgpu/gputypes_aliases.go @@ -0,0 +1,368 @@ +package wgpu + +import "github.com/gogpu/gputypes" + +// Type aliases from gputypes for single-import ergonomics. +// Importing "github.com/go-webgpu/webgpu/wgpu" is sufficient — no separate +// gputypes import required when using these aliases. + +// Extent3D is a 3D extent (width/height/depth or array layers). +type Extent3D = gputypes.Extent3D + +// Origin3D is a 3D origin (x/y/z or array layer offset). +type Origin3D = gputypes.Origin3D + +// Color is an RGBA color with double precision. +// Note: wgpu package also defines Color struct for render pass clear values. +// This alias shadows it — use wgpu.Color directly for the render pass type. + +// MapMode specifies buffer mapping mode. +// Note: MapMode is already defined as a native type in buffer.go (uint64). + +// Texture types. +// TextureAspect is defined as a native enum in enums.go. +type TextureFormat = gputypes.TextureFormat +type TextureDimension = gputypes.TextureDimension +type TextureViewDimension = gputypes.TextureViewDimension + +// Buffer types. +type BufferUsage = gputypes.BufferUsage + +// Texture usage type. +type TextureUsage = gputypes.TextureUsage + +// Shader stage type. +// ShaderStage is the uint32 bitflag for individual stages (vertex/fragment/compute). +// ShaderStages is an alias for the same type for use in pipeline descriptors. +type ShaderStage = gputypes.ShaderStage +type ShaderStages = gputypes.ShaderStages + +// Primitive assembly types. +type PrimitiveTopology = gputypes.PrimitiveTopology +type FrontFace = gputypes.FrontFace +type CullMode = gputypes.CullMode +type IndexFormat = gputypes.IndexFormat + +// Blend types. +type BlendFactor = gputypes.BlendFactor +type BlendOperation = gputypes.BlendOperation +type ColorWriteMask = gputypes.ColorWriteMask + +// Depth/stencil types. +type CompareFunction = gputypes.CompareFunction +type StencilOperation = gputypes.StencilOperation + +// Vertex types. +type VertexFormat = gputypes.VertexFormat +type VertexStepMode = gputypes.VertexStepMode + +// Sampler types. +type FilterMode = gputypes.FilterMode +type MipmapFilterMode = gputypes.MipmapFilterMode +type AddressMode = gputypes.AddressMode + +// Surface/presentation types. +type PresentMode = gputypes.PresentMode +type CompositeAlphaMode = gputypes.CompositeAlphaMode + +// Adapter types. +type PowerPreference = gputypes.PowerPreference + +// Render pass types. +type LoadOp = gputypes.LoadOp +type StoreOp = gputypes.StoreOp + +// Features is a bitmask of enabled GPU features. +// Note: Limits is defined as a native FFI struct in adapter.go (matches wgpu-native ABI). +type Features = gputypes.Features + +// --- BufferUsage constants --- + +const ( + BufferUsageNone = gputypes.BufferUsageNone + BufferUsageMapRead = gputypes.BufferUsageMapRead + BufferUsageMapWrite = gputypes.BufferUsageMapWrite + BufferUsageCopySrc = gputypes.BufferUsageCopySrc + BufferUsageCopyDst = gputypes.BufferUsageCopyDst + BufferUsageIndex = gputypes.BufferUsageIndex + BufferUsageVertex = gputypes.BufferUsageVertex + BufferUsageUniform = gputypes.BufferUsageUniform + BufferUsageStorage = gputypes.BufferUsageStorage + BufferUsageIndirect = gputypes.BufferUsageIndirect + BufferUsageQueryResolve = gputypes.BufferUsageQueryResolve +) + +// --- TextureUsage constants --- + +const ( + TextureUsageNone = gputypes.TextureUsageNone + TextureUsageCopySrc = gputypes.TextureUsageCopySrc + TextureUsageCopyDst = gputypes.TextureUsageCopyDst + TextureUsageTextureBinding = gputypes.TextureUsageTextureBinding + TextureUsageStorageBinding = gputypes.TextureUsageStorageBinding + TextureUsageRenderAttachment = gputypes.TextureUsageRenderAttachment +) + +// --- TextureFormat constants --- + +const ( + TextureFormatUndefined = gputypes.TextureFormatUndefined + TextureFormatR8Unorm = gputypes.TextureFormatR8Unorm + TextureFormatR8Snorm = gputypes.TextureFormatR8Snorm + TextureFormatR8Uint = gputypes.TextureFormatR8Uint + TextureFormatR8Sint = gputypes.TextureFormatR8Sint + TextureFormatR16Uint = gputypes.TextureFormatR16Uint + TextureFormatR16Sint = gputypes.TextureFormatR16Sint + TextureFormatR16Float = gputypes.TextureFormatR16Float + TextureFormatRG8Unorm = gputypes.TextureFormatRG8Unorm + TextureFormatRG8Snorm = gputypes.TextureFormatRG8Snorm + TextureFormatRG8Uint = gputypes.TextureFormatRG8Uint + TextureFormatRG8Sint = gputypes.TextureFormatRG8Sint + TextureFormatR32Float = gputypes.TextureFormatR32Float + TextureFormatR32Uint = gputypes.TextureFormatR32Uint + TextureFormatR32Sint = gputypes.TextureFormatR32Sint + TextureFormatRG16Uint = gputypes.TextureFormatRG16Uint + TextureFormatRG16Sint = gputypes.TextureFormatRG16Sint + TextureFormatRG16Float = gputypes.TextureFormatRG16Float + TextureFormatRGBA8Unorm = gputypes.TextureFormatRGBA8Unorm + TextureFormatRGBA8UnormSrgb = gputypes.TextureFormatRGBA8UnormSrgb + TextureFormatRGBA8Snorm = gputypes.TextureFormatRGBA8Snorm + TextureFormatRGBA8Uint = gputypes.TextureFormatRGBA8Uint + TextureFormatRGBA8Sint = gputypes.TextureFormatRGBA8Sint + TextureFormatBGRA8Unorm = gputypes.TextureFormatBGRA8Unorm + TextureFormatBGRA8UnormSrgb = gputypes.TextureFormatBGRA8UnormSrgb + TextureFormatRGB10A2Uint = gputypes.TextureFormatRGB10A2Uint + TextureFormatRGB10A2Unorm = gputypes.TextureFormatRGB10A2Unorm + TextureFormatRG11B10Ufloat = gputypes.TextureFormatRG11B10Ufloat + TextureFormatRG32Float = gputypes.TextureFormatRG32Float + TextureFormatRG32Uint = gputypes.TextureFormatRG32Uint + TextureFormatRG32Sint = gputypes.TextureFormatRG32Sint + TextureFormatRGBA16Uint = gputypes.TextureFormatRGBA16Uint + TextureFormatRGBA16Sint = gputypes.TextureFormatRGBA16Sint + TextureFormatRGBA16Float = gputypes.TextureFormatRGBA16Float + TextureFormatRGBA32Float = gputypes.TextureFormatRGBA32Float + TextureFormatRGBA32Uint = gputypes.TextureFormatRGBA32Uint + TextureFormatRGBA32Sint = gputypes.TextureFormatRGBA32Sint + TextureFormatDepth32Float = gputypes.TextureFormatDepth32Float + TextureFormatDepth24Plus = gputypes.TextureFormatDepth24Plus + TextureFormatDepth24PlusStencil8 = gputypes.TextureFormatDepth24PlusStencil8 + TextureFormatDepth16Unorm = gputypes.TextureFormatDepth16Unorm +) + +// --- TextureDimension constants --- + +const ( + TextureDimension1D = gputypes.TextureDimension1D + TextureDimension2D = gputypes.TextureDimension2D + TextureDimension3D = gputypes.TextureDimension3D +) + +// --- ShaderStage constants --- + +const ( + ShaderStageNone = gputypes.ShaderStageNone + ShaderStageVertex = gputypes.ShaderStageVertex + ShaderStageFragment = gputypes.ShaderStageFragment + ShaderStageCompute = gputypes.ShaderStageCompute +) + +// --- PrimitiveTopology constants --- + +const ( + PrimitiveTopologyPointList = gputypes.PrimitiveTopologyPointList + PrimitiveTopologyLineList = gputypes.PrimitiveTopologyLineList + PrimitiveTopologyLineStrip = gputypes.PrimitiveTopologyLineStrip + PrimitiveTopologyTriangleList = gputypes.PrimitiveTopologyTriangleList + PrimitiveTopologyTriangleStrip = gputypes.PrimitiveTopologyTriangleStrip +) + +// --- FrontFace constants --- + +const ( + FrontFaceCCW = gputypes.FrontFaceCCW + FrontFaceCW = gputypes.FrontFaceCW +) + +// --- CullMode constants --- + +const ( + CullModeNone = gputypes.CullModeNone + CullModeFront = gputypes.CullModeFront + CullModeBack = gputypes.CullModeBack +) + +// --- IndexFormat constants --- + +const ( + IndexFormatUint16 = gputypes.IndexFormatUint16 + IndexFormatUint32 = gputypes.IndexFormatUint32 +) + +// --- LoadOp constants --- + +const ( + LoadOpLoad = gputypes.LoadOpLoad + LoadOpClear = gputypes.LoadOpClear +) + +// --- StoreOp constants --- + +const ( + StoreOpStore = gputypes.StoreOpStore + StoreOpDiscard = gputypes.StoreOpDiscard +) + +// --- FilterMode constants --- + +const ( + FilterModeNearest = gputypes.FilterModeNearest + FilterModeLinear = gputypes.FilterModeLinear +) + +// --- AddressMode constants --- + +const ( + AddressModeRepeat = gputypes.AddressModeRepeat + AddressModeMirrorRepeat = gputypes.AddressModeMirrorRepeat + AddressModeClampToEdge = gputypes.AddressModeClampToEdge +) + +// --- CompareFunction constants --- + +const ( + CompareFunctionUndefined = gputypes.CompareFunctionUndefined + CompareFunctionNever = gputypes.CompareFunctionNever + CompareFunctionLess = gputypes.CompareFunctionLess + CompareFunctionEqual = gputypes.CompareFunctionEqual + CompareFunctionLessEqual = gputypes.CompareFunctionLessEqual + CompareFunctionGreater = gputypes.CompareFunctionGreater + CompareFunctionNotEqual = gputypes.CompareFunctionNotEqual + CompareFunctionGreaterEqual = gputypes.CompareFunctionGreaterEqual + CompareFunctionAlways = gputypes.CompareFunctionAlways +) + +// --- PresentMode constants --- + +const ( + PresentModeImmediate = gputypes.PresentModeImmediate + PresentModeMailbox = gputypes.PresentModeMailbox + PresentModeFifo = gputypes.PresentModeFifo + PresentModeFifoRelaxed = gputypes.PresentModeFifoRelaxed +) + +// --- CompositeAlphaMode constants --- + +const ( + CompositeAlphaModeAuto = gputypes.CompositeAlphaModeAuto + CompositeAlphaModeOpaque = gputypes.CompositeAlphaModeOpaque + CompositeAlphaModePremultiplied = gputypes.CompositeAlphaModePremultiplied + CompositeAlphaModeUnpremultiplied = gputypes.CompositeAlphaModeUnpremultiplied + CompositeAlphaModeInherit = gputypes.CompositeAlphaModeInherit +) + +// --- PowerPreference constants --- + +const ( + PowerPreferenceNone = gputypes.PowerPreferenceNone + PowerPreferenceLowPower = gputypes.PowerPreferenceLowPower + PowerPreferenceHighPerformance = gputypes.PowerPreferenceHighPerformance +) + +// --- ColorWriteMask constants --- + +const ( + ColorWriteMaskNone = gputypes.ColorWriteMaskNone + ColorWriteMaskRed = gputypes.ColorWriteMaskRed + ColorWriteMaskGreen = gputypes.ColorWriteMaskGreen + ColorWriteMaskBlue = gputypes.ColorWriteMaskBlue + ColorWriteMaskAlpha = gputypes.ColorWriteMaskAlpha + ColorWriteMaskAll = gputypes.ColorWriteMaskAll +) + +// --- VertexFormat constants --- + +const ( + VertexFormatUint8x2 = gputypes.VertexFormatUint8x2 + VertexFormatUint8x4 = gputypes.VertexFormatUint8x4 + VertexFormatSint8x2 = gputypes.VertexFormatSint8x2 + VertexFormatSint8x4 = gputypes.VertexFormatSint8x4 + VertexFormatFloat32 = gputypes.VertexFormatFloat32 + VertexFormatFloat32x2 = gputypes.VertexFormatFloat32x2 + VertexFormatFloat32x3 = gputypes.VertexFormatFloat32x3 + VertexFormatFloat32x4 = gputypes.VertexFormatFloat32x4 + VertexFormatUint32 = gputypes.VertexFormatUint32 + VertexFormatUint32x2 = gputypes.VertexFormatUint32x2 + VertexFormatUint32x3 = gputypes.VertexFormatUint32x3 + VertexFormatUint32x4 = gputypes.VertexFormatUint32x4 + VertexFormatSint32 = gputypes.VertexFormatSint32 + VertexFormatSint32x2 = gputypes.VertexFormatSint32x2 + VertexFormatSint32x3 = gputypes.VertexFormatSint32x3 + VertexFormatSint32x4 = gputypes.VertexFormatSint32x4 +) + +// --- VertexStepMode constants --- + +const ( + VertexStepModeVertex = gputypes.VertexStepModeVertex + VertexStepModeInstance = gputypes.VertexStepModeInstance +) + +// Binding layout types. +type BufferBindingType = gputypes.BufferBindingType +type SamplerBindingType = gputypes.SamplerBindingType +type TextureSampleType = gputypes.TextureSampleType + +// --- BufferBindingType constants --- + +const ( + BufferBindingTypeUndefined = gputypes.BufferBindingTypeUndefined + BufferBindingTypeUniform = gputypes.BufferBindingTypeUniform + BufferBindingTypeStorage = gputypes.BufferBindingTypeStorage + BufferBindingTypeReadOnlyStorage = gputypes.BufferBindingTypeReadOnlyStorage +) + +// --- SamplerBindingType constants --- + +const ( + SamplerBindingTypeUndefined = gputypes.SamplerBindingTypeUndefined + SamplerBindingTypeFiltering = gputypes.SamplerBindingTypeFiltering + SamplerBindingTypeNonFiltering = gputypes.SamplerBindingTypeNonFiltering + SamplerBindingTypeComparison = gputypes.SamplerBindingTypeComparison +) + +// --- TextureSampleType constants --- + +const ( + TextureSampleTypeUndefined = gputypes.TextureSampleTypeUndefined + TextureSampleTypeFloat = gputypes.TextureSampleTypeFloat + TextureSampleTypeUnfilterableFloat = gputypes.TextureSampleTypeUnfilterableFloat + TextureSampleTypeDepth = gputypes.TextureSampleTypeDepth + TextureSampleTypeSint = gputypes.TextureSampleTypeSint + TextureSampleTypeUint = gputypes.TextureSampleTypeUint +) + +// --- TextureViewDimension constants --- + +const ( + TextureViewDimensionUndefined = gputypes.TextureViewDimensionUndefined + TextureViewDimension1D = gputypes.TextureViewDimension1D + TextureViewDimension2D = gputypes.TextureViewDimension2D + TextureViewDimension2DArray = gputypes.TextureViewDimension2DArray + TextureViewDimensionCube = gputypes.TextureViewDimensionCube + TextureViewDimensionCubeArray = gputypes.TextureViewDimensionCubeArray + TextureViewDimension3D = gputypes.TextureViewDimension3D +) + +// --- StencilOperation constants --- + +const ( + StencilOperationUndefined = gputypes.StencilOperationUndefined + StencilOperationKeep = gputypes.StencilOperationKeep + StencilOperationZero = gputypes.StencilOperationZero + StencilOperationReplace = gputypes.StencilOperationReplace + StencilOperationInvert = gputypes.StencilOperationInvert + StencilOperationIncrementClamp = gputypes.StencilOperationIncrementClamp + StencilOperationDecrementClamp = gputypes.StencilOperationDecrementClamp + StencilOperationIncrementWrap = gputypes.StencilOperationIncrementWrap + StencilOperationDecrementWrap = gputypes.StencilOperationDecrementWrap +) diff --git a/wgpu/indirect_test.go b/wgpu/indirect_test.go index 29a00d1..bcdb644 100644 --- a/wgpu/indirect_test.go +++ b/wgpu/indirect_test.go @@ -26,7 +26,7 @@ func TestDispatchWorkgroupsIndirect(t *testing.T) { } defer device.Release() - queue := device.GetQueue() + queue := device.Queue() defer queue.Release() // Create compute shader @@ -41,15 +41,15 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { } } ` - shader := device.CreateShaderModuleWGSL(shaderCode) - if shader == nil { - t.Fatal("CreateShaderModuleWGSL returned nil") + shader, err := device.CreateShaderModuleWGSL(shaderCode) + if err != nil { + t.Fatalf("CreateShaderModuleWGSL failed: %v", err) } defer shader.Release() - pipeline := device.CreateComputePipelineSimple(nil, shader, "main") - if pipeline == nil { - t.Fatal("CreateComputePipelineSimple returned nil") + pipeline, err := device.CreateComputePipelineSimple(nil, shader, "main") + if err != nil { + t.Fatalf("CreateComputePipelineSimple failed: %v", err) } defer pipeline.Release() @@ -61,13 +61,13 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { } bufferSize := uint64(numElements * 4) - storageBuffer := device.CreateBuffer(&BufferDescriptor{ + storageBuffer, err := device.CreateBuffer(&BufferDescriptor{ Usage: gputypes.BufferUsageStorage | gputypes.BufferUsageCopySrc | gputypes.BufferUsageCopyDst, Size: bufferSize, - MappedAtCreation: True, + MappedAtCreation: true, }) - if storageBuffer == nil { - t.Fatal("CreateBuffer for storage returned nil") + if err != nil { + t.Fatalf("CreateBuffer for storage failed: %v", err) } defer storageBuffer.Release() @@ -86,13 +86,13 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { } indirectSize := uint64(unsafe.Sizeof(indirectArgs)) - indirectBuffer := device.CreateBuffer(&BufferDescriptor{ + indirectBuffer, err := device.CreateBuffer(&BufferDescriptor{ Usage: gputypes.BufferUsageIndirect | gputypes.BufferUsageCopyDst, Size: indirectSize, - MappedAtCreation: True, + MappedAtCreation: true, }) - if indirectBuffer == nil { - t.Fatal("CreateBuffer for indirect returned nil") + if err != nil { + t.Fatalf("CreateBuffer for indirect failed: %v", err) } defer indirectBuffer.Release() @@ -109,21 +109,24 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { } defer bindGroupLayout.Release() - bindGroup := device.CreateBindGroupSimple(bindGroupLayout, []BindGroupEntry{ + bindGroup, err := device.CreateBindGroupSimple(bindGroupLayout, []BindGroupEntry{ BufferBindingEntry(0, storageBuffer, 0, bufferSize), }) - if bindGroup == nil { - t.Fatal("CreateBindGroupSimple returned nil") + if err != nil { + t.Fatalf("CreateBindGroupSimple failed: %v", err) } defer bindGroup.Release() // Create and submit command buffer - encoder := device.CreateCommandEncoder(nil) - if encoder == nil { - t.Fatal("CreateCommandEncoder returned nil") + encoder, err := device.CreateCommandEncoder(nil) + if err != nil { + t.Fatalf("CreateCommandEncoder failed: %v", err) } - computePass := encoder.BeginComputePass(nil) + computePass, err := encoder.BeginComputePass(nil) + if err != nil { + t.Fatalf("BeginComputePass failed: %v", err) + } computePass.SetPipeline(pipeline) computePass.SetBindGroup(0, bindGroup, nil) @@ -133,7 +136,10 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { computePass.End() computePass.Release() - cmdBuffer := encoder.Finish(nil) + cmdBuffer, err := encoder.Finish(nil) + if err != nil { + t.Fatalf("Finish failed: %v", err) + } encoder.Release() queue.Submit(cmdBuffer) cmdBuffer.Release() @@ -226,13 +232,13 @@ func TestRenderBundleDrawIndirect(t *testing.T) { } indirectSize := uint64(unsafe.Sizeof(indirectArgs)) - indirectBuffer := device.CreateBuffer(&BufferDescriptor{ + indirectBuffer, err := device.CreateBuffer(&BufferDescriptor{ Usage: gputypes.BufferUsageIndirect | gputypes.BufferUsageCopyDst, Size: indirectSize, - MappedAtCreation: True, + MappedAtCreation: true, }) - if indirectBuffer == nil { - t.Fatal("CreateBuffer for indirect returned nil") + if err != nil { + t.Fatalf("CreateBuffer for indirect failed: %v", err) } defer indirectBuffer.Release() @@ -259,20 +265,20 @@ fn fs_main() -> @location(0) vec4 { return vec4(1.0, 0.0, 0.0, 1.0); } ` - shader := device.CreateShaderModuleWGSL(shaderCode) - if shader == nil { - t.Fatal("CreateShaderModuleWGSL returned nil") + shader, err := device.CreateShaderModuleWGSL(shaderCode) + if err != nil { + t.Fatalf("CreateShaderModuleWGSL failed: %v", err) } defer shader.Release() - pipeline := device.CreateRenderPipelineSimple( + pipeline, err := device.CreateRenderPipelineSimple( nil, shader, "vs_main", shader, "fs_main", gputypes.TextureFormatBGRA8Unorm, ) - if pipeline == nil { - t.Fatal("CreateRenderPipelineSimple returned nil") + if err != nil { + t.Fatalf("CreateRenderPipelineSimple failed: %v", err) } defer pipeline.Release() diff --git a/wgpu/instance.go b/wgpu/instance.go index 897852c..b261d66 100644 --- a/wgpu/instance.go +++ b/wgpu/instance.go @@ -2,21 +2,77 @@ package wgpu import ( "unsafe" + + "github.com/gogpu/gputypes" ) +// InstanceDescriptor configures instance creation. +// Matches the gogpu/wgpu API for cross-project compatibility. +// +// Pass nil to CreateInstance for default configuration (all primary backends enabled). +type InstanceDescriptor struct { + // Backends selects which GPU backends to enable. + // Use gputypes.BackendsPrimary (default) or specific backends. + Backends gputypes.Backends + // Flags controls instance features like debug layers and validation. + // Use gputypes.InstanceFlagsDebug to enable GPU debug layer. + Flags gputypes.InstanceFlags +} + +// instanceDescriptorWire is the FFI-compatible C-layout struct for wgpuCreateInstance. +// v29 layout: nextInChain(8)+requiredFeatureCount(8)+requiredFeatures(8)+requiredLimits(8) = 32 bytes. +// The v27 InstanceCapabilities/Features field is removed in v29. +type instanceDescriptorWire struct { + NextInChain uintptr // *ChainedStruct + RequiredFeatureCount uintptr // size_t + RequiredFeatures uintptr // *InstanceFeatureName (const) + RequiredLimits uintptr // *InstanceLimits (const, nullable) +} + +// InstanceLimits describes the limits required at instance creation. +// New in v29 — passed as RequiredLimits in instanceDescriptorWire. +type InstanceLimits struct { + NextInChain uintptr // *ChainedStruct (nullable) + TimedWaitAnyMaxCount uint64 +} + +// Bool is a WebGPU boolean (uint32). +type Bool uint32 + +const ( + // False is the WebGPU boolean false value (0). + False Bool = 0 + // True is the WebGPU boolean true value (1). + True Bool = 1 +) + +// ChainedStruct is used for struct chaining (both input and output). +// In v29 ChainedStructOut was unified with ChainedStruct — use ChainedStruct everywhere. +type ChainedStruct struct { + Next uintptr // *ChainedStruct + SType uint32 +} + +// ChainedStructOut is kept for backward compatibility. +// Deprecated: Use ChainedStruct. In v29 there is no separate ChainedStructOut in C header. +type ChainedStructOut = ChainedStruct + // CreateInstance creates a new WebGPU instance. -// Pass nil for default configuration. +// Pass nil for default configuration (all primary backends enabled). func CreateInstance(desc *InstanceDescriptor) (*Instance, error) { if err := checkInit(); err != nil { return nil, err } - var descPtr uintptr + // Convert Go-idiomatic descriptor to wire format. + // When desc is nil, pass null to wgpu-native for default behavior. + var wirePtr uintptr if desc != nil { - descPtr = uintptr(unsafe.Pointer(desc)) + wire := instanceDescriptorWire{} // zero = default, backends/flags handled by wgpu-native extensions + wirePtr = uintptr(unsafe.Pointer(&wire)) } - handle, _, _ := procCreateInstance.Call(descPtr) + handle, _, _ := procCreateInstance.Call(wirePtr) if handle == 0 { return nil, &WGPUError{Op: "CreateInstance", Message: "failed to create instance"} } @@ -41,40 +97,3 @@ func (i *Instance) ProcessEvents() { } procInstanceProcessEvents.Call(i.handle) //nolint:errcheck } - -// ChainedStruct is used for struct chaining (input). -type ChainedStruct struct { - Next uintptr // *ChainedStruct - SType uint32 -} - -// ChainedStructOut is used for struct chaining (output). -type ChainedStructOut struct { - Next uintptr // *ChainedStructOut - SType uint32 -} - -// InstanceCapabilities describes instance capabilities. -// Note: This struct has specific padding requirements to match C layout. -type InstanceCapabilities struct { - NextInChain uintptr // *ChainedStructOut - TimedWaitAnyEnable Bool - _pad uint32 // padding to align TimedWaitAnyMaxCount - TimedWaitAnyMaxCount uint64 -} - -// InstanceDescriptor describes an Instance. -type InstanceDescriptor struct { - NextInChain uintptr // *ChainedStruct - Features InstanceCapabilities -} - -// Bool is a WebGPU boolean (uint32). -type Bool uint32 - -const ( - // False is the WebGPU boolean false value (0). - False Bool = 0 - // True is the WebGPU boolean true value (1). - True Bool = 1 -) diff --git a/wgpu/instance_test.go b/wgpu/instance_test.go index 32c36fe..cb11ba8 100644 --- a/wgpu/instance_test.go +++ b/wgpu/instance_test.go @@ -15,8 +15,10 @@ func TestInit(t *testing.T) { func TestStructSizes(t *testing.T) { t.Logf("sizeof(ChainedStruct) = %d (expected 16)", unsafe.Sizeof(ChainedStruct{})) - t.Logf("sizeof(InstanceCapabilities) = %d (expected 24)", unsafe.Sizeof(InstanceCapabilities{})) + // v29: InstanceDescriptor changed layout — nextInChain(8) + requiredFeatureCount(8) + requiredFeatures(8) + requiredLimits(8) = 32 t.Logf("sizeof(InstanceDescriptor) = %d (expected 32)", unsafe.Sizeof(InstanceDescriptor{})) + // Public Limits (no NextInChain); limitsWire is the FFI struct (152 bytes). + t.Logf("sizeof(Limits) = %d (public, no NextInChain)", unsafe.Sizeof(Limits{})) } func TestCreateInstanceWithNil(t *testing.T) { @@ -34,14 +36,8 @@ func TestCreateInstanceWithNil(t *testing.T) { } func TestCreateInstanceWithDescriptor(t *testing.T) { - desc := &InstanceDescriptor{ - NextInChain: 0, - Features: InstanceCapabilities{ - NextInChain: 0, - TimedWaitAnyEnable: False, - TimedWaitAnyMaxCount: 0, - }, - } + // Pass an explicit InstanceDescriptor with default (zero) values. + desc := &InstanceDescriptor{} inst, err := CreateInstance(desc) if err != nil { diff --git a/wgpu/map_pending.go b/wgpu/map_pending.go new file mode 100644 index 0000000..31d088e --- /dev/null +++ b/wgpu/map_pending.go @@ -0,0 +1,216 @@ +package wgpu + +import ( + "context" + "runtime" + "unsafe" +) + +// MapPending represents an in-flight buffer map request. +// Created by [Buffer.MapAsync]; poll Status() or call Wait() to resolve. +// +// The caller must not access the mapped buffer data until Status() returns +// ready=true with err=nil, or Wait() returns nil. +type MapPending struct { + req *mapRequest + done bool + err error +} + +// Status reports whether the map request has completed. +// Non-blocking — returns (false, nil) if still pending. +// Once it returns (true, ...), subsequent calls return the same value. +func (p *MapPending) Status() (ready bool, err error) { + if p == nil { + return true, nil + } + if p.done { + return true, p.err + } + select { + case <-p.req.done: + p.done = true + if p.req.status != MapAsyncStatusSuccess { + msg := p.req.message + if msg == "" { + msg = "buffer map failed" + } + p.err = &WGPUError{Op: "Buffer.MapAsync", Message: msg} + } + return true, p.err + default: + return false, nil + } +} + +// Wait blocks until the map request completes or ctx is canceled. +// Returns nil on success, ctx.Err() if context was canceled before completion. +func (p *MapPending) Wait(ctx context.Context) error { + if p == nil { + return nil + } + if p.done { + return p.err + } + if ctx == nil { + ctx = context.Background() + } + select { + case <-p.req.done: + p.done = true + if p.req.status != MapAsyncStatusSuccess { + msg := p.req.message + if msg == "" { + msg = "buffer map failed" + } + p.err = &WGPUError{Op: "Buffer.MapAsync", Message: msg} + } + return p.err + case <-ctx.Done(): + return ctx.Err() + } +} + +// Release discards the pending handle. Safe to call after Wait/Status resolved. +func (p *MapPending) Release() {} + +// mapAsyncStart issues wgpuBufferMapAsync and returns the mapRequest. +// Shared by MapAsync (non-blocking) and Map (blocking with poll loop). +func (b *Buffer) mapAsyncStart(mode MapMode, offset, size uint64) (*mapRequest, error) { + if err := checkInit(); err != nil { + return nil, err + } + if b == nil || b.handle == 0 { + return nil, &WGPUError{Op: "Buffer.MapAsync", Message: "buffer is nil or released"} + } + + mapCallbackOnce.Do(initMapCallback) + + req := &mapRequest{ + done: make(chan struct{}), + } + + mapRequestsMu.Lock() + mapRequestID++ + reqID := mapRequestID + mapRequests[reqID] = req + mapRequestsMu.Unlock() + + callbackInfo := BufferMapCallbackInfo{ + NextInChain: 0, + Mode: CallbackModeAllowProcessEvents, + Callback: mapCallbackPtr, + Userdata1: reqID, + Userdata2: 0, + } + + procBufferMapAsync.Call( //nolint:errcheck + b.handle, + uintptr(mode), + uintptr(offset), + uintptr(size), + uintptr(unsafe.Pointer(&callbackInfo)), + ) + + return req, nil +} + +// MapAsync initiates an asynchronous buffer map without blocking. +// Returns a *MapPending that resolves once the GPU completes the operation. +// +// The caller must periodically drive Device.Poll(false) so the mapping resolves. +// For a blocking variant use [Buffer.Map]. +// +// Matches gogpu/wgpu Buffer.MapAsync(mode, offset, size) (*MapPending, error). +func (b *Buffer) MapAsync(mode MapMode, offset, size uint64) (*MapPending, error) { + req, err := b.mapAsyncStart(mode, offset, size) + if err != nil { + return nil, err + } + return &MapPending{req: req}, nil +} + +// Map blocks until a CPU-visible mapping is established for the given byte +// range, or until ctx is canceled. +// +// The buffer must have been created with BufferUsageMapRead or +// BufferUsageMapWrite matching mode. offset must be a multiple of 8 and +// size must be a multiple of 4 (WebGPU MAP_ALIGNMENT). +// +// After Map succeeds, call MappedRange to obtain a byte view and Unmap +// when finished: +// +// if err := buf.Map(ctx, wgpu.MapModeRead, 0, size); err != nil { +// return err +// } +// defer buf.Unmap() +// rng, _ := buf.MappedRange(0, size) +// data := rng.Bytes() +// +// Matches gogpu/wgpu Buffer.Map(ctx, mode, offset, size) error. +func (b *Buffer) Map(ctx context.Context, mode MapMode, offset, size uint64) error { + if ctx == nil { + ctx = context.Background() + } + if err := ctx.Err(); err != nil { + return err + } + + req, err := b.mapAsyncStart(mode, offset, size) + if err != nil { + return err + } + + // Resolve device: prefer b.device, fall back to nil poll path. + dev := b.device + + // Kick an initial synchronous poll — for immediate-complete backends. + if dev != nil { + dev.Poll(false) + } + + // Check if already done (fast path for synchronous backends). + select { + case <-req.done: + if req.status != MapAsyncStatusSuccess { + msg := req.message + if msg == "" { + msg = "buffer map failed" + } + return &WGPUError{Op: "Buffer.Map", Message: msg} + } + return nil + default: + } + + // Start a polling goroutine so the mapping resolves even when the + // caller does not drive Poll itself. This matches the gogpu/wgpu pattern. + if dev != nil { + go func() { + for { + select { + case <-req.done: + return + default: + dev.Poll(false) + runtime.Gosched() + } + } + }() + } + + // Wait for completion or context cancellation. + select { + case <-req.done: + if req.status != MapAsyncStatusSuccess { + msg := req.message + if msg == "" { + msg = "buffer map failed" + } + return &WGPUError{Op: "Buffer.Map", Message: msg} + } + return nil + case <-ctx.Done(): + return ctx.Err() + } +} diff --git a/wgpu/mapped_range.go b/wgpu/mapped_range.go new file mode 100644 index 0000000..fec78aa --- /dev/null +++ b/wgpu/mapped_range.go @@ -0,0 +1,71 @@ +package wgpu + +import "unsafe" + +// MappedRange provides safe access to a mapped buffer region. +// Obtained via [Buffer.MappedRange] after a successful [Buffer.Map] or +// [Buffer.MapAsync]. The data slice is invalidated by [Buffer.Unmap]. +// +// Matches gogpu/wgpu MappedRange. +type MappedRange struct { + data unsafe.Pointer + size uint64 + offset uint64 + buf *Buffer +} + +// Bytes returns the mapped memory as a byte slice. +// Returns nil if the buffer is not mapped or the range has been invalidated. +// +// The slice is valid only while the buffer remains mapped. Calling +// [Buffer.Unmap] or [Buffer.Release] after this will cause undefined +// behavior if the slice is still accessed. +func (m *MappedRange) Bytes() []byte { + if m == nil || m.data == nil { + return nil + } + // Safety check: buffer must still exist and be mapped. + if m.buf != nil && m.buf.MapState() != BufferMapStateMapped { + return nil + } + return unsafe.Slice((*byte)(m.data), m.size) +} + +// Len returns the size of the mapped range in bytes. +func (m *MappedRange) Len() int { + if m == nil { + return 0 + } + return int(m.size) +} + +// Offset returns the byte offset of this range within the buffer. +func (m *MappedRange) Offset() uint64 { + if m == nil { + return 0 + } + return m.offset +} + +// MappedRange returns a safe view over the mapped region [offset, offset+size). +// +// The buffer must be in the Mapped state ([Buffer.Map] or [Buffer.MapAsync] +// resolved to success). The returned MappedRange.Bytes() slice is invalidated +// by [Buffer.Unmap]. +// +// Matches gogpu/wgpu Buffer.MappedRange(offset, size) (*MappedRange, error). +func (b *Buffer) MappedRange(offset, size uint64) (*MappedRange, error) { + if b == nil || b.handle == 0 { + return nil, &WGPUError{Op: "Buffer.MappedRange", Message: "buffer is nil or released"} + } + ptr := b.GetMappedRange(offset, size) + if ptr == nil { + return nil, &WGPUError{Op: "Buffer.MappedRange", Message: "buffer not mapped or invalid range"} + } + return &MappedRange{ + data: ptr, + size: size, + offset: offset, + buf: b, + }, nil +} diff --git a/wgpu/null_guard_test.go b/wgpu/null_guard_test.go index c0a96b4..2e37d67 100644 --- a/wgpu/null_guard_test.go +++ b/wgpu/null_guard_test.go @@ -12,91 +12,91 @@ func TestNullGuard_Device_Creation(t *testing.T) { var d *Device t.Run("CreateCommandEncoder", func(t *testing.T) { - result := d.CreateCommandEncoder(nil) - if result != nil { - t.Error("expected nil for nil device") + result, err := d.CreateCommandEncoder(nil) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil device") } }) t.Run("CreateBuffer", func(t *testing.T) { - result := d.CreateBuffer(&BufferDescriptor{}) - if result != nil { - t.Error("expected nil for nil device") + result, err := d.CreateBuffer(&BufferDescriptor{}) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil device") } }) t.Run("CreateTexture", func(t *testing.T) { - result := d.CreateTexture(&TextureDescriptor{}) - if result != nil { - t.Error("expected nil for nil device") + result, err := d.CreateTexture(&TextureDescriptor{}) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil device") } }) t.Run("CreateShaderModuleWGSL", func(t *testing.T) { - result := d.CreateShaderModuleWGSL("@vertex fn main() {}") - if result != nil { - t.Error("expected nil for nil device") + result, err := d.CreateShaderModuleWGSL("@vertex fn main() {}") + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil device") } }) t.Run("CreateSampler", func(t *testing.T) { - result := d.CreateSampler(&SamplerDescriptor{}) - if result != nil { - t.Error("expected nil for nil device") + result, err := d.CreateSampler(&SamplerDescriptor{}) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil device") } }) t.Run("CreateBindGroupLayout", func(t *testing.T) { - result := d.CreateBindGroupLayout(&BindGroupLayoutDescriptor{}) - if result != nil { - t.Error("expected nil for nil device") + result, err := d.CreateBindGroupLayout(&BindGroupLayoutDescriptor{}) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil device") } }) t.Run("CreateBindGroup", func(t *testing.T) { - result := d.CreateBindGroup(&BindGroupDescriptor{}) - if result != nil { - t.Error("expected nil for nil device") + result, err := d.CreateBindGroup(&BindGroupDescriptor{}) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil device") } }) t.Run("CreatePipelineLayout", func(t *testing.T) { - result := d.CreatePipelineLayout(&PipelineLayoutDescriptor{}) - if result != nil { - t.Error("expected nil for nil device") + result, err := d.CreatePipelineLayout(&PipelineLayoutDescriptor{}) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil device") } }) t.Run("CreateComputePipeline", func(t *testing.T) { - result := d.CreateComputePipeline(&ComputePipelineDescriptor{}) - if result != nil { - t.Error("expected nil for nil device") + result, err := d.CreateComputePipeline(&ComputePipelineDescriptor{}) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil device") } }) t.Run("CreateRenderPipeline", func(t *testing.T) { - result := d.CreateRenderPipeline(&RenderPipelineDescriptor{}) - if result != nil { - t.Error("expected nil for nil device") + result, err := d.CreateRenderPipeline(&RenderPipelineDescriptor{}) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil device") } }) t.Run("CreateQuerySet", func(t *testing.T) { - result := d.CreateQuerySet(&QuerySetDescriptor{}) - if result != nil { - t.Error("expected nil for nil device") + result, err := d.CreateQuerySet(&QuerySetDescriptor{}) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil device") } }) t.Run("CreateRenderBundleEncoder", func(t *testing.T) { - result := d.CreateRenderBundleEncoder(&RenderBundleEncoderDescriptor{}) - if result != nil { - t.Error("expected nil for nil device") + result, err := d.CreateRenderBundleEncoder(&RenderBundleEncoderDescriptor{}) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil device") } }) - t.Run("GetQueue", func(t *testing.T) { - result := d.GetQueue() + t.Run("Queue", func(t *testing.T) { + result := d.Queue() if result != nil { t.Error("expected nil for nil device") } @@ -128,19 +128,21 @@ func TestNullGuard_Device_ZeroHandle(t *testing.T) { d := &Device{handle: 0} t.Run("CreateCommandEncoder", func(t *testing.T) { - if d.CreateCommandEncoder(nil) != nil { - t.Error("expected nil for zero-handle device") + result, err := d.CreateCommandEncoder(nil) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for zero-handle device") } }) t.Run("CreateBuffer", func(t *testing.T) { - if d.CreateBuffer(&BufferDescriptor{}) != nil { - t.Error("expected nil for zero-handle device") + result, err := d.CreateBuffer(&BufferDescriptor{}) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for zero-handle device") } }) - t.Run("GetQueue", func(t *testing.T) { - if d.GetQueue() != nil { + t.Run("Queue", func(t *testing.T) { + if d.Queue() != nil { t.Error("expected nil for zero-handle device") } }) @@ -193,22 +195,25 @@ func TestNullGuard_CommandEncoder(t *testing.T) { var enc *CommandEncoder t.Run("BeginComputePass", func(t *testing.T) { - if enc.BeginComputePass(nil) != nil { - t.Error("expected nil for nil encoder") + result, err := enc.BeginComputePass(nil) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil encoder") } }) t.Run("BeginRenderPass", func(t *testing.T) { - if enc.BeginRenderPass(&RenderPassDescriptor{ + result, err := enc.BeginRenderPass(&RenderPassDescriptor{ ColorAttachments: []RenderPassColorAttachment{{}}, - }) != nil { - t.Error("expected nil for nil encoder") + }) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil encoder") } }) t.Run("Finish", func(t *testing.T) { - if enc.Finish(nil) != nil { - t.Error("expected nil for nil encoder") + result, err := enc.Finish(nil) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil encoder") } }) @@ -404,8 +409,8 @@ func TestNullGuard_Buffer(t *testing.T) { } }) - t.Run("GetSize", func(t *testing.T) { - if buf.GetSize() != 0 { + t.Run("Size", func(t *testing.T) { + if buf.Size() != 0 { t.Error("expected 0 for nil buffer") } }) @@ -420,8 +425,9 @@ func TestNullGuard_Texture(t *testing.T) { var tex *Texture t.Run("CreateView", func(t *testing.T) { - if tex.CreateView(nil) != nil { - t.Error("expected nil for nil texture") + result, err := tex.CreateView(nil) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil texture") } }) } @@ -457,7 +463,7 @@ func TestNullGuard_Surface(t *testing.T) { var s *Surface t.Run("Configure", func(t *testing.T) { - s.Configure(nil) // should not panic + _ = s.Configure(nil, nil) // should not panic }) t.Run("Unconfigure", func(t *testing.T) { @@ -465,7 +471,7 @@ func TestNullGuard_Surface(t *testing.T) { }) t.Run("GetCurrentTexture", func(t *testing.T) { - result, _ := s.GetCurrentTexture() + result, _, _ := s.GetCurrentTexture() if result != nil { t.Error("expected nil for nil surface") } @@ -529,62 +535,72 @@ func TestNullGuard_NilDesc(t *testing.T) { d := &Device{handle: 1} // fake non-zero handle t.Run("CreateBuffer_NilDesc", func(t *testing.T) { - if d.CreateBuffer(nil) != nil { - t.Error("expected nil for nil desc") + result, err := d.CreateBuffer(nil) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil desc") } }) t.Run("CreateTexture_NilDesc", func(t *testing.T) { - if d.CreateTexture(nil) != nil { - t.Error("expected nil for nil desc") + result, err := d.CreateTexture(nil) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil desc") } }) t.Run("CreateSampler_NilDesc", func(t *testing.T) { - if d.CreateSampler(nil) != nil { - t.Error("expected nil for nil desc") + result, err := d.CreateSampler(nil) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil desc") } }) t.Run("CreateBindGroupLayout_NilDesc", func(t *testing.T) { - if d.CreateBindGroupLayout(nil) != nil { - t.Error("expected nil for nil desc") + result, err := d.CreateBindGroupLayout(nil) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil desc") } }) t.Run("CreateBindGroup_NilDesc", func(t *testing.T) { - if d.CreateBindGroup(nil) != nil { - t.Error("expected nil for nil desc") + result, err := d.CreateBindGroup(nil) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil desc") } }) t.Run("CreatePipelineLayout_NilDesc", func(t *testing.T) { - if d.CreatePipelineLayout(nil) != nil { - t.Error("expected nil for nil desc") + result, err := d.CreatePipelineLayout(nil) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil desc") } }) t.Run("CreateComputePipeline_NilDesc", func(t *testing.T) { - if d.CreateComputePipeline(nil) != nil { - t.Error("expected nil for nil desc") + result, err := d.CreateComputePipeline(nil) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil desc") } }) t.Run("CreateRenderPipeline_NilDesc", func(t *testing.T) { - if d.CreateRenderPipeline(nil) != nil { - t.Error("expected nil for nil desc") + result, err := d.CreateRenderPipeline(nil) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil desc") } }) t.Run("CreateQuerySet_NilDesc", func(t *testing.T) { - if d.CreateQuerySet(nil) != nil { - t.Error("expected nil for nil desc") + result, err := d.CreateQuerySet(nil) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil desc") } }) t.Run("CreateRenderBundleEncoder_NilDesc", func(t *testing.T) { - if d.CreateRenderBundleEncoder(nil) != nil { - t.Error("expected nil for nil desc") + result, err := d.CreateRenderBundleEncoder(nil) + if result != nil || err == nil { + t.Error("expected nil result and non-nil error for nil desc") } }) } diff --git a/wgpu/pipeline.go b/wgpu/pipeline.go index c9d814c..d9a0998 100644 --- a/wgpu/pipeline.go +++ b/wgpu/pipeline.go @@ -13,16 +13,58 @@ type ProgrammableStageDescriptor struct { Constants uintptr // *ConstantEntry } -// PipelineLayoutDescriptor describes a pipeline layout. +// PipelineLayoutDescriptor describes a pipeline layout to create. +// BindGroupLayouts is a slice of *BindGroupLayout; nil for auto layout. type PipelineLayoutDescriptor struct { + Label string + BindGroupLayouts []*BindGroupLayout +} + +// pipelineLayoutDescriptorWire is the FFI-compatible C-layout struct for wgpu-native. +// v29: Added ImmediateSize field for immediate data (push constants replacement). +// CRITICAL: layout must match WGPUPipelineLayoutDescriptor exactly. +// nextInChain(8)+label(16)+bindGroupLayoutCount(8)+bindGroupLayouts(8)+immediateSize(4)+pad(4) = 48 bytes. +type pipelineLayoutDescriptorWire struct { NextInChain uintptr // *ChainedStruct Label StringView BindGroupLayoutCount uintptr // size_t BindGroupLayouts uintptr // *WGPUBindGroupLayout + ImmediateSize uint32 // NEW in v29: bytes of immediate data allocated for shaders (requires NativeFeatureImmediates) + _pad [4]byte //nolint:unused // padding for FFI alignment +} + +// NativeLimits extends WGPULimits with wgpu-native specific limits. +// Chain via NextInChain in Limits with SType = STypeNativeLimits. +// v29 BREAKING: maxPushConstantSize renamed to maxImmediateSize; maxNonSamplerBindings and +// maxBindingArrayElementsPerShaderStage added. +type NativeLimits struct { + Chain ChainedStruct // chain.SType must be STypeNativeLimits + MaxImmediateSize uint32 // was maxPushConstantSize in v27 + MaxNonSamplerBindings uint32 // max live non-sampler bindings (DX12 only) + MaxBindingArrayElementsPerShaderStage uint32 // max resources in binding arrays per shader stage +} + +// PipelineLayoutExtras provides wgpu-native specific pipeline layout extensions. +// Chain via NextInChain in PipelineLayoutDescriptor with SType = STypePipelineLayoutExtras. +// v29 BREAKING: pushConstantRangeCount/pushConstantRanges replaced by immediateDataSize. +type PipelineLayoutExtras struct { + Chain ChainedStruct // chain.SType must be STypePipelineLayoutExtras + ImmediateDataSize uint32 // bytes of immediate data for shaders (requires NativeFeatureImmediates) } -// ComputePipelineDescriptor describes a compute pipeline. +// ComputePipelineDescriptor describes a compute pipeline to create. +// Layout is nil for auto layout. type ComputePipelineDescriptor struct { + Label string + Layout *PipelineLayout // nil for auto layout + Module *ShaderModule + EntryPoint string +} + +// computePipelineDescriptorWire is the FFI-compatible C-layout struct for wgpu-native. +// CRITICAL: layout must match WGPUComputePipelineDescriptor exactly. +// nextInChain(8)+label(16)+layout(8)+compute(48) = 80 bytes. +type computePipelineDescriptorWire struct { NextInChain uintptr // *ChainedStruct Label StringView Layout uintptr // WGPUPipelineLayout (nullable) @@ -30,48 +72,54 @@ type ComputePipelineDescriptor struct { } // CreatePipelineLayout creates a pipeline layout. -func (d *Device) CreatePipelineLayout(desc *PipelineLayoutDescriptor) *PipelineLayout { - mustInit() - if d == nil || d.handle == 0 || desc == nil { - return nil +// Returns an error if the FFI call fails or the device/descriptor is nil. +func (d *Device) CreatePipelineLayout(desc *PipelineLayoutDescriptor) (*PipelineLayout, error) { + if err := checkInit(); err != nil { + return nil, err + } + if d == nil || d.handle == 0 { + return nil, &WGPUError{Op: "CreatePipelineLayout", Message: "device is nil or released"} } + if desc == nil { + return nil, &WGPUError{Op: "CreatePipelineLayout", Message: "descriptor is nil"} + } + + // Convert []*BindGroupLayout → []uintptr handles + var layoutsPtr uintptr + var handles []uintptr + if len(desc.BindGroupLayouts) > 0 { + handles = make([]uintptr, len(desc.BindGroupLayouts)) + for i, l := range desc.BindGroupLayouts { + if l != nil { + handles[i] = l.handle + } + } + layoutsPtr = uintptr(unsafe.Pointer(&handles[0])) + } + + wire := pipelineLayoutDescriptorWire{ + Label: stringToStringView(desc.Label), + BindGroupLayoutCount: uintptr(len(desc.BindGroupLayouts)), + BindGroupLayouts: layoutsPtr, + } + handle, _, _ := procDeviceCreatePipelineLayout.Call( d.handle, - uintptr(unsafe.Pointer(desc)), + uintptr(unsafe.Pointer(&wire)), ) if handle == 0 { - return nil + return nil, &WGPUError{Op: "CreatePipelineLayout", Message: "wgpu returned null handle"} } trackResource(handle, "PipelineLayout") - return &PipelineLayout{handle: handle} + return &PipelineLayout{handle: handle}, nil } // CreatePipelineLayoutSimple creates a pipeline layout with the given bind group layouts. -func (d *Device) CreatePipelineLayoutSimple(layouts []*BindGroupLayout) *PipelineLayout { - mustInit() - if d == nil || d.handle == 0 { - return nil - } - if len(layouts) == 0 { - // Create empty pipeline layout - desc := PipelineLayoutDescriptor{ - Label: EmptyStringView(), - BindGroupLayoutCount: 0, - BindGroupLayouts: 0, - } - return d.CreatePipelineLayout(&desc) - } - // Convert to handles - handles := make([]uintptr, len(layouts)) - for i, l := range layouts { - handles[i] = l.handle - } - desc := PipelineLayoutDescriptor{ - Label: EmptyStringView(), - BindGroupLayoutCount: uintptr(len(handles)), - BindGroupLayouts: uintptr(unsafe.Pointer(&handles[0])), - } - return d.CreatePipelineLayout(&desc) +// Returns an error if the FFI call fails or the device is nil. +func (d *Device) CreatePipelineLayoutSimple(layouts []*BindGroupLayout) (*PipelineLayout, error) { + return d.CreatePipelineLayout(&PipelineLayoutDescriptor{ + BindGroupLayouts: layouts, + }) } // Release releases the pipeline layout. @@ -87,44 +135,66 @@ func (pl *PipelineLayout) Release() { func (pl *PipelineLayout) Handle() uintptr { return pl.handle } // CreateComputePipeline creates a compute pipeline. -func (d *Device) CreateComputePipeline(desc *ComputePipelineDescriptor) *ComputePipeline { - mustInit() - if d == nil || d.handle == 0 || desc == nil { - return nil +// Returns an error if the FFI call fails or the device/descriptor is nil. +func (d *Device) CreateComputePipeline(desc *ComputePipelineDescriptor) (*ComputePipeline, error) { + if err := checkInit(); err != nil { + return nil, err + } + if d == nil || d.handle == 0 { + return nil, &WGPUError{Op: "CreateComputePipeline", Message: "device is nil or released"} + } + if desc == nil { + return nil, &WGPUError{Op: "CreateComputePipeline", Message: "descriptor is nil"} + } + if desc.Module == nil { + return nil, &WGPUError{Op: "CreateComputePipeline", Message: "shader module is nil"} + } + + entryPointBytes := []byte(desc.EntryPoint) + + compute := ProgrammableStageDescriptor{ + Module: desc.Module.handle, + } + if len(entryPointBytes) > 0 { + compute.EntryPoint = StringView{ + Data: uintptr(unsafe.Pointer(&entryPointBytes[0])), + Length: uintptr(len(entryPointBytes)), + } + } else { + compute.EntryPoint = EmptyStringView() + } + + var layoutHandle uintptr + if desc.Layout != nil { + layoutHandle = desc.Layout.handle + } + + wire := computePipelineDescriptorWire{ + Label: stringToStringView(desc.Label), + Layout: layoutHandle, + Compute: compute, } + handle, _, _ := procDeviceCreateComputePipeline.Call( d.handle, - uintptr(unsafe.Pointer(desc)), + uintptr(unsafe.Pointer(&wire)), ) if handle == 0 { - return nil + return nil, &WGPUError{Op: "CreateComputePipeline", Message: "wgpu returned null handle"} } trackResource(handle, "ComputePipeline") - return &ComputePipeline{handle: handle} + return &ComputePipeline{handle: handle}, nil } // CreateComputePipelineSimple creates a compute pipeline with the given shader and entry point. // If layout is nil, auto layout is used. -func (d *Device) CreateComputePipelineSimple(layout *PipelineLayout, shader *ShaderModule, entryPoint string) *ComputePipeline { - mustInit() - if d == nil || d.handle == 0 || shader == nil { - return nil - } - entryBytes := []byte(entryPoint) - desc := ComputePipelineDescriptor{ - Label: EmptyStringView(), - Compute: ProgrammableStageDescriptor{ - Module: shader.handle, - EntryPoint: StringView{ - Data: uintptr(unsafe.Pointer(&entryBytes[0])), - Length: uintptr(len(entryBytes)), - }, - }, - } - if layout != nil { - desc.Layout = layout.handle - } - return d.CreateComputePipeline(&desc) +// Returns an error if the FFI call fails or the device/shader is nil. +func (d *Device) CreateComputePipelineSimple(layout *PipelineLayout, shader *ShaderModule, entryPoint string) (*ComputePipeline, error) { + return d.CreateComputePipeline(&ComputePipelineDescriptor{ + Layout: layout, + Module: shader, + EntryPoint: entryPoint, + }) } // GetBindGroupLayout returns the bind group layout at the given index. diff --git a/wgpu/pipeline_test.go b/wgpu/pipeline_test.go index 139cc82..2f86116 100644 --- a/wgpu/pipeline_test.go +++ b/wgpu/pipeline_test.go @@ -40,23 +40,23 @@ func TestCreatePipelineLayout(t *testing.T) { { Binding: 0, Visibility: gputypes.ShaderStageCompute, - Buffer: BufferBindingLayout{ + Buffer: &BufferBindingLayout{ Type: gputypes.BufferBindingTypeStorage, MinBindingSize: 0, }, }, } - bindGroupLayout := device.CreateBindGroupLayoutSimple(layoutEntries) - if bindGroupLayout == nil { - t.Fatal("CreateBindGroupLayoutSimple returned nil") + bindGroupLayout, err := device.CreateBindGroupLayoutSimple(layoutEntries) + if err != nil { + t.Fatalf("CreateBindGroupLayoutSimple failed: %v", err) } defer bindGroupLayout.Release() // Create pipeline layout t.Log("Creating pipeline layout...") - pipelineLayout := device.CreatePipelineLayoutSimple([]*BindGroupLayout{bindGroupLayout}) - if pipelineLayout == nil { - t.Fatal("CreatePipelineLayoutSimple returned nil") + pipelineLayout, err := device.CreatePipelineLayoutSimple([]*BindGroupLayout{bindGroupLayout}) + if err != nil { + t.Fatalf("CreatePipelineLayoutSimple failed: %v", err) } defer pipelineLayout.Release() @@ -87,17 +87,17 @@ func TestCreateComputePipeline(t *testing.T) { defer device.Release() // Create shader - shader := device.CreateShaderModuleWGSL(computeShaderDouble) - if shader == nil { - t.Fatal("CreateShaderModuleWGSL returned nil") + shader, err := device.CreateShaderModuleWGSL(computeShaderDouble) + if err != nil { + t.Fatalf("CreateShaderModuleWGSL failed: %v", err) } defer shader.Release() // Create pipeline with auto layout t.Log("Creating compute pipeline with auto layout...") - pipeline := device.CreateComputePipelineSimple(nil, shader, "main") - if pipeline == nil { - t.Fatal("CreateComputePipelineSimple returned nil") + pipeline, err := device.CreateComputePipelineSimple(nil, shader, "main") + if err != nil { + t.Fatalf("CreateComputePipelineSimple failed: %v", err) } defer pipeline.Release() @@ -128,16 +128,16 @@ func TestComputePipelineGetBindGroupLayout(t *testing.T) { defer device.Release() // Create shader - shader := device.CreateShaderModuleWGSL(computeShaderDouble) - if shader == nil { - t.Fatal("CreateShaderModuleWGSL returned nil") + shader, err := device.CreateShaderModuleWGSL(computeShaderDouble) + if err != nil { + t.Fatalf("CreateShaderModuleWGSL failed: %v", err) } defer shader.Release() // Create pipeline with auto layout - pipeline := device.CreateComputePipelineSimple(nil, shader, "main") - if pipeline == nil { - t.Fatal("CreateComputePipelineSimple returned nil") + pipeline, err := device.CreateComputePipelineSimple(nil, shader, "main") + if err != nil { + t.Fatalf("CreateComputePipelineSimple failed: %v", err) } defer pipeline.Release() @@ -172,9 +172,9 @@ func TestCreateComputePipelineWithExplicitLayout(t *testing.T) { defer device.Release() // Create shader - shader := device.CreateShaderModuleWGSL(computeShaderDouble) - if shader == nil { - t.Fatal("CreateShaderModuleWGSL returned nil") + shader, err := device.CreateShaderModuleWGSL(computeShaderDouble) + if err != nil { + t.Fatalf("CreateShaderModuleWGSL failed: %v", err) } defer shader.Release() @@ -183,30 +183,30 @@ func TestCreateComputePipelineWithExplicitLayout(t *testing.T) { { Binding: 0, Visibility: gputypes.ShaderStageCompute, - Buffer: BufferBindingLayout{ + Buffer: &BufferBindingLayout{ Type: gputypes.BufferBindingTypeStorage, MinBindingSize: 0, }, }, } - bindGroupLayout := device.CreateBindGroupLayoutSimple(layoutEntries) - if bindGroupLayout == nil { - t.Fatal("CreateBindGroupLayoutSimple returned nil") + bindGroupLayout, err := device.CreateBindGroupLayoutSimple(layoutEntries) + if err != nil { + t.Fatalf("CreateBindGroupLayoutSimple failed: %v", err) } defer bindGroupLayout.Release() // Create pipeline layout - pipelineLayout := device.CreatePipelineLayoutSimple([]*BindGroupLayout{bindGroupLayout}) - if pipelineLayout == nil { - t.Fatal("CreatePipelineLayoutSimple returned nil") + pipelineLayout, err := device.CreatePipelineLayoutSimple([]*BindGroupLayout{bindGroupLayout}) + if err != nil { + t.Fatalf("CreatePipelineLayoutSimple failed: %v", err) } defer pipelineLayout.Release() // Create pipeline with explicit layout t.Log("Creating compute pipeline with explicit layout...") - pipeline := device.CreateComputePipelineSimple(pipelineLayout, shader, "main") - if pipeline == nil { - t.Fatal("CreateComputePipelineSimple returned nil") + pipeline, err := device.CreateComputePipelineSimple(pipelineLayout, shader, "main") + if err != nil { + t.Fatalf("CreateComputePipelineSimple failed: %v", err) } defer pipeline.Release() diff --git a/wgpu/queryset.go b/wgpu/queryset.go index 816c98a..8938f94 100644 --- a/wgpu/queryset.go +++ b/wgpu/queryset.go @@ -18,15 +18,20 @@ type QuerySetDescriptor struct { } // CreateQuerySet creates a new QuerySet for GPU profiling/timestamps. -func (d *Device) CreateQuerySet(desc *QuerySetDescriptor) *QuerySet { - mustInit() - if d == nil || d.handle == 0 || desc == nil { - return nil +func (d *Device) CreateQuerySet(desc *QuerySetDescriptor) (*QuerySet, error) { + if err := checkInit(); err != nil { + return nil, err + } + if d == nil || d.handle == 0 { + return nil, &WGPUError{Op: "CreateQuerySet", Message: "device is nil or released"} + } + if desc == nil { + return nil, &WGPUError{Op: "CreateQuerySet", Message: "descriptor is nil"} } nativeDesc := querySetDescriptor{ nextInChain: 0, - label: EmptyStringView(), + label: stringToStringView(desc.Label), queryType: desc.Type, count: desc.Count, } @@ -36,10 +41,10 @@ func (d *Device) CreateQuerySet(desc *QuerySetDescriptor) *QuerySet { uintptr(unsafe.Pointer(&nativeDesc)), ) if handle == 0 { - return nil + return nil, &WGPUError{Op: "CreateQuerySet", Message: "wgpu returned null handle"} } trackResource(handle, "QuerySet") - return &QuerySet{handle: handle} + return &QuerySet{handle: handle}, nil } // Destroy destroys the QuerySet, making it invalid. diff --git a/wgpu/queryset_test.go b/wgpu/queryset_test.go index 7837a7c..91f1347 100644 --- a/wgpu/queryset_test.go +++ b/wgpu/queryset_test.go @@ -33,12 +33,12 @@ func TestCreateQuerySet(t *testing.T) { defer device.Release() t.Log("Creating timestamp query set...") - querySet := device.CreateQuerySet(&QuerySetDescriptor{ + querySet, err := device.CreateQuerySet(&QuerySetDescriptor{ Type: QueryTypeTimestamp, Count: 2, }) - if querySet == nil { - t.Fatal("CreateQuerySet returned nil") + if err != nil { + t.Fatalf("CreateQuerySet failed: %v", err) } defer querySet.Release() @@ -70,12 +70,12 @@ func TestQuerySetDestroy(t *testing.T) { } defer device.Release() - querySet := device.CreateQuerySet(&QuerySetDescriptor{ + querySet, err := device.CreateQuerySet(&QuerySetDescriptor{ Type: QueryTypeTimestamp, Count: 4, }) - if querySet == nil { - t.Fatal("CreateQuerySet returned nil") + if err != nil { + t.Fatalf("CreateQuerySet failed: %v", err) } t.Log("Destroying query set...") @@ -106,32 +106,32 @@ func TestWriteTimestamp(t *testing.T) { } defer device.Release() - queue := device.GetQueue() + queue := device.Queue() defer queue.Release() // Create query set for timestamps - querySet := device.CreateQuerySet(&QuerySetDescriptor{ + querySet, err := device.CreateQuerySet(&QuerySetDescriptor{ Type: QueryTypeTimestamp, Count: 2, }) - if querySet == nil { - t.Fatal("CreateQuerySet returned nil") + if err != nil { + t.Fatalf("CreateQuerySet failed: %v", err) } defer querySet.Release() // Create buffer to resolve query results - resultBuffer := device.CreateBuffer(&BufferDescriptor{ + resultBuffer, err := device.CreateBuffer(&BufferDescriptor{ Usage: gputypes.BufferUsageQueryResolve | gputypes.BufferUsageCopySrc, Size: 16, // 2 timestamps * 8 bytes each }) - if resultBuffer == nil { - t.Fatal("CreateBuffer for query results returned nil") + if err != nil { + t.Fatalf("CreateBuffer for query results failed: %v", err) } defer resultBuffer.Release() - encoder := device.CreateCommandEncoder(nil) - if encoder == nil { - t.Fatal("CreateCommandEncoder returned nil") + encoder, err := device.CreateCommandEncoder(nil) + if err != nil { + t.Fatalf("CreateCommandEncoder failed: %v", err) } t.Log("Writing timestamps...") @@ -141,7 +141,10 @@ func TestWriteTimestamp(t *testing.T) { t.Log("Resolving query set...") encoder.ResolveQuerySet(querySet, 0, 2, resultBuffer, 0) - cmdBuffer := encoder.Finish(nil) + cmdBuffer, err := encoder.Finish(nil) + if err != nil { + t.Fatalf("Finish failed: %v", err) + } encoder.Release() queue.Submit(cmdBuffer) diff --git a/wgpu/render.go b/wgpu/render.go index cd5dc4c..ad95bb8 100644 --- a/wgpu/render.go +++ b/wgpu/render.go @@ -78,21 +78,28 @@ type renderPassDepthStencilAttachment struct { stencilReadOnly Bool } -// renderPassTimestampWrites is the native structure for timestamp writes (24 bytes). -type renderPassTimestampWrites struct { +// passTimestampWrites is the native structure for v29 WGPUPassTimestampWrites. +// v29: nextInChain added as FIRST field; unifies former RenderPassTimestampWrites and ComputePassTimestampWrites. +// Layout: nextInChain(8) + querySet(8) + beginningOfPassWriteIndex(4) + endOfPassWriteIndex(4) = 24 bytes. +type passTimestampWrites struct { + nextInChain uintptr // 8 bytes (NEW in v29) querySet uintptr // 8 bytes (WGPUQuerySet) beginningOfPassWriteIndex uint32 // 4 bytes endOfPassWriteIndex uint32 // 4 bytes - _pad [8]byte // 8 bytes padding for alignment } -// RenderPassTimestampWrites describes timestamp writes for a render pass. -type RenderPassTimestampWrites struct { +// PassTimestampWrites describes timestamp writes for a render or compute pass. +// v29: Unified struct — replaces separate RenderPassTimestampWrites and ComputePassTimestampWrites. +type PassTimestampWrites struct { QuerySet *QuerySet BeginningOfPassWriteIndex uint32 // Use TimestampLocationUndefined to disable EndOfPassWriteIndex uint32 // Use TimestampLocationUndefined to disable } +// RenderPassTimestampWrites is a deprecated alias for PassTimestampWrites. +// Deprecated: Use PassTimestampWrites. Renamed in wgpu-native v29. +type RenderPassTimestampWrites = PassTimestampWrites + // RenderPassDescriptor describes a render pass. type RenderPassDescriptor struct { Label string @@ -102,10 +109,19 @@ type RenderPassDescriptor struct { } // BeginRenderPass begins a render pass. -func (enc *CommandEncoder) BeginRenderPass(desc *RenderPassDescriptor) *RenderPassEncoder { - mustInit() - if enc == nil || enc.handle == 0 || desc == nil || len(desc.ColorAttachments) == 0 { - return nil +// Returns an error if the FFI call fails, encoder is nil, or desc has no color attachments. +func (enc *CommandEncoder) BeginRenderPass(desc *RenderPassDescriptor) (*RenderPassEncoder, error) { + if err := checkInit(); err != nil { + return nil, err + } + if enc == nil || enc.handle == 0 { + return nil, &WGPUError{Op: "BeginRenderPass", Message: "encoder is nil or released"} + } + if desc == nil { + return nil, &WGPUError{Op: "BeginRenderPass", Message: "descriptor is nil"} + } + if len(desc.ColorAttachments) == 0 { + return nil, &WGPUError{Op: "BeginRenderPass", Message: "no color attachments"} } // Build native color attachments @@ -126,8 +142,8 @@ func (enc *CommandEncoder) BeginRenderPass(desc *RenderPassDescriptor) *RenderPa view: viewHandle, depthSlice: DepthSliceUndefined, // CRITICAL for 2D textures! resolveTarget: resolveHandle, - loadOp: toWGPULoadOp(ca.LoadOp), - storeOp: toWGPUStoreOp(ca.StoreOp), + loadOp: uint32(ca.LoadOp), + storeOp: uint32(ca.StoreOp), clearValue: ca.ClearValue, } } @@ -147,23 +163,24 @@ func (enc *CommandEncoder) BeginRenderPass(desc *RenderPassDescriptor) *RenderPa nativeDepthStencil = renderPassDepthStencilAttachment{ view: desc.DepthStencilAttachment.View.handle, - depthLoadOp: toWGPULoadOp(desc.DepthStencilAttachment.DepthLoadOp), - depthStoreOp: toWGPUStoreOp(desc.DepthStencilAttachment.DepthStoreOp), + depthLoadOp: uint32(desc.DepthStencilAttachment.DepthLoadOp), + depthStoreOp: uint32(desc.DepthStencilAttachment.DepthStoreOp), depthClearValue: desc.DepthStencilAttachment.DepthClearValue, depthReadOnly: depthRO, - stencilLoadOp: toWGPULoadOp(desc.DepthStencilAttachment.StencilLoadOp), - stencilStoreOp: toWGPUStoreOp(desc.DepthStencilAttachment.StencilStoreOp), + stencilLoadOp: uint32(desc.DepthStencilAttachment.StencilLoadOp), + stencilStoreOp: uint32(desc.DepthStencilAttachment.StencilStoreOp), stencilClearValue: desc.DepthStencilAttachment.StencilClearValue, stencilReadOnly: stencilRO, } depthStencilPtr = uintptr(unsafe.Pointer(&nativeDepthStencil)) } - // Build timestamp writes if present + // Build timestamp writes if present (v29: passTimestampWrites with nextInChain) var timestampWritesPtr uintptr - var nativeTimestampWrites renderPassTimestampWrites + var nativeTimestampWrites passTimestampWrites if desc.TimestampWrites != nil { - nativeTimestampWrites = renderPassTimestampWrites{ + nativeTimestampWrites = passTimestampWrites{ + nextInChain: 0, querySet: desc.TimestampWrites.QuerySet.handle, beginningOfPassWriteIndex: desc.TimestampWrites.BeginningOfPassWriteIndex, endOfPassWriteIndex: desc.TimestampWrites.EndOfPassWriteIndex, @@ -173,7 +190,7 @@ func (enc *CommandEncoder) BeginRenderPass(desc *RenderPassDescriptor) *RenderPa nativeDesc := renderPassDescriptor{ nextInChain: 0, - label: EmptyStringView(), + label: stringToStringView(desc.Label), colorAttachmentCount: uintptr(len(nativeColorAttachments)), colorAttachments: uintptr(unsafe.Pointer(&nativeColorAttachments[0])), depthStencilAttachment: depthStencilPtr, @@ -186,10 +203,10 @@ func (enc *CommandEncoder) BeginRenderPass(desc *RenderPassDescriptor) *RenderPa uintptr(unsafe.Pointer(&nativeDesc)), ) if handle == 0 { - return nil + return nil, &WGPUError{Op: "BeginRenderPass", Message: "wgpu returned null handle"} } trackResource(handle, "RenderPassEncoder") - return &RenderPassEncoder{handle: handle} + return &RenderPassEncoder{handle: handle}, nil } // SetPipeline sets the render pipeline for this pass. diff --git a/wgpu/render_bundle.go b/wgpu/render_bundle.go index a282bd7..ba7cd1a 100644 --- a/wgpu/render_bundle.go +++ b/wgpu/render_bundle.go @@ -6,15 +6,14 @@ import ( "github.com/gogpu/gputypes" ) -// RenderBundleEncoderDescriptor describes a render bundle encoder. +// RenderBundleEncoderDescriptor describes a render bundle encoder to create. type RenderBundleEncoderDescriptor struct { - Label StringView - ColorFormatCount uintptr // size_t - ColorFormats *gputypes.TextureFormat + Label string + ColorFormats []gputypes.TextureFormat DepthStencilFormat gputypes.TextureFormat SampleCount uint32 - DepthReadOnly Bool - StencilReadOnly Bool + DepthReadOnly bool + StencilReadOnly bool } // RenderBundleDescriptor describes a render bundle. @@ -23,69 +22,72 @@ type RenderBundleDescriptor struct { Label StringView } +// renderBundleEncoderDescriptorWire is the FFI-compatible C-layout struct. +// nextInChain(8)+label(16)+colorFormatCount(8)+colorFormats(8)+ +// depthStencilFormat(4)+sampleCount(4)+depthReadOnly(4)+stencilReadOnly(4) = 56 bytes. +type renderBundleEncoderDescriptorWire struct { + nextInChain uintptr + label StringView + colorFormatCount uintptr + colorFormats uintptr // *uint32 (converted from gputypes.TextureFormat) + depthStencilFormat uint32 + sampleCount uint32 + depthReadOnly Bool + stencilReadOnly Bool +} + // CreateRenderBundleEncoder creates a render bundle encoder for pre-recording render commands. // Render bundles allow you to pre-record a sequence of render commands that can be replayed // multiple times, which is useful for static geometry. -func (d *Device) CreateRenderBundleEncoder(desc *RenderBundleEncoderDescriptor) *RenderBundleEncoder { - mustInit() - if d == nil || d.handle == 0 || desc == nil { - return nil +func (d *Device) CreateRenderBundleEncoder(desc *RenderBundleEncoderDescriptor) (*RenderBundleEncoder, error) { + if err := checkInit(); err != nil { + return nil, err } - - // Build the native descriptor with converted format values - type nativeDesc struct { - nextInChain uintptr - label StringView - colorFormatCount uintptr - colorFormats uintptr - depthStencilFormat uint32 // converted from gputypes - sampleCount uint32 - depthReadOnly Bool - stencilReadOnly Bool + if d == nil || d.handle == 0 { + return nil, &WGPUError{Op: "CreateRenderBundleEncoder", Message: "device is nil or released"} + } + if desc == nil { + return nil, &WGPUError{Op: "CreateRenderBundleEncoder", Message: "descriptor is nil"} } - nd := nativeDesc{ - label: desc.Label, - colorFormatCount: desc.ColorFormatCount, - depthStencilFormat: toWGPUTextureFormat(desc.DepthStencilFormat), + wire := renderBundleEncoderDescriptorWire{ + label: stringToStringView(desc.Label), + colorFormatCount: uintptr(len(desc.ColorFormats)), + depthStencilFormat: uint32(desc.DepthStencilFormat), sampleCount: desc.SampleCount, - depthReadOnly: desc.DepthReadOnly, - stencilReadOnly: desc.StencilReadOnly, + depthReadOnly: boolToWGPU(desc.DepthReadOnly), + stencilReadOnly: boolToWGPU(desc.StencilReadOnly), } - // Convert color formats to wgpu-native values + // Convert color formats to uint32 (gputypes v0.3.0 values equal wgpu-native v29 values) var convertedFormats []uint32 - if desc.ColorFormats != nil && desc.ColorFormatCount > 0 { - formats := unsafe.Slice(desc.ColorFormats, desc.ColorFormatCount) - convertedFormats = make([]uint32, desc.ColorFormatCount) - for i, f := range formats { - convertedFormats[i] = toWGPUTextureFormat(f) + if len(desc.ColorFormats) > 0 { + convertedFormats = make([]uint32, len(desc.ColorFormats)) + for i, f := range desc.ColorFormats { + convertedFormats[i] = uint32(f) } - nd.colorFormats = uintptr(unsafe.Pointer(&convertedFormats[0])) + wire.colorFormats = uintptr(unsafe.Pointer(&convertedFormats[0])) } handle, _, _ := procDeviceCreateRenderBundleEncoder.Call( d.handle, - uintptr(unsafe.Pointer(&nd)), + uintptr(unsafe.Pointer(&wire)), ) if handle == 0 { - return nil + return nil, &WGPUError{Op: "CreateRenderBundleEncoder", Message: "wgpu returned null handle"} } trackResource(handle, "RenderBundleEncoder") - return &RenderBundleEncoder{handle: handle} + return &RenderBundleEncoder{handle: handle}, nil } // CreateRenderBundleEncoderSimple creates a render bundle encoder with common settings. func (d *Device) CreateRenderBundleEncoderSimple(colorFormats []gputypes.TextureFormat, depthFormat gputypes.TextureFormat, sampleCount uint32) *RenderBundleEncoder { - desc := &RenderBundleEncoderDescriptor{ - ColorFormatCount: uintptr(len(colorFormats)), + enc, _ := d.CreateRenderBundleEncoder(&RenderBundleEncoderDescriptor{ + ColorFormats: colorFormats, DepthStencilFormat: depthFormat, SampleCount: sampleCount, - } - if len(colorFormats) > 0 { - desc.ColorFormats = &colorFormats[0] - } - return d.CreateRenderBundleEncoder(desc) + }) + return enc } // SetPipeline sets the render pipeline for subsequent draw calls. @@ -204,15 +206,16 @@ func (rbe *RenderBundleEncoder) DrawIndexedIndirect(indirectBuffer *Buffer, indi } // Finish completes recording and returns the render bundle. -func (rbe *RenderBundleEncoder) Finish(desc *RenderBundleDescriptor) *RenderBundle { +// The optional desc parameter allows specifying a label; if omitted, nil is used. +func (rbe *RenderBundleEncoder) Finish(desc ...*RenderBundleDescriptor) *RenderBundle { mustInit() if rbe == nil || rbe.handle == 0 { return nil } var descPtr uintptr - if desc != nil { - descPtr = uintptr(unsafe.Pointer(desc)) + if len(desc) > 0 && desc[0] != nil { + descPtr = uintptr(unsafe.Pointer(desc[0])) } handle, _, _ := procRenderBundleEncoderFinish.Call(rbe.handle, descPtr) diff --git a/wgpu/render_bundle_test.go b/wgpu/render_bundle_test.go index 3eb73a7..5ce6f04 100644 --- a/wgpu/render_bundle_test.go +++ b/wgpu/render_bundle_test.go @@ -116,21 +116,21 @@ fn fs_main() -> @location(0) vec4 { return vec4(1.0, 0.0, 0.0, 1.0); } ` - shader := device.CreateShaderModuleWGSL(shaderCode) - if shader == nil { - t.Fatal("CreateShaderModuleWGSL returned nil") + shader, err := device.CreateShaderModuleWGSL(shaderCode) + if err != nil { + t.Fatalf("CreateShaderModuleWGSL: %v", err) } defer shader.Release() // Create pipeline - pipeline := device.CreateRenderPipelineSimple( + pipeline, err := device.CreateRenderPipelineSimple( nil, shader, "vs_main", shader, "fs_main", gputypes.TextureFormatBGRA8Unorm, ) - if pipeline == nil { - t.Fatal("CreateRenderPipelineSimple returned nil") + if err != nil { + t.Fatalf("CreateRenderPipelineSimple: %v", err) } defer pipeline.Release() @@ -180,13 +180,13 @@ func TestRenderBundleWithVertexBuffer(t *testing.T) { 0.5, -0.5, } bufferSize := uint64(len(vertices) * 4) - vertexBuffer := device.CreateBuffer(&BufferDescriptor{ + vertexBuffer, err := device.CreateBuffer(&BufferDescriptor{ Usage: gputypes.BufferUsageVertex | gputypes.BufferUsageCopyDst, Size: bufferSize, - MappedAtCreation: True, + MappedAtCreation: true, }) - if vertexBuffer == nil { - t.Fatal("CreateBuffer returned nil") + if err != nil { + t.Fatalf("CreateBuffer: %v", err) } defer vertexBuffer.Release() @@ -214,20 +214,20 @@ fn fs_main() -> @location(0) vec4 { return vec4(1.0, 0.0, 0.0, 1.0); } ` - shader := device.CreateShaderModuleWGSL(shaderCode) - if shader == nil { - t.Fatal("CreateShaderModuleWGSL returned nil") + shader, err := device.CreateShaderModuleWGSL(shaderCode) + if err != nil { + t.Fatalf("CreateShaderModuleWGSL: %v", err) } defer shader.Release() - pipeline := device.CreateRenderPipelineSimple( + pipeline, err := device.CreateRenderPipelineSimple( nil, shader, "vs_main", shader, "fs_main", gputypes.TextureFormatBGRA8Unorm, ) - if pipeline == nil { - t.Fatal("CreateRenderPipelineSimple returned nil") + if err != nil { + t.Fatalf("CreateRenderPipelineSimple: %v", err) } defer pipeline.Release() diff --git a/wgpu/render_pipeline.go b/wgpu/render_pipeline.go index ac7302d..5b3bded 100644 --- a/wgpu/render_pipeline.go +++ b/wgpu/render_pipeline.go @@ -34,8 +34,10 @@ type VertexBufferLayout struct { } // vertexBufferLayoutWire is the FFI-compatible structure with converted StepMode. -// Field order matches webgpu.h: stepMode, arrayStride, attributeCount, attributes +// v29 BREAKING: nextInChain added as FIRST field in WGPUVertexBufferLayout. +// Field order matches webgpu.h v29: nextInChain, stepMode, arrayStride, attributeCount, attributes. type vertexBufferLayoutWire struct { + NextInChain uintptr // NEW in v29 (must be 0 unless chaining) StepMode uint32 // converted from gputypes.VertexStepMode _pad [4]byte // padding to align arrayStride to 8 bytes ArrayStride uint64 @@ -206,10 +208,16 @@ type RenderPipelineDescriptor struct { } // CreateRenderPipeline creates a render pipeline. -func (d *Device) CreateRenderPipeline(desc *RenderPipelineDescriptor) *RenderPipeline { - mustInit() - if d == nil || d.handle == 0 || desc == nil { - return nil +// Returns an error if the FFI call fails or the device/descriptor is nil. +func (d *Device) CreateRenderPipeline(desc *RenderPipelineDescriptor) (*RenderPipeline, error) { + if err := checkInit(); err != nil { + return nil, err + } + if d == nil || d.handle == 0 { + return nil, &WGPUError{Op: "CreateRenderPipeline", Message: "device is nil or released"} + } + if desc == nil { + return nil, &WGPUError{Op: "CreateRenderPipeline", Message: "descriptor is nil"} } // Build vertex state @@ -257,6 +265,7 @@ func (d *Device) CreateRenderPipeline(desc *RenderPipelineDescriptor) *RenderPip attrsPtr = uintptr(unsafe.Pointer(&allNativeAttrs[i][0])) } nativeBuffers[i] = vertexBufferLayoutWire{ + NextInChain: 0, // v29: required first field StepMode: toWGPUVertexStepMode(buf.StepMode), ArrayStride: buf.ArrayStride, AttributeCount: buf.AttributeCount, @@ -308,7 +317,7 @@ func (d *Device) CreateRenderPipeline(desc *RenderPipelineDescriptor) *RenderPip nativeDepthStencil = depthStencilStateWire{ nextInChain: 0, - format: toWGPUTextureFormat(desc.DepthStencil.Format), + format: uint32(desc.DepthStencil.Format), depthWriteEnabled: depthWriteOpt, depthCompare: desc.DepthStencil.DepthCompare, stencilFront: desc.DepthStencil.StencilFront, @@ -353,17 +362,14 @@ func (d *Device) CreateRenderPipeline(desc *RenderPipelineDescriptor) *RenderPip // Build color targets with wire format (uint64 writeMask!) nativeTargets = make([]colorTargetStateWire, len(desc.Fragment.Targets)) for i, target := range desc.Fragment.Targets { - convertedFormat := toWGPUTextureFormat(target.Format) nativeTargets[i] = colorTargetStateWire{ nextInChain: 0, - format: convertedFormat, + format: uint32(target.Format), writeMask: uint64(target.WriteMask), // widen to uint64 } if target.Blend != nil { nativeTargets[i].blend = uintptr(unsafe.Pointer(target.Blend)) } - // DEBUG: print the target bytes - _ = convertedFormat // silence unused warning } if len(nativeTargets) > 0 { @@ -396,14 +402,15 @@ func (d *Device) CreateRenderPipeline(desc *RenderPipelineDescriptor) *RenderPip uintptr(unsafe.Pointer(&nativeDesc)), ) if handle == 0 { - return nil + return nil, &WGPUError{Op: "CreateRenderPipeline", Message: "wgpu returned null handle"} } trackResource(handle, "RenderPipeline") - return &RenderPipeline{handle: handle} + return &RenderPipeline{handle: handle}, nil } // CreateRenderPipelineSimple creates a simple render pipeline with common defaults. +// Returns an error if the FFI call fails or the device/shaders are nil. func (d *Device) CreateRenderPipelineSimple( layout *PipelineLayout, vertexShader *ShaderModule, @@ -411,7 +418,7 @@ func (d *Device) CreateRenderPipelineSimple( fragmentShader *ShaderModule, fragmentEntryPoint string, targetFormat gputypes.TextureFormat, -) *RenderPipeline { +) (*RenderPipeline, error) { return d.CreateRenderPipeline(&RenderPipelineDescriptor{ Layout: layout, Vertex: VertexState{ diff --git a/wgpu/render_pipeline_test.go b/wgpu/render_pipeline_test.go index 2357dba..7f44563 100644 --- a/wgpu/render_pipeline_test.go +++ b/wgpu/render_pipeline_test.go @@ -41,21 +41,21 @@ fn fs_main() -> @location(0) vec4 { return vec4(1.0, 0.0, 0.0, 1.0); } ` - shader := device.CreateShaderModuleWGSL(shaderCode) - if shader == nil { - t.Fatal("CreateShaderModuleWGSL returned nil") + shader, err := device.CreateShaderModuleWGSL(shaderCode) + if err != nil { + t.Fatalf("CreateShaderModuleWGSL: %v", err) } defer shader.Release() t.Log("Creating simple render pipeline...") - pipeline := device.CreateRenderPipelineSimple( + pipeline, err := device.CreateRenderPipelineSimple( nil, shader, "vs_main", shader, "fs_main", gputypes.TextureFormatBGRA8Unorm, ) - if pipeline == nil { - t.Fatal("CreateRenderPipelineSimple returned nil") + if err != nil { + t.Fatalf("CreateRenderPipelineSimple: %v", err) } defer pipeline.Release() @@ -101,14 +101,14 @@ fn fs_main() -> @location(0) vec4 { return vec4(1.0, 0.0, 0.0, 1.0); } ` - shader := device.CreateShaderModuleWGSL(shaderCode) - if shader == nil { - t.Fatal("CreateShaderModuleWGSL returned nil") + shader, err := device.CreateShaderModuleWGSL(shaderCode) + if err != nil { + t.Fatalf("CreateShaderModuleWGSL: %v", err) } defer shader.Release() t.Log("Creating render pipeline with descriptor...") - pipeline := device.CreateRenderPipeline(&RenderPipelineDescriptor{ + pipeline, err := device.CreateRenderPipeline(&RenderPipelineDescriptor{ Vertex: VertexState{ Module: shader, EntryPoint: "vs_main", @@ -131,8 +131,8 @@ fn fs_main() -> @location(0) vec4 { Mask: 0xFFFFFFFF, }, }) - if pipeline == nil { - t.Fatal("CreateRenderPipeline returned nil") + if err != nil { + t.Fatalf("CreateRenderPipeline: %v", err) } defer pipeline.Release() @@ -181,20 +181,20 @@ fn fs_main() -> @location(0) vec4 { return uniforms.color; } ` - shader := device.CreateShaderModuleWGSL(shaderCode) - if shader == nil { - t.Fatal("CreateShaderModuleWGSL returned nil") + shader, err := device.CreateShaderModuleWGSL(shaderCode) + if err != nil { + t.Fatalf("CreateShaderModuleWGSL: %v", err) } defer shader.Release() - pipeline := device.CreateRenderPipelineSimple( + pipeline, err := device.CreateRenderPipelineSimple( nil, shader, "vs_main", shader, "fs_main", gputypes.TextureFormatBGRA8Unorm, ) - if pipeline == nil { - t.Fatal("CreateRenderPipelineSimple returned nil") + if err != nil { + t.Fatalf("CreateRenderPipelineSimple: %v", err) } defer pipeline.Release() @@ -247,14 +247,14 @@ fn fs_main() -> @location(0) vec4 { return vec4(1.0, 0.0, 0.0, 1.0); } ` - shader := device.CreateShaderModuleWGSL(shaderCode) - if shader == nil { - t.Fatal("CreateShaderModuleWGSL returned nil") + shader, err := device.CreateShaderModuleWGSL(shaderCode) + if err != nil { + t.Fatalf("CreateShaderModuleWGSL: %v", err) } defer shader.Release() t.Log("Creating render pipeline with depth testing...") - pipeline := device.CreateRenderPipeline(&RenderPipelineDescriptor{ + pipeline, err := device.CreateRenderPipeline(&RenderPipelineDescriptor{ Vertex: VertexState{ Module: shader, EntryPoint: "vs_main", @@ -280,8 +280,8 @@ fn fs_main() -> @location(0) vec4 { Mask: 0xFFFFFFFF, }, }) - if pipeline == nil { - t.Fatal("CreateRenderPipeline with depth returned nil") + if err != nil { + t.Fatalf("CreateRenderPipeline with depth: %v", err) } defer pipeline.Release() diff --git a/wgpu/sampler.go b/wgpu/sampler.go index e19404f..16177af 100644 --- a/wgpu/sampler.go +++ b/wgpu/sampler.go @@ -8,49 +8,90 @@ import ( // SamplerDescriptor describes a sampler to create. type SamplerDescriptor struct { - NextInChain uintptr - Label StringView - AddressModeU gputypes.AddressMode - AddressModeV gputypes.AddressMode - AddressModeW gputypes.AddressMode - MagFilter gputypes.FilterMode - MinFilter gputypes.FilterMode - MipmapFilter gputypes.MipmapFilterMode - LodMinClamp float32 - LodMaxClamp float32 - Compare gputypes.CompareFunction - MaxAnisotropy uint16 - _pad [2]byte + Label string + AddressModeU gputypes.AddressMode + AddressModeV gputypes.AddressMode + AddressModeW gputypes.AddressMode + MagFilter gputypes.FilterMode + MinFilter gputypes.FilterMode + MipmapFilter gputypes.MipmapFilterMode + LodMinClamp float32 + LodMaxClamp float32 + Compare gputypes.CompareFunction + // Anisotropy is the maximum anisotropy level for anisotropic filtering. + // wgpu-native requires a value >= 1; 0 is automatically clamped to 1. + Anisotropy uint16 +} + +// MaxAnisotropy is deprecated. Use Anisotropy instead. + +// samplerDescriptorWire is the FFI-compatible C-layout struct for wgpu-native. +// CRITICAL: layout must match WGPUSamplerDescriptor exactly. +// nextInChain(8)+label(16)+addressModeU(4)+addressModeV(4)+addressModeW(4)+ +// magFilter(4)+minFilter(4)+mipmapFilter(4)+lodMinClamp(4)+lodMaxClamp(4)+ +// compare(4)+maxAnisotropy(2)+pad(2) = 64 bytes. +type samplerDescriptorWire struct { + NextInChain uintptr // 8 bytes + Label StringView // 16 bytes + AddressModeU gputypes.AddressMode // 4 bytes + AddressModeV gputypes.AddressMode // 4 bytes + AddressModeW gputypes.AddressMode // 4 bytes + MagFilter gputypes.FilterMode // 4 bytes + MinFilter gputypes.FilterMode // 4 bytes + MipmapFilter gputypes.MipmapFilterMode // 4 bytes + LodMinClamp float32 // 4 bytes + LodMaxClamp float32 // 4 bytes + Compare gputypes.CompareFunction // 4 bytes + MaxAnisotropy uint16 // 2 bytes + _pad [2]byte //nolint:unused // padding to align to 4 bytes } // CreateSampler creates a sampler with the specified descriptor. -func (d *Device) CreateSampler(desc *SamplerDescriptor) *Sampler { - mustInit() - if d == nil || d.handle == 0 || desc == nil { - return nil +func (d *Device) CreateSampler(desc *SamplerDescriptor) (*Sampler, error) { + if err := checkInit(); err != nil { + return nil, err + } + if d == nil || d.handle == 0 { + return nil, &WGPUError{Op: "CreateSampler", Message: "device is nil or released"} + } + if desc == nil { + return nil, &WGPUError{Op: "CreateSampler", Message: "descriptor is nil"} } - // wgpu-native requires MaxAnisotropy >= 1 - descCopy := *desc - if descCopy.MaxAnisotropy == 0 { - descCopy.MaxAnisotropy = 1 + // wgpu-native requires Anisotropy >= 1 + anisotropy := desc.Anisotropy + if anisotropy == 0 { + anisotropy = 1 + } + + wire := samplerDescriptorWire{ + Label: stringToStringView(desc.Label), + AddressModeU: desc.AddressModeU, + AddressModeV: desc.AddressModeV, + AddressModeW: desc.AddressModeW, + MagFilter: desc.MagFilter, + MinFilter: desc.MinFilter, + MipmapFilter: desc.MipmapFilter, + LodMinClamp: desc.LodMinClamp, + LodMaxClamp: desc.LodMaxClamp, + Compare: desc.Compare, + MaxAnisotropy: anisotropy, } handle, _, _ := procDeviceCreateSampler.Call( d.handle, - uintptr(unsafe.Pointer(&descCopy)), + uintptr(unsafe.Pointer(&wire)), ) if handle == 0 { - return nil + return nil, &WGPUError{Op: "CreateSampler", Message: "wgpu returned null handle"} } trackResource(handle, "Sampler") - return &Sampler{handle: handle} + return &Sampler{handle: handle}, nil } // CreateLinearSampler creates a sampler with linear filtering. -func (d *Device) CreateLinearSampler() *Sampler { - desc := SamplerDescriptor{ - Label: EmptyStringView(), +func (d *Device) CreateLinearSampler() (*Sampler, error) { + return d.CreateSampler(&SamplerDescriptor{ AddressModeU: gputypes.AddressModeClampToEdge, AddressModeV: gputypes.AddressModeClampToEdge, AddressModeW: gputypes.AddressModeClampToEdge, @@ -59,14 +100,12 @@ func (d *Device) CreateLinearSampler() *Sampler { MipmapFilter: gputypes.MipmapFilterModeLinear, LodMinClamp: 0.0, LodMaxClamp: 32.0, - } - return d.CreateSampler(&desc) + }) } // CreateNearestSampler creates a sampler with nearest filtering. -func (d *Device) CreateNearestSampler() *Sampler { - desc := SamplerDescriptor{ - Label: EmptyStringView(), +func (d *Device) CreateNearestSampler() (*Sampler, error) { + return d.CreateSampler(&SamplerDescriptor{ AddressModeU: gputypes.AddressModeClampToEdge, AddressModeV: gputypes.AddressModeClampToEdge, AddressModeW: gputypes.AddressModeClampToEdge, @@ -75,8 +114,7 @@ func (d *Device) CreateNearestSampler() *Sampler { MipmapFilter: gputypes.MipmapFilterModeNearest, LodMinClamp: 0.0, LodMaxClamp: 1.0, - } - return d.CreateSampler(&desc) + }) } // Release releases the sampler reference. diff --git a/wgpu/shader.go b/wgpu/shader.go index e491696..988fda4 100644 --- a/wgpu/shader.go +++ b/wgpu/shader.go @@ -17,10 +17,16 @@ type ShaderSourceWGSL struct { } // CreateShaderModuleWGSL creates a shader module from WGSL source code. -func (d *Device) CreateShaderModuleWGSL(code string) *ShaderModule { - mustInit() +// Returns an error if the FFI call fails or the device is nil. +func (d *Device) CreateShaderModuleWGSL(code string) (*ShaderModule, error) { + if err := checkInit(); err != nil { + return nil, err + } if d == nil || d.handle == 0 { - return nil + return nil, &WGPUError{Op: "CreateShaderModuleWGSL", Message: "device is nil or released"} + } + if code == "" { + return nil, &WGPUError{Op: "CreateShaderModuleWGSL", Message: "shader source is empty"} } // Create WGSL source with embedded string data @@ -47,28 +53,110 @@ func (d *Device) CreateShaderModuleWGSL(code string) *ShaderModule { uintptr(unsafe.Pointer(&desc)), ) if handle == 0 { - return nil + return nil, &WGPUError{Op: "CreateShaderModuleWGSL", Message: "wgpu returned null handle"} } trackResource(handle, "ShaderModule") - return &ShaderModule{handle: handle} + return &ShaderModule{handle: handle}, nil } // CreateShaderModule creates a shader module from a descriptor. -// For WGSL shaders, use CreateShaderModuleWGSL instead. -func (d *Device) CreateShaderModule(desc *ShaderModuleDescriptor) *ShaderModule { - mustInit() - if d == nil || d.handle == 0 || desc == nil { - return nil +// For WGSL shaders, prefer CreateShaderModuleWGSL or use ShaderDescriptor. +// Returns an error if the FFI call fails or the device/descriptor is nil. +func (d *Device) CreateShaderModule(desc *ShaderModuleDescriptor) (*ShaderModule, error) { + if err := checkInit(); err != nil { + return nil, err + } + if d == nil || d.handle == 0 { + return nil, &WGPUError{Op: "CreateShaderModule", Message: "device is nil or released"} + } + if desc == nil { + return nil, &WGPUError{Op: "CreateShaderModule", Message: "descriptor is nil"} } handle, _, _ := procDeviceCreateShaderModule.Call( d.handle, uintptr(unsafe.Pointer(desc)), ) if handle == 0 { - return nil + return nil, &WGPUError{Op: "CreateShaderModule", Message: "wgpu returned null handle"} + } + trackResource(handle, "ShaderModule") + return &ShaderModule{handle: handle}, nil +} + +// CreateShaderModuleFromDescriptor creates a shader module from a Go-idiomatic ShaderDescriptor. +// If both WGSL and SPIRV are set, WGSL takes precedence. +// Returns an error if the FFI call fails, the device is nil, or both sources are empty. +// +// Deprecated: use CreateShaderModule which has the same signature. +func (d *Device) CreateShaderModuleFromDescriptor(desc *ShaderDescriptor) (*ShaderModule, error) { + return d.createShaderModuleFromDesc(desc) +} + +// CreateShaderModule creates a shader module from a Go-idiomatic ShaderDescriptor. +// This is the preferred method name matching the gogpu/wgpu API. +// If both WGSL and SPIRV are set, WGSL takes precedence. +// Returns an error if the FFI call fails, the device is nil, or both sources are empty. +// +// Note: there is also a lower-level CreateShaderModule(*ShaderModuleDescriptor) that accepts +// the wire-level descriptor with a chained struct for direct FFI use. +func (d *Device) CreateShaderModuleFromDesc(desc *ShaderDescriptor) (*ShaderModule, error) { + return d.createShaderModuleFromDesc(desc) +} + +func (d *Device) createShaderModuleFromDesc(desc *ShaderDescriptor) (*ShaderModule, error) { + if desc == nil { + return nil, &WGPUError{Op: "CreateShaderModule", Message: "descriptor is nil"} + } + if desc.WGSL != "" { + return d.CreateShaderModuleWGSL(desc.WGSL) + } + if len(desc.SPIRV) > 0 { + return d.CreateShaderModuleSPIRV(desc.Label, desc.SPIRV) + } + return nil, &WGPUError{Op: "CreateShaderModule", Message: "shader source is empty (set WGSL or SPIRV)"} +} + +// CreateShaderModuleSPIRV creates a shader module from SPIR-V bytecode. +// Returns an error if the FFI call fails, the device is nil, or the bytecode is empty. +func (d *Device) CreateShaderModuleSPIRV(label string, spirv []uint32) (*ShaderModule, error) { + if err := checkInit(); err != nil { + return nil, err + } + if d == nil || d.handle == 0 { + return nil, &WGPUError{Op: "CreateShaderModuleSPIRV", Message: "device is nil or released"} + } + if len(spirv) == 0 { + return nil, &WGPUError{Op: "CreateShaderModuleSPIRV", Message: "SPIR-V bytecode is empty"} + } + + spirvSource := struct { + Chain ChainedStruct + Code uintptr // *uint32 + CodeSize uint32 + _pad [4]byte + }{ + Chain: ChainedStruct{ + Next: 0, + SType: uint32(STypeShaderSourceSPIRV), + }, + Code: uintptr(unsafe.Pointer(&spirv[0])), + CodeSize: uint32(len(spirv)), + } + + desc := ShaderModuleDescriptor{ + NextInChain: uintptr(unsafe.Pointer(&spirvSource)), + Label: stringToStringView(label), + } + + handle, _, _ := procDeviceCreateShaderModule.Call( + d.handle, + uintptr(unsafe.Pointer(&desc)), + ) + if handle == 0 { + return nil, &WGPUError{Op: "CreateShaderModuleSPIRV", Message: "wgpu returned null handle"} } trackResource(handle, "ShaderModule") - return &ShaderModule{handle: handle} + return &ShaderModule{handle: handle}, nil } // Release releases the shader module resources. diff --git a/wgpu/shader_test.go b/wgpu/shader_test.go index 9431efc..9478099 100644 --- a/wgpu/shader_test.go +++ b/wgpu/shader_test.go @@ -34,9 +34,9 @@ func TestCreateShaderModuleWGSL(t *testing.T) { defer device.Release() t.Log("Creating shader module from WGSL...") - shader := device.CreateShaderModuleWGSL(testComputeShader) - if shader == nil { - t.Fatal("CreateShaderModuleWGSL returned nil") + shader, err := device.CreateShaderModuleWGSL(testComputeShader) + if err != nil { + t.Fatalf("CreateShaderModuleWGSL: %v", err) } defer shader.Release() @@ -84,9 +84,9 @@ func TestCreateShaderModuleVertex(t *testing.T) { defer device.Release() t.Log("Creating vertex/fragment shader module...") - shader := device.CreateShaderModuleWGSL(simpleShader) - if shader == nil { - t.Fatal("CreateShaderModuleWGSL returned nil") + shader, err := device.CreateShaderModuleWGSL(simpleShader) + if err != nil { + t.Fatalf("CreateShaderModuleWGSL: %v", err) } defer shader.Release() diff --git a/wgpu/surface.go b/wgpu/surface.go index d92ebba..71e9223 100644 --- a/wgpu/surface.go +++ b/wgpu/surface.go @@ -50,7 +50,11 @@ type surfaceCapabilitiesWire struct { } // SurfaceConfiguration describes how to configure a surface. +// Note: the Device field is deprecated — pass the device as a separate argument to Configure. +// It remains here for backward compatibility; if non-nil it takes precedence over the explicit arg. type SurfaceConfiguration struct { + // Device is deprecated: pass the device to Configure() directly instead. + // Kept for backward compatibility. If non-nil, overrides the explicit device argument. Device *Device Format gputypes.TextureFormat Usage gputypes.TextureUsage @@ -81,23 +85,44 @@ var ( ErrSurfaceNeedsReconfigure = &WGPUError{Op: "Surface.GetCurrentTexture", Message: "surface needs reconfigure"} ErrSurfaceLost = &WGPUError{Op: "Surface.GetCurrentTexture", Message: "surface lost"} ErrSurfaceTimeout = &WGPUError{Op: "Surface.GetCurrentTexture", Message: "surface texture timeout"} - ErrSurfaceOutOfMemory = &WGPUError{Op: "Surface.GetCurrentTexture", Message: "out of memory"} - ErrSurfaceDeviceLost = &WGPUError{Op: "Surface.GetCurrentTexture", Message: "device lost"} + // ErrSurfaceOccluded is returned on macOS Metal when the window is minimized or fully covered. + // Applications should skip rendering for the current frame and try again when unoccluded. + // New in wgpu-native v29. + ErrSurfaceOccluded = &WGPUError{Op: "Surface.GetCurrentTexture", Message: "surface occluded (window minimized or covered)"} + // ErrSurfaceOutOfMemory is kept for backward compatibility. + // Deprecated: In v29 this is reported as generic Error status. + ErrSurfaceOutOfMemory = &WGPUError{Op: "Surface.GetCurrentTexture", Message: "out of memory"} + // ErrSurfaceDeviceLost is kept for backward compatibility. + // Deprecated: In v29 this is reported as generic Error status. + ErrSurfaceDeviceLost = &WGPUError{Op: "Surface.GetCurrentTexture", Message: "device lost"} ) // Configure configures the surface for rendering. +// The device argument specifies which logical device to use for the surface. +// If config.Device is also set (deprecated usage), it takes precedence over the device arg. +// Returns nil on success. Errors are surfaced through the Device uncaptured-error callback +// in this FFI implementation; the error return matches the gogpu/wgpu API signature. // This replaces the deprecated SwapChain API. // Enum values are converted from gputypes to wgpu-native values before FFI call. -func (s *Surface) Configure(config *SurfaceConfiguration) { +func (s *Surface) Configure(device *Device, config *SurfaceConfiguration) error { mustInit() - if s == nil || s.handle == 0 || config == nil || config.Device == nil || config.Device.handle == 0 { - return + if s == nil || s.handle == 0 || config == nil { + return nil + } + + // config.Device takes precedence (backward compat) over the device argument. + dev := device + if config.Device != nil { + dev = config.Device + } + if dev == nil || dev.handle == 0 { + return nil } nativeConfig := surfaceConfigurationWire{ nextInChain: 0, - device: config.Device.handle, - format: toWGPUTextureFormat(config.Format), + device: dev.handle, + format: uint32(config.Format), usage: uint64(config.Usage), width: config.Width, height: config.Height, @@ -111,6 +136,13 @@ func (s *Surface) Configure(config *SurfaceConfiguration) { s.handle, uintptr(unsafe.Pointer(&nativeConfig)), ) + return nil +} + +// ConfigureLegacy configures the surface using only the config struct (legacy API). +// Deprecated: use Configure(device, config) instead. +func (s *Surface) ConfigureLegacy(config *SurfaceConfiguration) { + _ = s.Configure(nil, config) } // Unconfigure removes the surface configuration. @@ -123,13 +155,14 @@ func (s *Surface) Unconfigure() { } // GetCurrentTexture gets the current texture to render to. -// Returns the texture and its status. Check status before using the texture. -func (s *Surface) GetCurrentTexture() (*SurfaceTexture, error) { +// Returns the texture, a suboptimal flag (true if the surface needs reconfiguration +// but is still usable this frame), and any error. This matches the gogpu/wgpu API. +func (s *Surface) GetCurrentTexture() (*SurfaceTexture, bool, error) { if err := checkInit(); err != nil { - return nil, err + return nil, false, err } if s == nil || s.handle == 0 { - return nil, &WGPUError{Op: "Surface.GetCurrentTexture", Message: "surface is nil or released"} + return nil, false, &WGPUError{Op: "Surface.GetCurrentTexture", Message: "surface is nil or released"} } var surfTex surfaceTexture @@ -145,31 +178,39 @@ func (s *Surface) GetCurrentTexture() (*SurfaceTexture, error) { } switch surfTex.status { - case SurfaceGetCurrentTextureStatusSuccessOptimal, - SurfaceGetCurrentTextureStatusSuccessSuboptimal: - return result, nil + case SurfaceGetCurrentTextureStatusSuccessOptimal: + return result, false, nil + case SurfaceGetCurrentTextureStatusSuccessSuboptimal: + // Surface still usable but caller should reconfigure soon. + return result, true, nil case SurfaceGetCurrentTextureStatusOutdated: - return result, ErrSurfaceNeedsReconfigure + return result, false, ErrSurfaceNeedsReconfigure case SurfaceGetCurrentTextureStatusLost: - return nil, ErrSurfaceLost + return nil, false, ErrSurfaceLost case SurfaceGetCurrentTextureStatusTimeout: - return nil, ErrSurfaceTimeout - case SurfaceGetCurrentTextureStatusOutOfMemory: - return nil, ErrSurfaceOutOfMemory - case SurfaceGetCurrentTextureStatusDeviceLost: - return nil, ErrSurfaceDeviceLost + return nil, false, ErrSurfaceTimeout + case NativeSurfaceGetCurrentTextureStatusOccluded: + // wgpu-native v29: window is occluded/minimized (Metal backend only). + // No texture is returned; caller should skip this frame and try again. + return nil, false, ErrSurfaceOccluded default: - return nil, &WGPUError{Op: "Surface.GetCurrentTexture", Message: "failed to get surface texture"} + // v29: SurfaceGetCurrentTextureStatusError (0x06) covers all error cases + // including former OutOfMemory (0x06) and DeviceLost (0x07). + return nil, false, &WGPUError{Op: "Surface.GetCurrentTexture", Message: "failed to get surface texture"} } } // Present presents the current frame to the surface. -func (s *Surface) Present() { +// The texture argument is accepted for API compatibility with gogpu/wgpu but +// is unused in the FFI implementation (wgpuSurfacePresent takes no texture arg). +// Returns nil on success. +func (s *Surface) Present(texture ...*SurfaceTexture) error { mustInit() if s == nil || s.handle == 0 { - return + return nil } procSurfacePresent.Call(s.handle) //nolint:errcheck + return nil } // Release releases the surface. @@ -217,7 +258,7 @@ func (s *Surface) GetCapabilities(adapter *Adapter) (*SurfaceCapabilities, error rawFormats := unsafe.Slice((*uint32)(ptrFromUintptr(wire.formats)), wire.formatCount) caps.Formats = make([]gputypes.TextureFormat, len(rawFormats)) for i, f := range rawFormats { - caps.Formats[i] = fromWGPUTextureFormat(f) + caps.Formats[i] = gputypes.TextureFormat(f) } } diff --git a/wgpu/texture.go b/wgpu/texture.go index 3269bb7..7f3bdf1 100644 --- a/wgpu/texture.go +++ b/wgpu/texture.go @@ -8,16 +8,14 @@ import ( // TextureDescriptor describes a texture to create. type TextureDescriptor struct { - NextInChain uintptr - Label StringView - Usage gputypes.TextureUsage - Dimension gputypes.TextureDimension - Size gputypes.Extent3D - Format gputypes.TextureFormat - MipLevelCount uint32 - SampleCount uint32 - ViewFormatCount uintptr - ViewFormats uintptr + Label string + Usage gputypes.TextureUsage + Dimension gputypes.TextureDimension + Size gputypes.Extent3D + Format gputypes.TextureFormat + MipLevelCount uint32 + SampleCount uint32 + ViewFormats []gputypes.TextureFormat } // textureDescriptorWire is the FFI-compatible struct with wgpu-native enum values. @@ -37,8 +35,7 @@ type textureDescriptorWire struct { // TextureViewDescriptor describes a texture view to create. type TextureViewDescriptor struct { - NextInChain uintptr - Label StringView + Label string Format gputypes.TextureFormat Dimension gputypes.TextureViewDimension BaseMipLevel uint32 @@ -46,7 +43,6 @@ type TextureViewDescriptor struct { BaseArrayLayer uint32 ArrayLayerCount uint32 Aspect TextureAspect - _pad [4]byte //nolint:unused // padding for FFI alignment Usage gputypes.TextureUsage } @@ -69,20 +65,22 @@ type textureViewDescriptorWire struct { // CreateView creates a view into this texture. // Pass nil for default view parameters. // Enum values are converted from gputypes to wgpu-native values before FFI call. -func (t *Texture) CreateView(desc *TextureViewDescriptor) *TextureView { - mustInit() +// Returns an error if the FFI call fails or the texture is nil. +func (t *Texture) CreateView(desc *TextureViewDescriptor) (*TextureView, error) { + if err := checkInit(); err != nil { + return nil, err + } if t == nil || t.handle == 0 { - return nil + return nil, &WGPUError{Op: "CreateView", Message: "texture is nil or released"} } var descPtr uintptr if desc != nil { - // Convert to wire format with wgpu-native enum values + // Convert Go-idiomatic descriptor to FFI wire format wireDesc := textureViewDescriptorWire{ - NextInChain: desc.NextInChain, - Label: desc.Label, - Format: toWGPUTextureFormat(desc.Format), - Dimension: toWGPUTextureViewDimension(desc.Dimension), + Label: stringToStringView(desc.Label), + Format: uint32(desc.Format), + Dimension: uint32(desc.Dimension), BaseMipLevel: desc.BaseMipLevel, MipLevelCount: desc.MipLevelCount, BaseArrayLayer: desc.BaseArrayLayer, @@ -98,10 +96,10 @@ func (t *Texture) CreateView(desc *TextureViewDescriptor) *TextureView { descPtr, ) if handle == 0 { - return nil + return nil, &WGPUError{Op: "CreateView", Message: "wgpu returned null handle"} } trackResource(handle, "TextureView") - return &TextureView{handle: handle} + return &TextureView{handle: handle}, nil } // Destroy destroys the texture. @@ -124,8 +122,8 @@ func (t *Texture) Release() { // Handle returns the underlying handle. For advanced use only. func (t *Texture) Handle() uintptr { return t.handle } -// GetWidth returns the width of the texture in texels. -func (t *Texture) GetWidth() uint32 { +// Width returns the width of the texture in texels. +func (t *Texture) Width() uint32 { mustInit() if t == nil || t.handle == 0 { return 0 @@ -134,8 +132,8 @@ func (t *Texture) GetWidth() uint32 { return uint32(result) } -// GetHeight returns the height of the texture in texels. -func (t *Texture) GetHeight() uint32 { +// Height returns the height of the texture in texels. +func (t *Texture) Height() uint32 { mustInit() if t == nil || t.handle == 0 { return 0 @@ -144,8 +142,8 @@ func (t *Texture) GetHeight() uint32 { return uint32(result) } -// GetDepthOrArrayLayers returns the depth (for 3D textures) or array layer count. -func (t *Texture) GetDepthOrArrayLayers() uint32 { +// DepthOrArrayLayers returns the depth (for 3D textures) or array layer count. +func (t *Texture) DepthOrArrayLayers() uint32 { mustInit() if t == nil || t.handle == 0 { return 0 @@ -154,8 +152,8 @@ func (t *Texture) GetDepthOrArrayLayers() uint32 { return uint32(result) } -// GetMipLevelCount returns the number of mip levels. -func (t *Texture) GetMipLevelCount() uint32 { +// MipLevelCount returns the number of mip levels. +func (t *Texture) MipLevelCount() uint32 { mustInit() if t == nil || t.handle == 0 { return 0 @@ -164,16 +162,15 @@ func (t *Texture) GetMipLevelCount() uint32 { return uint32(result) } -// GetFormat returns the texture format. -// The format is converted from wgpu-native enum to gputypes enum. -func (t *Texture) GetFormat() gputypes.TextureFormat { +// Format returns the texture format. +// TextureFormat values match between gputypes v0.3.0 and wgpu-native v29 exactly. +func (t *Texture) Format() gputypes.TextureFormat { mustInit() if t == nil || t.handle == 0 { return gputypes.TextureFormatUndefined } result, _, _ := procTextureGetFormat.Call(t.handle) - // Convert from wgpu-native enum to gputypes - return fromWGPUTextureFormat(uint32(result)) + return gputypes.TextureFormat(result) } // Release releases the texture view reference. @@ -190,10 +187,16 @@ func (tv *TextureView) Handle() uintptr { return tv.handle } // CreateTexture creates a texture with the specified descriptor. // Enum values are converted from gputypes to wgpu-native values before FFI call. -func (d *Device) CreateTexture(desc *TextureDescriptor) *Texture { - mustInit() - if d == nil || d.handle == 0 || desc == nil { - return nil +// Returns an error if the FFI call fails or the device/descriptor is nil. +func (d *Device) CreateTexture(desc *TextureDescriptor) (*Texture, error) { + if err := checkInit(); err != nil { + return nil, err + } + if d == nil || d.handle == 0 { + return nil, &WGPUError{Op: "CreateTexture", Message: "device is nil or released"} + } + if desc == nil { + return nil, &WGPUError{Op: "CreateTexture", Message: "descriptor is nil"} } // wgpu-native requires MipLevelCount >= 1 and SampleCount >= 1 @@ -206,18 +209,30 @@ func (d *Device) CreateTexture(desc *TextureDescriptor) *Texture { sampleCount = 1 } + // Convert []TextureFormat → []uint32 for FFI (values match, but wire struct needs uint32 pointer) + var viewFormatCount uintptr + var viewFormatsPtr uintptr + if len(desc.ViewFormats) > 0 { + // Convert to uint32 slice (gputypes values equal wgpu-native values) + wireFormats := make([]uint32, len(desc.ViewFormats)) + for i, f := range desc.ViewFormats { + wireFormats[i] = uint32(f) + } + viewFormatCount = uintptr(len(wireFormats)) + viewFormatsPtr = uintptr(unsafe.Pointer(&wireFormats[0])) + } + // Convert to wire format with wgpu-native enum values wireDesc := textureDescriptorWire{ - NextInChain: desc.NextInChain, - Label: desc.Label, + Label: stringToStringView(desc.Label), Usage: uint64(desc.Usage), // bitflags, uint64 in wgpu-native - Dimension: toWGPUTextureDimension(desc.Dimension), + Dimension: uint32(desc.Dimension), Size: desc.Size, - Format: toWGPUTextureFormat(desc.Format), + Format: uint32(desc.Format), MipLevelCount: mipLevelCount, SampleCount: sampleCount, - ViewFormatCount: desc.ViewFormatCount, - ViewFormats: desc.ViewFormats, + ViewFormatCount: viewFormatCount, + ViewFormats: viewFormatsPtr, } handle, _, _ := procDeviceCreateTexture.Call( @@ -225,13 +240,14 @@ func (d *Device) CreateTexture(desc *TextureDescriptor) *Texture { uintptr(unsafe.Pointer(&wireDesc)), ) if handle == 0 { - return nil + return nil, &WGPUError{Op: "CreateTexture", Message: "wgpu returned null handle"} } trackResource(handle, "Texture") - return &Texture{handle: handle} + return &Texture{handle: handle}, nil } -// TexelCopyTextureInfo describes a texture for WriteTexture. +// TexelCopyTextureInfo describes a texture for WriteTexture (low-level wire type). +// Prefer [ImageCopyTexture] for new code — it holds a *Texture handle. type TexelCopyTextureInfo struct { Texture uintptr MipLevel uint32 @@ -239,7 +255,8 @@ type TexelCopyTextureInfo struct { Aspect TextureAspect } -// TexelCopyBufferLayout describes buffer layout for WriteTexture. +// TexelCopyBufferLayout describes buffer layout for WriteTexture (low-level wire type). +// Prefer [ImageDataLayout] for new code. type TexelCopyBufferLayout struct { Offset uint64 BytesPerRow uint32 @@ -252,11 +269,74 @@ type TexelCopyBufferInfo struct { Buffer uintptr // Buffer handle } +// ImageCopyTexture describes a texture subresource and origin for copy/write operations. +// Matches gogpu/wgpu ImageCopyTexture. +type ImageCopyTexture struct { + Texture *Texture + MipLevel uint32 + Origin gputypes.Origin3D + Aspect TextureAspect +} + +// toWire converts to the FFI wire format (TexelCopyTextureInfo). +func (i *ImageCopyTexture) toWire() TexelCopyTextureInfo { + if i == nil { + return TexelCopyTextureInfo{} + } + var handle uintptr + if i.Texture != nil { + handle = i.Texture.handle + } + return TexelCopyTextureInfo{ + Texture: handle, + MipLevel: i.MipLevel, + Origin: i.Origin, + Aspect: i.Aspect, + } +} + +// ImageDataLayout describes the layout of image data in a buffer. +// Matches gogpu/wgpu ImageDataLayout. +type ImageDataLayout struct { + Offset uint64 + BytesPerRow uint32 + RowsPerImage uint32 +} + // WriteTexture writes data to a texture. -func (q *Queue) WriteTexture(dest *TexelCopyTextureInfo, data []byte, layout *TexelCopyBufferLayout, size *gputypes.Extent3D) { +// Returns nil on success. In this FFI implementation errors are surfaced through +// the Device uncaptured-error callback; the signature matches gogpu/wgpu for API compatibility. +// +// Accepts either [ImageCopyTexture] or [TexelCopyTextureInfo] as dest (via overloads below). +// This overload takes the high-level [ImageCopyTexture] type. +func (q *Queue) WriteTexture(dest *ImageCopyTexture, data []byte, layout *ImageDataLayout, size *gputypes.Extent3D) error { + mustInit() + if q == nil || q.handle == 0 || dest == nil || layout == nil || size == nil || len(data) == 0 { + return nil + } + wire := dest.toWire() + wireLayout := TexelCopyBufferLayout{ + Offset: layout.Offset, + BytesPerRow: layout.BytesPerRow, + RowsPerImage: layout.RowsPerImage, + } + procQueueWriteTexture.Call( //nolint:errcheck + q.handle, + uintptr(unsafe.Pointer(&wire)), + uintptr(unsafe.Pointer(&data[0])), + uintptr(len(data)), + uintptr(unsafe.Pointer(&wireLayout)), + uintptr(unsafe.Pointer(size)), + ) + return nil +} + +// WriteTextureRaw writes data to a texture using the low-level wire types. +// Prefer [WriteTexture] for new code. +func (q *Queue) WriteTextureRaw(dest *TexelCopyTextureInfo, data []byte, layout *TexelCopyBufferLayout, size *gputypes.Extent3D) error { mustInit() if q == nil || q.handle == 0 || dest == nil || layout == nil || size == nil || len(data) == 0 { - return + return nil } procQueueWriteTexture.Call( //nolint:errcheck q.handle, @@ -266,4 +346,27 @@ func (q *Queue) WriteTexture(dest *TexelCopyTextureInfo, data []byte, layout *Te uintptr(unsafe.Pointer(layout)), uintptr(unsafe.Pointer(size)), ) + return nil +} + +// BufferTextureCopy defines a buffer-texture copy region. +// Matches gogpu/wgpu BufferTextureCopy. +type BufferTextureCopy struct { + // BufferLayout describes the memory layout of the buffer data. + BufferLayout ImageDataLayout + // TextureBase describes the texture subresource and origin. + TextureBase ImageCopyTexture + // Size is the extent of the copy operation. + Size gputypes.Extent3D +} + +// TextureCopy describes a texture-to-texture copy region. +// Matches gogpu/wgpu TextureCopy. +type TextureCopy struct { + // Source describes the source texture subresource and origin. + Source ImageCopyTexture + // Destination describes the destination texture subresource and origin. + Destination ImageCopyTexture + // Size is the extent of the copy operation. + Size gputypes.Extent3D } diff --git a/wgpu/texture_test.go b/wgpu/texture_test.go index a03b365..e0dbed7 100644 --- a/wgpu/texture_test.go +++ b/wgpu/texture_test.go @@ -26,7 +26,7 @@ func TestCreateTexture(t *testing.T) { defer device.Release() t.Log("Creating 2D texture...") - texture := device.CreateTexture(&TextureDescriptor{ + texture, err := device.CreateTexture(&TextureDescriptor{ Usage: gputypes.TextureUsageTextureBinding | gputypes.TextureUsageCopyDst, Dimension: gputypes.TextureDimension2D, Size: gputypes.Extent3D{ @@ -38,8 +38,8 @@ func TestCreateTexture(t *testing.T) { MipLevelCount: 1, SampleCount: 1, }) - if texture == nil { - t.Fatal("CreateTexture returned nil") + if err != nil { + t.Fatalf("CreateTexture: %v", err) } defer texture.Release() @@ -69,7 +69,7 @@ func TestCreateTextureView(t *testing.T) { } defer device.Release() - texture := device.CreateTexture(&TextureDescriptor{ + texture, err := device.CreateTexture(&TextureDescriptor{ Usage: gputypes.TextureUsageTextureBinding, Dimension: gputypes.TextureDimension2D, Size: gputypes.Extent3D{ @@ -81,15 +81,15 @@ func TestCreateTextureView(t *testing.T) { MipLevelCount: 1, SampleCount: 1, }) - if texture == nil { - t.Fatal("CreateTexture returned nil") + if err != nil { + t.Fatalf("CreateTexture: %v", err) } defer texture.Release() t.Log("Creating texture view...") - view := texture.CreateView(nil) - if view == nil { - t.Fatal("CreateView returned nil") + view, err := texture.CreateView(nil) + if err != nil { + t.Fatalf("CreateView: %v", err) } defer view.Release() @@ -153,17 +153,17 @@ func TestCreateSampler(t *testing.T) { defer device.Release() t.Log("Creating sampler...") - sampler := device.CreateSampler(&SamplerDescriptor{ - AddressModeU: gputypes.AddressModeRepeat, - AddressModeV: gputypes.AddressModeRepeat, - AddressModeW: gputypes.AddressModeRepeat, - MagFilter: gputypes.FilterModeLinear, - MinFilter: gputypes.FilterModeLinear, - MipmapFilter: gputypes.MipmapFilterModeLinear, - MaxAnisotropy: 1, + sampler, err := device.CreateSampler(&SamplerDescriptor{ + AddressModeU: gputypes.AddressModeRepeat, + AddressModeV: gputypes.AddressModeRepeat, + AddressModeW: gputypes.AddressModeRepeat, + MagFilter: gputypes.FilterModeLinear, + MinFilter: gputypes.FilterModeLinear, + MipmapFilter: gputypes.MipmapFilterModeLinear, + Anisotropy: 1, }) - if sampler == nil { - t.Fatal("CreateSampler returned nil") + if err != nil { + t.Fatalf("CreateSampler: %v", err) } defer sampler.Release() @@ -194,11 +194,11 @@ func TestCreateSamplerSimple(t *testing.T) { defer device.Release() t.Log("Creating sampler with minimal settings...") - sampler := device.CreateSampler(&SamplerDescriptor{ - MaxAnisotropy: 1, // Required to be >= 1 + sampler, err := device.CreateSampler(&SamplerDescriptor{ + Anisotropy: 1, // Required to be >= 1 }) - if sampler == nil { - t.Fatal("CreateSampler returned nil") + if err != nil { + t.Fatalf("CreateSampler: %v", err) } defer sampler.Release() diff --git a/wgpu/types.go b/wgpu/types.go index 10ffa1a..38a6e0d 100644 --- a/wgpu/types.go +++ b/wgpu/types.go @@ -23,19 +23,28 @@ type Instance struct{ handle uintptr } // Adapter represents a physical GPU and its capabilities. // Obtained via [Instance.RequestAdapter], release with [Adapter.Release]. -type Adapter struct{ handle uintptr } +type Adapter struct { + handle uintptr + limits Limits // cached at request time, returned by Limits() without FFI call +} // Device is the logical connection to a GPU, used to create all other resources. // Obtained via [Adapter.RequestDevice], release with [Device.Release]. -type Device struct{ handle uintptr } +type Device struct { + handle uintptr + limits Limits // cached at request time, returned by Limits() without FFI call +} // Queue is used to submit command buffers and write data to buffers/textures. -// Obtained via [Device.GetQueue], release with [Queue.Release]. +// Obtained via [Device.Queue], release with [Queue.Release]. type Queue struct{ handle uintptr } // Buffer represents a block of GPU-accessible memory. // Create with [Device.CreateBuffer], release with [Buffer.Release]. -type Buffer struct{ handle uintptr } +type Buffer struct { + handle uintptr + device *Device // retained for Map/Poll; set by CreateBuffer +} // Texture represents a GPU texture resource (1D, 2D, or 3D). // Create with [Device.CreateTexture], release with [Texture.Release]. diff --git a/wgpu/wgpu.go b/wgpu/wgpu.go index 0c6c77b..d1d5a0a 100644 --- a/wgpu/wgpu.go +++ b/wgpu/wgpu.go @@ -19,14 +19,15 @@ var ( procInstanceProcessEvents Proc // Function pointers - Adapter - procAdapterRelease Proc - procInstanceRequestAdapter Proc - procAdapterRequestDevice Proc - procAdapterGetLimits Proc - procAdapterEnumerateFeatures Proc - procAdapterHasFeature Proc - procAdapterGetInfo Proc - procAdapterInfoFreeMembers Proc + procAdapterRelease Proc + procInstanceRequestAdapter Proc + procAdapterRequestDevice Proc + procAdapterGetLimits Proc + procAdapterGetFeatures Proc // v29: replaces EnumerateFeatures (single-call with SupportedFeatures) + procSupportedFeaturesFreeMembers Proc + procAdapterHasFeature Proc + procAdapterGetInfo Proc + procAdapterInfoFreeMembers Proc // Function pointers - Device procDeviceRelease Proc @@ -43,15 +44,22 @@ var ( procQueueRelease Proc procQueueWriteBuffer Proc + // Function pointers - Instance (global) + procGetInstanceFeatures Proc // v29: global instance feature query + procGetInstanceLimits Proc // v29: global instance limits query + procHasInstanceFeature Proc // v29: check a single instance feature + // Function pointers - Buffer - procBufferRelease Proc - procBufferDestroy Proc - procBufferGetMappedRange Proc - procBufferUnmap Proc - procBufferGetSize Proc - procBufferMapAsync Proc - procBufferGetUsage Proc - procBufferGetMapState Proc + procBufferRelease Proc + procBufferDestroy Proc + procBufferGetMappedRange Proc + procBufferReadMappedRange Proc // v29: explicit read mapped range + procBufferWriteMappedRange Proc // v29: explicit write mapped range + procBufferUnmap Proc + procBufferGetSize Proc + procBufferMapAsync Proc + procBufferGetUsage Proc + procBufferGetMapState Proc // Function pointers - ShaderModule procDeviceCreateShaderModule Proc @@ -100,7 +108,8 @@ var ( procCommandBufferRelease Proc // Function pointers - Queue (additional) - procQueueSubmit Proc + procQueueSubmit Proc + procQueueSubmitForIndex Proc // wgpu-native extension: returns WGPUSubmissionIndex (uint64) // Function pointers - Surface procInstanceCreateSurface Proc @@ -113,16 +122,19 @@ var ( procSurfacePresent Proc // Function pointers - Texture - procDeviceCreateTexture Proc - procTextureRelease Proc - procTextureDestroy Proc - procTextureCreateView Proc - procTextureViewRelease Proc - procTextureGetWidth Proc - procTextureGetHeight Proc - procTextureGetDepthOrArrayLayers Proc - procTextureGetMipLevelCount Proc - procTextureGetFormat Proc + procDeviceCreateTexture Proc + procTextureRelease Proc + procTextureDestroy Proc + procTextureCreateView Proc + procTextureViewRelease Proc + procTextureGetWidth Proc + procTextureGetHeight Proc + procTextureGetDepthOrArrayLayers Proc + procTextureGetMipLevelCount Proc + procTextureGetFormat Proc + procTextureGetSampleCount Proc // v29: new getter + procTextureGetUsage Proc // v29: new getter + procTextureGetTextureBindingViewDimension Proc // v29: new getter // Function pointers - Sampler procDeviceCreateSampler Proc @@ -228,7 +240,8 @@ func initSymbols() { procInstanceRequestAdapter = wgpuLib.NewProc("wgpuInstanceRequestAdapter") procAdapterRequestDevice = wgpuLib.NewProc("wgpuAdapterRequestDevice") procAdapterGetLimits = wgpuLib.NewProc("wgpuAdapterGetLimits") - procAdapterEnumerateFeatures = wgpuLib.NewProc("wgpuAdapterEnumerateFeatures") + procAdapterGetFeatures = wgpuLib.NewProc("wgpuAdapterGetFeatures") // v29: replaces wgpuAdapterEnumerateFeatures + procSupportedFeaturesFreeMembers = wgpuLib.NewProc("wgpuSupportedFeaturesFreeMembers") procAdapterHasFeature = wgpuLib.NewProc("wgpuAdapterHasFeature") procAdapterGetInfo = wgpuLib.NewProc("wgpuAdapterGetInfo") procAdapterInfoFreeMembers = wgpuLib.NewProc("wgpuAdapterInfoFreeMembers") @@ -248,10 +261,17 @@ func initSymbols() { procQueueRelease = wgpuLib.NewProc("wgpuQueueRelease") procQueueWriteBuffer = wgpuLib.NewProc("wgpuQueueWriteBuffer") + // Instance global queries (v29) + procGetInstanceFeatures = wgpuLib.NewProc("wgpuGetInstanceFeatures") + procGetInstanceLimits = wgpuLib.NewProc("wgpuGetInstanceLimits") + procHasInstanceFeature = wgpuLib.NewProc("wgpuHasInstanceFeature") + // Buffer procBufferRelease = wgpuLib.NewProc("wgpuBufferRelease") procBufferDestroy = wgpuLib.NewProc("wgpuBufferDestroy") procBufferGetMappedRange = wgpuLib.NewProc("wgpuBufferGetMappedRange") + procBufferReadMappedRange = wgpuLib.NewProc("wgpuBufferReadMappedRange") // v29 + procBufferWriteMappedRange = wgpuLib.NewProc("wgpuBufferWriteMappedRange") // v29 procBufferUnmap = wgpuLib.NewProc("wgpuBufferUnmap") procBufferGetSize = wgpuLib.NewProc("wgpuBufferGetSize") procBufferMapAsync = wgpuLib.NewProc("wgpuBufferMapAsync") @@ -306,6 +326,7 @@ func initSymbols() { // Queue (additional) procQueueSubmit = wgpuLib.NewProc("wgpuQueueSubmit") + procQueueSubmitForIndex = wgpuLib.NewProc("wgpuQueueSubmitForIndex") // wgpu-native extension // Surface procInstanceCreateSurface = wgpuLib.NewProc("wgpuInstanceCreateSurface") @@ -328,6 +349,9 @@ func initSymbols() { procTextureGetDepthOrArrayLayers = wgpuLib.NewProc("wgpuTextureGetDepthOrArrayLayers") procTextureGetMipLevelCount = wgpuLib.NewProc("wgpuTextureGetMipLevelCount") procTextureGetFormat = wgpuLib.NewProc("wgpuTextureGetFormat") + procTextureGetSampleCount = wgpuLib.NewProc("wgpuTextureGetSampleCount") // v29 + procTextureGetUsage = wgpuLib.NewProc("wgpuTextureGetUsage") // v29 + procTextureGetTextureBindingViewDimension = wgpuLib.NewProc("wgpuTextureGetTextureBindingViewDimension") // v29 // Sampler procDeviceCreateSampler = wgpuLib.NewProc("wgpuDeviceCreateSampler")