Skip to content

docs(adr): ADR 0011 — root data as GPU pointer, thin PSOs, split barriers (#691 Phase 6) - #761

Merged
drsnuggles8 merged 1 commit into
masterfrom
docs/adr-0011-root-data-thin-pso-split-barriers-691
Aug 8, 2026
Merged

docs(adr): ADR 0011 — root data as GPU pointer, thin PSOs, split barriers (#691 Phase 6)#761
drsnuggles8 merged 1 commit into
masterfrom
docs/adr-0011-root-data-thin-pso-split-barriers-691

Conversation

@drsnuggles8

@drsnuggles8 drsnuggles8 commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds four new decision sections to ADR 0011, pre-deciding parts of RHI: add a Vulkan backend alongside OpenGL 4.6 — latest point release only, heap-bindless (no legacy descriptor path) #691 Phase 6 before that phase starts — the same pattern §1–§3 already used for Phases 2/3.
    • §4 — root data becomes one GPU pointer per draw/dispatch (bump-allocated struct + single push-constant pointer via VK_KHR_shader_untyped_pointers, already pinned in ADR 0010's capability contract), replacing per-draw UBO/SSBO binding.
    • §4.2 — indirect draw/dispatch takes the same single-pointer contract, so GPU-driven passes (culling, LOD selection, virtual-geometry cluster culling) write their own indirect args on GPU with no CPU round trip.
    • §5 — minimize PSO permutation axes before the first VkPipeline: vertex-pulling via buffer-device-address (extends Phase 3's bindless-texture pattern to vertex data), dynamic depth/stencil/blend state via VK_EXT_extended_dynamic_state{,2,3}.
    • §6 — split barriers modeled as a GPU-pointer Signal/Wait pair, unified with Vulkan's existing timeline semaphores rather than a separate split-barrier/events abstraction.
  • Docs-only change — no code motion, matching ADR 0011's own "no code motion" framing for Phase 1.

Context

Prompted by cross-referencing Sebastian Aaltonen's Reducing Graphics API Complexity talk against the existing #691 design. The headline finding was that ADR 0010/0011 already independently match the sharpest parts of that talk (heap-bindless exclusively, no image-layout tracking, hazard-flag barriers instead of a resource list) — these four sections close the remaining gaps, all scoped to Phase 6 (not started) and consumed pass-by-pass in Phase 7.

Issue #691's Phase 6/7 bullets and Scope section were already updated to reference these sections. The talk's more speculative closing idea (collapsing ~16 shader-stage entry points into one generic kernel type + intrinsics) is deliberately not phased into #691 — it's tracked separately as a watch/re-check issue in #760, since it needs hardware/microcode support that doesn't exist yet.

Test plan

  • Docs-only diff; pre-commit hooks ran clean (no code/test files touched, so clang-format/test-classification steps skipped).
  • N/A build/test — no source files changed.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation
    • Updated the resource and binding architecture decision record with guidance for GPU-visible root data and 64-bit pointers.
    • Documented strategies for reducing pipeline permutations through vertex pulling and dynamic state.
    • Clarified split-barrier synchronization using GPU pointer signaling and timeline-semaphore semantics.
    • Added guidance for GPU-driven indirect draws and their shared root-pointer contract.

…iers (#691 Phase 6)

Pre-decides three more Phase 6 design points ahead of that phase starting,
the same way §1-§3 already pre-decided Phase 2/3's shape:

- §4 — per-draw data (transforms, material scalars, heap indices, buffer
  pointers) becomes one struct from a new GPU-visible bump allocator, reached
  via a single push-constant pointer instead of per-draw UBO/SSBO binding.
- §4.2 — indirect draw/dispatch takes the same single-pointer contract, so
  GPU-driven passes (culling, LOD, virtual geometry) write their own indirect
  args on GPU with no CPU round trip.
- §5 — minimize PSO permutation axes before the first VkPipeline exists:
  vertex-pulling via buffer-device-address, dynamic depth/stencil/blend state.
- §6 — split barriers as a GPU-pointer signal/wait pair, unified with
  Vulkan's existing timeline semaphores instead of a separate abstraction.

Prompted by cross-referencing Sebastian Aaltonen's "Reducing Graphics API
Complexity" talk against the existing ADR 0010/0011 design, which turned out
to already independently match the sharpest parts of it (heap-bindless
exclusively, no image-layout tracking, hazard-flag barriers). These four
sections close the remaining gaps. Issue #691's Phase 6/7 bullets and Scope
section were updated on GitHub to match; the more speculative "one generic
shader-kernel type" idea from the same talk is tracked separately in #760
rather than phased here, since it needs hardware/microcode support that
doesn't exist yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The ADR records decisions for GPU-visible root data, direct and indirect draw contracts, PSO permutation reduction, and split-barrier synchronization.

Changes

RHI model decisions

Layer / File(s) Summary
Root data and draw contracts
docs/adr/0011-rhi-neutral-resource-and-binding-model.md
The ADR defines GPU-visible root structures passed through one 64-bit pointer. It specifies per-command-buffer allocation, Vulkan and OpenGL binding behavior, and shared direct and indirect draw/dispatch contracts.
PSO permutation minimization
docs/adr/0011-rhi-neutral-resource-and-binding-model.md
The ADR removes vertex input from pipeline state through vertex pulling. It makes depth, stencil, and supported blend state dynamic while retaining minimal render-target and rasterization descriptors.
Split-barrier synchronization
docs/adr/0011-rhi-neutral-resource-and-binding-model.md
The ADR defines GPU-memory signal and wait operations with one-shot and monotonically increasing timeline semantics. It assigns the existing RHI::GpuFence mechanism to GPU dependencies and frame pacing.】【。
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the ADR changes: GPU-pointer root data, thinner PSOs, and split barriers.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@drsnuggles8
drsnuggles8 merged commit 36541d8 into master Aug 8, 2026
1 check passed
@drsnuggles8
drsnuggles8 deleted the docs/adr-0011-root-data-thin-pso-split-barriers-691 branch August 8, 2026 18:50

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 9

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/adr/0011-rhi-neutral-resource-and-binding-model.md`:
- Around line 796-805: Update the push-constant discussion to state that
exceeding maxPushConstantsSize makes vkCmdPushConstants invalid rather than
silently truncating data. Preserve the guidance that push constants carry only
the 8-byte GPU pointer, and explicitly reject or avoid inline root payloads that
exceed the device limit.
- Around line 857-861: Update the §4.2 GPU-driven pass access declarations used
by RenderGraph::ComputeBarrierPlan to register the root-data buffer and the
allocator-backed root-data range in addition to the indirect-argument hazard.
Ensure the declared write and consuming access cover the same root-data range so
the compute-to-indirect-draw barrier is generated correctly.
- Around line 838-855: Expand the indirect draw/dispatch contract in section 4.2
to define how commands select root-data records despite Vulkan indirect command
fields lacking a root pointer. Specify whether the pushed pointer targets an
array, how drawId or firstInstance indexes each record, and how GPU-driven
compute writes that mapping; only retain the identical direct-call contract
after documenting the corresponding command and shader layout.
- Around line 927-954: Revise the ADR section describing RHI::GpuFence, Signal,
and Wait to choose a single synchronization model: either map them to Vulkan
VkSemaphore timeline handles and submission-level values, or define a separate
mapped-device-memory protocol with explicit atomic, cache, queue, and host-wait
semantics. Remove claims that timeline semaphores are plain GPU-memory
locations, and specify that GPU signal/wait operations are attached to
submission boundaries rather than arbitrary nodes within one command buffer.
- Around line 900-905: Add a shader-visible vertex-pulling contract before the
Phase 6 checkpoint, defining formats, offsets, strides, index type, index base,
and per-draw selection. Include the pilot shader contract and update the
vertex-pulling sequencing text to make this specification an explicit
prerequisite before implementation.
- Around line 816-818: Update the mapped root-write section to document the
allocation’s host-visible memory flags, explicitly accounting for the absence of
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT. Define the required flush policy: flush
each written range before GPU read access when memory is not host-coherent,
while preserving the existing persistent-mapping behavior.
- Around line 813-815: Revise the lifetime guidance around
TransientPool::ReleaseAll() so allocator storage is reset only after the
relevant RHI::GpuFence or timeline value confirms GPU retirement, rather than at
the frame boundary alone. Update the linear/ring strategy description to
preserve allocations until submitted command buffers finish reading root data
and avoid reusing storage prematurely.
- Around line 891-898: Expand the PSO state-decision table to explicitly cover
cull mode, front face, primitive topology, polygon mode, color write mask,
sample mask, alpha-to-coverage, depth bias, line width, and all other
rasterization or blending flags that can remain static. For each axis, document
its Vulkan feature gate, fallback behavior when dynamic state is unavailable,
and whether it participates in the pipeline key; ensure Phase 6’s capability
audit can verify these decisions.
- Around line 773-785: Add the buffer device-address capability contract to the
ADR section covering root-struct buffer pointers, or centralize it in ADR 0010.
Declare VK_KHR_buffer_device_address and the required
PhysicalStorageBufferAddresses and PhysicalStorageBuffer64 features, along with
buffer usage/allocation flags and address alignment and lifetime requirements;
keep VK_KHR_shader_untyped_pointers documented only for its existing shader-side
use.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 19eae646-1056-4384-91f4-8338857f1d31

📥 Commits

Reviewing files that changed from the base of the PR and between cafd173 and 240e1bb.

📒 Files selected for processing (1)
  • docs/adr/0011-rhi-neutral-resource-and-binding-model.md

Comment on lines +773 to +785
already pointed at, and the shader-side dependency it needs
(`VK_KHR_shader_untyped_pointers`) was *already* pinned in ADR 0010's
capability contract — for the narrower reason of heap-offset dereferencing.
Widening its use to carry the whole root struct is a design decision, not a
new dependency.

**Buffers become pointers, not heap slots.** Vertex, index, and storage data
are addressed by buffer device address embedded directly in the root struct,
mirroring how a texture is addressed via `HeapOffset` (§1.1). The resource heap
therefore continues to hold only texture descriptors, exactly as §1.2a already
decided for the unrelated reason that texture and buffer descriptors aren't
the same size — no analogous "buffer heap" is ever built, and the two
decisions reinforce rather than duplicate each other.

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "Files:"
git ls-files | rg '(^|/)0011-rhi-neutral-resource-and-binding-model\.md$|(^|/)0010-|VK_KHR|StorageBuffer|buffer_device_address|shader_untyped|PhysicalStorageBuffer' || true

echo
echo "Target section:"
if [ -f docs/adr/0011-rhi-neutral-resource-and-binding-model.md ]; then
  sed -n '720,820p' docs/adr/0011-rhi-neutral-resource-and-binding-model.md | cat -n
fi

echo
echo "Search relevant terms in ADRs:"
rg -n "VK_KHR_(shader_untyped_pointers|buffer_device_address)|PhysicalStorageBuffer|BufferDeviceAddress|storageBuffer|buffer address|HeapOffset|pointer" docs/adr -S || true

Repository: drsnuggles8/OloEngineBase

Length of output: 14796


🌐 Web query:

Vulkan VK_KHR_buffer_device_address physicalStorageBuffer buffer device addresses spec

💡 Result:

The Vulkan VK_KHR_buffer_device_address extension, which was promoted to core in Vulkan 1.2 [1][2], provides a mechanism for accessing buffer memory in shaders using 64-bit physical addresses, bypassing the traditional descriptor-based access model [3][4]. This system relies on three main components: 1. API Support (VK_KHR_buffer_device_address): Applications create a VkBuffer with the VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT flag and allocate memory with the VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT flag [1]. Using the command vkGetBufferDeviceAddress, the application retrieves a 64-bit VkDeviceAddress, which represents the start of the buffer [3][5]. This address can be passed to shaders (e.g., via constant buffers or push constants) [3][4]. 2. SPIR-V/Shading Language Support (SPV_KHR_physical_storage_buffer): To use these addresses in shaders, the SPIR-V code must utilize the PhysicalStorageBuffer storage class [3][1]. This is enabled by the PhysicalStorageBufferAddresses capability, which introduces the PhysicalStorageBuffer64 addressing model [6][7]. This model allows for hybrid addressing where pointers in this specific storage class are treated as 64-bit physical pointers, while other storage classes remain logical [6][7]. 3. Usage: Once the shader receives the VkDeviceAddress, it can perform direct reads, writes, and atomic operations on the memory at that location, provided the address is within the range of a valid buffer created with the appropriate usage flags [5][4]. This capability is essential for advanced features like ray tracing and implementing complex, pointer-based data structures directly in GPU memory [3]. Top results: [3][1][6][5][7][2][4]

Citations:


🌐 Web query:

Vulkan VK_KHR_shader_untyped_pointers spec physicalStorageBuffer storageBuffer

💡 Result:

VK_KHR_shader_untyped_pointers does not introduce a special Vulkan descriptor type for physicalStorageBuffer vs storageBuffer; instead, it enables SPIR-V untyped pointers (OpTypeUntypedPointerKHR / OpUntypedVariableKHR / OpUntypedAccessChainKHR) so shaders can reinterpret memory through an “untyped” pointer while still starting from specific, layout-compatible storage classes/objects [1][2]. Key constraint tying it to physicalStorageBuffer storage buffers: 1) Unturned/untyped pointers are limited to storage classes that have an explicit layout. The VK_KHR_shader_untyped_pointers proposal states that it “limits the use of untyped pointers to storage classes with an explicit layout” to ensure consistent data layout across reinterpretations [1][3]. 2) SPIR-V’s untyped-pointer extension (SPV_KHR_untyped_pointers) defines that untyped pointers can be used with physical-storage-buffer-related pointer variables, but it also states restrictions on what variables/pointers may be declared with the PhysicalStorageBuffer storage class: - “OpVariable must not use the PhysicalStorageBuffer storage class. Variables must not use the PhysicalStorageBuffer storage class.” [2] This means you don’t declare a variable directly in the PhysicalStorageBuffer storage class when using SPV_KHR_untyped_pointers; rather, you use untyped-pointer constructs that can point to objects referenced via the PhysicalStorageBuffer addressing model. 3) For PhysicalStorageBuffer pointers (logical vs physical addressing), Vulkan’s physical storage buffer extension (SPV_KHR_physical_storage_buffer) adds the PhysicalStorageBuffer storage class and defines that it represents physical pointers (64-bit addresses) and only applies when the addressing model is PhysicalStorageBuffer64 [4]. So, how physicalStorageBuffer interacts with “storageBuffer” when using VK_KHR_shader_untyped_pointers: - Your storage buffer descriptor in Vulkan is still a storage buffer descriptor type (VK_DESCRIPTOR_TYPE_STORAGE_BUFFER) described as a struct whose members are load/store/atomic in shaders [5]. - When you want physical addressing (PhysicalStorageBuffer / buffer device address) you use SPV_KHR_physical_storage_buffer (and Vulkan equivalents) to access buffer memory using PhysicalStorageBuffer pointers and buffer device addresses, with all loads/stores/atomics through PhysicalStorageBuffer pointers constrained to addresses that fall within the address range of some buffer [5]. - VK_KHR_shader_untyped_pointers then controls how you can reinterpret what you load/store through those pointer accesses, but only where the pointed-to storage class has explicit layout, and with SPIR-V-level constraints preventing direct PhysicalStorageBuffer variable declarations (OpVariable/Variables must not use the PhysicalStorageBuffer storage class) [1][2]. Practical implication (mapping to your terms): - “storageBuffer” (descriptor type / storage buffer block) is the logical storage buffer object the shader normally declares and indexes. - “physicalStorageBuffer” (PhysicalStorageBuffer storage class in SPIR-V) is about how buffer device addresses are treated for pointer addressing; with untyped pointers enabled, you can reinterpret the data via untyped pointer access chains, but you must follow SPIR-V restrictions that disallow declaring variables directly in the PhysicalStorageBuffer storage class [2] and you must ensure the relevant storage class used with untyped pointers has an explicit layout [1].

Citations:


🌐 Web query:

VK_KHR_untyped_buffer_host pointer VK_KHR_buffer_device_address difference

💡 Result:

There is no technical feature or Vulkan extension named VK_KHR_untyped_buffer_host_pointer. It appears this may be a conflation of several different, distinct Vulkan extensions related to memory, pointers, and shaders. To clarify the concepts you may be combining: VK_KHR_buffer_device_address is a significant, widely used Vulkan feature (promoted to core in Vulkan 1.2) that allows applications to query a 64-bit GPU virtual address (VkDeviceAddress) for a VkBuffer [1][2][3]. This address can be used within shaders to access buffer memory via pointers, effectively bypassing standard descriptor sets and enabling complex data structures like linked lists or trees directly on the GPU [2][4]. VK_KHR_shader_untyped_pointers is a separate extension that introduces support for untyped pointers in SPIR-V [5][6]. It allows shader authors to reinterpret data accessed through memory and atomic instructions without requiring the strict type declarations typically associated with strongly-typed pointers [5][6]. This simplifies shader code by separating type interpretation from addressing logic [6]. VK_EXT_external_memory_host relates to importing memory allocated by the host (CPU) into Vulkan using a host pointer [7]. This is an entirely different mechanism used for zero-copy memory sharing between the CPU and GPU, rather than for shader-based pointer arithmetic or buffer addressing [7]. In summary, VK_KHR_buffer_device_address is about creating and using GPU-side pointers for buffers [2][4], while VK_KHR_shader_untyped_pointers is about the flexibility of pointer typing within shader code [5][6]. Neither extension provides the functionality of the other, and they are distinct from host memory import mechanisms [7].

Citations:


🌐 Web query:

Vulkan PhysicalStorageBuffer storage class spec PhysicalStorageBufferAddresses OpTypePointer PhysicalStorageBuffer shader code examples

💡 Result:

The PhysicalStorageBuffer storage class is a feature of SPIR-V, introduced by the SPV_KHR_physical_storage_buffer extension, that enables "buffer device addressing" in Vulkan [1][2]. It allows shaders to access buffer memory using raw 64-bit GPU virtual addresses, bypassing standard descriptor sets [2][3][4]. PhysicalStorageBuffer Overview The storage class is used within the PhysicalStorageBuffer64 addressing model [1][5][6]. Unlike logical addressing (used in standard Vulkan), where pointers are abstract, PhysicalStorageBuffer64 treats pointers to the PhysicalStorageBuffer storage class as physical 64-bit addresses [1][2][6]. Key Technical Requirements: - Capability: Shaders using this must declare OpCapability PhysicalStorageBufferAddresses [2][5][6]. - Memory Model: The SPIR-V module must use the PhysicalStorageBuffer64 addressing model [1][6]. - Alignment: Because the compiler cannot infer the alignment of arbitrary physical addresses, all OpLoad and OpStore operations on PhysicalStorageBuffer pointers must explicitly specify an alignment (e.g., Aligned 8 or Aligned 16) [6]. - Restrictions: OpVariable cannot use the PhysicalStorageBuffer storage class [1][5]. Instead, these pointers are typically passed to the shader via push constants or stored in other buffers [2][3][4]. When used in a variable or function parameter, the pointer must be decorated with either AliasedPointer or RestrictPointer [1][5]. SPIR-V Code Example (Conceptual) The following illustrates defining a pointer type and accessing it, based on SPIR-V specification patterns [2][6]:; Declaration requirements OpCapability PhysicalStorageBufferAddresses OpExtension "SPV_KHR_physical_storage_buffer" OpMemoryModel PhysicalStorageBuffer64 GLSL450; Define the type to point to %int_type = OpTypeInt 32 0 %ptr_type = OpTypePointer PhysicalStorageBuffer %int_type; Example access (requires a base address provided to the shader); %ptr is a loaded 64-bit address, %val is the data to store OpStore %ptr %val Aligned 4 High-Level Usage (GLSL) In GLSL, this feature is accessed via the GL_EXT_buffer_reference extension [7][4]. Instead of standard descriptors, you define a structure with the buffer_reference layout qualifier [4]: #extension GL_EXT_buffer_reference: enable layout(buffer_reference, std430) buffer MyData { vec4 value; }; layout(push_constant) uniform Constants { MyData myPtr; // Receives 64-bit address }; void main { // Access directly via the physical pointer vec4 v = myPtr.value; } This mechanism is essential for complex data structures like linked lists, trees, or graphs that are difficult to implement with static descriptor sets [7][4]. [1][2][4][6]

Citations:


🌐 Web query:

SPV_KHR_physical_storage_buffer PhysicalStorageBuffer variable declaration StorageBuffer storage class

💡 Result:

The SPV_KHR_physical_storage_buffer extension introduces the PhysicalStorageBuffer storage class, which functions differently from the standard StorageBuffer storage class used in Vulkan and other SPIR-V environments [1][2]. Storage Class Distinction The PhysicalStorageBuffer storage class is specifically designed to support physical addressing (using 64-bit addresses), whereas the StorageBuffer class relies on logical addressing [1][2]. While StorageBuffer is typically used for standard shader storage buffer objects (SSBOs) accessed through descriptors, PhysicalStorageBuffer is used for buffer device address (BDA) pointers, allowing shaders to hold and dereference raw device memory addresses [2][3]. Variable Declaration Rules The extension explicitly prohibits the use of PhysicalStorageBuffer with OpVariable in the same way as standard storage classes [1][4]. Specifically: 1. OpVariable Declaration: An OpVariable must not use the PhysicalStorageBuffer storage class directly [1][4]. 2. Memory Object Definition: A memory object in this context is defined as an OpVariable (or function parameter) that holds a pointer to the PhysicalStorageBuffer storage class, or an array of such pointers [1][4]. 3. Aliasing Decorators: When an OpVariable is used to store a pointer of the PhysicalStorageBuffer storage class, the variable must be decorated with exactly one of either AliasedPointer or RestrictPointer [1][5]. This is necessary because pointers in this storage class are often loaded from memory or generated via conversion (e.g., OpConvertUToPtr) rather than being created directly by an OpVariable, requiring these decorations to clarify potential memory aliasing [1][4]. In contrast, a standard StorageBuffer variable is declared normally using OpVariable with a pointer type in the StorageBuffer storage class [6][7].

Citations:


Declare the buffer device-address capability contract.

VK_KHR_shader_untyped_pointers is not the API path for buffer device addresses. The root struct can still pass an address through push constants, but addressable buffers need VK_KHR_buffer_device_address on the Vulkan side, plus PhysicalStorageBufferAddresses, PhysicalStorageBuffer64, buffer usage flags, allocation flags, and alignment/lifetime rules. Add those to this ADR or ADR 0010 so the contract matches the implementation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/adr/0011-rhi-neutral-resource-and-binding-model.md` around lines 773 -
785, Add the buffer device-address capability contract to the ADR section
covering root-struct buffer pointers, or centralize it in ADR 0010. Declare
VK_KHR_buffer_device_address and the required PhysicalStorageBufferAddresses and
PhysicalStorageBuffer64 features, along with buffer usage/allocation flags and
address alignment and lifetime requirements; keep VK_KHR_shader_untyped_pointers
documented only for its existing shader-side use.

Comment on lines +796 to +805
**The Vulkan-specific wrinkle the talk's slides gloss over: push constants are
small.** The guaranteed minimum is 128 bytes (desktop drivers on the ADR 0010
floor typically expose 256), which is nowhere near enough to hold a root
struct with several matrices and pointers inline. The push constant therefore
carries **only the 8-byte GPU pointer** to the bump-allocated struct — never
the struct's fields directly. This matches the talk's own CUDA-kernel-launch
framing (the "argument" is the pointer, not the payload) but is worth stating
explicitly because a first implementation reaching for `vkCmdPushConstants`
with an inline struct will silently truncate past 128/256 bytes rather than
fail loudly.

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Vulkan registry vkCmdPushConstants size validity maxPushConstantsSize silently truncate documentation

💡 Result:

The Vulkan specification does not allow for silent truncation of push constant data when calling vkCmdPushConstants [1][2]. The size parameter provided to vkCmdPushConstants is strictly validated against the device's physical limits, and any call that exceeds the allowed range constitutes a validation error [1][2]. Specifically, the Vulkan specification imposes the following validation requirements for the size parameter: 1. The sum of the offset and size parameters must be less than or equal to the device's maxPushConstantsSize limit (VUID-vkCmdPushConstants-size-00371) [1][2]. 2. The size must be a multiple of 4 bytes (VUID-vkCmdPushConstants-size-00369) [1][2]. 3. The size must be greater than 0 (VUID-vkCmdPushConstants-size-arraylength) [1][2]. Because these are explicitly defined as validation requirements, if an application passes a size that exceeds maxPushConstantsSize, it violates the specification [1][2]. Under the Vulkan validation layers, such a call will trigger a validation error, and behavior is undefined if the application continues execution despite this error. There is no mechanism in the specification that silently truncates the data to fit within the limit; instead, the API call itself is considered invalid [1][2]. Application developers are responsible for ensuring that their push constant updates do not exceed the maxPushConstantsSize limit reported by the physical device [3][4].

Citations:


Correct the push-constant failure description.

vkCmdPushConstants does not silently truncate data beyond maxPushConstantsSize. If offset + size exceeds the device’s push-constant size limit, the call is invalid. Reject an inline root payload instead of relying on truncation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/adr/0011-rhi-neutral-resource-and-binding-model.md` around lines 796 -
805, Update the push-constant discussion to state that exceeding
maxPushConstantsSize makes vkCmdPushConstants invalid rather than silently
truncating data. Preserve the guidance that push constants carry only the 8-byte
GPU pointer, and explicitly reject or avoid inline root payloads that exceed the
device limit.

Comment on lines +813 to +815
with a linear/ring strategy, reset at the same frame boundary
`TransientPool::ReleaseAll()` already uses, so the two lifetimes stay aligned
without inventing a third one (§1.2's "do not invent a third lifetime class"

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)(0011-rhi-neutral-resource-and-binding-model\.md|.*rhi.*|.*RHI.*|.*fence.*|.*TransientPool.*)$|docs/adr/0011-rhi-neutral-resource-and-binding-model.md' || true

echo "== target lines =="
if [ -f docs/adr/0011-rhi-neutral-resource-and-binding-model.md ]; then
  wc -l docs/adr/0011-rhi-neutral-resource-and-binding-model.md
  sed -n '780,830p' docs/adr/0011-rhi-neutral-resource-and-binding-model.md
  echo "== relevant cross-frame lines =="
  sed -n '930,970p' docs/adr/0011-rhi-neutral-resource-and-binding-model.md
fi

echo "== search allocation/fence/TransientPool mentions =="
rg -n "TransientPool::ReleaseAll|ReleaseAll|RHI::GpuFence|GpuFence|root data|map|mapped|timeline|retire|Retire|fence complete|command buffer" docs/adr/0011-rhi-neutral-resource-and-binding-model.md .

Repository: drsnuggles8/OloEngineBase

Length of output: 50382


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== ADR target sections =="
if [ -f docs/adr/0011-rhi-neutral-resource-and-binding-model.md ]; then
  echo "target 780-825:"
  sed -n '780,825p' docs/adr/0011-rhi-neutral-resource-and-binding-model.md
  echo "fence section 930-970:"
  sed -n '930,970p' docs/adr/0011-rhi-neutral-resource-and-binding-model.md
  echo "lifecycle/fence/timeline mentions in ADR:"
  rg -n "TransientPool::ReleaseAll|RHI::GpuFence|GpuFence|timeline|retire|Retire|submitted|GPU|sync|mapped|map|lifetime|frame boundary|cross-frame|Reset|Release" docs/adr/0011-rhi-neutral-resource-and-binding-model.md --max-count 80
fi

echo "== exact allocation/fence references in repo (filtered) =="
rg -n "TransientPool::ReleaseAll|RHI::GpuFence|GpuFence|timeline|Retire|retire|RootData|mapped|ReleaseAll" . \
  --glob '!*.png' --glob '!*.jpg' --glob '!*.jpeg' --glob '!*.gif' \
  --glob '!*.svg' --glob '!*.tiff' --glob '!*.otf' --glob '!*.ttf' \
  --glob '!*.woff' --glob '!*.woff2' --glob '!*.eot' --glob '!*.map' \
  --max-count 120

Repository: drsnuggles8/OloEngineBase

Length of output: 50382


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

path = Path("docs/adr/0011-rhi-neutral-resource-and-binding-model.md")
text = path.read_text()
start = text.index("reset at the same frame boundary")
end = text.index("This is the one part of", start)
proposal = text[start:end].strip()

checks = {
    "resets_at_TransientPool_ReleaseAll_boundary": "TransientPool::ReleaseAll()" in proposal,
    "persistently_mapped": "persistently mapped" in proposal.lower(),
    "backed_by_VMA": "VMA" in proposal and "Vulkan Memory Allocator" not in proposal,
    "cpu_writes_directly": "CPU writes" in proposal and "into GPU memory" in proposal,
    "timeline_semaphore_present": re.search(r"\b**timeline semaphore**", text) is not None,
    "gpu_fence_uses_same_lifetime": "same per-frame lifetime discipline" in text and "RHI::GpuFence" in text,
}
for k, v in checks.items():
    print(f"{k}={v}")

print("\nRelevant source:")
print(proposal)
print("\nFence allocation snippet:")
print(text[text.index("RHI::GpuFence"):text.index("one persistently-mapped counter", text.index("RHI::GpuFence")) + len("one persistently-mapped counter")])
PY

Repository: drsnuggles8/OloEngineBase

Length of output: 952


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

path = Path("docs/adr/0011-rhi-neutral-resource-and-binding-model.md")
text = path.read_text()
start = text.index("reset at the same frame boundary")
end = text.index("This is the one part of", start)
proposal = text[start:end].strip()

checks = {
    "resets_at_TransientPool_ReleaseAll_boundary": "TransientPool::ReleaseAll()" in proposal,
    "persistently_mapped": "persistently mapped" in proposal.lower(),
    "backed_by_VMA": "VMA" in proposal and "Vulkan Memory Allocator" not in proposal,
    "cpu_writes_directly": "CPU writes" in proposal and "into GPU memory" in proposal,
    "timeline_semaphore_present": "**timeline semaphore**" in text,
    "gpu_fence_uses_same_lifetime": "same per-frame lifetime discipline" in text and "RHI::GpuFence" in text,
    "proposal_mentions_retirement_or_fence_completed": bool(re.search(r"(retir|fence complete|timeline completed)", proposal, flags=re.I)),
}
for k, v in checks.items():
    print(f"{k}={v}")

print("\nRelevant source:")
print(proposal)
print("\nFence allocation snippet:")
print(text[text.index("RHI::GpuFence"):text.index("one persistently-mapped counter", text.index("RHI::GpuFence")) + len("one persistently-mapped counter")])
PY

Repository: drsnuggles8/OloEngineBase

Length of output: 944


Tie allocator reset to GPU retirement.

TransientPool::ReleaseAll() is a frame boundary, but it does not guarantee submitted command buffers have stopped reading root data. The same lifetime is reused for cross-frame RHI::GpuFence values, so retire allocations after the relevant timeline or fence completes instead of resetting storage while the GPU can still reference it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/adr/0011-rhi-neutral-resource-and-binding-model.md` around lines 813 -
815, Revise the lifetime guidance around TransientPool::ReleaseAll() so
allocator storage is reset only after the relevant RHI::GpuFence or timeline
value confirms GPU retirement, rather than at the frame boundary alone. Update
the linear/ring strategy description to preserve allocations until submitted
command buffers finish reading root data and avoid reusing storage prematurely.

Comment on lines +816 to +818
rule applies here too). Because the allocation is persistently mapped
(ReBAR/UMA, per the driver floor), the CPU writes the struct's fields directly
into GPU memory — no staging buffer, no copy command. This is the one part of

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the relevant ADR section, plus nearby allocation/mapping mentions.
file="docs/adr/0011-rhi-neutral-resource-and-binding-model.md"
if [ -f "$file" ]; then
  echo "== file exists =="
  wc -l "$file"
  echo
  echo "== relevant lines 780-845 =="
  sed -n '780,845p' "$file" | nl -ba -v780
else
  echo "missing $file"
fi

echo
echo "== allocate/map/host coherency mentions in ADR =="
rg -n -i 'persist.map|persistently mapped|VMA|vma|vk_allocate_memory|deviceLocal|hostVisible|HOST_COHERENT|memory property|mapping|map memory|flush|invalidate' "$file"

Repository: drsnuggles8/OloEngineBase

Length of output: 313


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="docs/adr/0011-rhi-neutral-resource-and-binding-model.md"

echo "== relevant lines 780-845 =="
sed -n '780,845p' "$file"

echo
echo "== allocation/map/host coherency mentions in ADR =="
rg -n -i 'persist.map|persistently mapped|VMA|vma|vk_allocate_memory|deviceLocal|hostVisible|HOST_COHERENT|memory property|mapping|map memory|flush|invalidate' "$file"

Repository: drsnuggles8/OloEngineBase

Length of output: 6847


🌐 Web query:

Vulkan deviceLocalHostVisible mapped resource HCU coherence without host coherency memory property

💡 Result:

When working with Vulkan memory that is host-visible but lacks the VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, the memory is considered non-coherent [1][2]. In this state, the host (CPU) and device (GPU) may maintain separate, inconsistent views of the memory in their respective caches [3][4]. To manage visibility and availability of data in non-coherent memory, you must manually manage cache consistency using the following mechanisms: 1. CPU Cache Management: Before the device reads data written by the CPU, you must call vkFlushMappedMemoryRanges to flush the CPU's writes out to the memory domain [5][4]. Conversely, before the CPU reads data written by the device, you must call vkInvalidateMappedMemoryRanges to discard any stale CPU cache lines and pull in the latest data from the memory domain [6][3]. 2. Alignment Constraints: When working with non-coherent memory, operations must be performed on memory ranges aligned to the VkPhysicalDeviceLimits::nonCoherentAtomSize [5][4]. If you do not explicitly flush or invalidate on these boundaries, you may encounter data inconsistencies or race conditions [5][4]. 3. Execution Synchronization: Even if memory is coherent, it does not imply synchronization of execution [3]. You must always use proper Vulkan synchronization primitives (such as barriers, fences, or semaphores) to ensure that the producer has finished writing and the consumer is ready to read [3][5][7]. Coherence only handles the data movement between caches; it does not replace the need to order operations relative to one another [3]. In summary, for memory that is Device Local and Host Visible but not Host Coherent, you are responsible for both explicit cache management (flushing/invalidating on the CPU) and explicit execution synchronization (to prevent data races) [3][5][4]. If a memory type lacks the host coherent property, the implementation will not automatically make writes visible across the CPU/GPU boundary [1][3].

Citations:


🌐 Web query:

Vulkan specification mapped memory HCU coherence hostVisibleDeviceLocal device writes CPU map memory coherent

💡 Result:

In Vulkan, memory coherence and visibility are governed by specific memory property flags and synchronization requirements [1][2]. Here is the breakdown of how these concepts interact with host-mapped memory and device-local memory. Memory Property Flags VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT: This flag indicates that the memory can be mapped into the host's address space using vkMapMemory, allowing the CPU to read or write to it directly [1][3]. VK_MEMORY_PROPERTY_HOST_COHERENT_BIT: When this flag is set, host cache management (vkFlushMappedMemoryRanges and vkInvalidateMappedMemoryRanges) is not required [1][2]. The hardware automatically ensures that CPU writes to the mapped memory are available to the device, and device writes are visible to the CPU [1][4]. Host-Visible, Device-Local Memory It is possible to allocate memory that is both host-visible and device-local [5][6]. This allows the CPU to access the memory directly while the GPU enjoys high-performance access as if it were VRAM [6][7]. When this memory also has the HOST_COHERENT_BIT set, the CPU and GPU can interact without explicit cache flushing [6][7]. Device Writes and CPU Mapping Regardless of coherence, you must guarantee synchronization when the device and host share access to memory [3]. If the memory is host-coherent, you do not need to call vkFlushMappedMemoryRanges or vkInvalidateMappedMemoryRanges, but you must still provide proper execution dependencies (e.g., using pipeline barriers or semaphores) to ensure that the device has finished writing to or reading from the memory before the CPU accesses it, and vice versa [3][7]. If the memory is NOT host-coherent, you must manually manage cache visibility using vkFlushMappedMemoryRanges and vkInvalidateMappedMemoryRanges [1][3]. Furthermore, when mapping such memory, you are responsible for flushing or invalidating the memory in increments of at least VkPhysicalDeviceLimits::nonCoherentAtomSize to ensure data integrity [3][8]. Summary Table Memory Property | Host Cache Management (Flush/Invalidate) Required? | Notes --- | --- | --- Host Coherent | No [1][7] | Still requires execution synchronization (barriers/semaphores) [3][7]. Non-Coherent | Yes [1][3] | Must manage flush/invalidate ranges (aligned to nonCoherentAtomSize) [3][8]. For optimal performance, applications typically use device-local memory for steady-state data and host-visible/coherent memory for dynamic updates or staging [6][7].

Citations:


Define host visibility for mapped root writes.

This allocation is host-visible, but persistent mapped memory is not host-coherent by default. If the Vulkan/VMA allocation lacks VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, flush each written range before GPU read access; document the allocation flags and flush policy here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/adr/0011-rhi-neutral-resource-and-binding-model.md` around lines 816 -
818, Update the mapped root-write section to document the allocation’s
host-visible memory flags, explicitly accounting for the absence of
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT. Define the required flush policy: flush
each written range before GPU read access when memory is not host-coherent,
while preserving the existing persistent-mapping behavior.

Comment on lines +838 to +855
### 4.2 GPU-driven indirect root data: no separate shape for a compute-written draw

Because root data is just a GPU pointer, the struct an ordinary draw call
points to and the struct an *indirect* draw call points to are the same kind
of thing — the only difference is who writes it. **Decision: indirect
draw/dispatch takes the identical single-pointer root-data contract as a
direct call.** There is no separate "indirect root data" shape, no CPU-side
indirect-args staging step, and no driver-managed command-signature object
(the DX12-shaped `ID3D12CommandSignature` has nothing to abstract here,
because the pointer *is* the argument list already). A GPU-driven pass —
visibility/occlusion culling, LOD selection, virtual-geometry cluster culling
(`VirtualClusterCull.comp`, per `docs/agent-rules/cluster-lod-simplification.md`)
— writes its output root-data structs directly into GPU memory and issues the
indirect draw pointing at them; nothing round-trips through the CPU. This
codebase already has prior art for "a GPU-written buffer is the argument list,"
in the two-counter overflow contract issue #725's GPU debug draws use — the
same shape, applied here to indirect draw/dispatch arguments instead of debug
primitives.

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate ADR and related files =="
fd -a '0011-rhi-neutral-resource-and-binding-model|cluster-lod-simplification|RenderGraph|BarrierPlan|Indirect' . | sed 's#^\./##' | head -200

echo
echo "== Inspect relevant ADR lines =="
if [ -f docs/adr/0011-rhi-neutral-resource-and-binding-model.md ]; then
  sed -n '800,870p' docs/adr/0011-rhi-neutral-resource-and-binding-model.md | cat -n
fi

echo
echo "== Search for indirect draw/dispatch/vk indirect definitions and root data terms =="
rg -n "indirect|DrawIndirect|DispatchIndirect|root data|root pointer|RootData|drawId|firstInstance|draw index|pipeline layout|descriptor" docs src include apps test -S 2>/dev/null | head -300

Repository: drsnuggles8/OloEngineBase

Length of output: 30606


🌐 Web query:

Vulkan VkDrawIndirectCommand fields VkDispatchIndirectCommand fields documentation

💡 Result:

In Vulkan, the VkDrawIndirectCommand and VkDispatchIndirectCommand structures are used to provide parameters for indirect drawing and compute dispatching commands, respectively. Both structures are composed entirely of 32-bit unsigned integers [1][2]. VkDrawIndirectCommand Structure The VkDrawIndirectCommand structure specifies the parameters for an indirect drawing command [1]: typedef struct VkDrawIndirectCommand { uint32_t vertexCount; uint32_t instanceCount; uint32_t firstVertex; uint32_t firstInstance; } VkDrawIndirectCommand; Fields: vertexCount: The number of vertices to draw [1][3]. instanceCount: The number of instances to draw [1][3]. firstVertex: The index of the first vertex to draw [1][3]. firstInstance: The instance ID of the first instance to draw [1][3]. The members of this structure have the same meaning as the parameters of the vkCmdDraw command [1][4]. VkDispatchIndirectCommand Structure The VkDispatchIndirectCommand structure specifies the parameters for an indirect dispatching command [2]: typedef struct VkDispatchIndirectCommand { uint32_t x; uint32_t y; uint32_t z; } VkDispatchIndirectCommand; Fields: x: The number of local workgroups to dispatch in the X dimension [2][5]. y: The number of local workgroups to dispatch in the Y dimension [2][5]. z: The number of local workgroups to dispatch in the Z dimension [2][5]. The members of this structure have the same meaning as the corresponding parameters of the vkCmdDispatch command [2][5]. These values must be less than or equal to the corresponding limits defined in VkPhysicalDeviceLimits::maxComputeWorkGroupCount [2][5].

Citations:


Define how indirect commands select root data.

VkDrawIndirectCommand contains only vertexCount, instanceCount, firstVertex, and firstInstance; VkDispatchIndirectCommand contains only x, y, and z. Neither carries the 64-bit root-pointer value.

State whether one pushed pointer addresses an array of root records, how drawId or firstInstance selects each record, and how the GPU-driven compute pass writes that mapping. Do not claim the indirect path uses the same direct-call contract until that selection is part of the command and shader layout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/adr/0011-rhi-neutral-resource-and-binding-model.md` around lines 838 -
855, Expand the indirect draw/dispatch contract in section 4.2 to define how
commands select root-data records despite Vulkan indirect command fields lacking
a root pointer. Specify whether the pushed pointer targets an array, how drawId
or firstInstance indexes each record, and how GPU-driven compute writes that
mapping; only retain the identical direct-call contract after documenting the
corresponding command and shader layout.

Comment on lines +857 to +861
The only new requirement is the ordinary one: a barrier between the writing
compute dispatch and the consuming indirect draw, using §1.5's existing
hazard-flag model (an indirect-args hazard, the same category §1.5 already
names for descriptor writes). No new barrier machinery — this is an
application of §1.5, not an extension of it.

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Repository files around ADR 0011:"
if [ -f docs/adr/0011-rhi-neutral-resource-and-binding-model.md ]; then
  wc -l docs/adr/0011-rhi-neutral-resource-and-binding-model.md
  sed -n '820,880p' docs/adr/0011-rhi-neutral-resource-and-binding-model.md | cat -n
else
  echo "doc file missing"
fi

echo
echo "Search for RenderGraph::ComputeBarrierPlan and hazard terminology:"
rg -n "RenderGraph::ComputeBarrierPlan|ComputeBarrierPlan|indirect-args|indirect args|indirect[ -]arg|root-data|root_data|hazard|barrier" docs src . --glob '!*' 2>/dev/null | head -n 200

Repository: drsnuggles8/OloEngineBase

Length of output: 4213


Register the root-data buffer hazard too.

§4.2 says a GPU-driven pass writes root-data structs directly and issues the indirect draw. If RenderGraph::ComputeBarrierPlan builds barriers from declared pass access ranges, add a root-data access/range declaration for the same buffer. An indirect-argument barrier does not cover the writing compute dispatch against the consumer’s allocator-backed root-data range.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/adr/0011-rhi-neutral-resource-and-binding-model.md` around lines 857 -
861, Update the §4.2 GPU-driven pass access declarations used by
RenderGraph::ComputeBarrierPlan to register the root-data buffer and the
allocator-backed root-data range in addition to the indirect-argument hazard.
Ensure the declared write and consuming access cover the same root-data range so
the compute-to-indirect-draw barrier is generated correctly.

Comment on lines +891 to +898
**Decision, per state axis, following Aaltonen's thin-PSO argument:**

| Axis | Decision | Why it's safe on this ADR's driver floor |
| --- | --- | --- |
| Vertex input layout | **Not baked at all.** Extend Phase 3's bindless-texture pulling pattern to vertex data: vertex shaders fetch through a buffer-device-address pointer with manual indexing, not `VkPipelineVertexInputStateCreateInfo`. | Removes the axis entirely rather than making it cheaper — the same move §4 makes for buffers generally, applied to the one buffer type (vertex) that still has a dedicated binding mechanism. |
| Depth/stencil state | Dynamic, via `VK_EXT_extended_dynamic_state` / `…state2` (core-promoted). | Universal on desktop; Metal has proven the same split safe across Nvidia/AMD/Intel/Apple since its first version (talk, §4.8). |
| Blend state | Dynamic where `VK_EXT_extended_dynamic_state3`'s blend-enable/equation states are available; baked into the PSO only where they aren't. | The talk's finding is that *only* Apple/PowerVR mobile GPUs genuinely require blend baked into the shader — irrelevant to this ADR's Vulkan desktop floor (NVIDIA/AMD), so the fallback path should never actually trigger here. Verify at Phase 6's device-capability audit rather than assume — same discipline ADR 0010 used for the heap extension's feature bits. |
| Rasterizer/render-target description | Stays minimal: target formats/count, depth/stencil format, sample count, dual-source blending flag. Nothing else. | This is already the shape §1's survey implies; stating it here makes it a decision instead of an accident. |

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## repo files matching ADR 0011"
git ls-files | rg '(^|/)0011-rhi-neutral-resource-and-binding-model\.md$|ads/adr/0011|docs/adr/0011' || true

echo "## file size"
wc -l docs/adr/0011-rhi-neutral-resource-and-binding-model.md

echo "## relevant lines 860-920"
sed -n '860,920p' docs/adr/0011-rhi-neutral-resource-and-binding-model.md | cat -n

echo "## search for dynamic state and PSO keys"
rg -n "ExtendedDynamicState|extended_dynamic_state|pipeline binding|pipeline key|pipelineKeys|pipeline key|cull|front face|primitive topology|polygon|sample mask|alpha-to-coverage|blend-enable|blend equation" docs/adr/0011-rhi-neutral-resource-and-binding-model.md

Repository: drsnuggles8/OloEngineBase

Length of output: 6517


🌐 Web query:

Vulkan VK_EXT_extended_dynamic_state3 dynamicStateBlendEnable dynamicStateBlendEquationsEXT dynamicStateAlphaToCoverageEnableEXT dynamicStateSampleMaskEXT dynamicStateExclusiveScissorEnableNV

💡 Result:

The extension VK_EXT_extended_dynamic_state3 is a Vulkan device extension that allows applications to specify various pipeline states dynamically, reducing the need for creating and binding a large number of monolithic pipelines [1][2]. The states you mentioned are part of the functionality added by this extension [2][3]. Below is a breakdown of these specific dynamic states: VK_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT This allows the blendEnable state (defined per attachment in VkPipelineColorBlendAttachmentState) to be set dynamically using vkCmdSetColorBlendEnableEXT [4][5]. VK_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT This enables dynamic control over blending equations (specifically srcColorBlendFactor, dstColorBlendFactor, colorBlendOp, srcAlphaBlendFactor, dstAlphaBlendFactor, and alphaBlendOp) for each color attachment using vkCmdSetColorBlendEquationEXT [4][6]. VK_DYNAMIC_STATE_ALPHA_TO_COVERAGE_ENABLE_EXT This allows the alphaToCoverageEnable state (originally from VkPipelineMultisampleStateCreateInfo) to be set dynamically using vkCmdSetAlphaToCoverageEnableEXT [7][8]. VK_DYNAMIC_STATE_SAMPLE_MASK_EXT This permits the sample mask to be set dynamically using vkCmdSetSampleMaskEXT instead of being baked into the pipeline via VkPipelineMultisampleStateCreateInfo::pSampleMask [7][9]. VK_DYNAMIC_STATE_EXCLUSIVE_SCISSOR_ENABLE_NV This state is associated with the VK_NV_scissor_exclusive extension and allows toggling the exclusive scissor test dynamically if supported by the implementation [2][7]. To use these, an application must enable the VK_EXT_extended_dynamic_state3 extension and ensure the corresponding feature bits in the VkPhysicalDeviceExtendedDynamicState3FeaturesEXT structure are supported and enabled (e.g., extendedDynamicState3ColorBlendEnable, extendedDynamicState3SampleMask, etc.) [3][10][6]. When these states are enabled, the corresponding values in the pipeline state objects are ignored at draw time, and the command buffer must have the state explicitly set [5][8][9].

Citations:


🌐 Web query:

Vulkan VK_EXT_extended_dynamic_state2 dynamicStateColorBlendEnable dynamicStateColorBlendFuncEXT dynamicStateViewportWithCountEXT dynamicStateScissorWithCountEXT dynamicStateDepthBiasEXT dynamicStateLinePolygonOffsetEXT dynamicStateDepthBounds dynamicStateStencilCompareMask dynamicStateStencilReference dynamicStateStencilWriteMask dynamicStateSampleLocationsEXT

💡 Result:

The Vulkan dynamic states you listed are part of various "extended dynamic state" extensions designed to reduce the number of Pipeline State Objects (PSOs) an application must compile and bind [1][2]. They are not all part of the same extension; they are distributed across VK_EXT_extended_dynamic_state, VK_EXT_extended_dynamic_state2, and VK_EXT_extended_dynamic_state3 [3][1][4]. Below is the classification of the requested states and their corresponding commands/extensions: 1. VK_EXT_extended_dynamic_state: - dynamicStateViewportWithCountEXT: Maps to VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT and the command vkCmdSetViewportWithCountEXT [5][4]. - dynamicStateScissorWithCountEXT: Maps to VK_DYNAMIC_STATE_SCISSOR_WITH_COUNT and the command vkCmdSetScissorWithCountEXT [6][4]. 2. VK_EXT_extended_dynamic_state2: - This extension primarily introduced states for primitive topology, patch control points, rasterizer discard, depth bias enable, and primitive restart enable [7][8]. - Note: The states you listed such as dynamicStateDepthBiasEXT (often associated with vkCmdSetDepthBias) and stencil-related states (e.g., dynamicStateStencilCompareMask, dynamicStateStencilReference, dynamicStateStencilWriteMask) are part of core Vulkan (1.0+) [9][4]. They are not introduced by VK_EXT_extended_dynamic_state2. 3. VK_EXT_extended_dynamic_state3: - dynamicStateColorBlendEnable: Maps to VK_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT and the command vkCmdSetColorBlendEnableEXT [10][9][11]. - dynamicStateColorBlendFuncEXT: While there is no direct command named this, VK_EXT_extended_dynamic_state3 provides vkCmdSetColorBlendEquationEXT and vkCmdSetColorBlendAdvancedEXT to dynamically set blending equations and factors [3][12][11]. - dynamicStateSampleLocationsEXT: Maps to VK_DYNAMIC_STATE_SAMPLE_LOCATIONS_EXT [9][4]. 4. Other/Miscellaneous: - dynamicStateLinePolygonOffsetEXT: There is no dynamic state by this exact name. Line rasterization and polygon offset states are managed via various extensions (e.g., VK_EXT_line_rasterization for line stipple, or standard pipeline state for depth bias/polygon offset) [3][9][4]. - dynamicStateDepthBounds: This is part of core Vulkan (1.0+) [9]. - dynamicStateStencilCompareMask, dynamicStateStencilReference, dynamicStateStencilWriteMask: These are all part of core Vulkan (1.0+) [9]. In summary, these states allow for granular control of pipeline parameters at command buffer recording time [1][2]. Always check VkPhysicalDeviceFeatures to ensure your target hardware supports these specific extensions and their corresponding features [13][3][14].

Citations:


🌐 Web query:

Vulkan Khronos Extension registry VK_EXT_extended_dynamic_state2 dynamicStatePrimitiveTopologyEXT dynamicStatePatchControlPointsEXT dynamicStateRasterizerDiscardEnableEXT dynamicStateDepthBiasEnableEXT dynamicStateLogicOpEXT dynamicStatePrimitiveRestartEnableEXT

💡 Result:

The Vulkan extension VK_EXT_extended_dynamic_state2 adds several dynamic states to the graphics pipeline, allowing applications to modify specific states without rebinding or re-creating pipeline objects [1][2]. Key aspects of this extension include: Promotion to Core: Several features originally introduced in VK_EXT_extended_dynamic_state2 were promoted to core functionality in Vulkan 1.3 [1][3]. The following states were promoted, with the EXT suffix removed in the core API [1][4]: - VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE (via vkCmdSetDepthBiasEnable) [1][2] - VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE (via vkCmdSetPrimitiveRestartEnable) [1][2] - VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE (via vkCmdSetRasterizerDiscardEnable) [1][2] Non-Promoted Features: The following states and their corresponding commands remain as extension-specific functionality and were not promoted to core Vulkan 1.3 [1][3]: - dynamicStateLogicOpEXT (via vkCmdSetLogicOpEXT; uses VK_DYNAMIC_STATE_LOGIC_OP_EXT) [1][5][6] - dynamicStatePatchControlPointsEXT (via vkCmdSetPatchControlPointsEXT; uses VK_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT) [1][5][6] Feature Requirements: To use these features, the application must enable them via the VkPhysicalDeviceExtendedDynamicState2FeaturesEXT structure when creating a device [2][5]. This structure contains the following boolean fields [5][7]: - extendedDynamicState2: Enables the base dynamic states (Depth Bias, Primitive Restart, and Rasterizer Discard) [5]. - extendedDynamicState2LogicOp: Enables support for dynamic logic operations [5]. - extendedDynamicState2PatchControlPoints: Enables support for dynamic patch control points [5]. Note on Primitive Topology: While your query mentions dynamicStatePrimitiveTopologyEXT, this specific state is not part of VK_EXT_extended_dynamic_state2. Dynamic primitive topology is typically handled by other extensions, such as VK_EXT_primitive_topology_list_restart or features available in later Vulkan versions and other extensions like VK_EXT_shader_object [8].

Citations:


🌐 Web query:

Vulkan dynamic state pipeline binding VK_DYNAMIC_STATE_CULL_MODE_EXT VK_DYNAMIC_STATE_FRONT_FACE_EXT VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT VK_DYNAMIC_STATE_LINE_RASTERIZATION_MODE_EXT VK_DYNAMIC_STATE_COLOR_WRITE_MASK_EXT

💡 Result:

In Vulkan, these dynamic state constants allow applications to modify specific pipeline states during command buffer recording rather than baking them into monolithic pipeline objects [1][2]. This flexibility helps reduce the number of pipeline state objects (PSOs) that need to be created and compiled [1][3]. The specified dynamic states fall into different categories and extension support levels: 1. Core Vulkan 1.3 States: The following states were originally introduced via the VK_EXT_extended_dynamic_state extension and were promoted to core functionality in Vulkan 1.3 (with the _EXT suffix omitted in core) [4][3]: - VK_DYNAMIC_STATE_CULL_MODE: Allows dynamic setting of the cull mode via vkCmdSetCullMode [4][5]. - VK_DYNAMIC_STATE_FRONT_FACE: Allows dynamic setting of the front-facing triangle orientation via vkCmdSetFrontFace [4][6]. - VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY: Allows dynamic setting of the input assembly primitive topology via vkCmdSetPrimitiveTopology [4][7]. 2. Extended Dynamic State 3 (VK_EXT_extended_dynamic_state3): These states are provided by the VK_EXT_extended_dynamic_state3 extension [8]: - VK_DYNAMIC_STATE_LINE_RASTERIZATION_MODE_EXT: Allows dynamic setting of the line rasterization mode via vkCmdSetLineRasterizationModeEXT (requires support for VK_EXT_line_rasterization or similar) [8][9]. - VK_DYNAMIC_STATE_COLOR_WRITE_MASK_EXT: Allows dynamic setting of the color write masks (this is part of the state managed by extended_dynamic_state3) [8]. Usage: To use these states, you must include the corresponding enumeration value in the pDynamicStates array within the VkPipelineDynamicStateCreateInfo structure when creating a graphics pipeline [1][4]. If a state is enabled as dynamic, the corresponding static value defined in the original pipeline creation structure (e.g., VkPipelineRasterizationStateCreateInfo) is ignored, and the state must be set using the associated command (e.g., vkCmdSetCullMode) before any drawing commands that rely on that state [4][5][6][9]. For states provided by extensions like VK_EXT_extended_dynamic_state3, hardware support is optional, and you should check the appropriate feature structures to ensure the device supports the specific dynamic states you intend to use [8][2].

Citations:


🌐 Web query:

Vulkan VK_EXT_extended_dynamic_state_depth_bias clamp color blend advanced polygon offset line width

💡 Result:

In the Vulkan API, the specific functionality you are asking about is spread across multiple "Extended Dynamic State" extensions rather than a single extension named "extended_dynamic_state_depth_bias" [1][2]. The primary extensions that address these dynamic state requirements are VK_EXT_extended_dynamic_state, VK_EXT_extended_dynamic_state2, and later additions in core Vulkan 1.3 [1][2][3]. Key breakdown of these states: Depth Bias and Polygon Offset: The dynamic state for depth bias (often associated with polygon offset) is managed via: - VK_DYNAMIC_STATE_DEPTH_BIAS [4]: Allows setting depth bias factors (constant, clamp, and slope) dynamically [4]. - VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE [4]: Introduced in VK_EXT_extended_dynamic_state2 (and later core Vulkan 1.3), this allows toggling whether depth bias is enabled [5][1][4]. Line Width: - VK_DYNAMIC_STATE_LINE_WIDTH is part of core Vulkan and has been available since version 1.0, not requiring an extension to be used dynamically [4]. Color Blend and Advanced States: - While basic blend state is standard, advanced dynamic state control for blending and other fixed-function states is typically handled by extensions like VK_EXT_extended_dynamic_state3 (which covers many more states not included in the earlier versions) or through specific hardware features [6]. - "Clamp color" is not a standard dynamic state enum; color clamping is generally handled via pipeline configuration or specific platform-dependent features [7][8]. In summary, if you are looking to move static pipeline states to dynamic command buffer calls to reduce pipeline creation overhead, you should use the combination of VK_EXT_extended_dynamic_state, VK_EXT_extended_dynamic_state2, and evaluate VK_EXT_extended_dynamic_state3 if you require further dynamic control [1][2][6]. Core Vulkan 1.3 incorporates most of the functionality from the first two extensions [1][2][3].

Citations:


Define every PSO dynamic-state axis and fallback.

The table currently uses only high-level labels, while the remaining static state can still multiply PSOs if Phase 6 does not lock it down. List cull mode, front face, primitive topology, polygon mode, color write mask, sample mask, alpha-to-coverage, depth bias, line width, and any rasterization or blending flags that may bake into a PSO. For each axis, specify the feature gate, fallback behavior when the dynamic feature is absent, and whether it contributes to the pipeline key.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/adr/0011-rhi-neutral-resource-and-binding-model.md` around lines 891 -
898, Expand the PSO state-decision table to explicitly cover cull mode, front
face, primitive topology, polygon mode, color write mask, sample mask,
alpha-to-coverage, depth bias, line width, and all other rasterization or
blending flags that can remain static. For each axis, document its Vulkan
feature gate, fallback behavior when dynamic state is unavailable, and whether
it participates in the pipeline key; ensure Phase 6’s capability audit can
verify these decisions.

Comment on lines +900 to +905
**Sequencing note:** vertex-pulling is comparable in size to Phase 3's
bindless-texture rehearsal (a real conversion across every draw call, not a
config flag), so it should be an explicit Phase 6 sub-step completed before
the "one pass renders correctly" checkpoint — not deferred to Phase 7, where
retrofitting it after 35 passes have been ported against a baked vertex-input
PSO would cost far more than doing it once, up front, on the one pilot pass.

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files matching vertex pull / OpenGLRendererAPI:"
git ls-files | rg '(^docs/adr/0011-rhi-neutral-resource-and-binding-model\.md$|OloEngine/src/Platform/OpenGL/OpenGLRendererAPI\.cpp$|vertex.?pull|drawing-indirect|vertex-input)' || true

echo
echo "ADR lines 880-920:"
sed -n '880,920p' docs/adr/0011-rhi-neutral-resource-and-binding-model.md || true

echo
echo "OpenGLRendererAPI lines 740-770:"
sed -n '740,770p' OloEngine/src/Platform/OpenGL/OpenGLRendererAPI.cpp || true

echo
echo "Search draw/vertex input symbols in OpenGLRendererAPI:"
rg -n "glMultiDrawElements|VkPipelineVertexInputStateCreateInfo|vertex.?input|VertexInput|DrawIndexedIndirect|MultiDraw|Stride|Binding|Index" OloEngine/src/Platform/OpenGL/OpenGLRendererAPI.cpp OloEngine/src -g '*.{cpp,h,hpp,c,hpp}' || true

Repository: drsnuggles8/OloEngineBase

Length of output: 50382


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "ADR line count:"
wc -l docs/adr/0011-rhi-neutral-resource-and-binding-model.md

echo
echo "Focused ADR lines around vertex-pulling note:"
sed -n '860,915p' docs/adr/0011-rhi-neutral-resource-and-binding-model.md

echo
echo "Focused OpenGLRendererAPI lines 730-775:"
sed -n '730,775p' OloEngine/src/Platform/OpenGL/OpenGLRendererAPI.cpp

echo
echo "Vertex pulling mentions in ADR:"
rg -n -C 2 -i "vertex.?pull|vertex pulling|pulling" docs/adr/0011-rhi-neutral-resource-and-binding-model.md || true

echo
echo "Vertex format/layout/schema mentions:"
rg -n -C 2 -i "vertex.?format|vertex.?binding|vertex.?stride|vertex.?offset|VkVertexInput|PipelineVertex|vertex-input|vertex_input|draw.?vertex|vertex.?selection|vertex.?contracts|shader-visible" docs OloEngine -g '*.{md,cpp,h,hpp,hlsl,glsl,hlslc}' | head -n 240 || true

Repository: drsnuggles8/OloEngineBase

Length of output: 34520


Define the vertex-pulling contract before the Phase 6 checkpoint.

Vertex pulling currently describes the VkPipelineVertexInputStateCreateInfo behavior to remove, but no shader-visible layout schema exists for formats, offsets, strides, index type, index base, or per-draw selection. Add this contract and pilot shader contract before making vertex pulling a Phase 6 prerequisite.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/adr/0011-rhi-neutral-resource-and-binding-model.md` around lines 900 -
905, Add a shader-visible vertex-pulling contract before the Phase 6 checkpoint,
defining formats, offsets, strides, index type, index base, and per-draw
selection. Include the pilot shader contract and update the vertex-pulling
sequencing text to make this specification an explicit prerequisite before
implementation.

Comment on lines +927 to +954
**Decision: a split barrier is a plain GPU-memory location plus two
operations — `Signal(pointer, value, op)` and `Wait(pointer, value,
compareOp)` — with `op`/`compareOp` covering at minimum `{Set/Equal,
AtomicMax/GreaterEqual}`.** The max/greater-equal pair is what generalizes
this from a one-shot flag into a monotonically increasing counter, i.e. a
timeline. This is deliberately not new Vulkan machinery: it is the existing
**timeline semaphore** primitive (core since Vulkan 1.2, well inside the ADR
0010 1.4 floor), promoted to the render graph's one mechanism for any
cross-pass or cross-frame dependency, rather than inventing a second,
Vulkan-events-shaped abstraction alongside it the way DX12 keeps both split
barriers and fences as separate concepts.

**How this plugs into the existing model.** `RenderGraphBarrierPlanner`
(§1.5) keeps computing ordinary same-submission dependencies unchanged — nothing
here replaces that. A dependency that spans further — a compute culling pass
whose tail latency can be hidden behind independent work later in the same
frame, or a cross-frame dependency the render graph currently expresses as an
ad hoc CPU/GPU fence — becomes an `RHI::GpuFence`: one GPU pointer, allocated
from the same per-frame lifetime discipline §4's temp allocator already
established, with `Signal` attached to the producing render-graph node and
`Wait` attached to the consuming one (or to nothing, if the wait is a CPU-side
frame-pacing check instead).

**One primitive, both sides of the API.** The same `Signal`/`Wait` pair
replaces ad hoc CPU-side fence chains too — a single persistently-mapped
counter, incremented by `Signal`, observed via a CPU `WaitGreaterEqual` — so
the render graph's existing CPU/GPU frame-pacing mechanism and its future
GPU-side split-barrier mechanism are the same code, not two.

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Find ADR and related files"
git ls-files | rg 'docs/adr/0011|rhi|GpuFence|RenderGraphBarrierPlanner|gpu fence|split barrier' || true

echo
echo "Relevant sections in ADR 0011"
sed -n '880,970p' docs/adr/0011-rhi-neutral-resource-and-binding-model.md

echo
echo "Search for RHI GpuFence / Signal / Wait definitions or mentions"
rg -n "RHI::GpuFence|GpuFence|Signal\\(|Wait\\(|WaitGreaterEqual|split barrier|timeline semaphore|DeviceMemory|mapped|counter" docs/adr -S || true

Repository: drsnuggles8/OloEngineBase

Length of output: 11929


🌐 Web query:

Vulkan VkSemaphore timeline semaphore opaque payload vkWaitSemaphores host wait memory mapped semaphore payload

💡 Result:

In Vulkan, a timeline semaphore is a synchronization primitive that utilizes a monotonically increasing 64-bit unsigned integer as its payload [1][2][3]. Unlike binary semaphores, which have only two states (signaled/unsignaled), timeline semaphores allow for more complex synchronization by tracking progress against specific integer values [2][4][3]. Key aspects regarding the host and payload operations include: Host Wait and Signal: The host can block until a timeline semaphore reaches a specified value using vkWaitSemaphores [1][5][6]. Conversely, the host can advance the timeline semaphore value using vkSignalSemaphore [7][8][3]. Host Query: Applications can perform non-blocking queries of the current payload value of a timeline semaphore at any time using vkGetSemaphoreCounterValue [1][2][8]. Payload and Memory Mapping: The term "payload" refers to the internal data representing the semaphore's current state [3]. While external semaphore mechanisms allow for importing and exporting these payloads (e.g., between different APIs or processes), Vulkan does not provide a mechanism to directly "memory map" a semaphore payload for host access [3]. Instead, host-side interaction is explicitly handled through the formal API commands (vkWaitSemaphores, vkSignalSemaphore, and vkGetSemaphoreCounterValue), which abstract the underlying implementation [1][2][3]. This design replaces many requirements for VkFence objects, as the host can wait for specific points in a GPU's timeline rather than just waiting for an entire command buffer to complete [2][4].

Citations:


Separate timeline semaphores from mapped GPU-memory signaling.

Vulkan timeline semaphores are backend VkSemaphore objects with opaque counter values. Signal/wait operations reference the semaphore, and host waits use vkWaitSemaphores; the payload is not an application-mapped GPU memory location.

Choose one model:

  • Map RHI::GpuFence to backend semaphore handles and submission-level timeline values.
  • Define a separate device-memory protocol with explicit atomic, cache, queue, and CPU-wait semantics.

Also define submission boundaries. Vulkan semaphore signal/wait operations occur around submission command buffers; they are not arbitrary commands between render-graph nodes inside one command buffer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/adr/0011-rhi-neutral-resource-and-binding-model.md` around lines 927 -
954, Revise the ADR section describing RHI::GpuFence, Signal, and Wait to choose
a single synchronization model: either map them to Vulkan VkSemaphore timeline
handles and submission-level values, or define a separate mapped-device-memory
protocol with explicit atomic, cache, queue, and host-wait semantics. Remove
claims that timeline semaphores are plain GPU-memory locations, and specify that
GPU signal/wait operations are attached to submission boundaries rather than
arbitrary nodes within one command buffer.

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.

1 participant