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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 8 additions & 5 deletions core/surface.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
}

Expand Down
25 changes: 8 additions & 17 deletions core/surface_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -696,19 +696,17 @@ 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()

if err := surface.Configure(device, config); err != nil {
t.Fatalf("Configure: %v", err)
}

// Acquire a texture
_, err := surface.AcquireTexture(nil)
if err != nil {
t.Fatalf("AcquireTexture: %v", err)
Expand All @@ -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())
}
}
Loading