# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [0.30.30] - 2026-07-31 ### Fixed - **Memory leak: validation maps pinned every resource per frame** — `SetBindGroup` copied every bound buffer and texture into encoder maps per draw call (O(bindings × draws)). Validation maps were never cleared after Submit or Release, pinning BindGroup objects indefinitely. Now tracks bind groups only; `validateCommandBufferForSubmit` walks `boundBuffers`/`boundTextures` transitively. `dropUsedSets()` clears maps in both `postSubmit` and `Release()`. Contributor: @samyfodil (#291) - **Validation error precedence: buffer/texture errors beat bind group errors** — Two-pass validation: first walks all bound resources from all bind groups, then checks `bg.released`. Deterministic regardless of map iteration order. Matches Rust wgpu ordering (buffers 1780-1808, bind groups 1815-1817). (#291) ## [0.30.29] - 2026-07-30 ### Fixed - **Queue.LastSubmissionIndex deadlock** — `onZero` callbacks from `Triage()` called `lastSubmissionIndex()` which locked `Queue.mu`, already held by `Submit()`. Changed `lastSubmissionIndex` from `mutex`-protected `uint64` to `atomic.Uint64` — single writer (Submit), lock-free readers (onZero callbacks). ADR-056 deadlock chain: Submit→Triage→onZero→lastSubmissionIndex→mu. ## [0.30.28] - 2026-07-30 ### Fixed - **Resource lifecycle: BindGroup/Pipeline Release() bypasses ref-counting** — `Release()` called `dq.Defer(lastSubmissionIndex)` directly, ignoring the `ResourceRef` ref-counting system. On shared encoder path, this caused use-after-free: HAL resource destroyed while GPU still processing commands. Now `Release()` calls `ref.Drop()` — HAL destruction deferred until ALL refs (user + GPU) are dropped. Matches Rust wgpu `Arc` pattern. Applied to: BindGroup, RenderPipeline, ComputePipeline. (ADR-056, #287) - **DestroyQueue deadlock: Triage → onZero → Defer re-entry** — `Triage()` held mutex while executing callbacks. When `onZero` fired and called `Defer()`, it tried to acquire the same mutex → deadlock. Now callbacks execute outside the lock. Same fix applied to `FlushAll()`. ## [0.30.27] - 2026-07-30 ### Fixed - **GLES: depth/stencil not attached to swapchain FBO on surface render pass** — `setupSurfaceTarget()` had an early return that skipped depth/stencil attachment, causing `GL_INVALID_FRAMEBUFFER_OPERATION` (0x506) on every draw call. Now attaches depth/stencil to the swapchain FBO via `AttachDepthStencilToFBOCommand`. Attachment point chosen by format: `GL_DEPTH_ATTACHMENT` for depth-only, `GL_DEPTH_STENCIL_ATTACHMENT` for combined formats (Rust wgpu-hal parity, command.rs:577-580). Also fixes the same wrong attachment point in the existing `AttachDepthStencilCommand` for offscreen FBOs. 2D overlay now renders correctly on GLES surface targets. (#284) - **GLES: MappedAtCreation buffer data silently discarded on Unmap** — `UnmapBuffer` only flushed shadow data to GL when `BufferUsageMapWrite` was set. Per WebGPU spec, `MappedAtCreation` does NOT require `MapWrite` usage. Buffers created with `Uniform|CopyDst` + `MappedAtCreation` (the standard g3d pattern) had their data thrown away, leaving GL buffers zero-filled — zero MVP matrices, zero vertices, zero indices. Root cause of invisible 3D geometry on GLES. (#284) - **GLES: 3D geometry invisible due to stale depth mask** — `ClearDepthCommand` did not call `glDepthMask(true)` before `glClear(GL_DEPTH_BUFFER_BIT)`. If a prior pipeline set `DepthWriteEnabled=false`, the depth clear was silently masked on subsequent frames, causing all 3D geometry to fail the depth test. Also adds `glClearDepth(value)` before clear (was relying on GL default 1.0). Rust ref: queue.rs:1199-1205. (#284) - **GLES: viewport depth range ignored** — `SetViewportCommand` called `glViewport` but not `glDepthRange(minDepth, maxDepth)`. Depth range fields were stored but never passed to GL. Rust ref: queue.rs:1295-1296. (#284) ### Added - `gl.Context.ClearDepth()` and `gl.Context.DepthRange()` — wrapper methods for both Windows (syscall, double) and Linux (goffi, float32 for GLES). Function pointers were loaded via `getProcAddr` but had no callable methods. ### Changed - **deps:** gpucontext v0.22.0 → v0.23.0 ## [0.30.25] - 2026-07-29 ### Changed - **deps:** gpucontext v0.21.1 → v0.22.0 — `FontSmoothing` type + coordinate space docs (gogpu#396, gogpu#398) ## [0.30.24] - 2026-07-28 ### Fixed - **Browser/Rust Surface missing methods** — `PresentPixels`, `WritePixels`, `SetPrepareFrame`, and `SetPresentsWithTransaction` now exist on all backends. Missing methods return clear errors or no-op, matching ADR-047 (unified public API). Fixes WASM build failure introduced by gogpu PR #370. (#281) - **Metal checkptr abort under `-race`** — ObjC block callback trampolines used `unsafe.Pointer(blockPtr + 32)` which violates Go's pointer provenance rules. Replaced with `unsafe.Add(unsafe.Pointer(blockPtr), 32)` (Go 1.17+) at all 4 block callback locations. `go test -race` now works on Metal compute. (#280) ### Added - **Compile-time Surface API contract** — anonymous interface assertion (`var _ interface{...} = (*Surface)(nil)`) in each backend file enforces that all public methods exist at compile time (ADR-047 enforcement). - **CI cross-compile job** — `GOOS=js GOARCH=wasm` (browser) and `-tags=rust` (Rust FFI) builds added to CI pipeline. Missing Surface methods now fail the PR, not the downstream consumer. ## [0.30.23] - 2026-07-26 ### Fixed - **Device teardown GPU drain ordering** — `Device.Release()` now drains GPU work via internal `waitIdle()` before destroying staging buffers and encoders. Previously, the public `WaitIdle()` returned `ErrReleased` immediately due to the released flag, leaving in-flight submissions unreferenced. Matches Rust wgpu `Queue::Drop` ordering. Contributor: @besmpl (#264). - **Vulkan swapchain fail-closed lifecycle** — surface capabilities are fully validated before committing to state changes, semaphore/fence errors propagate instead of being silently ignored, and swapchain reconfiguration is transactional (old swapchain survives until replacement is ready). Adds `broken` flag to prevent reuse after synchronization failures. Contributor: @besmpl (#265). - **Explicit mock adapter construction** — `core.NewInstance` no longer fabricates a mock adapter when no HAL backend yields adapters. Registration failures are now observable. Tests that need a deterministic adapter opt in via `NewInstanceWithMock`. Matches Rust wgpu behavior. Contributor: @besmpl (#266). - **Surface lifetime ownership** — centralized acquisition/teardown with opaque lease system that invalidates retained texture wrappers on present/discard/ unconfigure/destruction. Instance owns deterministic release ordering (devices before surfaces before native instance). Vulkan HAL gains device-level swapchain tracking with orderly and device-loss abandon paths. Contributor: @besmpl (#269). ### Added - **Surface-qualified adapter selection** — `RequestAdapterWithSurface` validates adapters against the target surface's presentation queue via `vkGetPhysicalDeviceSurfaceSupportKHR`. Creates request-local adapter wrappers that carry the proven queue family into `Open()`, keeping cached adapters immutable. Iterates all queue families — ahead of Rust wgpu which hardcodes `queue_family_index = 0`. Contributor: @besmpl (#267). - **Typed surface targets** — add retained-provider `CreateSurfaceFromTarget` and explicit raw-handle `CreateSurfaceUnsafe` paths modeled on Rust `wgpu` v29. The original two-`uintptr` method remains a compatibility adapter across native, Rust, and browser implementations. The native path creates one surface per successful enabled backend and qualifies adapters against their matching backend surface, as Rust `wgpu` does. - **Android raw surface target** — route `ANativeWindow*` explicitly through public, HAL, Vulkan, and Rust-tag surface creation without cgo or Activity/JNI policy in WGPU. - **Headless software surface readback (non-standard)** — add the zero-sized `HeadlessSurfaceTarget` and root `Surface.ReadPixels()` lifecycle. The Pure-Go software backend now returns owned, tightly packed RGBA8 snapshots after present/discard for both RGBA8 and BGRA8 configurations. Other backends fail explicitly through the optional `hal.PixelReader` capability rather than widening the mandatory HAL surface interface. (#256) ### Changed - **Counted indirect draws** — added `RenderPassEncoder.MultiDrawIndirect` and `MultiDrawIndexedIndirect` for consecutive 16-byte and 20-byte argument records. Existing two-argument `DrawIndirect` and `DrawIndexedIndirect` remain single-draw APIs. The Vulkan `FeatureMultiDrawIndirect` capability is used only as a performance hint; Vulkan multi-draw calls fall back to exact loops when unavailable or over the device limit. GLES indirect drawing remains unsupported as before. `FeatureMultiDrawIndirectCount` remains reserved for future GPU-driven count buffers. External HAL adapters must add the `drawCount` parameter to both indirect draw methods. - **CONTRIBUTING.md** — Smart Coding framework (AI-assisted policy), updated project structure, pre-submit checklist with cross-platform lint. ### Changed - **Bump naga** v0.17.15 → v0.17.16 — MSL 64-bit atomics rejection (#82, @besmpl) - **Bump goffi** v0.6.1 → v0.6.2 — Windows AMD64 float returns fix (#65, @besmpl) - **Bump webgpu** v0.5.3 → v0.5.4 — Android surface, Queue.GetTimestampPeriod, callback strings (#23-#25, @besmpl) - **DX12 lint cleanup** — `copy` param renamed to `region` (builtin shadow), `placedSlice` struct replaces 6 named returns, dead code removed, redundant `//nolint` directives removed ## [0.30.22] - 2026-07-16 ### Fixed - **Metal MSAA storage mode crash on Intel Mac** — use `MTLGPUFamilyApple1` detection instead of `hasUnifiedMemory` for texture storage mode selection. Non-Apple GPU family devices (Intel/AMD) now always use `MTLStorageModePrivate` for textures. Apple Silicon keeps `MTLStorageModeShared` optimization for single-sample textures. Fixes #271. ## [0.30.21] - 2026-07-15 ### Fixed - **Software `CopyTextureToBuffer` row stride** — row-by-row copy respecting `BytesPerRow` from `BufferLayout`. Was flat `copy(dst, src)` ignoring row alignment, causing progressive row shift (128 bytes/row at 800px width) that produced horizontal dashed-line artifacts in GPU texture readback. - **Software `configureRasterPipeline` blend state** — extract blend state from `Fragment.Targets[0].Blend` into the raster pipeline. Was always `BlendDisabled`, preventing premultiplied alpha compositing for textured quads. Added `convertBlendState`, `convertBlendFactor`, `convertBlendOp` conversion functions. - **Software `readTexel` BGRA format** — swap R/B channels when sampling textures with `BGRA8Unorm` or `BGRA8UnormSrgb` format. Was always reading as RGBA, causing red/blue channel swap for offscreen BGRA textures. ## [0.30.20] - 2026-07-14 ### Fixed - **PresentPixels check-before-mutate** — move `hal.PixelPresenter` type assertion before acquired texture discard. Failed `PresentPixels` on GPU backends no longer corrupts surface state. Validate-then-mutate pattern matches Rust wgpu. ### Added - **DX12 UMA GPU classification** — `CheckFeatureSupport(D3D12_FEATURE_ARCHITECTURE)` replaces DedicatedVideoMemory heuristic for integrated/discrete detection. Matches Rust wgpu pattern exactly. Contributor: @Zeroes1 (#254). - **DX12 `CacheCoherentUMA`** — stored for future memory pool optimization (`D3D12_MEMORY_POOL_L0` vs `L1`). - **DX12 architecture diagnostic logging** — `hal.Logger().Info("dx12: adapter architecture", ...)` ## [0.30.19] - 2026-07-12 ### Changed - **Bump `go-webgpu/webgpu`** v0.5.2 → v0.5.3 (Rust FFI backend) ## [0.30.18] - 2026-07-12 ### Fixed - **Vulkan crash: C array params in vk-gen** — `vkCmdSetBlendConstants(const float[4])` was generated with scalar float CIF (`SigVoidHandleF32`) instead of pointer CIF (`SigVoidHandlePtr`). C array function parameters decay to pointers in C ABI. Generator now detects `[` in vk.xml RawXML and classifies as `ptr` with double-pointer arg pattern (ADR-044). Also fixes `CmdSetFragmentShadingRateKHR` and `CmdSetFragmentShadingRateEnumNV` (`combinerOps[2]`). - **vk-gen deterministic output** — extension enum map keys sorted before iteration. Go map order is random; without sorting, `const_gen.go` changed on every regeneration (1780+ line noise diffs). Now fully idempotent. ## [0.30.17] - 2026-07-12 ### Changed - **Bump `goffi`** v0.5.6 → v0.6.0 — `CallFunction` now returns `(syscall.Errno, error)`. Migrated all 764 FFI call sites (17 files) to three-tier error handling per ADR-049: Tier 1 (creation/submit) checks FFI error + API result; Tier 2 (void/hot path) infallible by GPU API contract; Tier 3 (platform syscalls) captures errno for diagnostics. Enterprise reference: Rust wgpu-hal `()` for all draw/destroy/barrier commands. ## [0.30.16] - 2026-07-12 ### Changed - **Bump `golang.org/x/sys`** v0.46.0 → v0.47.0 - **Bump `gpucontext`** v0.21.0 → v0.21.1 ## [0.30.15] - 2026-07-11 ### Fixed - **Software: remove debug rejected logs from hot path** — `executeFullscreenBlit` per-attempt debug logs (blitStateValid, no bound texture) incorrectly included in v0.30.14 merge. Removed. ## [0.30.14] - 2026-07-11 ### Fixed - **Software: WriteTexture SurfaceTexture bug** (BUG-SW-010) — `Queue.WriteTexture` silently dropped writes to `*SurfaceTexture` due to Go type assertion failure (`*SurfaceTexture` embeds `Texture` by value, not pointer). Fix: `resolveTexture()` type switch handles both `*Texture` and `*SurfaceTexture`. - **Software: Tier 1 blit false-positive** — tightened fullscreen quad detection to require bound texture dimensions match render target. Prevents incorrect blit when 2-triangle quad has mismatched texture. - **Software: removed debug logging from hot path** — `executeFullscreenBlit` rejection logs removed (per-attempt overhead). Path-tracing logs (per-frame) retained. ### Extensions (non-standard) - **`Surface.PresentPixels(data, width, height, damageRects)`** — software-backend-only atomic pixel write + present. Single-pass RGBA→BGRA swizzle into DIB section framebuffer + BitBlt to window. Bypasses entire WebGPU render pass pipeline (WriteTexture → render pass → blit). 3 copies → 1 copy per frame. Not part of WebGPU specification. Returns error on GPU backends. Enterprise references: SDL3 `UpdateWindowSurface`, Qt6 `QBackingStore::flush`, rio/softbuffer `buffer.present()`. - **`Surface.WritePixels(data, width, height)`** — write RGBA pixels into surface framebuffer without presenting. Software-backend-only extension. - **`hal.PixelPresenter`** / **`hal.PixelWriter`** — optional HAL capability interfaces (`io.WriterTo` pattern alongside `hal.Surface`). ### Architecture - 3-layer `PresentPixels`: public API (`surface_native.go`) → core state machine validation (`core/surface.go`) → HAL implementation (`hal/software/resource.go`). Bypasses Acquire state — surface stays in Configured. - ADR: `ADR-SOFTWARE-ZERO-COPY-PRESENTATION.md` — 10 research agents, SDL3/Qt6/Skia/rio enterprise reference analysis. ## [0.30.13] - 2026-07-10 ### Fixed - **Software: Tier 1 fullscreen quad blit detection** (#241) — when vertex buffers are bound and the resulting 2 triangles form a fullscreen axis-aligned quad covering the render target, bypass SPIR-V fragment shader entirely and use direct memcpy/swizzle. Restores 50+ FPS from 18 FPS for textured quad render passes on software backend. Detection: `isFullscreenQuad()` (bounding box check, 1.5px tolerance) + `blitStateValid()` (blend, depth/stencil, write mask, multisampling). ## [0.30.12] - 2026-07-10 ### Fixed - **`SurfaceTexture.CreateView()` sets parent texture** — all 3 build targets (native, Rust FFI, browser) now pass parent texture to `TextureView` when created from `SurfaceTexture.CreateView()`. Without this, `view.Texture()` returned nil for surface texture views. Found during v0.30.11 validation. ## [0.30.11] - 2026-07-09 ### Added - **`TextureView.Texture()`** — returns parent Texture (Rust wgpu parity: `TextureView::texture()`). All 3 build targets (native, Rust FFI, browser). Unblocks gg BUG-SW-002 offscreen texture upload. - **`SurfaceTexture.AsTexture()`** — lightweight Texture wrapper for direct `Queue.WriteTexture()` to surface textures. Enables spec-compliant CPU pixmap display without render pass. Configure surface with `TextureUsageCopyDst`. ### Fixed - **Software: hardened `executeFullscreenBlit`** — added Skia-inspired state validation (`blitStateValid`): checks blend mode, depth/stencil, write mask, multisampling before allowing memcpy fast path. Previously blit bypassed these checks, potentially producing incorrect results with non-trivial render state. ## [0.30.10] - 2026-07-08 ### Fixed - **Metal: IOSurface retain leak** — `AcquireTexture` retained both the drawable and MTLTexture, but nothing released the texture retain. Every presented frame leaked one IOSurface pin, accumulating 4+ GB during resize-heavy sessions. Fix: `releaseAcquired()` called from `SurfaceTexture.Destroy()` and `DiscardTexture`. - **Metal: nextDrawable nil spiral** — with default `allowsNextDrawableTimeout=true`, `nextDrawable` returned nil under pool pressure, triggering surface reconfiguration (positive-feedback leak loop). Fix: `allowsNextDrawableTimeout=false` (matches Rust wgpu-hal and rio terminal). - **Metal: CA stretch artifact** — `kCAGravityResize` (default) scaled the last rendered frame during live resize. Fix: `contentsGravity=topLeft`. - **Metal: drawable stockpiling** — `presentsWithTransaction=true` during live drag synchronizes drawable swap with CA transaction, preventing old drawable accumulation. - **Metal: test crash** — removed incorrect `defer Release(desc)` on +0 autoreleased `MTLRenderPassDescriptor`. ### Added - **Metal: UMA direct writes** — `MTLStorageModeShared` on Apple Silicon; `WriteTexture` uses `replaceRegion:` for zero-copy CPU→GPU upload (bypasses staging buffers). - **Metal: `setPurgeableState(empty)`** in `DestroyTexture` for immediate page reclaim. - **Metal: `frameSemaphore`** (cap=2) — CPU-side throttle for in-flight frames. - **Metal: `maximumDrawableCount=3`** — matches Rust wgpu and rio terminal. - `Surface.SetPresentsWithTransaction()` — public API for toggling transaction present. - `Device.maintainAfterIdle()` — processes DestroyQueue after WaitIdle. - `RunInFramePool()` — per-frame NSAutoreleasePool wrapper. - Reusable `alignBuf` in `pendingWrites` — eliminates per-upload allocation. ### Contributors - **@lkmavi** ([PR #239](https://github.com/gogpu/wgpu/pull/239)) — discovered and fixed Metal IOSurface leak, verified on Apple Silicon M4. ## [0.30.9] - 2026-07-05 ### Dependencies - goffi v0.5.5 → v0.5.6 — fixes callback stack-move corruption. Callbacks that caused goroutine stack growth could corrupt `callbackArgs` pointers because Go's moving GC relocates stacks. Fix uses `sync.Pool` + `runtime.Pinner` (PR go-webgpu/goffi#59, @tie). ## [0.30.8] - 2026-06-28 ### Fixed - **Software: reject SampleCount > 1** (#234) — `CreateTexture` now returns error for MSAA textures. Software backend has no real multisampling; the false acceptance caused gg to allocate useless MSAA textures and perform no-op resolve copies every frame. Also removed `Multisample`/`MultisampleResolve` flags from adapter capabilities. - **Software: BGRA swizzle on alpha blending readback** (#235) — when loading existing framebuffer pixels for compositing, raw BGRA bytes were read as RGBA. On Windows (BGRA8 surface) this produced incorrect colors for semi-transparent blending. Both vertex-draw and SPIR-V draw paths fixed. - **Software: buffer binding offset/size + dynamic offsets** (#236) — `CreateBindGroup` now stores `BufferBinding.Offset` and `Size`; `SetBindGroup` accepts `dynamicOffsets`; `buildExecutionContext` slices buffers correctly. Previously all buffer data was passed from byte 0, violating WebGPU spec for sub-allocated uniform buffers. ## [0.30.7] - 2026-06-28 ### Fixed - **Software: format-aware copy commands** — `CopyBufferToTexture`, `CopyTextureToBuffer`, `CopyTextureToTexture` used hardcoded 4 bytes/pixel. Now use `formatBytesPerPixel()` → `BlockCopySize()`. R8 copies computed 4× too much data, RGBA16Float computed half. - **Software: format-aware Clear()** — texture clear used `i += 4` loop. Now handles 1-byte (R8), 2-byte (RG8/R16), and 4+ byte formats correctly. R8 render target clear no longer writes out of format bounds. - **GLES: flaky integration test** — `TestGLObjectCreation` intermittently returned 0 from `glGenBuffers` in CI. Root cause: EGL/GL context bound to OS thread, but Go test goroutines migrate between threads. Added `runtime.LockOSThread()` to all 5 EGL tests. ## [0.30.6] - 2026-06-28 ### Changed - **Migrate to `gputypes.TextureFormat.BlockCopySize()`** — canonical format size from gputypes v0.5.1 replaces two independent implementations: - Deleted `hal/vulkan/command.go:blockCopySize()` (30 formats, private) - Simplified `hal/software/resource.go:formatBytesPerPixel()` to delegate (was 8 formats) - Software backend now correctly handles RGBA16Float (8 bytes), RGBA32Float (16 bytes), Stencil8 (1 byte), Depth16 (2 bytes) — previously all defaulted to 4. ### Dependencies - gputypes v0.5.0 → v0.5.1 (adds `BlockCopySize()` — 87 formats, Rust wgpu-types parity) ## [0.30.5] - 2026-06-27 ### Fixed - **Software: R8 texture sampling stride** — texture allocation, `WriteTexture`, and `readTexel` all hardcoded 4 bytes/pixel (RGBA8). For R8 textures (1 byte/texel, e.g. glyph coverage atlas) this caused a 4× horizontal stride: sampling at U read texel at 4×U, producing garbled/blank text. Now derived from format via `formatBytesPerPixel`. - **Software: DrawIndexed implemented** — was a no-op (`func DrawIndexed(_, _, _ uint32, _ int32, _ uint32) {}`). Glyph-mask and MSDF text pipelines use indexed draws exclusively, so all text vanished. Resolves index buffer (uint16/uint32, baseVertex) into vertex indices shared with the non-indexed rasterizer path. ### Added - `formatBytesPerPixel` helper — single source of truth for R8(1), RG8/R16(2), RGBA8/BGRA8(4). Used by CreateTexture, WriteTexture, and SPIR-V sampler. - `readTexel` format-aware unpacking — R8→(r,0,0,1), RG8→(r,g,0,1) per WebGPU spec. - `Texture2D.BytesPerPixel` field for SPIR-V interpreter texture sampling. - Integration tests: `TestR8SampleCoordRepro`, `TestDrawIndexedRenders`. ### Contributors - **@samyfodil** ([PR #229](https://github.com/gogpu/wgpu/pull/229)) — discovered and fixed R8 stride + DrawIndexed via gg glyph-mask text on software backend. ## [0.30.4] - 2026-06-25 ### Fixed - **Metal: stencil state translation** — `CreateRenderPipeline` now translates `StencilFront`/`StencilBack` face states (compare function, fail/depth-fail/pass ops) and read/write masks into `MTLDepthStencilState`. Previously Metal kept defaults (`Always` + `Keep`), silently disabling the stencil test. Visible as rounded UI rendering as squares on macOS (stencil-then-cover fill path). - **Metal: stencil attachment pixel format** — set `stencilAttachmentPixelFormat` on the render pipeline descriptor for formats with stencil aspects (`Depth24PlusStencil8`, `Depth32FloatStencil8`, `Stencil8`). Matches Rust wgpu-hal Metal pattern. - **Metal: depth-stencil descriptor leak** — `Release(depthStencilDesc)` after `newDepthStencilStateWithDescriptor:` (descriptor properties copy). ### Added - `stencilOperationToMTL` — explicit WebGPU→Metal stencil operation mapping (required: enum values differ, e.g. `Invert`: WebGPU=4, Metal=5). ### Contributors - **@samyfodil** ([PR #227](https://github.com/gogpu/wgpu/pull/227)) — discovered and fixed Metal stencil state, verified on Apple Silicon M4. ## [0.30.3] - 2026-06-24 ### Added - **SPIR-V interpreter: hyperbolic math** — `Sinh`/`Cosh`/`Tanh`/`Asinh`/`Acosh`/`Atanh` (GLSL.std.450 opcodes 19–24). Previously returned silent zeros. - **SPIR-V interpreter: integer clamp** — `UClamp` (44) and `SClamp` (45) for unsigned and signed integers. Only float `FClamp` was implemented before. - `glslTernaryUint`/`glslTernaryInt` helpers for ternary integer operations. ### Contributors - **@amery** ([PR #225](https://github.com/gogpu/wgpu/pull/225)) — discovered and fixed missing opcodes via Born ML's software backend path. ## [0.30.2] - 2026-06-17 ### Dependencies - goffi v0.5.3 → v0.5.5 ## [0.30.1] - 2026-06-15 ### Fixed - **Remove broken gpucontext sentinel methods** — `gpucontext_sentinel.go` deleted. Unexported methods don't work cross-package per Go spec. ### Added - **gpucontext handle helpers** — type-safe conversion isolating `unsafe.Pointer` from consumers: `DeviceFromHandle/ToHandle`, `QueueFromHandle/ToHandle`, `AdapterFromHandle/ToHandle`, `SurfaceFromHandle/ToHandle`, `InstanceFromHandle/ToHandle`. ### Dependencies - **gpucontext v0.21.0** — opaque struct tokens (ADR-018 pattern). New dependency: wgpu → gpucontext (DIP: implementation → abstraction). ## [0.30.0] - 2026-06-15 ### Changed (BREAKING) - **Unified public API across all build targets (ADR-047)** — 6 divergences fixed between native, Rust, and browser builds. Consumer code now compiles on all targets. - **StencilOperation → gputypes** — canonical webgpu.h spec values (uint32, 0x1-based). Was: HAL uint8 iota (0-based) on native, independent uint32 (0-based) on browser. Breaking: numeric values changed (Keep: 0→0x1, Zero: 1→0x2, etc.). - **Device.released → atomic.Bool** — fixes data race when Release() called concurrently with CreateBuffer(). Was plain bool. - **gpucontext sentinel methods** — `*wgpu.Device` now satisfies `gpucontext.Device` interface (with sentinel). Requires gpucontext v0.20.0. ### Added - **MinBindGroups = 4** — WebGPU spec guaranteed minimum. Portable code should use ≤4 bind groups. MaxBindGroups (8) is HAL hard cap. - **CommandEncoder.CopyBufferToTexture** — WebGPU spec method, was browser-only. Now available on native (routes through HAL). - **CommandEncoder.ClearBuffer** — WebGPU spec method, was browser-only. Now available on native. - **Browser fence stubs** — DestroyFence, ResetFence, GetFenceStatus, WaitForFence (no-op on browser, matching native signatures). - **Browser ComputePipelineDescriptor** — added Constants and ZeroInitializeWorkgroupMemory fields (were native-only). - **browser-compute example** — WASM WebGPU compute validation (shader → dispatch → readback). ## [0.29.16] - 2026-06-15 ### Fixed - **HAL wrapper stubs for Rust and Browser builds** — `NewDeviceFromHAL`, `NewSurfaceFromHAL`, `NewTextureFromHAL`, `NewTextureViewFromHAL`, `NewSamplerFromHAL` now have stubs in `wrap_rust.go` (error return) and `wrap_browser.go` (`any` params, nil return). Previously these 5 public functions disappeared with `-tags=rust` or `js/wasm`, breaking consumers that reference them. Public API must compile on all build targets. ## [0.29.15] - 2026-06-15 ### Dependencies - naga v0.17.15 — HLSL sampler comparison fix (@georgebuilds first contribution, @lkmavi hardware verification) - golang.org/x/sys v0.46.0 ## [0.29.14] - 2026-06-11 ### Fixed - **GLES: missing vertexBuffers in Linux RenderPipeline** — `device_linux.go` `CreateRenderPipeline` did not store `desc.Vertex.Buffers` in the pipeline struct. Windows `device.go` had it. Without vertex buffer layout, vertex attributes were never configured → all geometry silently discarded → blank window. One-line fix enables full gg rendering (SDF shapes, text, widgets) on GLES Linux. Found by @lkmavi (PR #215). ## [0.29.13] - 2026-06-11 ### Added - **GLES: GLSL version propagation from driver (Rust wgpu-hal parity)** — `compileWGSLToGLSL` no longer hardcodes `glsl.Version430`. The detected `GL_SHADING_LANGUAGE_VERSION` is propagated from adapter → device → naga GLSL writer. On GL 4.1 (GLSL 410) emits `#version 410 core` instead of `#version 430 core`. Verified on WSL2 Mesa d3d12 gallium (GL 4.1). Rust reference: `adapter.rs:279-298` → `device.rs:1228` → naga Options. - **GLES: runtime binding fallback for GL < 4.2 (Rust device.rs:438-461)** — When `layout(binding=N)` is unavailable (GLSL < 420), bindings are assigned after `glLinkProgram` via `glGetUniformBlockIndex` + `glUniformBlockBinding` (UBOs) and `glGetUniformLocation` + `glUniform1i` (samplers). Storage buffers return error on GL < 4.2. New `shaderBindingLayout` capability flag mirrors Rust `PrivateCapabilities::SHADER_BINDING_LAYOUT`. - **GLES: MSAA sample count validation in CreateTexture** — validates `SampleCount` against `GL_MAX_SAMPLES` before texture allocation. Logs warning and clamps if exceeded. `glGetError` checked after `TexImage2DMultisample` and `TexImage2D` — returns proper error instead of leaving stale GL errors in queue. - **GLES: GLES-aware GL function loading** — `gl.Context.Load()` accepts `isGLES` flag. On GLES contexts, loads `glClearDepthf` (not `glClearDepth`), tries OES suffix for VAO functions. Fixes Mesa d3d12 gallium dispatch stub errors. - **GLES: lazy VAO creation in CreateCommandEncoder** — persistent VAO allocated on first encoder creation (after Configure), not during Adapter.Open (before Configure). Ensures VAO lives on the window surface context. - **GLES: compute dispatch VERTEX_ATTRIB_ARRAY_BARRIER_BIT** — added to memory barrier after compute dispatch. Required when compute writes SSBO that is read as vertex buffer (e.g. particles ping-pong). Found by @lkmavi (PR #215). ### Fixed - **GLES: tiered EGL config — WindowBit-only tier for Wayland** — `chooseEGLConfig` adds `WindowBit` alone as middle tier. Mesa Wayland EGL does not support `PbufferBit` — combined `WindowBit|PbufferBit` returns 0 configs. `WindowBit` alone = 48 configs on Mesa Wayland. - **GLES: skip Wayland instance context** — `CreateInstance` skips EGL context on Wayland (no `wl_display*` at init). Prevents second wl_display connection and GL object namespace issues between pbuffer and window surface. - **GLES: GLES 3.0 fallback in CreateSurface** — tries desktop GL first, falls back to GLES 3.0 when Mesa Wayland EGL only exposes `EGL_OPENGL_ES3_BIT` configs. Found by @lkmavi (PR #215). - **GLES: CoreProfile guard for GLES contexts** — `createEGLContext` skips `EGL_CONTEXT_OPENGL_PROFILE_MASK` on GLES (invalid, some drivers reject it). Found by @lkmavi (PR #215). ### Verified - **Triangle renders on GLES WSL2 Wayland** — first Pure Go GLES rendering on Linux Wayland. Red triangle on gray background, GL 4.1 Core Profile, Mesa d3d12 gallium, `#version 410 core`. ### Known Issues - gg rendering (SDF shapes, text) not yet working on GL 4.1 — separate gg task. Wayland EGL alpha channel causes transparent window (gg clears with alpha=0). ### Dependencies - naga v0.17.14 — `SupportsExplicitLocations()`, `UniformInfo` reflection, version-gated `layout(binding=N)` emission ## [0.29.12] - 2026-06-08 ### Fixed - **GLES: Wayland EGL nativeDisplay=0 fallback to surfaceless** — `GetEGLDisplay` with `nativeDisplay=0` on Wayland tried `eglGetPlatformDisplay(WAYLAND, 0)` which opens a second `wl_display` connection via `wl_display_connect(NULL)`. Surface's `wl_surface*` is on the app's display → "Proxy and queue point to different wl_displays" crash. Fix: skip Wayland platform when `nativeDisplay=0` (Instance init), fall back to surfaceless. `CreateSurface` calls again with real `wl_display*`. Also: `CreateSurface` Path A skips sharing Instance context when it used surfaceless display (can't create window surface on surfaceless EGL display). Found by @lkmavi. ## [0.29.11] - 2026-06-08 ### Added - **GLES: shared context in CreateSurface (Windows AdapterContext parity)** — When Instance has a pre-created EGL context (X11/headless), `CreateSurface` shares it instead of creating a new one. Surface stores `ownsContext=false` and only references the Instance context. On Wayland (no Instance context), `CreateSurface` creates its own (`ownsContext=true`). Matches Windows pattern where Surface is lightweight (HWND + shared AdapterContext reference). - **GLES: tiered EGL config selection (Rust wgpu-hal egl.rs:218-293 parity)** — `chooseEGLConfig` now tries WINDOW+PBUFFER first, falls back to PBUFFER-only. Ensures the chosen config supports both headless rendering (pbuffer) and later window presentation. - **GLES: CI GL object creation test** — `TestGLObjectCreation` verifies `GenBuffers`, `GenTextures`, `GenVertexArrays`, `GenFramebuffers` return non-zero IDs through goffi. Catches the FFI pointer convention bug (PR #210, ADR-044) that went undetected because CI previously only tested EGL init. ## [0.29.10] - 2026-06-08 ### Added - **GLES: instance-level EGL context on Linux (Rust wgpu-hal parity)** — `CreateInstance` now creates an EGL context via surfaceless/pbuffer at init time, matching Rust wgpu-hal `egl.rs:846` and Windows hidden window pattern (v0.28.6). `EnumerateAdapters` uses the instance context to return real GPU name/vendor/version instead of placeholder "Unknown" values. On Wayland (no `wl_display*` at init), gracefully degrades — `CreateSurface` provides context later (nil guard from PR #210). ### Fixed - **GLES: EGL surfaceless context fallback (Rust wgpu-hal egl.rs:735-758 parity)** — `NewContext` now checks for `EGL_KHR_surfaceless_context` or EGL 1.5+ before creating pbuffer. When surfaceless is available, `MakeCurrent(EGL_NO_SURFACE)` works without a dummy 1×1 pbuffer. Fallback to pbuffer on older drivers. - **GLES: FFI pointer convention in context_linux.go** (PR #210, @lkmavi) — 30+ GL calls fixed: `unsafe.Pointer(&value)` → `unsafe.Pointer(&pointer)` for PointerType goffi arguments. See ADR-044. Fundamental fix enabling GLES rendering on Linux for the first time. ## [0.29.9] - 2026-06-07 ### Fixed - **Software: Wayland SHM triple-buffer freeze — bufferBusyMap pointer fix** — `bufferBusyMap` stored pointer to temporary struct from `waylandCreateShmBuffer`, but caller did `*buf = *newBuf` (value copy into `wl.buffers[idx]`). Release callback set `busy=false` on the dead heap object, not on the array element — all 3 buffers stuck `busy=true` after 3 frames, presentation frozen. Fix: register in `bufferBusyMap` AFTER value copy, with pointer to `&wl.buffers[idx]`. - **Software: Wayland SHM dispatch — roundtrip_queue instead of dispatch_queue_pending** — `wl_display_dispatch_queue_pending` only processes already-read events. Nobody reads wire data into `shmQueue` — gogpu's `read_events` runs on the main thread independently. Result: `dispatch_queue_pending` sees empty queue, release callbacks never fire. Fix: `wl_display_roundtrip_queue(display, shmQueue)` which does prepare_read → read_events → dispatch in one call. ## [0.29.8] - 2026-06-06 ### Fixed - **Software: Wayland queue mismatch — display wrapper pattern (gogpu#292)** — `obtainWlShm()` created `wl_registry` and `wl_shm` via raw `wl_display*`, placing them on the DEFAULT queue. gogpu dispatches only its `appQueue` (ADR-041) — DEFAULT queue events were never dispatched, causing proxy state corruption and SIGSEGV on frame 2. Fix: create a display wrapper via `wl_proxy_create_wrapper(display)` + `wl_proxy_set_queue(wrapper, shmQueue)` so all child proxies (registry, wl_shm, pool, buffer) inherit `shmQueue`. Uses `wl_display_roundtrip_queue` instead of `wl_display_roundtrip` to dispatch the correct queue. Matches Qt6 ephemeral wrapper pattern (`qwaylandwindow.cpp:1799-1802`). Also fixes proxy leak: registry and wl_shm now properly destroyed in `destroyWaylandBlitState`. Removes manual `wl_proxy_set_queue` on buffer proxies — queue inheritance is automatic via libwayland `proxy_create` (`wayland-client.c:492`). ## [0.29.7] - 2026-06-06 ### Fixed - **Software: eager Wayland wl_shm init in Configure — fix SIGSEGV (gogpu#292)** — `obtainWlShm()` called `wl_display_roundtrip()` on the render thread during first present, concurrent with the main thread's Wayland event dispatch. Two concurrent dispatches on the same `wl_display` corrupt proxy state → SIGSEGV at offset 0x18 (`wl_proxy.display` field from NULL). Moved detection + wl_shm binding to `Configure()` on the main thread, before the event loop starts. Enterprise references unanimously do this during init: GLFW (`glfwInit`, `wl_init.c:568`), SDL3 (`SDL_Init`, `SDL_waylandvideo.c:1548`), Qt6 (`QWaylandDisplay()`, `qwaylanddisplay.cpp:531` — explicit comment about this exact race), winit (`EventLoop::new`, `event_loop/mod.rs:92`). Affects both @lkmavi (ARM64 Fedora) and @omer316 (x86_64 Pop!_OS) from gogpu#292. ## [0.29.6] - 2026-06-06 ### Fixed - **GLES: Fence.Signal returns error on glFenceSync failure (Rust parity)** — `Fence.Signal()` silently fell back to marking submissions as immediately complete when `glFenceSync` returned 0 (OOM). Now returns `error`, propagated through `Queue.Submit()`. Matches Rust wgpu-hal `fence.rs:37-52` which returns `Result<(), DeviceError::OutOfMemory>`. ## [0.29.5] - 2026-06-06 ### Fixed - **GLES: fence sync ordering — glFenceSync before glFlush (Rust parity)** — `Queue.Submit()` called `glFlush()` before `fence.Signal()` (`glFenceSync`), leaving the fence in an unflushed client-side buffer. `PollCompleted()` uses non-blocking `glGetSynciv` (no implicit flush), so the fence returned `GL_UNSIGNALED` indefinitely — stalling deferred resource destruction in headless/compute workloads. Headed rendering was unaffected (`SwapBuffers` provides implicit flush). Reordered to match Rust wgpu-hal `queue.rs:1915-1921`: `fence.Maintain` → `fence.Signal` → `glFlush`. Confirmed by ANGLE bug 6464 (Samsung deadlock in production), virglrenderer commit `21bbc9ea` (Windows/macOS stalls), Mesa Gallium (internally flushes in `glFenceSync`, masking the bug on Linux). Affects both Windows WGL and Linux EGL paths. - **GLES: remove redundant Maintain() from PollCompleted** — Rust wgpu-hal only calls `get_latest()` in the poll path (`device.rs:1564`); fence cleanup happens during `Submit()`. Removes double `GetLatest()` per poll cycle. ## [0.29.4] - 2026-06-06 ### Fixed - **Software: dedicated wl_event_queue for SHM buffer release dispatch** — SHM `wl_buffer` proxies were on Wayland default queue, but gogpu dispatches a separate app queue — release callbacks never fired, causing triple-buffer exhaustion after 3 frames. Fix: per-surface `wl_event_queue` via `wl_display_create_queue`, buffer proxies moved via `wl_proxy_set_queue`, `wl_display_dispatch_queue_pending` after flush processes release events. No race with main thread (queue is per-surface, render thread only). ## [0.29.3] - 2026-06-05 ### Fixed - **Software: Wayland SHM present (enterprise quality)** (ADR-042/043, gogpu#292) — `blit_wayland.go` implements Wayland-native presentation via wl_shm triple-buffered SHM. Auto-detects Wayland vs X11 via `WAYLAND_DISPLAY` env var. Loads libwayland-client.so independently. No pixel format conversion (BGRA = ARGB8888 on LE). Enterprise features validated against Qt6, Rust wgpu-hal, SDL3, GLFW: - `wl_buffer.release` listener (Qt6 `qwaylandbuffer.cpp` pattern) — compositor signals when buffer is safe to reuse, prevents data race on busy buffers - Triple-buffering (Qt6 uses up to 5) — eliminates frame skips on slow compositors - `PrepareVariadicCallInterface` (goffi v0.5.2+ ADR-043) — correct ARM64 AAPCS64 variadic ABI for `wl_proxy_marshal` / `wl_proxy_marshal_constructor` (7 CIFs) - Cached CIFs — zero heap allocations per frame in present hot path - `wl_surface.damage_buffer` opcode 9 (buffer coordinates, HiDPI correct) - Row-based partial copy — `waylandPresentDamage` copies only damaged regions (1080p small damage: 332µs → 1.6µs, 208× speedup) - Eager `obtainWlShm` init — roundtrip removed from per-frame hot path - Dedicated helpers: `waylandSurfaceAttach`/`waylandSurfaceDamageBuffer`/`waylandSurfaceCommit` - **GLES: Wayland EGL window surface (Rust wgpu-hal parity)** (ADR-042, gogpu#292) — `egl/wayland_egl.go` loads libwayland-egl.so for `wl_egl_window_create/destroy/resize`. `resource_linux.go` creates EGL window surface via wl_egl_window. EGL 1.5 `eglCreatePlatformWindowSurface` with runtime detection + EGL 1.4 fallback (Rust wgpu-hal `egl.rs:1479` parity). Correct destroy order (eglDestroySurface before wl_egl_window_destroy). - **GLES: fix compute shaders, instanced rendering, primitive topology** (Rust wgpu-hal parity) — Three critical bugs in `command.go` fixed: (1) storage buffers now bind as `GL_SHADER_STORAGE_BUFFER` instead of `GL_UNIFORM_BUFFER` — compute shaders work (Rust `command.rs:731-746`); (2) draw commands use pipeline topology instead of hardcoded `GL_TRIANGLES` — PointList/LineList/LineStrip/TriangleStrip all correct; (3) `glVertexAttribDivisor` called for per-instance vertex attributes — instanced rendering works (Rust `queue.rs:1372`). Verified: particles example renders 4096 compute-driven particles on GLES. ## [0.29.2] - 2026-06-05 ### Fixed - **Vulkan: comprehensive VK_ERROR_SURFACE_LOST_KHR handling (Rust wgpu parity)** — `acquireNextImage`, `present`, and `createSwapchain` now handle `VK_ERROR_SURFACE_LOST_KHR` → `hal.ErrSurfaceLost` in all code paths, matching Rust wgpu-hal `native.rs` (acquire line 452, present line 584, create line 228). Added defensive `sc.handle == 0` guard in `present()`. Triggered by Wayland SIGSEGV investigation (gogpu#292) — two users on Intel ANV and AMD RADV confirmed crash in `vkQueuePresentKHR`. While the root cause is in gogpu's Wayland surface lifecycle (missing configure gate + thread safety), robust error handling in the Vulkan HAL prevents future crashes from surfacing as segfaults. ### Changed - **deps:** goffi v0.5.2 → v0.5.3 ## [0.29.1] - 2026-05-27 ### Changed - **deps:** go-webgpu/webgpu v0.5.1 → v0.5.2 ## [0.29.0] - 2026-05-27 ### Added - **ADR-038: Rust backend at public API level (browser pattern)** — Triple-backend architecture via build tags. `(default)` Pure Go core→HAL, `(-tags rust)` go-webgpu/webgpu v0.5.0 → wgpu-native v29, `(js,wasm)` Browser WebGPU. 24 new `_rust.go` files with platform-specific surface creation (Windows HWND, macOS Metal, Linux X11/Wayland). Same `*wgpu.Device`, `*wgpu.Buffer`, `*wgpu.Texture` API on all backends. ### Removed - **ADR-039: Speculative Hal* escape hatches** — Removed `HalTexture()`, `HalTextureView()`, `HalQueue()` from public API. Zero production callers across ecosystem. Browser backend already lacked them. HAL is an internal detail of the Native Go path, not part of cross-backend API. ### Changed - **deps:** go-webgpu/webgpu v0.5.1 (direct), goffi v0.5.2, x/sys v0.45.0 (Rust backend) ## [0.28.11] - 2026-05-26 ### Fixed - **Core: eliminate intermediate trackedRefs copies in pass encoders** — Pass encoders now write tracked ResourceRefs directly to the parent CommandEncoder's slice, eliminating per-pass intermediate slices and the copy at End(). Previously each pass had its own `trackedRefs` slice copied to the encoder via `append()` at End(), creating abandoned backing arrays. Encoder pre-allocates trackedRefs with capacity 64. For Born ML (10K dispatches × 4 buffers): eliminates 40K pointer copies per pass + all intermediate arrays. Matches Rust wgpu where trackers live in the command encoder throughout recording. ## [0.28.10] - 2026-05-26 ### Fixed - **Core: pre-allocate trackedRefs slices in pass encoders** — `BeginComputePass`, `BeginRenderPass`, and `CreateCommandEncoder` now pre-allocate `trackedRefs` with capacity 64. ## [0.28.9] - 2026-05-26 ### Fixed - **Core: refcount-driven Buffer destruction via onZero (Rust Drop parity)** — `Buffer.Release()` now calls `ResourceRef.Drop()` instead of Phase 1 `DestroyQueue.Defer(lastSubmissionIndex)`. The onZero callback on ResourceRef fires `core.Buffer.Destroy()` only when the last reference drops — either from explicit Release (refCount 1→0 if never encoded) or from Phase 2 Triage after GPU completion (refCount 1→0 when tracked Clone'd refs drop). Previously Phase 1 used `lastSubmissionIndex` which could be stale if Release() was called before Submit(), allowing premature HAL destruction while GPU still used the buffer. Now Phase 2 Clone/Drop is the sole mechanism for buffer lifetime — deterministic, refcount-driven, Rust parity. ## [0.28.8] - 2026-05-26 ### Fixed - **Core: Clone buffer ResourceRefs in SetBindGroup (use-after-free prevention)** — `SetBindGroup` now calls `ResourceRef.Clone()` on each buffer referenced by the bind group, keeping buffers alive until GPU completes the submission. Previously only `BindGroup.ref` was Clone'd — individual buffer refs were tracked for validation only (VAL-A6), not refcounted. If a caller called `Buffer.Release()` before `Queue.Submit()`, the buffer could be HAL-destroyed while still referenced by a pending command buffer. Matches Rust wgpu `merge_bind_group` → `ResourceMetadata.insert(Arc)` pattern. Affects both `RenderPassEncoder.SetBindGroup` and `ComputePassEncoder.SetBindGroup`. ## [0.28.7] - 2026-05-21 ### Fixed - **Core: trigger deferred GLES enumeration in RequestAdapter** — `RequestAdapter()` now calls `enumerateDeferredGLES(nil)` before adapter selection. Previously, GLES adapters were only enumerated via `RequestAdapterWithSurface(surface)` with a non-nil surface hint. After gogpu LIFECYCLE Phase 2 decoupled adapter from surface, `RequestAdapter(nil)` skipped GLES enumeration entirely, returning "Software Renderer" instead of "Intel Iris Xe". Safe with nil since v0.28.6 (hidden window GL context ignores surface parameter). ## [0.28.6] - 2026-05-21 ### Fixed - **GLES: Instance-owned GL context on hidden window (Rust wgpu parity)** — GL context now lives on a hidden 1×1 HWND owned by Instance, surviving any user Surface destruction. Previously the context was tied to the user window — closing the window killed the context, leaving Adapter/Device/Queue with dangling references. New `AdapterContext` wraps the context with `sync.Mutex`-protected `MakeCurrent` switching: `Lock()` → hidden DC for resource creation/submit, `LockForDC()` → user DC for presentation. Surface is lightweight (no context ownership). Windows WGL Phase 1; Linux EGL Phase 2 planned. Follows Rust wgpu-hal/src/gles/wgl.rs `AdapterContext::lock()`/`lock_with_dc()` pattern. ## [0.28.5] - 2026-05-21 ### Fixed - **Metal: autorelease pool leak in Present()** — `pool.Drain()` now deferred, preventing ObjC autorelease pool leak on panic. Rust wgpu uses closure-scoped `autoreleasepool`. - **Metal: explicit drawable count in Configure()** — added `setMaximumDrawableCount:3` matching Rust wgpu `surface.rs:176` (`maximum_frame_latency + 1`). - **Core: indirect validation nil pointer on noop backend** (#189) — `NewIndirectValidation` now checks `MaxComputeWorkgroupSizeX > 0` before creating GPU resources. Noop/test backends skip validation gracefully. Rust wgpu checks `DownlevelFlags::INDIRECT_EXECUTION`. ## [0.28.4] - 2026-05-21 ### Added - **macOS software backend presentation** (PR #187 by @k-chimi) — windowed rendering via CoreGraphics (CALayer `setContents:`) and Metal (CAMetalLayer `nextDrawable` + `replaceRegion`). Dual-path: runtime detection selects CALayer or CAMetalLayer. Damage rects on CALayer, full-blit fallback on CAMetalLayer. Self-contained ObjC bindings with selector caching. Headless CI guard via `os.Stat` before Metal framework load. 1310 LOC. Closes #163. **All 3 desktop platforms now have software backend presentation** (Windows GDI, Linux X11, macOS CG+Metal). - **GPU dispatch indirect validation** — pre-dispatch compute shader validates workgroup counts against `maxComputeWorkgroupsPerDimension`. Invalid counts produce `(0,0,0)` output, preventing GPU hang/TDR. Matches Rust wgpu-core `indirect_validation/dispatch.rs`. ### Fixed - **GLES: GPU fence via `glFenceSync`** — replaced atomic-only fake fence (always "complete") with real `glFenceSync`/`glClientWaitSync`. Proper GPU completion tracking. Graceful fallback to atomic mode when GL sync unavailable. Matches Rust wgpu-hal `gles/fence.rs`. - **GLES: `CopyBufferToTexture`** — was no-op stub. Now uses `GL_PIXEL_UNPACK_BUFFER` (PBO) + `glTexSubImage2D`. Matches Rust wgpu-hal `gles/queue.rs`. - **GLES: `CopyTextureToTexture`** — was no-op stub. Now uses FBO attach + `glCopyTexSubImage2D`. Matches Rust wgpu-hal `gles/queue.rs`. - **GLES: timestamp queries** — `CreateQuerySet` was returning `ErrTimestampsNotSupported`. Now creates GL query objects via `glGenQueries`, writes timestamps via `glQueryCounter`. Resolves via `glGetQueryObjectui64v`. Matches Rust wgpu-hal `gles/device.rs`. - **GLES: adapter capability detection** — replaced 146 LOC of hardcoded defaults with real GL introspection (813 LOC): version/extension probing, 10+ WebGPU features from GL extensions, 30+ limits from `GL_MAX_*` queries, per-format texture capabilities, device type + vendor ID inference. Matches Rust wgpu-hal `gles/adapter.rs`. - **macOS blit lint fix** — resolved 38 golangci-lint issues in `blit_darwin.go`: errcheck, unconvert, revive, staticcheck, unused. 0 issues on all 3 platforms. - Closes #163. ## [0.28.3] - 2026-05-17 ### Fixed - **Vulkan: log actual swapchain extent after creation** — diagnostic logging after `vkCreateSwapchainKHR` shows actual extent, image count, format, and present mode. Complements v0.28.2 capabilities logging to verify driver-created images match requested dimensions on HiDPI configurations. Closes #185. ## [0.28.2] - 2026-05-17 ### Added - **`Surface.ActualExtent()`** — returns actual swapchain dimensions after driver clamping. On Vulkan, the driver may clamp the requested extent to its capabilities range (common on X11 HiDPI where compositor reports physical pixels). Previously silent — now logged with `slog.Warn` when clamping occurs. All 8 backends implement the HAL interface method. Non-Vulkan backends return configured dimensions (no clamping). Closes #183. ## [0.28.0] - 2026-05-14 ### Added - **Browser WebGPU backend** (WASM-001) — complete browser WebGPU support via `syscall/js` → `navigator.gpu`. Same public API, same user code — `GOOS=js GOARCH=wasm go build` produces a working WASM binary. ~6500 LOC across 5 phases: - **Phase 1:** Instance, Adapter, Device — `navigator.gpu` access, pre-bound JS methods (Ebiten pattern) - **Phase 2:** Resources + Pipelines — Buffer, Texture, ShaderModule (WGSL passthrough), BindGroup, RenderPipeline, ComputePipeline. 97 TextureFormats + 31 VertexFormats + all WebGPU enum conversions - **Phase 3:** Command Recording + Submit — CommandEncoder, RenderPassEncoder (13 methods), ComputePassEncoder, Queue.Submit/WriteBuffer/WriteTexture with `js.CopyBytesToJS` data transfer - **Phase 4:** Surface/Canvas — HTMLCanvasElement + GPUCanvasContext, Configure, GetCurrentTexture, Present (no-op — browser auto-presents), preferred format detection - **Phase 5:** Buffer Mapping — MapAsync (Promise→goroutine), MappedRange with lazy copy (Rust OnceCell pattern), dirty write-back on Release (Rust Drop pattern) - Architecture: bypasses core/hal (browser validates internally), matches Rust wgpu `backend/webgpu.rs` - Zero external dependencies — only `syscall/js` from Go stdlib - 29+ enum conversion tests running on native (no WASM required) ## [0.27.5] - 2026-05-14 ### Fixed - **Vulkan: crash on destroyed texture barrier** — `TransitionTextures` passed NULL VkImage to `vkCmdPipelineBarrier` when a destroyed texture (handle=0) was in the barrier array, causing access violation (Exception 0xc0000005 at address 0x23e). Now skips invalid barriers with a warning log. Same fix applied to `TransitionBuffers`. Rust wgpu prevents this at the core layer via `Snatchable` + `SnatchGuard`. Our fix adds defense-in-depth at three levels: 1. **Public API** (`encoder_native.go`) — filters nil/destroyed textures before HAL 2. **Vulkan HAL** (`hal/vulkan/command.go`) — skips barriers with handle=0 3. **DX12 HAL** (`hal/dx12/command.go`) — skips barriers with nil raw resource ## [0.27.4] - 2026-05-13 ### Fixed - **Flaky TestThread_CallAsync on Windows CI** — replaced `time.Sleep(10ms)` with channel-based synchronization. Same deterministic pattern as SnatchLock fix (f940eb7). Verified: 100/100 passes with `-count=100`. ### Changed - **goffi v0.5.0 → v0.5.1** — struct by-value argument passing (System V AMD64 ABI), 9-16B struct return via XMM registers (NSPoint, CGSize — critical for Metal backend), callback struct arguments, CGO_ENABLED=1 support (race detector), E2E test infra. Contributors: @jiyeyuran (CGO), @pekim (callbacks). - **x/sys v0.43.0 → v0.44.0** — latest platform syscall definitions. ## [0.27.3] - 2026-05-11 ### Added - **Software backend: render pass instrumentation** (FEAT-SW-009) — two-level observability: 1. `hal.Logger().Debug()` events at BeginRenderPass, SetScissorRect, Draw, End — zero overhead when logger is nop (default). Enable via `GOGPU_LOG=debug`. 2. `RenderPassStats` struct via `pass.(*software.RenderPassEncoder).Stats()` — typed assertions for CI e2e tests (DrawCount, HasScissor, ScissorRect, Width, Height, ColorLoadOp). ### Fixed - **Metal: documented MsgSend void-return pattern** — `_ = MsgSend(obj, Sel("set..."))` is correct for void-returning ObjC selectors. objc_msgSend always returns a register-sized value regardless of method return type. Same pattern as Rust wgpu-hal/metal via objc msg_send! macro. ## [0.27.2] - 2026-05-10 ### Added - **DX12: timestamp queries** — `CreateQuerySet`, `DestroyQuerySet`, `ResolveQuerySet`, begin/end-of-pass timestamp writes. Uses `EndQuery` for timestamps (DX12 pattern, not begin/end pair). FFI wrappers: `EndQuery`, `ResolveQueryData`. Matches Rust wgpu-hal dx12 command.rs:834-887. ### Fixed - **Vulkan: GetTimestampPeriod** returned hardcoded 1.0 — now reads `VkPhysicalDeviceLimits.TimestampPeriod` at device init. Previously incorrect on AMD/NVIDIA GPUs. - **GLES: compute memory barriers** — `TransitionBuffers`/`TransitionTextures` were no-ops. Compute shader writes not guaranteed visible to subsequent draw/dispatch. Now emits `glMemoryBarrier` with appropriate barrier bits when transitioning from storage usage. Matches Rust wgpu-hal/gles command.rs:279-327. - **Queue thread safety** — `Submit`, `WriteBuffer`, `WriteTexture`, `LastSubmissionIndex` serialized via `sync.Mutex`. Matches Rust wgpu-core which uses `RwLock::write()` on `device.fence` + `device.command_indices` during submit (queue.rs:1171-1173). Prevents data race on `lastSubmissionIndex` from concurrent goroutines. ## [0.27.1] - 2026-05-08 ### Fixed - **Vulkan offscreen texture trail artifacts** (BUG-WGPU-VK-007) — offscreen textures with `TextureBinding` usage were left in `COLOR_ATTACHMENT_OPTIMAL` layout after render pass. When subsequently sampled by a fragment shader, Intel CCS (Color Compression Subsystem) metadata was not decompressed → stale pixels → visual trails. Fix: `offscreenFinalLayout()` returns `IMAGE_LAYOUT_GENERAL` for mixed-usage textures (render + sample), matching Rust wgpu `derive_image_layout()` behavior. - **Vulkan MSAA resolve target stale pixels** (BUG-WGPU-MSAA-RESOLVE-001) — resolve attachment had `LoadOp=DONT_CARE`, leaving uncovered pixels with previous frame content (trail artifacts). Fix: `LoadOp=CLEAR` with MSAA clear color, matching Rust wgpu. Only Vulkan affected — DX12/GLES/Metal/Software already resolve all pixels. - **Software: persistent stencil buffer** (BUG-SW-005) — stencil buffer was recreated per Draw() call, losing stencil writes from previous draws within the same render pass. On GPU the stencil buffer is the depth/stencil attachment texture — persistent for the entire pass. Fix: create once at BeginRenderPass, reuse for all Draw() calls. Enables stencil-based clipping (gg clip_demo rounded rect clip). ### Added - **Software: OpTypeMatrix in SPIR-V interpreter** (BUG-SW-003) — `mat4x4` uniform support. Enables textured_quad shader execution (ortho projection matrix). Required for offscreen texture compositing on software backend. - **Software: SamplerBinding in CreateBindGroup** (BUG-SW-004) — sampler resources now registered and wired into SPIR-V ExecutionContext for correct texture filtering. - **Damage rect pixel-level tests** — 29 tests + 3 benchmarks: partial blit correctness, BGRA byte order, rect clipping, integration with multiple Present calls. ### Dependencies - **naga** v0.17.11 → **v0.17.13** — DXIL PHI ordering fix, ARCH-001 internal packages, ~60% test coverage, 13 panics→errors, public API real types. ## [0.27.0] - 2026-05-06 ### Added - **Software backend: full SPIR-V interpreter** (FEAT-SW-004) — CPU interpreter for SPIR-V shaders. Designed for **shader debugging, CI/CD testing, and GPU-less fallback** — not for production rendering (interpreted, ~100× slower than JIT). ~10K LOC, 370+ tests, 84% coverage. Seven phases: - **Phase 1**: Basic vertex/fragment (triangle renders red) - **Phase 2**: Uniform/storage buffers, vector/matrix ops - **Phase 3**: Texture sampling (nearest, bilinear, 3 wrap modes) - **Phase 4**: GLSL.std.450 math intrinsics (30+ functions) - **Phase 5**: Control flow (loops, phi nodes, function calls, switch) - **Phase 6**: Compute shaders (dispatch, atomics, workgroup shared memory) - **Phase 7**: Shader debugger (DebugContext, breakpoints, JSON trace, zero overhead) - **Software compute HAL integration** — `CreateComputePipeline` + `ComputePassEncoder.Dispatch` execute SPIR-V compute shaders via interpreter. Naga WGSL→SPIR-V compilation in `CreateShaderModule`. Full integration with storage buffers. - **Per-pixel SPIR-V fragment shader** — `DrawTrianglesWithFragmentShader` in raster pipeline. Executes fragment shader per-pixel with interpolated `@location` inputs from vertex outputs. - **Instanced rendering + TriangleStrip** on software backend — instance-rate vertex buffers, `@builtin(vertex_index)` + `@builtin(instance_index)`, alternating winding. - **Software-test example** — `examples/software-test/` end-to-end compute verification. ### Changed - **SPIR-V interpreter performance** — 3× faster, 75% less memory. Slice-based values (replaced `map[uint32]Value`), Pointer pool, optimized compositeConstruct. Vertex: 18→8 allocs (-56%), 2848→712 B (-75%). Fragment: 11→4 allocs (-64%). ### Fixed - **RGBA→BGRA pixel order** — unified `writeRasterToTarget` helper for all draw paths (SPIR-V, vertex buffer, fragment shader). Framebuffer is BGRA for GDI/X11. - **OpAccessChain on function-local composites** — SubPointer type ensures struct member writes propagate to parent variable (was creating disconnected copies). - **Struct byte size calculation** — uses MemberDecorate Offset instead of naive fallback (wrong stride caused storage buffer corruption for RuntimeArray elements). - **zeroValueForVar for TypeStruct** — creates zero-initialized Array with per-member recursion (was returning Uint32(0), breaking struct navigation). - **All 71 lint warnings resolved** — 0 issues on Windows, Linux, macOS. - **Flaky TestSnatchLock_ReadWriteExclusion** — replaced `time.Sleep(10ms)` with channel-based synchronization. Was failing intermittently on Windows CI (goroutine scheduler too slow for 10ms deadline). Now deterministic — 100/100 passes. ### Changed - **Tagged union Value** — SPIR-V interpreter values replaced from `any` (interface boxing, heap alloc per scalar) to 48-byte tagged struct with inline F[4]float32 + U[4]uint32. Fragment shader: 4→3 allocs (-25%), 490→438 ns (-11%). - **Fullscreen blit priority** (ADR-020) — `executeFullscreenBlit` (memcpy) checked before `executeSPIRVDraw` (interpreted). 60 FPS vs 0.65 FPS for textured quad blit. ### Dependencies - **naga** v0.17.10 → **v0.17.11** — fix #170 (SIGSEGV → error for `dot(v)` wrong arg count), BUG-DXIL-041 (fine.wgsl 100% valid), lint hardening. gg production: 61/61 DXIL valid. ## [0.26.12] - 2026-05-01 ### Added - **Test coverage boost** (COV-004) — 8 new test files, ~160 test functions: - `core/error_test.go` — 10 error types: all Kind variants, Unwrap/errors.Is/errors.As chains - `core/buffer_mapping_test.go` — MapWaiter lifecycle, BeginMap states, concurrent Wait - `core/device_poll_test.go` — deviceMapTracker, PollMaps, submission resolution - Root package: device lifecycle, encoder tracking, mapping state machine, pipeline accessors, wrap.go HAL constructors, fence CRUD, resource Release idempotency - core/ coverage: 73.9% → 85.5%, root package: 69.1% → 78.4% ### Fixed - **Metal: entry point name resolution** (#168, PR #167 by @k-chimi) — naga MSL renames reserved words (`main` → `main_`), but the Metal HAL used the original name to look up functions in MTLLibrary. Now stores `TranslationInfo.EntryPointNames` from naga compile and uses the translated name at pipeline creation. Applies to vertex, fragment, and compute. - **Codecov ignore patterns** — `hal/**/*` format did not exclude GPU backends from coverage calculation. Changed to `hal/` (matching gogpu pattern). Badge now reflects testable code only. ### Dependencies - **naga** v0.17.8 → **v0.17.10** ## [0.26.11] - 2026-04-30 ### Fixed - **DX12: DispatchIndirect, DrawIndirect, DrawIndexedIndirect** (BUG-DX12-012) — all three were no-op stubs. Implemented via `ID3D12GraphicsCommandList::ExecuteIndirect` with pre-created `ID3D12CommandSignature` objects (Rust wgpu-hal pattern). Three command signatures created at device init: dispatch (12B stride), draw (16B), draw indexed (20B). DX12 was the only GPU backend with stub indirect methods — now all 4 GPU backends (Vulkan, Metal, GLES, DX12) have full indirect dispatch/draw support. ## [0.26.10] - 2026-04-30 ### Added - **Validation Phase B — correctness checks** (5 P1 checks, full Rust wgpu-core parity): - **VAL-B1:** CreateBindGroup `MinBindingSize` early check — rejects buffer smaller than layout-declared minimum at bind group creation (not deferred to draw time) - **VAL-B2:** DrawIndexed index buffer format mismatch — validates format matches pipeline's `StripIndexFormat` for strip topologies - **VAL-B3:** Indirect buffer validation — `INDIRECT` usage check, 4-byte offset alignment, and buffer overrun bounds check (DrawIndirect 16B, DrawIndexedIndirect 20B, DispatchIndirect 12B) - **VAL-B4:** Depth/stencil format aspect granularity — `hasDepthAspect`/`hasStencilAspect` helpers, rejects depth-only format with stencil ops or stencil-only with depth ops - **VAL-B5:** BindGroup destruction tracking at Queue.Submit — tracks bind group references during encoding, validates not destroyed at submit time - 7 new sentinel errors: `ErrDrawIndexFormatMismatch`, `ErrDrawIndirectBufferUsage`, `ErrDrawIndirectOffsetAlignment`, `ErrDrawIndirectBufferOverrun`, `ErrDispatchIndirectBufferUsage`, `ErrDispatchIndirectOffsetAlignment`, `ErrDispatchIndirectBufferOverrun`, `ErrSubmitBindGroupDestroyed` ## [0.26.8] - 2026-04-26 ### Fixed - **Vulkan: buffer mapping spec compliance** (BUG-VK-009) — 6 fixes from Rust wgpu-hal audit: 1. **CopyDst buffers no longer force host-visible memory** — only MapRead/MapWrite/MappedAtCreation get HOST_ACCESS. CopyDst stays DEVICE_LOCAL (Rust parity). Fixes wrong memory type selection. 2. **nonCoherentAtomSize alignment** for InvalidateMappedMemoryRanges/FlushMappedMemoryRanges. Offset aligned down, size aligned up. Fixes Vulkan spec violation on non-Intel GPUs. 3. **InvalidateMappedMemoryRanges return value checked** — was silently ignored (`_ =`). 4. **mappedMemory cache invalidated on vkFreeMemory** — prevents use-after-free via VkDeviceMemory handle recycling. Allocator notifies Device via onFreeCallback. 5. **Conditional invalidation** — skip for HOST_COHERENT memory (Intel, most desktop GPUs). `BufferMapping.IsCoherent` now reports actual coherency from memory type properties. 6. **Diagnostic logging** in MapBuffer — slog.Debug with pointer chain for debugging. ## [0.26.7] - 2026-04-26 ### Added - **DX12: buffer state tracking** (BUG-DX12-012) — per-buffer `D3D12_RESOURCE_STATES` tracking with automatic transition barriers before copy commands. Storage buffers marked `UNORDERED_ACCESS` after compute dispatch. Implements DX12 implicit promotion rules. Follows Rust wgpu-core `BufferTracker` pattern. 11 tests, 27+ test cases. - **Pipeline overridable constants passthrough** (FEAT-COMPUTE-001) — `Constants map[string]float64` now flows from public API through HAL to all backends. Backend-specific implementation (VkSpecializationInfo, MTLFunctionConstantValues) tracked as TODOs. - **Zero-initialize workgroup memory** (FEAT-COMPUTE-002) — `ZeroInitializeWorkgroupMemory` field added to compute pipeline descriptor. Defaults to `true` per WebGPU spec. `*bool` in public API (nil = true), plain `bool` at HAL boundary. ## [0.26.5] - 2026-04-26 ### Added - **Public API: `CommandEncoder.CopyTextureToTexture`** — WebGPU spec `GPUCommandEncoder.copyTextureToTexture()`. DMA hardware copy between textures with sub-region support (Origin + Size). Uses Copy engine, not 3D engine — significantly cheaper than render pass blit. Enables dirty-region-only present path for UI frameworks. Implemented in all 6 HAL backends, now exposed on public API. - `TextureCopy` descriptor type for texture-to-texture copy regions. ## [0.26.6] - 2026-04-26 ### Added - **Compute dispatch memory barriers** (VAL-008) — automatic compute→compute memory barrier after every `Dispatch` and `DispatchIndirect` across all GPU backends. Prevents silent data corruption when consecutive dispatches share storage buffers. Vulkan: `vkCmdPipelineBarrier`, DX12: global UAV barrier, Metal: `memoryBarrierWithScope:`, GLES: already had per-dispatch barriers. Matches Rust wgpu-core `flush_bindings` pattern. - **Dispatch workgroup count validation** (VAL-009) — `Dispatch(x, y, z)` checks each dimension against `MaxComputeWorkgroupsPerDimension`. Returns clean error instead of driver crash. `(0, 0, 0)` allowed as no-op per WebGPU spec. - **CreateComputePipeline workgroup_size validation** (VAL-010) — validates each dimension against `MaxComputeWorkgroupSizeX/Y/Z` and total invocations against `MaxComputeInvocationsPerWorkgroup`. Rejects workgroup_size containing 0. ## [0.26.4] - 2026-04-25 ### Fixed - **Vulkan: missing PRESENT_SRC_KHR layout transition** (BUG-WGPU-VK-006) — swapchain image presented in `VK_IMAGE_LAYOUT_UNDEFINED` when render pass didn't directly target it (blit-only, resolve, offscreen paths) → validation error VUID-VkPresentInfoKHR-pImageIndices-01430 → flickering. Fix: track swapchain image layout per-image, insert explicit pipeline barrier before `vkQueuePresentKHR` when layout ≠ PRESENT_SRC_KHR. Fence-synchronized barrier pool prevents `vkResetCommandPool` on pending command buffers (VUID-vkResetCommandPool-commandPool-00040). Zero overhead in common case (render pass sets finalLayout). ADR-020. ## [0.26.3] - 2026-04-25 ### Fixed - **Vulkan: offscreen submit hijacks swapchain semaphores** (BUG-WGPU-VK-005) — when two GPU submits occur per frame (offscreen RepaintBoundary + compositor), the first submit consumed acquire/present semaphores meant for the compositor → race condition → flickering. Fix: `Queue.SetSwapchainSuppressed(bool)` temporarily disables semaphore binding for offscreen submits. Follows VK-004 precedent (`WriteTexture` save/restore pattern). ADR-019. ## [0.26.2] - 2026-04-25 ### Fixed - **Automatic Buffer/BindGroup cleanup via `runtime.AddCleanup`** (BUG-WGPU-RESOURCE-LIFECYCLE-001) — per-frame resources that are not explicitly Released are now automatically scheduled for deferred destruction when collected by GC. Prevents resource leaks (120 buffers/sec) that caused stuttering on Intel Iris Xe. Matches Rust wgpu `Arc` + `Drop` pattern and gogpu `Texture` `AddCleanup` pattern. Leak detection: `slog.Warn` when GC releases a forgotten resource. Explicit `Release()` still preferred and cancels the cleanup (no double-free). ADR-018. - **Interior pointer bug in `released` field** — changed `Buffer.released` and `BindGroup.released` from embedded `atomic.Bool` to heap-allocated `*atomic.Bool`. The embedded value created an interior pointer in the cleanup ref, preventing GC from ever collecting the object. ### Changed - **Zero-allocation WriteBuffer batching** — pre-allocate `[1]hal.BufferCopy` in `pendingWrites` struct to avoid heap escape through `hal.CommandEncoder` interface method call. Stack-allocate `[8]hal.BufferBarrier` for flush barriers. Batching path: 43 B/op → 19 B/op, 1 alloc → **0 allocs**. All PendingWrites hot paths now 0 allocs/op. ## [0.26.1] - 2026-04-25 ### Added - **Vulkan: damage-aware present via `VK_KHR_incremental_present`** — chains `VkPresentRegionsKHR` into `VkPresentInfoKHR.pNext` when damage rects provided. Extension probed at adapter enumeration; graceful fallback when unavailable. - **DX12: damage-aware present via `IDXGISwapChain1::Present1`** — passes `DXGI_PRESENT_PARAMETERS.pDirtyRects` to DWM compositor. Requires opt-in `EnableDamagePresent: true` in `SurfaceConfiguration` (selects `FLIP_SEQUENTIAL`). Triple guard: damage rects + FLIP_SEQUENTIAL + no tearing. Falls back to `Present()`. - **GLES: damage-aware present via `eglSwapBuffersWithDamageKHR`** (Linux only) — probes extension at init, Y-flips coordinates (EGL bottom-left → WebGPU top-left). Falls back to `eglSwapBuffers` when extension unavailable. WGL (Windows) unchanged. - `hal.SurfaceConfiguration.EnableDamagePresent` — opt-in flag for DX12 `FLIP_SEQUENTIAL`. - Zero allocations maintained: stack arrays `[8]vk.RectLayerKHR`, `[8]dxgi.RECT`, `[32]int32` for ≤8 damage rects across all backends. ## [0.26.0] - 2026-04-25 ### Added - **Damage-aware surface presentation** (#150) — first WebGPU implementation with compositor damage hints. `Surface.PresentWithDamage(texture, rects)` passes dirty rectangles to the display compositor (DWM, Wayland), allowing it to skip recompositing unchanged pixels. `Present()` remains unchanged — calls `PresentWithDamage(nil)` internally. - **Software backend:** partial `BitBlt` (Windows) and `XPutImage` (Linux) per damage rect - **All GPU backends:** accept damage rects parameter (Vulkan/DX12/GLES implementation in future phases — currently pass through to standard present) - **Zero allocations:** 0 B/op for nil path (identical to old Present), 0 B/op for ≤8 rects (stack-allocated platform struct conversion) - Coordinates: physical pixels, top-left origin (`image.Rectangle`) - Neither Rust wgpu, Dawn, nor wgpu-native support this feature ## [0.25.7] - 2026-04-24 ### Fixed - **v0.25.6 retag fix** — v0.25.6 was incorrectly retagged after Go module proxy cached the original. This release contains the correct code with all pool reset fixes. ## [0.25.6] - 2026-04-24 ### Changed - **Vulkan: command buffer free list architecture** (VK-CMD-001) — replaced single-CB-per-encoder with industry-standard free list pattern. Each `CommandEncoder` owns a pool of pre-allocated command buffers (batch 16, matching Rust `ALLOCATION_GRANULARITY`). `BeginEncoding` pops from free list, `EndEncoding` returns handle to caller, `ResetAll` recycles via `vkResetCommandPool`. Enterprise parity: Khronos, NVIDIA, ARM, Mesa, Rust wgpu-hal. ### Fixed - **Vulkan: `recyclePool` now calls `vkResetCommandPool`** before returning pool to free list. Without this, recycled pools had CBs in executable state, causing validation errors on reuse. - **`returnEncoderToPool` calls `ResetAll`** before release — ensures command pool is reset when encoder is returned on error/discard path. Matches Rust `InnerCommandEncoder::drop` (`command/mod.rs:726-738`). ### Dependencies - **naga** v0.17.5 → **v0.17.6** ## [0.25.5] - 2026-04-23 ### Changed - **WASM platform split** (WASM-001 Phase 0) — root package files split into `_native.go` / `_browser.go` variants with build tags. Browser stubs compile for `GOOS=js GOARCH=wasm` but panic at runtime (Phase 1+ adds real implementations). All `core/`, `hal/`, and `internal/` directories excluded from WASM build via `//go:build !(js && wasm)`. Native path unchanged — zero behavior differences. 304 files touched, pure structural refactoring. ## [0.25.4] - 2026-04-23 ### Added - **Late buffer binding size validation** (VAL-006) — WebGPU spec compliance: when `MinBindingSize = 0`, buffer sizes are validated at draw/dispatch time against shader requirements. Full Rust wgpu-core parity: naga IR stored on ShaderModule, `LateSizedBufferGroup` on pipelines, `LateBufferBindingInfo` on bind groups, order-independent binder tracking. 12 new tests. - **Vulkan relay semaphores** (VK-SYNC-001) — two alternating binary VkSemaphores chain consecutive `vkQueueSubmit` calls for GPU-side execution ordering. Fixes undefined behavior in WriteTexture staging→draw path. Mesa ANV bug #5508 workaround. Rust wgpu-hal parity. ### Dependencies - **naga** v0.17.4 → **v0.17.5** — `ir.TypeSize()` for buffer size reflection (VAL-006) ## [0.26.9] - 2026-04-30 ### Added - **WebGPU validation Phase A — crash prevention** (ADR-VALIDATION-PHASES) — 18 P0 checks matching Rust wgpu-core, preventing GPU crashes from invalid API usage: **Queue.WriteBuffer bounds** (VAL-A1): - Buffer not mapped, COPY_DST usage required, offset/size 4-byte alignment, bounds check **CreateBindGroup buffer validation** (VAL-A2): - Buffer usage matches binding type (UNIFORM/STORAGE), offset alignment (minUniformBufferOffsetAlignment/minStorageBufferOffsetAlignment), binding size limits, offset+size bounds, zero-size rejection, storage 4-byte alignment **Draw-time state validation** (VAL-A3): - 12 typed sentinel errors with `errors.Is()` support: `ErrDrawMissingPipeline`, `ErrDrawMissingBindGroup`, `ErrDrawIncompatibleBindGroup`, `ErrDrawMissingVertexBuffer`, `ErrDrawMissingIndexBuffer`, `ErrDrawMissingBlendConstant`, `ErrDrawLateBufferTooSmall`, `ErrDispatchMissingPipeline`, `ErrDispatchMissingBindGroup`, `ErrDispatchIncompatibleBindGroup`, `ErrDispatchLateBufferTooSmall`, `ErrDispatchWorkgroupCountExceeded` - `EncoderStateError.Unwrap()` — error chain preserved through Finish() **CreatePipelineLayout validation** (VAL-A4): - Bind group layout count vs `MaxBindGroups` limit **Format type guards** (VAL-A5): - Color target must be color format (not depth/stencil) - Depth/stencil attachment must be depth/stencil format (not color) **Queue.Submit resource validation** (VAL-A6): - Referenced buffers not destroyed, not mapped - Referenced textures not destroyed - Double-submit prevention - Resource tracking during encoding (SetBindGroup, SetVertexBuffer, SetIndexBuffer, etc.) - `CreatePipelineLayoutError` typed error with `errors.As()` support - `CreateBindGroupError` extended with 6 buffer validation error kinds - `CreateRenderPipelineError` extended with color/depth format guard error kinds ### Dependencies - **naga** v0.17.6 → **v0.17.8** ### Research - **Validation Gap Analysis** — 121-check comparison vs Rust wgpu-core. Coverage: 22% → ~37% after Phase A. - **Validation Phases ADR** — phased implementation plan (A: crash prevention, B: correctness, C: spec compliance) ## [0.25.4] - 2026-04-23 ### Fixed - **Vulkan: GetTimestampPeriod returned hardcoded 1.0** — now reads `VkPhysicalDeviceLimits.TimestampPeriod` from the physical device at init. Previously all timestamp durations were incorrect on AMD/NVIDIA GPUs (Intel typically has period=1.0, but others vary). ## [0.25.3] - 2026-04-23 ### Fixed - **SIGSEGV on large Queue.WriteBuffer** (#142) — `copyToMappedMemory` crashed when writing data larger than `maxMemoryAllocationSize` (e.g., 544MB on Lavapipe with 256MB limit). Root cause: `vkAllocateMemory` silently fails on oversized requests; the staging belt then wrote past the valid mapped region. Four fixes applied: 1. **Query and enforce `maxMemoryAllocationSize`** (Vulkan 1.1 core) — allocator rejects allocations exceeding the driver limit with `ErrAllocationTooLarge` 2. **Chunk large writes** — staging belt caps individual staging buffers at 64MB (Rust wgpu parity: `1 << 26`), automatically splits larger writes into multiple CopyBufferToBuffer 3. **MappedPtr null check** — `ensureMemoryMapped` returns error if `vkMapMemory` returns null 4. **MappedSize bounds check** — `WriteBuffer` verifies `offset + len(data) <= MappedSize` before `copyToMappedMemory`, turning SIGSEGV into a clear error message ### Added - `hal.MaxStagingBufferSizer` optional interface — backends can advertise their maximum staging buffer size. Vulkan returns `min(64MB, maxMemoryAllocationSize)`. Non-implementing backends default to 64MB. - `memory.ErrAllocationTooLarge` sentinel error for allocations exceeding device limits. - `MemoryBlock.MappedSize` field for bounds checking mapped memory access. ## [0.25.2] - 2026-04-21 ### Dependencies - **gputypes** v0.4.0 → **v0.5.0** — PrimitiveState zero value = WebGPU spec default (breaking enum renumber in gputypes). Go zero-init now produces correct defaults without explicit field assignment. Aligns with gpucontext v0.13.0. ## [0.25.1] - 2026-04-21 ### Added - **Software backend: Linux X11 windowed presentation** — `blitFramebufferToWindow` via `XPutImage` (Skia enterprise pattern). Lazy-loads libX11.so via goffi on first blit. Constructs XImage on stack pointing to BGRA framebuffer — no pixel format conversion needed (BGRA = X11 ZPixmap LSBFirst on little-endian). Graceful fallback: if libX11 is unavailable (headless CI), blit silently no-ops. ### Changed - **Software Surface: store display handle** — `CreateSurface(display, window)` now stores both handles. Required for X11 `XPutImage` which needs Display* in addition to Window ID. ## [0.25.0] - 2026-04-21 ### Added - **WebGPU-compliant Buffer mapping API** (FEAT-WGPU-MAPPING-001) — `Buffer.Map(ctx)`, `Buffer.MapAsync`, `Buffer.MappedRange`, `Buffer.Unmap`, `Buffer.MapState`, and `Device.Poll(PollType)`. Follows WebGPU spec §5.3 with a Go-idiomatic dual-layer design: a primary blocking path driven by `context.Context` (the 95% path) and an async escape hatch (`MapAsync` + `MapPending` + `Device.Poll(PollPoll)`) for game loops and event-driven renderers. Both layers are zero-allocation in steady state (benchmarked: `0 B/op, 0 allocs/op` on both `BenchmarkBufferMapReadPrimary` and `BenchmarkBufferMapAsyncEscapeHatch`). Example — primary blocking path (matches `database/sql` idiom): ```go if err := buf.Map(ctx, wgpu.MapModeRead, 0, size); err != nil { return err } defer buf.Unmap() rng, _ := buf.MappedRange(0, size) data := rng.Bytes() ``` Example — async escape hatch (for frame-budgeted readback): ```go pending, _ := buf.MapAsync(wgpu.MapModeRead, 0, size) // ... continue rendering other frames ... device.Poll(wgpu.PollPoll) if ready, _ := pending.Status(); ready { rng, _ := buf.MappedRange(0, size) handle(rng.Bytes()) buf.Unmap() } ``` Typed error taxonomy: `ErrMapAlignment`, `ErrMapAlreadyPending`, `ErrMapAlreadyMapped`, `ErrMapNotMapped`, `ErrMapRangeOverlap`, `ErrMapRangeOverflow`, `ErrMapRangeDetached`, `ErrMapInvalidMode`, `ErrMapCanceled`, `ErrBufferDestroyed`, `ErrMapDeviceLost` — all usable with `errors.Is`. Core state machine mirrors Rust wgpu-core `BufferMapState` + `LifetimeTracker::triage_submissions` (`wgpu-core/src/device/life.rs:319`). A per-device `pendingMaps` map tracks which buffers are waiting on which submission index; `Device.Poll` drains completed submissions and issues the HAL `MapBuffer` call. `Queue.Submit` auto-polls at its tail (matching Rust `queue.rs:1429-1431`) so beginner code paths never have to call `Device.Poll` explicitly. Safety (all CVE classes from the WebGPU real-world history are mitigated): - **Destroy during pending map** (CVE-2026-5281 Dawn UAF pattern) → state transitions to `BufferMapStateDestroyed` under the buffer mutex; Poll triage checks it before calling HAL. - **Unmap during pending map** → `ErrMapCanceled` signalled to waiter. - **Overlapping MappedRange** → `ErrMapRangeOverlap` (spec §5.3.4). - **UAF after Unmap** → per-buffer atomic generation counter; stale `MappedRange.Bytes()` returns nil rather than exposing freed memory. - **Alignment** → WebGPU `MAP_ALIGNMENT=8`, size multiple of 4, checked synchronously. - **Concurrent Device.Poll** → safe (mutex-protected `pendingMaps`, short critical section dropped around each HAL call). - HAL interface now exposes `Device.MapBuffer(buf, offset, size) (BufferMapping, error)` and `Device.UnmapBuffer(buf)`. All 6 backends (DX12, Vulkan, Metal, GLES, software, noop) implement them. DX12 persists mapped pointers across calls; Vulkan returns the cached `VkDeviceMemory` map pointer; Metal calls `MTLBuffer.contents()`; GLES uses a CPU shadow slice plus `glMapBuffer` fallback; software returns a slice pointer; noop reuses its in-memory backing storage. - **GLES: swapchain offscreen FBO + Present Y-flip blit** (BUG-GLES-YFLIP-001) — added the missing swapchain FBO architecture matching Rust wgpu-hal/gles. Previously, non-MSAA render-to-surface paths rendered upside-down because GLES Y-axis is inverted vs WebGPU. Now all GLES rendering goes through an offscreen FBO with a Y-flipping blit at Present time. - **DX12: in-process DXIL validation + BindingMap plumbing** — naga IR → DXIL direct compilation pipeline with register binding remapping matching the root signature layout. Enables `GOGPU_DX12_DXIL=1` for compute and graphics pipelines. - **DX12: sampler heap plumbing for DXIL** (BUG-DXIL-028) — forward `SamplerBufferBindingMap` and `SamplerHeapTargets` from HLSL options to DXIL options so texture/sampler pipelines match the root signature. Without this, any DXIL shader using textures/samplers got `E_INVALIDARG` from `CreateGraphicsPipelineState`. - **DX12: pipeline error logging** — `CreateGraphicsPipelineState` and `CreateComputePipelineState` failures now log via `slog.Error` with pipeline label, entry points, and HRESULT. Previously errors were silently swallowed because `hal.Logger()` defaults to nop. - **DX12: headless triangle example** — `examples/triangle-headless` for DXIL debugging with `GOGPU_DX12_DXIL_OVERRIDE_VS` / `GOGPU_DX12_DXIL_OVERRIDE_PS` env-var hooks. - **DX12: ID3D12InfoQueue.GetMessage** now accepts `S_FALSE` on size query (was incorrectly treated as error). ### Fixed - **DX12 compute readback data race** — the removed `Queue.ReadBuffer` mapped the staging buffer before the GPU finished the `CopyBufferToBuffer`, so `compute-copy`/`compute-sum` returned all zeros on DX12. The new state-machine-driven mapping path waits on the submission fence via `Device.Poll` before calling HAL `MapBuffer`, fixing the race at the root. Verified: `GOGPU_GRAPHICS_API=dx12 go run ./examples/compute-copy` returns 1024/1024 matches; `compute-sum` CPU sum matches GPU sum. ### Removed (breaking) - `Queue.ReadBuffer` removed from both the public `wgpu.Queue` API and the `hal.Queue` interface; all 6 backends dropped their `ReadBuffer` implementation. Callers migrate to the WebGPU spec-compliant `Buffer.Map(ctx, wgpu.MapModeRead, 0, size) + MappedRange + Unmap` flow. Migration — before: ```go data := make([]byte, size) queue.ReadBuffer(buf, 0, data) ``` Migration — after: ```go if err := buf.Map(ctx, wgpu.MapModeRead, 0, size); err != nil { return err } defer buf.Unmap() rng, _ := buf.MappedRange(0, size) copy(data, rng.Bytes()) ``` `Queue.WriteBuffer` is **kept** — it is legitimate `GPUQueue.writeBuffer` from the WebGPU spec and is distinct from mapping. - All three in-repo examples (`compute-copy`, `compute-sum`, `compute-particles`) migrated to the new API. Downstream repos (`gogpu/gg`, `gogpu/gogpu`, `gogpu/ui`, `gogpu/g3d`) have separate migration tasks in their kanban. ### Dependencies - **naga** v0.17.3 → **v0.17.4** — DXIL full rendering (55 commits: DCE pass, inline pass, mem2reg, phi promotion, sampler heap config, CBV struct layout, register packing). First Pure Go DXIL generator that renders full 2D applications (text, SDF shapes, widgets). ## [0.24.7] - 2026-04-11 ### Fixed - **Software backend: DWM freeze after window resize** — replaced `GetDC` + `StretchDIBits` (raw pixel pointer) with `CreateDIBSection` + `BitBlt` (GDI-managed bitmap). The old approach caused DWM compositor to stop updating the window after resize because DWM does not track raw GDI paints from non-UI threads. The new approach follows the SDL3/Qt6 enterprise pattern: a DIB section is created at `Configure` time, the render pipeline writes directly into its pixel buffer (zero-copy), and `Present` does `BitBlt` from the memory DC. (#133) ### Changed - **Software backend: skip redundant clear for fullscreen blit** — `applyClear` is no longer called before `executeFullscreenBlit` since the blit overwrites every pixel. Saves ~18% CPU at fullscreen resolution. - **Software backend: optimized scaled blit** — pre-computed column map eliminates per-pixel multiply+divide; row deduplication skips ~50% of pixel loops when upscaling during resize. - **Software backend: format-aware Clear** — `Texture.Clear` now writes bytes in the texture's native format (BGRA for BGRA textures), fixing wrong colors in clear-only frames. - **Software backend: proper WriteTexture** — respects `dst.Origin`, `layout.BytesPerRow`, `layout.Offset`, and `size` for correct partial texture updates. ### Dependencies - **naga** v0.17.1 → **v0.17.3** — SPIR-V OpIMul→OpVectorTimesScalar fix, DXIL hardening - **x/sys** v0.42.0 → **v0.43.0** ## [0.24.6] - 2026-04-08 ### Added - **Metal: depth texture support in render pass** — `DepthStencil` descriptor now applied on Metal: depth attachment pixel format, `MTLDepthStencilState` with compare function and write enable, depth bias parameters. Set on render encoder via `setDepthStencilState:` / `setDepthBias:slopeScale:clamp:`. (PR #136 by @jdbann) ### Changed - **naga** v0.17.0 → **v0.17.1** — SPIR-V Workgroup ArrayStride fix (text invisible on Adreno Vulkan) ## [0.24.5] - 2026-04-08 ### Fixed - **Metal: cull mode and front face winding** — `RenderPipelineDescriptor.Primitive.CullMode` and `FrontFace` were not applied on Metal. Now stored in `RenderPipeline` and set on the render encoder via `setCullMode:` / `setFrontFacingWinding:` at `SetPipeline` time. (PR #132 by @jdbann) ## [0.24.4] - 2026-04-08 ### Added #### Software Backend — Enterprise Windowed Rendering - **Present() auto-blits framebuffer via GDI** — Same pattern as `vkQueuePresentKHR` (Vulkan), `presentDrawable` (Metal), `swapChain.Present` (DX12). Framebuffer is BGRA after `executeFullscreenBlit` swizzle — passed directly to GDI `StretchDIBits`, zero conversion needed. Headless (hwnd=0) remains no-op. gogpu calls `Present()` identically for all backends — no backend-specific knowledge required. - **Core routing for software surface** — `ensureHALSurface` auto-swaps HAL surface when device backend differs from initially created one. `halInstanceMap` for backend-to-instance lookup. `FilterBackendsByMask` includes software as fallback for all masks; `RequestAdapter` prefers GPU, software only if no GPU or `ForceFallbackAdapter` set. - **allbackends registers software** instead of noop — software backend is production fallback, noop is testing-only. - **GetFramebuffer BGRA→RGBA normalization** — callers always receive RGBA for consistent readback API regardless of surface format. - **Software triangle example** — `cmd/sw-triangle` renders red triangle on blue background using CPU rasterizer + GDI blit. ~30 FPS at 800×600. - **Float32x3 vertex color support** — `hasVertexColors` recognizes RGB (Float32x3) in addition to RGBA. Vertex attributes padded to 4 components (alpha=1.0). - **Adapter selection logging** — `RequestAdapter` logs selected adapter name, backend, and device type via slog. ### Fixed #### GLES - **X11 display use-after-close in eglInitialize** — `GetEGLDisplay()` closed X11 display connection (`defer owner.Close()`) before returning. EGL display referenced the closed X11 → SIGSEGV in `eglInitialize` on Linux. `DisplayOwner` now stored in `Context` and closed after `eglTerminate` in `Destroy()`. Matches Rust wgpu-hal where `DisplayOwner` lives in Instance, `XCloseDisplay` only in `Drop`. (BUG-GLES-001) ## [0.24.2] - 2026-04-07 ### Fixed #### Metal - **Cross-group sequential slot offsets in SetBindGroup** — `SetBindGroup` reset buffer/texture/sampler slot counters to 0 on each call, but naga MSL generates `[[buffer(N)]]` with cross-group sequential numbering (group(0)→`[[buffer(0)]]`, group(1)→`[[buffer(1)]]`). PipelineLayout now stores cumulative `GroupSlotOffsets` per bind group. Fixes SDF shapes invisible on Metal (gg#171). Matches Rust wgpu-hal `base_resource_indices` pattern. (BUG-METAL-002) ## [0.24.1] - 2026-04-07 ### Fixed #### Metal - **Actual GPU completion tracking via addCompletedHandler** — `PollCompleted()` returned a conservative heuristic (`submissionIndex - maxFramesInFlight`) causing `maintain()` to recycle staging belt chunks before GPU finished using them. Now uses `atomic.Uint64` updated by Metal completion handler — same pattern as Rust wgpu-hal `Fence.completed_value`. (BUG-METAL-001 Fix #2) #### Core - **Command encoder pool — recycle HAL encoders after GPU completion** — Each `CreateCommandEncoder()` created a new DX12 `ID3D12CommandAllocator` (~64KB) that leaked after `Finish()`. Device-level pool acquires/releases encoders, matching Rust wgpu-core `CommandAllocator` pattern. Encoder travels: Pool → CommandEncoder → CommandBuffer → Submit → GPU done → ResetAll → Pool. Fixes ~7.5 MB/min memory leak on DX12. (BUG-DX12-004) - **Unified single encoder pool per device** — User command encoders and PendingWrites internal staging encoders now share single `encoderPool`, matching Rust wgpu-core single `device.command_allocator` (allocator.rs). PendingWrites borrows pool reference, does not own or destroy it. (CLEANUP-ENCODER-003) - **CommandBuffer.Release()** — Explicit cleanup for non-submitted command buffers. Returns HAL encoder to pool and drops tracked resource refs. Matches Rust `InnerCommandEncoder::Drop` (command/mod.rs:726-738). A CommandBuffer must be either Submit()'d or Release()'d to avoid encoder leak. (CLEANUP-ENCODER-001) - **Vulkan VkCommandPool double-free fix** — In pool-managed mode, CommandBuffer no longer carries VkCommandPool ref (cb.pool=0). Encoder exclusively owns pool. Prevents triple-free at shutdown (FreeCommandBuffer + encoder.Destroy + destroyAllocators). (BUG-DX12-004) - **Shutdown order: WaitIdle → Triage → FlushAll → pool.destroy → hal.Destroy** — WaitIdle ensures PollCompleted returns final index so all deferred encoder recycling callbacks fire before pool destruction. Prevents vkDestroyCommandPool crash on shutdown. (BUG-DX12-004) - **All WriteBuffer through staging + DX12 HEAP_TYPE_CUSTOM** — MapWrite buffers bypassed PendingWrites with direct memcpy, causing data races when CPU overwrites while GPU reads (texture flickering on Metal). Now ALL WriteBuffer goes through staging belt on all backends. DX12 MapWrite buffers switched from `D3D12_HEAP_TYPE_UPLOAD` (GENERIC_READ, can't be copy destination) to `D3D12_HEAP_TYPE_CUSTOM` with `WRITE_COMBINE` + `COMMON` state (allows implicit promotion to COPY_DST). Matches Rust wgpu-hal `suballocation.rs:437-464`. Readback buffers also use CUSTOM heap with WRITE_BACK. (BUG-METAL-001 Fix #1) ## [0.24.0] - 2026-04-06 ### Added #### DX12 - **DXIL direct compilation path** — DX12 HAL can now compile shaders via naga DXIL backend (`GOGPU_DX12_DXIL=1`), bypassing HLSL→FXC entirely. Generates LLVM 3.7 bitcode with dx.op intrinsics wrapped in DXBC container with BYPASS hash. SM 6.0+, zero external dependencies (no d3dcompiler_47.dll). First Pure Go DXIL generator. Integrated with existing shader cache. HLSL→FXC remains default. (TASK-WGPU-DXIL-001) ### Changed - **naga v0.16.6 → v0.17.0** — DXIL backend (12,475 LOC, 190 tests). Direct LLVM 3.7 bitcode generation from naga IR. Vertex + fragment shaders, SM 6.0, BYPASS hash. Dead code cleanup (flattenBinding removed). ## [0.23.9] - 2026-04-05 ### Performance - **BuddyAllocator thread-safe + slice-based free lists** — Added mutex for concurrent safety (fixed crash in parallel benchmark). Replaced map-based free lists with slices and bitset. 3.3× faster Alloc, 2× faster Free. (PERF-BUDDY-001) - **SnatchGuard zero heap allocation** — Return guards as value types instead of pointers. Eliminates 1 heap alloc per HAL call on hottest path (~3000 allocs/sec eliminated at 60 FPS). Snatchable.Get: 82→42 ns/op. (PERF-SNATCH-001) - **Vulkan debug name reuse** — Reuse byte buffer for null-terminated label strings. Embed BuddyBlock as value. Saves 2-3 allocs per object creation. (PERF-VK-001) - **PendingWrites map bucket reuse** — Use `clear()` instead of `make()` after flush to keep allocated hash table buckets. WriteBufferBatching: 354→155 ns/op, 2→1 allocs. (PERF-PW-001) - **Registry capped growth** — Changed from unbounded 2× doubling to capped 1.5× with max 1024 extra slots. Peak allocation: 324KB→193KB. (PERF-REG-001) ### Changed - **naga v0.16.4 → v0.16.6** — 4.9× fewer allocs for SPIR-V backend, TypeRegistry zero-alloc lookups, lexer/parser pre-sizing. Overall: 594→562 allocs/op. ## [0.23.8] - 2026-04-05 ### Fixed #### Metal - **Descending vertex buffer indices** — Metal vertex, uniform and storage buffers share the same index range. Vertex buffers now use descending indices from the end of the range (`maxVertexBuffers - 1 - slot`) to avoid collisions with uniform/storage buffers assigned from the start. Matches Rust wgpu-hal pattern. Contributed by @jdbann. (gogpu/gogpu#165) #### GLES - **Per-type sequential binding counters** — Replaced hardcoded `group*16+binding` formula with per-type sequential counters (samplers, textures, images, uniform buffers, storage buffers) computed at PipelineLayout creation. Fixes binding collision when >16 bindings per group. Removed all `maxBindingsPerGroup=16` constants. Matches Rust wgpu-hal `device.rs:1154-1221`. (GLES-001) - **StagingBelt configurable alignment** — Default alignment changed from 16 to 8 bytes (Rust wgpu `MAP_ALIGNMENT` parity). Alignment now configurable per-belt. WebGPU `COPY_BUFFER_ALIGNMENT` is 4. (TASK-WGPU-BELT-002) #### Core - **Default limits when RequiredLimits is zero struct** — `RequestDevice()` with a descriptor containing zero-value `RequiredLimits` caused all device limits to be 0, rejecting all bind group layouts ("binding count N exceeds maximum 0"). Now detects zero struct and falls back to WebGPU spec defaults. Matches Rust wgpu `DeviceDescriptor::default()` behavior. (BUG-WGPU-LIMITS-001) - **PowerPreference fallback per WebGPU spec** — `RequestAdapter()` with `PowerPreference: HighPerformance` on systems with only integrated GPU returned error instead of falling back. WebGPU spec: powerPreference is a hint, "must not cause requestAdapter() to fail if there is at least one available adapter." Now uses two-pass selection: prefer matching, fall back to any GPU. Matches Rust wgpu sort-not-filter approach. (BUG-WGPU-ADAPTER-002) - **Device inherits adapter limits instead of WebGPU defaults** — `RequestDevice()` with empty `RequiredLimits` returned WebGPU spec minimums (e.g., 8 storage buffers) instead of the adapter's actual capabilities (e.g., 200 on Intel Iris Xe). Blocked gg Vello coarse shader (9 storage buffer bindings). Device now inherits adapter limits when no explicit limits requested. (BUG-WGPU-LIMITS-002) ## [0.23.7] - 2026-04-04 ### Changed - **naga v0.16.1 → v0.16.4** — HLSL 72/72 parity, ForceLoopBounding, per-element workgroup array zero-init (330× faster FXC for arrays ≥256). GLSL same fix for GL driver compiler slowdown. ## [0.23.6] - 2026-04-04 ### Added #### Core - **Deferred resource destruction (ResourceRef + DestroyQueue)** — All GPU resources (Buffer, Texture, TextureView, BindGroup, Pipeline, Sampler, etc.) now defer HAL destruction until the GPU completes the submission that was active when `Release()` was called. Prevents use-after-free crashes on DX12 (TDR) and validation errors on Vulkan when resources are released while the GPU is still rendering with them. Implements Go equivalent of Rust wgpu-core's `LifetimeTracker` pattern with `ResourceRef` (atomic refcount, Go analog of Rust `Arc`) and `DestroyQueue` (submission-scoped triage). (TASK-WGPU-CORE-LIFETIME-001) - **Per-command-buffer resource tracking** — Command encoders now Clone() ResourceRef for every resource bound during render/compute pass encoding (SetVertexBuffer, SetBindGroup, SetPipeline, etc.). Refs transfer through `End()` → `Finish()` → `Submit()` → `DestroyQueue.TrackSubmission()` and are Drop()'d when GPU completes the submission. Matches Rust wgpu-core `EncoderInFlight` pattern where `Arc` refs in trackers keep resources alive. (TASK-WGPU-CORE-LIFETIME-002) - **Unified destruction mechanism** — Migrated TextureView and BindGroup from duplicate `pendingWrites` deferred mechanism to `core.DestroyQueue`. All 9 resource types now use a single destruction path. Removed 234 lines of duplicate code. (TASK-WGPU-CORE-LIFETIME-003) #### DX12 - **In-memory HLSL→DXBC shader cache** — Caches FXC compilation results keyed by SHA-256(HLSL source) + entry point + stage + target profile. 30 pipelines sharing 8 unique shaders → 8 FXC calls instead of 30. LRU eviction at 200 entries retaining last 100. Matches Rust wgpu ShaderCache pattern (wgpu-hal/src/dx12/mod.rs:1136). Fixes DEVICE_HUNG on first frame for complex UI (Gallery with 15-30 PSOs). (TASK-DX12-PSO-CACHE-001) - **DRED diagnostics (Device Removed Extended Data)** — When DX12 debug mode is enabled (`InstanceFlagsDebug`), auto-breadcrumbs and page fault tracking are activated. On TDR/device removal, logs which GPU command was executing (breadcrumb context window around hang point) and which allocation was accessed (use-after-free detection via recently freed allocations list). Provides enterprise-level GPU crash diagnostics not available in Rust wgpu. (TASK-DX12-DRED-001) ### Fixed #### Core - **Deferred resource destruction — use-after-free fix** — `Buffer.Release()`, `Texture.Release()`, and all other resource `Release()` methods were calling HAL destroy immediately while the GPU was still using the resource. On DX12 this caused TDR (DEVICE_HUNG) after ~300-700 frames in gallery app. Root cause: missing LifetimeTracker after wgpu core API migration (v0.21.0). Now all 9 resource types defer destruction via `core.DestroyQueue` until GPU completes the associated submission. (TASK-WGPU-CORE-LIFETIME-001) ## [0.23.5] - 2026-04-04 ### Fixed #### GLES - **ADJUST_COORDINATE_SPACE for correct gl_FragCoord Y convention** — GLES backend was missing naga's `ADJUST_COORDINATE_SPACE` flag, causing `gl_FragCoord.y` to use OpenGL convention (Y=0 at bottom) instead of WebGPU (Y=0 at top). This broke `rrect_clip_coverage()` in fragment shaders and required a fragile manual scissor Y-flip. Now matches Rust wgpu-hal GLES with 4 coordinated changes: naga Y-flip in vertex shader, scissor pass-through, front face CW↔CCW swap, and MSAA resolve blit Y-flip for presentation. Fixes invisible scrollbar and dividers in UI on GLES. (BUG-GLES-SCROLLBAR-001) - **Normalized vertex format support (unorm/snorm)** — `vertexFormatToGL()` was missing `Unorm8x4`, `Snorm8x4`, `Unorm16x2`, `Snorm16x2` etc. These fell back to float formats (16 bytes instead of 4), causing incorrect per-vertex color rendering. Added all normalized variants and `normalized=true` parameter in `glVertexAttribPointer`. Required for text batching per-vertex color (unorm8x4). #### Vulkan - **Fence pool recycling before allocation** — `fencePool.signal()` was not calling `maintain()` before allocating a fence, causing signaled fences to accumulate in the active list instead of being recycled to the free list. On the binary fence path (Vulkan 1.0/1.1 without timeline semaphores), every `vkQueueSubmit` created a new `VkFence` via `vkCreateFence`. On NVIDIA Linux drivers, each `VkFence` consumes a file descriptor — ~1000 unrecycled fences exhaust the default FD limit and crash with `VK_ERROR_OUT_OF_HOST_MEMORY`. Now calls `maintain()` before allocation, matching Rust wgpu-hal `Queue::submit`. No impact on timeline semaphore path (Vulkan 1.2+). (VK-SYNC-002) ### Added - **Blend constant draw-time validation** — `Draw`, `DrawIndexed`, `DrawIndirect`, and `DrawIndexedIndirect` now validate that `SetBlendConstant()` has been called when the current pipeline uses `BlendFactorConstant` or `BlendFactorOneMinusConstant`. Without this, the GPU uses undefined blend constant values, causing silent rendering errors. Pipeline creation scans color targets to detect constant blend factor usage. Matches Rust wgpu-core `OptionalState` pattern and `DrawError::MissingBlendConstant`. (VAL-005) ### Changed - **gputypes v0.3.0 → v0.4.0** — adds `BlendComponent.UsesConstant()` used by blend constant validation (VAL-005). - **naga v0.16.0 → v0.16.1** — SPIR-V backend 164/164 validation pass (100%). Fixes atomics, barriers, images, pointer spill, binding-arrays, depth sampling, integer ops, matrix decomposition. ## [0.23.4] - 2026-04-02 ### Fixed #### GLES - **Texture completeness for non-mipmapped textures** — `GL_TEXTURE_MAX_LEVEL` defaulted to 1000, making single-mip textures incomplete (invisible text, missing UI elements). Now set to `MipLevelCount-1`. Texture uploads use `glTexSubImage2D` after pre-allocated `glTexImage2D` storage, matching Rust wgpu-hal pattern. (BUG-GLES-TEXT-001) - **DYNAMIC_DRAW for all writable buffers** — `GL_STATIC_DRAW` was used for non-MAP_READ buffers. Some vendors (Intel) take the hint literally, causing stale data on frequently rewritten uniform/storage buffers. Now uses `GL_DYNAMIC_DRAW` for all non-read-only buffers, matching Rust wgpu-hal. #### DX12 - **Deferred BindGroup/TextureView descriptor destruction** — Root cause of DX12 TDR (GPU timeout) with `maxFramesInFlight=2`: descriptors in SRV/sampler heaps were freed immediately on `Release()` while the GPU was still referencing them. Descriptors now tracked via `AddPendingRef`/`DecPendingRef` and freed only after GPU fence confirms completion. (BUG-DX12-007) - **Staging SRV/sampler heap descriptor recycling** — Heap descriptor slots were not returned to the free list after GPU completion, causing gradual exhaustion. (BUG-DX12-008) - **Texture initial state COMMON instead of COPY_DEST** — DX12 textures were created in `D3D12_RESOURCE_STATE_COPY_DEST`. Correct initial state is `D3D12_RESOURCE_STATE_COMMON`, which is implicitly promotable to any read state. (BUG-DX12-009) - **Buffer barriers after PendingWrites copies** — Buffers stayed in `COPY_DEST` state after staging copies, causing undefined behavior on subsequent shader reads. Added `COPY_DEST -> VERTEX/INDEX/CONSTANT/SRV` transition barriers after copy commands. (BUG-DX12-010) ### Added - **GLES: SamplerBindMap for combined texture-sampler binding** — GLES lacks separate texture and sampler objects. WGSL `texture_sample(t, s, uv)` now correctly maps to combined GLSL `sampler2D` via `SamplerBindMap` derived from naga `TextureMappings`. Matches Rust wgpu-hal GLES architecture. - **DX12: GPU-based validation** — `InstanceFlagsValidation` enables D3D12 GPU-based validation (GBV) for catching shader-level resource access errors. - **DX12: encoder pool (Rust wgpu-core CommandAllocator pattern)** — Command allocators are pooled and recycled after GPU fence completion instead of allocated per-encoder. Reduces DX12 memory churn. - **StagingBelt ring-buffer allocator (Rust wgpu util::StagingBelt pattern)** — Replaces per-WriteBuffer staging buffer creation with bump-pointer sub-allocation from reusable 256KB chunks. Zero heap allocations in steady state (0 allocs/op, 22ns — 15× faster than per-write staging). Oversized writes (> chunkSize) fall back to one-off buffers. Chunks recycled after GPU completion via recall(). - **Instance flags propagation** — `InstanceFlags` (debug layer, validation) now propagated from `wgpu.CreateInstance` through to HAL backends. ### Changed - **naga v0.15.2 → v0.16.0** — GLSL TextureMappings for SamplerBindMap, 34 SPIR-V validation fixes (spirv-val 52% → 73%), depth texture combined sampler fix. ## [0.23.3] - 2026-04-01 ### Fixed - **GLES: blurred text on Qualcomm Adreno** — Unconditional `GL_LINEAR` texture defaults caused blurry font rendering when sampler override was incomplete. Now aligned with Rust wgpu: only set `GL_NEAREST` for non-filterable formats (integer, depth, 32-bit float), let sampler objects control filterable textures. - **DX12/Vulkan/Metal: PendingWrites batching (Rust wgpu-core pattern)** — `WriteBuffer`/`WriteTexture` previously did per-call staging→submit→WaitIdle (20+ GPU round-trips per frame). Now batched into a single shared command encoder, flushed once at `Queue.Submit`. Staging buffers freed asynchronously via fence tracking. Reduces DX12 submits from 120 to ~10 per frame. ### Added - **Enterprise logging system (Rust wgpu parity)** — Comprehensive diagnostic logging across DX12 and GLES backends, matching Rust wgpu's tracing patterns: - **DX12**: adapter capabilities (ResourceBindingTier, TiledResourcesTier), descriptor heap creation, pipeline layout, HLSL compilation preview, pipeline creation timing, submit/present timing, fence signal timing, descriptor heap exhaustion errors, texture label in error logs - **GLES**: GL_VENDOR/GL_RENDERER/GL_VERSION at device init, generated GLSL source preview, shader compile/link info log on success (driver warnings), texture creation, sampler creation/binding, WriteTexture, pipeline timing - Enable with `GOGPU_LOG=debug` or `GOGPU_WGPU_LOG=debug` ## [0.23.2] - 2026-03-31 ### Fixed - **DX12: vertex input semantic mismatch** — Changed input layout semantic from `TEXCOORD{N}` to `LOC{N}` to match naga HLSL output. DX12 validates exact semantic name match between shader and input layout. Previous mismatch caused `CreateGraphicsPipelineState` to fail with `E_INVALIDARG` on all render pipelines. - **GLES: texture sampling broken — BindingMap not passed to GLSL compiler** — Without BindingMap, naga GLSL emitted default `layout(binding=0)` for all samplers. Runtime bound textures at `group*16+binding` (unit 1+). Shader sampled unit 0 (empty) → invisible text and textures on all GLES backends. - **GLES (Linux): WriteTexture used wrong internalFormat** — Linux path discarded `internalFormat` from `textureFormatToGL()` and passed `GL_TEXTURE_2D` (0x0DE1) as internal format to `glTexImage2D`. Texture upload silently failed. - **GLES: missing GL_UNPACK_ALIGNMENT for R8 textures** — Added `glPixelStorei(GL_UNPACK_ALIGNMENT, 1)` before R8 uploads to prevent corrupted font glyphs on non-power-of-2 widths. - **DX12: proper sampler heap** — Implemented global sampler pool + per-group sampler index buffers (matching Rust wgpu-hal architecture). Deferred HLSL compilation with SamplerBufferBindingMap. Fixes invisible text/textures on DX12. ### Known Issues - **DX12: rendering noticeably slower than Vulkan/GLES** — Needs profiling. Possible causes: HLSL compilation overhead, descriptor heap allocation, sync. Tracked in PERF-DX12-001. ## [0.23.0] - 2026-03-30 ### Added - **`Adapter.GetSurfaceCapabilities(surface)`** — New public API. Returns supported texture formats, present modes, and composite alpha modes for a surface. Queries HAL capabilities (`vkGetPhysicalDeviceSurfacePresentModesKHR` on Vulkan). Follows Rust wgpu `surface.get_capabilities(&adapter)` pattern. - **`Queue.Poll()`** — Non-blocking completion query. Returns the highest submission index known to be completed by the GPU. ### Changed - **Enterprise fence architecture** — HAL `Queue.Submit()` no longer accepts user fence parameter. Returns `(submissionIndex uint64, err error)` instead. HAL owns all fence management internally (binary fence pool or timeline semaphore). Matches Rust wgpu, Dawn, Godot, DXVK, vkd3d-proton architecture. All 6 backends updated (Vulkan, DX12, Metal, GLES, Software, Noop). Eliminates double `vkQueueSubmit` on binary fence path that caused first-frame loss on llvmpipe Vulkan 1.0.2. (BUG-GOGPU-004, fixes ui#66) - **`Queue.Submit()` is now non-blocking** — Returns `(uint64, error)` with submission index for deferred resource tracking. Use `Queue.Poll()` to check completion. - **deps: naga v0.14.8 → v0.15.0** — Full Rust parity: IR 144/144, SPIR-V 87/87, MSL 91/91, HLSL 58/58, GLSL 68/68. ### Removed - **`Queue.SubmitWithFence()`** — Replaced by `Queue.Submit()` + `Queue.Poll()`. HAL manages fences internally, application layer uses submission indices. ## [0.22.2] - 2026-03-29 ### Fixed - **Metal: per-type sequential slot indices in SetBindGroup** — Fixed descriptor set binding for Metal backend when multiple bind groups use different resource types (samplers, textures, buffers). Previously all resources shared a single index counter, causing incorrect slot assignments. (PR #112 by @timzifer) - **GLES: disable scissor test before MSAA resolve blit** — Prevents clipped resolve when scissor rect from previous draw call was still active during MSAA resolve. Fixes rendering artifacts on NVIDIA GPUs. (gg#226) ### Changed - **deps: goffi v0.4.2 → v0.5.0** — Adds Windows ARM64 (Snapdragon X) support. First Go GPU framework with Windows ARM64 support. (go-webgpu/goffi#31, tested by @SideFx) ## [0.22.1] - 2026-03-20 ### Fixed - **Vulkan: null command buffer guards** — Defense-in-depth checks across 19 methods prevent SIGSEGV if vkAllocateCommandBuffers silently returns null handle. 17 unit tests. - **GLES: disable scissor before glClear** — Prevents garbage/noise pixels during window resize. glClear was clipped by stale scissor rect from previous frame. ### Performance - **Vulkan: restore post-acquire fence wait** — Re-enabled fence in vkAcquireNextImageKHR for proper frame pacing on Windows (Intel driver timeouts fixed in 2026 drivers). Matches Rust wgpu pattern (issues #8310, #8354). - **DX12: waitable swapchain frame latency** — GetFrameLatencyWaitableObject + wait in AcquireTexture. Was flag-only (no-op). Now provides proper CPU frame pacing. ## [0.22.0] - 2026-03-20 ### Added - **GLES: GL sampler objects** — Proper sampler state via `glGenSamplers`/`glBindSampler` (GL 3.3+). Samplers now honor `FilterModeLinear`/`FilterModeNearest`, address modes, LOD clamp, anisotropy, and compare functions. Previously all textures were hardcoded to `GL_NEAREST` and sampler bindings were no-ops. Matches Rust wgpu GLES approach. - **GLES: texture unit overflow validation** — Warns via `slog` when flattened binding index exceeds `GL_MAX_TEXTURE_IMAGE_UNITS` (typically 8 on Intel). Reports actual hardware limit in adapter `Limits.MaxSampledTexturesPerShaderStage`. ### Fixed - **GLES: scissor Y-coordinate flip** — `glScissor` now correctly converts WebGPU top-left origin to OpenGL bottom-left origin (`glY = fbHeight - y - height`). Previously the scissor was vertically mirrored, clipping out content in complex UI layouts with nested clip rects. Includes clamp to 0 for safety. - **GLES: Linux colorWriteMask** — `CreateRenderPipeline` on Linux was missing `colorWriteMask` extraction from fragment targets, causing all color writes to be masked (black screen). Now matches Windows implementation. - **GLES: Linux CreateBuffer nil check** — Added nil descriptor guard matching the Windows version to prevent nil pointer panic. - **GLES: texture defaults changed to LINEAR** — Default texture filter changed from `GL_NEAREST` to `GL_LINEAR`. GL sampler objects override this when bound. ### Performance - **DX12: batch CopyDescriptors** — `CreateBindGroup` now batches descriptor copies via the full `CopyDescriptors` D3D12 API instead of calling `CopyDescriptorsSimple` per descriptor (~800 syscalls/frame → ~200). Estimated +20-30% FPS for complex UI. - **DX12: frame pacing** — GPU wait moved from `Present` to `AcquireTexture`, allowing CPU/GPU overlap. Matches Rust wgpu approach. Estimated +15-25% FPS when GPU is the bottleneck. - **DX12: pool descriptor heap slice** — Replaced heap-allocated slice in `ensureDescriptorHeapsBound` with fixed `[2]` array field on `CommandEncoder`. ## [0.21.3] - 2026-03-16 ### Added - **software: Draw() with vertex rasterization + textured blit** — Software backend now renders textured quads (fullscreen blit) and vertex-buffer-based triangles via `raster.Pipeline`. Resource registry for handle→resource lookup. MSAA resolve in End(). 21 tests. - **core: entry-by-entry BindGroupLayout compatibility** — Layouts compared by entries, not pointer equality, matching WebGPU spec and Rust wgpu-core. 7 tests. - **core: lazy GLES adapter enumeration with surface hint** — GLES backends defer adapter enumeration until `RequestAdapter` with `CompatibleSurface`. OpenGL requires GL context which only exists after surface creation. - **RequestAdapterOptions** — Proper struct with `CompatibleSurface *Surface` field (was alias to gputypes). Follows WebGPU spec `requestAdapter({compatibleSurface})`. ### Fixed - **DX12: reduce CBV/SRV/UAV heap to 1M** — D3D12 Tier 1/2 spec maximum. Was 1,048,576. Fixes `E_INVALIDARG` on NVIDIA. ([wgpu#106](https://github.com/gogpu/wgpu/issues/106)) - **GLES: nil context guard in Adapter.Open** — Returns error instead of panic when adapter created without surface. ([wgpu#107](https://github.com/gogpu/wgpu/issues/107)) - **GLES: match naga flattened binding indices** — GL binding = `group * 16 + binding`, matching naga GLSL output. Fixes SDF shapes invisible on GLES. - **core: prefer GPU adapters over Software in RequestAdapter** — GPU adapters selected before CPU/Software. ForceFallbackAdapter correctly returns CPU. 3 tests. ### Dependencies - naga v0.14.7 → v0.14.8 (GLSL bind group collision fix) ## [0.21.2] - 2026-03-16 ### Added - **core: Binder struct for render/compute pass validation** — Tracks assigned vs expected bind group layouts per slot (matching Rust wgpu-core pattern). At draw/dispatch time, `checkCompatibility()` verifies all expected slots have compatible bind groups assigned. 13 binder tests. - **core: comprehensive render/compute pass state validation** — SetBindGroup validates MAX_BIND_GROUPS hard cap (8), pipeline bind group count, and dynamic offset alignment (256 bytes). Draw/DrawIndexed validate pipeline is set, vertex buffer count, and index buffer presence. Dispatch validates pipeline set + bind group compatibility. 25+ new tests. ### Fixed - **core: SetBindGroup index bounds validation** — Prevents `vkCmdBindDescriptorSets` crash on AMD/NVIDIA GPUs when bind group index exceeds pipeline layout set count. Intel silently tolerates this spec violation; AMD/NVIDIA crash with access violation. Fixes [ui#52](https://github.com/gogpu/ui/issues/52). ## [0.21.1] - 2026-03-15 ### Fixed - **core: per-stage resource limit validation in CreateBindGroupLayout** — Validates storage buffer, uniform buffer, sampler, sampled texture, and storage texture counts per shader stage against device limits before calling HAL. Prevents wgpu-native abort when Vello compute requests 9 storage buffers on devices with limit 8. Error is now returned gracefully, enabling fallback to SDF renderer. ## [0.21.0] - 2026-03-15 ### Added - **public API: complete three-layer WebGPU stack** — The root `wgpu` package now provides a full typed API for GPU programming. All operations go through wgpu (public) → wgpu/core (validation) → wgpu/hal (backend). Consumers never need to import `wgpu/hal` for standard use. - **public API: SetLogger / Logger** — `wgpu.SetLogger()` and `wgpu.Logger()` propagate the logger to the entire stack (API, core, HAL backends). - **public API: Fence and async submission** — `Fence` type, `Device.CreateFence()`, `WaitForFence()`, `ResetFence()`, `GetFenceStatus()`, `FreeCommandBuffer()`. `Queue.SubmitWithFence()` for non-blocking GPU submission with fence signaling. - **public API: Surface lifecycle** — `Surface.SetPrepareFrame()` for platform HiDPI/DPI hooks. `Surface.DiscardTexture()` for canceled frames. `Surface.HAL()` escape hatch. Delegates to `core.Surface` state machine. - **public API: CommandEncoder extensions** — `CopyTextureToBuffer()`, `TransitionTextures()`, `DiscardEncoding()`. All use wgpu types (no hal in signatures). - **public API: HAL accessors** — `Device.HalDevice()`, `Device.HalQueue()`, `Texture.HalTexture()`, `TextureView.HalTextureView()` for advanced interop. - **public API: proper type definitions** — Replaced hal type aliases with proper structs: `Extent3D`, `Origin3D`, `ImageDataLayout`, `DepthStencilState`, `StencilFaceState`, `TextureBarrier`, `TextureRange`, `TextureUsageTransition`, `BufferTextureCopy`. Unexported `toHAL()` converters. No hal leakage in godoc. - **core: complete resource types (CORE-001)** — All 12 stub resource types (Texture, Sampler, BindGroupLayout, PipelineLayout, BindGroup, ShaderModule, RenderPipeline, ComputePipeline, CommandEncoder, CommandBuffer, QuerySet, Surface) now have full struct definitions with HAL handle wrapping. - **core: Surface state machine (CORE-002)** — Unconfigured → Configured → Acquired lifecycle with PrepareFrameFunc hook and auto-reconfigure on dimension changes. - **core: CommandEncoder state machine (CORE-003)** — Recording/InRenderPass/ InComputePass/Finished/Error states with validated transitions. - **core: resource accessors (CORE-004)** — Read-only accessors and idempotent Destroy() for all resource types. - **cmd/wgpu-triangle** — Single-threaded wgpu API triangle example. - **cmd/wgpu-triangle-mt** — Multi-threaded wgpu API triangle example. ### Changed - **Updated naga v0.14.6 → v0.14.7** — Fixes MSL sequential per-type binding indices across bind groups. ## [0.20.2] - 2026-03-12 ### Fixed - **Vulkan: validate WSI query functions in LoadInstance** — `vkGetPhysicalDevice- SurfaceCapabilitiesKHR`, `vkGetPhysicalDeviceSurfaceFormatsKHR`, and `vkGetPhysicalDeviceSurfacePresentModesKHR` are now verified during instance initialization. Previously, if any WSI function failed to load (returned nil), the error was silent until a later SIGSEGV via goffi nil function pointer call. Now fails fast with a clear error message. ## [0.20.1] - 2026-03-11 ### Fixed - **Metal: missing stencil attachment in render pass** — `BeginRenderPass` configured only the depth attachment, completely skipping the stencil attachment. On Apple Silicon TBDR GPUs, this left the stencil load action as `MTLLoadActionDontCare`, causing undefined stencil values and progressive rendering artifacts on Retina displays. Now configures `rpDesc.stencilAttachment` with texture, load/store actions, and clear value — matching the Vulkan and DX12 backends. ([#171](https://github.com/gogpu/gg/issues/171)) - **Metal: missing `setClearDepth:` call** — depth clear value was never explicitly set, relying on Metal's default of 1.0. Now calls `setClearDepth:` when `DepthLoadOp` is `LoadOpClear` for correctness. ## [0.20.0] - 2026-03-10 ### Added - **Core validation layer** (VAL-002) — exhaustive spec-level validation before HAL calls. 7 validation functions in `core/validate.go` covering 30+ WebGPU rules for textures, samplers, shaders, pipelines, bind groups, and bind group layouts. Validates dimensions, limits, multisampling, formats, and usage flags. - **Typed error types** (VAL-002) — 7 new typed errors with specific error kinds and context fields: `CreateTextureError` (13 kinds), `CreateSamplerError` (5), `CreateShaderModuleError` (3), `CreateRenderPipelineError` (8), `CreateComputePipelineError` (3), `CreateBindGroupLayoutError` (3), `CreateBindGroupError` (2). All support `errors.As()` for programmatic handling. - **Deferred nil error detection** (VAL-003) — 10 pass encoder and command encoder methods that previously silently ignored nil inputs now record deferred errors following the WebGPU spec pattern. Errors surface at `End()` / `Finish()`: `RenderPass.SetPipeline`, `SetBindGroup`, `SetVertexBuffer`, `SetIndexBuffer`, `DrawIndirect`, `DrawIndexedIndirect`, `ComputePass.SetPipeline`, `SetBindGroup`, `DispatchIndirect`, `CommandEncoder.CopyBufferToBuffer`. - **Format conversion tests** (COV-001) — 26 new test functions across Metal (20), Vulkan (4), DX12 (2), and GLES (5 format cases) backends. ### Fixed - **5 nil panic paths** (VAL-001) — added nil checks in `CreateBindGroup` (nil layout), `CreatePipelineLayout` (nil bind group layout element), `Queue.Submit` (nil command buffer), `Surface.Configure` (nil device), `Surface.Present` (nil texture). - **Metal: CopyDst buffer storage mode** — buffers with `CopyDst` usage were allocated with `StorageModePrivate` (GPU-only), causing "buffer not mappable" errors on Apple Silicon when `Queue.WriteBuffer()` tried to write. Now uses `StorageModeShared` for `CopyDst` and `MappedAtCreation` buffers, matching the Vulkan backend behavior. On UMA (all Apple Silicon) this is zero-cost. ([gg#170](https://github.com/gogpu/gg/issues/170)) - **Metal: staging buffer fallback for ReadBuffer/WriteBuffer** — defense-in-depth: if a buffer is `StorageModePrivate`, `WriteBuffer` and `ReadBuffer` now fall back to a temporary staging buffer + blit instead of failing. Mirrors the pattern already used by `WriteTexture` and matches Rust wgpu behavior. - **Metal: zero-length data guard** — `WriteBuffer` and `ReadBuffer` now return early for empty data slices, preventing a potential panic in the staging path. ### Changed - **HAL defense-in-depth** (VAL-004) — HAL nil checks now use `"BUG: ..."` prefix to signal core validation gaps. Removed 6 redundant spec checks (buffer size, texture dimensions) from Vulkan, Metal, DX12 — core validates these. Added 9 missing nil checks to GLES, Software, and Noop backends. ### Dependencies - **gputypes v0.2.0 → v0.3.0** — `TextureUsage.ContainsUnknownBits()` method, used by core validation for texture descriptor validation (VAL-002). ## [0.19.7] - 2026-03-07 ### Added - **Queue.WriteTexture** — public API for writing data to textures. Includes `ImageCopyTexture` descriptor, `ImageDataLayout` alias, and full nil validation with specific error messages. ([#95](https://github.com/gogpu/wgpu/pull/95) by [@Carmen-Shannon](https://github.com/Carmen-Shannon)) ### Changed - **Update naga v0.14.5 → v0.14.6** — MSL pass-through globals fix: helper functions now receive texture/sampler as extra parameters instead of using `[[binding]]` attributes. Fixes black screen on M3 Mac. ([naga#40](https://github.com/gogpu/naga/pull/40)) ## [0.19.6] - 2026-03-05 ### Fixed - **Metal: MSAA resolve store action** — when a render pass has a resolve target (MSAA → single-sample), Metal requires `MTLStoreActionMultisampleResolve` or `MTLStoreActionStoreAndMultisampleResolve`. We were setting `MTLStoreActionStore`, causing Metal to silently skip the resolve. The surface texture stayed uninitialized (purple/magenta screen). ([ui#23](https://github.com/gogpu/ui/issues/23)) ## [0.19.5] - 2026-03-05 ### Fixed - **Metal: add vertex descriptor to render pipeline creation** — Metal requires an explicit `MTLVertexDescriptor` when the vertex function has input attributes. Without it, pipeline creation fails with "Vertex function has input attributes but no vertex descriptor was set." Added `buildVertexDescriptor()` that maps WebGPU `VertexBufferLayout` to Metal vertex attributes and buffer layouts. ([ui#23](https://github.com/gogpu/ui/issues/23)) ### Added - **Complete Metal vertex format mapping** — all WebGPU vertex formats (8/16/32-bit int/uint/float, normalized, packed 10-10-10-2) now map to corresponding `MTLVertexFormat` constants. ### Changed - **Update goffi v0.4.1 → v0.4.2** - **Update naga v0.14.4 → v0.14.5** ## [0.19.4] - 2026-03-02 ### Changed - **Update goffi v0.3.9 → v0.4.1** — fix SIGSEGV on Linux/macOS for Vulkan functions with >6 arguments (`vkCmdPipelineBarrier`, etc.) ([goffi#19](https://github.com/go-webgpu/goffi/issues/19), [gogpu#119](https://github.com/gogpu/gogpu/issues/119)) ## [0.19.3] - 2026-03-01 ### Changed - **Update naga v0.14.3 → v0.14.4** — MSL backend fixes: vertex `[[stage_in]]` for struct-typed arguments, `metal::discard_fragment()` namespace prefix ([naga#38](https://github.com/gogpu/naga/pull/38), [ui#23](https://github.com/gogpu/ui/issues/23)) ## [0.19.2] - 2026-03-01 ### Fixed - **Metal: SIGBUS crash on Apple Silicon from ObjC block PAC re-signing** — ObjC blocks were constructed with `_NSConcreteStackBlock` but allocated on the Go heap. When Metal calls `Block_copy()` during `addCompletedHandler:`, ARM64e Pointer Authentication (PAC) re-signs the invoke function pointer. Since `ffi.NewCallback` pointers are unsigned, authentication fails and produces a corrupted pointer that causes SIGBUS ~0.7s after launch when Metal's completion queue invokes the callback. Fixed by switching to `_NSConcreteGlobalBlock` with `BLOCK_IS_GLOBAL` flag, which makes `Block_copy()` a complete no-op (no memmove, no PAC re-signing). Added `blockPinRegistry` to prevent GC collection of block literals while Metal holds references. Removed stale `runtime.KeepAlive(uintptr)` calls that were no-ops (GC doesn't track `uintptr` as roots). ([wgpu#89](https://github.com/gogpu/wgpu/issues/89), [ui#23](https://github.com/gogpu/ui/issues/23)) ### Changed - **CI: upgraded codecov-action v4 → v5**, added `codecov.yml` configuration - **Tests: added coverage tests** for core, HAL backends, and format conversion ## [0.19.1] - 2026-03-01 ### Fixed - **Metal: crash on Apple Silicon (M1/M2/M3/M4) with depth/stencil textures** — `Depth24PlusStencil8` was hardcoded to `MTLPixelFormatDepth24UnormStencil8` (255), which is unsupported on Apple Silicon GPUs (only available on legacy AMD GPUs in Intel-era Macs). Metal rejected the invalid pixel format with SIGABRT. Additionally, `Depth24Plus` was completely missing from the format mapping, returning `MTLPixelFormatInvalid` (0). Fixed by detecting device capability via `isDepth24Stencil8PixelFormatSupported` at adapter enumeration and choosing `Depth32Float`/`Depth32FloatStencil8` (universally supported) when Depth24 is unavailable. Follows the same pattern as wgpu-rs (`wgpu-hal/src/metal/adapter.rs`). ([ui#23](https://github.com/gogpu/ui/issues/23)) ## [0.19.0] - 2026-03-01 ### Changed - **BREAKING: `hal.Queue.WriteBuffer` now returns `error`** — previously a silent void method that could swallow errors from all backends (Vulkan `FlushMappedMemoryRanges`, Metal/DX12 buffer mapping, etc.). All 7 backend implementations (vulkan, metal, dx12, gles, gles_linux, software, noop) updated. All callers in tests and examples now check errors. - **BREAKING: `hal.Queue.WriteTexture` now returns `error`** — previously a void method. All 7 backend implementations updated with proper error propagation from staging buffer allocation, data copy, and submission. Callers updated across the ecosystem. - **BREAKING: `wgpu.Queue.WriteBuffer` now returns `error`** — public API wrapper updated to propagate errors from HAL layer. - **BREAKING: `wgpu.Queue.WriteTexture` now returns `error`** — public API wrapper updated to propagate errors from HAL layer. ### Fixed - **Vulkan: `WriteTexture` consumes swapchain acquire semaphore** — `WriteTexture` performs an internal staging `Submit()` that consumed the swapchain acquire semaphore meant for the render pass. This caused `vkQueueSubmit` to fail or produce undefined behavior when the render pass subsequently tried to use the already-consumed semaphore. Fixed by saving and restoring `activeSwapchain`/`acquireUsed` state around the staging submit, protected by mutex. ([gogpu#119](https://github.com/gogpu/gogpu/issues/119)) - **Vulkan: `VK_ERROR_DEVICE_LOST` masked by void `WriteTexture`** — Vulkan staging submit errors were silently discarded because `WriteTexture` returned void. Now all Vulkan errors (buffer mapping, memory flush, queue submit) propagate to the caller. - **Vulkan: `CmdSetBlendConstants` codegen regression** — auto-generated binding used scalar float signature instead of pointer-to-float-array. Vulkan ABI expects `const float[4]` as pointer, not scalar. Caused SIGSEGV in `BeginRenderPass` for any application using blend. - **Noop: `WriteBuffer` rejects non-mapped buffers** — noop `CreateBuffer` returns `*Resource` (not `*Buffer`) for non-mapped buffers. `WriteBuffer` type assertion now handles both types. ## [0.18.1] - 2026-02-27 ### Fixed - **Vulkan: buffer-to-image copy row stride corruption** — `convertBufferImageCopyRegions` incorrectly inferred `bytesPerTexel` via integer division `BytesPerRow / Width` instead of using the texture format's known block size. When `BytesPerRow` was padded to 256-byte alignment, the division produced wrong results for most image widths (126 out of 204 possible widths for RGBA8). For example, width=204: `1024 / 204 = 5` (should be 4) → Vulkan received wrong `bufferRowLength` → pixel corruption on rounded rectangles and other non-power-of-2 width textures. Fixed by adding `blockCopySize()` static lookup matching the Rust wgpu reference implementation's `TextureFormat::block_copy_size()`. Covers all non-compressed WebGPU texture formats. ([gogpu#96](https://github.com/gogpu/gogpu/discussions/96)) ## [0.18.0] - 2026-02-27 ### Added - **Public API root package** — `import "github.com/gogpu/wgpu"` provides a safe, ergonomic, WebGPU-spec-aligned API for third-party applications. Wraps `core/` and `hal/` into 20 public types: Instance, Adapter, Device, Queue, Buffer, Texture, TextureView, Sampler, ShaderModule, BindGroupLayout, PipelineLayout, BindGroup, RenderPipeline, ComputePipeline, CommandEncoder, CommandBuffer, RenderPassEncoder, ComputePassEncoder, Surface, SurfaceTexture. - `wgpu.CreateInstance()` → `instance.RequestAdapter()` → `adapter.RequestDevice()` flow - All `Create*` methods on Device with `(T, error)` returns - Synchronous `Queue.Submit()` with internal fence management - `Queue.WriteBuffer()` / `Queue.ReadBuffer()` for CPU↔GPU data transfer - Type aliases re-exported from `gputypes` (no need to import `gputypes` directly) - Deterministic cleanup via `Release()` on all resource types - Backend registration via blank import (`_ "github.com/gogpu/wgpu/hal/allbackends"`) - Full command recording: `RenderPassEncoder.SetPipeline/SetBindGroup`, `ComputePassEncoder.SetPipeline/SetBindGroup`, `CommandEncoder.CopyBufferToBuffer` delegate to HAL via new `RawPass()` / `RawEncoder()` core accessors - Examples rewritten to use public API (`examples/compute-copy/`, `examples/compute-sum/`) - Integration tests with software backend (15 tests covering full Instance→Submit flow) - `core/instance.go`: software backend now enumerated as real adapter (noop still skipped) ## [0.17.1] - 2026-02-27 ### Fixed - **Metal: MSAA texture view crash** — `CreateTextureView` crashed on Apple Silicon (M3) when creating a `TextureViewDimension2D` view from a multisampled (4x MSAA) source texture. Metal requires the view type to match the source texture's multisample state (`MTLTextureType2DMultisample`), unlike Vulkan which handles this implicitly. ([ui#23](https://github.com/gogpu/ui/issues/23), [#80](https://github.com/gogpu/wgpu/issues/80)) ## [0.17.0] - 2026-02-27 ### Added - **Wayland Vulkan surface creation** — `CreateWaylandSurface()` method on Vulkan API for creating surfaces from `wl_display*` and `wl_surface*` C pointers via `VK_KHR_wayland_surface` extension. Function pointer `vkCreateWaylandSurfaceKHR` loaded via `vkGetInstanceProcAddr`, following the same pattern as X11, XCB, and Metal surface creation. ## [0.16.17] - 2026-02-26 ### Fixed - **Vulkan: load platform surface creation functions** — `vkCreateXlibSurfaceKHR`, `vkCreateXcbSurfaceKHR`, `vkCreateWaylandSurfaceKHR`, and `vkCreateMetalSurfaceEXT` were never loaded via `GetInstanceProcAddr` — only `vkCreateWin32SurfaceKHR` was. On Linux/macOS the function pointer stayed nil, and goffi FFI returned zeros (result=0, surface=0x0) instead of crashing, causing "null surface" errors downstream. ([gogpu#106](https://github.com/gogpu/gogpu/issues/106)) ## [0.16.16] - 2026-02-25 ### Fixed - **Vulkan: X11/macOS surface creation pointer bug** — `CreateSurface` passed the Go stack address of the `display` parameter (`unsafe.Pointer(&display)`) instead of the actual `Display*` value (`unsafe.Pointer(display)`). This caused `vkCreateXlibSurfaceKHR` to receive a Go stack pointer instead of the real Xlib `Display*`, resulting in null surfaces or SIGSEGV. Same fix applied to macOS `CAMetalLayer*` in the Vulkan-on-MoltenVK path. ([gogpu#106](https://github.com/gogpu/gogpu/issues/106)) ## [0.16.15] - 2026-02-25 ### Changed - **Software backend: always compiled** — removed `//go:build software` build tags from all 34 files in `hal/software/`, `hal/software/raster/`, and `hal/software/shader/`. The software backend is now always available without `-tags software`. Pure Go, zero system dependencies — ideal for CI/CD, headless rendering, and fallback when no GPU is available. ([gogpu#106](https://github.com/gogpu/gogpu/issues/106)) ### Fixed - **Software backend: nestif complexity** — extracted `clearDepthStencilAttachment()` helper in `RenderPassEncoder.End()` to reduce nesting depth (pre-existing issue exposed by build tag removal). ## [0.16.14] - 2026-02-25 ### Fixed - **Vulkan: null surface handle guard** — `EnumerateAdapters`, `SurfaceCapabilities`, and `createSwapchain` now check for null `VkSurfaceKHR` handle before calling Vulkan surface functions. Prevents SIGSEGV on Linux when surface creation fails (e.g., X11 connection issues). ([gogpu#106](https://github.com/gogpu/gogpu/issues/106)) ### Changed - **Dependencies:** naga v0.14.2 → v0.14.3 (5 SPIR-V compute shader bug fixes) ## [0.16.13] - 2026-02-24 ### Fixed - **Vulkan: load VK_EXT_debug_utils via GetInstanceProcAddr** — `vkSetDebugUtilsObjectNameEXT` was loaded via `GetDeviceProcAddr`, which bypasses the validation layer's handle wrapping on NVIDIA drivers, causing `VUID-VkDebugUtilsObjectNameInfoEXT-objectType-02590` ("Invalid VkDescriptorPool Object") errors. Now loaded via `GetInstanceProcAddr` as required for instance extensions. Also loads `vkCreateDebugUtilsMessengerEXT` and `vkDestroyDebugUtilsMessengerEXT` which were previously missing — debug messenger callback now works correctly. ([gogpu#98](https://github.com/gogpu/gogpu/issues/98)) ## [0.16.12] - 2026-02-23 ### Fixed - **Vulkan: debug object naming** (VK-VAL-002) — added `setObjectName` helper that calls `vkSetDebugUtilsObjectNameEXT` after every Vulkan object creation. Labels buffers, textures, pipelines, render passes, framebuffers, descriptor pools, swapchain images, semaphores, and more with human-readable names. Eliminates false-positive `VUID-VkDebugUtilsObjectNameInfoEXT-objectType-02590` validation errors on NVIDIA where the validation layer's handle tracking lost sync with packed non-dispatchable handles. No-op when `VK_EXT_debug_utils` is unavailable. Resources display named labels in RenderDoc/Nsight captures. ([gogpu#98](https://github.com/gogpu/gogpu/issues/98)) ## [0.16.11] - 2026-02-23 ### Fixed - **Vulkan: zero-extent swapchain on window minimize** (VK-VAL-001) — `createSwapchain()` used `capabilities.CurrentExtent` as primary extent source. NVIDIA drivers report `CurrentExtent = {0, 0}` when minimized, passing zero directly to `vkCreateSwapchainKHR` and violating `VUID-VkSwapchainCreateInfoKHR-imageExtent-01274`. Now uses `config` dimensions as primary source (matching Rust wgpu-hal `native.rs:189-197` pattern), with `CurrentExtent` only for clamping to the valid range. Returns `hal.ErrZeroArea` when clamped extent is zero. ([gogpu#98](https://github.com/gogpu/gogpu/issues/98)) - **Vulkan: unconditional viewport/scissor in BeginRenderPass** — viewport and scissor dynamic state was conditionally set only when render dimensions > 0. When zero-extent frames slipped through, the pipeline's dynamic state was never initialized, causing `VUID-vkCmdDraw-None-07831` and `VUID-vkCmdDraw-None-07832` validation errors. Now always sets viewport/scissor using `max(dim, 1)` as safety net. ([gogpu#98](https://github.com/gogpu/gogpu/issues/98)) ### Changed - **Public examples moved to `examples/`** — `compute-copy` and `compute-sum` moved from `cmd/` to `examples/` following Go project layout conventions. `cmd/` retains internal tools (vk-gen, backend tests). ## [0.16.10] - 2026-02-22 ### Fixed - **Vulkan: pre-acquire semaphore wait** (VK-IMPL-004) — Acquire semaphores are rotated across frames, but nothing guaranteed the GPU had consumed the previous wait before reuse, violating `VUID-vkAcquireNextImageKHR-semaphore-01779` on some drivers. Now tracks the submission fence value per acquire semaphore and waits before reuse, matching Rust wgpu's `previously_used_submission_index` pattern. Also adds binary fence pool tracking to `SubmitForPresent` which previously submitted with no fence at all. ([gogpu#98](https://github.com/gogpu/gogpu/issues/98)) ### Dependencies - naga v0.14.1 → v0.14.2 (GLSL GL_ARB_separate_shader_objects fix, golden snapshot tests) ## [0.16.9] - 2026-02-21 ### Dependencies - naga v0.14.0 → v0.14.1 (HLSL row_major matrices for DX12, GLSL namedExpressions leak fix for GLES) ## [0.16.8] - 2026-02-21 ### Fixed - **Metal: blank window on macOS** ([gogpu#89](https://github.com/gogpu/gogpu/issues/89)) — `Queue.Present()` only released the drawable reference without calling `presentDrawable:`. Now creates a dedicated command buffer for presentation matching the Rust wgpu pattern: `commandBuffer` → `presentDrawable:` → `commit`. Fixes blank rendering on macOS Tahoe M2 Max. ## [0.16.7] - 2026-02-21 ### Dependencies - naga v0.13.1 → v0.14.0 (Essential 15/15 reference shaders, 48 type aliases, 25 math ops, 20+ SPIR-V fixes) ## [0.16.6] - 2026-02-18 ### Added - **Metal backend debug logging** — 23 new `hal.Logger()` calls across the critical rendering path: `AcquireTexture`, `Submit`, `Present`, `BeginEncoding`/`EndEncoding`, `CreateCommandEncoder`, `Wait`/`WaitIdle`, `Destroy`, and all three ObjC block callback invocations (shared event, completion handler, frame completion). Enables diagnosis of blank window issues on macOS (gogpu/gogpu#89) and validates goffi callback delivery (go-webgpu/goffi#16). Metal backend now has ~38 log points, matching Vulkan/DX12 coverage. ### Changed - **goffi** v0.3.8 → v0.3.9 ## [0.16.5] - 2026-02-18 ### Fixed - **Vulkan per-encoder command pools** (VK-POOL-001) — Each `CreateCommandEncoder` now gets its own dedicated `VkCommandPool` + `VkCommandBuffer` pair, matching Rust wgpu-hal architecture. Eliminates race condition between per-frame bulk pool reset (`vkResetCommandPool`) and individual command buffer freeing (`vkFreeCommandBuffers`) that caused `vkBeginCommandBuffer(): Couldn't find VkCommandBuffer Object` access violation crashes. Pools are recycled via a thread-safe free list with lazy reset on next acquire. No API changes — `hal.Device` interface unchanged. ## [0.16.4] - 2026-02-18 Vulkan timeline semaphore fences, binary fence pool, hot-path allocation optimization, and enterprise benchmarks. Internal performance improvements — no API changes. ### Added - **Enterprise hot-path benchmarks** — 44+ benchmarks with `ReportAllocs()` covering Vulkan Submit/Present/Encoding cycle, descriptor operations, memory allocator, noop backend overhead, and cross-backend HAL interface. Table-driven sub-benchmarks for different sizes and workloads. - **Compute shader SDF integration test** — End-to-end GPU test: WGSL SDF shader → naga compile → Vulkan compute pipeline → dispatch → ReadBuffer → CPU reference verification (256 pixels, ±0.01). - **Compute shader examples** — `examples/compute-sum/` (parallel pairwise reduction) and `examples/compute-copy/` (scaled buffer copy) demonstrating the compute pipeline API. - **Timestamp queries for compute passes** — `ComputePassTimestampWrites`, `CreateQuerySet`, `ResolveQuerySet` with full Vulkan implementation (`vkCmdWriteTimestamp`, `vkCmdCopyQueryPoolResults`). Other backends return `ErrTimestampsNotSupported`. - **Software backend compute error** — `ErrComputeNotSupported` sentinel error with `errors.Is` support. - **Compute shader documentation** — `docs/compute-shaders.md` (getting started guide) and `docs/compute-backends.md` (backend support matrix). ### Changed - **Vulkan timeline semaphore fence** (VK-IMPL-001) — Single `VkSemaphore` with monotonic `uint64` counter replaces binary `VkFence` ring buffer on Vulkan 1.2+. Signal attached to real `vkQueueSubmit` (eliminates empty submit per frame). Replaces transfer fence state machine. Graceful fallback to binary fences on pre-1.2 drivers. Based on Rust wgpu-hal `Fence::TimelineSemaphore`. - **Vulkan command buffer batch allocation** (VK-IMPL-002) — Batch-allocate 16 command buffers per `vkAllocateCommandBuffers` call (matches wgpu-hal `ALLOCATION_GRANULARITY`). Free/used list recycling per frame slot. Handles are valid after `vkResetCommandPool` (flag 0). - **Vulkan binary fence pool** (VK-IMPL-003) — `fencePool` with per-submission tracking for Vulkan <1.2 where timeline semaphores are unavailable. Active/free lists with non-blocking `maintain()` polling, `signal()` fence acquisition, `wait()` with watermark fast-path. Replaces 2-slot binary fence ring buffer and separate transfer fence. Mirrors Rust wgpu-hal `FencePool` pattern. `deviceFence` now always created (never nil) — unified dual-path dispatch. - **Vulkan hot-path allocation reduction** — `sync.Pool` for CommandEncoder, CommandBuffer, ComputePassEncoder, RenderPassEncoder. Stack-allocated `[3]vk.ClearValue` in BeginRenderPass. Removed CommandPool wrapper struct. Per-frame Submit uses pooled `[]vk.CommandBuffer` slices. Result: BeginEndEncoding 15→13 allocs, ComputePassBeginEnd 25→22 allocs, EncodeSubmitCycle 28→26 allocs. ### Fixed - **Vulkan transfer fence race condition** — `Submit()` now waits for previous GPU work before resetting transfer fence, preventing "vkResetFences: pFences[0] is in use" validation error. - **Vulkan swapchain image view leak** — `createSwapchain()` now calls `destroyResources()` (semaphores + image views) instead of `releaseSyncResources()` (semaphores only) when reconfiguring, preventing "VkImageView has not been destroyed" validation errors on shutdown. - **Vulkan device destroy fence wait** — `Destroy()` waits for all in-flight frame slots before destroying fences, preventing fence-in-use errors during cleanup. ## [0.16.3] - 2026-02-16 ### Added - **`hal.Device.WaitIdle()` interface method** — Waits for all GPU work to complete before resource destruction. Implemented across all backends: Vulkan (`vkDeviceWaitIdle`), DX12 (`waitForGPU`), Metal (`waitUntilCompleted`), GLES (`glFinish`), noop/software (no-op). ### Fixed - **Vulkan per-frame fence tracking** — Replaced single shared `frameFence` with per-slot `VkFence` objects (one per frame-in-flight). Each fence is only reset after `vkWaitForFences` confirms it is signaled. Fixes `vkResetFences(): pFences[0] is in use` validation error. Frame fence signaling moved from `Submit()` to `Present()` to avoid fence reuse across multiple submits per frame. Pattern based on Rust wgpu-hal FencePool design. - **DX12 per-frame fence tracking** — Per-frame command allocator pool with timeline fence. `advanceFrame()` waits only for the specific old frame slot instead of all GPU work. Eliminates two `waitForGPU()` stalls per frame (in `BeginEncoding` and `Present`). - **Metal per-frame fence tracking** — `maxFramesInFlight` semaphore (capacity 2) limits CPU-ahead-of-GPU buffering. `frameCompletionHandler` signals semaphore on GPU completion. Event-based `Wait()` replaces polling loop. Async `WriteTexture` via staging buffer and blit encoder. - **GLES VSync on Windows** — Load `wglSwapIntervalEXT` via `wglGetProcAddress` during `Surface.Configure()`. Maps `PresentMode` to swap interval: Fifo=1 (VSync on), Immediate=0 (VSync off). Fixes 100% GPU usage on the GLES Windows backend. ## [0.16.2] - 2026-02-16 ### Fixed - **Metal autorelease pool LIFO violation** — Replaced stored autorelease pools with scoped pools that drain immediately within the same function. Previously, pools were stored in `CommandBuffer` structs and drained asynchronously via `FencePool`, causing LIFO violations when frame N+1 overlapped with frame N on the ObjC pool stack. macOS Tahoe (26.2) upgraded this from a warning to fatal SIGABRT. Fix matches the Rust wgpu-hal Metal backend pattern. Fixes gogpu/gogpu#83. ## [0.16.1] - 2026-02-15 ### Fixed - **Vulkan framebuffer cache invalidation** — `DestroyTextureView` now invalidates cached framebuffers before calling `vkDestroyImageView`, ensuring framebuffers that reference the view are destroyed first. Fixes Vulkan validation error: `vkDestroyImageView`/`vkDestroyFramebuffer` in use by `VkCommandBuffer`. ## [0.16.0] - 2026-02-15 Major release: full GLES rendering pipeline, structured logging across all backends, MSAA support, and cross-backend stability fixes. ### Added #### Structured Logging - **`log/slog` integration** — All HAL backends now emit structured diagnostic logs via Go's standard `log/slog` package. Silent by default; enable with `slog.SetLogLoggerLevel(slog.LevelDebug)` or a custom handler. Zero overhead when logging is disabled. #### OpenGL ES Backend (Full Rendering Pipeline) - **WGSL-to-GLSL shader compilation** — End-to-end shader pipeline via naga: WGSL source is compiled to GLSL, then loaded via `glShaderSource`/`glCompileShader`. Includes VAO creation, FBO setup, and triangle rendering. - **Offscreen FBO and MSAA textures** — Framebuffer objects for off-screen rendering, multi-sample texture support, and `CopyTextureToBuffer` readback path. - **Vertex attributes, stencil state, color mask** — Full vertex attribute layout configuration, stencil test state, per-channel color write masks, and BGRA readback format conversion. - **VAO, viewport, blend state, bind group commands** — Vertex Array Objects, viewport/scissor state, blend equation/factor configuration, and bind group resource binding. #### Metal Backend - **SetBindGroup** — Bind group resource binding for render and compute encoders. - **WriteTexture** — GPU texture upload via staging buffer and blit encoder. - **Fence synchronization** — CPU-GPU synchronization for command completion. #### DX12 Backend - **CreateBindGroup** — Bind group creation with SRV/CBV/sampler descriptor mapping to root parameter slots. - **InfoQueue debug messages** — `ID3D12InfoQueue` captures validation errors/warnings when debug layer is enabled. `DrainDebugMessages()` reads and logs all pending messages after Submit and Present. #### Vulkan Backend - **MSAA render pass support** — Multi-sample render pass with automatic resolve attachment configuration. Includes depth/stencil usage flag fixes for MSAA targets. ### Fixed #### DX12 Backend - **GPU hang causing DPC_WATCHDOG_VIOLATION BSOD** — Resolved a device hang that triggered a Windows kernel watchdog timeout on some hardware configurations. - **Texture resource state tracking** — Correct resource barriers via per-texture state tracking. Fixes rendering corruption from missing or incorrect COMMON/COPY_DEST/SHADER_RESOURCE transitions. Also fixes a COM reference leak. - **MSAA resolve, view dimensions, descriptor recycling** — MSAA resolve copies now target the correct subresource. Texture view dimensions match the underlying resource. Descriptor recycling frees slots from the correct staging heaps. - **Readback pitch alignment and barrier states** — Buffer readback row pitch is now aligned to D3D12_TEXTURE_DATA_PITCH_ALIGNMENT (256 bytes). Resource barriers use correct before/after states for copy operations. - **Staging descriptor heaps** — SRV and sampler descriptors are now created in non-shader-visible staging heaps, then copied to shader-visible heaps via `CopyDescriptorsSimple`. Follows the DX12 specification requirement that `CopyDescriptorsSimple` source must be non-shader-visible. Prevents subtle rendering corruption on some hardware. - **Descriptor recycling** — `TextureView.Destroy()` and `Sampler.Destroy()` now free descriptors from the correct staging heaps, enabling proper slot reuse. #### Vulkan Backend - **Descriptor pool allocation** — Always include all descriptor types (uniform buffer, storage buffer, sampled image, sampler, storage image) in pool creation. Fixes `VK_ERROR_OUT_OF_POOL_MEMORY` when bind groups reference previously unused types. - **vkCmdSetBlendConstants FFI signature** — Corrected goffi calling convention to pass blend constants by pointer, matching the Vulkan specification. - **Dynamic pipeline states** — All 4 dynamic states (viewport, scissor, stencil reference, blend constants) are now declared on every render pipeline. Prevents validation errors on drivers that require complete dynamic state declarations. #### Metal Backend - **Command buffer creation deferred to BeginEncoding** — `CreateCommandEncoder` eagerly created a Metal command buffer, conflicting with `BeginEncoding`'s guard (`cmdBuffer != 0`). Every `BeginEncoding` call returned "already recording" error, and the pre-allocated command buffer + autorelease pool were never released. At 60fps this leaked ~30GB in minutes. Fix: defer command buffer creation to `BeginEncoding`, matching the two-step pattern used by Vulkan and DX12 backends. (Fixes [#55]) #### GLES Backend - **Surface resolve** — Correct resolve blit from MSAA renderbuffer to single-sample surface texture for presentation. ### Changed - **Metal queue** — Eliminated `go vet` unsafe.Pointer warnings by using typed wrapper functions for Objective-C message sends. - **DX12 descriptor heap management** — Free list recycling for descriptor slots, reducing allocation overhead for long-running applications. - **naga v0.12.0 → v0.13.0** — GLSL backend improvements, HLSL/SPIR-V fixes ## [0.15.1] - 2026-02-13 Critical fixes across DX12, Metal, and Vulkan backends. ### Fixed - **DX12 WriteBuffer** was a no-op stub, causing blank renders with uniform data - Staging buffer + `CopyBufferRegion` for DEFAULT heap (GPU-only) buffers - Direct CPU mapping for UPLOAD heap buffers (zero-copy path) - D3D12 auto-promotion from COMMON state for buffer copies - **DX12 WriteTexture** was a no-op stub, textures never uploaded to GPU - Staging buffer + `CopyTextureRegion` with 256-byte row pitch alignment - Resource barriers: COMMON → COPY_DEST → SHADER_RESOURCE - **DX12 shader compilation** produced empty DXBC bytecode - Added `d3dcompile` package — Pure Go bindings to d3dcompiler_47.dll - Wired `compileWGSLModule`: WGSL → naga HLSL → D3DCompile → DXBC - **Metal memory leak** — 30GB+ memory usage on macOS (Issue #55) - `FreeCommandBuffer` was a no-op — command buffers never released after submit - NSString labels leaked in `BeginEncoding`, `BeginComputePass`, `CreateBuffer`, `CreateTexture` ### Added - **Vulkan debug messenger** — validation errors now logged via `log.Printf` (Issue #53) - `VK_EXT_debug_utils` messenger created when `InstanceFlagsDebug` is set - Captures ERROR and WARNING severity from validation layers - Cross-platform callback via `goffi/ffi.NewCallback` - Zero overhead when debug mode is off ## [0.15.0] - 2026-02-10 HAL Queue ReadBuffer for GPU→CPU data transfer, enabling compute shader result readback. ### Added #### HAL Interface - **`ReadBuffer`** on `Queue` interface — GPU→CPU buffer readback for storage/staging buffers - Maps buffer memory, copies data to Go byte slice, unmaps - Enables compute shader pipelines (e.g., SDF rendering) to read results back to CPU - Implemented in Vulkan backend via `vkMapMemory`/`vkUnmapMemory` ### Changed - **naga** dependency updated v0.11.1 → v0.12.0 — adds `OpFunctionCall`, compute shader codegen fixes - **golang.org/x/sys** updated v0.39.0 → v0.41.0 ## [0.14.0] - 2026-02-09 Debug toolkit for GPU resource management and error handling. ### Added #### Debug & Diagnostics (`core/`) - **GPU Resource Leak Detection** — Track unreleased GPU resources at runtime - `SetDebugMode(true)` enables tracking with zero overhead when disabled (~1ns atomic load) - `ReportLeaks()` returns `LeakReport` with per-type counts (Buffer, Texture, Device, etc.) - `ResetLeakTracker()` for test cleanup - Integrated into Device, Buffer, Instance, CommandEncoder lifecycle - **W3C WebGPU Error Scopes** — Programmatic GPU error capture per the WebGPU spec - `ErrorScopeManager` with LIFO stack-based scopes - `ErrorFilter`: Validation, OutOfMemory, Internal - `GPUError` type implementing Go `error` interface - Device integration: `device.PushErrorScope()`, `device.PopErrorScope()` - Lazy initialization, thread-safe via internal mutex - **Thread Safety Tests** — Concurrent access validation - Concurrent leak tracking (track/untrack from 50+ goroutines) - Concurrent error scope operations (push/pop/report) - Concurrent instance creation and adapter requests ### Changed - **naga** dependency updated v0.11.0 → v0.11.1 — fixes SPIR-V OpLogicalAnd, comparison/shift opcodes, variable initializers, runtime-sized arrays ## [0.13.2] - 2026-02-07 ### Changed - **naga** dependency updated v0.10.0 → v0.11.0 — fixes SPIR-V `if/else` GPU hang, adds 55 new WGSL built-in functions ## [0.13.1] - 2026-02-06 ### Fixed - **Render pass InitialLayout for LoadOpLoad** — Set correct `InitialLayout` when `LoadOp` is `Load` instead of unconditional `ImageLayoutUndefined`. Previously, Vulkan was allowed to discard image contents between render passes, causing ClearColor output to be lost (black background instead of the expected color). Affects both color and depth/stencil attachments. ## [0.13.0] - 2026-02-01 Major HAL interface additions: format capabilities, array textures, and render bundles. ### Added #### Format & Surface Capabilities - **GetTextureFormatCapabilities** — Query actual Vulkan format capabilities - Returns TextureFormatCapabilityFlags based on `vkGetPhysicalDeviceFormatProperties` - No more hardcoded flags — real hardware support detection - **GetSurfaceCapabilities** — Query surface capabilities from Vulkan - Uses `vkGetPhysicalDeviceSurfaceFormatsKHR` and `vkGetPhysicalDeviceSurfacePresentModesKHR` - Returns real supported formats, present modes, and alpha modes #### Array Textures & Cubemaps - **Array texture support** — Proper VkImageViewType selection - `VK_IMAGE_VIEW_TYPE_2D_ARRAY` for 2D array textures - `VK_IMAGE_VIEW_TYPE_CUBE` for cubemaps (6 layers) - `VK_IMAGE_VIEW_TYPE_CUBE_ARRAY` for cubemap arrays - **ArrayLayers tracking** — Separate from depth dimension in Texture struct #### Render Bundles - **RenderBundleEncoder interface** — Pre-record render commands for reuse - SetPipeline, SetBindGroup, SetVertexBuffer, SetIndexBuffer - Draw, DrawIndexed, Finish - **RenderBundle interface** — Execute pre-recorded commands - **Vulkan implementation** — Secondary command buffers with `VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT` - **ExecuteBundle** — Execute render bundles via `vkCmdExecuteCommands` #### HAL Interface Extensions - **ResetFence** — Reset fence to unsignaled state - **GetFenceStatus** — Non-blocking fence status check - **FreeCommandBuffer** — Explicit command buffer cleanup - **CreateRenderBundleEncoder** / **DestroyRenderBundle** — Bundle lifecycle ### Changed - All HAL backends updated with stub implementations for new interface methods ## [0.12.0] - 2026-01-30 ### Added - **NativeHandle interface** (`hal/`) — Access raw GPU handles for interop - `NativeTextureHandle()` returns platform-specific texture handle - Enables integration with external graphics libraries ### Fixed - **Vulkan texture rendering** — Critical BufferRowLength fix - `BufferRowLength` now correctly specified in **texels**, not bytes - Fixes aspect ratio distortion (squashed circles → proper circles) - Root cause: Vulkan `VkBufferImageCopy` expects texel count, not byte count - **WriteBuffer support** — Buffer memory mapping implementation - Proper staging buffer creation and memory mapping - Fixes texture upload pipeline ### Changed - **Vulkan pipeline creation** — Code cleanup and refactoring - **Update naga v0.8.4 → v0.9.0** — Sampler types, swizzle, SPIR-V fixes ## [0.11.2] - 2026-01-29 ### Changed - **Update gputypes to v0.2.0** for webgpu.h spec-compliant enum values - All enum values now match official WebGPU C header specification - Binary compatibility with wgpu-native and other WebGPU implementations ### Fixed - **CompositeAlphaMode naming** — Fixed `PreMultiplied` → `Premultiplied` in all HAL adapters - Matches webgpu.h spec naming convention - Affected: Vulkan, DX12, GLES, Metal, Noop, Software adapters ## [0.11.1] - 2026-01-29 ### Breaking Changes - **Removed `types/` package** — Use `github.com/gogpu/gputypes` instead - All WebGPU types now come from shared `gputypes` package - Import `github.com/gogpu/gputypes` for TextureFormat, BufferUsage, etc. - 1,745 lines removed, unified ecosystem types ### Changed - All packages now import `gputypes` for WebGPU type definitions - **HAL types are now gputypes aliases** — No more type converters needed! - `hal.PresentMode` = `gputypes.PresentMode` - `hal.CompositeAlphaMode` = `gputypes.CompositeAlphaMode` - 97 files updated for consistent type usage ### Migration ```go // Before (wgpu v0.10.x) import "github.com/gogpu/wgpu/types" types.TextureFormatRGBA8Unorm // After (wgpu v0.11.1) import "github.com/gogpu/gputypes" gputypes.TextureFormatRGBA8Unorm ``` ## [0.10.3] - 2026-01-28 Enterprise-level multi-thread architecture for window responsiveness. ### Added #### Internal - **Thread Package** (`internal/thread/`) — Cross-platform thread abstraction - `Thread` — Dedicated OS thread with `runtime.LockOSThread()` for GPU operations - `RenderLoop` — Manages UI/render thread separation with deferred resize - `Call()`, `CallVoid()`, `CallAsync()` — Sync/async thread communication - `RequestResize()` / `ConsumePendingResize()` — Thread-safe resize coordination - Comprehensive tests (`thread_test.go`) #### Vulkan Triangle Demo - **Multi-Thread Architecture** — Ebiten-style separation for responsive windows - Main thread: Win32 message pump only (`runtime.LockOSThread()` in `init()`) - Render thread: All GPU operations including `vkDeviceWaitIdle` - Deferred swapchain resize: size captured in WM_SIZE, applied on render thread - No more "Not Responding" during resize/drag operations #### Windows Platform - **WM_SETCURSOR Handling** — Proper cursor restoration after resize - Fixes resize cursor staying 5-10 seconds after resize ends - Arrow cursor explicitly set when mouse enters client area ### Changed #### HAL/Vulkan - Removed unused fence wrapper functions from `swapchain.go` - `vkCreateFenceSwapchain`, `vkDestroyFenceSwapchain` - `vkWaitForFencesSwapchain`, `vkResetFencesSwapchain` - `vkGetFenceStatusSwapchain` ### Architecture The multi-thread pattern follows Ebiten/Gio best practices: ``` Main Thread (OS Thread 0) Render Thread (Dedicated) ├─ runtime.LockOSThread() ├─ runtime.LockOSThread() ├─ Win32 Message Pump ├─ Vulkan Device Operations ├─ WM_SIZE → RequestResize() ├─ ConsumePendingResize() └─ PollEvents() ├─ vkDeviceWaitIdle (non-blocking UI!) └─ Acquire → Render → Present ``` This architecture ensures: - Window remains responsive during GPU operations - Swapchain recreation doesn't freeze UI - Proper handling of modal resize loops (WM_ENTERSIZEMOVE/WM_EXITSIZEMOVE) ## [0.10.2] - 2026-01-24 ### Changed - **goffi v0.3.8** — Fixed CGO build tag consistency ([#43](https://github.com/gogpu/wgpu/issues/43)) - Clear error message when building with CGO enabled: `undefined: GOFFI_REQUIRES_CGO_ENABLED_0` - Consistent `!cgo` build tags across all FFI files - See [goffi v0.3.8 release notes](https://github.com/go-webgpu/goffi/releases/tag/v0.3.8) ## [0.10.1] - 2026-01-16 Window responsiveness fix for Vulkan swapchain. ### Added #### HAL - **ErrNotReady Error** — New error for non-blocking acquire signaling - Returned when swapchain image is not ready yet - Signals caller to skip frame without error ### Changed #### HAL/Vulkan - **Non-blocking swapchain acquire** — Improved window responsiveness - Use 16ms timeout instead of infinite wait in `acquireNextImage()` - Return `ErrNotReady` on timeout instead of blocking forever - Don't advance semaphore rotation on timeout (matches wgpu-hal pattern) - Based on wgpu-hal `vulkan/swapchain/native.rs` implementation ### Fixed - Window lag during resize/drag operations on Windows - "Not responding" window state during GPU-bound rendering ## [0.10.0] - 2026-01-15 New HAL backend integration layer for unified multi-backend support. ### Added #### Core - **Backend Interface** — New abstraction for HAL backend management - `Backend` interface with `Name()`, `CreateInstance()`, `SupportsWindow()` methods - `Resource` interface for GPU resource lifecycle management - Platform-independent backend selection - **HAL Backend Integration** — Seamless backend auto-registration - `hal/allbackends` package for platform-specific registration - Vulkan backend auto-registered on Windows/Linux - Metal backend auto-registered on macOS - Import `_ "github.com/gogpu/wgpu/hal/allbackends"` to enable all available backends - **Enhanced Instance** — HAL backend support in core.Instance - `Instance.Backend()` returns active backend - `Instance.AvailableBackends()` lists registered backends - Automatic backend selection based on platform #### HAL - **Backend Init Functions** — Auto-registration via `init()` - `hal/vulkan/init.go` — Registers Vulkan backend - `hal/metal/init.go` — Registers Metal backend ### Changed - Instance creation now uses HAL backend abstraction internally ## [0.9.3] - 2026-01-10 Critical Intel Vulkan fixes: VkRenderPass support, wgpu-style swapchain synchronization. ### Added #### HAL - **ErrDriverBug Error** — New error type for driver specification violations - Returned when GPU driver violates API spec (e.g., returns success but invalid handle) - Provides actionable guidance: update driver, try different backend, or use software rendering #### Vulkan Backend - **VkRenderPass Support** — Classic render pass implementation for Intel compatibility - New `renderpass.go` with VkRenderPass and VkFramebuffer management - Switched from VK_KHR_dynamic_rendering (broken on Intel) to classic approach - Works across all GPU vendors - **wgpu-Style Swapchain Synchronization** — Proper frame pacing for Windows/Intel - Rotating acquire semaphores (one per max frames in flight) - Per-image present semaphores - Post-acquire fence wait (fixes "Not Responding" on Windows) - Per-acquire fence tracking for stutter-free rendering - **Fence Status Optimization** — Skip unnecessary fence waits - `vkGetFenceStatus` check before blocking wait - Improves frame latency when GPU is already done - **Device Management** — New methods for resource management - `Device.WaitIdle()` — Wait for all GPU operations - `Device.ResetCommandPool()` — Reset all command buffers - **WSI Function Loading** — Explicit loading of Window System Integration functions ### Fixed #### Vulkan Backend - **Intel Null Pipeline Workaround** — Defensive check for Intel Vulkan driver bug - Intel Iris Xe drivers may return `VK_SUCCESS` but write `VK_NULL_HANDLE` to pipeline - Returns `hal.ErrDriverBug` instead of crashing - **goffi Pointer Argument Passing** — Fixed FFI calling convention - goffi expects pointer-to-pointer pattern for pointer arguments - **vkGetDeviceProcAddr Loading** — Fixed device function loading on Intel - **Validation Layer Availability** — Gracefully skip validation if Vulkan SDK not installed ### Changed - Updated naga dependency v0.8.3 → v0.8.4 (SPIR-V instruction ordering fix) ### Dependencies - `github.com/gogpu/naga` v0.8.4 (was v0.8.3) ## [0.9.2] - 2026-01-05 ### Fixed #### Metal Backend - **NSString Double-Free** — Fix crash on autorelease pool drain ([#39]) - `NSString()` used `stringWithUTF8String:` returning autoreleased object - Callers called `Release()` causing double-free when pool drained - Fix: Use `alloc/initWithUTF8String:` for +1 retained ownership [#39]: https://github.com/gogpu/wgpu/pull/39 ## [0.9.1] - 2026-01-05 ### Fixed #### Vulkan Backend - **vkDestroyDevice Memory Leak** — Fixed memory leak when destroying Vulkan devices ([#32]) - Device was not properly destroyed due to missing goffi call - Now correctly calls `vkDestroyDevice` via `ffi.CallFunction` with `SigVoidHandlePtr` signature - **Features Mapping** — Implemented `featuresFromPhysicalDevice()` ([#33]) - Maps 9 Vulkan features to WebGPU features (BC, ETC2, ASTC, IndirectFirstInstance, etc.) - Reference: wgpu-hal/src/vulkan/adapter.rs:584-829 - **Limits Mapping** — Implemented proper Vulkan→WebGPU limits mapping ([#34]) - Maps 25+ hardware limits from `VkPhysicalDeviceLimits` - Includes: texture dimensions, descriptor limits, buffer limits, compute limits - Reference: wgpu-hal/src/vulkan/adapter.rs:1254-1392 [#32]: https://github.com/gogpu/wgpu/issues/32 [#33]: https://github.com/gogpu/wgpu/issues/33 [#34]: https://github.com/gogpu/wgpu/issues/34 ## [0.9.0] - 2026-01-05 ### Added #### Core-HAL Bridge - **Snatchable Pattern** — Safe deferred resource destruction with `Snatchable[T]` wrapper - **TrackerIndex Allocator** — Efficient dense index allocation for resource state tracking - **Buffer State Tracker** — Tracks buffer usage states for validation - **Core Device with HAL** — `NewDevice()` creates device with HAL backend integration - **Core Buffer with HAL** — `Device.CreateBuffer()` creates GPU-backed buffers - **Core CommandEncoder** — Command recording with HAL dispatch ### Changed - **Code Quality** — Replaced 58 TODO comments with proper documentation notes - Core layer: Deprecated legacy ID-based API functions with HAL-based alternatives - HAL backends: Documented feature gaps with version targets (v0.5.0, v0.6.0) ### Known Limitations (Vulkan Backend) The following features are not yet fully implemented in the Vulkan backend: | Feature | Status | Target | |---------|--------|--------| | Feature Detection | ~~Returns 0~~ **Fixed in v0.9.1** | ✅ | | Limits Mapping | ~~Uses defaults~~ **Fixed in v0.9.1** | ✅ | | Array Textures | Single layer only | v0.10.0 | | Render Bundles | Not implemented | v0.10.0 | | Timestamp Period | Hardcoded to 1.0 | v0.10.0 | **Note:** Basic rendering (triangles, textures, compute) works correctly. These limitations affect capability reporting and advanced features only. ## [0.8.8] - 2026-01-04 ### Fixed #### CI - **Metal Tests on CI** — Skip Metal tests on GitHub Actions (Metal unavailable in virtualized macOS) - See: https://github.com/actions/runner-images/discussions/6138 ### Changed - Updated dependency: `github.com/gogpu/naga` v0.8.2 → v0.8.3 - Fixes MSL `[[position]]` attribute placement (now on struct member, not function) ## [0.8.7] - 2026-01-04 ### Fixed #### Metal Backend (ARM64) - **ObjC Typed Arguments** — Proper type-safe wrappers for ARM64 AAPCS64 ABI compliance - **Shader Creation** — Improved error handling in Metal shader module creation - **Pipeline Creation** — Better error messages for render pipeline failures ### Added - **Metal ObjC Tests** — Comprehensive test coverage for ObjC interop (`objc_test.go`) - **Surface Tests** — Metal surface creation and configuration tests (`surface_test.go`) ### Changed - Updated dependency: `github.com/go-webgpu/goffi` v0.3.6 → v0.3.7 - Updated dependency: `github.com/gogpu/naga` v0.8.1 → v0.8.2 ### Contributors - @ppoage — ARM64 ObjC fixes and Metal backend testing ## [0.8.6] - 2025-12-29 ### Fixed - **Metal Double Present Issue** — Removed duplicate `[drawable present]` call in `Queue.Present()` - `presentDrawable:` is already scheduled in `Submit()` before command buffer commit - Duplicate present was causing synchronization issues on some Metal drivers ### Changed - Updated dependency: `github.com/go-webgpu/goffi` v0.3.5 → v0.3.6 - **ARM64 HFA Returns** — `NSRect` (4×float64) now correctly returns all values on Apple Silicon - **Large Struct Returns** — Structs >16 bytes properly use X8 register for implicit pointer - **Fixes macOS ARM64 blank window** — `GetSize()` no longer returns (0,0) on M1/M2/M3/M4 Macs - Resolves [gogpu/gogpu#24](https://github.com/gogpu/gogpu/issues/24) ## [0.8.5] - 2025-12-29 ### Added - **DX12 Backend Registration** — DirectX 12 backend now auto-registers on Windows - Added `hal/dx12/init.go` with `RegisterBackend()` call - DX12 backend (~12.7K LOC) now available alongside Vulkan on Windows - Windows backend priority: Vulkan → DX12 → GLES → Software ## [0.8.4] - 2025-12-29 ### Changed - Updated dependency: `github.com/gogpu/naga` v0.8.0 → v0.8.1 - Fixes missing `clamp()` built-in function in WGSL shader compilation - Adds comprehensive math function tests ## [0.8.3] - 2025-12-29 ### Fixed - **Metal macOS Blank Window** (Issue [gogpu/gogpu#24](https://github.com/gogpu/gogpu/issues/24)) - Root cause: `[drawable present]` called separately after command buffer commit - Fix: Schedule `presentDrawable:` on command buffer BEFORE `commit` (Metal requirement) - Added `SetDrawable()` method to CommandBuffer for drawable attachment - Added `Drawable()` accessor to SurfaceTexture - **Metal TextureView NSRange Parameters** - Root cause: `newTextureViewWithPixelFormat:textureType:levels:slices:` expects `NSRange` structs - Fix: Pass `NSRange` struct pointers instead of raw integers - Fixed array layer count calculation (was previously ignored) ## [0.8.2] - 2025-12-29 ### Changed - Updated dependency: `github.com/gogpu/naga` v0.6.0 → v0.8.0 - HLSL backend for DirectX 11/12 - Code quality and SPIR-V bug fixes - All 4 shader backends now stable - Updated dependency: `github.com/go-webgpu/goffi` v0.3.3 → v0.3.5 ## [0.8.1] - 2025-12-28 ### Fixed - **DX12 COM Calling Convention Bug** — Fixes device operations on Intel GPUs - Root cause: D3D12 methods returning structs require `this` pointer first, output pointer second - Affected methods: `GetCPUDescriptorHandleForHeapStart`, `GetGPUDescriptorHandleForHeapStart`, `GetDesc` (multiple types), `GetResourceAllocationInfo` - Reference: [D3D12 Struct Return Convention](https://joshstaiger.org/notes/C-Language-Problems-in-Direct3D-12-GetCPUDescriptorHandleForHeapStart.html) - **Vulkan goffi Argument Passing Bug** — Fixes Windows crash (Exception 0xc0000005) - Root cause: vk-gen generated incorrect FFI calls after syscall→goffi migration - Before: `unsafe.Pointer(ptr)` passed pointer value directly - After: `unsafe.Pointer(&ptr)` passes pointer TO pointer (goffi requirement) - Affected all Vulkan functions with pointer parameters ### Added - **DX12 Integration Test** (`cmd/dx12-test`) — Validates DX12 backend on Windows - Tests: backend creation, instance, adapter enumeration, device, pipeline layout - **Compute Shader Support (Phase 2)** — Core API implementation - `ComputePipelineDescriptor` and `ProgrammableStage` types - `DeviceCreateComputePipeline()` and `DeviceDestroyComputePipeline()` functions - `ComputePassEncoder` with SetPipeline, SetBindGroup, Dispatch, DispatchIndirect - `CommandEncoderImpl.BeginComputePass()` for compute pass creation - Bind group index validation (0-3 per WebGPU spec) - Indirect dispatch offset alignment validation (4-byte) - Comprehensive tests (~700 LOC) with concurrent access testing - **HAL Compute Infrastructure (Phase 1)** - GLES: `glDispatchCompute`, `glMemoryBarrier`, compute shader constants - DX12: `SetBindGroup` for ComputePassEncoder/RenderPassEncoder - Metal: Pipeline workgroup size extraction from naga IR ## [0.8.0] - 2025-12-26 ### Added - **DirectX 12 Backend** — Complete HAL implementation (~12K LOC) - Pure Go COM bindings via syscall (no CGO!) - D3D12 API access via COM interface vtables - DXGI integration for swapchain and adapter enumeration - Descriptor heap management (CBV/SRV/UAV, Sampler, RTV, DSV) - Flip model swapchain with tearing support (VRR) - Command list recording with resource barriers - Root signature and PSO creation - Buffer, Texture, TextureView, Sampler resources - RenderPipeline, ComputePipeline creation - Full format conversion (WebGPU → DXGI) - **Metal CommandEncoder Test** — Regression test for Issue #24 ### Changed - All 5 HAL backends now complete: - Vulkan (~27K LOC) — Windows, Linux, macOS - Metal (~3K LOC) — macOS, iOS - DX12 (~12K LOC) — Windows - GLES (~7.5K LOC) — Windows, Linux - Software (~10K LOC) — All platforms ### Fixed - Metal encoder test updated to use `IsRecording()` method instead of non-existent field ## [0.7.2] - 2025-12-26 ### Fixed - **Metal CommandEncoder State Bug** — Fixes Issue [#24](https://github.com/gogpu/wgpu/issues/24) - Root cause: `isRecording` flag was not set in `CreateCommandEncoder()` - Caused `BeginRenderPass()` to return `nil` on macOS - Fix: Removed boolean flag, use `cmdBuffer != 0` as state indicator - Follows wgpu-rs pattern where `Option` presence indicates state - Added `IsRecording()` method for explicit state checking ### Changed - Updated `github.com/gogpu/naga` dependency from v0.5.0 to v0.6.0 ## [0.7.1] - 2025-12-26 ### Added - **ErrZeroArea error** — Sentinel error for zero-dimension surface configuration - Matches wgpu-core `ConfigureSurfaceError::ZeroArea` pattern - Comprehensive unit tests in `hal/error_test.go` ### Fixed - **macOS Zero Dimension Crash** — Fixes Issue [#20](https://github.com/gogpu/gogpu/issues/20) - Added zero-dimension validation to all `Surface.Configure()` implementations - Returns `ErrZeroArea` when width or height is zero - Affected backends: Metal, Vulkan, GLES (Linux/Windows), Software - Follows wgpu-core pattern: "Wait to recreate the Surface until the window has non-zero area" ### Notes - This fix allows proper handling of minimized windows and macOS timing issues - Window becomes visible asynchronously on macOS; initial dimensions may be 0,0 ## [0.7.0] - 2025-12-24 ### Added - **Metal WGSL→MSL Compilation** — Full shader compilation pipeline via naga v0.5.0 - Parse WGSL source - Lower to intermediate representation - Compile to Metal Shading Language (MSL) - Create MTLLibrary from MSL source - **CreateRenderPipeline** — Complete Metal implementation (~120 LOC) - Get vertex/fragment functions from library - Configure color attachments and blending - Create MTLRenderPipelineState ### Changed - Added `github.com/gogpu/naga v0.5.0` dependency ## [0.6.1] - 2025-12-24 ### Fixed - **macOS ARM64 SIGBUS crash** — Corrected goffi API usage in Metal backend - Fixed pointer argument passing pattern for Objective-C runtime calls - Resolved SIGBUS errors on Apple Silicon (M1/M2/M3) systems - **GLES/EGL CI integration tests** — Implemented EGL surfaceless platform - Added `EGL_MESA_platform_surfaceless` support for headless testing - Added `QueryClientExtensions()` and `HasSurfacelessSupport()` functions - Updated `DetectWindowKind()` to prioritize surfaceless in CI environments - Removed Xvfb dependency, using Mesa llvmpipe software renderer - **staticcheck SA5011 warnings** — Added explicit returns after `t.Fatal()` calls ### Changed - Updated goffi to v0.3.2 for ARM64 macOS compatibility - CI workflow now uses `LIBGL_ALWAYS_SOFTWARE=1` for reliable headless EGL ## [0.6.0] - 2025-12-23 ### Added - **Metal backend** (`hal/metal/`) — Pure Go via goffi (~3K LOC) - Objective-C runtime bindings via goffi (go-webgpu/goffi) - Metal framework access: MTLDevice, MTLCommandQueue, MTLCommandBuffer - Render encoder: MTLRenderCommandEncoder, MTLRenderPassDescriptor - Resource management: MTLBuffer, MTLTexture, MTLSampler - Pipeline state: MTLRenderPipelineState, MTLDepthStencilState - Surface presentation via CAMetalLayer - Format conversion: WebGPU → Metal texture formats - Cross-compilable from Windows/Linux to macOS ### Changed - Updated ecosystem: gogpu v0.5.0 (macOS Cocoa), naga v0.5.0 (MSL backend) - Pre-release check script now uses kolkov/racedetector (Pure Go, no CGO) ### Notes - **Community Testing Requested**: Metal backend needs testing on real macOS systems (12+ Monterey) - Requires naga v0.5.0 for MSL shader compilation ## [0.5.0] - 2025-12-19 ### Added - **Software rasterization pipeline** (`hal/software/raster/`) — Full CPU-based triangle rendering - Edge function (Pineda) algorithm with top-left fill rule - Perspective-correct attribute interpolation - Depth buffer with 8 compare functions (Never, Less, Equal, LessEqual, etc.) - Stencil buffer with 8 operations (Keep, Zero, Replace, IncrementClamp, etc.) - 13 blend factors, 5 blend operations (WebGPU spec compliant) - 6-plane frustum clipping (Sutherland-Hodgman algorithm) - Backface culling (CW/CCW winding) - 8x8 tile-based rasterization for cache locality - Parallel rasterization with worker pool - Incremental edge evaluation (O(1) per pixel stepping) - ~6K new lines of code, 70+ tests - **Callback-based shader system** (`hal/software/shader/`) - `VertexShaderFunc` and `FragmentShaderFunc` interfaces - Built-in shaders: SolidColor, VertexColor, Textured - Custom shader support for flexible rendering - Matrix utilities (Mat4, transforms) - ~1K new lines of code, 30+ tests ### Changed - Pre-release check script now matches CI behavior for go vet exclusions - Improved WSL fallback for race detector tests ## [0.4.0] - 2025-12-13 ### Added - **Linux support for OpenGL ES backend** (`hal/gles/`) via EGL - EGL bindings using goffi (Pure Go FFI) - Platform detection: X11, Wayland, Surfaceless (headless) - Full Device and Queue HAL implementations - CI integration tests with Mesa software renderer - ~4000 new lines of code ## [0.3.0] - 2025-12-10 ### Added - **Software backend** (`hal/software/`) - CPU-based rendering for headless scenarios - Real data storage for buffers and textures - Clear operations (fill framebuffer with color) - Buffer/texture copy operations - Thread-safe access with `sync.RWMutex` - `Surface.GetFramebuffer()` for pixel readback - 11 unit tests - Build tag: `-tags software` - Use cases: CI/CD testing, server-side image generation, embedded systems ## [0.2.0] - 2025-12-08 ### Added - **Vulkan backend** (`hal/vulkan/`) - Complete HAL implementation (~27K LOC) - Auto-generated bindings from official Vulkan XML specification - Memory allocator with buddy allocation - Vulkan 1.3 dynamic rendering - Swapchain management with automatic recreation - Complete resource support: Buffer, Texture, Sampler, Pipeline, etc. - 93 unit tests - Native Go backend integration with gogpu/gogpu ### Changed - Backend registration system improved ## [0.1.0] - 2025-12-07 ### Added - Initial release - **Types package** (`types/`) - WebGPU type definitions - Backend types (Vulkan, Metal, DX12, GL) - 100+ texture formats - Buffer, sampler, shader types - Vertex formats with size calculations - **Core package** (`core/`) - Validation and state management - Type-safe ID system with generics - Epoch-based use-after-free prevention - Hub with 17 resource registries - 127 tests with 95% coverage - **HAL package** (`hal/`) - Hardware abstraction layer - Backend, Instance, Adapter, Device, Queue interfaces - Resource interfaces - Command encoding - Backend registration system - 54 tests with 94% coverage - **Noop backend** (`hal/noop/`) - Reference implementation for testing - **OpenGL ES backend** (`hal/gles/`) - Pure Go via goffi (~3.5K LOC) [#55]: https://github.com/gogpu/wgpu/issues/55 [Unreleased]: https://github.com/gogpu/wgpu/compare/v0.17.0...HEAD [0.17.0]: https://github.com/gogpu/wgpu/compare/v0.16.17...v0.17.0 [0.16.14]: https://github.com/gogpu/wgpu/compare/v0.16.13...v0.16.14 [0.16.13]: https://github.com/gogpu/wgpu/compare/v0.16.12...v0.16.13 [0.16.12]: https://github.com/gogpu/wgpu/compare/v0.16.11...v0.16.12 [0.16.11]: https://github.com/gogpu/wgpu/compare/v0.16.10...v0.16.11 [0.16.10]: https://github.com/gogpu/wgpu/compare/v0.16.9...v0.16.10 [0.16.9]: https://github.com/gogpu/wgpu/compare/v0.16.8...v0.16.9 [0.16.8]: https://github.com/gogpu/wgpu/compare/v0.16.7...v0.16.8 [0.16.7]: https://github.com/gogpu/wgpu/compare/v0.16.6...v0.16.7 [0.16.6]: https://github.com/gogpu/wgpu/compare/v0.16.5...v0.16.6 [0.16.5]: https://github.com/gogpu/wgpu/compare/v0.16.4...v0.16.5 [0.16.4]: https://github.com/gogpu/wgpu/compare/v0.16.3...v0.16.4 [0.16.3]: https://github.com/gogpu/wgpu/compare/v0.16.2...v0.16.3 [0.16.2]: https://github.com/gogpu/wgpu/compare/v0.16.1...v0.16.2 [0.16.1]: https://github.com/gogpu/wgpu/compare/v0.16.0...v0.16.1 [0.16.0]: https://github.com/gogpu/wgpu/compare/v0.15.1...v0.16.0 [0.15.1]: https://github.com/gogpu/wgpu/compare/v0.15.0...v0.15.1 [0.15.0]: https://github.com/gogpu/wgpu/compare/v0.14.0...v0.15.0 [0.14.0]: https://github.com/gogpu/wgpu/compare/v0.13.2...v0.14.0 [0.13.2]: https://github.com/gogpu/wgpu/compare/v0.13.1...v0.13.2 [0.13.1]: https://github.com/gogpu/wgpu/compare/v0.13.0...v0.13.1 [0.13.0]: https://github.com/gogpu/wgpu/compare/v0.12.0...v0.13.0 [0.12.0]: https://github.com/gogpu/wgpu/compare/v0.11.2...v0.12.0 [0.11.2]: https://github.com/gogpu/wgpu/compare/v0.11.1...v0.11.2 [0.11.1]: https://github.com/gogpu/wgpu/compare/v0.10.3...v0.11.1 [0.10.3]: https://github.com/gogpu/wgpu/compare/v0.10.2...v0.10.3 [0.10.2]: https://github.com/gogpu/wgpu/compare/v0.10.1...v0.10.2 [0.10.1]: https://github.com/gogpu/wgpu/compare/v0.10.0...v0.10.1 [0.10.0]: https://github.com/gogpu/wgpu/compare/v0.9.3...v0.10.0 [0.9.3]: https://github.com/gogpu/wgpu/compare/v0.9.2...v0.9.3 [0.9.2]: https://github.com/gogpu/wgpu/compare/v0.9.1...v0.9.2 [0.9.1]: https://github.com/gogpu/wgpu/compare/v0.9.0...v0.9.1 [0.9.0]: https://github.com/gogpu/wgpu/compare/v0.8.8...v0.9.0 [0.8.8]: https://github.com/gogpu/wgpu/compare/v0.8.7...v0.8.8 [0.8.7]: https://github.com/gogpu/wgpu/compare/v0.8.6...v0.8.7 [0.8.6]: https://github.com/gogpu/wgpu/compare/v0.8.5...v0.8.6 [0.8.5]: https://github.com/gogpu/wgpu/compare/v0.8.4...v0.8.5 [0.8.4]: https://github.com/gogpu/wgpu/compare/v0.8.3...v0.8.4 [0.8.3]: https://github.com/gogpu/wgpu/compare/v0.8.2...v0.8.3 [0.8.2]: https://github.com/gogpu/wgpu/compare/v0.8.1...v0.8.2 [0.8.1]: https://github.com/gogpu/wgpu/compare/v0.8.0...v0.8.1 [0.8.0]: https://github.com/gogpu/wgpu/compare/v0.7.2...v0.8.0 [0.7.2]: https://github.com/gogpu/wgpu/compare/v0.7.1...v0.7.2 [0.7.1]: https://github.com/gogpu/wgpu/compare/v0.6.1...v0.7.1 [0.6.1]: https://github.com/gogpu/wgpu/compare/v0.6.0...v0.6.1 [0.6.0]: https://github.com/gogpu/wgpu/compare/v0.5.0...v0.6.0 [0.5.0]: https://github.com/gogpu/wgpu/compare/v0.4.0...v0.5.0 [0.4.0]: https://github.com/gogpu/wgpu/compare/v0.3.0...v0.4.0 [0.3.0]: https://github.com/gogpu/wgpu/compare/v0.2.0...v0.3.0 [0.2.0]: https://github.com/gogpu/wgpu/compare/v0.1.0...v0.2.0 [0.1.0]: https://github.com/gogpu/wgpu/releases/tag/v0.1.0