fix(surface): centralize acquisition and teardown ownership - #269
Conversation
85eeeb0 to
70f93a0
Compare
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
70f93a0 to
2a069cb
Compare
kolkov
left a comment
There was a problem hiding this comment.
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:
- 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. - Instance ownership ordering is correct. Devices → Surfaces → native Instance matches the Rust wgpu drop ordering (
Surface::DroptakesPresentationwhich holdsArc<Device>). - Vulkan HAL changes are well-designed. The
destroyAfterIdle/abandonDeviceResourcessplit handles both orderly shutdown and device-loss correctly. ThebeginDestroyatomic transition prevents double-destroy. - Queue validation reorder (command buffer validation before
pending.flush()) is a good improvement — prevents stranding flushed internal encoders on user command buffer validation failure. - 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)
surfacesForDevicereadssurface.devicewithout a lock on the Surface struct — safe becauseDevice.Release()atomically blocks newConfigure()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.
|
@kolkov Thank you for the approval. I followed up on both documentation suggestions in
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. |
|
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 ( |
4913795 to
aa7654d
Compare
|
@kolkov Done — #269 is rebased onto current 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 Local verification also passed:
Thank you — it should be ready to merge. |
Summary
Make the existing public lifetime contract enforceable without adding a HAL interface:
core.Surfaceowns an opaque acquisition lease, invalidated by present, discard, unconfigure, replacement, and destruction.TextureandTextureViewuse one private HAL resolver, so retained swapchain wrappers fail before native access at render, copy, binding, write, and submit boundaries.Instanceowns the wrappers it creates and releases devices, then platform surfaces, then the native instance; cross-instance surface/device configuration is rejected.Deviceowns the set of configured swapchains and retires all of them in one device-destroy pass. VulkanSurfacecontinues to own onlyVkSurfaceKHR; VulkanInstanceneeds no child registry.Queue.WriteTextureretains 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
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/vulkanGOWORK=off go vet . ./core ./hal/vulkanGOWORK=off go test -tags rust ./...GOWORK=off go mod verifygolangci-lint v2.12.2 run --new-from-rev upstream/main(0 issues)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 at0eb4143eff2bbe0fc50907d009770ce2bfd3c69d. 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.