Skip to content

fix(surface): centralize acquisition and teardown ownership - #269

Merged
kolkov merged 2 commits into
gogpu:mainfrom
besmpl:besmpl/surface-lifetime-ownership
Jul 22, 2026
Merged

fix(surface): centralize acquisition and teardown ownership#269
kolkov merged 2 commits into
gogpu:mainfrom
besmpl:besmpl/surface-lifetime-ownership

Conversation

@besmpl

@besmpl besmpl commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Make the existing public lifetime contract enforceable without adding a HAL interface:

  • core.Surface owns an opaque acquisition lease, invalidated by present, discard, unconfigure, replacement, and destruction.
  • Texture and TextureView use one private HAL resolver, so retained swapchain wrappers fail before native access at render, copy, binding, write, and submit boundaries.
  • the public Instance owns the wrappers it creates and releases devices, then platform surfaces, then the native instance; cross-instance surface/device configuration is rejected.
  • Vulkan Device owns the set of configured swapchains and retires all of them in one device-destroy pass. Vulkan Surface continues to own only VkSurfaceKHR; Vulkan Instance needs no child registry.
  • a batched Queue.WriteTexture retains and revalidates the wrapper that authorized it, so a write recorded before present/discard cannot later submit a retired swapchain image.

This is an Android-independent prerequisite for #268. Android exposed the ownership gap, but the borrowed-surface-texture rule is common to every backend.

Ownership and release order

public Instance
├── Device ── configured swapchains / device children
├── Surface ─ platform surface handle
└── core Instance ─ native instance

release: Device → Surface → core Instance

Active acquisitions are discarded while the device is alive. After device teardown, the logical core configuration is retired before the platform surface is destroyed. Small DX12 guards keep its surface-owned swapchain cleanup safe in that order; Vulkan device loss uses a handle-abandon path rather than issuing child destroys through a lost device.

Verification

  • GOWORK=off go test ./...
  • GOWORK=off go test -race . ./core ./hal/vulkan
  • GOWORK=off go vet . ./core ./hal/vulkan
  • GOWORK=off go test -tags rust ./...
  • GOWORK=off go mod verify
  • golangci-lint v2.12.2 run --new-from-rev upstream/main (0 issues)
  • Windows/amd64 cgo-disabled DX12 test binary cross-compile
  • Linux/amd64 cgo-disabled root test binary cross-compile

Focused public tests cover present, discard, reconfigure, unconfigure, surface release, device/instance release, foreign acquisition, retained views, idempotent wrapper release, render/copy/bind/submit rejection, deferred writes, submit failure cleanup, and cross-instance ownership. Vulkan tests cover configured-surface ownership and the device-loss abandon path.

Android integration relationship

This Android-independent change is one of five prerequisites replayed in #268. Its canonical candidate head is 2a069cb1fde942339813efd7199ed305a3a2ca84; the corresponding replay in #268 ends at 0eb4143eff2bbe0fc50907d009770ce2bfd3c69d. The #268 description records the current exact integration head and full replay map; it drops this replay after this PR merges. Rust-v29 typed surface parity remains separate in #273. Android runtime and release claims remain in #268.

@besmpl
besmpl requested a review from kolkov as a code owner July 15, 2026 18:49
@besmpl
besmpl force-pushed the besmpl/surface-lifetime-ownership branch from 85eeeb0 to 70f93a0 Compare July 19, 2026 07:14
@codecov

codecov Bot commented Jul 19, 2026

Copy link
Copy Markdown

@besmpl
besmpl force-pushed the besmpl/surface-lifetime-ownership branch from 70f93a0 to 2a069cb Compare July 19, 2026 08:36

@kolkov kolkov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thorough review of this +2091/-112 surface lifecycle refactor and comparison with Rust wgpu-core Surface ownership and wgpu-hal Vulkan swapchain teardown.

The architecture is sound, matches Rust wgpu patterns well, and has exceptional test coverage (~650 lines of focused lifecycle tests). Key observations:

  1. Lease system is correct and simple. The uint64 counter with zero-skip wrap properly invalidates retained wrappers at every lifecycle boundary. The resolveHAL() single-point-of-entry pattern prevents stale HAL access cleanly.
  2. Instance ownership ordering is correct. Devices → Surfaces → native Instance matches the Rust wgpu drop ordering (Surface::Drop takes Presentation which holds Arc<Device>).
  3. Vulkan HAL changes are well-designed. The destroyAfterIdle/abandonDeviceResources split handles both orderly shutdown and device-loss correctly. The beginDestroy atomic transition prevents double-destroy.
  4. Queue validation reorder (command buffer validation before pending.flush()) is a good improvement — prevents stranding flushed internal encoders on user command buffer validation failure.
  5. All five backends covered correctly. Vulkan gets full swapchain tracking, DX12 gets defensive nil guards in releaseBackBuffers, Metal/GLES/Software correctly unchanged at HAL level.

Minor suggestions (non-blocking)

  • surfacesForDevice reads surface.device without a lock on the Surface struct — safe because Device.Release() atomically blocks new Configure() calls first, but documenting why this is safe would help future readers.
  • resolveHAL() is called multiple times per operation in some encoder paths (validation + HAL conversion) — safe because the render loop owns acquire/present lifecycle single-threaded, but noting this assumption in a comment would be valuable.

LGTM.

@besmpl

besmpl commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

@kolkov Thank you for the approval. I followed up on both documentation suggestions in 4913795:

  • documented why Device.Release may inspect surface ownership without a per-surface lock after it blocks new configuration;
  • documented the render-loop serialization invariant that makes repeated resolveHAL calls safe.

This is comments-only with no behavior change. The branch remains mergeable and all 10 repository Actions checks are green. A quick re-review would be appreciated if the follow-up commit dismissed the prior approval.

@kolkov

kolkov commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Prerequisites #264, #265, #266, and #267 are now merged. CI is green on all four merge commits.

#269 has merge conflicts from the overlapping files (device_native.go, hal/vulkan/api.go, adapter_native.go, instance_native.go). Could you rebase onto current main? Our approval stands — once rebased and CI green, we'll merge.

@besmpl
besmpl force-pushed the besmpl/surface-lifetime-ownership branch from 4913795 to aa7654d Compare July 22, 2026 11:46
@besmpl

besmpl commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

@kolkov Done — #269 is rebased onto current main at e96d480, including merged #264, #265, #266, and #267.

I resolved the overlapping lifecycle code by preserving the merged behavior: #264's internal idle-drain/queue teardown ordering and #265's transactional, error-propagating Vulkan swapchain cleanup remain intact, with #269's device/surface ownership tracking layered on top.

Current head is aa7654d; GitHub reports the PR mergeable, and all 10 fresh repository Actions checks are green across Linux, macOS, Windows, GLES integration, lint, formatting, and dependencies.

Local verification also passed:

  • go test -count=1 ./...
  • go vet . ./core ./hal/vulkan
  • Windows amd64 compile-only package matrix

Thank you — it should be ready to merge.

@kolkov
kolkov merged commit 6712ffd into gogpu:main Jul 22, 2026
10 checks passed
kolkov added a commit that referenced this pull request Jul 22, 2026
* docs: update CONTRIBUTING.md — Smart Coding framework, AI-assisted policy, current project structure

* docs: CHANGELOG for merged prerequisite PRs (#264, #265, #266, #267, #269)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants