Handle ownership determination for vulkan.hpp

ObjectUsingVulkan is now a template class which can store vulkan.hpp
handles.
This commit is contained in:
matthias@arch 2022-10-29 16:05:31 +02:00
parent cdae3347a4
commit de544a5b0f
2 changed files with 204 additions and 70 deletions

View File

@ -1,12 +1,10 @@
#include "vulkan_util.hpp"
#include <bits/ranges_base.h>
#include <iostream>
#include <vulkan/vulkan_core.h>
#include <vulkan/vulkan_enums.hpp>
#include <vulkan/vulkan_structs.hpp>
#define VULKAN_HPP_NO_CONSTRUCTORS
#include <vulkan/vulkan.hpp>
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<void*>&& ptrsToHandles, std::vector<void*>&& vectorWithPtrsToHandles) {
objectName = std::move(name);
for (auto it = vectorWithPtrsToHandles.begin(); it != vectorWithPtrsToHandles.end(); it++) {
vectorWithPtrToHandle.emplace_back(reinterpret_cast<std::vector<void*>*>(*it));
}
ptrToHandle = std::move(ptrsToHandles);
updateHandles();
}
void ObjectUsingVulkan::updateHandles() {
handles.clear();
for (auto it = ptrToHandle.begin(); it != ptrToHandle.end(); it++) {
handles.insert(reinterpret_cast<uint64_t>(reinterpret_cast<void*>(*it)));
}
for (auto it = vectorWithPtrToHandle.begin(); it != vectorWithPtrToHandle.end(); it++) {
for (auto it2 = (*it)->begin(); it2 != (*it)->end(); it2++) {
handles.insert(reinterpret_cast<uint64_t>(reinterpret_cast<void*>(*it2)));
}
}
}
} // namespace gz::vlk

View File

@ -2,9 +2,12 @@
#include "vertex.hpp"
#include <chrono>
#include <optional>
#include <set>
#include <type_traits>
#include <unordered_map>
#define VULKAN_HPP_NO_CONSTRUCTORS
#define VULKAN_HPP_NO_EXCEPTIONS
#include <vulkan/vulkan.hpp>
@ -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<vk::ShaderModule> shaderModules;
void operator=(const Pipeline& other) = delete;
};
template<typename T>
concept PipelineKeyType = requires { std::unordered_map<T, Pipeline>(); };
/**
* @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<MyPipelines> 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<PipelineKeyType T>
class PipelineContainer {
public:
using iterator = std::unordered_map<PipelineT, Pipeline>::iterator;
Pipeline& operator[](const PipelineT& key) { return pipelines[key]; }
using iterator = typename std::unordered_map<T, Pipeline>::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<PipelineT, Pipeline> pipelines;
std::unordered_map<T, Pipeline> pipelines;
};
template<PipelineKeyType T>
void PipelineContainer<T>::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<PipelineKeyType T>
typename PipelineContainer<T>::iterator PipelineContainer<T>::erase(const PipelineContainer<T>::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<PipelineKeyType T>
void PipelineContainer<T>::clear(const vk::Device& device, const vk::AllocationCallbacks* pAllocator) {
while (pipelines.size() > 0) {
erase(pipelines.begin(), device);
}
}
struct SwapChainSupport {
vk::SurfaceCapabilities2KHR capabilities;
std::vector<vk::SurfaceFormat2KHR> 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<uint64_t> handles;
};
template<typename T>
concept VulkanHppHandle = requires (const T& t) {
static_cast<typename T::NativeType>(t);
};
template<typename T>
concept PtrToVulkanHppHandle = VulkanHppHandle<std::remove_pointer_t<T>>;
template<typename T>
concept PtrToVulkanHppHandleRange = std::ranges::forward_range<std::remove_pointer_t<T>>
and VulkanHppHandle<std::ranges::range_value_t<std::remove_pointer_t<T>>>;
template<typename T>
concept PtrToVulkanHppHandleConvertible = PtrToVulkanHppHandle<T> || PtrToVulkanHppHandleRange<T>;
static_assert(VulkanHppHandle<vk::RenderPass>);
static_assert(PtrToVulkanHppHandle<vk::RenderPass*>);
static_assert(PtrToVulkanHppHandleRange<std::vector<vk::RenderPass>*>);
/**
* @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<uint64_t>(static_cast<typename VkHppHandle::NativeType>(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<void*>&& ptrsToHandles, std::vector<void*>&& vectorWithPtrsToHandles);
std::string objectName;
std::vector<void*> ptrToHandle;
std::vector<std::vector<void*>*> vectorWithPtrToHandle;
std::set<uint64_t> handles;
/**
* @brief Update the handles map with the current handle (aka segfault generator)
*/
void updateHandles();
template<PtrToVulkanHppHandleConvertible... T>
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<PtrToVulkanHppHandleConvertible PHandleConvertible, PtrToVulkanHppHandleConvertible... PHandleConvertibles>
void addHandlesProxy(const PHandleConvertible&& pHandleConvertible, const PHandleConvertibles&&... pHandleConvertibles);
/// End of recursion
void addHandlesProxy() {}
/// Add reinterpret_cast<uint64_t>(static_cast<PHandle::NativeType>(handle)) to handles set
template<PtrToVulkanHppHandle PHandle>
void addHandles(const PHandle& pHandle);
template<PtrToVulkanHppHandleRange PHandleRange>
void addHandles(const PHandleRange& pHandleRange);
/// Contains pointers to (vulkan.hpp handles or ranges of vulkan.hpp handles)
std::tuple<T...> handlePack;
};
// IMPLEMENTATION
template<PtrToVulkanHppHandleConvertible... T>
ObjectUsingVulkan<T...>::ObjectUsingVulkan(std::string&& name, const T&&... pHandleConvertibles)
: ObjectUsingVulkanBase(std::move(name))
{
handlePack = std::make_tuple<const T...>(std::forward<const T>(pHandleConvertibles)...);
updateHandles();
}
template<PtrToVulkanHppHandleConvertible... T>
void ObjectUsingVulkan<T...>::updateHandles() {
handles.clear();
auto f = [this](auto... args){ this->addHandlesProxy(std::forward<decltype(args)>(args)...); };
std::apply(f, handlePack);
/* std::apply(std::bind(&addHandles, this, std::placeholders::_1), handlePack); */
}
template<PtrToVulkanHppHandleConvertible... T>
template<PtrToVulkanHppHandleConvertible PHandleConvertible, PtrToVulkanHppHandleConvertible... PHandleConvertibles>
void ObjectUsingVulkan<T...>::addHandlesProxy(const PHandleConvertible&& pHandleConvertible, const PHandleConvertibles&&... pHandleConvertibles) {
addHandles(pHandleConvertible);
addHandlesProxy(std::forward<const PHandleConvertibles>(pHandleConvertibles)...);
}
template<PtrToVulkanHppHandleConvertible... T>
template<PtrToVulkanHppHandle PHandle>
void ObjectUsingVulkan<T...>::addHandles(const PHandle& pHandle) {
handles.insert(reinterpret_cast<uint64_t>(static_cast<typename std::remove_pointer_t<PHandle>::NativeType>(*pHandle)));
}
template<PtrToVulkanHppHandleConvertible... T>
template<PtrToVulkanHppHandleRange PHandleRange>
void ObjectUsingVulkan<T...>::addHandles(const PHandleRange& pHandleRange) {
for (auto it = pHandleRange->begin(); it != pHandleRange->end(); it++) {
handles.insert(reinterpret_cast<uint64_t>(
static_cast<typename std::ranges::range_value_t<std::remove_pointer_t<PHandleRange>>::NativeType>(*it)));
}
}
/**