diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp index a2465a58..1496774d 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp @@ -2137,32 +2137,46 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } + Uint32 UniformManager::NextDescriptorPoolMaxSets(const Vector& pools, Bool updateAfterBind, + Uint32 initialMaxSets) { + const Uint32 minSets = std::max(1, initialMaxSets); + const Uint32 maxSets = std::max(minSets, kMaxDescriptorPoolSets); + Uint32 largest = 0; + for (const auto& pool : pools) { + if (pool.updateAfterBind == updateAfterBind) { + largest = std::max(largest, pool.maxSets); + } + } + if (largest == 0) { + return minSets; + } + const Uint64 doubled = static_cast(largest) * 2; + return static_cast(std::clamp(doubled, minSets, maxSets)); + } + Bool UniformManager::GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex, Bool updateAfterBind) { if (frame.descriptorPools.empty()) { return false; } - const auto matchingBucket = std::find_if( - frame.descriptorPools.begin(), frame.descriptorPools.end(), - [updateAfterBind](const DescriptorPoolBucket& candidate) { return candidate.updateAfterBind == updateAfterBind; }); - const Uint32 currentMaxSets = matchingBucket != frame.descriptorPools.end() - ? std::max(1, matchingBucket->maxSets) - : m_setsPerFrame; - const Uint32 grownMaxSets = currentMaxSets <= (std::numeric_limits::max() / 2) ? (currentMaxSets * 2) - : currentMaxSets; - + Uint32 grownMaxSets = NextDescriptorPoolMaxSets(frame.descriptorPools, updateAfterBind, m_setsPerFrame); VkDescriptorPool grownPool = VK_NULL_HANDLE; - if (!CreateDescriptorPool(grownMaxSets, updateAfterBind, grownPool)) { - MGLOG_E_ONCE("UniformDescriptorBinder::GrowFrameDescriptorPool failed: cannot create grown pool (%u -> %u sets)", - currentMaxSets, grownMaxSets); - return false; + // A large pool is a large driver allocation; if the device refuses one, settle for less + // rather than failing the draw - halving down to the base size, where the old policy lived. + while (!CreateDescriptorPool(grownMaxSets, updateAfterBind, grownPool)) { + if (grownMaxSets <= m_setsPerFrame) { + MGLOG_E_ONCE("UniformDescriptorBinder::GrowFrameDescriptorPool failed: cannot create grown pool (%u sets)", + grownMaxSets); + return false; + } + grownMaxSets = std::max(m_setsPerFrame, grownMaxSets / 2); } frame.descriptorPools.push_back({grownPool, grownMaxSets, 0, updateAfterBind}); frame.activeDescriptorPoolIndex = static_cast(frame.descriptorPools.size() - 1); - MGLOG_D( - "UniformDescriptorBinder: frame %u descriptor pool exhausted, grew pool (%u -> %u sets), poolCount=%zu", - frameIndex, currentMaxSets, grownMaxSets, frame.descriptorPools.size()); + MGLOG_D("UniformDescriptorBinder: frame %u descriptor pool exhausted, grew a %u-set pool (updateAfterBind=%d), " + "poolCount=%zu", + frameIndex, grownMaxSets, updateAfterBind ? 1 : 0, frame.descriptorPools.size()); return true; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h index d1b363ed..d6951c57 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h @@ -130,13 +130,33 @@ namespace MobileGL::MG_Backend::DirectVulkan { static Bool ProgramSamplesOnlySingleLevelTextures(const MG_State::GLState::ProgramObject& program, const ProgramFactory::VkProgramObject& programObj); - private: + // ---- per-frame descriptor pools ---------------------------------------------------- + // Each frame slot owns a list of pools. The first is the base pool (non-update-after-bind, + // setsPerFrame sets) and is never destroyed while the manager lives; every later one was + // added by GrowFrameDescriptorPool when an allocation found no room. A pool serves one + // updateAfterBind flavour, because a set can only come from a pool created with the flag + // its layout was. struct DescriptorPoolBucket { VkDescriptorPool handle = VK_NULL_HANDLE; Uint32 maxSets = 0; Uint32 allocatedSets = 0; Bool updateAfterBind = false; }; + // One pool's maxSets never exceeds this. It bounds one pool's driver memory: CreateDescriptorPool + // declares 8 descriptors of each of 6 types per set, so a 4096-set pool asks for 196,608 + // descriptors - about 12 MiB at 64 bytes each on a driver that backs every declared one. + static constexpr Uint32 kMaxDescriptorPoolSets = 4096; + // The growth policy, pure so the host tests can drive it: the size of the next pool a slot + // adds for `updateAfterBind` layouts, given the slot's existing pools. Twice the LARGEST pool + // of that flavour, capped at kMaxDescriptorPoolSets; `initialMaxSets` when the slot has no + // pool of that flavour yet. So a frame that needs N sets adds O(log N) pools while N stays + // under the cap, and one more pool per kMaxDescriptorPoolSets sets beyond it. Taking the + // largest (not the first) pool is the point: the first same-flavour pool is the oldest, + // smallest one, and doubling IT made every grown pool the same size. + static Uint32 NextDescriptorPoolMaxSets(const Vector& pools, Bool updateAfterBind, + Uint32 initialMaxSets); + + private: // A cached descriptor set together with the pool it was allocated from, so a // layout-destroyed purge can vkFreeDescriptorSets it back and credit the