blob: db373886a2d1fa46f8ba4393912f3eb6c4357ddc [file]
/*
* Copyright (c) 2025-2026 The Khronos Group Inc.
* Copyright (c) 2025-2026 Valve Corporation
* Copyright (c) 2025-2026 LunarG, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*/
#include <vulkan/vulkan_core.h>
#include "../framework/layer_validation_tests.h"
#include "../framework/pipeline_helper.h"
#include "../framework/buffer_helper.h"
#include "../utils/math_utils.h"
class PositiveGpuAVDescriptorHeap : public GpuAVDescriptorHeap {};
TEST_F(PositiveGpuAVDescriptorHeap, BufferPointerOffset) {
AddRequiredExtensions(VK_KHR_SHADER_UNTYPED_POINTERS_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::bufferDeviceAddress);
AddRequiredFeature(vkt::Feature::shaderUntypedPointers);
RETURN_IF_SKIP(InitGpuAVDescriptorHeap());
InitRenderTarget();
const VkDeviceSize resource_stride = heap_props.bufferDescriptorSize;
CreateResourceHeap(resource_stride);
vkt::Buffer buffer(*m_device, sizeof(float) * 4, VK_BUFFER_USAGE_2_STORAGE_BUFFER_BIT_KHR, vkt::device_address);
VkHostAddressRangeEXT descriptor_host;
descriptor_host.address = resource_heap_data_;
descriptor_host.size = resource_stride;
VkDeviceAddressRangeEXT device_range = buffer.AddressRange();
VkResourceDescriptorInfoEXT descriptor_info;
descriptor_info = vku::InitStructHelper();
descriptor_info.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptor_info.data.pAddressRange = &device_range;
vk::WriteResourceDescriptorsEXT(*m_device, 1u, &descriptor_info, &descriptor_host);
char const *cs_source = R"glsl(
#version 450
#extension GL_EXT_descriptor_heap : require
layout(local_size_x = 1) in;
layout(descriptor_heap) buffer A { vec4 a; } heap[];
void main() {
heap[0].a = vec4(0.5f);
}
)glsl";
VkShaderObj cs_module(*m_device, cs_source, VK_SHADER_STAGE_COMPUTE_BIT, SPV_ENV_VULKAN_1_2);
VkPipelineCreateFlags2CreateInfoKHR pipeline_create_flags_2_create_info = vku::InitStructHelper();
pipeline_create_flags_2_create_info.flags = VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_EXT;
CreateComputePipelineHelper pipe(*this, &pipeline_create_flags_2_create_info);
pipe.cp_ci_.layout = VK_NULL_HANDLE;
pipe.cp_ci_.stage = cs_module.GetStageCreateInfo();
pipe.CreateComputePipeline(false);
m_command_buffer.Begin();
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipe);
BindResourceHeap();
vk::CmdDispatch(m_command_buffer, 1u, 1u, 1u);
m_command_buffer.End();
m_default_queue->SubmitAndWait(m_command_buffer);
}
TEST_F(PositiveGpuAVDescriptorHeap, SamplerPointerOffset) {
AddRequiredExtensions(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
AddRequiredExtensions(VK_KHR_SHADER_UNTYPED_POINTERS_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::bufferDeviceAddress);
AddRequiredFeature(vkt::Feature::shaderUntypedPointers);
RETURN_IF_SKIP(InitGpuAVDescriptorHeap());
InitRenderTarget();
const uint32_t buffer_index = 16u;
const VkDeviceSize image_offset = 0u;
const VkDeviceSize image_size = heap_props.imageDescriptorSize;
const VkDeviceSize buffer_offset = heap_props.bufferDescriptorSize * buffer_index;
const VkDeviceSize buffer_size = heap_props.bufferDescriptorSize;
const VkDeviceSize resource_heap_app_size = buffer_offset + buffer_size;
CreateResourceHeap(resource_heap_app_size);
vkt::Buffer buffer(*m_device, sizeof(float) * 4u, VK_BUFFER_USAGE_2_STORAGE_BUFFER_BIT_KHR, vkt::device_address);
vkt::Image image(*m_device, 32u, 32u, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
VkHostAddressRangeEXT resource_host[2];
resource_host[0].address = resource_heap_data_ + image_offset;
resource_host[0].size = image_size;
resource_host[1].address = resource_heap_data_ + buffer_offset;
resource_host[1].size = buffer_size;
VkImageViewCreateInfo view_info = image.BasicViewCreatInfo(VK_IMAGE_ASPECT_COLOR_BIT);
VkImageDescriptorInfoEXT image_info = vku::InitStructHelper();
image_info.pView = &view_info;
image_info.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
VkDeviceAddressRangeEXT buffer_address_range = buffer.AddressRange();
VkResourceDescriptorInfoEXT descriptor_info[2];
descriptor_info[0] = vku::InitStructHelper();
descriptor_info[0].type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
descriptor_info[0].data.pImage = &image_info;
descriptor_info[1] = vku::InitStructHelper();
descriptor_info[1].type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptor_info[1].data.pAddressRange = &buffer_address_range;
vk::WriteResourceDescriptorsEXT(*m_device, 2u, descriptor_info, resource_host);
const VkDeviceSize sampler_offset = 0u;
const VkDeviceSize sampler_size = heap_props.samplerDescriptorSize;
CreateSamplerHeap(sampler_size);
VkSamplerCreateInfo sampler_info = SafeSaneSamplerCreateInfo();
VkHostAddressRangeEXT sampler_host;
sampler_host.address = sampler_heap_data_ + sampler_offset;
sampler_host.size = sampler_size;
vk::WriteSamplerDescriptorsEXT(*m_device, 1u, &sampler_info, &sampler_host);
char const *cs_source = R"glsl(
#version 450
#extension GL_EXT_descriptor_heap : require
layout(descriptor_heap, rgba8i) uniform iimage2D heapImages[];
layout(descriptor_heap) buffer ssbo {
ivec4 data;
} heapBuffer[];
void main() {
heapBuffer[16].data = imageLoad(heapImages[0], ivec2(0));
}
)glsl";
VkShaderObj cs_module = VkShaderObj(*m_device, cs_source, VK_SHADER_STAGE_COMPUTE_BIT, SPV_ENV_VULKAN_1_2);
VkPipelineCreateFlags2CreateInfoKHR pipeline_create_flags_2_create_info = vku::InitStructHelper();
pipeline_create_flags_2_create_info.flags = VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_EXT;
CreateComputePipelineHelper pipe(*this, &pipeline_create_flags_2_create_info);
pipe.cp_ci_.layout = VK_NULL_HANDLE;
pipe.cp_ci_.stage = cs_module.GetStageCreateInfo();
pipe.CreateComputePipeline(false);
m_command_buffer.Begin();
VkImageMemoryBarrier image_barrier = vku::InitStructHelper();
image_barrier.srcAccessMask = VK_ACCESS_NONE;
image_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
image_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
image_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
image_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_barrier.image = image;
image_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u};
vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0u, 0u, nullptr, 0u,
nullptr, 1u, &image_barrier);
VkClearColorValue color = {};
color.float32[0] = 0.2f;
color.float32[1] = 0.4f;
color.float32[2] = 0.6f;
color.float32[3] = 0.8f;
vk::CmdClearColorImage(m_command_buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &color, 1u,
&image_barrier.subresourceRange);
image_barrier.srcAccessMask = image_barrier.dstAccessMask;
image_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
image_barrier.oldLayout = image_barrier.newLayout;
image_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0u, 0u, nullptr,
0u, nullptr, 1u, &image_barrier);
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipe);
BindResourceHeap();
BindSamplerHeap();
vk::CmdDispatch(m_command_buffer, 1u, 1u, 1u);
m_command_buffer.End();
m_default_queue->SubmitAndWait(m_command_buffer);
}
TEST_F(PositiveGpuAVDescriptorHeap, HeapBoundInMultimpleCmdBuffers) {
AddRequiredExtensions(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::bufferDeviceAddress);
RETURN_IF_SKIP(InitGpuAVDescriptorHeap());
InitRenderTarget();
const VkDeviceSize heap_size = 256;
CreateResourceHeap(heap_size);
vkt::CommandBuffer cmd_buffer(*m_device, m_command_pool);
VkBindHeapInfoEXT bind_resource_info = vku::InitStructHelper();
bind_resource_info.heapRange.address = resource_heap_.Address();
bind_resource_info.heapRange.size = resource_heap_.CreateInfo().size;
bind_resource_info.reservedRangeOffset = resource_heap_.CreateInfo().size - heap_props.minResourceHeapReservedRange;
bind_resource_info.reservedRangeSize = heap_props.minResourceHeapReservedRange;
m_command_buffer.Begin();
vk::CmdBindResourceHeapEXT(m_command_buffer, &bind_resource_info);
m_command_buffer.End();
cmd_buffer.Begin();
vk::CmdBindResourceHeapEXT(cmd_buffer, &bind_resource_info);
cmd_buffer.End();
}
TEST_F(PositiveGpuAVDescriptorHeap, PushAddress) {
AddRequiredFeature(vkt::Feature::bufferDeviceAddress);
AddRequiredFeature(vkt::Feature::vertexPipelineStoresAndAtomics);
RETURN_IF_SKIP(InitGpuAVDescriptorHeap());
InitRenderTarget();
vkt::Buffer read_buffer(*m_device, sizeof(uint32_t) * 4, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, vkt::device_address);
vkt::Buffer write_buffer(*m_device, sizeof(uint32_t) * 4, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, vkt::device_address);
const uint32_t read_offset = 48u;
const VkDeviceSize write_offset =
Align(read_offset + heap_props.bufferDescriptorSize * 7u, heap_props.bufferDescriptorSize);
const VkDeviceSize resource_stride = heap_props.bufferDescriptorSize;
const VkDeviceSize descriptor_size = AlignResource(write_offset + resource_stride);
const VkDeviceSize heap_size = descriptor_size + heap_props.minResourceHeapReservedRange;
vkt::Buffer heap(*m_device, heap_size, VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT, vkt::device_address);
uint8_t *heap_data = static_cast<uint8_t *>(heap.Memory().Map());
VkHostAddressRangeEXT descriptor_host;
descriptor_host.address = heap_data + write_offset;
descriptor_host.size = resource_stride;
VkDeviceAddressRangeEXT device_range = write_buffer.AddressRange();
VkResourceDescriptorInfoEXT descriptor_info;
descriptor_info = vku::InitStructHelper();
descriptor_info.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptor_info.data.pAddressRange = &device_range;
vk::WriteResourceDescriptorsEXT(*m_device, 1u, &descriptor_info, &descriptor_host);
VkDescriptorSetAndBindingMappingEXT mappings[2];
mappings[0] = MakeSetAndBindingMapping(2, 3, 1, VK_SPIRV_RESOURCE_TYPE_READ_WRITE_STORAGE_BUFFER_BIT_EXT);
mappings[0].source = VK_DESCRIPTOR_MAPPING_SOURCE_PUSH_ADDRESS_EXT;
mappings[0].sourceData.pushAddressOffset = static_cast<uint32_t>(read_offset);
mappings[1] = MakeSetAndBindingMapping(1, 0, 1, VK_SPIRV_RESOURCE_TYPE_READ_WRITE_STORAGE_BUFFER_BIT_EXT);
mappings[1].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT;
mappings[1].sourceData.constantOffset.heapOffset = static_cast<uint32_t>(write_offset);
mappings[1].sourceData.constantOffset.heapArrayStride = 0u;
VkShaderDescriptorSetAndBindingMappingInfoEXT mapping_info = vku::InitStructHelper();
mapping_info.mappingCount = 2u;
mapping_info.pMappings = mappings;
char const *vert_source = R"glsl(
#version 450
layout(set = 2, binding = 3) buffer ReadData {
uvec4 read_data;
};
layout(set = 1, binding = 0) buffer WriteData {
uvec4 write_data;
};
void main() {
if (gl_VertexIndex == 0) {
write_data[0] = read_data[3];
}
gl_Position = vec4(1.0f);
gl_PointSize = 1.0f;
}
)glsl";
VkShaderObj vert_module = VkShaderObj(*m_device, vert_source, VK_SHADER_STAGE_VERTEX_BIT);
VkShaderObj frag_module = VkShaderObj(*m_device, kFragmentMinimalGlsl, VK_SHADER_STAGE_FRAGMENT_BIT);
VkPipelineCreateFlags2CreateInfoKHR pipeline_create_flags_2_create_info = vku::InitStructHelper();
pipeline_create_flags_2_create_info.flags = VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_EXT;
VkPipelineShaderStageCreateInfo stages[2];
stages[0] = vert_module.GetStageCreateInfo(&mapping_info);
stages[1] = frag_module.GetStageCreateInfo();
CreatePipelineHelper descriptor_heap_pipe(*this, &pipeline_create_flags_2_create_info);
descriptor_heap_pipe.gp_ci_.layout = VK_NULL_HANDLE;
descriptor_heap_pipe.gp_ci_.stageCount = 2u;
descriptor_heap_pipe.gp_ci_.pStages = stages;
descriptor_heap_pipe.CreateGraphicsPipeline(false);
VkDeviceAddress read_address = read_buffer.Address();
VkPushDataInfoEXT push_data = vku::InitStructHelper();
push_data.offset = read_offset;
push_data.data.address = &read_address;
push_data.data.size = sizeof(read_address);
m_command_buffer.Begin();
m_command_buffer.BeginRenderPass(m_renderPassBeginInfo);
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, descriptor_heap_pipe);
VkBindHeapInfoEXT bind_resource_info = vku::InitStructHelper();
bind_resource_info.heapRange.address = heap.Address();
bind_resource_info.heapRange.size = heap_size;
bind_resource_info.reservedRangeOffset = descriptor_size;
bind_resource_info.reservedRangeSize = heap_props.minResourceHeapReservedRange;
vk::CmdBindResourceHeapEXT(m_command_buffer, &bind_resource_info);
vk::CmdPushDataEXT(m_command_buffer, &push_data);
vk::CmdDraw(m_command_buffer, 3u, 1u, 0u, 0u);
m_command_buffer.EndRenderPass();
m_command_buffer.End();
m_default_queue->SubmitAndWait(m_command_buffer);
}
TEST_F(PositiveGpuAVDescriptorHeap, ResourceHeapImage) {
AddRequiredExtensions(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
AddRequiredExtensions(VK_KHR_SHADER_UNTYPED_POINTERS_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::bufferDeviceAddress);
AddRequiredFeature(vkt::Feature::shaderUntypedPointers);
RETURN_IF_SKIP(InitGpuAVDescriptorHeap());
InitRenderTarget();
const uint32_t buffer_index = 16u;
const VkDeviceSize image_offset = 0u;
const VkDeviceSize image_size = heap_props.imageDescriptorSize;
const VkDeviceSize buffer_offset = heap_props.bufferDescriptorSize * buffer_index;
const VkDeviceSize buffer_size = heap_props.bufferDescriptorSize;
const VkDeviceSize resource_heap_app_size = buffer_offset + buffer_size;
CreateResourceHeap(resource_heap_app_size);
vkt::Buffer buffer(*m_device, sizeof(float) * 4u, VK_BUFFER_USAGE_2_STORAGE_BUFFER_BIT_KHR, vkt::device_address);
vkt::Image image(*m_device, 32u, 32u, VK_FORMAT_R8G8B8A8_SINT, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_STORAGE_BIT);
VkImageCreateInfo image_ci = image.CreateInfo();
image_ci.imageType = VK_IMAGE_TYPE_3D;
image_ci.format = VK_FORMAT_R8G8B8A8_SINT;
image_ci.extent = {32u, 32u, 1u};
image_ci.mipLevels = 1u;
image_ci.arrayLayers = 1u;
image_ci.samples = VK_SAMPLE_COUNT_1_BIT;
image_ci.tiling = VK_IMAGE_TILING_OPTIMAL;
image_ci.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_STORAGE_BIT;
vkt::Image image_3d(*m_device, image_ci);
VkHostAddressRangeEXT resource_host[2];
resource_host[0].address = resource_heap_data_ + image_offset;
resource_host[0].size = image_size;
resource_host[1].address = resource_heap_data_ + buffer_offset;
resource_host[1].size = buffer_size;
VkImageViewCreateInfo view_info = image.BasicViewCreatInfo(VK_IMAGE_ASPECT_COLOR_BIT);
VkImageDescriptorInfoEXT image_info = vku::InitStructHelper();
image_info.pView = &view_info;
image_info.layout = VK_IMAGE_LAYOUT_GENERAL;
VkDeviceAddressRangeEXT buffer_address_range = buffer.AddressRange();
VkResourceDescriptorInfoEXT descriptor_info[2];
descriptor_info[0] = vku::InitStructHelper();
descriptor_info[0].type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
descriptor_info[0].data.pImage = &image_info;
descriptor_info[1] = vku::InitStructHelper();
descriptor_info[1].type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptor_info[1].data.pAddressRange = &buffer_address_range;
vk::WriteResourceDescriptorsEXT(*m_device, 2u, descriptor_info, resource_host);
char const *cs_source = R"glsl(
#version 450
#extension GL_EXT_descriptor_heap : require
layout(descriptor_heap, rgba8i) uniform iimage2D heapImages[];
layout(descriptor_heap) buffer ssbo {
ivec4 data;
} heapBuffer[];
void main() {
heapBuffer[16].data = imageLoad(heapImages[0], ivec2(0));
}
)glsl";
VkShaderObj cs_module = VkShaderObj(*m_device, cs_source, VK_SHADER_STAGE_COMPUTE_BIT, SPV_ENV_VULKAN_1_2);
VkPipelineCreateFlags2CreateInfoKHR pipeline_create_flags_2_create_info = vku::InitStructHelper();
pipeline_create_flags_2_create_info.flags = VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_EXT;
CreateComputePipelineHelper pipe(*this, &pipeline_create_flags_2_create_info);
pipe.cp_ci_.layout = VK_NULL_HANDLE;
pipe.cp_ci_.stage = cs_module.GetStageCreateInfo();
pipe.CreateComputePipeline(false);
m_command_buffer.Begin();
VkImageMemoryBarrier image_barrier = vku::InitStructHelper();
image_barrier.srcAccessMask = image_barrier.dstAccessMask;
image_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
image_barrier.oldLayout = image_barrier.newLayout;
image_barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
image_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_barrier.image = image;
image_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u};
vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0u, 0u, nullptr,
0u, nullptr, 1u, &image_barrier);
image_barrier.image = image_3d;
vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0u, 0u, nullptr,
0u, nullptr, 1u, &image_barrier);
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipe);
BindResourceHeap();
vk::CmdDispatch(m_command_buffer, 1u, 1u, 1u);
m_command_buffer.End();
m_default_queue->SubmitAndWait(m_command_buffer);
}