From 69c710ee094670a83b6394db18739f1921c73917 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Tue, 14 Jul 2026 10:49:03 +0300 Subject: [PATCH 1/2] fix(core): PresentPixels checks backend support before discarding texture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move hal.PixelPresenter type assertion before acquired texture discard. Failed PresentPixels call on GPU backends no longer corrupts surface state — acquired texture preserved for normal render→Present fallback. Validate-then-mutate pattern matches Rust wgpu Surface::present() and configure_surface() precondition checks. --- core/surface.go | 13 ++++++++----- core/surface_test.go | 25 ++++++++----------------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/core/surface.go b/core/surface.go index 808b5f0..4658ebb 100644 --- a/core/surface.go +++ b/core/surface.go @@ -199,6 +199,14 @@ func (s *Surface) PresentPixels(data []byte, width, height uint32, damageRects [ return ErrSurfaceNotConfigured } + // Check backend support BEFORE discarding acquired texture. + // GPU backends don't implement PixelPresenter — early return preserves + // the acquired texture so the normal render→Present path still works. + pp, ok := s.raw.(hal.PixelPresenter) + if !ok { + return fmt.Errorf("core: PresentPixels not supported on this backend") + } + // Discard stale acquired texture if any — PresentPixels replaces the // entire AcquireTexture→render→Present flow. if s.state == SurfaceStateAcquired && s.acquiredTex != nil { @@ -207,11 +215,6 @@ func (s *Surface) PresentPixels(data []byte, width, height uint32, damageRects [ s.state = SurfaceStateConfigured } - // Only software backend implements hal.PixelPresenter. - pp, ok := s.raw.(hal.PixelPresenter) - if !ok { - return fmt.Errorf("core: PresentPixels not supported on this backend") - } return pp.PresentPixels(data, width, height, damageRects) } diff --git a/core/surface_test.go b/core/surface_test.go index 9795c5b..0a3ceb4 100644 --- a/core/surface_test.go +++ b/core/surface_test.go @@ -696,11 +696,10 @@ func TestPresentPixels_UnsupportedBackend(t *testing.T) { } } -func TestPresentPixels_DiscardsAcquiredTexture(t *testing.T) { - // After AcquireTexture, PresentPixels should discard the stale texture - // and succeed (on a backend that supports it). Since noop doesn't support - // PresentPixels, we verify the state transition: acquired → configured, - // then the "not supported" error comes from the duck-type check. +func TestPresentPixels_PreservesAcquiredOnUnsupported(t *testing.T) { + // On backends that don't support PixelPresenter (like noop), PresentPixels + // should return an error WITHOUT discarding the acquired texture. This + // preserves the surface state so the normal render→Present path still works. surface, device, _ := newTestSurface(t) config := testSurfaceConfig() @@ -708,7 +707,6 @@ func TestPresentPixels_DiscardsAcquiredTexture(t *testing.T) { t.Fatalf("Configure: %v", err) } - // Acquire a texture _, err := surface.AcquireTexture(nil) if err != nil { t.Fatalf("AcquireTexture: %v", err) @@ -717,21 +715,14 @@ func TestPresentPixels_DiscardsAcquiredTexture(t *testing.T) { t.Fatalf("state = %d, want SurfaceStateAcquired", surface.State()) } - // PresentPixels discards the acquired texture, then fails because noop - // doesn't implement the interface. + // PresentPixels on noop should fail without side effects. err = surface.PresentPixels([]byte{0, 0, 0, 0}, 1, 1, nil) if err == nil { t.Error("PresentPixels on noop should return error") } - // State should be Configured (texture was discarded before the duck-type check). - if surface.State() != SurfaceStateConfigured { - t.Errorf("state after PresentPixels = %d, want SurfaceStateConfigured", surface.State()) - } - - // Should be able to acquire again (proves discard happened). - _, err = surface.AcquireTexture(nil) - if err != nil { - t.Errorf("AcquireTexture after PresentPixels: %v", err) + // State must stay Acquired — texture NOT discarded on unsupported backend. + if surface.State() != SurfaceStateAcquired { + t.Errorf("state after PresentPixels = %d, want SurfaceStateAcquired", surface.State()) } } From 3d94d04bc3c09df49107c8077d4047d15d2feffe Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Tue, 14 Jul 2026 14:15:24 +0300 Subject: [PATCH 2/2] docs: CHANGELOG + AGENTS.md for v0.30.20 --- AGENTS.md | 2 +- CHANGELOG.md | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index e040bac..b323f4d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -65,7 +65,7 @@ wgpu (public API — Device, Queue, Buffer, Texture, Pipeline...) ## Current Version -v0.30.19 | Go 1.25+ | Dependencies: naga v0.17.15, gpucontext v0.21.1, gputypes v0.5.1, goffi v0.6.0, webgpu v0.5.3 +v0.30.20 | Go 1.25+ | Dependencies: naga v0.17.15, gpucontext v0.21.1, gputypes v0.5.1, goffi v0.6.0, webgpu v0.5.3 ## Build & Test diff --git a/CHANGELOG.md b/CHANGELOG.md index ede82cd..7cb3465 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ 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.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