mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13:23 +09:00
[Perf] (MG_Pipe): a per-VAO memo keeps the emitters' handles and buffer set across draws, and an unchanged context-value record skips its hash (rd12 Espryt monolith +1%).
This commit is contained in:
@@ -3165,11 +3165,22 @@ namespace MobileGL::MG_Pipe {
|
|||||||
values.TransformFeedbackGeneration = ctx.GetTransformFeedbackGeneration();
|
values.TransformFeedbackGeneration = ctx.GetTransformFeedbackGeneration();
|
||||||
values.BoundTransformFeedbackLifetimeId = ctx.GetBoundTransformFeedbackLifetimeId();
|
values.BoundTransformFeedbackLifetimeId = ctx.GetBoundTransformFeedbackLifetimeId();
|
||||||
values.TransformFeedbackCapturedVertices = ctx.GetTransformFeedbackCapturedVertices();
|
values.TransformFeedbackCapturedVertices = ctx.GetTransformFeedbackCapturedVertices();
|
||||||
|
// P14: the record is rebuilt every draw and nearly always equal to the last one. When it
|
||||||
|
// is byte-equal to the record whose hash the suppressor still holds, the suppressor
|
||||||
|
// would drop it: skip the hash. (An InvalidateAll changes what the suppressor holds,
|
||||||
|
// so a reset still re-emits.)
|
||||||
|
static MGPContextValues s_lastValues{};
|
||||||
|
static Uint64 s_lastHash = 0;
|
||||||
|
auto& suppressor = MGPipeSetHashSuppressorInstance();
|
||||||
|
if (s_lastHash != 0 && std::memcmp(&values, &s_lastValues, sizeof(values)) == 0 &&
|
||||||
|
suppressor.LastEmitted(MGPipeSuppressorSlot::SetContextValues) == s_lastHash)
|
||||||
|
return 0;
|
||||||
// The whole record is the hash input, padding included - `values{}` zeroes it, so
|
// The whole record is the hash input, padding included - `values{}` zeroes it, so
|
||||||
// the pad bytes are defined and the hash is stable.
|
// the pad bytes are defined and the hash is stable.
|
||||||
const Uint64 contentHash = XXH64(&values, sizeof(values), 0);
|
const Uint64 contentHash = XXH64(&values, sizeof(values), 0);
|
||||||
if (!MGPipeSetHashSuppressorInstance().ShouldEmit(MGPipeSuppressorSlot::SetContextValues,
|
s_lastValues = values;
|
||||||
contentHash)) {
|
s_lastHash = contentHash == 0 ? 1 : contentHash; // the suppressor's own latch value
|
||||||
|
if (!suppressor.ShouldEmit(MGPipeSuppressorSlot::SetContextValues, contentHash)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
MGPipeRouteSetContextValues(values);
|
MGPipeRouteSetContextValues(values);
|
||||||
|
|||||||
@@ -308,6 +308,7 @@ namespace MobileGL::MG_Pipe {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MGPipeSlotAllocator::Reset() {
|
void MGPipeSlotAllocator::Reset() {
|
||||||
|
++m_resetEpoch;
|
||||||
for (KindState& state : m_kinds) {
|
for (KindState& state : m_kinds) {
|
||||||
state.Slots.clear();
|
state.Slots.clear();
|
||||||
state.FreeList.clear();
|
state.FreeList.clear();
|
||||||
|
|||||||
@@ -120,6 +120,9 @@ namespace MobileGL::MG_Pipe {
|
|||||||
|
|
||||||
// Context teardown / server reset / a unit test's fixture.
|
// Context teardown / server reset / a unit test's fixture.
|
||||||
void Reset();
|
void Reset();
|
||||||
|
// Moves at every Reset(): a memo of handles minted by this allocator is good only while it
|
||||||
|
// holds (a handle lives as long as its object, and only a reset forgets that).
|
||||||
|
Uint64 ResetEpoch() const { return m_resetEpoch; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct SlotState {
|
struct SlotState {
|
||||||
@@ -161,6 +164,7 @@ namespace MobileGL::MG_Pipe {
|
|||||||
Uint32 BandLiveCount = 0;
|
Uint32 BandLiveCount = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Uint64 m_resetEpoch = 0;
|
||||||
KindState& StateOf(MGPipeKind kind);
|
KindState& StateOf(MGPipeKind kind);
|
||||||
const KindState& StateOf(MGPipeKind kind) const;
|
const KindState& StateOf(MGPipeKind kind) const;
|
||||||
// The SlotState a (kind, slot) names, in whichever of the two vectors holds it, or
|
// The SlotState a (kind, slot) names, in whichever of the two vectors holds it, or
|
||||||
|
|||||||
@@ -168,7 +168,12 @@ namespace MobileGL::MG_Pipe {
|
|||||||
|
|
||||||
const Uint64 lifetimeId = vao->GetLifetimeId();
|
const Uint64 lifetimeId = vao->GetLifetimeId();
|
||||||
const Uint32 configVersion = vao->GetConfigVersion();
|
const Uint32 configVersion = vao->GetConfigVersion();
|
||||||
const MGPipeHandle handle = MGPipeSlots().Acquire(MGPipeKind::VertexElementsCso, lifetimeId);
|
// The VAO's handle is the allocator's for the object's whole life (only an allocator
|
||||||
|
// reset forgets it), so it is resolved once per VAO rather than per draw (VaoMemo).
|
||||||
|
VaoMemo& memo = MemoFor(*vao);
|
||||||
|
if (MGPipeHandleIsNull(memo.ElementsHandle))
|
||||||
|
memo.ElementsHandle = MGPipeSlots().Acquire(MGPipeKind::VertexElementsCso, lifetimeId);
|
||||||
|
const MGPipeHandle handle = memo.ElementsHandle;
|
||||||
const SizeT slot = handle.Slot;
|
const SizeT slot = handle.Slot;
|
||||||
if (slot >= m_latch.size()) m_latch.resize(slot + 1);
|
if (slot >= m_latch.size()) m_latch.resize(slot + 1);
|
||||||
Latch& latch = m_latch[slot];
|
Latch& latch = m_latch[slot];
|
||||||
@@ -200,7 +205,20 @@ namespace MobileGL::MG_Pipe {
|
|||||||
const Array<MGPipeHandle, kMGPipeMaxVertexAttribs>* ownedClientBuffers = nullptr) {
|
const Array<MGPipeHandle, kMGPipeMaxVertexAttribs>* ownedClientBuffers = nullptr) {
|
||||||
const auto& vao = ctx.GetBoundVertexArray();
|
const auto& vao = ctx.GetBoundVertexArray();
|
||||||
Uint32 count = 0;
|
Uint32 count = 0;
|
||||||
if (vao) {
|
// The resolved set is a function of the VAO's configuration alone (buffers, strides,
|
||||||
|
// divisors and enables all move its version; its buffers' handles live as long as the
|
||||||
|
// VAO holds the buffers), so it is walked once per configuration rather than per draw -
|
||||||
|
// a Minecraft frame binds hundreds of chunk VAOs. Not for client-memory arrays, whose
|
||||||
|
// entries the draw itself supplies.
|
||||||
|
VaoMemo* memo = vao && ownedClientBuffers == nullptr ? &MemoFor(*vao) : nullptr;
|
||||||
|
if (memo != nullptr && memo->BuffersValid && memo->ConfigVersion == vao->GetConfigVersion()) {
|
||||||
|
count = memo->BufferCount;
|
||||||
|
for (SizeT i = 0; i < count; ++i) {
|
||||||
|
m_entries[i] = memo->Buffers[i];
|
||||||
|
MGPipeResourceTrackerInstance().NoteBoundAs(m_entries[i].Res, BufferTarget::Vertex);
|
||||||
|
}
|
||||||
|
} else if (vao) {
|
||||||
|
Bool memoizable = memo != nullptr;
|
||||||
for (SizeT i = 0; i < kAttribs; ++i) {
|
for (SizeT i = 0; i < kAttribs; ++i) {
|
||||||
if (vao->GetAttribute(static_cast<Uint>(i)).Enabled) count = static_cast<Uint32>(i) + 1;
|
if (vao->GetAttribute(static_cast<Uint>(i)).Enabled) count = static_cast<Uint32>(i) + 1;
|
||||||
}
|
}
|
||||||
@@ -230,6 +248,13 @@ namespace MobileGL::MG_Pipe {
|
|||||||
entry.Stride = static_cast<Uint32>(attrib.Stride);
|
entry.Stride = static_cast<Uint32>(attrib.Stride);
|
||||||
entry.Divisor = static_cast<Uint32>(attrib.Divisor);
|
entry.Divisor = static_cast<Uint32>(attrib.Divisor);
|
||||||
entry.BindingIndex = static_cast<Uint32>(i);
|
entry.BindingIndex = static_cast<Uint32>(i);
|
||||||
|
if (attrib.Enabled && !attrib.Buffer) memoizable = false;
|
||||||
|
}
|
||||||
|
if (memo != nullptr) {
|
||||||
|
memo->BuffersValid = memoizable && count <= kVaoMemoBuffers;
|
||||||
|
memo->ConfigVersion = vao->GetConfigVersion();
|
||||||
|
memo->BufferCount = count;
|
||||||
|
for (SizeT i = 0; i < count && i < kVaoMemoBuffers; ++i) memo->Buffers[i] = m_entries[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,7 +286,13 @@ namespace MobileGL::MG_Pipe {
|
|||||||
m_lastIndex = MGPIndexBuffer{};
|
m_lastIndex = MGPIndexBuffer{};
|
||||||
if (vao) {
|
if (vao) {
|
||||||
if (const auto& bound = vao->GetIndexBufferBindingSlot().GetBoundObject()) {
|
if (const auto& bound = vao->GetIndexBufferBindingSlot().GetBoundObject()) {
|
||||||
m_lastIndex.Res = MGPipeSlots().Acquire(MGPipeKind::Buffer, bound->GetLifetimeId());
|
// Per VAO, keyed on the element buffer's own lifetime id (VaoMemo).
|
||||||
|
VaoMemo& memo = MemoFor(*vao);
|
||||||
|
if (memo.IndexLifetimeId != bound->GetLifetimeId() || MGPipeHandleIsNull(memo.IndexHandle)) {
|
||||||
|
memo.IndexLifetimeId = bound->GetLifetimeId();
|
||||||
|
memo.IndexHandle = MGPipeSlots().Acquire(MGPipeKind::Buffer, memo.IndexLifetimeId);
|
||||||
|
}
|
||||||
|
m_lastIndex.Res = memo.IndexHandle;
|
||||||
// The INDEX|ELEMENT_ARRAY bind bits. ELEMENT_ARRAY's D-B7 reader
|
// The INDEX|ELEMENT_ARRAY bind bits. ELEMENT_ARRAY's D-B7 reader
|
||||||
// (kCapNeedsHostIndexBytes) is RESERVED (P8-F) and was never built.
|
// (kCapNeedsHostIndexBytes) is RESERVED (P8-F) and was never built.
|
||||||
// Noted at every draw for RefreshBindMask's reason: an EBO defined through
|
// Noted at every draw for RefreshBindMask's reason: an EBO defined through
|
||||||
@@ -353,6 +384,36 @@ namespace MobileGL::MG_Pipe {
|
|||||||
void ResetCounters() { m_creates = m_binds = m_bufferSets = m_indexSets = 0; }
|
void ResetCounters() { m_creates = m_binds = m_bufferSets = m_indexSets = 0; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// P14: what EmitVertexElements / EmitVertexBuffers / EmitIndexBuffer resolve from a VAO,
|
||||||
|
// direct-mapped on the VAO's lifetime id (monotonic, never reused) and dropped whole at an
|
||||||
|
// allocator reset. A colliding VAO simply takes the entry over. It changes no record: the
|
||||||
|
// same handles and entries are emitted, only found without the per-draw walk and lookups.
|
||||||
|
static constexpr SizeT kVaoMemoEntries = 1024;
|
||||||
|
static constexpr SizeT kVaoMemoBuffers = 8;
|
||||||
|
struct VaoMemo {
|
||||||
|
Uint64 LifetimeId = 0;
|
||||||
|
Uint64 SlotEpoch = 0;
|
||||||
|
MGPipeHandle ElementsHandle = kMGPipeNullHandle;
|
||||||
|
Uint64 IndexLifetimeId = 0;
|
||||||
|
MGPipeHandle IndexHandle = kMGPipeNullHandle;
|
||||||
|
Bool BuffersValid = false;
|
||||||
|
Uint32 ConfigVersion = 0;
|
||||||
|
Uint32 BufferCount = 0;
|
||||||
|
Array<MGPVertexBuffer, kVaoMemoBuffers> Buffers{};
|
||||||
|
};
|
||||||
|
VaoMemo& MemoFor(const VertexArrayObject& vao) {
|
||||||
|
const Uint64 lifetimeId = vao.GetLifetimeId();
|
||||||
|
const Uint64 epoch = MGPipeSlots().ResetEpoch();
|
||||||
|
VaoMemo& memo = m_vaoMemo[lifetimeId % kVaoMemoEntries];
|
||||||
|
if (memo.LifetimeId != lifetimeId || memo.SlotEpoch != epoch) {
|
||||||
|
memo = VaoMemo{};
|
||||||
|
memo.LifetimeId = lifetimeId;
|
||||||
|
memo.SlotEpoch = epoch;
|
||||||
|
}
|
||||||
|
return memo;
|
||||||
|
}
|
||||||
|
Array<VaoMemo, kVaoMemoEntries> m_vaoMemo{};
|
||||||
|
|
||||||
struct Latch {
|
struct Latch {
|
||||||
// The PER-CONTEXT half: "has this emitter told THIS server about this handle's
|
// The PER-CONTEXT half: "has this emitter told THIS server about this handle's
|
||||||
// configuration". Cleared by Reset() at every make-current.
|
// configuration". Cleared by Reset() at every make-current.
|
||||||
|
|||||||
Reference in New Issue
Block a user