diff --git a/README.md b/README.md index 460fb1d..f784ab4 100644 --- a/README.md +++ b/README.md @@ -75,3 +75,24 @@ Some ideas were also taken from [Zeux's blog](https://zeux.io/2020/02/27/writing - [ ] Create a style to autoformat with - [ ] Move swap chain to its own class - [ ] Fix the hash for checking if vertices are equal. + +### Descriptor Layout Idea + +The DescriptorSet setup could work by creating DescriptorSetLayouts before. +Then validating the created DescriptorSets against a provided layout. +The DescriptorSetLayout could also have a builder +`.addBinding(uint32_t binding, vk::DescriptorType type, vk::ShaderStageFlags stageFlags, uint32t_t count = 1)` +If any binding has > 1 count, then it should be variable length and partially bound. +Only the last binding may have variable length, so this should also be checked. Maybe it should just be a flag? +[VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDescriptorBindingFlagBits.html) + +Using a buffer for the other locations might be a possibility to still add all material properties as bindless. But that is to be looked at. + + +Then the DescriptorBuilder would have +```cpp +DescriptorBuilder &bindBuffer(uint32_t binding, vk::DescriptorBufferInfo *bufferInfo, vk::DescriptorType type); +DescriptorBuilder &bindImage(uint32_t binding, vk::DescriptorImageInfo *imageInfo, vk::DescriptorType type); +DescriptorBuilder &bindImages(uint32_t binding, std::vector& imageInfos, vk::DescriptorType type); +``` +bindImages would also validate that the length is less than the max set in descriptor builder. \ No newline at end of file diff --git a/src/AssetManager.cpp b/src/AssetManager.cpp index 2c65c69..221a388 100644 --- a/src/AssetManager.cpp +++ b/src/AssetManager.cpp @@ -4,7 +4,7 @@ #define STB_IMAGE_IMPLEMENTATION #include -std::shared_ptr AssetManager::getTexture(std::string filename) { +std::shared_ptr AssetManager::getTexture(const std::string& filename) { if (uploadedTextures.find(filename) == uploadedTextures.end()) { auto texture = std::make_shared(); createTextureImage(filename.c_str(), texture); @@ -15,7 +15,7 @@ std::shared_ptr AssetManager::getTexture(std::string filename) return uploadedTextures[filename]; } -void AssetManager::createTextureImage(const char *filename, std::shared_ptr texture) { +void AssetManager::createTextureImage(const char *filename, const std::shared_ptr& texture) { int texWidth, texHeight, texChannels; stbi_uc* pixels = stbi_load(filename, &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); @@ -55,14 +55,14 @@ void AssetManager::createTextureImage(const char *filename, std::shared_ptr texture) { +void AssetManager::createTextureImageView(const std::shared_ptr& texture) { texture->textureImageView = createImageView(device->device(), texture->textureImage._image, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor, texture->mipLevels); deletionQueue.push_function([&, texture]() { device->device().destroyImageView(texture->textureImageView); }); } -void AssetManager::createTextureSampler(std::shared_ptr texture) { +void AssetManager::createTextureSampler(const std::shared_ptr& texture) { auto properties = device->physicalDevice().getProperties(); vk::SamplerCreateInfo samplerInfo{ @@ -120,7 +120,7 @@ AllocatedImage AssetManager::createImage(int width, int height, uint32_t mipLeve .usage = VMA_MEMORY_USAGE_GPU_ONLY}; VkImage image; - VkImageCreateInfo imageInfoCreate = static_cast(imageInfo); // TODO: Add VMA HPP and fix this soup. + auto imageInfoCreate = static_cast(imageInfo); // TODO: Add VMA HPP and fix this soup. if (vmaCreateImage(device->allocator(), &imageInfoCreate, &vmaAllocCreateInfo, &image, &allocatedImage._allocation, nullptr) != VK_SUCCESS) { throw std::runtime_error("Failed to allocate image"); } diff --git a/src/AssetManager.h b/src/AssetManager.h index a868a83..b70f4e5 100644 --- a/src/AssetManager.h +++ b/src/AssetManager.h @@ -28,7 +28,7 @@ class AssetManager { deletionQueue.flush(); } - std::shared_ptr getTexture(std::string filename); + std::shared_ptr getTexture(const std::string& filename); [[nodiscard]] AllocatedImage createImage(int width, int height, uint32_t mipLevels, vk::SampleCountFlagBits numSamples, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags flags); @@ -38,11 +38,11 @@ class AssetManager { DeletionQueue deletionQueue{}; std::map> uploadedTextures; - void createTextureImage(const char* filename, std::shared_ptr texture); + void createTextureImage(const char* filename, const std::shared_ptr& texture); - void createTextureImageView(std::shared_ptr texture); + void createTextureImageView(const std::shared_ptr& texture); - void createTextureSampler(std::shared_ptr texture); + void createTextureSampler(const std::shared_ptr& texture); template [[nodiscard]] AllocatedBuffer stageData(std::span& dataToStage); diff --git a/src/Renderer.cpp b/src/Renderer.cpp index 43e371e..1195ca6 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -39,11 +39,17 @@ Renderer::Renderer(std::shared_ptr window, std::shared_ptrrenderData = renderData; try { + descriptorAllocator.init(device->device()); + descriptorLayoutCache.init(device->device()); + mainDeletionQueue.push_function([&](){ + descriptorLayoutCache.cleanup(); + descriptorAllocator.cleanup(); + }); createSwapChain(); createImageViews(); createRenderPass(); createDescriptorSetLayout(); - createTextureDescriptorSetLayout(); + uploadMeshes(); createGraphicsPipelineLayout(); createGraphicsPipeline(); createCommandPool(); @@ -56,7 +62,6 @@ Renderer::Renderer(std::shared_ptr window, std::shared_ptr& texturePaths) { textures.push_back(assetManager.getTexture(filename)); } - Material material{}; - material.textureSet = createTextureDescriptorSet(textures); + + std::vector imageInfos; + for (const auto& texture : textures) { + vk::DescriptorImageInfo imageInfo { + .sampler = texture->textureSampler, + .imageView = texture->textureImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal, + }; + imageInfos.push_back(imageInfo); + } + + Material material{}; + auto textureResult = DescriptorBuilder::begin(&descriptorLayoutCache, &descriptorAllocator) + .bindImages(0, imageInfos, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment) + .build(material.textureSet, textureDescriptorSetLayout); + + if(!textureResult) { + throw std::runtime_error("Failed to create Material"); + } return material; } @@ -396,40 +418,6 @@ void Renderer::createDescriptorSetLayout() { }); } -void Renderer::createTextureDescriptorSetLayout() { - vk::DescriptorSetLayoutBinding samplerLayoutBinding { - .binding = 0, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .descriptorCount = 256, - .stageFlags = vk::ShaderStageFlagBits::eFragment, - .pImmutableSamplers = nullptr - }; - - std::array bindings = {samplerLayoutBinding}; - - std::array flags = {vk::DescriptorBindingFlagBits::eVariableDescriptorCount | vk::DescriptorBindingFlagBits::ePartiallyBound}; - - vk::DescriptorSetLayoutBindingFlagsCreateInfo bindingFlags { - .bindingCount = static_cast(bindings.size()), - .pBindingFlags = flags.data() - }; - - vk::DescriptorSetLayoutCreateInfo layoutInfo { - .pNext = &bindingFlags, - .bindingCount = static_cast(bindings.size()), - .pBindings = bindings.data() - }; - - if(device->device().createDescriptorSetLayout(&layoutInfo, nullptr, &textureDescriptorSetLayout) != vk::Result::eSuccess) { - throw std::runtime_error("Failed to create texture descriptor set layout"); - } - mainDeletionQueue.push_function([&]() { - device->device().destroyDescriptorSetLayout(textureDescriptorSetLayout); - }); -} - - - void Renderer::createGraphicsPipelineLayout() { vk::PushConstantRange pushConstantRange { .stageFlags = vk::ShaderStageFlagBits::eVertex, @@ -901,50 +889,6 @@ void Renderer::createDescriptorSets() { } } -vk::DescriptorSet Renderer::createTextureDescriptorSet(std::vector>& textures) { - uint32_t counts[1]; - counts[0] = static_cast(textures.size()); - - vk::DescriptorSetVariableDescriptorCountAllocateInfo setCounts = { - .descriptorSetCount = 1, - .pDescriptorCounts = counts - }; - - vk::DescriptorSetAllocateInfo textureAllocInfo { - .pNext = &setCounts, - .descriptorPool = descriptorPool, - .descriptorSetCount = 1, - .pSetLayouts = &textureDescriptorSetLayout - }; - - vk::DescriptorSet textureSet{}; - if(device->device().allocateDescriptorSets(&textureAllocInfo, &textureSet) != vk::Result::eSuccess) { - throw std::runtime_error("Failed to create Texture Descriptor set!"); - } - - std::vector imageInfos; - for (const auto& texture : textures) { - vk::DescriptorImageInfo imageInfo { - .sampler = texture->textureSampler, - .imageView = texture->textureImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal, - }; - imageInfos.push_back(imageInfo); - } - - vk::WriteDescriptorSet imageDescriptor { - .dstSet = textureSet, - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = static_cast(imageInfos.size()), - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = imageInfos.data(), - }; - - device->device().updateDescriptorSets(1, &imageDescriptor, 0, nullptr); - return textureSet; -} - void Renderer::copyBuffer(vk::Buffer srcBuffer, vk::Buffer dstBuffer, vk::DeviceSize size) { device->immediateSubmit([&](vk::CommandBuffer cmd){ vk::BufferCopy copyRegion{ diff --git a/src/Renderer.h b/src/Renderer.h index 2235f3d..9726414 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -18,6 +18,7 @@ #include "RenderData.h" #include "VkTypes.h" #include "VulkanDevice.h" +#include "VkDescriptors.h" const int MAX_FRAMES_IN_FLIGHT = 2; @@ -58,6 +59,9 @@ class Renderer { vk::DescriptorPool descriptorPool; vk::RenderPass renderPass; + DescriptorAllocator descriptorAllocator; + DescriptorLayoutCache descriptorLayoutCache; + vk::PipelineLayout pipelineLayout; vk::Pipeline graphicsPipeline; @@ -125,9 +129,6 @@ class Renderer { template AllocatedBuffer uploadBuffer(std::vector& meshData, VkBufferUsageFlags usage); - void createTextureDescriptorSetLayout(); - vk::DescriptorSet createTextureDescriptorSet(std::vector>& texture); - void transitionImageLayout(vk::Image image, vk::Format format, vk::ImageLayout oldLayout, vk::ImageLayout newLayout, uint32_t mipLevels); void createUniformBuffers(); diff --git a/src/VkDescriptors.cpp b/src/VkDescriptors.cpp new file mode 100644 index 0000000..7687076 --- /dev/null +++ b/src/VkDescriptors.cpp @@ -0,0 +1,340 @@ +#include "VkDescriptors.h" +#include +#include + +// Shamelessly copied from https://github.com/vblanco20-1/vulkan-guide/blob/engine/extra-engine/vk_descriptors.cpp + +vk::DescriptorPool createPool(vk::Device device, const DescriptorAllocator::PoolSizes &poolSizes, int count, + vk::DescriptorPoolCreateFlags flags) { + std::vector sizes; + sizes.reserve(poolSizes.sizes.size()); + for (auto sz: poolSizes.sizes) { + sizes.push_back({sz.first, uint32_t(sz.second * count)}); + } + vk::DescriptorPoolCreateInfo poolInfo = { + .flags = flags, + .maxSets = static_cast(count), + .poolSizeCount = (uint32_t) sizes.size(), + .pPoolSizes = sizes.data() + }; + + return device.createDescriptorPool(poolInfo); +} + +void DescriptorAllocator::resetPools() { + for (auto p: usedPools) { + vkResetDescriptorPool(device, p, 0); + } + + freePools = usedPools; + usedPools.clear(); + currentPool = VK_NULL_HANDLE; +} + +bool DescriptorAllocator::allocate(vk::DescriptorSet *set, vk::DescriptorSetLayout layout, + const std::vector &counts) { + using vk::Result; + + if (!currentPool.has_value()) { + currentPool = grabPool(); + usedPools.push_back(currentPool.value()); + } + + + vk::DescriptorSetAllocateInfo allocInfo = { + .descriptorPool = currentPool.value(), + .descriptorSetCount = 1, + .pSetLayouts = &layout, + }; + + if (!counts.empty()) { + vk::DescriptorSetVariableDescriptorCountAllocateInfo setCounts = { + .descriptorSetCount = 1, + .pDescriptorCounts = counts.data() + }; + + allocInfo.pNext = &setCounts; + } + + auto allocResult = device.allocateDescriptorSets(&allocInfo, set); + bool needReallocate = false; + + switch (allocResult) { + case Result::eSuccess: + //all good, return + return true; + break; + + case Result::eErrorFragmentedPool: + case Result::eErrorOutOfPoolMemory: + //reallocate pool + needReallocate = true; + break; + + default: + //unrecoverable error + return false; + } + + if (needReallocate) { + // Allocate a new pool and retry + currentPool = grabPool(); + usedPools.push_back(currentPool.value()); + allocInfo.descriptorPool = currentPool.value(); + + auto allocRetryResult = device.allocateDescriptorSets(&allocInfo, set); + + // If it still fails then we have big issues + // Probably need to + if (allocRetryResult == Result::eSuccess) { + return true; + } else { + std::cerr << "Tried to allocate descriptorset but failed after creating new pool" << std::endl; + } + } + + return false; +} + +void DescriptorAllocator::init(vk::Device newDevice) { + device = newDevice; +} + +void DescriptorAllocator::cleanup() { + //delete every pool held + for (auto p: freePools) { + vkDestroyDescriptorPool(device, p, nullptr); + } + for (auto p: usedPools) { + vkDestroyDescriptorPool(device, p, nullptr); + } +} + +vk::DescriptorPool DescriptorAllocator::grabPool() { + if (!freePools.empty()) { + vk::DescriptorPool pool = freePools.back(); + freePools.pop_back(); + return pool; + } else { + return createPool(device, descriptorSizes, 1000, vk::DescriptorPoolCreateFlags()); + } +} + + +void DescriptorLayoutCache::init(vk::Device newDevice) { + device = newDevice; +} + +vk::DescriptorSetLayout DescriptorLayoutCache::createDescriptorLayout(vk::DescriptorSetLayoutCreateInfo *info) { + DescriptorLayoutInfo layoutInfo; + layoutInfo.bindings.reserve(info->bindingCount); + bool isSorted = true; + int32_t lastBinding = -1; + for (uint32_t i = 0; i < info->bindingCount; i++) { + layoutInfo.bindings.push_back(info->pBindings[i]); + + //check that the bindings are in strict increasing order + if (static_cast(info->pBindings[i].binding) > lastBinding) { + lastBinding = static_cast(info->pBindings[i].binding); + } else { + isSorted = false; + } + } + if (!isSorted) { + std::sort(layoutInfo.bindings.begin(), layoutInfo.bindings.end(), + [](vk::DescriptorSetLayoutBinding &a, vk::DescriptorSetLayoutBinding &b) { + return a.binding < b.binding; + }); + } + + auto it = layoutCache.find(layoutInfo); + if (it != layoutCache.end()) { + return (*it).second; + } else { + vk::DescriptorSetLayout layout; + auto result = device.createDescriptorSetLayout(info, nullptr, &layout); + + layoutCache[layoutInfo] = layout; + return layout; + } +} + + +void DescriptorLayoutCache::cleanup() { + //delete every descriptor layout held + for (const auto &pair: layoutCache) { + vkDestroyDescriptorSetLayout(device, pair.second, nullptr); + } +} + +DescriptorBuilder DescriptorBuilder::begin(DescriptorLayoutCache *layoutCache, DescriptorAllocator *allocator) { + DescriptorBuilder builder; + + builder.cache = layoutCache; + builder.alloc = allocator; + return builder; +} + + +DescriptorBuilder & +DescriptorBuilder::bindBuffer(uint32_t binding, vk::DescriptorBufferInfo *bufferInfo, vk::DescriptorType type, + vk::ShaderStageFlags stageFlags) { + vk::DescriptorSetLayoutBinding newBinding{ + .binding = binding, + .descriptorType = type, + .descriptorCount = 1, + .stageFlags = stageFlags + }; + + bindings.push_back(newBinding); + + vk::WriteDescriptorSet newWrite{ + .dstBinding = binding, + .descriptorCount = 1, + .descriptorType = type, + .pBufferInfo = bufferInfo, + }; + + writes.push_back(newWrite); + return *this; +} + + +DescriptorBuilder & +DescriptorBuilder::bindImage(uint32_t binding, vk::DescriptorImageInfo *imageInfo, vk::DescriptorType type, + vk::ShaderStageFlags stageFlags) { + vk::DescriptorSetLayoutBinding newBinding{ + .binding = binding, + .descriptorType = type, + .descriptorCount = 1, + .stageFlags = stageFlags + }; + + bindings.push_back(newBinding); + + vk::WriteDescriptorSet newWrite{ + .dstBinding = binding, + .descriptorCount = 1, + .descriptorType = type, + .pImageInfo = imageInfo, + }; + + writes.push_back(newWrite); + return *this; +} + +DescriptorBuilder & +DescriptorBuilder::bindImages(uint32_t binding, std::vector &imageInfos, + vk::DescriptorType type, + vk::ShaderStageFlags stageFlags) { + + // We take the power of two so caching is possible + vk::DescriptorSetLayoutBinding newBinding{ + .binding = binding, + .descriptorType = type, + .descriptorCount = 256, + .stageFlags = stageFlags + }; + + bindings.push_back(newBinding); + + vk::WriteDescriptorSet newWrite{ + .dstBinding = binding, + .descriptorCount = static_cast(imageInfos.size()), + .descriptorType = type, + .pImageInfo = imageInfos.data() + }; + + writes.push_back(newWrite); + return *this; +} + +bool DescriptorBuilder::build(vk::DescriptorSet &set, vk::DescriptorSetLayout &layout) { + //build layout first + vk::DescriptorSetLayoutCreateInfo layoutInfo{ + .bindingCount = static_cast(bindings.size()), + .pBindings = bindings.data() + }; + + bool anyPartialBound = false; + std::vector counts; + for (const auto &binding: bindings) { + if (binding.descriptorCount > 1 && !anyPartialBound) { + std::array flags = {vk::DescriptorBindingFlagBits::eVariableDescriptorCount | + vk::DescriptorBindingFlagBits::ePartiallyBound}; + + vk::DescriptorSetLayoutBindingFlagsCreateInfo bindingFlags{ + .bindingCount = static_cast(bindings.size()), + .pBindingFlags = flags.data() + }; + + layoutInfo.pNext = &bindingFlags; + anyPartialBound = true; + } + counts.push_back(binding.descriptorCount); + } + + layout = cache->createDescriptorLayout(&layoutInfo); + + //allocate descriptor + bool success = anyPartialBound ? alloc->allocate(&set, layout, counts) : alloc->allocate(&set, layout); + if (!success) { return false; } + + //write descriptor + + for (vk::WriteDescriptorSet &w: writes) { + w.dstSet = set; + } + + alloc->device.updateDescriptorSets(static_cast(writes.size()), writes.data(), 0, nullptr); + + return true; +} + +bool DescriptorBuilder::build(vk::DescriptorSet &set) { + vk::DescriptorSetLayout layout; + return build(set, layout); +} + + +bool DescriptorLayoutCache::DescriptorLayoutInfo::operator==(const DescriptorLayoutInfo &other) const { + if (other.bindings.size() != bindings.size()) { + return false; + } else { + //compare each of the bindings is the same. Bindings are sorted so they will match + for (int i = 0; i < bindings.size(); i++) { + if (other.bindings[i].binding != bindings[i].binding) { + return false; + } + if (other.bindings[i].descriptorType != bindings[i].descriptorType) { + return false; + } + if (other.bindings[i].descriptorCount != bindings[i].descriptorCount) { + return false; + } + if (other.bindings[i].stageFlags != bindings[i].stageFlags) { + return false; + } + } + return true; + } +} + +size_t DescriptorLayoutCache::DescriptorLayoutInfo::hash() const { + using std::size_t; + using std::hash; + + size_t result = hash()(bindings.size()); + + for (const vk::DescriptorSetLayoutBinding &b: bindings) { + //pack the binding data into a single int64. Not fully correct but it's ok + size_t binding_hash = b.binding | static_cast(b.descriptorType) << 8 | b.descriptorCount << 16 | + static_cast(b.stageFlags) << 24; + + //shuffle the packed binding data and xor it with the main hash + result ^= hash()(binding_hash); + } + + return result; +} + diff --git a/src/VkDescriptors.h b/src/VkDescriptors.h new file mode 100644 index 0000000..3d0210f --- /dev/null +++ b/src/VkDescriptors.h @@ -0,0 +1,112 @@ +// vulkan_guide.h : Include file for standard system include files, +// or project specific include files. + +#pragma once + +#include "VkTypes.h" +#include +#include +#include +#include + +// Shamelessly copied from https://github.com/vblanco20-1/vulkan-guide/blob/engine/extra-engine/vk_descriptors.h + +class DescriptorAllocator { +public: + + struct PoolSizes { + std::vector> sizes = + { + {vk::DescriptorType::eSampler, 0.5f}, + {vk::DescriptorType::eCombinedImageSampler, 4.0f}, + {vk::DescriptorType::eSampledImage, 4.0f}, + {vk::DescriptorType::eStorageImage, 1.0f}, + {vk::DescriptorType::eUniformTexelBuffer, 1.0f}, + {vk::DescriptorType::eStorageTexelBuffer, 1.0f}, + {vk::DescriptorType::eUniformBuffer, 2.0f}, + {vk::DescriptorType::eStorageBuffer, 2.0f}, + {vk::DescriptorType::eUniformBufferDynamic, 1.0f}, + {vk::DescriptorType::eStorageBufferDynamic, 1.0f}, + {vk::DescriptorType::eInputAttachment, 0.5f} + }; + }; + + void resetPools(); + + bool allocate(vk::DescriptorSet *set, vk::DescriptorSetLayout layout, const std::vector& counts = std::vector()); + + void init(vk::Device newDevice); + + void cleanup(); + + vk::Device device; +private: + vk::DescriptorPool grabPool(); + + std::optional currentPool; + PoolSizes descriptorSizes; + std::vector usedPools; + std::vector freePools; +}; + + +class DescriptorLayoutCache { +public: + void init(vk::Device newDevice); + + void cleanup(); + + vk::DescriptorSetLayout createDescriptorLayout(vk::DescriptorSetLayoutCreateInfo *info); + + struct DescriptorLayoutInfo { + //good idea to turn this into a inlined array + std::vector bindings; + + bool operator==(const DescriptorLayoutInfo &other) const; + + size_t hash() const; + }; + + +private: + + struct DescriptorLayoutHash { + + std::size_t operator()(const DescriptorLayoutInfo &k) const { + return k.hash(); + } + }; + + std::unordered_map layoutCache; + vk::Device device; +}; + + +class DescriptorBuilder { +public: + + static DescriptorBuilder begin(DescriptorLayoutCache *layoutCache, DescriptorAllocator *allocator); + + DescriptorBuilder &bindBuffer(uint32_t binding, vk::DescriptorBufferInfo *bufferInfo, vk::DescriptorType type, + vk::ShaderStageFlags stageFlags); + + DescriptorBuilder &bindImage(uint32_t binding, vk::DescriptorImageInfo *imageInfo, vk::DescriptorType type, + vk::ShaderStageFlags stageFlags); + + DescriptorBuilder &bindImages(uint32_t binding, std::vector& imageInfos, vk::DescriptorType type, + vk::ShaderStageFlags stageFlags); + + bool build(vk::DescriptorSet &set, vk::DescriptorSetLayout &layout); + + bool build(vk::DescriptorSet &set); + +private: + + std::vector writes; + std::vector bindings; + + + DescriptorLayoutCache *cache; + DescriptorAllocator *alloc; +}; + diff --git a/src/VkTypes.h b/src/VkTypes.h index bf24494..57cc38f 100644 --- a/src/VkTypes.h +++ b/src/VkTypes.h @@ -1,5 +1,6 @@ #pragma once +#define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS // Version 145 at least #include #include #include