Fix descriptor pool fragmentation (#2949)

This commit is contained in:
Adrian Cojocaru
2024-10-22 18:45:02 +03:00
committed by GitHub
parent c098a12c37
commit 7ff0c71312
6 changed files with 160 additions and 92 deletions

View File

@@ -12,6 +12,7 @@
#include <mbgl/vulkan/uniform_buffer.hpp>
#include <mbgl/vulkan/renderer_backend.hpp>
#include <mbgl/vulkan/pipeline.hpp>
#include <mbgl/vulkan/descriptor_set.hpp>
#include <memory>
#include <optional>
@@ -135,21 +136,14 @@ public:
const std::unique_ptr<BufferResource>& getDummyVertexBuffer();
const std::unique_ptr<BufferResource>& getDummyUniformBuffer();
const std::unique_ptr<Texture2D>& getDummyTexture();
const vk::UniqueDescriptorSetLayout& getImageDescriptorSetLayout();
const vk::UniqueDescriptorSetLayout& buildUniformDescriptorSetLayout(vk::UniqueDescriptorSetLayout& layout,
size_t uniformCount,
const std::string& name);
const std::vector<vk::DescriptorSetLayout>& getDescriptorSetLayouts();
const vk::DescriptorSetLayout& getDescriptorSetLayout(DescriptorSetType type);
DescriptorPoolGrowable& getDescriptorPool(DescriptorSetType type);
const vk::UniquePipelineLayout& getGeneralPipelineLayout();
const vk::UniquePipelineLayout& getPushConstantPipelineLayout();
void bindDescriptorSet(DescriptorSetType type, const vk::DescriptorSet& descriptorSet);
uint8_t getCurrentFrameResourceIndex() const { return frameResourceIndex; }
const vk::UniqueDescriptorPool& getGlobalDescriptorPool() const;
void enqueueDeletion(std::function<void(const Context&)>&& function);
void enqueueDeletion(std::function<void(Context&)>&& function);
void submitOneTimeCommand(const std::function<void(const vk::UniqueCommandBuffer&)>& function) const;
void requestSurfaceUpdate() { surfaceUpdateRequested = true; }
@@ -162,7 +156,7 @@ private:
vk::UniqueSemaphore frameSemaphore;
vk::UniqueFence flightFrameFence;
std::vector<std::function<void(const Context&)>> deletionQueue;
std::vector<std::function<void(Context&)>> deletionQueue;
FrameResources(vk::UniqueCommandBuffer& cb,
vk::UniqueSemaphore&& surf,
@@ -173,17 +167,22 @@ private:
frameSemaphore(std::move(frame)),
flightFrameFence(std::move(flight)) {}
void runDeletionQueue(const Context&);
void runDeletionQueue(Context&);
};
void initFrameResources();
void destroyResources();
void buildImageDescriptorSetLayout();
void buildUniformDescriptorSetLayout(vk::UniqueDescriptorSetLayout& layout,
size_t uniformCount,
const std::string& name);
private:
RendererBackend& backend;
vulkan::UniformBufferArray globalUniformBuffers;
vk::UniqueDescriptorPool globalDescriptorPool;
std::unordered_map<DescriptorSetType, DescriptorPoolGrowable> descriptorPoolMap;
std::unique_ptr<BufferResource> dummyVertexBuffer;
std::unique_ptr<BufferResource> dummyUniformBuffer;
@@ -191,8 +190,7 @@ private:
vk::UniqueDescriptorSetLayout globalUniformDescriptorSetLayout;
vk::UniqueDescriptorSetLayout layerUniformDescriptorSetLayout;
vk::UniqueDescriptorSetLayout drawableUniformDescriptorSetLayout;
vk::UniqueDescriptorSetLayout imageDescriptorSetLayout;
std::vector<vk::DescriptorSetLayout> descriptorSetLayouts;
vk::UniqueDescriptorSetLayout drawableImageDescriptorSetLayout;
vk::UniquePipelineLayout generalPipelineLayout;
vk::UniquePipelineLayout pushConstantPipelineLayout;

View File

@@ -17,6 +17,32 @@ enum class DescriptorSetType : uint8_t {
Count,
};
struct DescriptorPoolGrowable {
struct PoolInfo {
vk::UniqueDescriptorPool pool;
uint32_t remainingSets{0};
PoolInfo(vk::UniqueDescriptorPool&& pool_, uint32_t remainingSets_)
: pool(std::move(pool_)),
remainingSets(remainingSets_) {}
};
const uint32_t maxSets{0};
const uint32_t descriptorsPerSet{0};
const float growFactor{1.5f};
std::vector<PoolInfo> pools;
int32_t currentPoolIndex{-1};
PoolInfo& current() { return pools[currentPoolIndex]; }
DescriptorPoolGrowable() = default;
DescriptorPoolGrowable(uint32_t maxSets_, uint32_t descriptorsPerSet_, float growFactor_ = 1.5f)
: maxSets(maxSets_),
descriptorsPerSet(descriptorsPerSet_),
growFactor(growFactor_) {}
};
class DescriptorSet {
public:
DescriptorSet(Context& context_, DescriptorSetType type_);
@@ -27,12 +53,16 @@ public:
void markDirty(bool value = true);
void bind(CommandEncoder& encoder);
protected:
void createDescriptorPool(DescriptorPoolGrowable& growablePool);
protected:
Context& context;
DescriptorSetType type;
std::vector<bool> dirty;
std::vector<vk::DescriptorSet> descriptorSets;
int32_t descriptorPoolIndex{-1};
};
class UniformDescriptorSet : public DescriptorSet {

View File

@@ -118,7 +118,7 @@ BufferResource::~BufferResource() noexcept {
if (!bufferAllocation) return;
context.enqueueDeletion([allocation = std::move(bufferAllocation)](const auto&) mutable { allocation.reset(); });
context.enqueueDeletion([allocation = std::move(bufferAllocation)](auto&) mutable { allocation.reset(); });
}
BufferResource BufferResource::clone() const {

View File

@@ -27,10 +27,6 @@
#include <algorithm>
#include <cstring>
#ifndef MLN_VULKAN_DESCRIPTOR_POOL_SIZE
#define MLN_VULKAN_DESCRIPTOR_POOL_SIZE 30000
#endif
namespace mbgl {
namespace vulkan {
@@ -39,6 +35,12 @@ namespace vulkan {
// per https://vulkan.gpuinfo.org/displaydevicelimit.php?name=maxVertexInputBindings
// this can be queried at runtime (VkPhysicalDeviceLimits.maxVertexInputBindings)
constexpr uint32_t maximumVertexBindingCount = 16;
constexpr uint32_t globalDescriptorPoolSize = 3 * 4;
constexpr uint32_t layerDescriptorPoolSize = 3 * 256;
constexpr uint32_t drawableUniformDescriptorPoolSize = 3 * 1024;
constexpr uint32_t drawableImageDescriptorPoolSize = drawableUniformDescriptorPoolSize / 2;
static uint32_t glslangRefCount = 0;
class RenderbufferResource : public gfx::RenderbufferResource {
@@ -71,17 +73,19 @@ void Context::initFrameResources() {
const auto& device = backend.getDevice();
const auto frameCount = backend.getMaxFrames();
// descriptor pool info
const std::vector<vk::DescriptorPoolSize> poolSizes = {
{vk::DescriptorType::eUniformBuffer, MLN_VULKAN_DESCRIPTOR_POOL_SIZE},
{vk::DescriptorType::eCombinedImageSampler, MLN_VULKAN_DESCRIPTOR_POOL_SIZE},
};
descriptorPoolMap.emplace(DescriptorSetType::Global,
DescriptorPoolGrowable(globalDescriptorPoolSize, shaders::globalUBOCount));
const auto descriptorPoolInfo = vk::DescriptorPoolCreateInfo(vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet)
.setPoolSizes(poolSizes)
.setMaxSets(MLN_VULKAN_DESCRIPTOR_POOL_SIZE);
descriptorPoolMap.emplace(DescriptorSetType::Layer,
DescriptorPoolGrowable(layerDescriptorPoolSize, shaders::maxUBOCountPerLayer));
globalDescriptorPool = device->createDescriptorPoolUnique(descriptorPoolInfo);
descriptorPoolMap.emplace(
DescriptorSetType::DrawableUniform,
DescriptorPoolGrowable(drawableUniformDescriptorPoolSize, shaders::maxUBOCountPerDrawable));
descriptorPoolMap.emplace(
DescriptorSetType::DrawableImage,
DescriptorPoolGrowable(drawableImageDescriptorPoolSize, shaders::maxTextureCountPerShader));
// command buffers
const vk::CommandBufferAllocateInfo allocateInfo(
@@ -107,6 +111,14 @@ void Context::initFrameResources() {
// force placeholder texture upload before any descriptor sets
(void)getDummyTexture();
buildUniformDescriptorSetLayout(
globalUniformDescriptorSetLayout, shaders::globalUBOCount, "GlobalUniformDescriptorSetLayout");
buildUniformDescriptorSetLayout(
layerUniformDescriptorSetLayout, shaders::maxUBOCountPerLayer, "LayerUniformDescriptorSetLayout");
buildUniformDescriptorSetLayout(
drawableUniformDescriptorSetLayout, shaders::maxUBOCountPerDrawable, "DrawableUniformDescriptorSetLayout");
buildImageDescriptorSetLayout();
}
void Context::destroyResources() {
@@ -122,11 +134,7 @@ void Context::destroyResources() {
frameResources.clear();
}
const vk::UniqueDescriptorPool& Context::getGlobalDescriptorPool() const {
return globalDescriptorPool;
}
void Context::enqueueDeletion(std::function<void(const Context&)>&& function) {
void Context::enqueueDeletion(std::function<void(Context&)>&& function) {
if (frameResources.empty()) {
function(*this);
return;
@@ -522,13 +530,9 @@ const std::unique_ptr<Texture2D>& Context::getDummyTexture() {
return dummyTexture2D;
}
const vk::UniqueDescriptorSetLayout& Context::buildUniformDescriptorSetLayout(vk::UniqueDescriptorSetLayout& layout,
size_t uniformCount,
const std::string& name) {
if (layout) {
return layout;
}
void Context::buildUniformDescriptorSetLayout(vk::UniqueDescriptorSetLayout& layout,
size_t uniformCount,
const std::string& name) {
std::vector<vk::DescriptorSetLayoutBinding> bindings;
const auto stageFlags = vk::ShaderStageFlags() | vk::ShaderStageFlagBits::eVertex |
vk::ShaderStageFlagBits::eFragment;
@@ -544,62 +548,63 @@ const vk::UniqueDescriptorSetLayout& Context::buildUniformDescriptorSetLayout(vk
const auto descriptorSetLayoutCreateInfo = vk::DescriptorSetLayoutCreateInfo().setBindings(bindings);
layout = backend.getDevice()->createDescriptorSetLayoutUnique(descriptorSetLayoutCreateInfo);
backend.setDebugName(layout.get(), name);
return layout;
}
const vk::UniqueDescriptorSetLayout& Context::getImageDescriptorSetLayout() {
if (!imageDescriptorSetLayout) {
std::vector<vk::DescriptorSetLayoutBinding> bindings;
void Context::buildImageDescriptorSetLayout() {
std::vector<vk::DescriptorSetLayoutBinding> bindings;
for (size_t i = 0; i < shaders::maxTextureCountPerShader; ++i) {
bindings.push_back(vk::DescriptorSetLayoutBinding()
.setBinding(static_cast<uint32_t>(i))
.setStageFlags(vk::ShaderStageFlagBits::eFragment)
.setDescriptorType(vk::DescriptorType::eCombinedImageSampler)
.setDescriptorCount(1));
}
const auto descriptorSetLayoutCreateInfo = vk::DescriptorSetLayoutCreateInfo().setBindings(bindings);
imageDescriptorSetLayout = backend.getDevice()->createDescriptorSetLayoutUnique(descriptorSetLayoutCreateInfo);
backend.setDebugName(imageDescriptorSetLayout.get(), "ImageDescriptorSetLayout");
for (size_t i = 0; i < shaders::maxTextureCountPerShader; ++i) {
bindings.push_back(vk::DescriptorSetLayoutBinding()
.setBinding(static_cast<uint32_t>(i))
.setStageFlags(vk::ShaderStageFlagBits::eFragment)
.setDescriptorType(vk::DescriptorType::eCombinedImageSampler)
.setDescriptorCount(1));
}
return imageDescriptorSetLayout;
}
const std::vector<vk::DescriptorSetLayout>& Context::getDescriptorSetLayouts() {
if (descriptorSetLayouts.empty()) {
descriptorSetLayouts = {
buildUniformDescriptorSetLayout(
globalUniformDescriptorSetLayout, shaders::globalUBOCount, "GlobalUniformDescriptorSetLayout")
.get(),
buildUniformDescriptorSetLayout(
layerUniformDescriptorSetLayout, shaders::maxUBOCountPerLayer, "LayerUniformDescriptorSetLayout")
.get(),
buildUniformDescriptorSetLayout(drawableUniformDescriptorSetLayout,
shaders::maxUBOCountPerDrawable,
"DrawableUniformDescriptorSetLayout")
.get(),
getImageDescriptorSetLayout().get()};
}
return descriptorSetLayouts;
const auto descriptorSetLayoutCreateInfo = vk::DescriptorSetLayoutCreateInfo().setBindings(bindings);
drawableImageDescriptorSetLayout = backend.getDevice()->createDescriptorSetLayoutUnique(
descriptorSetLayoutCreateInfo);
backend.setDebugName(drawableImageDescriptorSetLayout.get(), "ImageDescriptorSetLayout");
}
const vk::DescriptorSetLayout& Context::getDescriptorSetLayout(DescriptorSetType type) {
uint32_t index = static_cast<uint32_t>(type);
assert(index < static_cast<uint32_t>(DescriptorSetType::Count));
return getDescriptorSetLayouts()[index];
switch (type) {
case DescriptorSetType::Global:
return globalUniformDescriptorSetLayout.get();
case DescriptorSetType::Layer:
return layerUniformDescriptorSetLayout.get();
case DescriptorSetType::DrawableUniform:
return drawableUniformDescriptorSetLayout.get();
case DescriptorSetType::DrawableImage:
return drawableImageDescriptorSetLayout.get();
default:
assert(static_cast<uint32_t>(type) < static_cast<uint32_t>(DescriptorSetType::Count));
return globalUniformDescriptorSetLayout.get();
break;
}
}
DescriptorPoolGrowable& Context::getDescriptorPool(DescriptorSetType type) {
assert(static_cast<uint32_t>(type) < static_cast<uint32_t>(DescriptorSetType::Count));
return descriptorPoolMap[type];
}
const vk::UniquePipelineLayout& Context::getGeneralPipelineLayout() {
if (generalPipelineLayout) return generalPipelineLayout;
const auto& descriptorLayouts = getDescriptorSetLayouts();
const std::vector<vk::DescriptorSetLayout> layouts = {
globalUniformDescriptorSetLayout.get(),
layerUniformDescriptorSetLayout.get(),
drawableUniformDescriptorSetLayout.get(),
drawableImageDescriptorSetLayout.get(),
};
generalPipelineLayout = backend.getDevice()->createPipelineLayoutUnique(
vk::PipelineLayoutCreateInfo().setSetLayouts(descriptorLayouts));
vk::PipelineLayoutCreateInfo().setSetLayouts(layouts));
backend.setDebugName(generalPipelineLayout.get(), "PipelineLayout_general");
@@ -620,7 +625,7 @@ const vk::UniquePipelineLayout& Context::getPushConstantPipelineLayout() {
return pushConstantPipelineLayout;
}
void Context::FrameResources::runDeletionQueue(const Context& context) {
void Context::FrameResources::runDeletionQueue(Context& context) {
for (const auto& function : deletionQueue) function(context);
deletionQueue.clear();

View File

@@ -3,8 +3,10 @@
#include <mbgl/vulkan/context.hpp>
#include <mbgl/vulkan/command_encoder.hpp>
#include <mbgl/vulkan/texture2d.hpp>
#include <mbgl/util/logging.hpp>
#include <cassert>
#include <math.h>
namespace mbgl {
namespace vulkan {
@@ -14,25 +16,58 @@ DescriptorSet::DescriptorSet(Context& context_, DescriptorSetType type_)
type(type_) {}
DescriptorSet::~DescriptorSet() {
context.enqueueDeletion([sets = std::move(descriptorSets)](const auto& context_) mutable {
context_.getBackend().getDevice()->freeDescriptorSets(context_.getGlobalDescriptorPool().get(), sets);
});
context.enqueueDeletion(
[type_ = type, poolIndex = descriptorPoolIndex, sets = std::move(descriptorSets)](auto& context_) mutable {
auto& poolInfo = context_.getDescriptorPool(type_).pools[poolIndex];
context_.getBackend().getDevice()->freeDescriptorSets(poolInfo.pool.get(), sets);
poolInfo.remainingSets += sets.size();
});
}
void DescriptorSet::createDescriptorPool(DescriptorPoolGrowable& growablePool) {
const auto& device = context.getBackend().getDevice();
const uint32_t maxSets = static_cast<uint32_t>(growablePool.maxSets *
std::pow(growablePool.growFactor, growablePool.pools.size()));
const vk::DescriptorPoolSize size = {type != DescriptorSetType::DrawableImage
? vk::DescriptorType::eUniformBuffer
: vk::DescriptorType::eCombinedImageSampler,
maxSets * growablePool.descriptorsPerSet};
const auto descriptorPoolInfo = vk::DescriptorPoolCreateInfo(vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet)
.setPoolSizes(size)
.setMaxSets(maxSets);
growablePool.pools.emplace_back(device->createDescriptorPoolUnique(descriptorPoolInfo), maxSets);
growablePool.currentPoolIndex = growablePool.pools.size() - 1;
};
void DescriptorSet::allocate() {
if (!descriptorSets.empty()) {
return;
}
const auto& device = context.getBackend().getDevice();
const auto& descriptorPool = context.getGlobalDescriptorPool();
const auto& descriptorSetLayout = context.getDescriptorSetLayout(type);
auto& growablePool = context.getDescriptorPool(type);
const std::vector<vk::DescriptorSetLayout> layouts(context.getBackend().getMaxFrames(), descriptorSetLayout);
const auto descriptorAllocInfo =
vk::DescriptorSetAllocateInfo().setDescriptorPool(*descriptorPool).setSetLayouts(layouts);
if (growablePool.currentPoolIndex == -1 || growablePool.current().remainingSets < layouts.size()) {
const auto& poolIt = std::find_if(growablePool.pools.begin(), growablePool.pools.end(), [&](const auto& p) {
return p.remainingSets >= layouts.size();
});
descriptorSets = device->allocateDescriptorSets(descriptorAllocInfo);
if (poolIt != growablePool.pools.end()) {
growablePool.currentPoolIndex = std::distance(growablePool.pools.begin(), poolIt);
} else {
createDescriptorPool(growablePool);
}
}
descriptorPoolIndex = growablePool.currentPoolIndex;
descriptorSets = device->allocateDescriptorSets(
vk::DescriptorSetAllocateInfo().setDescriptorPool(growablePool.current().pool.get()).setSetLayouts(layouts));
growablePool.current().remainingSets -= descriptorSets.size();
markDirty(true);
}

View File

@@ -201,7 +201,7 @@ void Texture2D::uploadSubRegion(const void* pixelData,
enqueueCommands(commandBuffer);
context.enqueueDeletion([buffAlloc = std::move(bufferAllocation)](const auto&) mutable { buffAlloc.reset(); });
context.enqueueDeletion([buffAlloc = std::move(bufferAllocation)](auto&) mutable { buffAlloc.reset(); });
context.renderingStats().numTextureUpdates++;
}
@@ -391,7 +391,7 @@ void Texture2D::createSampler() {
void Texture2D::destroyTexture() {
if (imageAllocation) {
context.enqueueDeletion([allocation = std::move(imageAllocation)](const auto&) mutable { allocation.reset(); });
context.enqueueDeletion([allocation = std::move(imageAllocation)](auto&) mutable { allocation.reset(); });
imageLayout = vk::ImageLayout::eUndefined;
}
@@ -399,7 +399,7 @@ void Texture2D::destroyTexture() {
void Texture2D::destroySampler() {
if (sampler) {
context.enqueueDeletion([sampler_ = std::move(sampler)](const auto& context_) mutable {
context.enqueueDeletion([sampler_ = std::move(sampler)](auto& context_) mutable {
context_.getBackend().getDevice()->destroySampler(sampler_);
});