Description of Bug
When using the Vulkan fragment shader debugger with Buffer Device Address (PhysicalStorageBuffer), indexing an array through a pointer that was loaded from memory (like a nested buffer pointer) always evaluates to element 0 regardless of the index.
In a shader where an inner buffer pointer is loaded from another buffer struct and indexed via OpPtrAccessChain (such as frameBuffer.instanceBuffer[i]), the debugger calculates a byte offset of 0. Every array element read evaluates to the data of element 0. When this struct contains descriptor, texture, or sampler IDs for bindless access, the debugger requests descriptors that the GPU never touched, spamming the console with:
Internal error: Binding Read-only Resource 0[x] did not exist in calculated descriptor access when performing ImageSampleImplicitLod operation.
Internal error: Binding Sampler 0[y] did not exist in calculated descriptor access when performing ImageSampleImplicitLod operation.
The pointer should be indexed using the type's ArrayStride, dereferencing the actual element 'i' at the correct byte offset. If the pointer is taken directly from push constants instead of loaded from memory it works correctly as expected. The draw call renders correctly on the GPU, and GetDescriptorAccess() contains the bindings the GPU actually accessed.
Steps to reproduce
StrideBug.zip
- Open the attached capture StrideBug.rdc in RenderDoc.
- In the Event Browser, expand Main Pass and select Event 153vkCmdDrawIndexedIndirectCount.
- Open the Texture Viewer for Color Attachment 191 and debug the fragment at any pixel coordinate.
- Step forward into SimpleShaders.slang to line 81 (the texture sample on material.albedoTextureId).
- Observe the calculated descriptor access errors in the Errors and Warnings tab, and notice in the variable watch that instance and material evaluate to element 0's values instead of the values for inputVertex.instanceID.
Environment
- RenderDoc version: v1.47 (commit 54f92c2), self-built from source with Wayland support (also reproduced on v1.45)
- Operating System: Arch Linux (Kernel 7.2.6-arch2-1, Wayland)
- Graphics API: Vulkan 1.4 (NVIDIA GeForce GTX 1060, Driver 580.178.04)
Additional Information
I traced it to the spirv_debug.cpp file.
When a pointer is loaded from memory via Op::Load (ReadPointerValue), the destination ShaderVariable does not have its array stride populated via setArrayStride
When Op::PtrAccessChain subsequently indexes the pointer:
uint64_t byteOffset = element * debugger.GetPointerArrayStride(base);
debugger.GetPointerArrayStride(base) returns 0. ByteOffset evaluates to 0 for any element index, causing every read to fetch element 0.
This does not happen for root pointers coming directly from push constants. They have their array stride populated in cbufferCallback / MakeTypedPointer and evaluate the correct non-zero byte offset.
Quick local patch I made for myself:
// in spirv_debug.h (class Debugger)
uint32_t GetPointerTypeArrayStride(Id ptrType) const;
// in spirv_debug.cpp
uint32_t Debugger::GetPointerTypeArrayStride(Id ptrType) const
{
const Decorations &dec = decorations[ptrType];
return (dec.flags & Decorations::HasArrayStride) ? dec.arrayStride : 0;
}
void setArrayStride(ShaderVariable &var, uint32_t stride)
{
var.value.u64v[9] = stride;
}
// In case Op::PtrAccessChain:
uint32_t stride = debugger.GetPointerArrayStride(base);
if(stride == 0 && chain.indexes.empty())
{
// pointer was loaded from memory without stride info, recover it from the result type
stride = debugger.GetPointerTypeArrayStride(chain.resultType);
if(stride)
setArrayStride(base, stride);
}
uint64_t byteOffset = (uint64_t)element * stride;
Description of Bug
When using the Vulkan fragment shader debugger with Buffer Device Address (PhysicalStorageBuffer), indexing an array through a pointer that was loaded from memory (like a nested buffer pointer) always evaluates to element 0 regardless of the index.
In a shader where an inner buffer pointer is loaded from another buffer struct and indexed via OpPtrAccessChain (such as frameBuffer.instanceBuffer[i]), the debugger calculates a byte offset of 0. Every array element read evaluates to the data of element 0. When this struct contains descriptor, texture, or sampler IDs for bindless access, the debugger requests descriptors that the GPU never touched, spamming the console with:
The pointer should be indexed using the type's ArrayStride, dereferencing the actual element 'i' at the correct byte offset. If the pointer is taken directly from push constants instead of loaded from memory it works correctly as expected. The draw call renders correctly on the GPU, and GetDescriptorAccess() contains the bindings the GPU actually accessed.
Steps to reproduce
StrideBug.zip
Environment
Additional Information
I traced it to the spirv_debug.cpp file.
When a pointer is loaded from memory via Op::Load (ReadPointerValue), the destination ShaderVariable does not have its array stride populated via setArrayStride
When Op::PtrAccessChain subsequently indexes the pointer:
uint64_t byteOffset = element * debugger.GetPointerArrayStride(base);debugger.GetPointerArrayStride(base) returns 0. ByteOffset evaluates to 0 for any element index, causing every read to fetch element 0.
This does not happen for root pointers coming directly from push constants. They have their array stride populated in cbufferCallback / MakeTypedPointer and evaluate the correct non-zero byte offset.
Quick local patch I made for myself: