Handle ownership determination for vulkan.hpp
ObjectUsingVulkan is now a template class which can store vulkan.hpp handles.
This commit is contained in:
parent
cdae3347a4
commit
de544a5b0f
@ -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
|
||||
|
||||
|
||||
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user