mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Fix] (Magma): a frame slot's next descriptor pool doubles its LARGEST pool of the same update-after-bind flavour, capped at 4096 sets - doubling the first (oldest, smallest) one made every grown pool the same size, so rd12's 15,016-set loading frame grew one slot to 60 pools (1x128 + 59x256); now 9 (64 ... 4096, 4096, 4096)
This commit is contained in:
@@ -2137,32 +2137,46 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return true;
|
||||
}
|
||||
|
||||
Uint32 UniformManager::NextDescriptorPoolMaxSets(const Vector<DescriptorPoolBucket>& pools, Bool updateAfterBind,
|
||||
Uint32 initialMaxSets) {
|
||||
const Uint32 minSets = std::max<Uint32>(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<Uint64>(largest) * 2;
|
||||
return static_cast<Uint32>(std::clamp<Uint64>(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<Uint32>(1, matchingBucket->maxSets)
|
||||
: m_setsPerFrame;
|
||||
const Uint32 grownMaxSets = currentMaxSets <= (std::numeric_limits<Uint32>::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<Uint32>(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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<DescriptorPoolBucket>& 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
|
||||
|
||||
Reference in New Issue
Block a user