From 7aff28fde56549280f34cc0e5bc77351d46bc8c0 Mon Sep 17 00:00:00 2001 From: SpinnerX Date: Thu, 19 Mar 2026 11:12:18 -0700 Subject: [PATCH 1/7] WIP in providing API's for specifying vulkan feature extensions --- vulkan-cpp/feature_extensions.cppm | 188 +++++++++++++++++++++++++++++ vulkan-cpp/vk.cppm | 1 + 2 files changed, 189 insertions(+) create mode 100644 vulkan-cpp/feature_extensions.cppm diff --git a/vulkan-cpp/feature_extensions.cppm b/vulkan-cpp/feature_extensions.cppm new file mode 100644 index 0000000..bd20017 --- /dev/null +++ b/vulkan-cpp/feature_extensions.cppm @@ -0,0 +1,188 @@ +module; + +#include +#include +#include + +export module vk::feature_extensions; + +namespace vk { + /** + * @brief ExtensionConcept being a concept + * + * Used for mainly performing compile-time checks if the features specified + * are actual valid features + * + * Because Vulkan is a C API and .pNext being a void*. This can be bug-prone + * for accidentally pointing to a type that is not a feature struct like a + * std::string, for example. + * + * VulkanChainable concept is used to verify the conditions of the type that + * is being passed to chain_features. + * + * Which is important to ensure that we only accept types that do contain + * .sType and .pNext + * + * TODO: Probably might have this be a template specialization due to being + * able to verify at compile-time if the vulkan features, we need to check + * for are valid. This way even if the struct has `.sType` and `.pNext`, we + * can at least verify the struct is a feature that is valid + * + * + * Example: + * + * ```C++ + * + * // This trait should be false by default + * // Meaning if you reach this, then it should error out + * template + * struct device_feature : public std::false_type { + * // Having this be a compiler-error by default if this struct is + * reached. + * // Users should not be allowed to supply a non vulkan feature struct + * ONLY the vulkan feature struct static_assert(*this, "Invalid vulkan + * feature specified. Only accepting valid vulkan features."); + * }; + * + * + * template<> + * struct device_feature : + * public std::true_type {}; + */ + template + concept ExtensionConcept = requires(T t) { + { t.sType } -> std::convertible_to; + { t.pNext } -> std::convertible_to; + }; + + template + void chain_features(T& head, Args&... tail) { + /** + * @brief Passing an arbitrary sequences of VkPhysicalDevice*Features + * + * + * @brief Handled using Variadic Templates + * Compiler does not generate a loop, rather it'll generate a flat + * sequence of instructions. + * + * Essentially doing something like: chain_features(descriptor_indexing, + * dynamic_rendering). + * + * The compiler will automatically assign the chaining .pNext pointer + * per sequence: (where it will automatically assign the .pNext pointer + * per feature specified in it's sequence) 1.) current.pNext = + * &descriptor_indexing 2.) descriptor_indexing.pNext = + * &dynamic_rendering + * + */ + auto* current = reinterpret_cast(&head); + ((current->pNext = reinterpret_cast(&tail), + current = reinterpret_cast(&tail)), + ...); + current->pNext = nullptr; + } + + template + struct feature_type : public T { + explicit feature_type(T p_intiializer) + : T(p_intiializer) { + this->sType = STYPE; + } + }; + + using descriptor_indexing_feature = feature_type< + VkPhysicalDeviceDescriptorIndexingFeatures, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES>; + using dynamic_rendering = feature_type< + VkPhysicalDeviceDynamicRenderingFeatures, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES>; + + template + struct features { + public: + features(Features&&... p_args) + : m_data(std::forward(p_args)...) { + + // We take our unpacked structure of vulkan features + // Then we chain the pNext pointer altogether for each feature we + // unpack + if constexpr (sizeof...(Features) > 0) { + std::apply([](auto&... spec) { chain_features(spec...); }, + m_data); + } + } + + /** + * @brief We provide the chained pNext ptr + * + * This will let us directly have the chained features the user can + * specify. + * + * @return nullptr if no features are specified, returns the pNext + * feature chain otherwise. + * + */ + [[nodiscard]] void* get_head() noexcept { + if constexpr (sizeof...(Features) > 0) { + return &std::get<0>(m_data); + } + + return nullptr; + } + + private: + std::tuple m_data; + }; + + template + struct feature_type : public T { + explicit feature_type(T p_intiializer) + : T(p_intiializer) { + this->sType = STYPE; + } + }; + + using descriptor_indexing_feature = feature_type< + VkPhysicalDeviceDescriptorIndexingFeatures, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES>; + using dynamic_rendering = feature_type< + VkPhysicalDeviceDynamicRenderingFeatures, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES>; + + template + struct features { + public: + features(Features&&... p_args) + : m_data(std::forward(p_args)...) { + + // We take our unpacked structure of vulkan features + // Then we chain the pNext pointer altogether for each feature we + // unpack + if constexpr (sizeof...(Features) > 0) { + std::apply([](auto&... spec) { chain_features(spec...); }, + m_data); + } + } + + /** + * @brief We provide the chained pNext ptr + * + * This will let us directly have the chained features the user can + * specify. + * + * @return nullptr if no features are specified, returns the pNext + * feature chain otherwise. + * + */ + [[nodiscard]] void* get_head() noexcept { + if constexpr (sizeof...(Features) > 0) { + return &std::get<0>(m_data); + } + + return nullptr; + } + + private: + std::tuple m_data; + }; +}; \ No newline at end of file diff --git a/vulkan-cpp/vk.cppm b/vulkan-cpp/vk.cppm index 08e7609..4262616 100644 --- a/vulkan-cpp/vk.cppm +++ b/vulkan-cpp/vk.cppm @@ -3,6 +3,7 @@ export module vk; export import :types; export import :instance; export import :physical_device; +export import :feature_extensions; export import :device; export import :device_queue; export import :surface; From 80fcd6af601d9ca27985bc127518b75ba6acd27d Mon Sep 17 00:00:00 2001 From: SpinnerX Date: Thu, 19 Mar 2026 11:15:17 -0700 Subject: [PATCH 2/7] Made some API changes for providing internal API's for chaining pNext pointers --- vulkan-cpp/feature_extensions.cppm | 131 ++++++++++++----------------- 1 file changed, 56 insertions(+), 75 deletions(-) diff --git a/vulkan-cpp/feature_extensions.cppm b/vulkan-cpp/feature_extensions.cppm index bd20017..d0e9576 100644 --- a/vulkan-cpp/feature_extensions.cppm +++ b/vulkan-cpp/feature_extensions.cppm @@ -55,32 +55,34 @@ namespace vk { { t.pNext } -> std::convertible_to; }; - template - void chain_features(T& head, Args&... tail) { - /** - * @brief Passing an arbitrary sequences of VkPhysicalDevice*Features - * - * - * @brief Handled using Variadic Templates - * Compiler does not generate a loop, rather it'll generate a flat - * sequence of instructions. - * - * Essentially doing something like: chain_features(descriptor_indexing, - * dynamic_rendering). - * - * The compiler will automatically assign the chaining .pNext pointer - * per sequence: (where it will automatically assign the .pNext pointer - * per feature specified in it's sequence) 1.) current.pNext = - * &descriptor_indexing 2.) descriptor_indexing.pNext = - * &dynamic_rendering - * - */ - auto* current = reinterpret_cast(&head); - ((current->pNext = reinterpret_cast(&tail), - current = reinterpret_cast(&tail)), - ...); - current->pNext = nullptr; - } + // template + // void chain_features(T& head, Args&... tail) { + // /** + // * @brief Passing an arbitrary sequences of VkPhysicalDevice*Features + // * + // * + // * @brief Handled using Variadic Templates + // * Compiler does not generate a loop, rather it'll generate a flat + // * sequence of instructions. + // * + // * Essentially doing something like: + // chain_features(descriptor_indexing, + // * dynamic_rendering). + // * + // * The compiler will automatically assign the chaining .pNext pointer + // * per sequence: (where it will automatically assign the .pNext + // pointer + // * per feature specified in it's sequence) 1.) current.pNext = + // * &descriptor_indexing 2.) descriptor_indexing.pNext = + // * &dynamic_rendering + // * + // */ + // auto* current = reinterpret_cast(&head); + // ((current->pNext = reinterpret_cast(&tail), + // current = reinterpret_cast(&tail)), + // ...); + // current->pNext = nullptr; + // } template struct feature_type : public T { @@ -90,6 +92,7 @@ namespace vk { } }; + // TEMP: Use for testing using descriptor_indexing_feature = feature_type< VkPhysicalDeviceDescriptorIndexingFeatures, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES>; @@ -131,58 +134,36 @@ namespace vk { } private: - std::tuple m_data; - }; - - template - struct feature_type : public T { - explicit feature_type(T p_intiializer) - : T(p_intiializer) { - this->sType = STYPE; - } - }; - - using descriptor_indexing_feature = feature_type< - VkPhysicalDeviceDescriptorIndexingFeatures, - VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES>; - using dynamic_rendering = feature_type< - VkPhysicalDeviceDynamicRenderingFeatures, - VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES>; - - template - struct features { - public: - features(Features&&... p_args) - : m_data(std::forward(p_args)...) { - - // We take our unpacked structure of vulkan features - // Then we chain the pNext pointer altogether for each feature we - // unpack - if constexpr (sizeof...(Features) > 0) { - std::apply([](auto&... spec) { chain_features(spec...); }, - m_data); - } - } - - /** - * @brief We provide the chained pNext ptr - * - * This will let us directly have the chained features the user can - * specify. - * - * @return nullptr if no features are specified, returns the pNext - * feature chain otherwise. - * - */ - [[nodiscard]] void* get_head() noexcept { - if constexpr (sizeof...(Features) > 0) { - return &std::get<0>(m_data); - } - - return nullptr; + template + void chain_pointers(T& head, Features&... tail) { + /** + * @brief Passing an arbitrary sequences of + * VkPhysicalDevice*Features + * + * + * @brief Handled using Variadic Templates + * Compiler does not generate a loop, rather it'll generate a flat + * sequence of instructions. + * + * Essentially doing something like: + * chain_features(descriptor_indexing, dynamic_rendering). + * + * The compiler will automatically assign the chaining .pNext + * pointer per sequence: (where it will automatically assign the + * .pNext pointer per feature specified in it's sequence) 1.) + * current.pNext = &descriptor_indexing 2.) + * descriptor_indexing.pNext = &dynamic_rendering + * + */ + auto* current = reinterpret_cast(&head); + ((current->pNext = reinterpret_cast(&tail), + current = reinterpret_cast(&tail)), + ...); + current->pNext = nullptr; } private: std::tuple m_data; }; + }; \ No newline at end of file From b56813a7b7713d93385dfd3db845b323588c80af Mon Sep 17 00:00:00 2001 From: SpinnerX Date: Thu, 19 Mar 2026 14:15:23 -0700 Subject: [PATCH 3/7] More updates made for additional documentation --- vulkan-cpp/feature_extensions.cppm | 64 ++++++++++++++---------------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/vulkan-cpp/feature_extensions.cppm b/vulkan-cpp/feature_extensions.cppm index d0e9576..6a0fcef 100644 --- a/vulkan-cpp/feature_extensions.cppm +++ b/vulkan-cpp/feature_extensions.cppm @@ -7,6 +7,19 @@ module; export module vk::feature_extensions; namespace vk { + + //! @brief Provide additional compile-time verification checks + template + struct feature_trait : std::false_type {}; + + template<> + struct feature_trait + : std::true_type {}; + + template<> + struct feature_trait + : std::true_type {}; + /** * @brief ExtensionConcept being a concept * @@ -55,35 +68,6 @@ namespace vk { { t.pNext } -> std::convertible_to; }; - // template - // void chain_features(T& head, Args&... tail) { - // /** - // * @brief Passing an arbitrary sequences of VkPhysicalDevice*Features - // * - // * - // * @brief Handled using Variadic Templates - // * Compiler does not generate a loop, rather it'll generate a flat - // * sequence of instructions. - // * - // * Essentially doing something like: - // chain_features(descriptor_indexing, - // * dynamic_rendering). - // * - // * The compiler will automatically assign the chaining .pNext pointer - // * per sequence: (where it will automatically assign the .pNext - // pointer - // * per feature specified in it's sequence) 1.) current.pNext = - // * &descriptor_indexing 2.) descriptor_indexing.pNext = - // * &dynamic_rendering - // * - // */ - // auto* current = reinterpret_cast(&head); - // ((current->pNext = reinterpret_cast(&tail), - // current = reinterpret_cast(&tail)), - // ...); - // current->pNext = nullptr; - // } - template struct feature_type : public T { explicit feature_type(T p_intiializer) @@ -101,16 +85,16 @@ namespace vk { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES>; template - struct features { + struct device_features { public: - features(Features&&... p_args) + device_features(Features&&... p_args) : m_data(std::forward(p_args)...) { // We take our unpacked structure of vulkan features // Then we chain the pNext pointer altogether for each feature we // unpack if constexpr (sizeof...(Features) > 0) { - std::apply([](auto&... spec) { chain_features(spec...); }, + std::apply([](auto&... spec) { internal_data(spec...); }, m_data); } } @@ -124,8 +108,14 @@ namespace vk { * @return nullptr if no features are specified, returns the pNext * feature chain otherwise. * + * @returns the internal data of the vulkan device extensions that are + * chained altogether. + * + * Will return nullptr if no extensions are enabled. */ - [[nodiscard]] void* get_head() noexcept { + [[nodiscard]] void* data() const noexcept { + + // This just ensures the data that if constexpr (sizeof...(Features) > 0) { return &std::get<0>(m_data); } @@ -133,9 +123,15 @@ namespace vk { return nullptr; } + // Deleting copy and move semantics + // This can cause the internal address members to change, which we do + // not want. + device_features(const device_features&) = delete; + device_features& operator=(const device_features&) = delete; + private: template - void chain_pointers(T& head, Features&... tail) { + void internal_data(T& head, Features&... tail) { /** * @brief Passing an arbitrary sequences of * VkPhysicalDevice*Features From a936867de854cee7a5c1b30c0c7a2f5e4de60e33 Mon Sep 17 00:00:00 2001 From: SpinnerX Date: Thu, 19 Mar 2026 17:12:48 -0700 Subject: [PATCH 4/7] Working feature extensions for enabling vulkan-specific features --- vulkan-cpp/device.cppm | 3 +- vulkan-cpp/feature_extensions.cppm | 107 ++++++++++++----------------- vulkan-cpp/types.cppm | 3 + 3 files changed, 49 insertions(+), 64 deletions(-) diff --git a/vulkan-cpp/device.cppm b/vulkan-cpp/device.cppm index 1a3f199..8310692 100644 --- a/vulkan-cpp/device.cppm +++ b/vulkan-cpp/device.cppm @@ -35,7 +35,8 @@ export namespace vk { VkDeviceCreateInfo create_info = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, - .pNext = nullptr, + // .pNext = nullptr, + .pNext = p_config.features, .flags = 0, .queueCreateInfoCount = 1, .pQueueCreateInfos = &device_queue_ci, diff --git a/vulkan-cpp/feature_extensions.cppm b/vulkan-cpp/feature_extensions.cppm index 6a0fcef..130d252 100644 --- a/vulkan-cpp/feature_extensions.cppm +++ b/vulkan-cpp/feature_extensions.cppm @@ -3,22 +3,11 @@ module; #include #include #include +#include -export module vk::feature_extensions; +export module vk:feature_extensions; -namespace vk { - - //! @brief Provide additional compile-time verification checks - template - struct feature_trait : std::false_type {}; - - template<> - struct feature_trait - : std::true_type {}; - - template<> - struct feature_trait - : std::true_type {}; +export namespace vk { /** * @brief ExtensionConcept being a concept @@ -56,11 +45,6 @@ namespace vk { * ONLY the vulkan feature struct static_assert(*this, "Invalid vulkan * feature specified. Only accepting valid vulkan features."); * }; - * - * - * template<> - * struct device_feature : - * public std::true_type {}; */ template concept ExtensionConcept = requires(T t) { @@ -68,22 +52,54 @@ namespace vk { { t.pNext } -> std::convertible_to; }; - template - struct feature_type : public T { - explicit feature_type(T p_intiializer) - : T(p_intiializer) { - this->sType = STYPE; + /** + * @brief a trait that can be aliased to an + */ + template + struct feature_trait : public T { + + feature_trait(T p_initialize_feature) + : T(p_initialize_feature) { + this->sType = SType; } }; - // TEMP: Use for testing - using descriptor_indexing_feature = feature_type< + // These are shorthand aliases that already setup the features + using descriptor_indexing_feature = feature_trait< VkPhysicalDeviceDescriptorIndexingFeatures, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES>; - using dynamic_rendering = feature_type< + using dynamic_rendering = feature_trait< VkPhysicalDeviceDynamicRenderingFeatures, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES>; + template + void internal_data(T& head, Features&... tail) { + /** + * @brief Passing an arbitrary sequences of + * VkPhysicalDevice*Features + * + * + * @brief Handled using Variadic Templates + * Compiler does not generate a loop, rather it'll generate a flat + * sequence of instructions. + * + * Essentially doing something like: + * chain_features(descriptor_indexing, dynamic_rendering). + * + * The compiler will automatically assign the chaining .pNext + * pointer per sequence: (where it will automatically assign the + * .pNext pointer per feature specified in it's sequence) 1.) + * current.pNext = &descriptor_indexing 2.) + * descriptor_indexing.pNext = &dynamic_rendering + * + */ + auto* current = reinterpret_cast(&head); + ((current->pNext = reinterpret_cast(&tail), + current = reinterpret_cast(&tail)), + ...); + current->pNext = nullptr; + } + template struct device_features { public: @@ -113,7 +129,7 @@ namespace vk { * * Will return nullptr if no extensions are enabled. */ - [[nodiscard]] void* data() const noexcept { + [[nodiscard]] void* data() noexcept { // This just ensures the data that if constexpr (sizeof...(Features) > 0) { @@ -123,41 +139,6 @@ namespace vk { return nullptr; } - // Deleting copy and move semantics - // This can cause the internal address members to change, which we do - // not want. - device_features(const device_features&) = delete; - device_features& operator=(const device_features&) = delete; - - private: - template - void internal_data(T& head, Features&... tail) { - /** - * @brief Passing an arbitrary sequences of - * VkPhysicalDevice*Features - * - * - * @brief Handled using Variadic Templates - * Compiler does not generate a loop, rather it'll generate a flat - * sequence of instructions. - * - * Essentially doing something like: - * chain_features(descriptor_indexing, dynamic_rendering). - * - * The compiler will automatically assign the chaining .pNext - * pointer per sequence: (where it will automatically assign the - * .pNext pointer per feature specified in it's sequence) 1.) - * current.pNext = &descriptor_indexing 2.) - * descriptor_indexing.pNext = &dynamic_rendering - * - */ - auto* current = reinterpret_cast(&head); - ((current->pNext = reinterpret_cast(&tail), - current = reinterpret_cast(&tail)), - ...); - current->pNext = nullptr; - } - private: std::tuple m_data; }; diff --git a/vulkan-cpp/types.cppm b/vulkan-cpp/types.cppm index ee37c02..b0bfac2 100644 --- a/vulkan-cpp/types.cppm +++ b/vulkan-cpp/types.cppm @@ -10,6 +10,8 @@ module; export module vk:types; +import :feature_extensions; + export namespace vk { inline namespace v1 { enum format : uint32_t { @@ -536,6 +538,7 @@ export namespace vk { }; struct device_params { + void* features{}; std::span queue_priorities{}; std::span extensions{}; // Can add VK_KHR_SWAPCHAIN_EXTENSION_NAME to this From 6704bc60612462c81f5a2868aa42281c48b3d89a Mon Sep 17 00:00:00 2001 From: SpinnerX Date: Thu, 19 Mar 2026 17:16:07 -0700 Subject: [PATCH 5/7] Updated project cmake --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 843e98b..13afa8c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,7 @@ target_sources(${PROJECT_NAME} PUBLIC TYPE CXX_MODULES FILES vulkan-cpp/vk.cppm + vulkan-cpp/feature_extensions.cppm vulkan-cpp/types.cppm vulkan-cpp/utilities.cppm vulkan-cpp/instance.cppm From 29721af358b136df8a9b974f74c6f9a3b3ccd9d3 Mon Sep 17 00:00:00 2001 From: SpinnerX Date: Mon, 23 Mar 2026 16:31:12 -0700 Subject: [PATCH 6/7] Added additional comments to the feature_extensions and added to demo 12 for testing if features are enabled for specific descriptor indexing feature for validation --- demos/12-loading-models/application.cpp | 15 ++- vulkan-cpp/feature_extensions.cppm | 149 ++++++++++++++++++------ 2 files changed, 126 insertions(+), 38 deletions(-) diff --git a/demos/12-loading-models/application.cpp b/demos/12-loading-models/application.cpp index cccc321..e7ca04c 100644 --- a/demos/12-loading-models/application.cpp +++ b/demos/12-loading-models/application.cpp @@ -314,7 +314,19 @@ main() { std::array extensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; #endif + vk::device_features device_features{ + vk::descriptor_indexing_feature{{ + .descriptorBindingPartiallyBound = true, + .descriptorBindingVariableDescriptorCount = true, + .descriptorBindingSampledImageUpdateAfterBind = true, + } }, + vk::dynamic_rendering_feature{ { + .dynamicRendering = true, + } }, + }; + vk::device_params logical_device_params = { + .features = device_features.data(), .queue_priorities = priorities, .extensions = extensions, .queue_family_index = 0, @@ -701,8 +713,7 @@ main() { }; ubo.proj[1][1] *= -1; - std::array ubo_arr = { ubo }; - test_ubo.transfer(ubo_arr); + test_ubo.transfer(std::span(&ubo, 1)); // Before we can send stuff to the GPU, since we already updated the // descriptor set 0 beforehand, we must bind that descriptor resource diff --git a/vulkan-cpp/feature_extensions.cppm b/vulkan-cpp/feature_extensions.cppm index 130d252..f3999c5 100644 --- a/vulkan-cpp/feature_extensions.cppm +++ b/vulkan-cpp/feature_extensions.cppm @@ -53,8 +53,8 @@ export namespace vk { }; /** - * @brief a trait that can be aliased to an - */ + * @brief a trait that can be aliased to an + */ template struct feature_trait : public T { @@ -64,53 +64,58 @@ export namespace vk { } }; - // These are shorthand aliases that already setup the features + // Descriptor indexing to access unbounded array of a given uniform using descriptor_indexing_feature = feature_trait< VkPhysicalDeviceDescriptorIndexingFeatures, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES>; - using dynamic_rendering = feature_trait< + + //! @brief Modernized Vulkan feature that reduces state managing for + //! renderpasses + framebuffers + using dynamic_rendering_feature = feature_trait< VkPhysicalDeviceDynamicRenderingFeatures, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES>; - template - void internal_data(T& head, Features&... tail) { - /** - * @brief Passing an arbitrary sequences of - * VkPhysicalDevice*Features - * - * - * @brief Handled using Variadic Templates - * Compiler does not generate a loop, rather it'll generate a flat - * sequence of instructions. - * - * Essentially doing something like: - * chain_features(descriptor_indexing, dynamic_rendering). - * - * The compiler will automatically assign the chaining .pNext - * pointer per sequence: (where it will automatically assign the - * .pNext pointer per feature specified in it's sequence) 1.) - * current.pNext = &descriptor_indexing 2.) - * descriptor_indexing.pNext = &dynamic_rendering - * - */ - auto* current = reinterpret_cast(&head); - ((current->pNext = reinterpret_cast(&tail), - current = reinterpret_cast(&tail)), - ...); - current->pNext = nullptr; - } + //! @brief Represent buffers as direct pointer in shaders + // using buffer_device_address_feature = feature_trait< + // VkPhysicalDeviceBufferAddressFeatures, + // VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES>; + + //! @brief Modernize approach for bindless techniques + using descriptor_buffer_feature = feature_trait< + VkPhysicalDeviceDescriptorBufferFeaturesEXT, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_FEATURES_EXT>; + + //! @brief Updating descriptors similar fashion as push constants + using push_descriptors_feature = feature_trait< + VkPhysicalDevicePushDescriptorPropertiesKHR, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR>; + + //! @brief Feature that can skip monolith pipeline objects + using shader_object_feature = feature_trait< + VkPhysicalDeviceShaderObjectFeaturesEXT, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT>; + + //! @brief Handled dynamic pipeline states + using pipeline_library = feature_trait< + VkPhysicalDeviceGraphicsPipelineLibraryFeaturesEXT, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GRAPHICS_PIPELINE_LIBRARY_FEATURES_EXT>; + + //! @brief Clean barriers and semaphores + using sync2_feature = feature_trait< + VkPhysicalDeviceSynchronization2Features, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES>; template - struct device_features { + class device_features { public: device_features(Features&&... p_args) : m_data(std::forward(p_args)...) { - // We take our unpacked structure of vulkan features + // Check unpacked structures of vulkan features // Then we chain the pNext pointer altogether for each feature we - // unpack + // unpack if there are any if constexpr (sizeof...(Features) > 0) { - std::apply([](auto&... spec) { internal_data(spec...); }, + std::apply([this](auto&... spec) { chain_features(spec...); }, m_data); } } @@ -131,7 +136,8 @@ export namespace vk { */ [[nodiscard]] void* data() noexcept { - // This just ensures the data that + // At compile-time we retrieve the head pointer of the entire + // feature list chain altogether. if constexpr (sizeof...(Features) > 0) { return &std::get<0>(m_data); } @@ -139,6 +145,77 @@ export namespace vk { return nullptr; } + private: + /** + * @brief Vulkan uses a linked-list specifying lists of features to + * enable. + * + * This linked-list feature is used to enable modern GPU features. This + * internal API allows to take in a collection of structs that are + * inter-connected them into a single chain the internal API's can read + * from. + * + * The pNext used for chaining + * + * [ Feature A ] [ Feature B ] [ Feature N ] + * +------------+ +------------+ +-----------+ + * | .sType | | .sType | | .sType | + * | .pNext | --> | .pNext | -> | .pNext | + * +------------+ +------------+ +-----------+ + * ( head ) ( internal ) ( tail ) + * + * Example Usage: + * + * ```C++ + * + * vk::device_features device_features{ + * vk::descriptor_indexing_feature{{ + * .descriptorBindingPartiallyBound = true, + * .descriptorBindingVariableDescriptorCount = true, + * .descriptorBindingSampledImageUpdateAfterBind = true, + * } }, + * vk::dynamic_rendering{ { + * .dynamicRendering = true, + * } }, + * }; + * + * vk::device_params config_logical_device = { + * .features = device_features, + * }; + * + * ``` + * + * + */ + template + void chain_features(T& head, Feature&... tail) { + /** + * @brief Passing an arbitrary sequences of + * VkPhysicalDevice*Features + * + * + * @brief Handled using Variadic Templates + * Compiler does not generate a loop, rather it'll generate a flat + * sequence of instructions. + * + * Essentially doing something like: + * chain_features(descriptor_indexing, dynamic_rendering). + * + * The compiler will automatically assign the chaining .pNext + * pointer per sequence: (where it will automatically assign the + * .pNext pointer per feature specified in it's sequence) + * + * 1.) current.pNext = &descriptor_indexing + * 2.) descriptor_indexing.pNext = &dynamic_rendering + * + */ + auto* current = reinterpret_cast(&head); + ((current->pNext = reinterpret_cast(&tail), + current = reinterpret_cast(&tail)), + ...); + current->pNext = nullptr; + } + private: std::tuple m_data; }; From dc830f666b743665a03242fb6e71a8ba2aff0a25 Mon Sep 17 00:00:00 2001 From: SpinnerX Date: Mon, 23 Mar 2026 18:31:27 -0700 Subject: [PATCH 7/7] Formatted demo 12 using clang-format --- demos/12-loading-models/application.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/demos/12-loading-models/application.cpp b/demos/12-loading-models/application.cpp index e7ca04c..c99a1d0 100644 --- a/demos/12-loading-models/application.cpp +++ b/demos/12-loading-models/application.cpp @@ -315,10 +315,10 @@ main() { #endif vk::device_features device_features{ - vk::descriptor_indexing_feature{{ - .descriptorBindingPartiallyBound = true, - .descriptorBindingVariableDescriptorCount = true, - .descriptorBindingSampledImageUpdateAfterBind = true, + vk::descriptor_indexing_feature{ { + .descriptorBindingPartiallyBound = true, + .descriptorBindingVariableDescriptorCount = true, + .descriptorBindingSampledImageUpdateAfterBind = true, } }, vk::dynamic_rendering_feature{ { .dynamicRendering = true, @@ -713,7 +713,8 @@ main() { }; ubo.proj[1][1] *= -1; - test_ubo.transfer(std::span(&ubo, 1)); + test_ubo.transfer( + std::span(&ubo, 1)); // Before we can send stuff to the GPU, since we already updated the // descriptor set 0 beforehand, we must bind that descriptor resource