Repository navigation
feat(vk): shared Vulkan frame ring (vk_frame_ring.h) + FencedStage reuses its command buffer - #74
Merged
Merged
Conversation
…t per use FencedStage allocated and freed a command buffer on every upload/readback — the per-frame allocate/free cost on Adreno that runtime #1936 removed from the compositor. The stage now allocates ONE command buffer at init (the pool already had RESET_COMMAND_BUFFER_BIT) and vkResetCommandBuffer()s it in beginCmd(). Public API (CachedLayerUploader, PipelinedReadback) unchanged. Also drop the header's claim that the runtime's per-frame commit drains the queue: it no longer does (runtime #1923 / #1936). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e (vk_frame_ring.h) dxr::VkFrameRing is the generic half of displayxr-demo-modelviewer #170's fix (13 -> 30 fps on Adreno 740): N slots (default 3, DXR_VK_FRAMES_IN_FLIGHT 1..8 for A/B), command buffers + fences allocated once, an optional persistently-mapped dynamic-offset UBO sliced per slot x view, tile barriers, per-slot deferred deletion, waitAll(), and submitImmediate() as the one named blocking escape hatch. endFrame() makes exactly one vkQueueSubmit and never waits. Header-only, no Vulkan link dependency; reachable through displayxr::rules for the Android legs. tests/vk_frame_ring_test.cpp checks the ring arithmetic, env clamp, UBO slice alignment and deferred-deletion order GPU-free (pure helpers compile without the Vulkan SDK), and compile-checks the full class when Vulkan headers are present. VERSION -> 2.28.0 (tracks the next tag). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On Android (Adreno 740,
msm-adreno-tzgovernor) the GPU clock follows GPU busy %. An app that submits → waits the fence → submits again, per frame or per tile, leaves the GPU idle while the CPU waits. The governor reads ~50 % busy and parks the GPU at ~1/3 clock (220 MHz), which makes each frame slower, which keeps it idle: ~10 fps. displayxr-demo-modelviewer #170 (b060835) fixed this with a 3-deep command-buffer/fence/UBO ring, ONEvkQueueSubmitper frame, a memory barrier between tiles, andendFrame()beforexrReleaseSwapchainImage: 13 → 30 fps.The rule: keep ≥ 2 frames in flight, make one submit per frame, never wait the fence of the frame just submitted, and never allocate command buffers per frame.
displayxr-common has had no shared helper, so the rest of the ecosystem still gets it wrong:
vkQueueWaitIdleper eye + a command-buffer alloc per eyebeginFrame→ ring overflow →vkQueueWaitIdlecube_*_vk_androidvkQueueWaitIdleevery frameThe only Vulkan sync code here,
vk_overlay_kit.h'sdetail::FencedStage, had one slot and allocated and freed a command buffer on every upload. That is the same per-frame Adreno cost runtime #1936 just removed from the compositor.What this PR adds
common/vk_frame_ring.h:dxr::VkFrameRing. Header-only, C++17, no Vulkan link dependency (it calls the loader-exportedvk*symbols the same wayvk_overlay_kit.hdoes).Config::framesInFlightsets it explicitly (the Android path). The env varDXR_VK_FRAMES_IN_FLIGHToverrides it for A/B tests, clamped to 1..8;1reproduces the anti-pattern.submitImmediate.minUniformBufferOffsetAlignment. Because the slots are disjoint ranges of one buffer, a singleUNIFORM_BUFFER_DYNAMICdescriptor set serves every slot.beginFrame()waits only the fence of the slot's previous use (N frames ago), runs that slot's deferred deletions, then resets and begins the command buffer.endFrame(...)ends the command buffer and makes exactly onevkQueueSubmit, with no wait. It returns theVkResult. A begun frame is always submitted, even an empty one. Call it beforexrReleaseSwapchainImage.tileBarrier()is the full ALL_COMMANDS memory barrier from model_renderer.cpp.colorAttachmentBarrier()is a narrower colour-only variant.deferDestroy(fn),waitAll()(for resize and teardown),submitImmediate(record)(the only sanctioned blocking submit),destroy()/ RAII.vk_overlay_kit.h:FencedStagenow allocates its command buffer once and callsvkResetCommandBufferon reuse (the pool already hadRESET_COMMAND_BUFFER_BIT). The public API is unchanged. The header comment no longer claims "the runtime's per-frame commit already drains the queue"; that stopped being true with runtime #1923 / #1936.Wire-up: the header is added to
DISPLAYXR_COMMON_SOURCESand is reachable through thedisplayxr::rulesINTERFACE target (it exposescommon/), with a comment there. The README gets rows forvk_frame_ring.handvk_overlay_kit.h(the latter was missing) and a Vulkan frame discipline section.VERSIONis set to 2.28.0, following the file's "tracks the NEXT tag" convention (latest tag is v2.27.0).Test evidence
tests/vk_frame_ring_test.cppcovers the ring schedule for N = 1..8 (the fence waited at frame k is frame k−N's, and never frame k−1's when N ≥ 2), env parsing and clamping,alignUp, UBO slice offsets (aligned, disjoint, in bounds) andDeferredQueueorder (FIFO, no re-entrancy).DXR_VK_FRAME_RING_PURE_ONLYand run there.vk_overlay_kit.h, andstatic_asserts the public signatures.ctest: 6/6 pass, both with Vulkan headers (brewvulkan-headers) and in the pure-only build.deferDestroy: 27 deletions ran beforewaitAll(30 − N) and 30 after. It also coveredsubmitImmediate, an empty frame, a doubleendFrame(returnsVK_NOT_READY),DXR_VK_FRAMES_IN_FLIGHT=1, and 5 consecutiveCachedLayerUploaderuploads reusing the one command buffer.Follow-ups (not in this PR)
docs/guides/displayxr-app-rules.mdplus acheck_displayxr_app.pylint (flagvkQueueWaitIdle/ per-framevkAllocateCommandBufferson the frame path), then convert thecube_*_vk_*test apps.🤖 Generated with Claude Code