Skip to content

Vulkan: promoted inline constants can bind stale dynamic UBO offsets after buffer ID reuse #808

Description

@hzqst

Summary

The Vulkan backend can bind a stale, misaligned dynamic uniform-buffer offset for an inline constant resource that the current pipeline promotes to push constants. The unused fallback UBO remains in the descriptor set layout, so its dynamic offset must still satisfy Vulkan's alignment requirements.

Reproduced against the implementation in upstream master at 18bfa7b7563a0ef5b5fe074d37c2e8304100e965 with a compute-dispatch regression:

VUID-vkCmdBindDescriptorSets-pDynamicOffsets-01971
vkCmdBindDescriptorSets(): pDynamicOffsets[1] is 68,
but must be a multiple of device limit minUniformBufferOffsetAlignment 64.

GPU output can still be correct because the shader reads push constants rather than the fallback UBO. GPU readback alone therefore does not detect this API validation error.

Reproduction

Use a Vulkan device/context with Khronos validation enabled:

  1. Map a dynamic uniform buffer to establish a uniform-aligned allocation anchor.
  2. Map a small dynamic vertex buffer with MAP_WRITE / MAP_FLAG_DISCARD until its allocation offset is not a multiple of minUniformBufferOffsetAlignment. On the tested device, vertex allocations require only 4-byte alignment, while UBOs require 64-byte alignment.
  3. Release the vertex buffer and immediately create a resource signature containing an inline constant resource. Its fallback UBO reuses the released dynamic buffer ID.
  4. Create a pipeline that promotes this resource to push constants, set the inline constants, and bind an SRB from that signature.
  5. Dispatch. The fallback UBO has never been mapped, but descriptor binding uses the vertex buffer's old allocation offset and reports VUID 01971.

Keeping the aligned anchor alive and checking the mapped-address difference modulo the UBO alignment makes the precondition explicit even when other tests have already used the context's dynamic heap.

Expected: the promoted resource's unused fallback UBO supplies a valid placeholder offset, while resources actually read through UBO descriptors retain their real offsets.

Actual: the unused UBO inherits an unrelated allocation's offset through its recycled dynamic buffer ID.

Root cause

  • PipelineResourceSignatureVkImpl retains a fallback dynamic UBO descriptor and backing buffer for every inline constant resource.
  • CommitInlineConstants only pushes data for the resource selected by the current pipeline; it does not map that resource's fallback UBO.
  • ShaderResourceCacheVk::WriteDynamicBufferOffsets nevertheless queries the buffer's dynamic offset with allocation verification disabled.
  • DeviceContextVkImpl::GetDynamicBufferOffset indexes previously mapped allocations using a recyclable dynamic buffer ID. An unmapped replacement buffer can consequently observe the previous owner's allocation offset.

The selection is pipeline-specific: the same SRB can use B as push constants in one pipeline and B as an emulated UBO in another. The SRB can also come from a different but compatible signature instance. A fix must therefore identify the promoted resource in the currently bound SRB cache, rather than assuming the PSO's signature owns the bound backing buffer. Zeroing every inline-constant offset would corrupt resources using UBO emulation.

Environment and validation

  • Windows build 10.0.26200.0, x64 Debug.
  • Visual Studio 2022 / MSVC 19.44.35228.0; CMake 3.31.12.
  • NVIDIA GeForce RTX 5060, driver 610.88.
  • Vulkan SDK / Khronos validation layer 1.4.350.
  • Reproduced with both the API test environment's default HLSL compiler selection and explicit DXC selection.
  • With the pre-fix offset-writing logic restored, two targeted regression cases failed with VUID 01971 in every one of 10 independent process runs (20 failing case executions), with randomized case order. Explicit DXC also reproduced the same VUID.
  • Separate processes matter for repeated negative runs: the validation layer may suppress a VUID after its duplicate-message limit is reached.

A fix and regression tests in InlineConstantsTest.cpp will follow in a PR.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions