From d1e41b3a497df32e40e6e9ad537bda94129aa061 Mon Sep 17 00:00:00 2001 From: SpinnerX Date: Wed, 22 Apr 2026 13:40:45 -0700 Subject: [PATCH 01/11] Made demo to use the 3d model example --- demos/16-descriptor-indexing/CMakeLists.txt | 20 + demos/16-descriptor-indexing/application.cpp | 811 +++++++++++++++++++ demos/16-descriptor-indexing/conanfile.py | 37 + 3 files changed, 868 insertions(+) create mode 100644 demos/16-descriptor-indexing/CMakeLists.txt create mode 100644 demos/16-descriptor-indexing/application.cpp create mode 100644 demos/16-descriptor-indexing/conanfile.py diff --git a/demos/16-descriptor-indexing/CMakeLists.txt b/demos/16-descriptor-indexing/CMakeLists.txt new file mode 100644 index 0000000..5bd84bc --- /dev/null +++ b/demos/16-descriptor-indexing/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 4.0) +project(descriptor-indexing CXX) + +build_application( + SOURCES + application.cpp + + PACKAGES + vulkan-cpp + Vulkan + glfw3 + glm + stb + tinyobjloader + + LINK_PACKAGES + vulkan-cpp + tinyobjloader + Vulkan::Vulkan +) \ No newline at end of file diff --git a/demos/16-descriptor-indexing/application.cpp b/demos/16-descriptor-indexing/application.cpp new file mode 100644 index 0000000..cb6be6c --- /dev/null +++ b/demos/16-descriptor-indexing/application.cpp @@ -0,0 +1,811 @@ +#define GLFW_INCLUDE_VULKAN +#if _WIN32 +#define VK_USE_PLATFORM_WIN32_KHR +#include +#define GLFW_EXPOSE_NATIVE_WIN32 +#include +#include +#else +#include +#include +#endif + +#include +#include +#include +#include +import vk; + +#include +#define GLM_FORCE_RADIANS +#include +#include +#define GLM_ENABLE_EXPERIMENTAL +#include + +#include + +#include + +static VKAPI_ATTR VkBool32 VKAPI_CALL +debug_callback( + [[maybe_unused]] VkDebugUtilsMessageSeverityFlagBitsEXT p_message_severity, + [[maybe_unused]] VkDebugUtilsMessageTypeFlagsEXT p_message_type, + const VkDebugUtilsMessengerCallbackDataEXT* p_callback_data, + [[maybe_unused]] void* p_user_data) { + std::print("validation layer:\t\t{}\n\n", p_callback_data->pMessage); + return false; +} + +struct global_uniform { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + +template +void +hash_combine(size_t& seed, const T& v, const Rest&... rest) { + seed ^= std::hash()(v) + 0x9e3779b9 + (seed << 6) + (seed << 2); + (hash_combine(seed, rest), ...); +} + +namespace std { + + template<> + struct hash { + size_t operator()(const vk::vertex_input& vertex) const { + size_t seed = 0; + hash_combine( + seed, vertex.position, vertex.color, vertex.normals, vertex.uv); + return seed; + } + }; +} + +// Part of this demo for loading a 3D .obj model +class obj_model { +public: + obj_model() = default; + obj_model(const std::filesystem::path& p_filename, + const VkDevice& p_device, + const vk::physical_device& p_physical) { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + + //! @note If we return the constructor then we can check if the mesh + //! loaded successfully + //! @note We also receive hints if the loading is successful! + //! @note Return default constructor automatically returns false means + //! that mesh will return the boolean as false because it wasnt + //! successful + if (!tinyobj::LoadObj(&attrib, + &shapes, + &materials, + &warn, + &err, + p_filename.string().c_str())) { + std::println("Could not load model from path {}", + p_filename.string()); + m_is_loaded = false; + return; + } + + std::vector vertices; + std::vector indices; + std::unordered_map unique_vertices{}; + + for (const auto& shape : shapes) { + for (const auto& index : shape.mesh.indices) { + vk::vertex_input vertex{}; + + // vertices.push_back(vertex); + if (!unique_vertices.contains(vertex)) { + unique_vertices[vertex] = + static_cast(vertices.size()); + vertices.push_back(vertex); + } + + if (index.vertex_index >= 0) { + vertex.position = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2] + }; + + vertex.color = { + attrib.colors[3 * index.vertex_index + 0], + attrib.colors[3 * index.vertex_index + 1], + attrib.colors[3 * index.vertex_index + 2] + }; + } + + if (index.normal_index >= 0) { + vertex.normals = { + attrib.normals[3 * index.normal_index + 0], + attrib.normals[3 * index.normal_index + 1], + attrib.normals[3 * index.normal_index + 2] + }; + } + + if (index.texcoord_index >= 0) { + vertex.uv = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] + }; + } + + if (!unique_vertices.contains(vertex)) { + unique_vertices[vertex] = + static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(unique_vertices[vertex]); + } + } + + m_has_indices = (indices.size() > 0) ? true : false; + + if (m_has_indices) { + m_indices_size = indices.size(); + } + m_indices_size = vertices.size(); + m_indices_size = indices.size(); + + //! @brief Creating vertex/index buffers with host visibility flags + const auto property_flags = static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit); + + vk::buffer_parameters vertex_params = { + .memory_mask = p_physical.memory_properties(property_flags), + .property_flags = vk::memory_property::device_local_bit, + .usage = static_cast(vk::buffer_usage::transfer_dst_bit) | + static_cast(vk::buffer_usage::vertex_buffer_bit), + }; + + vk::buffer_parameters index_params = { + .memory_mask = p_physical.memory_properties(property_flags), + .property_flags = static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit), + .usage = static_cast(vk::buffer_usage::index_buffer_bit), + }; + + m_vertex_buffer = vk::vertex_buffer(p_device, vertices, vertex_params); + m_index_buffer = vk::index_buffer(p_device, indices, index_params); + m_is_loaded = true; + } + + [[nodiscard]] bool loaded() const { return m_is_loaded; } + + [[nodiscard]] VkBuffer vertex_handle() const { return m_vertex_buffer; } + + [[nodiscard]] VkBuffer index_handle() const { return m_index_buffer; } + + [[nodiscard]] bool has_indices() const { return m_has_indices; } + + [[nodiscard]] uint32_t indices_size() const { return m_indices_size; } + + void draw(const VkCommandBuffer& p_command) { + if (m_has_indices) { + vkCmdDrawIndexed(p_command, m_indices_size, 1, 0, 0, 0); + } + else { + vkCmdDraw(p_command, m_indices_size, 1, 0, 0); + } + } + + void destroy() { + m_vertex_buffer.destroy(); + m_index_buffer.destroy(); + } + +private: + bool m_is_loaded = false; + bool m_has_indices = false; + uint32_t m_indices_size = 0; + vk::vertex_buffer m_vertex_buffer{}; + vk::index_buffer m_index_buffer{}; +}; + +std::vector +get_instance_extensions() { + std::vector extension_names; + uint32_t extension_count = 0; + const char** required_extensions = + glfwGetRequiredInstanceExtensions(&extension_count); + + for (uint32_t i = 0; i < extension_count; i++) { + std::println("Required Extension = {}", required_extensions[i]); + extension_names.emplace_back(required_extensions[i]); + } + + extension_names.emplace_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + +#if defined(__APPLE__) + extension_names.emplace_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + extension_names.emplace_back( + VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); +#endif + + return extension_names; +} + +int +main() { + //! @note Just added the some test code to test the conan-starter setup code + if (!glfwInit()) { + std::print("glfwInit could not be initialized!\n"); + return -1; + } + + if (!glfwVulkanSupported()) { + std::print("GLFW: Vulkan is not supported!"); + return -1; + } + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + int width = 800; + int height = 600; + std::string title = "Hello Window"; + GLFWwindow* window = + glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr); + + glfwMakeContextCurrent(window); + + std::array validation_layers = { + "VK_LAYER_KHRONOS_validation", + }; + + // setting up extensions + std::vector global_extensions = get_instance_extensions(); + + vk::debug_message_utility debug_callback_info = { + // .severity essentially takes in vk::message::verbose, + // vk::message::warning, vk::message::error + .severity = + vk::message::verbose | vk::message::warning | vk::message::error, + // .message_type essentially takes in vk::debug. Like: + // vk::debug::general, vk::debug::validation, vk::debug::performance + .message_type = + vk::debug::general | vk::debug::validation | vk::debug::performance, + .callback = debug_callback + }; + + vk::application_params config = { + .name = "vulkan instance", + .version = vk::api_version::vk_1_3, // specify to using vulkan 1.3 + .validations = + validation_layers, // .validation takes in a std::span + .extensions = + global_extensions // .extensions also takes in std::span + }; + + // 1. Setting up vk instance + vk::instance api_instance(config, debug_callback_info); + + if (api_instance.alive()) { + std::println("\napi_instance alive and initiated!!!"); + } + + // setting up physical device + vk::physical_enumeration enumerate_devices{ + .device_type = vk::physical_gpu::integrated, + }; + vk::physical_device physical_device(api_instance, enumerate_devices); + + vk::queue_indices queue_indices = physical_device.family_indices(); + + // setting up logical device + std::array priorities = { 0.f }; + +#if defined(__APPLE__) + std::array extensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + "VK_KHR_portability_subset", + }; +#else + std::array extensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + }; +#endif + + std::array format_support = { + vk::format::d32_sfloat, + vk::format::d32_sfloat_s8_uint, + vk::format::d24_unorm_s8_uint + }; + + // We provide a selection of format support that we want to check is + // supported on current hardware device. + // VkFormat depth_format = + // vk::select_depth_format(physical_device, format_support); + VkFormat depth_format = + physical_device.request_depth_format(format_support); + + vk::device_features device_features{ + vk::dynamic_rendering_feature{ { + .dynamicRendering = true, + } }, + vk::descriptor_indexing_feature{ { + .descriptorBindingPartiallyBound = true, + .runtimeDescriptorArray = true, + .descriptorBindingVariableDescriptorCount = true, + } }, + }; + + vk::device_params logical_device_params = { + .features = device_features.data(), + .queue_priorities = priorities, + .extensions = extensions, + .queue_family_index = queue_indices.graphics, + }; + + vk::device logical_device(physical_device, logical_device_params); + + vk::surface window_surface(api_instance, window); + + vk::surface_params surface_properties = + physical_device.request_surface(window_surface); + + vk::swapchain_params enumerate_swapchain_settings = { + .width = static_cast(width), + .height = static_cast(height), + .present_index = + physical_device.family_indices() + .graphics, // presentation index just uses the graphics index + }; + vk::swapchain main_swapchain(logical_device, + window_surface, + enumerate_swapchain_settings, + surface_properties); + + // querying presentable images + std::span images = main_swapchain.get_images(); + uint32_t image_count = static_cast(images.size()); + + // Creating Images + std::vector swapchain_images(image_count); + std::vector swapchain_depth_images(image_count); + + VkExtent2D swapchain_extent = surface_properties.capabilities.currentExtent; + + // Setting up the images + uint32_t layer_count = 1; + uint32_t mip_levels = 1; + for (uint32_t i = 0; i < swapchain_images.size(); i++) { + vk::image_params swapchain_image_config = { + .extent = { .width = swapchain_extent.width, + .height = swapchain_extent.height }, + .format = surface_properties.format.format, + .memory_mask = physical_device.memory_properties( + vk::memory_property::device_local_bit), + .aspect = vk::image_aspect_flags::color_bit, + .usage = vk::image_usage::color_attachment_bit, + .mip_levels = 1, + .layer_count = 1, + }; + + swapchain_images[i] = + vk::sample_image(logical_device, images[i], swapchain_image_config); + + vk::image_params image_config = { + .extent = { + .width = swapchain_extent.width, + .height = swapchain_extent.height, + }, + .format = depth_format, + .memory_mask = physical_device.memory_properties( + vk::memory_property::device_local_bit), + .aspect = vk::image_aspect_flags::depth_bit, + .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + .mip_levels = 1, + .layer_count = 1, + }; + swapchain_depth_images[i] = + vk::sample_image(logical_device, image_config); + } + + // setting up command buffers + std::vector swapchain_command_buffers(image_count); + for (size_t i = 0; i < swapchain_command_buffers.size(); i++) { + vk::command_params settings = { + .levels = vk::command_levels::primary, + .queue_index = enumerate_swapchain_settings.present_index, + .flags = vk::command_pool_flags::reset, + }; + + swapchain_command_buffers[i] = + vk::command_buffer(logical_device, settings); + } + + // setting up presentation queue to display commands to the screen + vk::queue_params enumerate_present_queue{ + .family = 0, + .index = 0, + }; + vk::device_present_queue presentation_queue( + logical_device, main_swapchain, enumerate_present_queue); + + // gets set with the renderpass + std::array color = { 0.f, 0.5f, 0.5f, 1.f }; + + // Loading graphics pipeline + std::array shader_sources = { + vk::shader_source{ + .filename = "shader_samples/sample5/test.vert.spv", + .stage = vk::shader_stage::vertex, + }, + vk::shader_source{ + .filename = "shader_samples/sample5/test.frag.spv", + .stage = vk::shader_stage::fragment, + }, + }; + + // To render triangle, we do not need to set any vertex attributes + vk::shader_resource_info shader_info = { + .sources = shader_sources, + .vertex_attributes = {} // this is to explicitly set to none, but also + // dont need to set this at all regardless + }; + vk::shader_resource geometry_resource(logical_device, shader_info); + + // Setting up vertex attributes in the test shaders + std::array attribute_entries = { + vk::vertex_attribute_entry{ + .location = 0, + .format = vk::format::rgb32_sfloat, + .stride = offsetof(vk::vertex_input, position), + }, + vk::vertex_attribute_entry{ + .location = 1, + .format = vk::format::rgb32_sfloat, + .stride = offsetof(vk::vertex_input, color), + }, + vk::vertex_attribute_entry{ + .location = 2, + .format = vk::format::rg32_sfloat, + .stride = offsetof(vk::vertex_input, uv), + }, + vk::vertex_attribute_entry{ + .location = 3, + .format = vk::format::rgb32_sfloat, + .stride = offsetof(vk::vertex_input, normals), + } + }; + + std::array attributes = { + vk::vertex_attribute{ + // layout (set = 0, binding = 0) + .binding = 0, + .entries = attribute_entries, + .stride = sizeof(vk::vertex_input), + .input_rate = vk::input_rate::vertex, + }, + }; + geometry_resource.vertex_attributes(attributes); + + // Set 0: For Uniform BUffers (or global scene data) + std::vector entries = { + vk::descriptor_entry{ + // specifies "layout (set = 0, binding = 0) uniform GlobalUbo" + .type = vk::buffer::uniform, + .binding_point = { + .binding = 0, + .stage = vk::shader_stage::vertex, + }, + .descriptor_count = 1, + }, + }; + vk::descriptor_layout set0_layout = { + .slot = 0, // indicate specific descriptor slot 0 + .max_sets = image_count, // max descriptors to allocate + .entries = entries, // descriptor layout entries description + }; + vk::descriptor_resource set0_resource(logical_device, set0_layout); + + // Set 1 = For Textures + std::vector entries_set1 = { + vk::descriptor_entry{ + // layout (set = 1, binding = 0) uniform sampler2D + .type = vk::buffer::combined_image_sampler, + .binding_point = { + .binding = 0, + .stage = vk::shader_stage::fragment, + }, + .descriptor_count = 1, + } + }; + vk::descriptor_layout set1_layout = { + .slot = 1, // indicate specific descriptor slot 0 + .max_sets = image_count, // max descriptors to allocate + .entries = entries_set1, // descriptor layout entries description + }; + + vk::descriptor_resource set1_resource(logical_device, set1_layout); + + + std::array layouts = { + set0_resource.layout(), + set1_resource.layout(), + }; + + std::array color_blend_attachments = { + vk::color_blend_attachment_state{}, + }; + + std::array dynamic_states = { + vk::dynamic_state::viewport, vk::dynamic_state::scissor + }; + + uint32_t format = static_cast(surface_properties.format.format); + vk::pipeline_params pipeline_configuration = { + .use_render_pipeline = true, + .color_attachment_formats = std::span(&format, 1), + .depth_format = static_cast(depth_format), + .stencil_format = static_cast(depth_format), + .renderpass = nullptr, + .shader_modules = geometry_resource.handles(), + .vertex_attributes = geometry_resource.vertex_attributes(), + .vertex_bind_attributes = geometry_resource.vertex_bind_attributes(), + .descriptor_layouts = layouts, + .color_blend = { + .attachments = color_blend_attachments, + }, + .depth_stencil_enabled = true, + .dynamic_states = dynamic_states, + }; + vk::pipeline main_graphics_pipeline(logical_device, pipeline_configuration); + + obj_model test_model(std::filesystem::path("asset_samples/viking_room.obj"), + logical_device, + physical_device); + + vk::buffer_parameters uniform_params = { + .memory_mask = + physical_device.memory_properties(static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit)), + .usage = static_cast(vk::buffer_usage::uniform_buffer_bit), + }; + vk::uniform_buffer test_ubo = vk::uniform_buffer( + logical_device, sizeof(global_uniform), uniform_params); + + std::array uniforms0 = { + vk::write_buffer{ + .buffer = test_ubo, + .offset = 0, + .range = static_cast(test_ubo.size_bytes()), + }, + }; + std::array uniforms = { + vk::write_buffer_descriptor{ + .dst_binding = 0, + .uniforms = uniforms0, + }, + }; + + vk::texture_params config_texture = { + .memory_mask = + physical_device.memory_properties(static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit)), + }; + vk::texture texture1(logical_device, + std::filesystem::path("asset_samples/viking_room.png"), + config_texture); + + std::array samplers = { + vk::write_image{ + .sampler = texture1.image().sampler(), + .view = texture1.image().image_view(), + .layout = vk::image_layout::shader_read_only_optimal, + }, + }; + + // Specify image descriptor images/samplers to the descriptor + std::array set1_samples = { + vk::write_image_descriptor{ + .dst_binding = 0, + .sample_images = samplers, + } + }; + set0_resource.update(uniforms); + + set1_resource.update({}, set1_samples); + + VkClearValue clear_color = { + { 0.f, 0.5f, 0.5f, 1.f }, + }; + + VkClearValue depth_value = { + .depthStencil = { .depth = 1.f, .stencil = 0 }, + }; + + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + + uint32_t current_frame = presentation_queue.acquire_next_image(); + vk::command_buffer current = swapchain_command_buffers[current_frame]; + + current.begin(vk::command_usage::simulatneous_use_bit); + + swapchain_images[current_frame].memory_barrier( + current, + surface_properties.format.format, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + + // Because dynamic rendering does not automatically handle layout + // transitions These memory barriers set the color and depth images for + // the output + swapchain_depth_images[current_frame].memory_barrier( + current, + depth_format, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT); + + vk::rendering_attachment color_render_attachment = { + .image_view = swapchain_images[current_frame].image_view(), + .layout = vk::image_layout::color_optimal, + .resolve_mode = vk::resolved_mode_flags::none, + .resolve_image_view = nullptr, + .resolve_image_layout = vk::image_layout::undefined, + .load = vk::attachment_load::clear, + .store = vk::attachment_store::store, + .clear_values = clear_color + }; + + vk::rendering_attachment depth_stencil_attachment = { + .image_view = swapchain_depth_images[current_frame].image_view(), + .layout = vk::image_layout::depth_stencil_optimal, + .resolve_mode = vk::resolved_mode_flags::none, + .resolve_image_view = nullptr, + .resolve_image_layout = vk::image_layout::undefined, + .load = vk::attachment_load::clear, + .store = vk::attachment_store::store, + .depth_values = depth_value + }; + + vk::rendering_begin_parameters begin_params = { + .render_area = { { 0, 0 }, + { + swapchain_extent.width, + swapchain_extent.height, + }, }, + .layer_count = 1, + .color_attachments = std::span( + &color_render_attachment, 1), + .depth_attachment = depth_stencil_attachment, + .stencil_attachment = depth_stencil_attachment, + }; + + vk::viewport_params viewport = { + .x = 0.0f, + .y = 0.0f, + .width = static_cast(swapchain_extent.width), + .height = static_cast(swapchain_extent.height), + .min_depth = 0.0f, + .max_depth = 1.0f, + }; + current.set_viewport( + 0, 1, std::span(&viewport, 1)); + + vk::scissor_params scissor = { + .offset = { 0, 0 }, + .extent = swapchain_extent, + }; + + current.set_scissor( + 0, 1, std::span(&scissor, 1)); + + current.begin_rendering(begin_params); + + main_graphics_pipeline.bind(current); + + const VkBuffer vertex = test_model.vertex_handle(); + uint64_t offset = 0; + current.bind_vertex_buffers(std::span(&vertex, 1), + std::span(&offset, 1)); + + if (test_model.has_indices()) { + current.bind_index_buffers32(test_model.index_handle()); + } + + static auto start_time = std::chrono::high_resolution_clock::now(); + + auto current_time = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration( + current_time - start_time) + .count(); + + // We set the uniforms and then we offload that to the GPU + global_uniform ubo = { + .model = glm::rotate(glm::mat4(1.0f), + time * glm::radians(90.0f), + glm::vec3(0.0f, 0.0f, 1.0f)), + .view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), + glm::vec3(0.0f, 0.0f, 0.0f), + glm::vec3(0.0f, 0.0f, 1.0f)), + .proj = glm::perspective(glm::radians(45.0f), + (float)swapchain_extent.width / + (float)swapchain_extent.height, + 0.1f, + 10.0f) + }; + ubo.proj[1][1] *= -1; + + test_ubo.transfer( + std::span(&ubo, 1)); + + std::array descriptors = { + set0_resource, + set1_resource, + }; + + current.bind_descriptors(main_graphics_pipeline.layout(), + VK_PIPELINE_BIND_POINT_GRAPHICS, + descriptors); + + // Drawing-call to render actual triangle to the screen + // vkCmdDrawIndexed(current, static_cast(indices.size()), 1, + // 0, 0, 0); + test_model.draw(current); + + // vkCmdDraw(current, 3, 1, 0, 0); + + current.end_rendering(); + + swapchain_images[current_frame].memory_barrier( + current, + surface_properties.format.format, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + + current.end(); + + // Submitting and then presenting to the screen + std::array commands = { current }; + presentation_queue.submit_async(commands); + presentation_queue.present_frame(current_frame); + } + + logical_device.wait(); + main_swapchain.destroy(); + + texture1.destroy(); + set0_resource.destroy(); + set1_resource.destroy(); + test_ubo.destroy(); + test_model.destroy(); + + geometry_resource.destroy(); + main_graphics_pipeline.destroy(); + + for (auto& command : swapchain_command_buffers) { + command.destroy(); + } + + for (auto& image : swapchain_images) { + image.destroy(); + } + + for (auto& image : swapchain_depth_images) { + image.destroy(); + } + + presentation_queue.destroy(); + + logical_device.destroy(); + window_surface.destroy(); + glfwDestroyWindow(window); + api_instance.destroy(); + return 0; +} diff --git a/demos/16-descriptor-indexing/conanfile.py b/demos/16-descriptor-indexing/conanfile.py new file mode 100644 index 0000000..eb977f5 --- /dev/null +++ b/demos/16-descriptor-indexing/conanfile.py @@ -0,0 +1,37 @@ +from conan import ConanFile +from conan.tools.cmake import CMake, cmake_layout + +class Demo(ConanFile): + name = "game-demo" + version = "1.0" + settings = "os", "compiler", "build_type", "arch" + generators = "CMakeDeps", "CMakeToolchain" + export_source = "CMakeLists.txt", "application.cpp" + + # Putting all of your build-related dependencies here + def build_requirements(self): + self.tool_requires("cmake/[^4.0.0]") + self.tool_requires("ninja/[^1.3.0]") + self.tool_requires("engine3d-cmake-utils/4.0") + + # Putting all of your packages here + # To build engine3d/1.0 locally do the following: + # conan create . --name=engine3d --version=0.1.0 --user=local --channel=12345 + def requirements(self): + self.requires("glfw/3.4") + self.requires("glm/1.0.1") + self.requires("stb/cci.20230920") + self.requires("tinyobjloader/2.0.0-rc10") + self.requires("vulkan-cpp/6.0") + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def package(self): + cmake = CMake(self) + cmake.install() + + def layout(self): + cmake_layout(self) \ No newline at end of file From 0da90830cbb91469a3bfc03805711ada1322f361 Mon Sep 17 00:00:00 2001 From: SpinnerX Date: Wed, 22 Apr 2026 13:41:10 -0700 Subject: [PATCH 02/11] Added vk::descriptor_bind_flags enum --- vulkan-cpp/types.cppm | 9 +++++++++ vulkan-cpp/utilities.cppm | 7 +++++++ 2 files changed, 16 insertions(+) diff --git a/vulkan-cpp/types.cppm b/vulkan-cpp/types.cppm index 9d7511f..0ed5a00 100644 --- a/vulkan-cpp/types.cppm +++ b/vulkan-cpp/types.cppm @@ -1465,6 +1465,14 @@ export namespace vk { // VK_DESCRIPTOR_SET_LAYOUT_CREATE_FLAG_BITS_MAX_ENUM }; + + enum class descriptor_bind_flags : uint32_t { + update_after_bind = VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT, // represents VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT + update_unused_while_pending = VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT, // represents VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT + partially_bound_bit = VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT, // represents VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT + variable_descriptor_count_bit = VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT, // represents VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT + }; + //! @brief high-level specification for a shader source struct shader_source { std::string filename; @@ -1518,6 +1526,7 @@ export namespace vk { buffer type; descriptor_binding_point binding_point; uint32_t descriptor_count; + descriptor_bind_flags flags; }; struct write_image { diff --git a/vulkan-cpp/utilities.cppm b/vulkan-cpp/utilities.cppm index 5e1a009..b1f4957 100644 --- a/vulkan-cpp/utilities.cppm +++ b/vulkan-cpp/utilities.cppm @@ -140,6 +140,13 @@ export namespace vk { static_cast(p_rhs)); } + inline descriptor_bind_flags operator|(descriptor_bind_flags p_lhs, + descriptor_bind_flags p_rhs) { + using T = std::underlying_type_t; + return static_cast(static_cast(p_lhs) | + static_cast(p_rhs)); + } + /** * @brief GPU memory is optimized differently for different tasks. An * image optimized for 'Transfer Destination' (filling with bytes) can From 96b1a657a251b5e279821f7bb6e0c66946708f83 Mon Sep 17 00:00:00 2001 From: SpinnerX Date: Wed, 22 Apr 2026 14:54:27 -0700 Subject: [PATCH 03/11] WIP getting descriptor indexing parameters supported --- vulkan-cpp/descriptor_resource.cppm | 25 +++++++++++++++++++++---- vulkan-cpp/types.cppm | 4 ++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/vulkan-cpp/descriptor_resource.cppm b/vulkan-cpp/descriptor_resource.cppm index 01e3dcf..7d20559 100644 --- a/vulkan-cpp/descriptor_resource.cppm +++ b/vulkan-cpp/descriptor_resource.cppm @@ -109,7 +109,8 @@ export namespace vk { * */ descriptor_resource(const VkDevice& p_device, - const descriptor_layout& p_info) + const descriptor_layout& p_info, + descriptor_layout_flags p_flags=descriptor_layout_flags::none) : m_device(p_device) , m_slot(p_info.slot) { std::vector pool_sizes( @@ -148,7 +149,7 @@ export namespace vk { VkDescriptorPoolCreateInfo pool_ci = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .pNext = nullptr, - .flags = 0, + .flags = static_cast((p_flags == descriptor_layout_flags::update_after_bind_pool) ? VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT : 0), .maxSets = p_info.max_sets, .poolSizeCount = static_cast(pool_sizes.size()), .pPoolSizes = pool_sizes.data() @@ -158,11 +159,27 @@ export namespace vk { m_device, &pool_ci, nullptr, &m_descriptor_pool), "vkCreateDescriptorPool"); + + // For Descriptor Indexing + // Enable binding flags + + std::vector binding_flags(p_info.entries.size()); + + for(uint32_t i = 0; i < binding_flags.size(); i++) { + binding_flags[i] = static_cast(p_info.entries[i].flags); + } + + VkDescriptorSetLayoutBindingFlagsCreateInfo descriptor_layout_binding_flags = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO, + .bindingCount = static_cast(binding_flags.size()), + .pBindingFlags = binding_flags.data(), + }; + VkDescriptorSetLayoutCreateInfo descriptor_layout_ci = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, - .pNext = nullptr, - .flags = 0, + .pNext = &descriptor_layout_binding_flags, + .flags = static_cast(p_flags), .bindingCount = static_cast(descriptor_layout_bindings.size()), .pBindings = descriptor_layout_bindings.data() diff --git a/vulkan-cpp/types.cppm b/vulkan-cpp/types.cppm index 0ed5a00..42a9679 100644 --- a/vulkan-cpp/types.cppm +++ b/vulkan-cpp/types.cppm @@ -1465,6 +1465,10 @@ export namespace vk { // VK_DESCRIPTOR_SET_LAYOUT_CREATE_FLAG_BITS_MAX_ENUM }; + enum class descriptor_layout_flags : uint32_t { + none = 0x00000000, + update_after_bind_pool = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT, + }; enum class descriptor_bind_flags : uint32_t { update_after_bind = VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT, // represents VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT From 7dbe4460ecd47a45e497bab73dba5336745fc894 Mon Sep 17 00:00:00 2001 From: SpinnerX Date: Wed, 22 Apr 2026 17:05:13 -0700 Subject: [PATCH 04/11] WIP descriptor indexing; added descriptor variable allocate info parameter support --- demos/16-descriptor-indexing/application.cpp | 9 ++++++++- vulkan-cpp/descriptor_resource.cppm | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/demos/16-descriptor-indexing/application.cpp b/demos/16-descriptor-indexing/application.cpp index cb6be6c..1663f59 100644 --- a/demos/16-descriptor-indexing/application.cpp +++ b/demos/16-descriptor-indexing/application.cpp @@ -337,6 +337,7 @@ main() { .descriptorBindingPartiallyBound = true, .runtimeDescriptorArray = true, .descriptorBindingVariableDescriptorCount = true, + .descriptorBindingSampledImageUpdateAfterBind = true, } }, }; @@ -520,15 +521,21 @@ main() { .stage = vk::shader_stage::fragment, }, .descriptor_count = 1, + .flags = vk::descriptor_bind_flags::partially_bound_bit | + vk::descriptor_bind_flags::variable_descriptor_count_bit | + vk::descriptor_bind_flags::update_after_bind, } }; + + uint32_t max_descriptor = 1; vk::descriptor_layout set1_layout = { .slot = 1, // indicate specific descriptor slot 0 .max_sets = image_count, // max descriptors to allocate .entries = entries_set1, // descriptor layout entries description + .descriptor_counts = std::span(&max_descriptor, 1), }; - vk::descriptor_resource set1_resource(logical_device, set1_layout); + vk::descriptor_resource set1_resource(logical_device, set1_layout, vk::descriptor_layout_flags::update_after_bind_pool); std::array layouts = { diff --git a/vulkan-cpp/descriptor_resource.cppm b/vulkan-cpp/descriptor_resource.cppm index 7d20559..62f42db 100644 --- a/vulkan-cpp/descriptor_resource.cppm +++ b/vulkan-cpp/descriptor_resource.cppm @@ -22,6 +22,7 @@ export namespace vk { uint32_t slot = 0; uint32_t max_sets = 0; std::span entries; + std::span descriptor_counts={}; }; /** @@ -190,9 +191,15 @@ export namespace vk { nullptr, &m_descriptor_layout), "vkCreateDescriptorSetLayout"); + + VkDescriptorSetVariableDescriptorCountAllocateInfo descriptor_variable_cound_info = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO, + .descriptorSetCount = static_cast(p_info.descriptor_counts.size()), + .pDescriptorCounts = p_info.descriptor_counts.data(), + }; VkDescriptorSetAllocateInfo descriptor_set_alloc_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, - .pNext = nullptr, + .pNext = (p_info.descriptor_counts.size() == 0) ? nullptr : &descriptor_variable_cound_info, .descriptorPool = m_descriptor_pool, .descriptorSetCount = 1, .pSetLayouts = &m_descriptor_layout From 7fe7fa71ff5b2a12583a569f2263a22c4458f9cc Mon Sep 17 00:00:00 2001 From: SpinnerX Date: Thu, 23 Apr 2026 01:40:22 -0700 Subject: [PATCH 05/11] Added support for specifying parameters for push constants --- vulkan-cpp/pipeline.cppm | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/vulkan-cpp/pipeline.cppm b/vulkan-cpp/pipeline.cppm index ef1c9d6..df4e6cb 100644 --- a/vulkan-cpp/pipeline.cppm +++ b/vulkan-cpp/pipeline.cppm @@ -75,6 +75,13 @@ export namespace vk { bool stencil_test_enable = false; }; + + struct push_constant_range { + shader_stage stage; + uint32_t offset = 0; + uint32_t range = 0; + }; + /** * @param renderpass is required for a VkPipeline to know up front * @param shader_modules is a std::span of the loaded @@ -119,6 +126,8 @@ export namespace vk { bool depth_stencil_enabled = false; depth_stencil_state depth_stencil; std::span dynamic_states = {}; + + std::span push_constants{}; }; /** @@ -353,6 +362,19 @@ export namespace vk { p_params.dynamic_states.data()) }; + + + std::vector push_constants(p_params.push_constants.size()); + + for(uint32_t i = 0; i < push_constants.size(); i++) { + const push_constant_range data = p_params.push_constants[i]; + push_constants[i] = { + .stageFlags = static_cast(data.stage), + .offset = data.offset, + .size = data.range, + }; + } + // Specifies layout of the uniforms (data resources) to be used // by this specified graphics pipeline VkPipelineLayoutCreateInfo pipeline_layout_ci = { @@ -360,6 +382,8 @@ export namespace vk { .setLayoutCount = static_cast(p_params.descriptor_layouts.size()), .pSetLayouts = p_params.descriptor_layouts.data(), + .pushConstantRangeCount = static_cast(push_constants.size()), + .pPushConstantRanges = push_constants.data(), }; vk_check( @@ -511,7 +535,7 @@ export namespace vk { // Perform compile-time checks // Should only accept 128 bytes of data to send over push // constants - static_assert(sizeof(T) == max_size); + static_assert(sizeof(T) != max_size); vkCmdPushConstants(p_current, m_pipeline_layout, From 8404ae746bd9fb641bb33d08d181936748b23b4a Mon Sep 17 00:00:00 2001 From: SpinnerX Date: Thu, 23 Apr 2026 01:41:12 -0700 Subject: [PATCH 06/11] Added fully working demo 16 for demonstrating descriptor indexing --- demos/16-descriptor-indexing/application.cpp | 26 ++++++++++++--- .../sample8-descriptor-indexing/test.frag | 22 +++++++++++++ .../sample8-descriptor-indexing/test.frag.spv | Bin 0 -> 1092 bytes .../sample8-descriptor-indexing/test.vert | 30 ++++++++++++++++++ .../sample8-descriptor-indexing/test.vert.spv | Bin 0 -> 2276 bytes 5 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 shader_samples/sample8-descriptor-indexing/test.frag create mode 100644 shader_samples/sample8-descriptor-indexing/test.frag.spv create mode 100644 shader_samples/sample8-descriptor-indexing/test.vert create mode 100644 shader_samples/sample8-descriptor-indexing/test.vert.spv diff --git a/demos/16-descriptor-indexing/application.cpp b/demos/16-descriptor-indexing/application.cpp index 1663f59..8f4d17b 100644 --- a/demos/16-descriptor-indexing/application.cpp +++ b/demos/16-descriptor-indexing/application.cpp @@ -235,6 +235,10 @@ get_instance_extensions() { return extension_names; } +struct push_constant_data { + uint32_t texture_index=0; +}; + int main() { //! @note Just added the some test code to test the conan-starter setup code @@ -324,8 +328,6 @@ main() { // We provide a selection of format support that we want to check is // supported on current hardware device. - // VkFormat depth_format = - // vk::select_depth_format(physical_device, format_support); VkFormat depth_format = physical_device.request_depth_format(format_support); @@ -338,6 +340,7 @@ main() { .runtimeDescriptorArray = true, .descriptorBindingVariableDescriptorCount = true, .descriptorBindingSampledImageUpdateAfterBind = true, + .shaderSampledImageArrayNonUniformIndexing = true, } }, }; @@ -440,11 +443,11 @@ main() { // Loading graphics pipeline std::array shader_sources = { vk::shader_source{ - .filename = "shader_samples/sample5/test.vert.spv", + .filename = "shader_samples/sample8-descriptor-indexing/test.vert.spv", .stage = vk::shader_stage::vertex, }, vk::shader_source{ - .filename = "shader_samples/sample5/test.frag.spv", + .filename = "shader_samples/sample8-descriptor-indexing/test.frag.spv", .stage = vk::shader_stage::fragment, }, }; @@ -552,6 +555,15 @@ main() { }; uint32_t format = static_cast(surface_properties.format.format); + uint32_t vertex_mask = static_cast(vk::shader_stage::vertex); + uint32_t fragment_mask = static_cast(vk::shader_stage::fragment); + uint32_t stage_mask = vertex_mask | fragment_mask; + vk::shader_stage stage = static_cast(stage_mask); + vk::push_constant_range range = { + .stage = stage, + .offset = 0, + .range = sizeof(push_constant_data), + }; vk::pipeline_params pipeline_configuration = { .use_render_pipeline = true, .color_attachment_formats = std::span(&format, 1), @@ -567,6 +579,7 @@ main() { }, .depth_stencil_enabled = true, .dynamic_states = dynamic_states, + .push_constants = std::span(&range, 1), }; vk::pipeline main_graphics_pipeline(logical_device, pipeline_configuration); @@ -733,6 +746,11 @@ main() { current_time - start_time) .count(); + push_constant_data push = { + .texture_index = 0, + }; + main_graphics_pipeline.push_constant(current, push, stage, 0, sizeof(push_constant_data)); + // We set the uniforms and then we offload that to the GPU global_uniform ubo = { .model = glm::rotate(glm::mat4(1.0f), diff --git a/shader_samples/sample8-descriptor-indexing/test.frag b/shader_samples/sample8-descriptor-indexing/test.frag new file mode 100644 index 0000000..44fad90 --- /dev/null +++ b/shader_samples/sample8-descriptor-indexing/test.frag @@ -0,0 +1,22 @@ +#version 450 + +#extension GL_EXT_nonuniform_qualifier : require + +layout(location = 0) in vec3 fragColor; +layout(location = 1) in vec2 fragTexCoords; +layout(location = 2) in vec3 fragNormals; +layout(location = 3) in flat int fragTexIndex; + +layout(location = 0) out vec4 outColor; + +// layout(set = 1, binding = 0) uniform sampler2D texture_image; +layout(set = 1, binding = 0) uniform sampler2D textures[]; + +void main() { + // outColor = vec4(fragColor, 1.0); + + // Adding texture + // outColor = texture(texture_image, fragTexCoords); + outColor = texture(textures[nonuniformEXT(fragTexIndex)], fragTexCoords); + +} \ No newline at end of file diff --git a/shader_samples/sample8-descriptor-indexing/test.frag.spv b/shader_samples/sample8-descriptor-indexing/test.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..76f3cbe44602f8b909521582ccbbb2af1db7a9c9 GIT binary patch literal 1092 zcmYk4U2D`(5QdN4WUX6kTWecut=-!D;)RMJ3Q|fC7AtH~@4~V|x5&qn`}9+*`#{`e_8 z|8f~dHlJp0UZh#*lE^Mxa;04TDn1yEN2hraoj!l@jLi2;uX=#o8_MEIBi9p`aB)Yn zCE1reksL`j6$3{_%Ic{iV}2`vry7D$RYH;`W#VROb{*c7li1Ci&2&HDKDf9DM(5#l zJ`eNaTWrJFB{qy)W~YVwYL|H$o=c{28FdN1GIeY8jbGK7mc>~brqfldDU+c+YHFocbdKpH;pcTejdh`FWAzU77<%ZhU6J z*^$2?J(Q3O{$^u?!^dZ4?Y%}-9rDx5o;0=5C-D!Z;er#7&%SHSGmmA%r#JYA%^f&= zd}ay1^_egJOZ^YL!`wLJ_#?!Zej}8c?@Oq6-1zJl?44ND&W`CHoEZ9G*Wmcn{YXO1 XpB2MS4kU2E-|AI%1NK){y^{O`QBh@X literal 0 HcmV?d00001 diff --git a/shader_samples/sample8-descriptor-indexing/test.vert b/shader_samples/sample8-descriptor-indexing/test.vert new file mode 100644 index 0000000..a05f65d --- /dev/null +++ b/shader_samples/sample8-descriptor-indexing/test.vert @@ -0,0 +1,30 @@ +#version 450 + +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inColor; +layout(location = 2) in vec2 inTexCoords; +layout(location = 3) in vec3 inNormals; + +layout(location = 0) out vec3 fragColor; +layout(location = 1) out vec2 fragTexCoords; +layout(location = 2) out vec3 fragNormals; +layout(location = 3) out flat int fragTexIndex; + + +layout(push_constant) uniform Constants { + int texture_index; +} push_const; + +layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +} ubo; + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); + fragColor = inColor; + fragTexCoords = inTexCoords; + fragNormals = inNormals; + fragTexIndex = push_const.texture_index; +} \ No newline at end of file diff --git a/shader_samples/sample8-descriptor-indexing/test.vert.spv b/shader_samples/sample8-descriptor-indexing/test.vert.spv new file mode 100644 index 0000000000000000000000000000000000000000..1f72704186b076776ba457fd38b0c0d94f51d557 GIT binary patch literal 2276 zcmZ9N+foxj5QbNh0D_2!98|=Fct#QBARagcoAlcwh%Wi73LG+4O&`0vA zys^sv+uey<4Aso^-+y<{boWB8r`+c_=bS-j$k}j`HRALLafJ28d!@Qtooh#pxy7Y< z7#E#fOoTHmjg^-i?HQl*kfbDeD0w7#B3Y0uNtPwgB{}`{X#cGsd1t_>RBM&(?Mn4s zt=?+Y+R=BoZGg0_iF;0x`G`MfK}jUVlrAIgkRV&l%TZF;8l*0iHNzwUNzR0Uhd^SZWz-bp=J z1;rTA{@FLpAUt~0X*S((d;d`HQ;b4vgLf1(Tu-~jxX-b1zhSc&_h^N|As9JEwRiRd zoeOd8jo*D%jM0l`xOcD_ctIHZG1@VM}(-EW2Bb+JUiK3?DH1WiifR%nz~YQxZGh?Mp!zd~7UzIL;+W7As@W z+EOHVIace9S;e!m|2IqQ*M5>5R@yWOcq(bnb&c z^N`gSQk}fmnE~upr~l~Gk{a@z^Vb0`=d6uh<45gm-)-$iWx-Zb3}SH}*Qr-~Q8J-; z)B>G;jp=v4_M(I}o9gFv<}Fjw2eiWno7Rb5+Zfo8qIegylN0;PROkBzBMN{CAy z^kE5WQabZMT<$fU&I*_na$J_qo0t!Bpwl}r8}mx4!yik}#IW{Bow)b2;n5%vTtctt&#IX8WI_UNIahUtLys)yn$FRvD+y|4K43Zit*+hb)Wv Date: Thu, 23 Apr 2026 02:06:11 -0700 Subject: [PATCH 07/11] Added README for descriptor indexing setup instructions and formatted source code for this demo using clang-format --- demos/16-descriptor-indexing/README.md | 188 +++++++++++++++++++ demos/16-descriptor-indexing/application.cpp | 27 +-- 2 files changed, 204 insertions(+), 11 deletions(-) create mode 100644 demos/16-descriptor-indexing/README.md diff --git a/demos/16-descriptor-indexing/README.md b/demos/16-descriptor-indexing/README.md new file mode 100644 index 0000000..39294d0 --- /dev/null +++ b/demos/16-descriptor-indexing/README.md @@ -0,0 +1,188 @@ +# Demo 16: Descriptor Indexing + + +In this demo, I show how to get descriptor indexing to work properly using vulkan-cpp's wrappers. + + +## Enabling Descriptor Indexing Features + +To enable the descriptor indexing features, you must add onto the `vk::device_features` initialization here, as follows. + +```C++ + +vk::device_features device_features{ + vk::dynamic_rendering_feature{ { + .dynamicRendering = true, + } }, + + // ADD This: Enabling descriptor indexing + vk::descriptor_indexing_feature{ { + .descriptorBindingPartiallyBound = true, + .runtimeDescriptorArray = true, + .descriptorBindingVariableDescriptorCount = true, + .descriptorBindingSampledImageUpdateAfterBind = true, + .shaderSampledImageArrayNonUniformIndexing = true, + } }, +}; +``` + +## Configuring Descriptor Flags + +After enabling the descriptor indexing features. You must set flags to your descriptor binding layouts. In vulkan-cpp, these are referred to as `vk::descriptor_entry`. + +Specifically these flags: +* `vk::descriptor_bind_flags::partially_bound_bit` +* `vk::descriptor_bind_flags::variable_descriptor_count_bit` +* `vk::descriptor_bind_flags::update_after_bind` + +As you can see below, I add these flags onto this specific binding point entry of this specific resource. + +These flags are a way to indicate this specific layout binding may have a max amount of descriptor (resources) that may be utilized at this specific binding point in the shader. + +```C++ +std::vector entries_set1 = { + vk::descriptor_entry{ + // layout (set = 1, binding = 0) uniform sampler2D + .type = vk::buffer::combined_image_sampler, + .binding_point = { + .binding = 0, + .stage = vk::shader_stage::fragment, + }, + .descriptor_count = 1, + .flags = vk::descriptor_bind_flags::partially_bound_bit | + vk::descriptor_bind_flags::variable_descriptor_count_bit | + vk::descriptor_bind_flags::update_after_bind, + } +}; +``` + +## Configuring Descriptor Layout + +After the descriptor entryr has its flags set. You are going to need to set the descriptor layout `.flag` parameters to `vk::descriptor_layout_flags::update_after_bind_pool`. + +This is a third parameter part of the `vk::descriptor_resource` constructor, since this is only ever needed to be set if you plan to use descriptor indexing to keep support for both traditional and modern approaches. + +You also have to set the max descriptors so Vulkan knows how much slots that may be in-used. In this demno, I only specify `1`. Though if you had, lets say 10. You would set max descriptors to `10`. + +Here is how you'd set the flag in the constructor: + +```C++ +uint32_t max_descriptor = 1; +vk::descriptor_layout set1_layout = { + .slot = 1, + .max_sets = image_count, + .entries = entries_set1, + .descriptor_counts = std::span(&max_descriptor, 1), +}; + +vk::descriptor_resource set1_resource(logical_device, set1_layout, vk::descriptor_layout_flags::update_after_bind_pool); +``` + +## Update Descriptors + +In this demo, you can leave the descriptors to be updated the same way it has been previously since we only ever use a single texture. + +Where we update them like this: + +```C++ +vk::texture_params config_texture = { + .memory_mask = + physical_device.memory_properties(static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit)), +}; +vk::texture texture1(logical_device, + std::filesystem::path("asset_samples/viking_room.png"), + config_texture); + +std::array samplers = { + vk::write_image{ + .sampler = texture1.image().sampler(), + .view = texture1.image().image_view(), + .layout = vk::image_layout::shader_read_only_optimal, + }, +}; + +// Specify image descriptor images/samplers to the descriptor +std::array set1_samples = { + vk::write_image_descriptor{ + .dst_binding = 0, + .sample_images = samplers, + } +}; + +set1_resource.update({}, set1_samples); +``` + +## Push Constants + +Then for accessibility to be able to use the textures specifically, we create a push_constant data below to use for our index data to retrieve specific textures. + +```C++ +struct push_constant_data { + uint32_t texture_index=0; +}; +``` + +## Configuring `vk::pipeline` for Push Constant + +Make sure to configur the graphics pipeline to ensure it will handle the push constants correctly. + +Below is a demonstration of the only parameters needed for specifying the parameters for sending over push constants. + +```C++ + +// ADD THIS: Used for specifying which accessibility in shader stages to access push constants data +uint32_t format = static_cast(surface_properties.format.format); +uint32_t vertex_mask = static_cast(vk::shader_stage::vertex); +uint32_t fragment_mask = static_cast(vk::shader_stage::fragment); +uint32_t stage_mask = vertex_mask | fragment_mask; +vk::shader_stage stage = static_cast(stage_mask); + +// ADD THIS +vk::push_constant_range range = { + .stage = stage, + .offset = 0, + .range = sizeof(push_constant_data), +}; +vk::pipeline_params pipeline_configuration = { + .use_render_pipeline = true, + .color_attachment_formats = std::span(&format, 1), + .depth_format = static_cast(depth_format), + .stencil_format = static_cast(depth_format), + .renderpass = nullptr, + .shader_modules = geometry_resource.handles(), + .vertex_attributes = geometry_resource.vertex_attributes(), + .vertex_bind_attributes = geometry_resource.vertex_bind_attributes(), + .descriptor_layouts = layouts, + .color_blend = { + .attachments = color_blend_attachments, + }, + .depth_stencil_enabled = true, + .dynamic_states = dynamic_states, + + // ADD THIS + .push_constants = std::span(&range, 1), +}; +``` + + +## Transferring Push Constant Data + +Now, that we have done the hard part of doing the boilerplate. You can then send over your texture index to access the texture and you should receive the same result when you do + +In here, you will see in the mainloop where, I call `vk::pipeline::push_constant` API to transfer over the push constant data over to the shader. + +```C++ +push_constant_data push = { + .texture_index = 0, +}; +main_graphics_pipeline.push_constant( + current, push, stage, 0, sizeof(push_constant_data)); +``` + + +## Thats it! + +As soon, you transfer over the push constants. You should still see the same viking room demo from demo 12. Where I use that same demo to demonstrate using descriptor indexing to fetch the viking room texture for the 3D mesh. + diff --git a/demos/16-descriptor-indexing/application.cpp b/demos/16-descriptor-indexing/application.cpp index 8f4d17b..3f49d47 100644 --- a/demos/16-descriptor-indexing/application.cpp +++ b/demos/16-descriptor-indexing/application.cpp @@ -236,7 +236,7 @@ get_instance_extensions() { } struct push_constant_data { - uint32_t texture_index=0; + uint32_t texture_index = 0; }; int @@ -336,11 +336,11 @@ main() { .dynamicRendering = true, } }, vk::descriptor_indexing_feature{ { - .descriptorBindingPartiallyBound = true, - .runtimeDescriptorArray = true, - .descriptorBindingVariableDescriptorCount = true, - .descriptorBindingSampledImageUpdateAfterBind = true, - .shaderSampledImageArrayNonUniformIndexing = true, + .descriptorBindingPartiallyBound = true, + .runtimeDescriptorArray = true, + .descriptorBindingVariableDescriptorCount = true, + .descriptorBindingSampledImageUpdateAfterBind = true, + .shaderSampledImageArrayNonUniformIndexing = true, } }, }; @@ -443,11 +443,13 @@ main() { // Loading graphics pipeline std::array shader_sources = { vk::shader_source{ - .filename = "shader_samples/sample8-descriptor-indexing/test.vert.spv", + .filename = + "shader_samples/sample8-descriptor-indexing/test.vert.spv", .stage = vk::shader_stage::vertex, }, vk::shader_source{ - .filename = "shader_samples/sample8-descriptor-indexing/test.frag.spv", + .filename = + "shader_samples/sample8-descriptor-indexing/test.frag.spv", .stage = vk::shader_stage::fragment, }, }; @@ -538,8 +540,10 @@ main() { .descriptor_counts = std::span(&max_descriptor, 1), }; - vk::descriptor_resource set1_resource(logical_device, set1_layout, vk::descriptor_layout_flags::update_after_bind_pool); - + vk::descriptor_resource set1_resource( + logical_device, + set1_layout, + vk::descriptor_layout_flags::update_after_bind_pool); std::array layouts = { set0_resource.layout(), @@ -749,7 +753,8 @@ main() { push_constant_data push = { .texture_index = 0, }; - main_graphics_pipeline.push_constant(current, push, stage, 0, sizeof(push_constant_data)); + main_graphics_pipeline.push_constant( + current, push, stage, 0, sizeof(push_constant_data)); // We set the uniforms and then we offload that to the GPU global_uniform ubo = { From 79224299c1b7b032b90112383a894cbd697c1e61 Mon Sep 17 00:00:00 2001 From: SpinnerX Date: Thu, 23 Apr 2026 02:10:42 -0700 Subject: [PATCH 08/11] More code formatting using clang-format --- vulkan-cpp/descriptor_resource.cppm | 60 ++++++++++++++++++----------- vulkan-cpp/pipeline.cppm | 14 +++---- vulkan-cpp/types.cppm | 19 ++++++--- 3 files changed, 59 insertions(+), 34 deletions(-) diff --git a/vulkan-cpp/descriptor_resource.cppm b/vulkan-cpp/descriptor_resource.cppm index 62f42db..4ccc75a 100644 --- a/vulkan-cpp/descriptor_resource.cppm +++ b/vulkan-cpp/descriptor_resource.cppm @@ -22,7 +22,7 @@ export namespace vk { uint32_t slot = 0; uint32_t max_sets = 0; std::span entries; - std::span descriptor_counts={}; + std::span descriptor_counts = {}; }; /** @@ -109,9 +109,10 @@ export namespace vk { * ``` * */ - descriptor_resource(const VkDevice& p_device, - const descriptor_layout& p_info, - descriptor_layout_flags p_flags=descriptor_layout_flags::none) + descriptor_resource( + const VkDevice& p_device, + const descriptor_layout& p_info, + descriptor_layout_flags p_flags = descriptor_layout_flags::none) : m_device(p_device) , m_slot(p_info.slot) { std::vector pool_sizes( @@ -150,7 +151,11 @@ export namespace vk { VkDescriptorPoolCreateInfo pool_ci = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .pNext = nullptr, - .flags = static_cast((p_flags == descriptor_layout_flags::update_after_bind_pool) ? VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT : 0), + .flags = static_cast( + (p_flags == + descriptor_layout_flags::update_after_bind_pool) + ? VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT + : 0), .maxSets = p_info.max_sets, .poolSizeCount = static_cast(pool_sizes.size()), .pPoolSizes = pool_sizes.data() @@ -160,27 +165,32 @@ export namespace vk { m_device, &pool_ci, nullptr, &m_descriptor_pool), "vkCreateDescriptorPool"); - // For Descriptor Indexing // Enable binding flags - std::vector binding_flags(p_info.entries.size()); - - for(uint32_t i = 0; i < binding_flags.size(); i++) { - binding_flags[i] = static_cast(p_info.entries[i].flags); + std::vector binding_flags( + p_info.entries.size()); + + for (uint32_t i = 0; i < binding_flags.size(); i++) { + binding_flags[i] = static_cast( + p_info.entries[i].flags); } - VkDescriptorSetLayoutBindingFlagsCreateInfo descriptor_layout_binding_flags = { - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO, - .bindingCount = static_cast(binding_flags.size()), - .pBindingFlags = binding_flags.data(), - }; + VkDescriptorSetLayoutBindingFlagsCreateInfo + descriptor_layout_binding_flags = { + .sType = + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO, + .bindingCount = + static_cast(binding_flags.size()), + .pBindingFlags = binding_flags.data(), + }; VkDescriptorSetLayoutCreateInfo descriptor_layout_ci = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .pNext = &descriptor_layout_binding_flags, - .flags = static_cast(p_flags), + .flags = + static_cast(p_flags), .bindingCount = static_cast(descriptor_layout_bindings.size()), .pBindings = descriptor_layout_bindings.data() @@ -192,14 +202,20 @@ export namespace vk { &m_descriptor_layout), "vkCreateDescriptorSetLayout"); - VkDescriptorSetVariableDescriptorCountAllocateInfo descriptor_variable_cound_info = { - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO, - .descriptorSetCount = static_cast(p_info.descriptor_counts.size()), - .pDescriptorCounts = p_info.descriptor_counts.data(), - }; + VkDescriptorSetVariableDescriptorCountAllocateInfo + descriptor_variable_cound_info = { + .sType = + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO, + .descriptorSetCount = + static_cast(p_info.descriptor_counts.size()), + .pDescriptorCounts = p_info.descriptor_counts.data(), + }; + VkDescriptorSetAllocateInfo descriptor_set_alloc_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, - .pNext = (p_info.descriptor_counts.size() == 0) ? nullptr : &descriptor_variable_cound_info, + .pNext = (p_info.descriptor_counts.size() == 0) + ? nullptr + : &descriptor_variable_cound_info, .descriptorPool = m_descriptor_pool, .descriptorSetCount = 1, .pSetLayouts = &m_descriptor_layout diff --git a/vulkan-cpp/pipeline.cppm b/vulkan-cpp/pipeline.cppm index df4e6cb..19b1421 100644 --- a/vulkan-cpp/pipeline.cppm +++ b/vulkan-cpp/pipeline.cppm @@ -75,7 +75,6 @@ export namespace vk { bool stencil_test_enable = false; }; - struct push_constant_range { shader_stage stage; uint32_t offset = 0; @@ -362,14 +361,14 @@ export namespace vk { p_params.dynamic_states.data()) }; + std::vector push_constants( + p_params.push_constants.size()); - - std::vector push_constants(p_params.push_constants.size()); - - for(uint32_t i = 0; i < push_constants.size(); i++) { + for (uint32_t i = 0; i < push_constants.size(); i++) { const push_constant_range data = p_params.push_constants[i]; push_constants[i] = { - .stageFlags = static_cast(data.stage), + .stageFlags = + static_cast(data.stage), .offset = data.offset, .size = data.range, }; @@ -382,7 +381,8 @@ export namespace vk { .setLayoutCount = static_cast(p_params.descriptor_layouts.size()), .pSetLayouts = p_params.descriptor_layouts.data(), - .pushConstantRangeCount = static_cast(push_constants.size()), + .pushConstantRangeCount = + static_cast(push_constants.size()), .pPushConstantRanges = push_constants.data(), }; diff --git a/vulkan-cpp/types.cppm b/vulkan-cpp/types.cppm index 42a9679..efa8e93 100644 --- a/vulkan-cpp/types.cppm +++ b/vulkan-cpp/types.cppm @@ -1467,14 +1467,23 @@ export namespace vk { enum class descriptor_layout_flags : uint32_t { none = 0x00000000, - update_after_bind_pool = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT, + update_after_bind_pool = + VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT, }; enum class descriptor_bind_flags : uint32_t { - update_after_bind = VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT, // represents VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT - update_unused_while_pending = VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT, // represents VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT - partially_bound_bit = VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT, // represents VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT - variable_descriptor_count_bit = VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT, // represents VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT + update_after_bind = + VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT, // represents + // VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT + update_unused_while_pending = + VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT, // represents + // VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT + partially_bound_bit = + VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT, // represents + // VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT + variable_descriptor_count_bit = + VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT, // represents + // VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT }; //! @brief high-level specification for a shader source From e2e5915bed19dbc6f43d54f28dd1639a37bdc688 Mon Sep 17 00:00:00 2001 From: SpinnerX Date: Thu, 23 Apr 2026 12:54:18 -0700 Subject: [PATCH 09/11] Minor fix for static_assert to work properly checking size byte of the push constant data --- vulkan-cpp/pipeline.cppm | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/vulkan-cpp/pipeline.cppm b/vulkan-cpp/pipeline.cppm index 19b1421..0a2cf3b 100644 --- a/vulkan-cpp/pipeline.cppm +++ b/vulkan-cpp/pipeline.cppm @@ -532,10 +532,12 @@ export namespace vk { shader_stage p_stage, uint32_t p_offset, uint32_t p_range) { - // Perform compile-time checks - // Should only accept 128 bytes of data to send over push - // constants - static_assert(sizeof(T) != max_size); + + // Perform compile-time checks if push constant data exceeds + // maximum size of data to be transferred via push constant + static_assert(sizeof(T) <= max_size, + "Type T exceeds max allowed size of bytes for " + "push constants."); vkCmdPushConstants(p_current, m_pipeline_layout, From e4c807d8078a379fe741047c3db3221b47af75aa Mon Sep 17 00:00:00 2001 From: SpinnerX Date: Thu, 23 Apr 2026 14:00:35 -0700 Subject: [PATCH 10/11] Minor comment fix --- demos/16-descriptor-indexing/application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos/16-descriptor-indexing/application.cpp b/demos/16-descriptor-indexing/application.cpp index 3f49d47..6741098 100644 --- a/demos/16-descriptor-indexing/application.cpp +++ b/demos/16-descriptor-indexing/application.cpp @@ -519,7 +519,7 @@ main() { // Set 1 = For Textures std::vector entries_set1 = { vk::descriptor_entry{ - // layout (set = 1, binding = 0) uniform sampler2D + // layout (set = 1, binding = 0) uniform sampler2D textures[]; .type = vk::buffer::combined_image_sampler, .binding_point = { .binding = 0, From cf7832c6d25e421d482d4b0908dd9bad686ede86 Mon Sep 17 00:00:00 2001 From: SpinnerX Date: Thu, 23 Apr 2026 14:12:32 -0700 Subject: [PATCH 11/11] Minor fix of initialization order for device_features --- demos/16-descriptor-indexing/application.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demos/16-descriptor-indexing/application.cpp b/demos/16-descriptor-indexing/application.cpp index 6741098..60b6756 100644 --- a/demos/16-descriptor-indexing/application.cpp +++ b/demos/16-descriptor-indexing/application.cpp @@ -336,11 +336,11 @@ main() { .dynamicRendering = true, } }, vk::descriptor_indexing_feature{ { + .shaderSampledImageArrayNonUniformIndexing = true, + .descriptorBindingSampledImageUpdateAfterBind = true, .descriptorBindingPartiallyBound = true, - .runtimeDescriptorArray = true, .descriptorBindingVariableDescriptorCount = true, - .descriptorBindingSampledImageUpdateAfterBind = true, - .shaderSampledImageArrayNonUniformIndexing = true, + .runtimeDescriptorArray = true, } }, };