diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 25f03f41..e364561f 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -159,6 +159,7 @@ add_library(skyline SHARED ${source_DIR}/skyline/gpu/texture_manager.cpp ${source_DIR}/skyline/gpu/buffer_manager.cpp ${source_DIR}/skyline/gpu/command_scheduler.cpp + ${source_DIR}/skyline/gpu/descriptor_allocator.cpp ${source_DIR}/skyline/gpu/texture/texture.cpp ${source_DIR}/skyline/gpu/buffer.cpp ${source_DIR}/skyline/gpu/presentation_engine.cpp diff --git a/app/src/main/cpp/skyline/gpu.cpp b/app/src/main/cpp/skyline/gpu.cpp index ce81d89c..17dee432 100644 --- a/app/src/main/cpp/skyline/gpu.cpp +++ b/app/src/main/cpp/skyline/gpu.cpp @@ -214,5 +214,6 @@ namespace skyline::gpu { presentation(state, *this), texture(*this), buffer(*this), + descriptor(*this), shader(state, *this) {} } diff --git a/app/src/main/cpp/skyline/gpu.h b/app/src/main/cpp/skyline/gpu.h index e69fdcbd..67ec9ac0 100644 --- a/app/src/main/cpp/skyline/gpu.h +++ b/app/src/main/cpp/skyline/gpu.h @@ -9,6 +9,7 @@ #include "gpu/presentation_engine.h" #include "gpu/texture_manager.h" #include "gpu/buffer_manager.h" +#include "gpu/descriptor_allocator.h" #include "gpu/shader_manager.h" namespace skyline::gpu { @@ -48,6 +49,7 @@ namespace skyline::gpu { TextureManager texture; BufferManager buffer; + DescriptorAllocator descriptor; ShaderManager shader; GPU(const DeviceState &state); diff --git a/app/src/main/cpp/skyline/gpu/descriptor_allocator.cpp b/app/src/main/cpp/skyline/gpu/descriptor_allocator.cpp new file mode 100644 index 00000000..ca1405a5 --- /dev/null +++ b/app/src/main/cpp/skyline/gpu/descriptor_allocator.cpp @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include +#include +#include "descriptor_allocator.h" + +namespace skyline::gpu { + DescriptorAllocator::DescriptorPool::DescriptorPool(const vk::raii::Device &device, const vk::DescriptorPoolCreateInfo &createInfo) : vk::raii::DescriptorPool(device, createInfo), setCount(createInfo.maxSets) {} + + void DescriptorAllocator::AllocateDescriptorPool() { + namespace maxwell3d = soc::gm20b::engine::maxwell3d::type; // We use Maxwell3D as reference for base descriptor counts + using DescriptorSizes = std::array; + constexpr DescriptorSizes BaseDescriptorSizes{ + vk::DescriptorPoolSize{ + .descriptorCount = maxwell3d::PipelineStageConstantBufferCount, + .type = vk::DescriptorType::eUniformBuffer, + }, + }; + + DescriptorSizes descriptorSizes{BaseDescriptorSizes}; + for (auto &descriptorSize : descriptorSizes) + descriptorSize.descriptorCount *= descriptorMultiplier; + + pool = std::make_shared(gpu.vkDevice, vk::DescriptorPoolCreateInfo{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = descriptorSetCount, + .pPoolSizes = descriptorSizes.data(), + .poolSizeCount = descriptorSizes.size(), + }); + } + + DescriptorAllocator::ActiveDescriptorSet::ActiveDescriptorSet(std::shared_ptr pPool, vk::DescriptorSet set) : pool(std::move(pPool)), DescriptorSet(set) { + pool->setCount--; + } + + DescriptorAllocator::ActiveDescriptorSet::~ActiveDescriptorSet() { + std::scoped_lock lock(*pool); + pool->getDevice().freeDescriptorSets(**pool, 1, this, *pool->getDispatcher()); + pool->setCount++; + } + + DescriptorAllocator::DescriptorAllocator(GPU &gpu) : gpu(gpu) { + AllocateDescriptorPool(); + } + + DescriptorAllocator::ActiveDescriptorSet DescriptorAllocator::AllocateSet(vk::DescriptorSetLayout layout) { + std::scoped_lock allocatorLock(mutex); + + while (true) { + std::scoped_lock poolLock(*pool); + + vk::DescriptorSetAllocateInfo allocateInfo{ + .descriptorPool = **pool, + .pSetLayouts = &layout, + .descriptorSetCount = 1, + }; + vk::DescriptorSet set{}; + + auto result{(*gpu.vkDevice).allocateDescriptorSets(&allocateInfo, &set, *gpu.vkDevice.getDispatcher())}; + if (result == vk::Result::eSuccess) { + return ActiveDescriptorSet(pool, set); + } else if (result == vk::Result::eErrorOutOfPoolMemory) { + if (pool->setCount == 0) + // The amount of maximum descriptor sets is insufficient + descriptorSetCount += BaseDescriptorSetCount; + else + // The amount of maximum descriptors is insufficient + descriptorMultiplier++; + AllocateDescriptorPool(); + continue; // Attempt to allocate again with the new pool + } else if (result == vk::Result::eErrorFragmentedPool) { + AllocateDescriptorPool(); // If the pool is fragmented, we reallocate without increasing the size + continue; + } else { + vk::throwResultException(result, __builtin_FUNCTION()); + } + } + } +} diff --git a/app/src/main/cpp/skyline/gpu/descriptor_allocator.h b/app/src/main/cpp/skyline/gpu/descriptor_allocator.h new file mode 100644 index 00000000..6478402e --- /dev/null +++ b/app/src/main/cpp/skyline/gpu/descriptor_allocator.h @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include "fence_cycle.h" + +namespace skyline::gpu { + /** + * @brief A dynamic descriptor set allocator with internal resizing of the descriptor pool to size up to allocation demand + */ + class DescriptorAllocator { + private: + GPU &gpu; + std::mutex mutex; //!< Synchronizes the creation and replacement of the pool object + + static constexpr u32 BaseDescriptorSetCount{64}; //!< An arbitrary amount of descriptor sets that we allocate in multiples of + u32 descriptorSetCount{BaseDescriptorSetCount}; //!< The maximum amount of descriptor sets in the pool + u32 descriptorMultiplier{1}; //!< A multiplier for the maximum amount of descriptors in the pool + + /** + * @brief A lockable VkDescriptorPool for maintaining external synchronization requirements + */ + struct DescriptorPool : public std::mutex, public vk::raii::DescriptorPool { + u64 setCount{}; //!< The amount of sets free to allocate from this pool + + DescriptorPool(vk::raii::Device const &device, vk::DescriptorPoolCreateInfo const &createInfo); + }; + + std::shared_ptr pool; //!< The current pool used by any allocations in the class, replaced when an error is ran into + + /** + * @brief (Re-)Allocates the descriptor pool with the current multiplier applied to the base counts + * @note `DescriptorAllocator::mutex` **must** be locked prior to calling this + */ + void AllocateDescriptorPool(); + + public: + /** + * @brief A RAII-bound descriptor set that automatically frees of resources into the pool on destruction while respecting external synchronization requirements + */ + struct ActiveDescriptorSet : public vk::DescriptorSet { + private: + friend DescriptorAllocator; + std::shared_ptr pool; + + /** + * @note The supplied pool **must** be locked prior to calling this + */ + ActiveDescriptorSet(std::shared_ptr pool, vk::DescriptorSet set); + + public: + ~ActiveDescriptorSet(); + }; + + DescriptorAllocator(GPU &gpu); + + /** + * @note It is UB to allocate a set with a descriptor type that isn't in the pool + */ + ActiveDescriptorSet AllocateSet(vk::DescriptorSetLayout layout); + }; +}