From 6a0cffc480973b3fac846a97e65750d548acbe30 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Mon, 23 Feb 2026 21:18:26 +0100 Subject: [PATCH 01/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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); }