Files
DroidDeck/docs/development/gpu-pending-completion.md
T

5.7 KiB

Compositor work whose completion is unknown

A timed-out fence wait does not cancel submitted GPU work. Continuing after that result can reset a command buffer or fence still in use, or destroy an image and its memory while the GPU may still read them. Previously the finite wait paths returned without putting the compositor into a terminal state, and swapchain teardown ignored the result of vkDeviceWaitIdle.

An unsuccessful completion check other than real VK_ERROR_DEVICE_LOST now puts the compositor in a distinct completion-unknown state. It stops subsequent presentation and retains potentially pending image/memory and swapchain resources until the DroidDeck app process exits. Device loss continues through the existing device-loss path. This is deliberate retention rather than an automatic recovery; restarting a guest session cannot reset the in-process GPU state, so the diagnostic asks for an app restart.

Initialization checks the black-image submit and fence result. An unsuccessful empty completion-marker submit no longer causes a wait on an unsignalled fence. Surface-rebuild and effect-map paths stop when teardown cannot establish idle. Acquire retries also stop: retaining the old swapchain is not permission to acquire from it again. A later frame-generation acquire that ends in a terminal state returns before the completion marker, fence wait or any further recording. Real device loss on present is handled before attempting that marker, too. Mapped SHM bytes are retained unchanged, including same-size wl_shm and single-pixel commits; keeping only the allocation would still race a GPU read. Off-screen pass copy/abort and the internal fence wait reject terminal states without further Vulkan calls. Release fds are still consumed on rejected copies. The normal frame fence still has its existing infinite timeout; this change is not a general GPU hang watchdog.

Externally owned DMA-BUF contents need the same protection. vkp_completion_unknown() exposes the terminal state to compositor-thread lifecycle paths. Replacement, surface/client destruction, cursor/idle-role releases, previously queued paced releases and SurfaceFlinger callbacks no longer return a DMA-BUF to its client after quarantine. The compositor retains DMA-BUF references/backing FDs and AHB references until app process exit. A display fence alone does not establish that the compositor's Vulkan reads completed. Queued releases detach destroyed surfaces and remain held without repeatedly arming an expired timer. New terminal releases require no allocation, so allocation failure cannot fall back to an unsafe release. Copied SHM releases and healthy/genuine-device-loss cleanup keep their existing behaviour. This intentionally prevents further client buffer reuse; restarting the Android app process, not just the guest session, is required.

Regression coverage

python3 -m unittest discover -s tools/tests -p test_vk_pending_completion.py -v compiles the production wait/destructor and swapchain teardown functions with a fake Vulkan dispatch table. The two cases cover success, timeout, host-memory errors, genuine device loss, resource retention and suppressed repeated teardown. No real GPU fault is injected. The harness matches main's Vulkan-image resources; it does not assume the experimental PowerVR branch's additional AHB fields.

python3 -m unittest discover -s tools/tests -p test_vk_terminal_callers.py -v additionally compiles the entire production vk_present.c, with fake Vulkan dispatch callbacks and stub external modules. The compositor's take_shm and take_pixel callers are inserted verbatim, not rewritten. It needs the Android NDK's Vulkan-Headers (ANDROID_HOME/ANDROID_SDK_ROOT/NDK), a host Vulkan development package, or DROIDDECK_VULKAN_INCLUDE; absent headers are explicitly reported as a skip. No Android runtime or GPU is required.

The 12 scenarios include first/later OUT_OF_DATE and SURFACE_LOST followed by an idle failure, a finite blit timeout followed by same-size SHM/single-pixel uploads, normal presentation/frame generation, a healthy later acquire timeout whose earlier work still needs draining, real acquire/present device loss, and terminal off-screen pass entry. Every work-issuing fake callback asserts the device is live. After quarantine, the tests verify retained handles and unchanged mapped bytes, and probe the public render/blit/readback/import/pass entry points for suppressed acquire/submit/wait/reset and destruction. Consumed fds are checked. Against PR #560's 68dc1c45 the same harness compiles, but nine scenarios fail (the three healthy controls pass); the corrected production unit passes all 12.

The baseline production fence/destructor compiles but fails the timeout-state oracle; the corrected functions pass both cases with -Wall -Wextra -Werror. The change was also exercised in an independently integrated Pixel PowerVR Lab, but its short game observations do not establish the cause of the earlier GNOC reboot or sustained device stability. Main's native CI and APK build remain separate validation.

python3 -m unittest discover -s tools/tests -p test_vk_dmabuf_quarantine.py -v compiles the production compositor/AHB release, reference and listener functions verbatim, using the real Wayland list layouts/macros with fake IPC/timer/AHB endpoints. It covers paced replacement, immediate surface cleanup, already queued releases, a mixed DMA-BUF/SHM queue, client destruction, a display-fence callback, healthy cleanup and genuine device loss. It checks withheld releases, retained FDs/references/AHB ownership, removal of surface listeners and disarmed timers. The same harness can test an old source directory via DROIDDECK_DMABUF_TEST_SOURCE; it does not inject a real driver fault or prove long-term device stability.