[Perf] (MG_Pipe): lifetime-id slot lookups get a direct-mapped front and buffer binding slots are indexed (rd12 monolith +1.5%).

This commit is contained in:
2026-10-07 10:27:00 -04:00
parent 549a34bb1a
commit 8346d80999
4 changed files with 31 additions and 0 deletions
+11
View File
@@ -206,10 +206,20 @@ namespace MobileGL::MG_Pipe {
#endif
if (lifetimeId == 0) return kMGPipeNullHandle;
const KindState& state = StateOf(kind);
// The direct-mapped front (SlotAllocator.h): a hit is only believed when the slot it
// names is live AND still owned by this very lifetime id.
auto& recent = state.Recent[lifetimeId % KindState::kRecentEntries];
if (recent.LifetimeId == lifetimeId) {
const SlotState* entry = EntryOf(state, kind, recent.Slot);
if (entry != nullptr && entry->Live && entry->LifetimeId == lifetimeId)
return MGPipeHandle{recent.Slot, entry->Gen};
}
const auto it = state.ByLifetimeId.find(lifetimeId);
if (it == state.ByLifetimeId.end()) return kMGPipeNullHandle;
const SlotState* entry = EntryOf(state, kind, it->second);
if (entry == nullptr || !entry->Live) return kMGPipeNullHandle;
recent.LifetimeId = lifetimeId;
recent.Slot = it->second;
return MGPipeHandle{it->second, entry->Gen};
}
@@ -304,6 +314,7 @@ namespace MobileGL::MG_Pipe {
state.BandSlots.clear();
state.BandFreeList.clear();
state.ByLifetimeId.clear();
state.Recent.fill({});
state.LiveCount = 0;
state.BandLiveCount = 0;
}
+11
View File
@@ -144,6 +144,17 @@ namespace MobileGL::MG_Pipe {
Vector<SlotState> BandSlots;
Vector<Uint32> BandFreeList;
UnorderedMap<Uint64, Uint32> ByLifetimeId;
// P14: a direct-mapped front for ByLifetimeId. Every draw resolves its VAO, its
// vertex buffers and its textures by lifetime id, and the hash map was ~2-3% of a
// Minecraft frame. Keyed on the lifetime id (monotonic, never reused) and confirmed
// against the slot's own LifetimeId and Live bit on every hit, so a stale entry can
// only miss. Mutable: FindByLifetimeId is const and fills it.
struct RecentEntry {
Uint64 LifetimeId = 0;
Uint32 Slot = 0;
};
static constexpr SizeT kRecentEntries = 1024;
mutable Array<RecentEntry, kRecentEntries> Recent{};
Uint32 LiveCount = 0;
// The band's share of LiveCount above, so the two spaces can be reported apart
// without walking either table. Always 0 for every kind but ShaderCso.
@@ -10,8 +10,11 @@
namespace MobileGL::MG_State::GLState {
BufferState::BufferState() {
m_bindingSlotIndex.fill(-1);
for (SizeT i = 0; i < m_bindingSlots.size(); ++i) {
m_bindingSlots[i] = BindingSlot<BufferObject>(GlobalBufferTargets[i]);
const auto target = static_cast<SizeT>(GlobalBufferTargets[i]);
if (target < m_bindingSlotIndex.size()) m_bindingSlotIndex[target] = static_cast<Int16>(i);
}
for (SizeT i = 0; i < m_touchedBindPointCount.size(); ++i) {
m_touchedBindPointCount[i] = 0;
@@ -19,6 +22,10 @@ namespace MobileGL::MG_State::GLState {
}
BindingSlot<BufferObject>& BufferState::GetBindingSlot(BufferTarget target) {
// O(1) through the index the constructor built (every draw asks for several targets).
const auto index = static_cast<SizeT>(target);
if (index < m_bindingSlotIndex.size() && m_bindingSlotIndex[index] >= 0)
return m_bindingSlots[static_cast<SizeT>(m_bindingSlotIndex[index])];
for (auto& bindingSlot : m_bindingSlots) {
if (bindingSlot.GetTarget() == target) {
return bindingSlot;
@@ -106,6 +106,8 @@ namespace MobileGL::MG_State::GLState {
Uint64 m_anyBufferChangeGeneration = 0;
Array<Uint64, BufferBindPointTargets.size()> m_bindPointGeneration{};
Array<BindingSlot<BufferObject>, GlobalBufferTargets.size()> m_bindingSlots;
// BufferTarget -> index into m_bindingSlots, -1 for a target with no global slot.
Array<Int16, static_cast<SizeT>(BufferTarget::BufferTargetCount)> m_bindingSlotIndex{};
// TODO: query the count somewhere globally?
// For glBindBufferBase / glBindBufferRange
Array<Array<BindingSlotRange1D<BufferObject>, BufferBindingPointCount>, BufferBindPointTargets.size()>