From de544a5b0f4e435321c89e64711f33bbab485e37 Mon Sep 17 00:00:00 2001 From: "matthias@arch" Date: Sat, 29 Oct 2022 16:05:31 +0200 Subject: [PATCH] Handle ownership determination for vulkan.hpp ObjectUsingVulkan is now a template class which can store vulkan.hpp handles. --- src/utility/vulkan_util.cpp | 47 +------- src/utility/vulkan_util.hpp | 227 ++++++++++++++++++++++++++++++++---- 2 files changed, 204 insertions(+), 70 deletions(-) diff --git a/src/utility/vulkan_util.cpp b/src/utility/vulkan_util.cpp index 8be2a38..b2e412f 100644 --- a/src/utility/vulkan_util.cpp +++ b/src/utility/vulkan_util.cpp @@ -1,12 +1,10 @@ #include "vulkan_util.hpp" +#include #include -#include -#include -#include + #define VULKAN_HPP_NO_CONSTRUCTORS #include - namespace gz::vlk { // // CONVENIENCE @@ -75,22 +73,6 @@ namespace gz::vlk { return true; } - void PipelineContainer::erase(const PipelineT& key, const vk::Device& device, const vk::AllocationCallbacks* pAllocator) { - device.destroyPipeline(pipelines[key].pipeline, pAllocator); - device.destroyPipelineLayout(pipelines[key].layout, pAllocator); - pipelines.erase(pipelines.find(key)); - } - PipelineContainer::iterator PipelineContainer::erase(const PipelineContainer::iterator& it, const vk::Device& device, const vk::AllocationCallbacks* pAllocator) { - device.destroyPipeline(it->second.pipeline, pAllocator); - device.destroyPipelineLayout(it->second.layout, pAllocator); - return pipelines.erase(it); - - } - void PipelineContainer::destroy(const vk::Device& device, const vk::AllocationCallbacks* pAllocator) { - auto it = pipelines.begin(); while (it != pipelines.end()) { - it = erase(it, device); - } - } vk::DependencyInfo getDepInfo(const vk::ImageMemoryBarrier2& barrier) { @@ -118,31 +100,6 @@ namespace gz::vlk { /* } */ -// -// -// - ObjectUsingVulkan::ObjectUsingVulkan(std::string&& name, std::vector&& ptrsToHandles, std::vector&& vectorWithPtrsToHandles) { - objectName = std::move(name); - for (auto it = vectorWithPtrsToHandles.begin(); it != vectorWithPtrsToHandles.end(); it++) { - vectorWithPtrToHandle.emplace_back(reinterpret_cast*>(*it)); - } - ptrToHandle = std::move(ptrsToHandles); - updateHandles(); - } - - void ObjectUsingVulkan::updateHandles() { - handles.clear(); - for (auto it = ptrToHandle.begin(); it != ptrToHandle.end(); it++) { - handles.insert(reinterpret_cast(reinterpret_cast(*it))); - } - for (auto it = vectorWithPtrToHandle.begin(); it != vectorWithPtrToHandle.end(); it++) { - for (auto it2 = (*it)->begin(); it2 != (*it)->end(); it2++) { - handles.insert(reinterpret_cast(reinterpret_cast(*it2))); - } - } - } - - } // namespace gz::vlk diff --git a/src/utility/vulkan_util.hpp b/src/utility/vulkan_util.hpp index 2d6448b..cc4bedd 100644 --- a/src/utility/vulkan_util.hpp +++ b/src/utility/vulkan_util.hpp @@ -2,9 +2,12 @@ #include "vertex.hpp" +#include #include #include +#include #include + #define VULKAN_HPP_NO_CONSTRUCTORS #define VULKAN_HPP_NO_EXCEPTIONS #include @@ -51,42 +54,118 @@ namespace gz::vlk { bool requiredFeaturesAvailable(const PhysicalDeviceFeatures& requiredFeatures) const; }; - enum PipelineT { - PL_3D, PL_2D - }; struct Pipeline { vk::Pipeline pipeline; vk::PipelineLayout layout; + std::vector shaderModules; void operator=(const Pipeline& other) = delete; }; + template + concept PipelineKeyType = requires { std::unordered_map(); }; /** * @brief Map holding pipelines + * @details + * A map holding Pipeline objects. + * I suggest declaring an enum for all pipelines and using this enum as key type. + * This way you can use a pipeline "by its name" without having to make it a member variable + * + * @section Usage + * Using operator[] creates a default initialized Pipeline struct. + * First, create the shader + * Pass this to + * @code + * enum MyPipelines { pl1, pl2, ... }; + * MyClass { + * ... + * PipelineContainer pipelines; + * ... + * }; + * + * MyClass::createPipeline() { + * // operator[] default initalizes a Pipeline struct + * // see text below for explanation + * if (pipelines[pl1].shaderModules.size() == 0) { + * // will create shader modules in pipelines[pl1].shaderModules + * // TODO + * VulkanInstance::createShaderModules({"shader.vert.spv", }, ..., pipelines[pl1]); + * } + * // create pipeline and layout in pipelines[pl1].layout, pipelines[pl1].pipeline + * VulkanInstance::createGraphicsPipeline(..., pipelines[pl1]) + * } + * + * MyClass::doStuff() { + * ... + * cmdBuffer.bindPipeline(pipelines[pl1].pipeline); + * ... + * } + * + * MyClass::swapChainRecreateCallback() { + * device.destroyPipeline(pipelines[pl1].pipeline); + * device.destroyPipelineLayout(pipelines[pl1].layout); + * createPipeline(); + * } + * + * MyClass::cleanup() { + * pipelines.clear(device); + * } + * + * + * + * @endcode + * + * The reason why the shader modules are stored together with the pipelines + * (for now, maybe this will change when I add pipeline caching) + * is that it can improve pipeline recreation times (eg. when window size changes). + * If the shader modules stay valid (meaning that the specialization values stay the same) + * you can just reuse them. + * */ + template class PipelineContainer { public: - using iterator = std::unordered_map::iterator; - Pipeline& operator[](const PipelineT& key) { return pipelines[key]; } + using iterator = typename std::unordered_map::iterator; + Pipeline& operator[](const T& key) { return pipelines[key]; } /** - * @brief Destroy the pipeline+layout and then remove the handles from the underlying map + * @brief Destroy the pipeline, layout and shader modules and then remove the handles from the underlying map */ - void erase(const PipelineT& key, const vk::Device& device, const vk::AllocationCallbacks* pAllocator=nullptr); + void erase(const T& key, const vk::Device& device, const vk::AllocationCallbacks* pAllocator=nullptr); /** - * @brief Destroy the pipeline+layout and then remove the handles from the underlying map + * @brief Destroy the pipeline, layout and shader modules and then remove the handles from the underlying map */ iterator erase(const iterator& it, const vk::Device& device, const vk::AllocationCallbacks* pAllocator=nullptr); /** - * @brief Destroy all pipelines#layouts and then remove the handles from the underlying map + * @brief Destroy all pipelines, layouts and shader modules and then remove the handles from the underlying map */ - void destroy(const vk::Device& device, const vk::AllocationCallbacks* pAllocator=nullptr); + void clear(const vk::Device& device, const vk::AllocationCallbacks* pAllocator=nullptr); iterator begin() { return pipelines.begin(); } iterator end() { return pipelines.end(); } size_t size() const { return pipelines.size(); } private: - std::unordered_map pipelines; - + std::unordered_map pipelines; }; + template + void PipelineContainer::erase(const T& key, const vk::Device& device, const vk::AllocationCallbacks* pAllocator) { + device.destroyPipeline(pipelines[key].pipeline, pAllocator); + device.destroyPipelineLayout(pipelines[key].layout, pAllocator); + pipelines.erase(pipelines.find(key)); + } + + template + typename PipelineContainer::iterator PipelineContainer::erase(const PipelineContainer::iterator& it, const vk::Device& device, const vk::AllocationCallbacks* pAllocator) { + device.destroyPipeline(it->second.pipeline, pAllocator); + device.destroyPipelineLayout(it->second.layout, pAllocator); + return pipelines.erase(it); + + } + template + void PipelineContainer::clear(const vk::Device& device, const vk::AllocationCallbacks* pAllocator) { + while (pipelines.size() > 0) { + erase(pipelines.begin(), device); + } + } + struct SwapChainSupport { vk::SurfaceCapabilities2KHR capabilities; std::vector formats; @@ -160,31 +239,129 @@ namespace gz::vlk { /* void getImageMemoryRequirements(const vk::Device& device, const vk::Image& image, vk::ImageMemoryRequirementsInfo2& imMemReq, vk::MemoryRequirements2& memReq); */ /// @} + +// +// OBJECT USING VULKAN +// + /** + * @brief Base class for ObjectUsingVulkan for storage of different ObjectUsingVulkan pointers in one container + */ + class ObjectUsingVulkanBase { + public: + ObjectUsingVulkanBase(std::string&& name) : objectName(name) {}; + virtual ~ObjectUsingVulkanBase() {}; + virtual void updateHandles() {}; + bool contains(const uint64_t& handle) const { return handles.contains(handle); }; + const std::string& getName() const { return objectName; }; + protected: + std::string objectName; + std::set handles; + }; + template + concept VulkanHppHandle = requires (const T& t) { + static_cast(t); + }; + template + concept PtrToVulkanHppHandle = VulkanHppHandle>; + + template + concept PtrToVulkanHppHandleRange = std::ranges::forward_range> + and VulkanHppHandle>>; + template + concept PtrToVulkanHppHandleConvertible = PtrToVulkanHppHandle || PtrToVulkanHppHandleRange; + static_assert(VulkanHppHandle); + static_assert(PtrToVulkanHppHandle); + static_assert(PtrToVulkanHppHandleRange*>); + /** * @brief Used for debugging * @see VulkanInstance::debugLog * @details - * Handles like vk::Device are pointers to the actual handle. * Register the handle to a vulkan object with this struct and call updateHandles(). * The handles set will then contain the actual address of the handle, as printed by the validation layers. * That means you can check a handle returned by a validation layer - * with this struct and see if this handle belongs to object that created this struct. + * with objects of this class using ObjectUsingVulkanBase::contains() and determine the owner of the handle. + * + * The pointers to the vulkan.hpp handles are stored in the handlePack tuple + * (these cpp structs contain the actual vulkan handle). + * + * This way, if a vulkan handle changes (eg when recreating an image) the new vulkan handle can be determined by "reading" the vulkan.hpp handle. + * + * The address of the vulkan handle (as returned by validation layers) is determined by casting the vulkan.hpp handle to the corresponding vulkan handle and casting that to uint64_t: + * @code + * // example, where vkHppHandle is handle of type VkHppHandle from vulkan.hpp (eg vk::Image) + * uint64_t address = reinterpret_cast(static_cast(vkHppHandle)); + * @endcode + * + * + * * @warning * Every pointer you submit to this must stay valid! * Use this struct only when debugging! */ - struct ObjectUsingVulkan { - ObjectUsingVulkan(std::string&& name, std::vector&& ptrsToHandles, std::vector&& vectorWithPtrsToHandles); - std::string objectName; - std::vector ptrToHandle; - std::vector*> vectorWithPtrToHandle; - std::set handles; - /** - * @brief Update the handles map with the current handle (aka segfault generator) - */ - void updateHandles(); - + template + class ObjectUsingVulkan : public ObjectUsingVulkanBase { + public: + ObjectUsingVulkan(std::string&& name, const T&&... pHandleConvertibles); + /** + * @brief Update the handles map with the current handle (aka segfault generator) + */ + virtual void updateHandles() override; + private: + /// Recursively call addHandles for every member of the pack + template + void addHandlesProxy(const PHandleConvertible&& pHandleConvertible, const PHandleConvertibles&&... pHandleConvertibles); + /// End of recursion + void addHandlesProxy() {} + /// Add reinterpret_cast(static_cast(handle)) to handles set + template + void addHandles(const PHandle& pHandle); + template + void addHandles(const PHandleRange& pHandleRange); + + /// Contains pointers to (vulkan.hpp handles or ranges of vulkan.hpp handles) + std::tuple handlePack; }; + // IMPLEMENTATION + template + ObjectUsingVulkan::ObjectUsingVulkan(std::string&& name, const T&&... pHandleConvertibles) + : ObjectUsingVulkanBase(std::move(name)) + { + handlePack = std::make_tuple(std::forward(pHandleConvertibles)...); + updateHandles(); + } + + template + void ObjectUsingVulkan::updateHandles() { + handles.clear(); + auto f = [this](auto... args){ this->addHandlesProxy(std::forward(args)...); }; + std::apply(f, handlePack); + /* std::apply(std::bind(&addHandles, this, std::placeholders::_1), handlePack); */ + } + + template + template + void ObjectUsingVulkan::addHandlesProxy(const PHandleConvertible&& pHandleConvertible, const PHandleConvertibles&&... pHandleConvertibles) { + addHandles(pHandleConvertible); + addHandlesProxy(std::forward(pHandleConvertibles)...); + } + + template + template + void ObjectUsingVulkan::addHandles(const PHandle& pHandle) { + handles.insert(reinterpret_cast(static_cast::NativeType>(*pHandle))); + } + + template + template + void ObjectUsingVulkan::addHandles(const PHandleRange& pHandleRange) { + for (auto it = pHandleRange->begin(); it != pHandleRange->end(); it++) { + handles.insert(reinterpret_cast( + static_cast>::NativeType>(*it))); + } + } + + /**