Skip to content

Commit 19290ee

Browse files
committed
Add descriptor handle support to render test framework
Fixes #8631 Implements automatic detection and binding of descriptor handle types (.Handle) in the slang-test framework, enabling execution tests for bindless descriptor features. Detection Strategy: Descriptor handles are lowered to uint2 (SPIRV/DX12) stored in uniform data. The detector specifically matches this pattern and excludes uint64 (device addresses) and Resource types (Metal/CUDA pointers) to avoid false positives. If getDescriptorHandle() fails, falls back to regular binding. Changes: - Add isDescriptorHandleType() detecting uint2 in UNIFORM category - Modify assignBuffer/Texture/Sampler to allocate and bind descriptor handles - Determine access mode via getResourceAccess() reflection - Fix resource lifetime by adding all resources to TestResourceContext - Add test cases requiring -render-feature bindless and -emit-spirv-directly This fixes Vulkan validation errors (VUID-vkDestroyBuffer-buffer-00922) where buffers were destroyed while still in use by command buffers when using bindless descriptors
1 parent a0f023e commit 19290ee

File tree

3 files changed

+183
-1
lines changed

3 files changed

+183
-1
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK):-vk -output-using-type -render-feature bindless -emit-spirv-directly
2+
//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK):-dx12 -output-using-type -profile sm_6_6 -render-feature bindless
3+
//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK):-mtl -output-using-type
4+
5+
// CHECK: type: uint
6+
// CHECK-NEXT: 42
7+
8+
//TEST_INPUT:ubuffer(data=[0], stride=4):out,name=outputBuffer
9+
RWStructuredBuffer<uint> outputBuffer;
10+
11+
//TEST_INPUT:ubuffer(data=[42], stride=4):name=input
12+
uniform RWStructuredBuffer<uint>.Handle input;
13+
14+
[numthreads(1, 1, 1)]
15+
void computeMain()
16+
{
17+
// Just read one value
18+
outputBuffer[0] = input[0];
19+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK):-vk -output-using-type -render-feature bindless -emit-spirv-directly
2+
//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK):-dx12 -output-using-type -profile sm_6_6 -render-feature bindless
3+
//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK):-mtl -output-using-type
4+
5+
// CHECK: type: float
6+
// CHECK-NEXT: 1.0
7+
// CHECK-NEXT: 2.0
8+
// CHECK-NEXT: 3.0
9+
10+
//TEST_INPUT:ubuffer(data=[0 0 0], stride=4):out,name=outputBuffer
11+
RWStructuredBuffer<float> outputBuffer;
12+
13+
//TEST_INPUT:ubuffer(data=[1.0 2.0 3.0], stride=4):name=input
14+
uniform RWStructuredBuffer<float>.Handle input;
15+
16+
[numthreads(1, 1, 1)]
17+
void computeMain()
18+
{
19+
for(int i = 0; i < 3; i++)
20+
outputBuffer[i] = input[i];
21+
}

tools/render-test/render-test-main.cpp

Lines changed: 143 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,62 @@ struct AssignValsFromLayoutContext
330330
return SLANG_OK;
331331
}
332332

333+
static bool isDescriptorHandleType(const ShaderCursor& cursor)
334+
{
335+
// Descriptor handles in SPIRV/DX12 with bindless are lowered to uint2
336+
// stored in uniform data.
337+
//
338+
// IMPORTANT: We only check for uint2, NOT uint64!
339+
// - uint64 in uniform data = device address (buffer pointer)
340+
// - uint2 in uniform data = descriptor handle (index into bindless heap)
341+
//
342+
// Note: We DON'T check for Kind::Resource in uniform data because:
343+
// - On Metal/CUDA, regular resources appear as pointers in uniform data
344+
// - Those work fine with regular setBinding(), not descriptor handles
345+
346+
auto typeLayout = cursor.getTypeLayout();
347+
348+
// Check if this parameter is stored in uniform data (not descriptor table)
349+
auto uniformSize = typeLayout->getSize(SLANG_PARAMETER_CATEGORY_UNIFORM);
350+
auto descriptorSlotSize =
351+
typeLayout->getSize(SLANG_PARAMETER_CATEGORY_DESCRIPTOR_TABLE_SLOT);
352+
353+
if (uniformSize == 8 && descriptorSlotSize == 0)
354+
{
355+
auto type = typeLayout->getType();
356+
auto kind = type->getKind();
357+
358+
// Check for uint2 (SPIRV/DX12 descriptor handle)
359+
// This is the ONLY pattern we detect as descriptor handles
360+
if (kind == slang::TypeReflection::Kind::Vector && type->getElementCount() == 2 &&
361+
type->getElementType()->getScalarType() ==
362+
slang::TypeReflection::ScalarType::UInt32)
363+
return true;
364+
}
365+
366+
return false;
367+
}
368+
369+
static DescriptorHandleAccess getDescriptorHandleAccess(SlangResourceAccess resourceAccess)
370+
{
371+
// Map Slang resource access to descriptor handle access
372+
// Most write-capable resources map to ReadWrite
373+
switch (resourceAccess)
374+
{
375+
case SLANG_RESOURCE_ACCESS_READ:
376+
return DescriptorHandleAccess::Read;
377+
case SLANG_RESOURCE_ACCESS_READ_WRITE:
378+
case SLANG_RESOURCE_ACCESS_WRITE:
379+
case SLANG_RESOURCE_ACCESS_RASTER_ORDERED:
380+
case SLANG_RESOURCE_ACCESS_APPEND:
381+
case SLANG_RESOURCE_ACCESS_CONSUME:
382+
case SLANG_RESOURCE_ACCESS_FEEDBACK:
383+
return DescriptorHandleAccess::ReadWrite;
384+
default:
385+
return DescriptorHandleAccess::ReadWrite;
386+
}
387+
}
388+
333389
SlangResult assignBuffer(ShaderCursor const& dstCursor, ShaderInputLayout::BufferVal* srcVal)
334390
{
335391
const InputBufferDesc& srcBuffer = srcVal->bufferDesc;
@@ -350,6 +406,38 @@ struct AssignValsFromLayoutContext
350406
device,
351407
bufferResource));
352408

409+
// Keep buffer alive in resource context
410+
resourceContext.resources.add(ComPtr<IResource>(bufferResource.get()));
411+
412+
// Check if this is a descriptor handle type
413+
if (isDescriptorHandleType(dstCursor))
414+
{
415+
// Try to allocate descriptor handle for bindless access
416+
DescriptorHandle handle;
417+
418+
// Determine access mode from the shader parameter's resource type
419+
auto handleType = dstCursor.getTypeLayout()->getType();
420+
auto resourceType = handleType->getElementType();
421+
auto resourceAccess =
422+
resourceType ? resourceType->getResourceAccess() : SLANG_RESOURCE_ACCESS_READ_WRITE;
423+
DescriptorHandleAccess access = getDescriptorHandleAccess(resourceAccess);
424+
425+
// Try to get descriptor handle - will fail on backends that don't support it
426+
auto result = bufferResource->getDescriptorHandle(
427+
access,
428+
srcBuffer.format,
429+
BufferRange{0, bufferSize},
430+
&handle);
431+
432+
if (SLANG_SUCCEEDED(result))
433+
{
434+
SLANG_RETURN_ON_FAIL(dstCursor.setDescriptorHandle(handle));
435+
maybeAddOutput(dstCursor, srcVal, bufferResource);
436+
return SLANG_OK;
437+
}
438+
// If getDescriptorHandle fails, fall through to regular binding
439+
}
440+
353441
if ((dstCursor.getTypeLayout()->getType()->getKind() ==
354442
slang::TypeReflection::Kind::Scalar &&
355443
dstCursor.getTypeLayout()->getType()->getScalarType() ==
@@ -360,7 +448,6 @@ struct AssignValsFromLayoutContext
360448
// we should write bufferResource as a pointer.
361449
uint64_t addr = bufferResource->getDeviceAddress();
362450
dstCursor.setData(&addr, sizeof(addr));
363-
resourceContext.resources.add(ComPtr<IResource>(bufferResource.get()));
364451
maybeAddOutput(dstCursor, srcVal, bufferResource);
365452
return SLANG_OK;
366453
}
@@ -405,6 +492,8 @@ struct AssignValsFromLayoutContext
405492

406493
if (counterResource)
407494
{
495+
// Keep counter buffer alive
496+
resourceContext.resources.add(ComPtr<IResource>(counterResource.get()));
408497
dstCursor.setBinding(Binding(bufferResource, counterResource));
409498
}
410499
else
@@ -430,8 +519,14 @@ struct AssignValsFromLayoutContext
430519
device,
431520
texture));
432521

522+
// Keep texture alive in resource context
523+
resourceContext.resources.add(ComPtr<IResource>(texture.get()));
524+
433525
auto sampler = _createSampler(device, samplerEntry->samplerDesc);
434526

527+
// Keep sampler alive in resource context
528+
resourceContext.resources.add(ComPtr<IResource>(sampler.get()));
529+
435530
dstCursor.setBinding(Binding(texture, sampler));
436531
maybeAddOutput(dstCursor, srcVal, texture);
437532

@@ -451,6 +546,36 @@ struct AssignValsFromLayoutContext
451546
device,
452547
texture));
453548

549+
// Keep texture alive in resource context
550+
resourceContext.resources.add(ComPtr<IResource>(texture.get()));
551+
552+
// Check if this is a descriptor handle type
553+
if (isDescriptorHandleType(dstCursor))
554+
{
555+
// Try to allocate descriptor handle for bindless access
556+
// Get default texture view first, as handles are allocated from views
557+
ComPtr<ITextureView> textureView;
558+
SLANG_RETURN_ON_FAIL(texture->getDefaultView(textureView.writeRef()));
559+
560+
// Determine access mode from the shader parameter's resource type
561+
auto handleType = dstCursor.getTypeLayout()->getType();
562+
auto resourceType = handleType->getElementType();
563+
auto resourceAccess =
564+
resourceType ? resourceType->getResourceAccess() : SLANG_RESOURCE_ACCESS_READ_WRITE;
565+
DescriptorHandle handle;
566+
DescriptorHandleAccess access = getDescriptorHandleAccess(resourceAccess);
567+
568+
// Try to get descriptor handle - will fail on backends that don't support it
569+
auto result = textureView->getDescriptorHandle(access, &handle);
570+
if (SLANG_SUCCEEDED(result))
571+
{
572+
SLANG_RETURN_ON_FAIL(dstCursor.setDescriptorHandle(handle));
573+
maybeAddOutput(dstCursor, srcVal, texture);
574+
return SLANG_OK;
575+
}
576+
// If getDescriptorHandle fails, fall through to regular binding
577+
}
578+
454579
dstCursor.setBinding(texture);
455580
maybeAddOutput(dstCursor, srcVal, texture);
456581
return SLANG_OK;
@@ -460,6 +585,23 @@ struct AssignValsFromLayoutContext
460585
{
461586
auto sampler = _createSampler(device, srcVal->samplerDesc);
462587

588+
// Keep sampler alive in resource context
589+
resourceContext.resources.add(ComPtr<IResource>(sampler.get()));
590+
591+
// Check if this is a descriptor handle type
592+
if (isDescriptorHandleType(dstCursor))
593+
{
594+
// Try to allocate descriptor handle for bindless access
595+
DescriptorHandle handle;
596+
auto result = sampler->getDescriptorHandle(&handle);
597+
if (SLANG_SUCCEEDED(result))
598+
{
599+
SLANG_RETURN_ON_FAIL(dstCursor.setDescriptorHandle(handle));
600+
return SLANG_OK;
601+
}
602+
// If getDescriptorHandle fails, fall through to regular binding
603+
}
604+
463605
dstCursor.setBinding(sampler);
464606
return SLANG_OK;
465607
}

0 commit comments

Comments
 (0)