From 6a0cffc480973b3fac846a97e65750d548acbe30 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Mon, 23 Feb 2026 21:18:26 +0100 Subject: [PATCH 01/34] Add sample for descriptor heaps --- framework/vulkan_type_mapping.h | 6 + .../extensions/descriptor_heap/CMakeLists.txt | 36 ++ .../descriptor_heap/descriptor_heap.cpp | 526 ++++++++++++++++++ .../descriptor_heap/descriptor_heap.h | 78 +++ 4 files changed, 646 insertions(+) create mode 100644 samples/extensions/descriptor_heap/CMakeLists.txt create mode 100644 samples/extensions/descriptor_heap/descriptor_heap.cpp create mode 100644 samples/extensions/descriptor_heap/descriptor_heap.h diff --git a/framework/vulkan_type_mapping.h b/framework/vulkan_type_mapping.h index 7431917c8d..e6e981e4bd 100644 --- a/framework/vulkan_type_mapping.h +++ b/framework/vulkan_type_mapping.h @@ -121,6 +121,12 @@ struct HPPType using Type = vk::PhysicalDeviceDescriptorBufferFeaturesEXT; }; +template <> +struct HPPType +{ + using Type = vk::PhysicalDeviceDescriptorHeapFeaturesEXT; +}; + template <> struct HPPType { diff --git a/samples/extensions/descriptor_heap/CMakeLists.txt b/samples/extensions/descriptor_heap/CMakeLists.txt new file mode 100644 index 0000000000..9ef8d30004 --- /dev/null +++ b/samples/extensions/descriptor_heap/CMakeLists.txt @@ -0,0 +1,36 @@ +# Copyright (c) 2026 Sascha Willems +# +# SPDX-License-Identifier: Apache-2.0 +# +# 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 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) +get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH) +get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME) + +add_sample( + ID ${FOLDER_NAME} + CATEGORY ${CATEGORY_NAME} + AUTHOR "Sascha Willems" + NAME "Descriptor Heap" + DESCRIPTION "Demonstrates the descriptor heap extension to streamline descriptor setup" + SHADER_FILES_GLSL + "descriptor_heap/glsl/cube.vert" + "descriptor_heap/glsl/cube.frag" + SHADER_FILES_HLSL + "descriptor_heap/hlsl/cube.vert.hlsl" + "descriptor_heap/hlsl/cube.frag.hlsl" + SHADER_FILES_SLANG + "descriptor_heap/slang/cube.vert.slang" + "descriptor_heap/slang/cube.frag.slang") \ No newline at end of file diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp new file mode 100644 index 0000000000..751d95328f --- /dev/null +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -0,0 +1,526 @@ +/* + * Copyright (c) 2026 Sascha Willems + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "descriptor_heap.h" + +DescriptorHeap::DescriptorHeap() +{ + title = "Descriptor heap"; + + add_device_extension(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME); + add_device_extension(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); + add_device_extension(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); + add_device_extension(VK_KHR_MAINTENANCE_5_EXTENSION_NAME); + add_device_extension(VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME); +} + +DescriptorHeap::~DescriptorHeap() +{ + if (has_device()) + { + textures = {}; + cube.reset(); + } +} + +bool DescriptorHeap::prepare(const vkb::ApplicationOptions &options) +{ + if (!ApiVulkanSample::prepare(options)) + { + return false; + } + + camera.type = vkb::CameraType::LookAt; + camera.set_position({0.f, 0.f, -4.f}); + camera.set_rotation({0.f, 180.f, 0.f}); + camera.set_perspective(60.f, static_cast(width) / static_cast(height), 256.f, 0.1f); + + load_assets(); + prepare_uniform_buffers(); + create_descriptor_heaps(); + create_pipeline(); + build_command_buffers(); + prepared = true; + + return true; +} + +void DescriptorHeap::request_gpu_features(vkb::core::PhysicalDeviceC &gpu) +{ + // Enable features required for this example + REQUEST_REQUIRED_FEATURE(gpu, VkPhysicalDeviceBufferDeviceAddressFeatures, bufferDeviceAddress); + REQUEST_REQUIRED_FEATURE(gpu, VkPhysicalDeviceDynamicRenderingFeaturesKHR, dynamicRendering); + + // We need to enable the descriptor heap feature to make use of them + REQUEST_REQUIRED_FEATURE(gpu, VkPhysicalDeviceDescriptorHeapFeaturesEXT, descriptorHeap); + + if (gpu.get_features().samplerAnisotropy) + { + gpu.get_mutable_requested_features().samplerAnisotropy = true; + } +} + +uint32_t DescriptorHeap::get_api_version() const +{ + // @todo: 1.3, add note that it's not required per se + return VK_API_VERSION_1_3; +} + +void DescriptorHeap::load_assets() +{ + cube = load_model("scenes/cube.gltf"); + textures[0] = load_texture("textures/crate01_color_height_rgba.ktx", vkb::sg::Image::Color); + textures[1] = load_texture("textures/crate02_color_height_rgba.ktx", vkb::sg::Image::Color); +} + +inline VkDeviceSize aligned_size(VkDeviceSize value, VkDeviceSize alignment) +{ + return (value + alignment - 1) & ~(alignment - 1); +} + +void DescriptorHeap::prepare_uniform_buffers() +{ + uniform_buffer = std::make_unique(get_device(), sizeof(UniformData), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU); +} + +void DescriptorHeap::update_uniform_buffers() +{ + uniform_data.projection_matrix = camera.matrices.perspective; + uniform_data.view_matrix = camera.matrices.view * glm::mat4(1.f); + uniform_buffer->convert_and_update(uniform_data); +} + +void DescriptorHeap::create_descriptor_heaps() +{ + // Descriptor heaps have varying offset, size and alignment requirements, so we store it's properties for later user + VkPhysicalDeviceProperties2 deviceProps2{.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2}; + descriptor_heap_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_PROPERTIES_EXT; + deviceProps2.pNext = &descriptor_heap_properties; + vkGetPhysicalDeviceProperties2(get_device().get_gpu().get_handle(), &deviceProps2); + + // There are two descriptor heap types: One that can store resources (buffers, images) and one that can store samplers + // We create heaps with a fixed size that's guaranteed to fit in the few descriptors we use + const VkDeviceSize heap_buffer_size = aligned_size(2048 + descriptor_heap_properties.minResourceHeapReservedRange, descriptor_heap_properties.resourceHeapAlignment); + descriptor_heap_resources = std::make_unique(get_device(), + heap_buffer_size, + VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, + VMA_MEMORY_USAGE_CPU_TO_GPU); + + const VkDeviceSize heap_sampler_size = aligned_size(2048 + descriptor_heap_properties.minSamplerHeapReservedRange, descriptor_heap_properties.samplerHeapAlignment); + descriptor_heap_samplers = std::make_unique(get_device(), + heap_buffer_size, + VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, + VMA_MEMORY_USAGE_CPU_TO_GPU); + + // Sampler heap + // We need to calculate some aligned offsets, heaps and strides to make sure we properly accress the descriptors + sampler_descriptor_size = aligned_size(descriptor_heap_properties.samplerDescriptorSize, descriptor_heap_properties.samplerDescriptorAlignment); + + // No need to create an actual VkSampler, we can simply pass the create info that describes the sampler + std::array sampler_create_infos{ + VkSamplerCreateInfo{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .magFilter = VK_FILTER_LINEAR, + .minFilter = VK_FILTER_LINEAR, + .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, + .mipLodBias = 0.0f, + .maxAnisotropy = 16.0f, + .compareOp = VK_COMPARE_OP_NEVER, + .minLod = 0.0f, + .maxLod = (float) textures[0].image.get()->get_mipmaps().size(), + .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE, + }, + VkSamplerCreateInfo{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .magFilter = VK_FILTER_NEAREST, + .minFilter = VK_FILTER_NEAREST, + .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, + .mipLodBias = 0.0f, + .maxAnisotropy = 16.0f, + .compareOp = VK_COMPARE_OP_NEVER, + .minLod = 0.0f, + .maxLod = (float) textures[0].image.get()->get_mipmaps().size(), + .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE, + }}; + + VkHostAddressRangeEXT host_address_range_samplers{ + .address = (uint8_t *) (descriptor_heap_samplers->get_data()), + .size = sampler_descriptor_size * static_cast(sampler_create_infos.size())}; + vkWriteSamplerDescriptorsEXT(get_device().get_handle(), 1, sampler_create_infos.data(), &host_address_range_samplers); + + // Resource heap (buffers and images) + buffer_descriptor_size = aligned_size(descriptor_heap_properties.bufferDescriptorSize, descriptor_heap_properties.bufferDescriptorAlignment); + // Images are storted after the last buffer (aligned) + image_heap_offset = aligned_size(buffer_descriptor_size, descriptor_heap_properties.imageDescriptorAlignment); + image_descriptor_size = aligned_size(descriptor_heap_properties.imageDescriptorSize, descriptor_heap_properties.imageDescriptorAlignment); + + // @todo: fif + auto vector_size{3}; // @todo: proper calculation/explanation + std::vector host_address_ranges_resources(vector_size); + std::vector resource_descriptor_infos(vector_size); + + size_t heapResIndex{0}; + + // Buffer + std::array deviceAddressRangesUniformBuffer{}; + for (auto i = 0; i < 1; i++) + { + deviceAddressRangesUniformBuffer[i] = {.address = uniform_buffer->get_device_address(), .size = uniform_buffer->get_size()}; + resource_descriptor_infos[heapResIndex] = { + .sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT, + .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .data = { + .pAddressRange = &deviceAddressRangesUniformBuffer[i]}}; + host_address_ranges_resources[heapResIndex] = { + .address = (uint8_t *) (descriptor_heap_resources->get_data()) + buffer_descriptor_size * i, + .size = buffer_descriptor_size}; + + heapResIndex++; + } + + // Images + std::array imageViewCreateInfos{}; + std::array imageDescriptorInfo{}; + + // @offset + for (auto i = 0; i < rotations.size(); i++) + { + imageViewCreateInfos[i] = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = textures[i].image->get_vk_image().get_handle(), + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = textures[i].image->get_format(), + .subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = static_cast(textures[i].image->get_mipmaps().size()), .baseArrayLayer = 0, .layerCount = 1}, + }; + + imageDescriptorInfo[i] = { + .sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT, + .pView = &imageViewCreateInfos[i], + .layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + }; + + resource_descriptor_infos[heapResIndex] = { + .sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT, + .type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, + .data = { + .pImage = &imageDescriptorInfo[i]}}; + + host_address_ranges_resources[heapResIndex] = { + .address = (uint8_t *) (descriptor_heap_resources->get_data()) + image_heap_offset + image_descriptor_size * i, + .size = image_descriptor_size}; + + heapResIndex++; + } + + vkWriteResourceDescriptorsEXT(get_device().get_handle(), static_cast(resource_descriptor_infos.size()), resource_descriptor_infos.data(), host_address_ranges_resources.data()); +} + +void DescriptorHeap::create_pipeline() +{ + VkPipelineInputAssemblyStateCreateInfo input_assembly_state = vkb::initializers::pipeline_input_assembly_state_create_info(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE); + VkPipelineRasterizationStateCreateInfo rasterization_state = vkb::initializers::pipeline_rasterization_state_create_info(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0); + VkPipelineColorBlendAttachmentState blend_attachment_state = vkb::initializers::pipeline_color_blend_attachment_state(0xf, VK_FALSE); + + const auto color_attachment_state = vkb::initializers::pipeline_color_blend_attachment_state(0xf, VK_FALSE); + + VkPipelineColorBlendStateCreateInfo color_blend_state = vkb::initializers::pipeline_color_blend_state_create_info(1, &blend_attachment_state); + color_blend_state.attachmentCount = 1; + color_blend_state.pAttachments = &color_attachment_state; + + // Note: Using reversed depth-buffer for increased precision, so Greater depth values are kept + VkPipelineDepthStencilStateCreateInfo depth_stencil_state = vkb::initializers::pipeline_depth_stencil_state_create_info(VK_FALSE, VK_FALSE, VK_COMPARE_OP_GREATER); + VkPipelineViewportStateCreateInfo viewport_state = vkb::initializers::pipeline_viewport_state_create_info(1, 1, 0); + VkPipelineMultisampleStateCreateInfo multisample_state = vkb::initializers::pipeline_multisample_state_create_info(VK_SAMPLE_COUNT_1_BIT, 0); + std::vector dynamic_state_enables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; + VkPipelineDynamicStateCreateInfo dynamic_state = vkb::initializers::pipeline_dynamic_state_create_info(dynamic_state_enables.data(), static_cast(dynamic_state_enables.size()), 0); + + // Vertex bindings an attributes for model rendering + // Binding description + std::array vertex_input_bindings = { + vkb::initializers::vertex_input_binding_description(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX), + }; + + // Attribute descriptions + std::array vertex_input_attributes = { + vkb::initializers::vertex_input_attribute_description(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0), // Position + vkb::initializers::vertex_input_attribute_description(0, 1, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3), // Normal + vkb::initializers::vertex_input_attribute_description(0, 2, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 6), // UV + vkb::initializers::vertex_input_attribute_description(0, 3, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 16), // Color + }; + + VkPipelineVertexInputStateCreateInfo vertex_input_state = vkb::initializers::pipeline_vertex_input_state_create_info(); + vertex_input_state.vertexBindingDescriptionCount = static_cast(vertex_input_bindings.size()); + vertex_input_state.pVertexBindingDescriptions = vertex_input_bindings.data(); + vertex_input_state.vertexAttributeDescriptionCount = static_cast(vertex_input_attributes.size()); + vertex_input_state.pVertexAttributeDescriptions = vertex_input_attributes.data(); + + std::array shader_stages{}; + shader_stages[0] = load_shader("descriptor_heap", "cube.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); + shader_stages[1] = load_shader("descriptor_heap", "cube.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); + + // Descriptor heaps can be used without having to explicitly change the shaders + // This is done by specifiying the bindings and their types at the shader stage level + // As samplers require a different heap (than images), we can't use combined images + + std::array setAndBindingMappings = { + + // Buffer binding + VkDescriptorSetAndBindingMappingEXT{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT, + .descriptorSet = 0, + .firstBinding = 0, + .bindingCount = 1, + .resourceMask = VK_SPIRV_RESOURCE_TYPE_UNIFORM_BUFFER_BIT_EXT, + .source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT, + .sourceData = { + .constantOffset = { + .heapArrayStride = static_cast(buffer_descriptor_size) + } + } + }, + + // We are using multiple images, which requires us to set heapArrayStride to let the implementation know where image n+1 starts + VkDescriptorSetAndBindingMappingEXT{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT, + .descriptorSet = 1, + .firstBinding = 0, + .bindingCount = 1, + .resourceMask = VK_SPIRV_RESOURCE_TYPE_SAMPLED_IMAGE_BIT_EXT, + .source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT, + .sourceData = { + .constantOffset = { + .heapOffset = static_cast(image_heap_offset), + .heapArrayStride = static_cast(image_descriptor_size) + } + } + }, + + // As samplers require a different heap (than images), we can't use combined images but split image and sampler + VkDescriptorSetAndBindingMappingEXT{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT, + .descriptorSet = 2, + .firstBinding = 0, + .bindingCount = 1, + .resourceMask = VK_SPIRV_RESOURCE_TYPE_SAMPLER_BIT_EXT, + .source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT, + .sourceData = { + .constantOffset = { + .heapOffset = static_cast(sampler_heap_offset), + .heapArrayStride = static_cast(sampler_descriptor_size) + } + } + } + + }; + + VkShaderDescriptorSetAndBindingMappingInfoEXT descriptorSetAndBindingMappingInfo{ + .sType = VK_STRUCTURE_TYPE_SHADER_DESCRIPTOR_SET_AND_BINDING_MAPPING_INFO_EXT, + .mappingCount = static_cast(setAndBindingMappings.size()), + .pMappings = setAndBindingMappings.data()}; + + shader_stages[0].pNext = &descriptorSetAndBindingMappingInfo; + shader_stages[1].pNext = &descriptorSetAndBindingMappingInfo; + + // Create graphics pipeline for dynamic rendering + VkFormat color_rendering_format = get_render_context().get_format(); + + // Provide information for dynamic rendering + VkPipelineRenderingCreateInfoKHR pipeline_rendering_create_info{VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR}; + pipeline_rendering_create_info.colorAttachmentCount = 1; + pipeline_rendering_create_info.pColorAttachmentFormats = &color_rendering_format; + pipeline_rendering_create_info.depthAttachmentFormat = depth_format; + if (!vkb::is_depth_only_format(depth_format)) + { + pipeline_rendering_create_info.stencilAttachmentFormat = depth_format; + } + + // Use the pNext to point to the rendering create struct + VkGraphicsPipelineCreateInfo pipeline_create_info{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO}; + pipeline_create_info.renderPass = VK_NULL_HANDLE; + pipeline_create_info.pInputAssemblyState = &input_assembly_state; + pipeline_create_info.pRasterizationState = &rasterization_state; + pipeline_create_info.pColorBlendState = &color_blend_state; + pipeline_create_info.pMultisampleState = &multisample_state; + pipeline_create_info.pViewportState = &viewport_state; + pipeline_create_info.pDepthStencilState = &depth_stencil_state; + pipeline_create_info.pDynamicState = &dynamic_state; + pipeline_create_info.pVertexInputState = &vertex_input_state; + pipeline_create_info.stageCount = static_cast(shader_stages.size()); + pipeline_create_info.pStages = shader_stages.data(); + + // With descriptor heaps we no longer need a pipeline layout + // This struct must be chained into pipeline creation to enable the use of heaps (allowing us to leave pipelineLayout empty) + VkPipelineCreateFlags2CreateInfo pipeline_create_flags_2{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_CREATE_FLAGS_2_CREATE_INFO, + .pNext = &pipeline_rendering_create_info, + .flags = VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_EXT}; + pipeline_create_info.pNext = &pipeline_create_flags_2; + + VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), VK_NULL_HANDLE, 1, &pipeline_create_info, VK_NULL_HANDLE, &pipeline)); +} + +void DescriptorHeap::draw() +{ + ApiVulkanSample::prepare_frame(); + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &draw_cmd_buffers[current_buffer]; + VK_CHECK(vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE)); + ApiVulkanSample::submit_frame(); +} + +void DescriptorHeap::build_command_buffers() +{ + std::array clear_values{}; + clear_values[0].color = {{0.0f, 0.0f, 0.0f, 0.0f}}; + clear_values[1].depthStencil = {0.0f, 0}; + + int i = 0; + for (auto &draw_cmd_buffer : draw_cmd_buffers) + { + auto command_begin = vkb::initializers::command_buffer_begin_info(); + VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffer, &command_begin)); + + VkImageSubresourceRange range{}; + range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + range.baseMipLevel = 0; + range.levelCount = VK_REMAINING_MIP_LEVELS; + range.baseArrayLayer = 0; + range.layerCount = VK_REMAINING_ARRAY_LAYERS; + + VkImageSubresourceRange depth_range{range}; + depth_range.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + + vkb::image_layout_transition(draw_cmd_buffer, + swapchain_buffers[i].image, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + 0, + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + range); + + vkb::image_layout_transition(draw_cmd_buffer, + depth_stencil.image, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, + depth_range); + + VkRenderingAttachmentInfoKHR color_attachment_info = vkb::initializers::rendering_attachment_info(); + color_attachment_info.imageView = swapchain_buffers[i].view; // color_attachment.image_view; + color_attachment_info.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + color_attachment_info.resolveMode = VK_RESOLVE_MODE_NONE; + color_attachment_info.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + color_attachment_info.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + color_attachment_info.clearValue = clear_values[0]; + + VkRenderingAttachmentInfoKHR depth_attachment_info = vkb::initializers::rendering_attachment_info(); + depth_attachment_info.imageView = depth_stencil.view; + depth_attachment_info.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL; + depth_attachment_info.resolveMode = VK_RESOLVE_MODE_NONE; + depth_attachment_info.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depth_attachment_info.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depth_attachment_info.clearValue = clear_values[1]; + + auto render_area = VkRect2D{VkOffset2D{}, VkExtent2D{width, height}}; + auto render_info = vkb::initializers::rendering_info(render_area, 1, &color_attachment_info); + render_info.layerCount = 1; + render_info.pDepthAttachment = &depth_attachment_info; + if (!vkb::is_depth_only_format(depth_format)) + { + render_info.pStencilAttachment = &depth_attachment_info; + } + + vkCmdBeginRenderingKHR(draw_cmd_buffer, &render_info); + + VkViewport viewport = vkb::initializers::viewport(static_cast(width), static_cast(height), 0.0f, 1.0f); + vkCmdSetViewport(draw_cmd_buffer, 0, 1, &viewport); + + VkRect2D scissor = vkb::initializers::rect2D(static_cast(width), static_cast(height), 0, 0); + vkCmdSetScissor(draw_cmd_buffer, 0, 1, &scissor); + + vkCmdBindPipeline(draw_cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + + // Pass options as push data + struct PushData + { + int32_t samplerIndex; + int32_t frameIndex; + } pushData = { + .samplerIndex = selected_sampler, + // .frameIndex = static_cast(currentBuffer), + }; + VkPushDataInfoEXT pushDataInfo{ + .sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT, + .data = {.address = &pushData, .size = sizeof(PushData)}}; + vkCmdPushDataEXT(draw_cmd_buffer, &pushDataInfo); + + // Bind the heap containing resources (buffers and images) + VkBindHeapInfoEXT bindHeapInfoRes{ + .sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT, + .heapRange{ + .address = descriptor_heap_resources->get_device_address(), + .size = descriptor_heap_resources->get_size()}, + .reservedRangeSize = descriptor_heap_properties.minResourceHeapReservedRange, + }; + vkCmdBindResourceHeapEXT(draw_cmd_buffer, &bindHeapInfoRes); + + // Bind the heap containing samplers + VkBindHeapInfoEXT bindHeapInfoSamplers{ + .sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT, + .heapRange{ + .address = descriptor_heap_samplers->get_device_address(), + .size = descriptor_heap_samplers->get_size()}, + .reservedRangeSize = descriptor_heap_properties.minSamplerHeapReservedRange}; + vkCmdBindSamplerHeapEXT(draw_cmd_buffer, &bindHeapInfoSamplers); + + draw_model(cube, draw_cmd_buffer); + + vkCmdEndRenderingKHR(draw_cmd_buffer); + + vkb::image_layout_transition(draw_cmd_buffer, + swapchain_buffers[i].image, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + range); + + VK_CHECK(vkEndCommandBuffer(draw_cmd_buffer)); + i++; + } +} + +void DescriptorHeap::render(float delta_time) +{ + if (!prepared) + { + return; + } + update_uniform_buffers(); + draw(); +} + +std::unique_ptr create_descriptor_heap() +{ + return std::make_unique(); +} diff --git a/samples/extensions/descriptor_heap/descriptor_heap.h b/samples/extensions/descriptor_heap/descriptor_heap.h new file mode 100644 index 0000000000..acd7dff946 --- /dev/null +++ b/samples/extensions/descriptor_heap/descriptor_heap.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2026 Sascha Willems + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "api_vulkan_sample.h" + +class DescriptorHeap : public ApiVulkanSample +{ + public: + DescriptorHeap(); + ~DescriptorHeap() override; + + bool prepare(const vkb::ApplicationOptions &options) override; + + void render(float delta_time) override; + void build_command_buffers() override; + void request_gpu_features(vkb::core::PhysicalDeviceC &gpu) override; + + private: + std::array textures; + std::array rotations; + + struct UniformData + { + glm::mat4 projection_matrix; + glm::mat4 view_matrix; + glm::mat4 model_matrix[2]; + } uniform_data; + + // @todo: fif + VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptor_heap_properties{}; + std::unique_ptr descriptor_heap_resources; + std::unique_ptr descriptor_heap_samplers; + std::unique_ptr uniform_buffer; + + int32_t selected_sampler{0}; + + std::unique_ptr cube; + + // Size and offset values for heap objects + VkDeviceSize buffer_heap_offset{0}; + VkDeviceSize buffer_descriptor_size{0}; + VkDeviceSize image_heap_offset{0}; + VkDeviceSize image_descriptor_size{0}; + VkDeviceSize sampler_heap_offset{0}; + VkDeviceSize sampler_descriptor_size{0}; + + std::vector sampler_names{"Linear", "Nearest"}; + + VkPipeline pipeline{nullptr}; + + uint32_t get_api_version() const override; + + void load_assets(); + void prepare_uniform_buffers(); + void update_uniform_buffers(); + void create_descriptor_heaps(); + void create_pipeline(); + void draw(); +}; + +std::unique_ptr create_descriptor_heap(); From 4ed5049838397c5372aa2207f9f6a9ef2ed378ba Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Fri, 27 Feb 2026 10:29:39 +0100 Subject: [PATCH 02/34] First working version --- .../descriptor_heap/descriptor_heap.cpp | 287 ++++++++++-------- .../descriptor_heap/descriptor_heap.h | 10 +- shaders/descriptor_heap/glsl/cube.frag | 35 +++ shaders/descriptor_heap/glsl/cube.frag.spv | Bin 0 -> 1216 bytes shaders/descriptor_heap/glsl/cube.vert | 41 +++ shaders/descriptor_heap/glsl/cube.vert.spv | Bin 0 -> 2200 bytes 6 files changed, 237 insertions(+), 136 deletions(-) create mode 100644 shaders/descriptor_heap/glsl/cube.frag create mode 100644 shaders/descriptor_heap/glsl/cube.frag.spv create mode 100644 shaders/descriptor_heap/glsl/cube.vert create mode 100644 shaders/descriptor_heap/glsl/cube.vert.spv diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp index 751d95328f..5c98f8f3d3 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.cpp +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -75,6 +75,14 @@ void DescriptorHeap::request_gpu_features(vkb::core::PhysicalDeviceC &gpu) } } +void DescriptorHeap::on_update_ui_overlay(vkb::Drawer &drawer) +{ + if (drawer.combo_box("Sampler type", &selected_sampler, sampler_names)) + { + rebuild_command_buffers(); + } +} + uint32_t DescriptorHeap::get_api_version() const { // @todo: 1.3, add note that it's not required per se @@ -83,7 +91,7 @@ uint32_t DescriptorHeap::get_api_version() const void DescriptorHeap::load_assets() { - cube = load_model("scenes/cube.gltf"); + cube = load_model("scenes/textured_unit_cube.gltf"); textures[0] = load_texture("textures/crate01_color_height_rgba.ktx", vkb::sg::Image::Color); textures[1] = load_texture("textures/crate02_color_height_rgba.ktx", vkb::sg::Image::Color); } @@ -98,10 +106,35 @@ void DescriptorHeap::prepare_uniform_buffers() uniform_buffer = std::make_unique(get_device(), sizeof(UniformData), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU); } -void DescriptorHeap::update_uniform_buffers() +void DescriptorHeap::update_uniform_buffers(float delta_time) { + if (animate) + { + rotations[0].x += 2.5f * delta_time; + if (rotations[0].x > 360.0f) + { + rotations[0].x -= 360.0f; + } + rotations[1].y += 2.0f * delta_time; + if (rotations[1].y > 360.0f) + { + rotations[1].y -= 360.0f; + } + } + uniform_data.projection_matrix = camera.matrices.perspective; - uniform_data.view_matrix = camera.matrices.view * glm::mat4(1.f); + uniform_data.view_matrix = camera.matrices.view; + + std::array positions = {glm::vec3(-2.0f, 0.0f, 0.0f), glm::vec3(1.5f, 0.5f, 0.0f)}; + for (auto i = 0; i < rotations.size(); i++) + { + glm::mat4 cubeMat = glm::translate(glm::mat4(1.0f), positions[i]); + cubeMat = glm::rotate(cubeMat, glm::radians(rotations[i].x), glm::vec3(1.0f, 0.0f, 0.0f)); + cubeMat = glm::rotate(cubeMat, glm::radians(rotations[i].y), glm::vec3(0.0f, 1.0f, 0.0f)); + cubeMat = glm::rotate(cubeMat, glm::radians(rotations[i].z), glm::vec3(0.0f, 0.0f, 1.0f)); + uniform_data.model_matrix[i] = cubeMat; + } + uniform_buffer->convert_and_update(uniform_data); } @@ -131,6 +164,11 @@ void DescriptorHeap::create_descriptor_heaps() // We need to calculate some aligned offsets, heaps and strides to make sure we properly accress the descriptors sampler_descriptor_size = aligned_size(descriptor_heap_properties.samplerDescriptorSize, descriptor_heap_properties.samplerDescriptorAlignment); + auto sampler_start = aligned_size(descriptor_heap_properties.minSamplerHeapReservedRange, descriptor_heap_properties.samplerDescriptorAlignment); + sampler_heap_offset = aligned_size(sampler_start, descriptor_heap_properties.samplerDescriptorSize); + + std::array host_address_ranges_samplers{}; + // No need to create an actual VkSampler, we can simply pass the create info that describes the sampler std::array sampler_create_infos{ VkSamplerCreateInfo{ @@ -138,36 +176,32 @@ void DescriptorHeap::create_descriptor_heaps() .magFilter = VK_FILTER_LINEAR, .minFilter = VK_FILTER_LINEAR, .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, - .addressModeU = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, - .addressModeV = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, - .addressModeW = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, - .mipLodBias = 0.0f, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT, .maxAnisotropy = 16.0f, - .compareOp = VK_COMPARE_OP_NEVER, - .minLod = 0.0f, .maxLod = (float) textures[0].image.get()->get_mipmaps().size(), - .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE, }, VkSamplerCreateInfo{ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .magFilter = VK_FILTER_NEAREST, .minFilter = VK_FILTER_NEAREST, - .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, - .addressModeU = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, - .addressModeV = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, - .addressModeW = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, - .mipLodBias = 0.0f, + .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT, .maxAnisotropy = 16.0f, - .compareOp = VK_COMPARE_OP_NEVER, - .minLod = 0.0f, .maxLod = (float) textures[0].image.get()->get_mipmaps().size(), - .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE, }}; - VkHostAddressRangeEXT host_address_range_samplers{ - .address = (uint8_t *) (descriptor_heap_samplers->get_data()), - .size = sampler_descriptor_size * static_cast(sampler_create_infos.size())}; - vkWriteSamplerDescriptorsEXT(get_device().get_handle(), 1, sampler_create_infos.data(), &host_address_range_samplers); + for (auto i = 0; i < static_cast(sampler_create_infos.size()); i++) + { + host_address_ranges_samplers[i] = { + .address = (uint8_t *) (descriptor_heap_samplers->get_data()) + sampler_heap_offset + sampler_descriptor_size * i, + .size = sampler_descriptor_size}; + } + + vkWriteSamplerDescriptorsEXT(get_device().get_handle(), static_cast(host_address_ranges_samplers.size()), sampler_create_infos.data(), host_address_ranges_samplers.data()); // Resource heap (buffers and images) buffer_descriptor_size = aligned_size(descriptor_heap_properties.bufferDescriptorSize, descriptor_heap_properties.bufferDescriptorAlignment); @@ -176,7 +210,7 @@ void DescriptorHeap::create_descriptor_heaps() image_descriptor_size = aligned_size(descriptor_heap_properties.imageDescriptorSize, descriptor_heap_properties.imageDescriptorAlignment); // @todo: fif - auto vector_size{3}; // @todo: proper calculation/explanation + auto vector_size{3}; // @todo: proper calculation/explanation std::vector host_address_ranges_resources(vector_size); std::vector resource_descriptor_infos(vector_size); @@ -239,36 +273,23 @@ void DescriptorHeap::create_descriptor_heaps() void DescriptorHeap::create_pipeline() { VkPipelineInputAssemblyStateCreateInfo input_assembly_state = vkb::initializers::pipeline_input_assembly_state_create_info(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE); - VkPipelineRasterizationStateCreateInfo rasterization_state = vkb::initializers::pipeline_rasterization_state_create_info(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0); + VkPipelineRasterizationStateCreateInfo rasterization_state = vkb::initializers::pipeline_rasterization_state_create_info(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_CLOCKWISE, 0); VkPipelineColorBlendAttachmentState blend_attachment_state = vkb::initializers::pipeline_color_blend_attachment_state(0xf, VK_FALSE); - - const auto color_attachment_state = vkb::initializers::pipeline_color_blend_attachment_state(0xf, VK_FALSE); - - VkPipelineColorBlendStateCreateInfo color_blend_state = vkb::initializers::pipeline_color_blend_state_create_info(1, &blend_attachment_state); - color_blend_state.attachmentCount = 1; - color_blend_state.pAttachments = &color_attachment_state; - - // Note: Using reversed depth-buffer for increased precision, so Greater depth values are kept - VkPipelineDepthStencilStateCreateInfo depth_stencil_state = vkb::initializers::pipeline_depth_stencil_state_create_info(VK_FALSE, VK_FALSE, VK_COMPARE_OP_GREATER); - VkPipelineViewportStateCreateInfo viewport_state = vkb::initializers::pipeline_viewport_state_create_info(1, 1, 0); - VkPipelineMultisampleStateCreateInfo multisample_state = vkb::initializers::pipeline_multisample_state_create_info(VK_SAMPLE_COUNT_1_BIT, 0); - std::vector dynamic_state_enables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; - VkPipelineDynamicStateCreateInfo dynamic_state = vkb::initializers::pipeline_dynamic_state_create_info(dynamic_state_enables.data(), static_cast(dynamic_state_enables.size()), 0); - - // Vertex bindings an attributes for model rendering - // Binding description - std::array vertex_input_bindings = { + VkPipelineColorBlendStateCreateInfo color_blend_state = vkb::initializers::pipeline_color_blend_state_create_info(1, &blend_attachment_state); + VkPipelineDepthStencilStateCreateInfo depth_stencil_state = vkb::initializers::pipeline_depth_stencil_state_create_info(VK_TRUE, VK_TRUE, VK_COMPARE_OP_GREATER); + VkPipelineViewportStateCreateInfo viewport_state = vkb::initializers::pipeline_viewport_state_create_info(1, 1, 0); + VkPipelineMultisampleStateCreateInfo multisample_state = vkb::initializers::pipeline_multisample_state_create_info(VK_SAMPLE_COUNT_1_BIT, 0); + std::vector dynamic_state_enables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; + VkPipelineDynamicStateCreateInfo dynamic_state = vkb::initializers::pipeline_dynamic_state_create_info(dynamic_state_enables); + + // Vertex bindings and attributes + const std::vector vertex_input_bindings = { vkb::initializers::vertex_input_binding_description(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX), }; - - // Attribute descriptions - std::array vertex_input_attributes = { - vkb::initializers::vertex_input_attribute_description(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0), // Position - vkb::initializers::vertex_input_attribute_description(0, 1, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3), // Normal - vkb::initializers::vertex_input_attribute_description(0, 2, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 6), // UV - vkb::initializers::vertex_input_attribute_description(0, 3, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 16), // Color + const std::vector vertex_input_attributes = { + vkb::initializers::vertex_input_attribute_description(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0), // Location 0: Position + vkb::initializers::vertex_input_attribute_description(0, 1, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 6), // Location 1: UV }; - VkPipelineVertexInputStateCreateInfo vertex_input_state = vkb::initializers::pipeline_vertex_input_state_create_info(); vertex_input_state.vertexBindingDescriptionCount = static_cast(vertex_input_bindings.size()); vertex_input_state.pVertexBindingDescriptions = vertex_input_bindings.data(); @@ -283,56 +304,43 @@ void DescriptorHeap::create_pipeline() // This is done by specifiying the bindings and their types at the shader stage level // As samplers require a different heap (than images), we can't use combined images - std::array setAndBindingMappings = { - - // Buffer binding - VkDescriptorSetAndBindingMappingEXT{ - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT, - .descriptorSet = 0, - .firstBinding = 0, - .bindingCount = 1, - .resourceMask = VK_SPIRV_RESOURCE_TYPE_UNIFORM_BUFFER_BIT_EXT, - .source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT, - .sourceData = { - .constantOffset = { - .heapArrayStride = static_cast(buffer_descriptor_size) - } - } - }, - - // We are using multiple images, which requires us to set heapArrayStride to let the implementation know where image n+1 starts - VkDescriptorSetAndBindingMappingEXT{ - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT, - .descriptorSet = 1, - .firstBinding = 0, - .bindingCount = 1, - .resourceMask = VK_SPIRV_RESOURCE_TYPE_SAMPLED_IMAGE_BIT_EXT, - .source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT, - .sourceData = { - .constantOffset = { - .heapOffset = static_cast(image_heap_offset), - .heapArrayStride = static_cast(image_descriptor_size) - } - } - }, - - // As samplers require a different heap (than images), we can't use combined images but split image and sampler - VkDescriptorSetAndBindingMappingEXT{ - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT, - .descriptorSet = 2, - .firstBinding = 0, - .bindingCount = 1, - .resourceMask = VK_SPIRV_RESOURCE_TYPE_SAMPLER_BIT_EXT, - .source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT, - .sourceData = { - .constantOffset = { - .heapOffset = static_cast(sampler_heap_offset), - .heapArrayStride = static_cast(sampler_descriptor_size) - } - } - } - - }; + std::array setAndBindingMappings{}; + + // Buffer binding + setAndBindingMappings[0] = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT, + .descriptorSet = 0, + .firstBinding = 0, + .bindingCount = 1, + .resourceMask = VK_SPIRV_RESOURCE_TYPE_UNIFORM_BUFFER_BIT_EXT, + .source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT, + .sourceData = { + .constantOffset = { + .heapArrayStride = static_cast(buffer_descriptor_size)}}}; + + // We are using multiple images, which requires us to set heapArrayStride to let the implementation know where image n+1 starts + setAndBindingMappings[1] = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT, + .descriptorSet = 1, + .firstBinding = 0, + .bindingCount = 1, + .resourceMask = VK_SPIRV_RESOURCE_TYPE_SAMPLED_IMAGE_BIT_EXT, + .source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT, + .sourceData = { + .constantOffset = { + .heapOffset = static_cast(image_heap_offset), .heapArrayStride = static_cast(image_descriptor_size)}}}; + + // As samplers require a different heap (than images), we can't use combined images but split image and sampler + setAndBindingMappings[2] = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT, + .descriptorSet = 2, + .firstBinding = 0, + .bindingCount = 1, + .resourceMask = VK_SPIRV_RESOURCE_TYPE_SAMPLER_BIT_EXT, + .source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT, + .sourceData = { + .constantOffset = { + .heapOffset = static_cast(sampler_heap_offset), .heapArrayStride = static_cast(sampler_descriptor_size)}}}; VkShaderDescriptorSetAndBindingMappingInfoEXT descriptorSetAndBindingMappingInfo{ .sType = VK_STRUCTURE_TYPE_SHADER_DESCRIPTOR_SET_AND_BINDING_MAPPING_INFO_EXT, @@ -346,28 +354,30 @@ void DescriptorHeap::create_pipeline() VkFormat color_rendering_format = get_render_context().get_format(); // Provide information for dynamic rendering - VkPipelineRenderingCreateInfoKHR pipeline_rendering_create_info{VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR}; - pipeline_rendering_create_info.colorAttachmentCount = 1; - pipeline_rendering_create_info.pColorAttachmentFormats = &color_rendering_format; - pipeline_rendering_create_info.depthAttachmentFormat = depth_format; + VkPipelineRenderingCreateInfoKHR pipeline_rendering_create_info{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR, + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &color_rendering_format, + .depthAttachmentFormat = depth_format}; if (!vkb::is_depth_only_format(depth_format)) { pipeline_rendering_create_info.stencilAttachmentFormat = depth_format; } // Use the pNext to point to the rendering create struct - VkGraphicsPipelineCreateInfo pipeline_create_info{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO}; - pipeline_create_info.renderPass = VK_NULL_HANDLE; - pipeline_create_info.pInputAssemblyState = &input_assembly_state; - pipeline_create_info.pRasterizationState = &rasterization_state; - pipeline_create_info.pColorBlendState = &color_blend_state; - pipeline_create_info.pMultisampleState = &multisample_state; - pipeline_create_info.pViewportState = &viewport_state; - pipeline_create_info.pDepthStencilState = &depth_stencil_state; - pipeline_create_info.pDynamicState = &dynamic_state; - pipeline_create_info.pVertexInputState = &vertex_input_state; - pipeline_create_info.stageCount = static_cast(shader_stages.size()); - pipeline_create_info.pStages = shader_stages.data(); + VkGraphicsPipelineCreateInfo pipeline_create_info{ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .stageCount = static_cast(shader_stages.size()), + .pStages = shader_stages.data(), + .pVertexInputState = &vertex_input_state, + .pInputAssemblyState = &input_assembly_state, + .pViewportState = &viewport_state, + .pRasterizationState = &rasterization_state, + .pMultisampleState = &multisample_state, + .pDepthStencilState = &depth_stencil_state, + .pColorBlendState = &color_blend_state, + .pDynamicState = &dynamic_state, + }; // With descriptor heaps we no longer need a pipeline layout // This struct must be chained into pipeline creation to enable the use of heaps (allowing us to leave pipelineLayout empty) @@ -395,11 +405,10 @@ void DescriptorHeap::build_command_buffers() clear_values[0].color = {{0.0f, 0.0f, 0.0f, 0.0f}}; clear_values[1].depthStencil = {0.0f, 0}; - int i = 0; - for (auto &draw_cmd_buffer : draw_cmd_buffers) + for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i) { auto command_begin = vkb::initializers::command_buffer_begin_info(); - VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffer, &command_begin)); + VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffers[i], &command_begin)); VkImageSubresourceRange range{}; range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; @@ -411,7 +420,7 @@ void DescriptorHeap::build_command_buffers() VkImageSubresourceRange depth_range{range}; depth_range.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - vkb::image_layout_transition(draw_cmd_buffer, + vkb::image_layout_transition(draw_cmd_buffers[i], swapchain_buffers[i].image, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, @@ -421,7 +430,7 @@ void DescriptorHeap::build_command_buffers() VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, range); - vkb::image_layout_transition(draw_cmd_buffer, + vkb::image_layout_transition(draw_cmd_buffers[i], depth_stencil.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, @@ -452,15 +461,15 @@ void DescriptorHeap::build_command_buffers() render_info.pStencilAttachment = &depth_attachment_info; } - vkCmdBeginRenderingKHR(draw_cmd_buffer, &render_info); + vkCmdBeginRenderingKHR(draw_cmd_buffers[i], &render_info); VkViewport viewport = vkb::initializers::viewport(static_cast(width), static_cast(height), 0.0f, 1.0f); - vkCmdSetViewport(draw_cmd_buffer, 0, 1, &viewport); + vkCmdSetViewport(draw_cmd_buffers[i], 0, 1, &viewport); VkRect2D scissor = vkb::initializers::rect2D(static_cast(width), static_cast(height), 0, 0); - vkCmdSetScissor(draw_cmd_buffer, 0, 1, &scissor); + vkCmdSetScissor(draw_cmd_buffers[i], 0, 1, &scissor); - vkCmdBindPipeline(draw_cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); // Pass options as push data struct PushData @@ -474,39 +483,47 @@ void DescriptorHeap::build_command_buffers() VkPushDataInfoEXT pushDataInfo{ .sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT, .data = {.address = &pushData, .size = sizeof(PushData)}}; - vkCmdPushDataEXT(draw_cmd_buffer, &pushDataInfo); + vkCmdPushDataEXT(draw_cmd_buffers[i], &pushDataInfo); // Bind the heap containing resources (buffers and images) - VkBindHeapInfoEXT bindHeapInfoRes{ + VkBindHeapInfoEXT bind_heap_info_res{ .sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT, .heapRange{ .address = descriptor_heap_resources->get_device_address(), .size = descriptor_heap_resources->get_size()}, .reservedRangeSize = descriptor_heap_properties.minResourceHeapReservedRange, }; - vkCmdBindResourceHeapEXT(draw_cmd_buffer, &bindHeapInfoRes); + vkCmdBindResourceHeapEXT(draw_cmd_buffers[i], &bind_heap_info_res); // Bind the heap containing samplers - VkBindHeapInfoEXT bindHeapInfoSamplers{ + VkBindHeapInfoEXT bind_heap_info_samplers{ .sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT, .heapRange{ .address = descriptor_heap_samplers->get_device_address(), .size = descriptor_heap_samplers->get_size()}, .reservedRangeSize = descriptor_heap_properties.minSamplerHeapReservedRange}; - vkCmdBindSamplerHeapEXT(draw_cmd_buffer, &bindHeapInfoSamplers); + vkCmdBindSamplerHeapEXT(draw_cmd_buffers[i], &bind_heap_info_samplers); + + VkDeviceSize offsets[1] = {0}; + + const auto &vertex_buffer = cube->vertex_buffers.at("vertex_buffer"); + auto &index_buffer = cube->index_buffer; + + vkCmdBindVertexBuffers(draw_cmd_buffers[i], 0, 1, vertex_buffer.get(), offsets); + vkCmdBindIndexBuffer(draw_cmd_buffers[i], index_buffer->get_handle(), 0, cube->index_type); + vkCmdDrawIndexed(draw_cmd_buffers[i], cube->vertex_indices, 2, 0, 0, 0); - draw_model(cube, draw_cmd_buffer); + vkCmdEndRenderingKHR(draw_cmd_buffers[i]); - vkCmdEndRenderingKHR(draw_cmd_buffer); + draw_ui(draw_cmd_buffers[i], i); - vkb::image_layout_transition(draw_cmd_buffer, + vkb::image_layout_transition(draw_cmd_buffers[i], swapchain_buffers[i].image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, range); - VK_CHECK(vkEndCommandBuffer(draw_cmd_buffer)); - i++; + VK_CHECK(vkEndCommandBuffer(draw_cmd_buffers[i])); } } @@ -516,7 +533,7 @@ void DescriptorHeap::render(float delta_time) { return; } - update_uniform_buffers(); + update_uniform_buffers(delta_time); draw(); } diff --git a/samples/extensions/descriptor_heap/descriptor_heap.h b/samples/extensions/descriptor_heap/descriptor_heap.h index acd7dff946..95656d19ab 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.h +++ b/samples/extensions/descriptor_heap/descriptor_heap.h @@ -31,8 +31,11 @@ class DescriptorHeap : public ApiVulkanSample void render(float delta_time) override; void build_command_buffers() override; void request_gpu_features(vkb::core::PhysicalDeviceC &gpu) override; + void on_update_ui_overlay(vkb::Drawer &drawer) override; private: + bool animate = true; + std::array textures; std::array rotations; @@ -53,6 +56,11 @@ class DescriptorHeap : public ApiVulkanSample std::unique_ptr cube; + struct Models + { + std::unique_ptr cube; + } models; + // Size and offset values for heap objects VkDeviceSize buffer_heap_offset{0}; VkDeviceSize buffer_descriptor_size{0}; @@ -69,7 +77,7 @@ class DescriptorHeap : public ApiVulkanSample void load_assets(); void prepare_uniform_buffers(); - void update_uniform_buffers(); + void update_uniform_buffers(float delta_time); void create_descriptor_heaps(); void create_pipeline(); void draw(); diff --git a/shaders/descriptor_heap/glsl/cube.frag b/shaders/descriptor_heap/glsl/cube.frag new file mode 100644 index 0000000000..7d2cc2fb4d --- /dev/null +++ b/shaders/descriptor_heap/glsl/cube.frag @@ -0,0 +1,35 @@ +#version 450 +/* Copyright (c) 2026, Sascha Willems + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +layout (set = 1, binding = 0) uniform texture2D textureImage[2]; +layout (set = 2, binding = 0) uniform sampler textureSampler[2]; + +layout (location = 0) in vec2 inUV; +layout (location = 1) flat in int inInstanceIndex; + +layout(push_constant) uniform PushConsts { + int samplerIndex; + int frameIndex; +} pushConsts; + +layout (location = 0) out vec4 outFragColor; + +void main() +{ + outFragColor = texture(sampler2D(textureImage[inInstanceIndex], textureSampler[pushConsts.samplerIndex]), inUV); +} \ No newline at end of file diff --git a/shaders/descriptor_heap/glsl/cube.frag.spv b/shaders/descriptor_heap/glsl/cube.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..9e37aee961ec7389e9e416623275ee37f7e8f196 GIT binary patch literal 1216 zcmYk5T~8BH5QdNR0~AFN0RhD=_>Cc6s4+2`7!qp}E=&k`y^$50&@Jh16Mmn+${Q1( zXLnENG_!N&J@3q%nRD8j+nW#JMpz6B;dQ8GCCq^Yux@4hN5@A8MR|7c_T3v9OQBT} z<=iE%ja5hKFn8l7dJlbxF2i+?BCJ|u4&e`h9njgB4z-LMPs^{9^!#vqF`nSU>rx|x zGP^3LlWZ_b&$EbIW0()}qD=Fj*&sj5t}6c4HUBsrU0!5l>hRkJ|2{2#AC3tr0-5J4 z%zQ;ntdrn(`N_A(Uz2pyq_6Tk0=t~aV=wkP%uh}+p^tAbJ35HIJ@hK?+TX=Df1kP7 z%Tl#&S8HSIy>_sl*;svJou#p!)AhHeN0wpF4-zqRi8*u6<9XxxR*5#gSKUyZYn^o$ zEI~Kmdw=@ICwv#Mz5?-&jc?Cln{aMt?;R*R_|D%N zb^SIu>i>Yae0|@H^RZ8ParU0`akCfWckva^u+2oDVm+hx@Eq%0osqFGu=XQvEq(7+ z>^<*6o_D5rH-AAo*iWF=TScA87f|Q1hB{MmIo4fA#pQU`1hpS~h-Q9eN>+KXF8~77{Wg~{DA%koc&7E literal 0 HcmV?d00001 diff --git a/shaders/descriptor_heap/glsl/cube.vert b/shaders/descriptor_heap/glsl/cube.vert new file mode 100644 index 0000000000..d07aebf182 --- /dev/null +++ b/shaders/descriptor_heap/glsl/cube.vert @@ -0,0 +1,41 @@ +#version 450 +/* Copyright (c) 2026, Sascha Willems + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +layout (location = 0) in vec3 inPos; +layout (location = 1) in vec2 inUV; + +layout(push_constant) uniform PushConsts { + int samplerIndex; + int frameIndex; +} pushConsts; + +layout (set = 0, binding = 0) uniform UBO { + mat4 projection; + mat4 view; + mat4 model[2]; +} ubo[2]; + +layout (location = 0) out vec2 outUV; +layout (location = 1) flat out int outInstanceIndex; + +void main() +{ + outUV = inUV; + gl_Position = ubo[pushConsts.frameIndex].projection * ubo[pushConsts.frameIndex].view * ubo[pushConsts.frameIndex].model[gl_InstanceIndex] * vec4(inPos.xyz, 1.0); + outInstanceIndex = gl_InstanceIndex; +} \ No newline at end of file diff --git a/shaders/descriptor_heap/glsl/cube.vert.spv b/shaders/descriptor_heap/glsl/cube.vert.spv new file mode 100644 index 0000000000000000000000000000000000000000..0d1fe0f39ec32fdc52e25e632b20e22f6f7550ba GIT binary patch literal 2200 zcmZXTYflqV5QYzw7QBIoTvV)E@Q&gQ5JjLZBtSwUhQL=-VbwM5ZqwbW@e_YRf0Dn- zFDAav?m2B!PcxmFcdlpVY;JIV*q8xx!Hk$~lbvxhD8vXWq}w?lC~f;pW^iJHSoV-cNn!B(Wze-C)cca z`$Hr-Z*EFHXvd$oqA*V4tY0urTx&Gzerqp0?cN_7H9od#4c}VGb4ysWCr@58VIR8J zo-%XaP9EmXo`OvvmHX&?Xj?$})wW#^!?D|GtU(S6tzcidGbWKQ-{x){D-aOX!1 z81r`+G8pfLS~KDWQ90GKJ!G|pI(x{=Mm#&>=#iVtqF!tsgU+sU;zu97ap?VVV8oS0 zE{@v0ap?VVV8oq?xC8qxi8xN5%hYs!?35V#b-VBOn_;dOHy3uxoEAjnWS`v~<v;M0Xu!6^UZ=$=c>3TA}@8&Cq4;Lp+ng4xh~Fo!iS!2h!YP^Pw4C)tSFuR(nr?YP`Wc_ z364KIgRd=f7w%{GUiB7Ju@i^LD`4&9XSNcDr1$HgNpW zd0Y6CAKzCZW6n0ozbT1W=7?Ugx~qp?wb+VgNUzMBL);%Bd{65_c|R9LyqyhMxhHx> z9wqTP5wr0`_)}wE1YS{R%;JuSd4scS{Fu#M5jL0Wp5*>q_l1GGTn{9Z3)~aATn{B< z`=!3Jxo7#oUH(Us`}02*2JU))CYfDSbaQ%tE}|ZO>=3)F`9d;wS93`+-#$1#>}yrT zuHLCH_Vn6fVjXPFVgo(c8;h}<1NFi#*G0tRkDZTUjP6A literal 0 HcmV?d00001 From 4f66dc26aac7bf80dcd56e9e735ed80300619f7c Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Fri, 27 Feb 2026 12:53:35 +0100 Subject: [PATCH 03/34] Trying to fix clang format --- .../descriptor_heap/descriptor_heap.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp index 5c98f8f3d3..e092ba2a69 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.cpp +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -149,16 +149,18 @@ void DescriptorHeap::create_descriptor_heaps() // There are two descriptor heap types: One that can store resources (buffers, images) and one that can store samplers // We create heaps with a fixed size that's guaranteed to fit in the few descriptors we use const VkDeviceSize heap_buffer_size = aligned_size(2048 + descriptor_heap_properties.minResourceHeapReservedRange, descriptor_heap_properties.resourceHeapAlignment); - descriptor_heap_resources = std::make_unique(get_device(), - heap_buffer_size, - VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, - VMA_MEMORY_USAGE_CPU_TO_GPU); + + descriptor_heap_resources = std::make_unique(get_device(), + heap_buffer_size, + VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, + VMA_MEMORY_USAGE_CPU_TO_GPU); const VkDeviceSize heap_sampler_size = aligned_size(2048 + descriptor_heap_properties.minSamplerHeapReservedRange, descriptor_heap_properties.samplerHeapAlignment); - descriptor_heap_samplers = std::make_unique(get_device(), - heap_buffer_size, - VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, - VMA_MEMORY_USAGE_CPU_TO_GPU); + + descriptor_heap_samplers = std::make_unique(get_device(), + heap_buffer_size, + VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, + VMA_MEMORY_USAGE_CPU_TO_GPU); // Sampler heap // We need to calculate some aligned offsets, heaps and strides to make sure we properly accress the descriptors From d726cc91911888c0f7782e5ddd034daf21ff233e Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Fri, 27 Feb 2026 12:59:00 +0100 Subject: [PATCH 04/34] Clang format --- samples/extensions/descriptor_heap/descriptor_heap.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/extensions/descriptor_heap/descriptor_heap.h b/samples/extensions/descriptor_heap/descriptor_heap.h index 95656d19ab..347f9fc50f 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.h +++ b/samples/extensions/descriptor_heap/descriptor_heap.h @@ -48,9 +48,9 @@ class DescriptorHeap : public ApiVulkanSample // @todo: fif VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptor_heap_properties{}; - std::unique_ptr descriptor_heap_resources; - std::unique_ptr descriptor_heap_samplers; - std::unique_ptr uniform_buffer; + std::unique_ptr descriptor_heap_resources; + std::unique_ptr descriptor_heap_samplers; + std::unique_ptr uniform_buffer; int32_t selected_sampler{0}; From 9b2037d41a6a5859f1c78ceee82fff380ea609c1 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Fri, 27 Feb 2026 13:20:15 +0100 Subject: [PATCH 05/34] Add Slang and HLSL shaders --- shaders/descriptor_heap/hlsl/cube.frag.hlsl | 40 ++++++++++++++ shaders/descriptor_heap/hlsl/cube.vert.hlsl | 53 +++++++++++++++++++ shaders/descriptor_heap/slang/cube.frag.slang | 32 +++++++++++ shaders/descriptor_heap/slang/cube.vert.slang | 46 ++++++++++++++++ 4 files changed, 171 insertions(+) create mode 100644 shaders/descriptor_heap/hlsl/cube.frag.hlsl create mode 100644 shaders/descriptor_heap/hlsl/cube.vert.hlsl create mode 100644 shaders/descriptor_heap/slang/cube.frag.slang create mode 100644 shaders/descriptor_heap/slang/cube.vert.slang diff --git a/shaders/descriptor_heap/hlsl/cube.frag.hlsl b/shaders/descriptor_heap/hlsl/cube.frag.hlsl new file mode 100644 index 0000000000..771da7a147 --- /dev/null +++ b/shaders/descriptor_heap/hlsl/cube.frag.hlsl @@ -0,0 +1,40 @@ +/* Copyright (c) 2026, Sascha Willems + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +[[vk::binding(0, 1)]] +Texture2D textureImage[2] : register(t1, space2); +[[vk::binding(0, 2)]] +SamplerState textureSampler[2] : register(s1, space2); + +struct VSOutput +{ + float4 Pos : SV_POSITION; + [[vk::location(0)]] float2 UV : TEXCOORD0; + [[vk::location(1)]] int InstanceIndex : TEXCOORD1; +}; + +struct PushConsts +{ + int samplerIndex; + int frameIndex; +}; +[[vk::push_constant]] PushConsts pushConsts; + +float4 main(VSOutput input) : SV_TARGET0 +{ + return textureImage[input.InstanceIndex].Sample(textureSampler[pushConsts.samplerIndex], input.UV); +} \ No newline at end of file diff --git a/shaders/descriptor_heap/hlsl/cube.vert.hlsl b/shaders/descriptor_heap/hlsl/cube.vert.hlsl new file mode 100644 index 0000000000..e2096ff606 --- /dev/null +++ b/shaders/descriptor_heap/hlsl/cube.vert.hlsl @@ -0,0 +1,53 @@ +/* Copyright (c) 2026, Sascha Willems + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +struct VSInput +{ + [[vk::location(0)]] float3 Pos : POSITION0; + [[vk::location(1)]] float2 UV : TEXCOORD0; +}; + +struct UBOCamera +{ + float4x4 projection; + float4x4 view; + float4x4 model[2]; +}; +[[vk::binding(0, 0)]] ConstantBuffer ubo[2] : register(b0, space0); + +struct VSOutput +{ + float4 Pos : SV_POSITION; + [[vk::location(0)]] float2 UV : TEXCOORD0; + [[vk::location(1)]] int InstanceIndex : TEXCOORD1; +}; + +struct PushConsts +{ + int samplerIndex; + int frameIndex; +}; +[[vk::push_constant]] PushConsts pushConsts; + +VSOutput main(VSInput input, uint InstanceIndex: SV_InstanceID) +{ + VSOutput output; + output.UV = input.UV; + output.Pos = mul(ubo[pushConsts.frameIndex].projection, mul(ubo[pushConsts.frameIndex].view, mul(ubo[pushConsts.frameIndex].model[InstanceIndex], float4(input.Pos.xyz, 1.0)))); + output.InstanceIndex = InstanceIndex; + return output; +}; \ No newline at end of file diff --git a/shaders/descriptor_heap/slang/cube.frag.slang b/shaders/descriptor_heap/slang/cube.frag.slang new file mode 100644 index 0000000000..2e90807f2c --- /dev/null +++ b/shaders/descriptor_heap/slang/cube.frag.slang @@ -0,0 +1,32 @@ +/* Copyright (c) 2026, Sascha Willems + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +struct VSOutput +{ + float4 Pos : SV_POSITION; + float2 UV; + int InstanceIndex; +}; + +[[vk::binding(0, 1)]] Texture2D textureImage[2]; +[[vk::binding(0, 2)]] SamplerState textureSampler[2]; + +[shader("fragment")] +float4 main(VSOutput input, uniform int samplerIndex, uniform int frameIndex) +{ + return textureImage[input.InstanceIndex].Sample(textureSampler[samplerIndex], input.UV); +} \ No newline at end of file diff --git a/shaders/descriptor_heap/slang/cube.vert.slang b/shaders/descriptor_heap/slang/cube.vert.slang new file mode 100644 index 0000000000..4ab1e60721 --- /dev/null +++ b/shaders/descriptor_heap/slang/cube.vert.slang @@ -0,0 +1,46 @@ +/* Copyright (c) 2026, Sascha Willems + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +struct VSInput +{ + float3 Pos; + float2 UV; +}; + +struct VSOutput +{ + float4 Pos : SV_POSITION; + float2 UV; + int InstanceIndex; +}; + +struct UBO { + float4x4 projection; + float4x4 view; + float4x4 model[2]; +}; +[[vk::binding(0, 0)]] ConstantBuffer ubo[2]; + +[shader("vertex")] +VSOutput main(VSInput input, uint InstanceIndex: SV_VulkanInstanceID, uniform int samplerIndex, uniform int frameIndex) +{ + VSOutput output; + output.UV = input.UV; + output.Pos = mul(ubo[frameIndex].projection, mul(ubo[frameIndex].view, mul(ubo[frameIndex].model[InstanceIndex], float4(input.Pos.xyz, 1.0)))); + output.InstanceIndex = InstanceIndex; + return output; +} \ No newline at end of file From a1c12a89df79da6267ed0ed4ec98002eb8f693e3 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Fri, 27 Feb 2026 15:54:06 +0100 Subject: [PATCH 06/34] No longer pre-record command buffers --- framework/api_vulkan_sample.h | 2 +- .../descriptor_heap/descriptor_heap.cpp | 233 ++++++++++-------- .../descriptor_heap/descriptor_heap.h | 4 +- 3 files changed, 128 insertions(+), 111 deletions(-) diff --git a/framework/api_vulkan_sample.h b/framework/api_vulkan_sample.h index 45954d1d06..0621a36dfd 100644 --- a/framework/api_vulkan_sample.h +++ b/framework/api_vulkan_sample.h @@ -299,7 +299,7 @@ class ApiVulkanSample : public vkb::VulkanSampleC /** * @brief Creates a new (graphics) command pool object storing command buffers */ - void create_command_pool(); + virtual void create_command_pool(); /** * @brief Setup default depth and stencil views diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp index e092ba2a69..3ea8b781e8 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.cpp +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -392,9 +392,11 @@ void DescriptorHeap::create_pipeline() VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), VK_NULL_HANDLE, 1, &pipeline_create_info, VK_NULL_HANDLE, &pipeline)); } -void DescriptorHeap::draw() +void DescriptorHeap::draw(float delta_time) { ApiVulkanSample::prepare_frame(); + update_uniform_buffers(delta_time); + build_command_buffer(); submit_info.commandBufferCount = 1; submit_info.pCommandBuffers = &draw_cmd_buffers[current_buffer]; VK_CHECK(vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE)); @@ -403,130 +405,135 @@ void DescriptorHeap::draw() void DescriptorHeap::build_command_buffers() { + // This sample doesn't use prebuilt command buffers +} + +void DescriptorHeap::build_command_buffer() +{ + VkCommandBuffer draw_cmd_buffer = draw_cmd_buffers[current_buffer]; + vkResetCommandBuffer(draw_cmd_buffer, 0); + std::array clear_values{}; clear_values[0].color = {{0.0f, 0.0f, 0.0f, 0.0f}}; clear_values[1].depthStencil = {0.0f, 0}; - for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i) + auto command_begin = vkb::initializers::command_buffer_begin_info(); + VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffer, &command_begin)); + + VkImageSubresourceRange range{}; + range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + range.baseMipLevel = 0; + range.levelCount = VK_REMAINING_MIP_LEVELS; + range.baseArrayLayer = 0; + range.layerCount = VK_REMAINING_ARRAY_LAYERS; + + VkImageSubresourceRange depth_range{range}; + depth_range.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + + vkb::image_layout_transition(draw_cmd_buffer, + swapchain_buffers[current_buffer].image, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + 0, + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + range); + + vkb::image_layout_transition(draw_cmd_buffer, + depth_stencil.image, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, + depth_range); + + VkRenderingAttachmentInfoKHR color_attachment_info = vkb::initializers::rendering_attachment_info(); + color_attachment_info.imageView = swapchain_buffers[current_buffer].view; + color_attachment_info.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + color_attachment_info.resolveMode = VK_RESOLVE_MODE_NONE; + color_attachment_info.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + color_attachment_info.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + color_attachment_info.clearValue = clear_values[0]; + + VkRenderingAttachmentInfoKHR depth_attachment_info = vkb::initializers::rendering_attachment_info(); + depth_attachment_info.imageView = depth_stencil.view; + depth_attachment_info.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL; + depth_attachment_info.resolveMode = VK_RESOLVE_MODE_NONE; + depth_attachment_info.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depth_attachment_info.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depth_attachment_info.clearValue = clear_values[1]; + + auto render_area = VkRect2D{VkOffset2D{}, VkExtent2D{width, height}}; + auto render_info = vkb::initializers::rendering_info(render_area, 1, &color_attachment_info); + render_info.layerCount = 1; + render_info.pDepthAttachment = &depth_attachment_info; + if (!vkb::is_depth_only_format(depth_format)) { - auto command_begin = vkb::initializers::command_buffer_begin_info(); - VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffers[i], &command_begin)); - - VkImageSubresourceRange range{}; - range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - range.baseMipLevel = 0; - range.levelCount = VK_REMAINING_MIP_LEVELS; - range.baseArrayLayer = 0; - range.layerCount = VK_REMAINING_ARRAY_LAYERS; - - VkImageSubresourceRange depth_range{range}; - depth_range.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - - vkb::image_layout_transition(draw_cmd_buffers[i], - swapchain_buffers[i].image, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - 0, - VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - range); - - vkb::image_layout_transition(draw_cmd_buffers[i], - depth_stencil.image, - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, - depth_range); - - VkRenderingAttachmentInfoKHR color_attachment_info = vkb::initializers::rendering_attachment_info(); - color_attachment_info.imageView = swapchain_buffers[i].view; // color_attachment.image_view; - color_attachment_info.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - color_attachment_info.resolveMode = VK_RESOLVE_MODE_NONE; - color_attachment_info.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - color_attachment_info.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - color_attachment_info.clearValue = clear_values[0]; - - VkRenderingAttachmentInfoKHR depth_attachment_info = vkb::initializers::rendering_attachment_info(); - depth_attachment_info.imageView = depth_stencil.view; - depth_attachment_info.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL; - depth_attachment_info.resolveMode = VK_RESOLVE_MODE_NONE; - depth_attachment_info.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - depth_attachment_info.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - depth_attachment_info.clearValue = clear_values[1]; - - auto render_area = VkRect2D{VkOffset2D{}, VkExtent2D{width, height}}; - auto render_info = vkb::initializers::rendering_info(render_area, 1, &color_attachment_info); - render_info.layerCount = 1; - render_info.pDepthAttachment = &depth_attachment_info; - if (!vkb::is_depth_only_format(depth_format)) - { - render_info.pStencilAttachment = &depth_attachment_info; - } + render_info.pStencilAttachment = &depth_attachment_info; + } - vkCmdBeginRenderingKHR(draw_cmd_buffers[i], &render_info); + vkCmdBeginRenderingKHR(draw_cmd_buffer, &render_info); - VkViewport viewport = vkb::initializers::viewport(static_cast(width), static_cast(height), 0.0f, 1.0f); - vkCmdSetViewport(draw_cmd_buffers[i], 0, 1, &viewport); + VkViewport viewport = vkb::initializers::viewport(static_cast(width), static_cast(height), 0.0f, 1.0f); + vkCmdSetViewport(draw_cmd_buffer, 0, 1, &viewport); - VkRect2D scissor = vkb::initializers::rect2D(static_cast(width), static_cast(height), 0, 0); - vkCmdSetScissor(draw_cmd_buffers[i], 0, 1, &scissor); + VkRect2D scissor = vkb::initializers::rect2D(static_cast(width), static_cast(height), 0, 0); + vkCmdSetScissor(draw_cmd_buffer, 0, 1, &scissor); - vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + vkCmdBindPipeline(draw_cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - // Pass options as push data - struct PushData - { - int32_t samplerIndex; - int32_t frameIndex; - } pushData = { - .samplerIndex = selected_sampler, - // .frameIndex = static_cast(currentBuffer), - }; - VkPushDataInfoEXT pushDataInfo{ - .sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT, - .data = {.address = &pushData, .size = sizeof(PushData)}}; - vkCmdPushDataEXT(draw_cmd_buffers[i], &pushDataInfo); - - // Bind the heap containing resources (buffers and images) - VkBindHeapInfoEXT bind_heap_info_res{ - .sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT, - .heapRange{ - .address = descriptor_heap_resources->get_device_address(), - .size = descriptor_heap_resources->get_size()}, - .reservedRangeSize = descriptor_heap_properties.minResourceHeapReservedRange, - }; - vkCmdBindResourceHeapEXT(draw_cmd_buffers[i], &bind_heap_info_res); + // Pass options as push data + struct PushData + { + int32_t samplerIndex; + int32_t frameIndex; + } pushData = { + .samplerIndex = selected_sampler, + // .frameIndex = static_cast(currentBuffer), + }; + VkPushDataInfoEXT pushDataInfo{ + .sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT, + .data = {.address = &pushData, .size = sizeof(PushData)}}; + vkCmdPushDataEXT(draw_cmd_buffer, &pushDataInfo); + + // Bind the heap containing resources (buffers and images) + VkBindHeapInfoEXT bind_heap_info_res{ + .sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT, + .heapRange{ + .address = descriptor_heap_resources->get_device_address(), + .size = descriptor_heap_resources->get_size()}, + .reservedRangeSize = descriptor_heap_properties.minResourceHeapReservedRange, + }; + vkCmdBindResourceHeapEXT(draw_cmd_buffer, &bind_heap_info_res); - // Bind the heap containing samplers - VkBindHeapInfoEXT bind_heap_info_samplers{ - .sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT, - .heapRange{ - .address = descriptor_heap_samplers->get_device_address(), - .size = descriptor_heap_samplers->get_size()}, - .reservedRangeSize = descriptor_heap_properties.minSamplerHeapReservedRange}; - vkCmdBindSamplerHeapEXT(draw_cmd_buffers[i], &bind_heap_info_samplers); + // Bind the heap containing samplers + VkBindHeapInfoEXT bind_heap_info_samplers{ + .sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT, + .heapRange{ + .address = descriptor_heap_samplers->get_device_address(), + .size = descriptor_heap_samplers->get_size()}, + .reservedRangeSize = descriptor_heap_properties.minSamplerHeapReservedRange}; + vkCmdBindSamplerHeapEXT(draw_cmd_buffer, &bind_heap_info_samplers); - VkDeviceSize offsets[1] = {0}; + VkDeviceSize offsets[1] = {0}; - const auto &vertex_buffer = cube->vertex_buffers.at("vertex_buffer"); - auto &index_buffer = cube->index_buffer; + const auto &vertex_buffer = cube->vertex_buffers.at("vertex_buffer"); + auto &index_buffer = cube->index_buffer; - vkCmdBindVertexBuffers(draw_cmd_buffers[i], 0, 1, vertex_buffer.get(), offsets); - vkCmdBindIndexBuffer(draw_cmd_buffers[i], index_buffer->get_handle(), 0, cube->index_type); - vkCmdDrawIndexed(draw_cmd_buffers[i], cube->vertex_indices, 2, 0, 0, 0); + vkCmdBindVertexBuffers(draw_cmd_buffer, 0, 1, vertex_buffer.get(), offsets); + vkCmdBindIndexBuffer(draw_cmd_buffer, index_buffer->get_handle(), 0, cube->index_type); + vkCmdDrawIndexed(draw_cmd_buffer, cube->vertex_indices, 2, 0, 0, 0); - vkCmdEndRenderingKHR(draw_cmd_buffers[i]); + vkCmdEndRenderingKHR(draw_cmd_buffer); - draw_ui(draw_cmd_buffers[i], i); + // draw_ui(draw_cmd_buffer, current_buffer); - vkb::image_layout_transition(draw_cmd_buffers[i], - swapchain_buffers[i].image, - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, - range); + vkb::image_layout_transition(draw_cmd_buffer, + swapchain_buffers[current_buffer].image, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + range); - VK_CHECK(vkEndCommandBuffer(draw_cmd_buffers[i])); - } + VK_CHECK(vkEndCommandBuffer(draw_cmd_buffer)); } void DescriptorHeap::render(float delta_time) @@ -535,8 +542,16 @@ void DescriptorHeap::render(float delta_time) { return; } - update_uniform_buffers(delta_time); - draw(); + draw(delta_time); +} + +void DescriptorHeap::create_command_pool() +{ + VkCommandPoolCreateInfo command_pool_info{ + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .queueFamilyIndex = get_device().get_queue_by_flags(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT, 0).get_family_index()}; + VK_CHECK(vkCreateCommandPool(get_device().get_handle(), &command_pool_info, nullptr, &cmd_pool)); } std::unique_ptr create_descriptor_heap() diff --git a/samples/extensions/descriptor_heap/descriptor_heap.h b/samples/extensions/descriptor_heap/descriptor_heap.h index 347f9fc50f..aafd8467dc 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.h +++ b/samples/extensions/descriptor_heap/descriptor_heap.h @@ -29,7 +29,9 @@ class DescriptorHeap : public ApiVulkanSample bool prepare(const vkb::ApplicationOptions &options) override; void render(float delta_time) override; + void create_command_pool() override; void build_command_buffers() override; + void build_command_buffer(); void request_gpu_features(vkb::core::PhysicalDeviceC &gpu) override; void on_update_ui_overlay(vkb::Drawer &drawer) override; @@ -80,7 +82,7 @@ class DescriptorHeap : public ApiVulkanSample void update_uniform_buffers(float delta_time); void create_descriptor_heaps(); void create_pipeline(); - void draw(); + void draw(float delta_time); }; std::unique_ptr create_descriptor_heap(); From 442cb0877b05e6f80e0420bf545bc0cde5c6f493 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Fri, 27 Feb 2026 16:05:04 +0100 Subject: [PATCH 07/34] Cleanup --- samples/extensions/descriptor_heap/descriptor_heap.cpp | 7 +++++-- samples/extensions/descriptor_heap/descriptor_heap.h | 5 ----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp index 3ea8b781e8..9355ddc1ea 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.cpp +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -33,8 +33,11 @@ DescriptorHeap::~DescriptorHeap() { if (has_device()) { + vkDestroyPipeline(get_device().get_handle(), pipeline, nullptr); textures = {}; cube.reset(); + descriptor_heap_resources.reset(); + descriptor_heap_samplers.reset(); } } @@ -96,7 +99,7 @@ void DescriptorHeap::load_assets() textures[1] = load_texture("textures/crate02_color_height_rgba.ktx", vkb::sg::Image::Color); } -inline VkDeviceSize aligned_size(VkDeviceSize value, VkDeviceSize alignment) +inline static VkDeviceSize aligned_size(VkDeviceSize value, VkDeviceSize alignment) { return (value + alignment - 1) & ~(alignment - 1); } @@ -525,7 +528,7 @@ void DescriptorHeap::build_command_buffer() vkCmdEndRenderingKHR(draw_cmd_buffer); - // draw_ui(draw_cmd_buffer, current_buffer); + draw_ui(draw_cmd_buffer, current_buffer); vkb::image_layout_transition(draw_cmd_buffer, swapchain_buffers[current_buffer].image, diff --git a/samples/extensions/descriptor_heap/descriptor_heap.h b/samples/extensions/descriptor_heap/descriptor_heap.h index aafd8467dc..8304d10183 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.h +++ b/samples/extensions/descriptor_heap/descriptor_heap.h @@ -58,11 +58,6 @@ class DescriptorHeap : public ApiVulkanSample std::unique_ptr cube; - struct Models - { - std::unique_ptr cube; - } models; - // Size and offset values for heap objects VkDeviceSize buffer_heap_offset{0}; VkDeviceSize buffer_descriptor_size{0}; From 930db39f233301c7a8deb5f7d3b320e370e6b574 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Fri, 27 Feb 2026 16:08:49 +0100 Subject: [PATCH 08/34] Cleanup --- samples/extensions/descriptor_heap/descriptor_heap.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp index 9355ddc1ea..cdaa64e31b 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.cpp +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -34,7 +34,11 @@ DescriptorHeap::~DescriptorHeap() if (has_device()) { vkDestroyPipeline(get_device().get_handle(), pipeline, nullptr); - textures = {}; + for (auto &texture : textures) + { + texture.image.reset(); + vkDestroySampler(get_device().get_handle(), texture.sampler, nullptr); + } cube.reset(); descriptor_heap_resources.reset(); descriptor_heap_samplers.reset(); From 8ec3157340684d67720bb4713efbd0fee5188daa Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sat, 28 Feb 2026 09:31:23 +0100 Subject: [PATCH 09/34] UBOs per Frame --- .../descriptor_heap/descriptor_heap.cpp | 21 +++++++++++-------- .../descriptor_heap/descriptor_heap.h | 9 ++++---- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp index cdaa64e31b..25f8a297ce 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.cpp +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -92,8 +92,7 @@ void DescriptorHeap::on_update_ui_overlay(vkb::Drawer &drawer) uint32_t DescriptorHeap::get_api_version() const { - // @todo: 1.3, add note that it's not required per se - return VK_API_VERSION_1_3; + return VK_API_VERSION_1_2; } void DescriptorHeap::load_assets() @@ -110,7 +109,11 @@ inline static VkDeviceSize aligned_size(VkDeviceSize value, VkDeviceSize alignme void DescriptorHeap::prepare_uniform_buffers() { - uniform_buffer = std::make_unique(get_device(), sizeof(UniformData), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU); + uniform_buffers.resize(draw_cmd_buffers.size()); + for (auto &uniform_buffer : uniform_buffers) + { + uniform_buffer = std::make_unique(get_device(), sizeof(UniformData), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU); + } } void DescriptorHeap::update_uniform_buffers(float delta_time) @@ -142,7 +145,7 @@ void DescriptorHeap::update_uniform_buffers(float delta_time) uniform_data.model_matrix[i] = cubeMat; } - uniform_buffer->convert_and_update(uniform_data); + uniform_buffers[current_buffer]->convert_and_update(uniform_data); } void DescriptorHeap::create_descriptor_heaps() @@ -219,22 +222,22 @@ void DescriptorHeap::create_descriptor_heaps() image_descriptor_size = aligned_size(descriptor_heap_properties.imageDescriptorSize, descriptor_heap_properties.imageDescriptorAlignment); // @todo: fif - auto vector_size{3}; // @todo: proper calculation/explanation + auto vector_size{rotations.size() + uniform_buffers.size()}; std::vector host_address_ranges_resources(vector_size); std::vector resource_descriptor_infos(vector_size); size_t heapResIndex{0}; // Buffer - std::array deviceAddressRangesUniformBuffer{}; - for (auto i = 0; i < 1; i++) + std::vector device_address_ranges_uniform_buffer(uniform_buffers.size()); + for (auto i = 0; i < uniform_buffers.size(); i++) { - deviceAddressRangesUniformBuffer[i] = {.address = uniform_buffer->get_device_address(), .size = uniform_buffer->get_size()}; + device_address_ranges_uniform_buffer[i] = {.address = uniform_buffers[i]->get_device_address(), .size = uniform_buffers[i]->get_size()}; resource_descriptor_infos[heapResIndex] = { .sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT, .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .data = { - .pAddressRange = &deviceAddressRangesUniformBuffer[i]}}; + .pAddressRange = &device_address_ranges_uniform_buffer[i]}}; host_address_ranges_resources[heapResIndex] = { .address = (uint8_t *) (descriptor_heap_resources->get_data()) + buffer_descriptor_size * i, .size = buffer_descriptor_size}; diff --git a/samples/extensions/descriptor_heap/descriptor_heap.h b/samples/extensions/descriptor_heap/descriptor_heap.h index 8304d10183..3e58c02e72 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.h +++ b/samples/extensions/descriptor_heap/descriptor_heap.h @@ -48,11 +48,10 @@ class DescriptorHeap : public ApiVulkanSample glm::mat4 model_matrix[2]; } uniform_data; - // @todo: fif - VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptor_heap_properties{}; - std::unique_ptr descriptor_heap_resources; - std::unique_ptr descriptor_heap_samplers; - std::unique_ptr uniform_buffer; + VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptor_heap_properties{}; + std::unique_ptr descriptor_heap_resources; + std::unique_ptr descriptor_heap_samplers; + std::vector> uniform_buffers; int32_t selected_sampler{0}; From 47886be6ea45e48eca7b9462b39869ac3877389e Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sat, 28 Feb 2026 09:31:39 +0100 Subject: [PATCH 10/34] Add precompiled SPIR-V --- shaders/descriptor_heap/glsl/cube.vert.spv | Bin 2200 -> 2272 bytes shaders/descriptor_heap/hlsl/cube.frag.spv | Bin 0 -> 1312 bytes shaders/descriptor_heap/hlsl/cube.vert.spv | Bin 0 -> 1848 bytes shaders/descriptor_heap/slang/cube.frag.spv | Bin 0 -> 1268 bytes shaders/descriptor_heap/slang/cube.vert.spv | Bin 0 -> 2036 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 shaders/descriptor_heap/hlsl/cube.frag.spv create mode 100644 shaders/descriptor_heap/hlsl/cube.vert.spv create mode 100644 shaders/descriptor_heap/slang/cube.frag.spv create mode 100644 shaders/descriptor_heap/slang/cube.vert.spv diff --git a/shaders/descriptor_heap/glsl/cube.vert.spv b/shaders/descriptor_heap/glsl/cube.vert.spv index 0d1fe0f39ec32fdc52e25e632b20e22f6f7550ba..450a8e8930059b419294ef64700a868c60f959c3 100644 GIT binary patch delta 95 zcmbOs_&|`GnMs+Qfq{{Mn}K&Ccdk1N0~dq4PrSRozq^lXd~!iSd~r!-PHKEkW?pK1 gN@h`Na!F=cDgy%x0|%12%)I2B(i9{G8_N|r0EZ|WzW@LL delta 23 ecmaDLI75(|nMs+Qfq{{Mn}K5@ckadm3LF44n*@sh diff --git a/shaders/descriptor_heap/hlsl/cube.frag.spv b/shaders/descriptor_heap/hlsl/cube.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..42b35c3ab3f5898d7d4b9585c63fb817a09be230 GIT binary patch literal 1312 zcmZ9KT~8BH5QdK}TM<71QIHRzrGAq%>V+{enh+$FD-1N~o!Nv{o7kmkw+8S0+1{A= zJnJ58oMbxZeP_;`ccxpfwJ(QoDTHt%?C`5ChFZncOKf&N%!V1F=Rw-fP1JBJjbNBPk_jJ_h?SI@mJUn{)6spX*kN zu2MU-gNwy=%kBDah0x`mS~hV_?E7N-iG6cA@6NZU^5Pb*S?`X`+c)Bve}~ez!!~&> za=s_$dup`K9=Bnx5u3O*I~BgpSu^h}O#xpkSIgi-?GU^g&fecMm0UbU-PtX}m+tX^l8d%-JeG^eTg7q^?l*Hk%k3%8%xnYVH8?sC_;+IajP ztx@;AinqpD@8fFpdx?D>RJ=9L{}6XBd28IoBV7K38h7#-x3~NUUUlbBaCvoVZ&2ra K^1qqvCH@D2K47K* literal 0 HcmV?d00001 diff --git a/shaders/descriptor_heap/hlsl/cube.vert.spv b/shaders/descriptor_heap/hlsl/cube.vert.spv new file mode 100644 index 0000000000000000000000000000000000000000..50c0e79cc29dffdecf69c281f189a5b93b1ee986 GIT binary patch literal 1848 zcmZ9MTThcg5Qeu$6ct3|pdz-^Gg`$1qDG}uBfYUzG4Zy@*H}Xj=>awIf}h~e^~S{a z*>9(*-DEoZ&O0+ZGrM0?$-w)QrBvn~n2ZorCC5I-@gBN)Put{>*9Z{h764|Erx_9D%-Q<(X1-@#@!G z-NUF?X&*#CVlL}B&TaHmWnePO&Lp~)R+%!&LoLmA@u=P_)~X*XdzI?D`{aRRMuFRV z^SNBDet13Nm|15y@PXqbam;d(+weKlejauYZ*>FQ0d=iQz}!cCW8jp}H4`{$U6ru# zr9Lpdf~jeor;c%+Ij>1r_=EEv#rU-L?)7>(Tl}V6LH%U zw#hjB#^dObxMPXs!ZsO)jW}k+`C$C*U5wX^&FZljY{V=m%NgmM1fTVQnfW-L6=oSv z@5C?a{5k2I1djbViF3!V$UU!J4yXkWJ})75Nq9k;80uQq@1is|@QQx96Kuq+hC2Zx zaQG}qGXs2b!pqWeeqz#oCiEEhnRXYn=kA#SHC&XYhLnW(OVSAmcK8)(cyhvDmWIa; zwl@~{ns!%YCl`Id=K?nWe8AK*udHj*c|~w%?1{N5O^sIbHSIZ{zMqSyM|f;tdg8mm z2BrtNM8MQcO@^tN85^c%zCC=1&q?zw7|v_Y8w794lRAlI6(oOj+I8s{ie+v$B%BYX z@0${0!OZEFg!4{AAe@%$9twG%efKw_o|P(e;}bJmf0R^kBvE)?NPANdrjx?1|Ca@#~+^Wc!U+wlMvelekeiuk!MHF)GK!1Rr)R&4StM>rJuY;PGJo$sD&z0JzjgDQEUDWjmtWJBFORTP9 z=RQJH@k-oxqDDjZE@QoyeXNP~yw?BBB<@`Y$z5{B%6Z4u$nc9b#n!Zr&%N(vz71IO zhw-*k-%a*ixD2~Xd=D=+KLZc&=E&axUypp^e14;TF298ut9bLxu|~7RuEV;<8hfAb zg4M@7Icu)tUGn0Mh*HcRSZ(Z=X6Z0@dZ*=^`djQ>=5OP@C*!y2svh>ek9XhneZbx& zZ{9GX6tja)kHF5UPEYWj(|a)X8Q!l@{vBA|?7389b@E+kuxIrCUB-I`-)nQk&XQp+ zvGu)6d%U5ubsBx;{(_w4J^$X7-?X*Fp4)HR{vXLz&)b;&<>l092Xh9sYw~t$-k2f# weJsP|jZqKZN9&2z^C4#32{~%!w`Yv}H>Oh~zdL#7bba7to=^TCr+bV22Q8Re#Q*>R literal 0 HcmV?d00001 diff --git a/shaders/descriptor_heap/slang/cube.vert.spv b/shaders/descriptor_heap/slang/cube.vert.spv new file mode 100644 index 0000000000000000000000000000000000000000..b5541686597dfdf39005bebde43d3fced1a94255 GIT binary patch literal 2036 zcmZvc+fP$L5XKi;S_}#za(N)NfHwrWNJW%e!4M#l&_wZ-9O)rBhMq&(gF@m1e}aFm zFD8D!(;aAt+sxVdzMYw!otzT&dKFERGCGqq++wWk{nU%0%47}J^y zlBxthtKqF*{)WS+)21l=P_nCf0H^IE!J}Vh|V3@~cYUQ+~#E`eAd|kGCvDNdl$DDap>(rnBz%Gxe zKkv;j_2<1AE=!+~m_O&C=QtDb*q< zeqhePyCXj^ISfh2@0T#WG3vQ3#h#QtB_UsW#C)E59vkXEC*ch6aPyiMB=GbSe%!~R z1m5EC2TXj6vm~83@D`^c9p3gQ&RYq*#aWi#8Rwlaao~wV54@Ma(?@Xh!KwtF9Bscf z&r=7>dtEyGp(;pCHzd>*Vb~qi6qml)lwebp{Px_Iba-MJw=Er>^BDJ0Iy|-7(2X8w UhC` Date: Sat, 28 Feb 2026 15:00:12 +0100 Subject: [PATCH 11/34] Override render pass / framebuffer creation Both not required, as the sample uses dynamic rendering --- samples/extensions/descriptor_heap/descriptor_heap.cpp | 10 ++++++++++ samples/extensions/descriptor_heap/descriptor_heap.h | 2 ++ 2 files changed, 12 insertions(+) diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp index 25f8a297ce..411482b935 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.cpp +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -82,6 +82,16 @@ void DescriptorHeap::request_gpu_features(vkb::core::PhysicalDeviceC &gpu) } } +void DescriptorHeap::setup_render_pass() +{ + // We use dynamic rendering, so we skip render pass setup +} + +void DescriptorHeap::setup_framebuffer() +{ + // We use dynamic rendering, so we skip framebuffer setup +} + void DescriptorHeap::on_update_ui_overlay(vkb::Drawer &drawer) { if (drawer.combo_box("Sampler type", &selected_sampler, sampler_names)) diff --git a/samples/extensions/descriptor_heap/descriptor_heap.h b/samples/extensions/descriptor_heap/descriptor_heap.h index 3e58c02e72..1883d462a7 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.h +++ b/samples/extensions/descriptor_heap/descriptor_heap.h @@ -33,6 +33,8 @@ class DescriptorHeap : public ApiVulkanSample void build_command_buffers() override; void build_command_buffer(); void request_gpu_features(vkb::core::PhysicalDeviceC &gpu) override; + void setup_render_pass() override; + void setup_framebuffer() override; void on_update_ui_overlay(vkb::Drawer &drawer) override; private: From 02e62e7788cc410d3c9987765ffefb1d4c8c1522 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sat, 28 Feb 2026 15:42:25 +0100 Subject: [PATCH 12/34] Slight restructuring --- .../descriptor_heap/descriptor_heap.cpp | 40 +++++++++---------- .../descriptor_heap/descriptor_heap.h | 8 +++- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp index 411482b935..947bda251d 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.cpp +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -107,9 +107,9 @@ uint32_t DescriptorHeap::get_api_version() const void DescriptorHeap::load_assets() { - cube = load_model("scenes/textured_unit_cube.gltf"); - textures[0] = load_texture("textures/crate01_color_height_rgba.ktx", vkb::sg::Image::Color); - textures[1] = load_texture("textures/crate02_color_height_rgba.ktx", vkb::sg::Image::Color); + cube = load_model("scenes/textured_unit_cube.gltf"); + cubes[0].texture = load_texture("textures/crate01_color_height_rgba.ktx", vkb::sg::Image::Color); + cubes[1].texture = load_texture("textures/crate02_color_height_rgba.ktx", vkb::sg::Image::Color); } inline static VkDeviceSize aligned_size(VkDeviceSize value, VkDeviceSize alignment) @@ -130,15 +130,15 @@ void DescriptorHeap::update_uniform_buffers(float delta_time) { if (animate) { - rotations[0].x += 2.5f * delta_time; - if (rotations[0].x > 360.0f) + cubes[0].rotation.x += 2.5f * delta_time; + if (cubes[0].rotation.x > 360.0f) { - rotations[0].x -= 360.0f; + cubes[0].rotation.x -= 360.0f; } - rotations[1].y += 2.0f * delta_time; - if (rotations[1].y > 360.0f) + cubes[1].rotation.y += 2.0f * delta_time; + if (cubes[1].rotation.y > 360.0f) { - rotations[1].y -= 360.0f; + cubes[1].rotation.y -= 360.0f; } } @@ -146,12 +146,12 @@ void DescriptorHeap::update_uniform_buffers(float delta_time) uniform_data.view_matrix = camera.matrices.view; std::array positions = {glm::vec3(-2.0f, 0.0f, 0.0f), glm::vec3(1.5f, 0.5f, 0.0f)}; - for (auto i = 0; i < rotations.size(); i++) + for (auto i = 0; i < cubes.size(); i++) { glm::mat4 cubeMat = glm::translate(glm::mat4(1.0f), positions[i]); - cubeMat = glm::rotate(cubeMat, glm::radians(rotations[i].x), glm::vec3(1.0f, 0.0f, 0.0f)); - cubeMat = glm::rotate(cubeMat, glm::radians(rotations[i].y), glm::vec3(0.0f, 1.0f, 0.0f)); - cubeMat = glm::rotate(cubeMat, glm::radians(rotations[i].z), glm::vec3(0.0f, 0.0f, 1.0f)); + cubeMat = glm::rotate(cubeMat, glm::radians(cubes[i].rotation.x), glm::vec3(1.0f, 0.0f, 0.0f)); + cubeMat = glm::rotate(cubeMat, glm::radians(cubes[i].rotation.y), glm::vec3(0.0f, 1.0f, 0.0f)); + cubeMat = glm::rotate(cubeMat, glm::radians(cubes[i].rotation.z), glm::vec3(0.0f, 0.0f, 1.0f)); uniform_data.model_matrix[i] = cubeMat; } @@ -202,7 +202,7 @@ void DescriptorHeap::create_descriptor_heaps() .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT, .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT, .maxAnisotropy = 16.0f, - .maxLod = (float) textures[0].image.get()->get_mipmaps().size(), + .maxLod = (float) cubes[0].texture.image.get()->get_mipmaps().size(), }, VkSamplerCreateInfo{ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, @@ -213,7 +213,7 @@ void DescriptorHeap::create_descriptor_heaps() .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT, .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT, .maxAnisotropy = 16.0f, - .maxLod = (float) textures[0].image.get()->get_mipmaps().size(), + .maxLod = (float) cubes[1].texture.image.get()->get_mipmaps().size(), }}; for (auto i = 0; i < static_cast(sampler_create_infos.size()); i++) @@ -232,7 +232,7 @@ void DescriptorHeap::create_descriptor_heaps() image_descriptor_size = aligned_size(descriptor_heap_properties.imageDescriptorSize, descriptor_heap_properties.imageDescriptorAlignment); // @todo: fif - auto vector_size{rotations.size() + uniform_buffers.size()}; + auto vector_size{cubes.size() + uniform_buffers.size()}; std::vector host_address_ranges_resources(vector_size); std::vector resource_descriptor_infos(vector_size); @@ -260,14 +260,14 @@ void DescriptorHeap::create_descriptor_heaps() std::array imageDescriptorInfo{}; // @offset - for (auto i = 0; i < rotations.size(); i++) + for (auto i = 0; i < cubes.size(); i++) { imageViewCreateInfos[i] = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - .image = textures[i].image->get_vk_image().get_handle(), + .image = cubes[i].texture.image->get_vk_image().get_handle(), .viewType = VK_IMAGE_VIEW_TYPE_2D, - .format = textures[i].image->get_format(), - .subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = static_cast(textures[i].image->get_mipmaps().size()), .baseArrayLayer = 0, .layerCount = 1}, + .format = cubes[i].texture.image->get_format(), + .subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = static_cast(cubes[i].texture.image->get_mipmaps().size()), .baseArrayLayer = 0, .layerCount = 1}, }; imageDescriptorInfo[i] = { diff --git a/samples/extensions/descriptor_heap/descriptor_heap.h b/samples/extensions/descriptor_heap/descriptor_heap.h index 1883d462a7..dc865c5d37 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.h +++ b/samples/extensions/descriptor_heap/descriptor_heap.h @@ -40,8 +40,12 @@ class DescriptorHeap : public ApiVulkanSample private: bool animate = true; - std::array textures; - std::array rotations; + struct Cube + { + Texture texture; + glm::vec3 rotation; + }; + std::array cubes; struct UniformData { From e5b7ef0a47b106463835dcfcb75556e5088de154 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sat, 28 Feb 2026 15:42:53 +0100 Subject: [PATCH 13/34] Slight restructuring --- samples/extensions/descriptor_heap/descriptor_heap.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp index 947bda251d..1ababb906b 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.cpp +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -34,10 +34,10 @@ DescriptorHeap::~DescriptorHeap() if (has_device()) { vkDestroyPipeline(get_device().get_handle(), pipeline, nullptr); - for (auto &texture : textures) + for (auto &cube : cubes) { - texture.image.reset(); - vkDestroySampler(get_device().get_handle(), texture.sampler, nullptr); + cube.texture.image.reset(); + vkDestroySampler(get_device().get_handle(), cube.texture.sampler, nullptr); } cube.reset(); descriptor_heap_resources.reset(); From eb12d9a07fd646730f23729ecde3977fe9f5c5f1 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sat, 28 Feb 2026 15:46:31 +0100 Subject: [PATCH 14/34] Clang format --- samples/extensions/descriptor_heap/descriptor_heap.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/extensions/descriptor_heap/descriptor_heap.h b/samples/extensions/descriptor_heap/descriptor_heap.h index dc865c5d37..3eeb4a3259 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.h +++ b/samples/extensions/descriptor_heap/descriptor_heap.h @@ -42,7 +42,7 @@ class DescriptorHeap : public ApiVulkanSample struct Cube { - Texture texture; + Texture texture; glm::vec3 rotation; }; std::array cubes; From f733978d8d2eecdc3e3aa2365b7a07b9871bcaab Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sat, 28 Feb 2026 16:20:38 +0100 Subject: [PATCH 15/34] Cleanup --- .../descriptor_heap/descriptor_heap.cpp | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp index 1ababb906b..52d8c85a9b 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.cpp +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -161,9 +161,10 @@ void DescriptorHeap::update_uniform_buffers(float delta_time) void DescriptorHeap::create_descriptor_heaps() { // Descriptor heaps have varying offset, size and alignment requirements, so we store it's properties for later user - VkPhysicalDeviceProperties2 deviceProps2{.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2}; descriptor_heap_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_PROPERTIES_EXT; - deviceProps2.pNext = &descriptor_heap_properties; + VkPhysicalDeviceProperties2 deviceProps2{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, + .pNext = &descriptor_heap_properties}; vkGetPhysicalDeviceProperties2(get_device().get_gpu().get_handle(), &deviceProps2); // There are two descriptor heap types: One that can store resources (buffers, images) and one that can store samplers @@ -231,7 +232,6 @@ void DescriptorHeap::create_descriptor_heaps() image_heap_offset = aligned_size(buffer_descriptor_size, descriptor_heap_properties.imageDescriptorAlignment); image_descriptor_size = aligned_size(descriptor_heap_properties.imageDescriptorSize, descriptor_heap_properties.imageDescriptorAlignment); - // @todo: fif auto vector_size{cubes.size() + uniform_buffers.size()}; std::vector host_address_ranges_resources(vector_size); std::vector resource_descriptor_infos(vector_size); @@ -309,8 +309,8 @@ void DescriptorHeap::create_pipeline() vkb::initializers::vertex_input_binding_description(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX), }; const std::vector vertex_input_attributes = { - vkb::initializers::vertex_input_attribute_description(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0), // Location 0: Position - vkb::initializers::vertex_input_attribute_description(0, 1, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 6), // Location 1: UV + vkb::initializers::vertex_input_attribute_description(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos)), + vkb::initializers::vertex_input_attribute_description(0, 1, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv)), }; VkPipelineVertexInputStateCreateInfo vertex_input_state = vkb::initializers::pipeline_vertex_input_state_create_info(); vertex_input_state.vertexBindingDescriptionCount = static_cast(vertex_input_bindings.size()); @@ -440,12 +440,12 @@ void DescriptorHeap::build_command_buffer() auto command_begin = vkb::initializers::command_buffer_begin_info(); VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffer, &command_begin)); - VkImageSubresourceRange range{}; - range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - range.baseMipLevel = 0; - range.levelCount = VK_REMAINING_MIP_LEVELS; - range.baseArrayLayer = 0; - range.layerCount = VK_REMAINING_ARRAY_LAYERS; + VkImageSubresourceRange range{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS}; VkImageSubresourceRange depth_range{range}; depth_range.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; @@ -466,21 +466,23 @@ void DescriptorHeap::build_command_buffer() VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, depth_range); - VkRenderingAttachmentInfoKHR color_attachment_info = vkb::initializers::rendering_attachment_info(); - color_attachment_info.imageView = swapchain_buffers[current_buffer].view; - color_attachment_info.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - color_attachment_info.resolveMode = VK_RESOLVE_MODE_NONE; - color_attachment_info.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - color_attachment_info.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - color_attachment_info.clearValue = clear_values[0]; - - VkRenderingAttachmentInfoKHR depth_attachment_info = vkb::initializers::rendering_attachment_info(); - depth_attachment_info.imageView = depth_stencil.view; - depth_attachment_info.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL; - depth_attachment_info.resolveMode = VK_RESOLVE_MODE_NONE; - depth_attachment_info.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - depth_attachment_info.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - depth_attachment_info.clearValue = clear_values[1]; + VkRenderingAttachmentInfoKHR color_attachment_info{ + .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR, + .imageView = swapchain_buffers[current_buffer].view, + .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .resolveMode = VK_RESOLVE_MODE_NONE, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .clearValue = clear_values[0]}; + + VkRenderingAttachmentInfoKHR depth_attachment_info{ + .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR, + .imageView = depth_stencil.view, + .imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, + .resolveMode = VK_RESOLVE_MODE_NONE, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .clearValue = clear_values[1]}; auto render_area = VkRect2D{VkOffset2D{}, VkExtent2D{width, height}}; auto render_info = vkb::initializers::rendering_info(render_area, 1, &color_attachment_info); From 50b4a96c5b49a7b6a7fc4db534abbfdff108d48e Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sat, 28 Feb 2026 16:32:15 +0100 Subject: [PATCH 16/34] Remove extension not required Core since 1.1 --- samples/extensions/descriptor_heap/descriptor_heap.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp index 52d8c85a9b..5a8604022e 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.cpp +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -24,7 +24,6 @@ DescriptorHeap::DescriptorHeap() add_device_extension(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME); add_device_extension(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); - add_device_extension(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); add_device_extension(VK_KHR_MAINTENANCE_5_EXTENSION_NAME); add_device_extension(VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME); } From bc15c700480c3868f9789c9f87f22358386ec784 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sat, 18 Apr 2026 07:42:42 +0200 Subject: [PATCH 17/34] Reserved range offsets --- samples/extensions/descriptor_heap/descriptor_heap.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp index 5a8604022e..ae571aee94 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.cpp +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -93,10 +93,7 @@ void DescriptorHeap::setup_framebuffer() void DescriptorHeap::on_update_ui_overlay(vkb::Drawer &drawer) { - if (drawer.combo_box("Sampler type", &selected_sampler, sampler_names)) - { - rebuild_command_buffers(); - } + drawer.combo_box("Sampler type", &selected_sampler, sampler_names); } uint32_t DescriptorHeap::get_api_version() const @@ -522,6 +519,7 @@ void DescriptorHeap::build_command_buffer() .heapRange{ .address = descriptor_heap_resources->get_device_address(), .size = descriptor_heap_resources->get_size()}, + .reservedRangeOffset = descriptor_heap_resources->get_size() - descriptor_heap_properties.minResourceHeapReservedRange, .reservedRangeSize = descriptor_heap_properties.minResourceHeapReservedRange, }; vkCmdBindResourceHeapEXT(draw_cmd_buffer, &bind_heap_info_res); @@ -532,6 +530,7 @@ void DescriptorHeap::build_command_buffer() .heapRange{ .address = descriptor_heap_samplers->get_device_address(), .size = descriptor_heap_samplers->get_size()}, + .reservedRangeOffset = descriptor_heap_samplers->get_size() - descriptor_heap_properties.minSamplerHeapReservedRange, .reservedRangeSize = descriptor_heap_properties.minSamplerHeapReservedRange}; vkCmdBindSamplerHeapEXT(draw_cmd_buffer, &bind_heap_info_samplers); From 0579063474e631ca5b0b95b4c32a71751caff0b4 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sat, 18 Apr 2026 09:09:40 +0200 Subject: [PATCH 18/34] Variable naming --- .../descriptor_heap/descriptor_heap.cpp | 53 ++++++++++--------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp index ae571aee94..fafa9d1630 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.cpp +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -158,17 +158,18 @@ void DescriptorHeap::create_descriptor_heaps() { // Descriptor heaps have varying offset, size and alignment requirements, so we store it's properties for later user descriptor_heap_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_PROPERTIES_EXT; - VkPhysicalDeviceProperties2 deviceProps2{ + VkPhysicalDeviceProperties2 device_props_2{ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, .pNext = &descriptor_heap_properties}; - vkGetPhysicalDeviceProperties2(get_device().get_gpu().get_handle(), &deviceProps2); + vkGetPhysicalDeviceProperties2(get_device().get_gpu().get_handle(), &device_props_2); // There are two descriptor heap types: One that can store resources (buffers, images) and one that can store samplers // We create heaps with a fixed size that's guaranteed to fit in the few descriptors we use const VkDeviceSize heap_buffer_size = aligned_size(2048 + descriptor_heap_properties.minResourceHeapReservedRange, descriptor_heap_properties.resourceHeapAlignment); - descriptor_heap_resources = std::make_unique(get_device(), - heap_buffer_size, + descriptor_heap_resources + = std::make_unique(get_device(), + heap_buffer_size, VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU); @@ -232,33 +233,33 @@ void DescriptorHeap::create_descriptor_heaps() std::vector host_address_ranges_resources(vector_size); std::vector resource_descriptor_infos(vector_size); - size_t heapResIndex{0}; + size_t resource_heap_index{0}; // Buffer std::vector device_address_ranges_uniform_buffer(uniform_buffers.size()); for (auto i = 0; i < uniform_buffers.size(); i++) { device_address_ranges_uniform_buffer[i] = {.address = uniform_buffers[i]->get_device_address(), .size = uniform_buffers[i]->get_size()}; - resource_descriptor_infos[heapResIndex] = { + resource_descriptor_infos[resource_heap_index] = { .sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT, .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .data = { .pAddressRange = &device_address_ranges_uniform_buffer[i]}}; - host_address_ranges_resources[heapResIndex] = { + host_address_ranges_resources[resource_heap_index] = { .address = (uint8_t *) (descriptor_heap_resources->get_data()) + buffer_descriptor_size * i, .size = buffer_descriptor_size}; - heapResIndex++; + resource_heap_index++; } // Images - std::array imageViewCreateInfos{}; - std::array imageDescriptorInfo{}; + std::array image_view_create_infos{}; + std::array image_descriptor_info{}; // @offset for (auto i = 0; i < cubes.size(); i++) { - imageViewCreateInfos[i] = { + image_view_create_infos[i] = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = cubes[i].texture.image->get_vk_image().get_handle(), .viewType = VK_IMAGE_VIEW_TYPE_2D, @@ -266,23 +267,23 @@ void DescriptorHeap::create_descriptor_heaps() .subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = static_cast(cubes[i].texture.image->get_mipmaps().size()), .baseArrayLayer = 0, .layerCount = 1}, }; - imageDescriptorInfo[i] = { + image_descriptor_info[i] = { .sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT, - .pView = &imageViewCreateInfos[i], + .pView = &image_view_create_infos[i], .layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, }; - resource_descriptor_infos[heapResIndex] = { + resource_descriptor_infos[resource_heap_index] = { .sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT, .type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, .data = { - .pImage = &imageDescriptorInfo[i]}}; + .pImage = &image_descriptor_info[i]}}; - host_address_ranges_resources[heapResIndex] = { + host_address_ranges_resources[resource_heap_index] = { .address = (uint8_t *) (descriptor_heap_resources->get_data()) + image_heap_offset + image_descriptor_size * i, .size = image_descriptor_size}; - heapResIndex++; + resource_heap_index++; } vkWriteResourceDescriptorsEXT(get_device().get_handle(), static_cast(resource_descriptor_infos.size()), resource_descriptor_infos.data(), host_address_ranges_resources.data()); @@ -322,10 +323,10 @@ void DescriptorHeap::create_pipeline() // This is done by specifiying the bindings and their types at the shader stage level // As samplers require a different heap (than images), we can't use combined images - std::array setAndBindingMappings{}; + std::array set_binding_mappings{}; // Buffer binding - setAndBindingMappings[0] = { + set_binding_mappings[0] = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT, .descriptorSet = 0, .firstBinding = 0, @@ -337,7 +338,7 @@ void DescriptorHeap::create_pipeline() .heapArrayStride = static_cast(buffer_descriptor_size)}}}; // We are using multiple images, which requires us to set heapArrayStride to let the implementation know where image n+1 starts - setAndBindingMappings[1] = { + set_binding_mappings[1] = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT, .descriptorSet = 1, .firstBinding = 0, @@ -349,7 +350,7 @@ void DescriptorHeap::create_pipeline() .heapOffset = static_cast(image_heap_offset), .heapArrayStride = static_cast(image_descriptor_size)}}}; // As samplers require a different heap (than images), we can't use combined images but split image and sampler - setAndBindingMappings[2] = { + set_binding_mappings[2] = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT, .descriptorSet = 2, .firstBinding = 0, @@ -360,13 +361,13 @@ void DescriptorHeap::create_pipeline() .constantOffset = { .heapOffset = static_cast(sampler_heap_offset), .heapArrayStride = static_cast(sampler_descriptor_size)}}}; - VkShaderDescriptorSetAndBindingMappingInfoEXT descriptorSetAndBindingMappingInfo{ + VkShaderDescriptorSetAndBindingMappingInfoEXT descriptor_set_binding_mapping_info{ .sType = VK_STRUCTURE_TYPE_SHADER_DESCRIPTOR_SET_AND_BINDING_MAPPING_INFO_EXT, - .mappingCount = static_cast(setAndBindingMappings.size()), - .pMappings = setAndBindingMappings.data()}; + .mappingCount = static_cast(set_binding_mappings.size()), + .pMappings = set_binding_mappings.data()}; - shader_stages[0].pNext = &descriptorSetAndBindingMappingInfo; - shader_stages[1].pNext = &descriptorSetAndBindingMappingInfo; + shader_stages[0].pNext = &descriptor_set_binding_mapping_info; + shader_stages[1].pNext = &descriptor_set_binding_mapping_info; // Create graphics pipeline for dynamic rendering VkFormat color_rendering_format = get_render_context().get_format(); From 20b95f0bb1170473c470e88dd13caa01f80dc080 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sat, 18 Apr 2026 09:31:38 +0200 Subject: [PATCH 19/34] Minor fixes --- .../descriptor_heap/descriptor_heap.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp index fafa9d1630..a32f1bdd4e 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.cpp +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -167,16 +167,15 @@ void DescriptorHeap::create_descriptor_heaps() // We create heaps with a fixed size that's guaranteed to fit in the few descriptors we use const VkDeviceSize heap_buffer_size = aligned_size(2048 + descriptor_heap_properties.minResourceHeapReservedRange, descriptor_heap_properties.resourceHeapAlignment); - descriptor_heap_resources - = std::make_unique(get_device(), - heap_buffer_size, + descriptor_heap_resources = std::make_unique(get_device(), + heap_buffer_size, VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU); const VkDeviceSize heap_sampler_size = aligned_size(2048 + descriptor_heap_properties.minSamplerHeapReservedRange, descriptor_heap_properties.samplerHeapAlignment); descriptor_heap_samplers = std::make_unique(get_device(), - heap_buffer_size, + heap_sampler_size, VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU); @@ -221,7 +220,7 @@ void DescriptorHeap::create_descriptor_heaps() .size = sampler_descriptor_size}; } - vkWriteSamplerDescriptorsEXT(get_device().get_handle(), static_cast(host_address_ranges_samplers.size()), sampler_create_infos.data(), host_address_ranges_samplers.data()); + VK_CHECK(vkWriteSamplerDescriptorsEXT(get_device().get_handle(), static_cast(host_address_ranges_samplers.size()), sampler_create_infos.data(), host_address_ranges_samplers.data())); // Resource heap (buffers and images) buffer_descriptor_size = aligned_size(descriptor_heap_properties.bufferDescriptorSize, descriptor_heap_properties.bufferDescriptorAlignment); @@ -239,7 +238,7 @@ void DescriptorHeap::create_descriptor_heaps() std::vector device_address_ranges_uniform_buffer(uniform_buffers.size()); for (auto i = 0; i < uniform_buffers.size(); i++) { - device_address_ranges_uniform_buffer[i] = {.address = uniform_buffers[i]->get_device_address(), .size = uniform_buffers[i]->get_size()}; + device_address_ranges_uniform_buffer[i] = {.address = uniform_buffers[i]->get_device_address(), .size = uniform_buffers[i]->get_size()}; resource_descriptor_infos[resource_heap_index] = { .sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT, .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, @@ -286,7 +285,7 @@ void DescriptorHeap::create_descriptor_heaps() resource_heap_index++; } - vkWriteResourceDescriptorsEXT(get_device().get_handle(), static_cast(resource_descriptor_infos.size()), resource_descriptor_infos.data(), host_address_ranges_resources.data()); + VK_CHECK(vkWriteResourceDescriptorsEXT(get_device().get_handle(), static_cast(resource_descriptor_infos.size()), resource_descriptor_infos.data(), host_address_ranges_resources.data())); } void DescriptorHeap::create_pipeline() @@ -521,7 +520,7 @@ void DescriptorHeap::build_command_buffer() .address = descriptor_heap_resources->get_device_address(), .size = descriptor_heap_resources->get_size()}, .reservedRangeOffset = descriptor_heap_resources->get_size() - descriptor_heap_properties.minResourceHeapReservedRange, - .reservedRangeSize = descriptor_heap_properties.minResourceHeapReservedRange, + .reservedRangeSize = descriptor_heap_properties.minResourceHeapReservedRange, }; vkCmdBindResourceHeapEXT(draw_cmd_buffer, &bind_heap_info_res); @@ -532,7 +531,7 @@ void DescriptorHeap::build_command_buffer() .address = descriptor_heap_samplers->get_device_address(), .size = descriptor_heap_samplers->get_size()}, .reservedRangeOffset = descriptor_heap_samplers->get_size() - descriptor_heap_properties.minSamplerHeapReservedRange, - .reservedRangeSize = descriptor_heap_properties.minSamplerHeapReservedRange}; + .reservedRangeSize = descriptor_heap_properties.minSamplerHeapReservedRange}; vkCmdBindSamplerHeapEXT(draw_cmd_buffer, &bind_heap_info_samplers); VkDeviceSize offsets[1] = {0}; From 422583bf67bc3c34c09eb49ac13f67a1e0e043ac Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sat, 18 Apr 2026 10:06:01 +0200 Subject: [PATCH 20/34] Adjust offset and size calculations --- .../descriptor_heap/descriptor_heap.cpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp index a32f1bdd4e..a09789fc9a 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.cpp +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -180,12 +180,8 @@ void DescriptorHeap::create_descriptor_heaps() VMA_MEMORY_USAGE_CPU_TO_GPU); // Sampler heap - // We need to calculate some aligned offsets, heaps and strides to make sure we properly accress the descriptors sampler_descriptor_size = aligned_size(descriptor_heap_properties.samplerDescriptorSize, descriptor_heap_properties.samplerDescriptorAlignment); - auto sampler_start = aligned_size(descriptor_heap_properties.minSamplerHeapReservedRange, descriptor_heap_properties.samplerDescriptorAlignment); - sampler_heap_offset = aligned_size(sampler_start, descriptor_heap_properties.samplerDescriptorSize); - std::array host_address_ranges_samplers{}; // No need to create an actual VkSampler, we can simply pass the create info that describes the sampler @@ -224,8 +220,8 @@ void DescriptorHeap::create_descriptor_heaps() // Resource heap (buffers and images) buffer_descriptor_size = aligned_size(descriptor_heap_properties.bufferDescriptorSize, descriptor_heap_properties.bufferDescriptorAlignment); - // Images are storted after the last buffer (aligned) - image_heap_offset = aligned_size(buffer_descriptor_size, descriptor_heap_properties.imageDescriptorAlignment); + // Images are stored after the last buffer (aligned) + image_heap_offset = aligned_size(buffer_descriptor_size * uniform_buffers.size(), descriptor_heap_properties.imageDescriptorAlignment); image_descriptor_size = aligned_size(descriptor_heap_properties.imageDescriptorSize, descriptor_heap_properties.imageDescriptorAlignment); auto vector_size{cubes.size() + uniform_buffers.size()}; @@ -234,7 +230,7 @@ void DescriptorHeap::create_descriptor_heaps() size_t resource_heap_index{0}; - // Buffer + // Buffers std::vector device_address_ranges_uniform_buffer(uniform_buffers.size()); for (auto i = 0; i < uniform_buffers.size(); i++) { @@ -253,9 +249,8 @@ void DescriptorHeap::create_descriptor_heaps() // Images std::array image_view_create_infos{}; - std::array image_descriptor_info{}; + std::array image_descriptor_infos{}; - // @offset for (auto i = 0; i < cubes.size(); i++) { image_view_create_infos[i] = { @@ -266,7 +261,7 @@ void DescriptorHeap::create_descriptor_heaps() .subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = static_cast(cubes[i].texture.image->get_mipmaps().size()), .baseArrayLayer = 0, .layerCount = 1}, }; - image_descriptor_info[i] = { + image_descriptor_infos[i] = { .sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT, .pView = &image_view_create_infos[i], .layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, @@ -276,7 +271,7 @@ void DescriptorHeap::create_descriptor_heaps() .sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT, .type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, .data = { - .pImage = &image_descriptor_info[i]}}; + .pImage = &image_descriptor_infos[i]}}; host_address_ranges_resources[resource_heap_index] = { .address = (uint8_t *) (descriptor_heap_resources->get_data()) + image_heap_offset + image_descriptor_size * i, From 49400eb6f07f6f796896c608d99da9d8fcf40e25 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sat, 18 Apr 2026 10:07:19 +0200 Subject: [PATCH 21/34] Add tutorial for descriptor heap sample --- samples/extensions/README.adoc | 12 +- .../extensions/descriptor_heap/README.adoc | 262 ++++++++++++++++++ 2 files changed, 272 insertions(+), 2 deletions(-) create mode 100644 samples/extensions/descriptor_heap/README.adoc diff --git a/samples/extensions/README.adoc b/samples/extensions/README.adoc index 66dd399829..7102c8a82c 100644 --- a/samples/extensions/README.adoc +++ b/samples/extensions/README.adoc @@ -250,11 +250,19 @@ Demonstrate how to use patch control points dynamically, which can reduce the nu Demonstrate how to use fragment shader barycentric feature, which allows accessing barycentric coordinates for each processed fragment. -=== xref:./{extension_samplespath}descriptor_buffer_basic/README.adoc[Basic descriptor buffer] +=== xref:./{extension_samplespath}descriptor_buffer_basic/README.adoc[Descriptor buffer] *Extension*: https://www.khronos.org/registry/vulkan/specs/latest/html/vkspec.html#VK_ext_descriptor_buffer[`VK_EXT_descriptor_buffer`] -Demonstrate how to use the new extension to replace descriptor sets with resource descriptor buffers +**Note:** If available, `VK_EXT_descriptor_heap` should be preferred. + +Demonstrate how to use the resource descriptor buffers to replace descriptor sets + +=== xref:./{extension_samplespath}descriptor_heap/README.adoc[Descriptor heap] + +*Extension*: https://www.khronos.org/registry/vulkan/specs/latest/html/vkspec.html#VK_ext_descriptor_heap[`VK_EXT_descriptor_heap`] + +Demonstrate how to use descriptor heaps that completely rework how descriptors are handled in Vulkan === xref:./{extension_samplespath}color_write_enable/README.adoc[Color write enable] diff --git a/samples/extensions/descriptor_heap/README.adoc b/samples/extensions/descriptor_heap/README.adoc new file mode 100644 index 0000000000..cfce95f314 --- /dev/null +++ b/samples/extensions/descriptor_heap/README.adoc @@ -0,0 +1,262 @@ +//// +Copyright (c) 2026, Sascha Willems + + SPDX-License-Identifier: Apache-2.0 + +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 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +//// += Descriptor heap + +ifdef::site-gen-antora[] +TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/descriptor_heap[Khronos Vulkan samples github repository]. +endif::[] + +== Overview + +Managing descriptors is one of the harder parts of Vulkan. The link:https://docs.vulkan.org/features/latest/features/proposals/VK_EXT_descriptor_buffer.html[VK_EXT_descriptor_buffer] extension tried to simplify this but came with a few caveats. + +The link:https://docs.vulkan.org/features/latest/features/proposals/VK_EXT_descriptor_heap.html[VK_EXT_descriptor_heap] extension fixes these and completely overhauls Vulkan's descriptor system by providing direct access to descriptor memory using heaps. All while preserving compatibility with shaders using legacy descriptor sets (optionally) or (more forward-looking) by using untyped pointers. + +This extension is intended to completely replace Vulkan's existing descriptor set mechanism, and, if available, supersedes descriptor buffers. It's meant as the way forward for passing data to shaders. + +Instead of having to create and bind individual descriptor, descriptor information is stored in heaps, which are just buffers. So descriptors become just memory. This also no longer requires the use of descriptor layouts. + +To sum it up: Descriptor heaps heavily simplify descriptor management and also make descriptor management a lot more flexible. + +== Heap types + +Implementations required two distinct heap types, so this extension differentiates between: + +- Sampler heaps for storing samplers +- Resource heaps for storing all other resource types like buffers and images + +So in a typical scenario where we want to use e.g. uniform buffers, storage buffers, images and samplers, we need to create two distinct heaps. + +== Creating heaps + +As noted above, heaps are basically just memory that you can copy descriptor related information to. So creating a heap isn't different from creating any other buffer in Vulkan and requires the specific `VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT` usage flag: + +[,cpp] +---- +const VkDeviceSize heap_buffer_size = aligned_size(2048 + descriptor_heap_properties.minResourceHeapReservedRange, descriptor_heap_properties.resourceHeapAlignment); + +descriptor_heap_resources = + std::make_unique( + get_device(), + heap_buffer_size, + VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, + VMA_MEMORY_USAGE_CPU_TO_GPU); +---- + +== Uploading data to heaps + +What information is required to store a descriptor type to a heap varies. The destination to where this is stored in the heap is is passed using link:https://docs.vulkan.org/refpages/latest/refpages/source/VkHostAddressRangeEXT.html[host address ranges]. + +For samplers, we pass `VkSamplerCreateInfo` (no need to create an actual `VkSampler`): + +[,cpp] +---- +VkSamplerCreateInfo sampler_ci{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .magFilter = VK_FILTER_LINEAR, + .minFilter = VK_FILTER_LINEAR, + ...}; +VkHostAddressRangeEXT sampler_har{ + .address = (uint8_t *) (descriptor_heap_samplers->get_data()) + sampler_heap_offset, + .size = sampler_descriptor_size}; +vkWriteSamplerDescriptorsEXT(get_device().get_handle(), 1, &sampler_ci, &sampler_har); +---- + +For images, we pass `VkImageViewCreateInfo` (pointing to the actual `VkImage`): + +[,cpp] +---- +VkImageViewCreateInfo image_view_ci{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = texture.image->get_vk_image().get_handle(), + .viewType = VK_IMAGE_VIEW_TYPE_2D + ...}; +VkImageDescriptorInfoEXT image_descriptor_info{ + .sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT, + .pView = &image_view_ci, + ...}; +VkResourceDescriptorInfoEXT image_resource_desc{ + .sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT, + .type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, + .data = {.pImage = &image_descriptor_info}}; +VkHostAddressRangeEXT image_host_address{ + .address = (uint8_t *) (descriptor_heap_resources->get_data()) + image_heap_offset, + .size = image_descriptor_size}; +vkWriteResourceDescriptorsEXT(get_device().get_handle(), 1, &image_resource_desc, &image_host_address); +---- + +For buffers, we just pass their device address and range: + +[,cpp] +---- +VkDeviceAddressRangeEXT buffer_device_addr_range{ + .address = uniform_buffer->get_device_address(), + .size = uniform_buffer->get_size()}; +VkResourceDescriptorInfoEXT buffer_resource_desc{ + .sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT, + .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .data = {.pAddressRange = &buffer_device_addr_range}}; +VkHostAddressRangeEXT buffer_host_address{ + .address = (uint8_t *) (descriptor_heap_resources->get_data()), + .size = buffer_descriptor_size}; +vkWriteResourceDescriptorsEXT(get_device().get_handle(), 1, &buffer_resource_desc, &buffer_host_address); +---- + +link:https://docs.vulkan.org/refpages/latest/refpages/source/vkWriteResourceDescriptorsEXT.html[vkWriteResourceDescriptorsEXT] and link:https://docs.vulkan.org/refpages/latest/refpages/source/vkWriteSamplerDescriptorsEXT.html[vkWriteSamplerDescriptorsEXT] are then used to write the descriptors to (heap) memory. + +**Note:** The above examples are simplified to show a basic setup. The `vkWrite*DescriptorsEXT` can be used to write multiple descriptors to a heap at once. + +== Binding heaps + +Heaps are bound per heap-type using link:https://docs.vulkan.org/refpages/latest/refpages/source/vkCmdBindResourceHeapEXT.html[vkCmdBindResourceHeapEXT], so if you use a resource heap and a sampler heap, you need to issue two binding calls: + +[,cpp] +---- +// Bind the resource heap (buffers and images) +VkBindHeapInfoEXT bind_heap_info_res{ + .sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT, + .heapRange{ + .address = descriptor_heap_resources->get_device_address(), + .size = descriptor_heap_resources->get_size()}, + .reservedRangeOffset = descriptor_heap_resources->get_size() - descriptor_heap_properties.minResourceHeapReservedRange, + .reservedRangeSize = descriptor_heap_properties.minResourceHeapReservedRange, +}; +vkCmdBindResourceHeapEXT(draw_cmd_buffer, &bind_heap_info_res); + +// Bind the sampler heap +VkBindHeapInfoEXT bind_heap_info_samplers{ + .sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT, + .heapRange{ + .address = descriptor_heap_samplers->get_device_address(), + .size = descriptor_heap_samplers->get_size()}, + .reservedRangeOffset = descriptor_heap_samplers->get_size() - descriptor_heap_properties.minSamplerHeapReservedRange, + .reservedRangeSize = descriptor_heap_properties.minSamplerHeapReservedRange}; +vkCmdBindSamplerHeapEXT(draw_cmd_buffer, &bind_heap_info_samplers); + +... + +// All draw calls will source from heaps (until non-heap bindings are issued) +vkCmdDrawIndexed(draw_cmd_buffer, cube->vertex_indices, 2, 0, 0, 0); +---- + +**Note:** One important difference (compared to other buffers) is the **reserved range** (e.g. `reservedRangeOffset` and `reservedRangeSize`). Implementations, to varying degrees, need additional space for some descriptor related operations. This space mustn't be used by the application so we both need to allocate and mark those regions as reserved. For this sample, we put the reserved range after the space where we've copy the descriptors to. + +== Accessing heaps + +The extension lets us access data in shaders via two different ways: + +=== Descriptor set and binding mappings + +link:https://docs.vulkan.org/refpages/latest/refpages/source/VkDescriptorSetAndBindingMappingEXT.html[VkDescriptorSetAndBindingMappingEXT] can be used to simplify porting existing code bases to descriptor heaps. By using this, existing shaders can be used without having to modify them. The syntax for binding descriptors (no matter what shading language) stays the same. + +[,glsl] +---- +// Vertex shader +layout (set = 0, binding = 0) uniform UBO { + mat4 projection; + mat4 view; + mat4 model; +} ubo[2]; + +// Fragment shader +layout (set = 1, binding = 0) uniform texture2D textureImage[2]; +layout (set = 2, binding = 0) uniform sampler textureSampler[2]; +---- + +The app then defines how the heaps are mapped to those bindings: + +[,cpp] +---- +std::array set_binding_mappings{}; + +// Maps "layout (set = 0, binding = 0) uniform UBO" +set_binding_mappings[0] = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT, + .descriptorSet = 0, + .firstBinding = 0, + .bindingCount = 1, + .resourceMask = VK_SPIRV_RESOURCE_TYPE_UNIFORM_BUFFER_BIT_EXT, + .source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT, + .sourceData = { + .constantOffset = { + .heapArrayStride = static_cast(buffer_descriptor_size)}}}; + +// Maps "layout (set = 1, binding = 0) uniform texture2D" +set_binding_mappings[1] = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT, + .descriptorSet = 1, + .firstBinding = 0, + .bindingCount = 1, + .resourceMask = VK_SPIRV_RESOURCE_TYPE_SAMPLED_IMAGE_BIT_EXT, + .source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT, + .sourceData = { + .constantOffset = { + .heapOffset = static_cast(image_heap_offset), .heapArrayStride = static_cast(image_descriptor_size)}}}; + +// Maps "layout (set = 2, binding = 0) uniform sampler" +set_binding_mappings[2] = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT, + .descriptorSet = 2, + .firstBinding = 0, + .bindingCount = 1, + .resourceMask = VK_SPIRV_RESOURCE_TYPE_SAMPLER_BIT_EXT, + .source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT, + .sourceData = { + .constantOffset = { + .heapOffset = static_cast(sampler_heap_offset), .heapArrayStride = static_cast(sampler_descriptor_size)}}}; +---- + +These mappings need to be passed to the respective shader stages: + +[,cpp] +---- +VkShaderDescriptorSetAndBindingMappingInfoEXT descriptor_set_binding_mapping_info{ + .sType = VK_STRUCTURE_TYPE_SHADER_DESCRIPTOR_SET_AND_BINDING_MAPPING_INFO_EXT, + .mappingCount = static_cast(set_binding_mappings.size()), + .pMappings = set_binding_mappings.data()}; + +shader_stages[0].pNext = &descriptor_set_binding_mapping_info; +shader_stages[1].pNext = &descriptor_set_binding_mapping_info; +---- + +=== Untyped pointers + +**Note:** This is not yet demonstrated in this sample. Support also varies between shading languages and might not be available everywhere. + +As a more forward-looking way, descriptors can directly be accessed from a shader using SPIR-V untyped pointers, which are exposed via link:https://docs.vulkan.org/refpages/latest/refpages/source/VK_KHR_shader_untyped_pointers.html[VK_KHR_shader_untyped_pointers] in Vulkan. + +This requires shaders to be adopted accordingly and als requires the shading language to support this feature. In GLSL e.g. this is provided via link:https://github.com/KhronosGroup/GLSL/blob/main/extensions/ext/GLSL_EXT_descriptor_heap.txt[GLSL_EXT_descriptor_heap]: + +[,glsl] +---- +#extension GL_EXT_descriptor_heap: require +... +layout(descriptor_heap) uniform texture2D textureImage[]; +layout(descriptor_heap) uniform sampler textureSampler[]; +... +vec4 color = texture(sampler2D(textureImage[someIndex])); +---- + +== Links + +* https://docs.vulkan.org/features/latest/features/proposals/VK_EXT_descriptor_heap.html[Extension proposal] +* https://docs.vulkan.org/guide/latest/descriptor_heap.html[Vulkan Guide entry] +* https://www.khronos.org/blog/vulkan-introduces-roadmap-2026-and-new-descriptor-heap-extension[Blog entry] +* https://vulkan.gpuinfo.org/displayextensiondetail.php?extension=VK_EXT_descriptor_heap[Extension device coverage] + + From cfd21db8622d6da15c104680754d91f682f72232 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sat, 18 Apr 2026 10:08:10 +0200 Subject: [PATCH 22/34] Add compiled shaders --- shaders/descriptor_heap/glsl/cube.frag.spv | Bin 1216 -> 1288 bytes shaders/descriptor_heap/slang/cube.frag.spv | Bin 1268 -> 1268 bytes shaders/descriptor_heap/slang/cube.vert.spv | Bin 2036 -> 2052 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/shaders/descriptor_heap/glsl/cube.frag.spv b/shaders/descriptor_heap/glsl/cube.frag.spv index 9e37aee961ec7389e9e416623275ee37f7e8f196..33867fdb4454e5d8802ff58914dc95c7245b3ff6 100644 GIT binary patch delta 95 zcmX@W*}=ul%%sfDz`)4B&A>a6JJ+3sfs4W2C*IxP-`&SGKDnSEzPO|^CpA7NGcPqh gC9^0sxg@hJm4ShUfdffhW?phmX$q2pjpd#!0C35=z%%sfDz`)4B&A>5{J9py&4;BC}4Fqri diff --git a/shaders/descriptor_heap/slang/cube.frag.spv b/shaders/descriptor_heap/slang/cube.frag.spv index b18ad943d918c682cdc369d9e976c519d7506394..51b98e56e3d73be6df9cce412cd7b6d3ef98ab21 100644 GIT binary patch literal 1268 zcmZ9L?MqZa6o zi)T4|=J%WPHfQG6Yy0icmXkcsTC>s`TETTK;1M~=e zjCRmv;`D2yb$$z2b2MSx9~S2q<;#9CDYN27-Y*9EC3~%yugovYi*eo`WxsM<;X7dk z`?)B`S0{glMR}5qv(e;iQV!m{ennI}?&~|rM(4ltag`9<;%BV2e-bn;75lK~{8mn= z*oSAD_VKk!Dc_9et9t)-ntY$JsOk=8dTuby8=lBcoX@R#XK9X2CObRVW3Y4DMeY5V z8H$~^d)gkVB<$>yuV*&j7b?3}_A7J4=_}`*n!_{C<`kRLJnp-;A4TkaC%lT6`FioT zt3KB7X~K0;_qt%;gKPP9eD6d)gv~G?--y_KYbFsZN7H`g$+=KF9hM%D)FYL*Ih&V$bY*P{f|ew{72Id-E<8v9-O+ z6mNd-(i(3$seGEh+%?EvKJf1xTB!9uft>F$YJGV*XSRXbgR@L=52w67&d0Z7U-J4m wi!N$!V&~!eG{4wcK0%E;C5JQf+tWw>I~_QqLsY(tDjzwS=ac`->E5CL0Qme{tN;K2 literal 1268 zcmZ9LS!)zQ6oo6ZYcz||Xcl8n;)W3s6cI$h2Z{3*5aVNLoK^;$Hq3M&{&Qahzptu0 z45{JHt#kTrb*j7FI_QVaQV8K`c+S6TJ+y+%N@z#yel;wI4!(s=^GWH0TUdtO!w#`) z*iCGWJmdOUo8L0t8eM+IOvc!U+wlMvelekeiuk!MHF)GK!1Rr)R&4StM>rJuY;PGJo$sD&z0JzjgDQEUDWjmtWJBFORTP9 z=RQJH@k-oxqDDjZE@QoyeXNP~yw?BBB<@`Y$z5{B%6Z4u$nc9b#n!Zr&%N(vz71IO zhw-*k-%a*ixD2~Xd=D=+KLZc&=E&axUypp^e14;TF298ut9bLxu|~7RuEV;<8hfAb zg4M@7Icu)tUGn0Mh*HcRSZ(Z=X6Z0@dZ*=^`djQ>=5OP@C*!y2svh>ek9XhneZbx& zZ{9GX6tja)kHF5UPEYWj(|a)X8Q!l@{vBA|?7389b@E+kuxIrCUB-I`-)nQk&XQp+ zvGu)6d%U5ubsBx;{(_w4J^$X7-?X*Fp4)HR{vXLz&)b;&<>l092Xh9sYw~t$-k2f# weJsP|jZqKZN9&2z^C4#32{~%!w`Yv}H>Oh~zdL#7bba7to=^TCr+bV22Q8Re#Q*>R diff --git a/shaders/descriptor_heap/slang/cube.vert.spv b/shaders/descriptor_heap/slang/cube.vert.spv index b5541686597dfdf39005bebde43d3fced1a94255..f844342b5c583c4f058499c39947ed4c4b934e8b 100644 GIT binary patch literal 2052 zcmZvcT~8BH5QYzQDHv2FL_|g0T0c+_K`J7j1!I5vb^KT`AR(hupPP?$viYXhkSh zVNCcSd=fqjtHQ7_FO>8d)J}Y#KJcyR_#~Ox!&S_H8`b_UN;+qaxD_RhpcAya``x5j zo1b-M*Pq()?FQ}Rqp-6RHN#W%vOdI;`%pn)Zs}7DHHDXQiqd(5?!NZ@jZP;xuW_26{$)LC#NE_BkoNY{eN=TTqK({%X8zZ{6Lpgy zI=JYKrsh=4$f9hezOy_)Nj2iIgd}hpa&S%EZ7xdd8j@&G`-_m%yRee+Q zkU)JK^5w1X2-L?6;X|Ew1$caTk4HjPfQREvo(S;F7ID1KqyTSuo=Ps}c_y7a@Ro<) z6}+8Ko@oKz@;sMZ%rhgMJn-aUCT0criePbblIiVF)yG`C65xnoE?x`7aR;ksG3W8M zd%ckikG`aRIhCE;RY%d+at@wlZ_v;=%+0!hPhI$xa~qQ3$!*-GWO(jx+*`@;^w#dR bC7FAz>4ljMe2|$7-WQpf!0(T$Srq;Pu6L06 literal 2036 zcmZvc+fP$L5XKi;S_}#za(N)NfHwrWNJW%e!4M#l&_wZ-9O)rBhMq&(gF@m1e}aFm zFD8D!(;aAt+sxVdzMYw!otzT&dKFERGCGqq++wWk{nU%0%47}J^y zlBxthtKqF*{)WS+)21l=P_nCf0H^IE!J}Vh|V3@~cYUQ+~#E`eAd|kGCvDNdl$DDap>(rnBz%Gxe zKkv;j_2<1AE=!+~m_O&C=QtDb*q< zeqhePyCXj^ISfh2@0T#WG3vQ3#h#QtB_UsW#C)E59vkXEC*ch6aPyiMB=GbSe%!~R z1m5EC2TXj6vm~83@D`^c9p3gQ&RYq*#aWi#8Rwlaao~wV54@Ma(?@Xh!KwtF9Bscf z&r=7>dtEyGp(;pCHzd>*Vb~qi6qml)lwebp{Px_Iba-MJw=Er>^BDJ0Iy|-7(2X8w UhC` Date: Sat, 18 Apr 2026 10:08:17 +0200 Subject: [PATCH 23/34] Add new sample to navigation --- antora/modules/ROOT/nav.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/antora/modules/ROOT/nav.adoc b/antora/modules/ROOT/nav.adoc index 9f9b795ecf..62bf3ecb1a 100644 --- a/antora/modules/ROOT/nav.adoc +++ b/antora/modules/ROOT/nav.adoc @@ -57,6 +57,7 @@ ** xref:samples/extensions/conservative_rasterization/README.adoc[Conservative rasterization] ** xref:samples/extensions/debug_utils/README.adoc[Debug utils] ** xref:samples/extensions/descriptor_buffer_basic/README.adoc[Descriptor buffer basic] +** xref:samples/extensions/descriptor_heap/README.adoc[Descriptor heap] ** xref:samples/extensions/descriptor_indexing/README.adoc[Descriptor indexing] ** xref:samples/extensions/dynamic_line_rasterization/README.adoc[Dynamic line rasterization] ** xref:samples/extensions/dynamic_primitive_clipping/README.adoc[Dynamic primitive clipping] From 14e7fe51f890e8446140c67d349cb6cd77abc357 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sat, 18 Apr 2026 13:52:45 +0200 Subject: [PATCH 24/34] Clean up, push data documentation --- samples/extensions/descriptor_heap/README.adoc | 13 +++++++++++++ .../extensions/descriptor_heap/descriptor_heap.cpp | 11 ++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/samples/extensions/descriptor_heap/README.adoc b/samples/extensions/descriptor_heap/README.adoc index cfce95f314..c91927948e 100644 --- a/samples/extensions/descriptor_heap/README.adoc +++ b/samples/extensions/descriptor_heap/README.adoc @@ -156,6 +156,19 @@ vkCmdDrawIndexed(draw_cmd_buffer, cube->vertex_indices, 2, 0, 0, 0); **Note:** One important difference (compared to other buffers) is the **reserved range** (e.g. `reservedRangeOffset` and `reservedRangeSize`). Implementations, to varying degrees, need additional space for some descriptor related operations. This space mustn't be used by the application so we both need to allocate and mark those regions as reserved. For this sample, we put the reserved range after the space where we've copy the descriptors to. +== Push data (instead of push constants) + +This extension also adds a more direct way to push data state to the command buffer. With no more need for pipeline layouts this is now much simpler and done via a single call to link:https://docs.vulkan.org/refpages/latest/refpages/source/vkCmdPushDataEXT.html[vkCmdPushDataEXT]: + +[,cpp] +---- +PushData push_data{ ... }; +VkPushDataInfoEXT push_data_info{ + .sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT, + .data = {.address = &push_data, .size = sizeof(PushData)}}; +vkCmdPushDataEXT(draw_cmd_buffer, &push_data_info); +---- + == Accessing heaps The extension lets us access data in shaders via two different ways: diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp index a09789fc9a..89e6214b8c 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.cpp +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -499,14 +499,15 @@ void DescriptorHeap::build_command_buffer() { int32_t samplerIndex; int32_t frameIndex; - } pushData = { + } push_data = { .samplerIndex = selected_sampler, - // .frameIndex = static_cast(currentBuffer), + // Samples do not support frames-in-flight yet, so frameIndex never changes + .frameIndex = 0 }; - VkPushDataInfoEXT pushDataInfo{ + VkPushDataInfoEXT push_data_info{ .sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT, - .data = {.address = &pushData, .size = sizeof(PushData)}}; - vkCmdPushDataEXT(draw_cmd_buffer, &pushDataInfo); + .data = {.address = &push_data, .size = sizeof(PushData)}}; + vkCmdPushDataEXT(draw_cmd_buffer, &push_data_info); // Bind the heap containing resources (buffers and images) VkBindHeapInfoEXT bind_heap_info_res{ From 3d29c19ff17f948ae4a2f13363227db5de1c8ea2 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sat, 18 Apr 2026 14:05:32 +0200 Subject: [PATCH 25/34] Fix clang format --- samples/extensions/descriptor_heap/descriptor_heap.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp index 89e6214b8c..1ba9ed78df 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.cpp +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -501,9 +501,8 @@ void DescriptorHeap::build_command_buffer() int32_t frameIndex; } push_data = { .samplerIndex = selected_sampler, - // Samples do not support frames-in-flight yet, so frameIndex never changes - .frameIndex = 0 - }; + // Samples do not support frames-in-flight yet, so frameIndex never changes + .frameIndex = 0}; VkPushDataInfoEXT push_data_info{ .sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT, .data = {.address = &push_data, .size = sizeof(PushData)}}; From b345cf4f99bbae6086d7aedfa7fa3494abb7f352 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sat, 18 Apr 2026 14:19:03 +0200 Subject: [PATCH 26/34] Variable naming --- samples/extensions/descriptor_heap/descriptor_heap.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp index 1ba9ed78df..63d801d7b7 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.cpp +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -497,12 +497,12 @@ void DescriptorHeap::build_command_buffer() // Pass options as push data struct PushData { - int32_t samplerIndex; - int32_t frameIndex; + int32_t sampler_index; + int32_t frame_index; } push_data = { - .samplerIndex = selected_sampler, + .sampler_index = selected_sampler, // Samples do not support frames-in-flight yet, so frameIndex never changes - .frameIndex = 0}; + .frame_index = 0}; VkPushDataInfoEXT push_data_info{ .sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT, .data = {.address = &push_data, .size = sizeof(PushData)}}; From 2ae165b8bea31cf724882d12f8ba8354c70548d2 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sun, 19 Apr 2026 16:35:42 +0200 Subject: [PATCH 27/34] Spelling and grammar --- .../extensions/descriptor_heap/README.adoc | 36 +++++++++---------- .../descriptor_heap/descriptor_heap.cpp | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/samples/extensions/descriptor_heap/README.adoc b/samples/extensions/descriptor_heap/README.adoc index c91927948e..661237c2a0 100644 --- a/samples/extensions/descriptor_heap/README.adoc +++ b/samples/extensions/descriptor_heap/README.adoc @@ -23,19 +23,19 @@ endif::[] == Overview -Managing descriptors is one of the harder parts of Vulkan. The link:https://docs.vulkan.org/features/latest/features/proposals/VK_EXT_descriptor_buffer.html[VK_EXT_descriptor_buffer] extension tried to simplify this but came with a few caveats. +Managing descriptors is one of the harder parts of Vulkan. The link:https://docs.vulkan.org/features/latest/features/proposals/VK_EXT_descriptor_buffer.html[VK_EXT_descriptor_buffer] extension aimed to simplify this, but it came with a few caveats. -The link:https://docs.vulkan.org/features/latest/features/proposals/VK_EXT_descriptor_heap.html[VK_EXT_descriptor_heap] extension fixes these and completely overhauls Vulkan's descriptor system by providing direct access to descriptor memory using heaps. All while preserving compatibility with shaders using legacy descriptor sets (optionally) or (more forward-looking) by using untyped pointers. +The link:https://docs.vulkan.org/features/latest/features/proposals/VK_EXT_descriptor_heap.html[VK_EXT_descriptor_heap] extension addresses these caveats and completely overhauls Vulkan's descriptor system by providing direct access to descriptor memory using heaps, while still preserving compatibility with shaders that use legacy descriptor sets (optionally) or, more forward-looking, untyped pointers. -This extension is intended to completely replace Vulkan's existing descriptor set mechanism, and, if available, supersedes descriptor buffers. It's meant as the way forward for passing data to shaders. +This extension is intended to completely replace Vulkan's existing descriptor set mechanism and, if available, supersedes descriptor buffers. It is meant to be the way forward for passing data to shaders. -Instead of having to create and bind individual descriptor, descriptor information is stored in heaps, which are just buffers. So descriptors become just memory. This also no longer requires the use of descriptor layouts. +Instead of having to create and bind individual descriptors, descriptor information is stored in heaps, which are just buffers. Descriptors effectively become memory. This also no longer requires the use of descriptor layouts. -To sum it up: Descriptor heaps heavily simplify descriptor management and also make descriptor management a lot more flexible. +To sum it up: descriptor heaps greatly simplify descriptor management and make it much more flexible. == Heap types -Implementations required two distinct heap types, so this extension differentiates between: +Implementations require two distinct heap types, so this extension differentiates between: - Sampler heaps for storing samplers - Resource heaps for storing all other resource types like buffers and images @@ -44,7 +44,7 @@ So in a typical scenario where we want to use e.g. uniform buffers, storage buff == Creating heaps -As noted above, heaps are basically just memory that you can copy descriptor related information to. So creating a heap isn't different from creating any other buffer in Vulkan and requires the specific `VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT` usage flag: +As noted above, heaps are basically just memory that you can copy descriptor-related information to. Creating a heap therefore is no different from creating any other buffer in Vulkan and requires the `VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT` usage flag: [,cpp] ---- @@ -60,7 +60,7 @@ descriptor_heap_resources = == Uploading data to heaps -What information is required to store a descriptor type to a heap varies. The destination to where this is stored in the heap is is passed using link:https://docs.vulkan.org/refpages/latest/refpages/source/VkHostAddressRangeEXT.html[host address ranges]. +What information is required to store a descriptor type in a heap varies. The destination where this information is stored in the heap is passed using link:https://docs.vulkan.org/refpages/latest/refpages/source/VkHostAddressRangeEXT.html[host address ranges]. For samplers, we pass `VkSamplerCreateInfo` (no need to create an actual `VkSampler`): @@ -117,13 +117,13 @@ VkHostAddressRangeEXT buffer_host_address{ vkWriteResourceDescriptorsEXT(get_device().get_handle(), 1, &buffer_resource_desc, &buffer_host_address); ---- -link:https://docs.vulkan.org/refpages/latest/refpages/source/vkWriteResourceDescriptorsEXT.html[vkWriteResourceDescriptorsEXT] and link:https://docs.vulkan.org/refpages/latest/refpages/source/vkWriteSamplerDescriptorsEXT.html[vkWriteSamplerDescriptorsEXT] are then used to write the descriptors to (heap) memory. +link:https://docs.vulkan.org/refpages/latest/refpages/source/vkWriteResourceDescriptorsEXT.html[vkWriteResourceDescriptorsEXT] and link:https://docs.vulkan.org/refpages/latest/refpages/source/vkWriteSamplerDescriptorsEXT.html[vkWriteSamplerDescriptorsEXT] are then used to write the descriptors to heap memory. -**Note:** The above examples are simplified to show a basic setup. The `vkWrite*DescriptorsEXT` can be used to write multiple descriptors to a heap at once. +**Note:** The above examples are simplified to show a basic setup. The `vkWrite*DescriptorsEXT` functions can be used to write multiple descriptors to a heap at once. == Binding heaps -Heaps are bound per heap-type using link:https://docs.vulkan.org/refpages/latest/refpages/source/vkCmdBindResourceHeapEXT.html[vkCmdBindResourceHeapEXT], so if you use a resource heap and a sampler heap, you need to issue two binding calls: +Heaps are bound per heap type using link:https://docs.vulkan.org/refpages/latest/refpages/source/vkCmdBindResourceHeapEXT.html[vkCmdBindResourceHeapEXT], so if you use a resource heap and a sampler heap, you need to issue two binding calls: [,cpp] ---- @@ -154,11 +154,11 @@ vkCmdBindSamplerHeapEXT(draw_cmd_buffer, &bind_heap_info_samplers); vkCmdDrawIndexed(draw_cmd_buffer, cube->vertex_indices, 2, 0, 0, 0); ---- -**Note:** One important difference (compared to other buffers) is the **reserved range** (e.g. `reservedRangeOffset` and `reservedRangeSize`). Implementations, to varying degrees, need additional space for some descriptor related operations. This space mustn't be used by the application so we both need to allocate and mark those regions as reserved. For this sample, we put the reserved range after the space where we've copy the descriptors to. +**Note:** One important difference, compared to other buffers, is the **reserved range** (e.g. `reservedRangeOffset` and `reservedRangeSize`). Implementations, to varying degrees, need additional space for some descriptor-related operations. This space must not be used by the application, so we need to both allocate it and mark those regions as reserved. For this sample, we put the reserved range after the space where we have copied the descriptors. == Push data (instead of push constants) -This extension also adds a more direct way to push data state to the command buffer. With no more need for pipeline layouts this is now much simpler and done via a single call to link:https://docs.vulkan.org/refpages/latest/refpages/source/vkCmdPushDataEXT.html[vkCmdPushDataEXT]: +This extension also adds a more direct way to push state data to the command buffer. With no need for pipeline layouts, this is now much simpler and is done via a single call to link:https://docs.vulkan.org/refpages/latest/refpages/source/vkCmdPushDataEXT.html[vkCmdPushDataEXT]: [,cpp] ---- @@ -171,11 +171,11 @@ vkCmdPushDataEXT(draw_cmd_buffer, &push_data_info); == Accessing heaps -The extension lets us access data in shaders via two different ways: +The extension lets us access descriptors in shaders in two different ways: === Descriptor set and binding mappings -link:https://docs.vulkan.org/refpages/latest/refpages/source/VkDescriptorSetAndBindingMappingEXT.html[VkDescriptorSetAndBindingMappingEXT] can be used to simplify porting existing code bases to descriptor heaps. By using this, existing shaders can be used without having to modify them. The syntax for binding descriptors (no matter what shading language) stays the same. +link:https://docs.vulkan.org/refpages/latest/refpages/source/VkDescriptorSetAndBindingMappingEXT.html[VkDescriptorSetAndBindingMappingEXT] can be used to simplify porting existing codebases to descriptor heaps. By using this, existing shaders can be used without having to modify them. The syntax for binding descriptors, regardless of shading language, stays the same. [,glsl] ---- @@ -234,7 +234,7 @@ set_binding_mappings[2] = { .heapOffset = static_cast(sampler_heap_offset), .heapArrayStride = static_cast(sampler_descriptor_size)}}}; ---- -These mappings need to be passed to the respective shader stages: +These mappings need to be passed to the corresponding shader stages: [,cpp] ---- @@ -251,9 +251,9 @@ shader_stages[1].pNext = &descriptor_set_binding_mapping_info; **Note:** This is not yet demonstrated in this sample. Support also varies between shading languages and might not be available everywhere. -As a more forward-looking way, descriptors can directly be accessed from a shader using SPIR-V untyped pointers, which are exposed via link:https://docs.vulkan.org/refpages/latest/refpages/source/VK_KHR_shader_untyped_pointers.html[VK_KHR_shader_untyped_pointers] in Vulkan. +As a more forward-looking approach, descriptors can be accessed directly from a shader using SPIR-V untyped pointers, which are exposed in Vulkan via link:https://docs.vulkan.org/refpages/latest/refpages/source/VK_KHR_shader_untyped_pointers.html[VK_KHR_shader_untyped_pointers]. -This requires shaders to be adopted accordingly and als requires the shading language to support this feature. In GLSL e.g. this is provided via link:https://github.com/KhronosGroup/GLSL/blob/main/extensions/ext/GLSL_EXT_descriptor_heap.txt[GLSL_EXT_descriptor_heap]: +This requires shaders to be adapted accordingly and also requires the shading language to support this feature. In GLSL, for example, this is provided via link:https://github.com/KhronosGroup/GLSL/blob/main/extensions/ext/GLSL_EXT_descriptor_heap.txt[GLSL_EXT_descriptor_heap]: [,glsl] ---- diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp index 63d801d7b7..92504df5c3 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.cpp +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -532,7 +532,7 @@ void DescriptorHeap::build_command_buffer() VkDeviceSize offsets[1] = {0}; const auto &vertex_buffer = cube->vertex_buffers.at("vertex_buffer"); - auto &index_buffer = cube->index_buffer; + const auto &index_buffer = cube->index_buffer; vkCmdBindVertexBuffers(draw_cmd_buffer, 0, 1, vertex_buffer.get(), offsets); vkCmdBindIndexBuffer(draw_cmd_buffer, index_buffer->get_handle(), 0, cube->index_type); From 2e7cfda905dadbbe22805f18ea1435570753ef83 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sun, 19 Apr 2026 16:54:59 +0200 Subject: [PATCH 28/34] Add comparison to traditional descriptor bindings --- samples/extensions/descriptor_heap/README.adoc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/samples/extensions/descriptor_heap/README.adoc b/samples/extensions/descriptor_heap/README.adoc index 661237c2a0..639b1c6db2 100644 --- a/samples/extensions/descriptor_heap/README.adoc +++ b/samples/extensions/descriptor_heap/README.adoc @@ -25,11 +25,15 @@ endif::[] Managing descriptors is one of the harder parts of Vulkan. The link:https://docs.vulkan.org/features/latest/features/proposals/VK_EXT_descriptor_buffer.html[VK_EXT_descriptor_buffer] extension aimed to simplify this, but it came with a few caveats. -The link:https://docs.vulkan.org/features/latest/features/proposals/VK_EXT_descriptor_heap.html[VK_EXT_descriptor_heap] extension addresses these caveats and completely overhauls Vulkan's descriptor system by providing direct access to descriptor memory using heaps, while still preserving compatibility with shaders that use legacy descriptor sets (optionally) or, more forward-looking, untyped pointers. +The link:https://docs.vulkan.org/features/latest/features/proposals/VK_EXT_descriptor_heap.html[VK_EXT_descriptor_heap] extension addresses these caveats and completely overhauls Vulkan's descriptor. This extension is intended to completely replace Vulkan's existing descriptor set mechanism and, if available, supersedes descriptor buffers. It is meant to be the way forward for passing data to shaders. -Instead of having to create and bind individual descriptors, descriptor information is stored in heaps, which are just buffers. Descriptors effectively become memory. This also no longer requires the use of descriptor layouts. +In Vulkan, descriptor state is built around several explicit objects and binding steps. Applications define descriptor set layouts, create compatible pipeline layouts, allocate descriptor sets from descriptor pools, update those sets with resource information, and bind them during command buffer generation. This model is very explicit and works well, but it also means that descriptor management tends to become one of the more rigid and verbose parts of using Vulkan. + +Descriptor heaps replace most of that object-centric workflow with a memory-centric one. Instead of allocating descriptor sets and binding them per draw or dispatch, the application writes descriptor data directly into heap memory and binds the heaps themselves. Shaders then access descriptors either through set-and-binding mappings, which can be used with existing shaders, or more directly via untyped pointers. In practice, this moves descriptor handling closer to how applications already think about buffers: allocate memory, write data into it, and provide an addressable view to the GPU. + +So the shift is not only about reducing API objects. It is also about changing the mental model from "create and bind descriptor objects" to "manage descriptor memory. To sum it up: descriptor heaps greatly simplify descriptor management and make it much more flexible. From f985bba9531975ada8d157dd38edbd87bcc367e7 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sun, 19 Apr 2026 17:06:10 +0200 Subject: [PATCH 29/34] Minor fix --- samples/extensions/descriptor_heap/README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/extensions/descriptor_heap/README.adoc b/samples/extensions/descriptor_heap/README.adoc index 639b1c6db2..c8cf893695 100644 --- a/samples/extensions/descriptor_heap/README.adoc +++ b/samples/extensions/descriptor_heap/README.adoc @@ -33,7 +33,7 @@ In Vulkan, descriptor state is built around several explicit objects and binding Descriptor heaps replace most of that object-centric workflow with a memory-centric one. Instead of allocating descriptor sets and binding them per draw or dispatch, the application writes descriptor data directly into heap memory and binds the heaps themselves. Shaders then access descriptors either through set-and-binding mappings, which can be used with existing shaders, or more directly via untyped pointers. In practice, this moves descriptor handling closer to how applications already think about buffers: allocate memory, write data into it, and provide an addressable view to the GPU. -So the shift is not only about reducing API objects. It is also about changing the mental model from "create and bind descriptor objects" to "manage descriptor memory. +So the shift is not only about reducing API objects. It is also about changing the mental model from "create and bind descriptor objects" to "manage descriptor memory". To sum it up: descriptor heaps greatly simplify descriptor management and make it much more flexible. From daa8c329aeb363423cfb7f85b2d16f2bceef02f3 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sun, 19 Apr 2026 17:13:56 +0200 Subject: [PATCH 30/34] Add some key benefits of descriptor heaps --- .../extensions/descriptor_heap/README.adoc | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/samples/extensions/descriptor_heap/README.adoc b/samples/extensions/descriptor_heap/README.adoc index c8cf893695..6e6185c260 100644 --- a/samples/extensions/descriptor_heap/README.adoc +++ b/samples/extensions/descriptor_heap/README.adoc @@ -37,6 +37,30 @@ So the shift is not only about reducing API objects. It is also about changing t To sum it up: descriptor heaps greatly simplify descriptor management and make it much more flexible. +== Key benefits + +Understanding why descriptor heaps matter in practice comes down to a handful of concrete advantages over the traditional model. + +=== No descriptor pools or descriptor sets + +In traditional Vulkan, applications must pre-allocate descriptor pools, specify counts per descriptor type, and allocate descriptor sets from those pools. Getting pool sizes wrong leads to out-of-memory errors, and fragmentation can become a problem over time. With descriptor heaps, there are no pools and no sets - just heap buffers. Allocation is as straightforward as sizing a buffer for the number of descriptors you need. + +=== No pipeline layout compatibility constraints + +Traditional descriptor sets are bound against a specific pipeline layout, and the layout of the bound sets must be compatible with the pipeline being used. With descriptor heaps, there are no pipeline layouts for descriptor data. The same heap bindings work across all pipelines. + +=== Descriptors as plain memory + +Updating descriptors in the traditional model requires a `vkUpdateDescriptorSets` call, and the set must not be in use by the GPU at the time. With descriptor heaps, updating a descriptor is just a memory write via the `vkWrite*DescriptorsEXT` functions. This removes the "in-use" constraint at the descriptor object level and reduces the CPU-side API overhead per update. + +=== Natural fit for bindless rendering + +Descriptor indexing (link:https://docs.vulkan.org/refpages/latest/refpages/source/VK_EXT_descriptor_indexing.html[VK_EXT_descriptor_indexing]) made bindless rendering possible in Vulkan by treating descriptor sets as large arrays. Descriptor heaps take that idea further: an offset into heap memory is all that is needed to address any resource. There is no large descriptor set to maintain or bind — just a heap and an index. + +=== Simplified command buffer state management + +With heaps bound once per command buffer, there is no need to rebind descriptor sets when switching pipelines that share the same resource layout. This reduces the number of binding commands recorded per frame, which can meaningfully lower CPU overhead in draw-call-heavy workloads. + == Heap types Implementations require two distinct heap types, so this extension differentiates between: From cad116c05a240ad95b3cedc2c24f7b7534fe44f8 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Fri, 1 May 2026 07:57:00 +0200 Subject: [PATCH 31/34] Address PR feedback --- .../extensions/descriptor_heap/descriptor_heap.cpp | 13 ++++++------- .../extensions/descriptor_heap/descriptor_heap.h | 4 +++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp index 92504df5c3..4efb9bf145 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.cpp +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -60,7 +60,6 @@ bool DescriptorHeap::prepare(const vkb::ApplicationOptions &options) prepare_uniform_buffers(); create_descriptor_heaps(); create_pipeline(); - build_command_buffers(); prepared = true; return true; @@ -141,8 +140,8 @@ void DescriptorHeap::update_uniform_buffers(float delta_time) uniform_data.projection_matrix = camera.matrices.perspective; uniform_data.view_matrix = camera.matrices.view; - std::array positions = {glm::vec3(-2.0f, 0.0f, 0.0f), glm::vec3(1.5f, 0.5f, 0.0f)}; - for (auto i = 0; i < cubes.size(); i++) + std::array positions = {glm::vec3(-2.0f, 0.0f, 0.0f), glm::vec3(1.5f, 0.5f, 0.0f)}; + for (auto i = 0; i < cube_count; i++) { glm::mat4 cubeMat = glm::translate(glm::mat4(1.0f), positions[i]); cubeMat = glm::rotate(cubeMat, glm::radians(cubes[i].rotation.x), glm::vec3(1.0f, 0.0f, 0.0f)); @@ -247,9 +246,9 @@ void DescriptorHeap::create_descriptor_heaps() resource_heap_index++; } - // Images - std::array image_view_create_infos{}; - std::array image_descriptor_infos{}; + // Images (one per cube) + std::array image_view_create_infos{}; + std::array image_descriptor_infos{}; for (auto i = 0; i < cubes.size(); i++) { @@ -536,7 +535,7 @@ void DescriptorHeap::build_command_buffer() vkCmdBindVertexBuffers(draw_cmd_buffer, 0, 1, vertex_buffer.get(), offsets); vkCmdBindIndexBuffer(draw_cmd_buffer, index_buffer->get_handle(), 0, cube->index_type); - vkCmdDrawIndexed(draw_cmd_buffer, cube->vertex_indices, 2, 0, 0, 0); + vkCmdDrawIndexed(draw_cmd_buffer, cube->vertex_indices, cube_count, 0, 0, 0); vkCmdEndRenderingKHR(draw_cmd_buffer); diff --git a/samples/extensions/descriptor_heap/descriptor_heap.h b/samples/extensions/descriptor_heap/descriptor_heap.h index 3eeb4a3259..c4c20224d5 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.h +++ b/samples/extensions/descriptor_heap/descriptor_heap.h @@ -40,12 +40,14 @@ class DescriptorHeap : public ApiVulkanSample private: bool animate = true; + static const size_t cube_count = 2; + struct Cube { Texture texture; glm::vec3 rotation; }; - std::array cubes; + std::array cubes; struct UniformData { From be319a62034b9536cea3e6d525b22482db1b7210 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Fri, 1 May 2026 08:10:48 +0200 Subject: [PATCH 32/34] Adjust heap size calculations --- .../extensions/descriptor_heap/README.adoc | 2 +- .../descriptor_heap/descriptor_heap.cpp | 21 +++++++++---------- .../descriptor_heap/descriptor_heap.h | 5 +++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/samples/extensions/descriptor_heap/README.adoc b/samples/extensions/descriptor_heap/README.adoc index 6e6185c260..d1565e0a5c 100644 --- a/samples/extensions/descriptor_heap/README.adoc +++ b/samples/extensions/descriptor_heap/README.adoc @@ -76,7 +76,7 @@ As noted above, heaps are basically just memory that you can copy descriptor-rel [,cpp] ---- -const VkDeviceSize heap_buffer_size = aligned_size(2048 + descriptor_heap_properties.minResourceHeapReservedRange, descriptor_heap_properties.resourceHeapAlignment); +const VkDeviceSize heap_buffer_size = aligned_size(image_heap_offset + cube_count * image_descriptor_size + descriptor_heap_properties.minResourceHeapReservedRange, descriptor_heap_properties.resourceHeapAlignment); descriptor_heap_resources = std::make_unique( diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp index 4efb9bf145..7294fb5c13 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.cpp +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -162,16 +162,23 @@ void DescriptorHeap::create_descriptor_heaps() .pNext = &descriptor_heap_properties}; vkGetPhysicalDeviceProperties2(get_device().get_gpu().get_handle(), &device_props_2); + // Resource heap (buffers and images) + buffer_descriptor_size = aligned_size(descriptor_heap_properties.bufferDescriptorSize, descriptor_heap_properties.bufferDescriptorAlignment); + // Images are stored after the last buffer (aligned) + image_heap_offset = aligned_size(buffer_descriptor_size * cube_count, descriptor_heap_properties.imageDescriptorAlignment); + image_descriptor_size = aligned_size(descriptor_heap_properties.imageDescriptorSize, descriptor_heap_properties.imageDescriptorAlignment); + // Samplers will be stored in a separate heap + sampler_descriptor_size = aligned_size(descriptor_heap_properties.samplerDescriptorSize, descriptor_heap_properties.samplerDescriptorAlignment); + // There are two descriptor heap types: One that can store resources (buffers, images) and one that can store samplers - // We create heaps with a fixed size that's guaranteed to fit in the few descriptors we use - const VkDeviceSize heap_buffer_size = aligned_size(2048 + descriptor_heap_properties.minResourceHeapReservedRange, descriptor_heap_properties.resourceHeapAlignment); + const VkDeviceSize heap_buffer_size = aligned_size(image_heap_offset + cube_count * image_descriptor_size + descriptor_heap_properties.minResourceHeapReservedRange, descriptor_heap_properties.resourceHeapAlignment); descriptor_heap_resources = std::make_unique(get_device(), heap_buffer_size, VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU); - const VkDeviceSize heap_sampler_size = aligned_size(2048 + descriptor_heap_properties.minSamplerHeapReservedRange, descriptor_heap_properties.samplerHeapAlignment); + const VkDeviceSize heap_sampler_size = aligned_size(sampler_count * sampler_descriptor_size + descriptor_heap_properties.minSamplerHeapReservedRange, descriptor_heap_properties.samplerHeapAlignment); descriptor_heap_samplers = std::make_unique(get_device(), heap_sampler_size, @@ -179,8 +186,6 @@ void DescriptorHeap::create_descriptor_heaps() VMA_MEMORY_USAGE_CPU_TO_GPU); // Sampler heap - sampler_descriptor_size = aligned_size(descriptor_heap_properties.samplerDescriptorSize, descriptor_heap_properties.samplerDescriptorAlignment); - std::array host_address_ranges_samplers{}; // No need to create an actual VkSampler, we can simply pass the create info that describes the sampler @@ -217,12 +222,6 @@ void DescriptorHeap::create_descriptor_heaps() VK_CHECK(vkWriteSamplerDescriptorsEXT(get_device().get_handle(), static_cast(host_address_ranges_samplers.size()), sampler_create_infos.data(), host_address_ranges_samplers.data())); - // Resource heap (buffers and images) - buffer_descriptor_size = aligned_size(descriptor_heap_properties.bufferDescriptorSize, descriptor_heap_properties.bufferDescriptorAlignment); - // Images are stored after the last buffer (aligned) - image_heap_offset = aligned_size(buffer_descriptor_size * uniform_buffers.size(), descriptor_heap_properties.imageDescriptorAlignment); - image_descriptor_size = aligned_size(descriptor_heap_properties.imageDescriptorSize, descriptor_heap_properties.imageDescriptorAlignment); - auto vector_size{cubes.size() + uniform_buffers.size()}; std::vector host_address_ranges_resources(vector_size); std::vector resource_descriptor_infos(vector_size); diff --git a/samples/extensions/descriptor_heap/descriptor_heap.h b/samples/extensions/descriptor_heap/descriptor_heap.h index c4c20224d5..40eaa7c055 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.h +++ b/samples/extensions/descriptor_heap/descriptor_heap.h @@ -40,7 +40,8 @@ class DescriptorHeap : public ApiVulkanSample private: bool animate = true; - static const size_t cube_count = 2; + static const size_t cube_count{2}; + static const size_t sampler_count{2}; struct Cube { @@ -53,7 +54,7 @@ class DescriptorHeap : public ApiVulkanSample { glm::mat4 projection_matrix; glm::mat4 view_matrix; - glm::mat4 model_matrix[2]; + glm::mat4 model_matrix[cube_count]; } uniform_data; VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptor_heap_properties{}; From 3124ad62181e0cb4e98def720810b3cf6e0efeaa Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Fri, 1 May 2026 08:29:11 +0200 Subject: [PATCH 33/34] Address PR feedback --- samples/extensions/descriptor_heap/descriptor_heap.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp index 7294fb5c13..7522a978e8 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.cpp +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -216,7 +216,7 @@ void DescriptorHeap::create_descriptor_heaps() for (auto i = 0; i < static_cast(sampler_create_infos.size()); i++) { host_address_ranges_samplers[i] = { - .address = (uint8_t *) (descriptor_heap_samplers->get_data()) + sampler_heap_offset + sampler_descriptor_size * i, + .address = const_cast(descriptor_heap_samplers->get_data()) + sampler_heap_offset + sampler_descriptor_size * i, .size = sampler_descriptor_size}; } @@ -239,7 +239,7 @@ void DescriptorHeap::create_descriptor_heaps() .data = { .pAddressRange = &device_address_ranges_uniform_buffer[i]}}; host_address_ranges_resources[resource_heap_index] = { - .address = (uint8_t *) (descriptor_heap_resources->get_data()) + buffer_descriptor_size * i, + .address = const_cast(descriptor_heap_resources->get_data()) + buffer_descriptor_size * i, .size = buffer_descriptor_size}; resource_heap_index++; @@ -272,7 +272,7 @@ void DescriptorHeap::create_descriptor_heaps() .pImage = &image_descriptor_infos[i]}}; host_address_ranges_resources[resource_heap_index] = { - .address = (uint8_t *) (descriptor_heap_resources->get_data()) + image_heap_offset + image_descriptor_size * i, + .address = const_cast(descriptor_heap_resources->get_data()) + image_heap_offset + image_descriptor_size * i, .size = image_descriptor_size}; resource_heap_index++; From 0c7132017fd0b054127bde2f8fb8153719913d86 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Thu, 14 May 2026 11:53:51 +0200 Subject: [PATCH 34/34] Add and use non-const memory access --- framework/core/allocated.h | 14 ++++++++++++++ .../extensions/descriptor_heap/descriptor_heap.cpp | 6 +++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/framework/core/allocated.h b/framework/core/allocated.h index a7734803ab..a292b221b9 100644 --- a/framework/core/allocated.h +++ b/framework/core/allocated.h @@ -176,6 +176,14 @@ class Allocated : public vkb::core::VulkanResource * @note This performs no checking that the memory is actually mapped, so it's possible to get a nullptr */ const uint8_t *get_data() const; + + /** + * @brief Retrieves a pointer to the host visible memory as an unsigned byte array. + * @return The pointer to the host visible memory. + * @note Non-const. This performs no checking that the memory is actually mapped, so it's possible to get a nullptr + */ + uint8_t *get_mapped_data() const; + /** * @brief Retrieves the raw Vulkan memory object. * @return The Vulkan memory object. @@ -588,6 +596,12 @@ inline const uint8_t *Allocated::get_data() const return mapped_data; } +template +inline uint8_t *Allocated::get_mapped_data() const +{ + return mapped_data; +} + template inline typename Allocated::DeviceMemoryType Allocated::get_memory() const { diff --git a/samples/extensions/descriptor_heap/descriptor_heap.cpp b/samples/extensions/descriptor_heap/descriptor_heap.cpp index 7522a978e8..86f6e9c59e 100644 --- a/samples/extensions/descriptor_heap/descriptor_heap.cpp +++ b/samples/extensions/descriptor_heap/descriptor_heap.cpp @@ -216,7 +216,7 @@ void DescriptorHeap::create_descriptor_heaps() for (auto i = 0; i < static_cast(sampler_create_infos.size()); i++) { host_address_ranges_samplers[i] = { - .address = const_cast(descriptor_heap_samplers->get_data()) + sampler_heap_offset + sampler_descriptor_size * i, + .address = descriptor_heap_samplers->get_mapped_data() + sampler_heap_offset + sampler_descriptor_size * i, .size = sampler_descriptor_size}; } @@ -239,7 +239,7 @@ void DescriptorHeap::create_descriptor_heaps() .data = { .pAddressRange = &device_address_ranges_uniform_buffer[i]}}; host_address_ranges_resources[resource_heap_index] = { - .address = const_cast(descriptor_heap_resources->get_data()) + buffer_descriptor_size * i, + .address = descriptor_heap_resources->get_mapped_data() + buffer_descriptor_size * i, .size = buffer_descriptor_size}; resource_heap_index++; @@ -272,7 +272,7 @@ void DescriptorHeap::create_descriptor_heaps() .pImage = &image_descriptor_infos[i]}}; host_address_ranges_resources[resource_heap_index] = { - .address = const_cast(descriptor_heap_resources->get_data()) + image_heap_offset + image_descriptor_size * i, + .address = descriptor_heap_resources->get_mapped_data() + image_heap_offset + image_descriptor_size * i, .size = image_descriptor_size}; resource_heap_index++;