mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Perf] (Magma): the wire vertex-input layout is kept on its vertex-elements record and buffer handles resolve through a slot-indexed front.
This commit is contained in:
@@ -18,6 +18,64 @@
|
||||
#include <utility>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
namespace {
|
||||
struct WireVertexInputMemo {
|
||||
struct Key {
|
||||
Uint64 Offset;
|
||||
Uint32 Stride;
|
||||
Uint32 Divisor;
|
||||
};
|
||||
Bool Valid = false;
|
||||
Uint64 ContentSerial = 0;
|
||||
Uint32 ActiveMask = 0;
|
||||
Uint32 Start = 0;
|
||||
Uint32 Count = 0;
|
||||
Uint32 KeyCount = 0;
|
||||
Array<Key, MG_Pipe::kMGPipeMaxVertexAttribs> Keys{};
|
||||
VertexInputStateFactory::BackendVertexInputState State;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
const VertexInputStateFactory::BackendVertexInputState* VertexInputStateFactory::ResolveWireVertexInput(
|
||||
const MG_Pipe::MGPipeVertexElementsRecord& elements, const MG_Pipe::MGPipeApplierState& state,
|
||||
Uint32 activeMask) const {
|
||||
auto* memo = static_cast<WireVertexInputMemo*>(elements.BackendMemo.get());
|
||||
if (memo == nullptr) {
|
||||
auto created = std::make_shared<WireVertexInputMemo>();
|
||||
memo = created.get();
|
||||
elements.BackendMemo = Move(created);
|
||||
}
|
||||
// The window first: a location outside it is BuildWireVertexInput's refusal, and the
|
||||
// keys below index the window.
|
||||
Bool hit = memo->Valid && memo->ContentSerial == elements.ContentSerial && memo->ActiveMask == activeMask &&
|
||||
memo->Start == state.VertexBufferStart && memo->Count == state.VertexBufferCount;
|
||||
if (hit) {
|
||||
Uint32 n = 0;
|
||||
for (Uint32 location = 0; location < elements.AttributeCount && hit; ++location) {
|
||||
if (!elements.Attributes[location].Enabled || !(activeMask & (1u << location))) continue;
|
||||
const auto& buffer = state.VertexBuffers[location];
|
||||
const auto& key = memo->Keys[n++];
|
||||
hit = key.Offset == buffer.Offset && key.Stride == buffer.Stride && key.Divisor == buffer.Divisor;
|
||||
}
|
||||
if (hit) return &memo->State;
|
||||
}
|
||||
memo->Valid = false;
|
||||
if (!BuildWireVertexInput(elements, state, activeMask, memo->State)) return nullptr;
|
||||
Uint32 n = 0;
|
||||
for (Uint32 location = 0; location < elements.AttributeCount; ++location) {
|
||||
if (!elements.Attributes[location].Enabled || !(activeMask & (1u << location))) continue;
|
||||
const auto& buffer = state.VertexBuffers[location];
|
||||
memo->Keys[n++] = {buffer.Offset, buffer.Stride, buffer.Divisor};
|
||||
}
|
||||
memo->KeyCount = n;
|
||||
memo->ContentSerial = elements.ContentSerial;
|
||||
memo->ActiveMask = activeMask;
|
||||
memo->Start = state.VertexBufferStart;
|
||||
memo->Count = state.VertexBufferCount;
|
||||
memo->Valid = true;
|
||||
return &memo->State;
|
||||
}
|
||||
|
||||
Bool VertexInputStateFactory::BuildWireVertexInput(const MG_Pipe::MGPipeVertexElementsRecord& elements,
|
||||
const MG_Pipe::MGPipeApplierState& state, Uint32 activeMask, BackendVertexInputState& out) const {
|
||||
// The caller's state is a per-draw scratch (WireDraw.inc): reset it without freeing.
|
||||
|
||||
@@ -119,6 +119,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Bool BuildWireVertexInput(const MG_Pipe::MGPipeVertexElementsRecord& elements,
|
||||
const MG_Pipe::MGPipeApplierState& state, Uint32 activeMask,
|
||||
BackendVertexInputState& out) const;
|
||||
// The same layout, kept on the record (MGPipeVertexElementsRecord::BackendMemo) and rebuilt
|
||||
// only when the record's configuration, the program's active mask, the buffer window or an
|
||||
// active attribute's buffer offset/stride/divisor moved - everything the build reads that
|
||||
// can change. Null where BuildWireVertexInput would return false.
|
||||
const BackendVertexInputState* ResolveWireVertexInput(const MG_Pipe::MGPipeVertexElementsRecord& elements,
|
||||
const MG_Pipe::MGPipeApplierState& state,
|
||||
Uint32 activeMask) const;
|
||||
|
||||
private:
|
||||
static VkFormat ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger, Bool isBgra = false,
|
||||
|
||||
@@ -184,13 +184,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
} // namespace
|
||||
|
||||
VkBufferManager::WireBufferResource* VkBufferManager::FindWireBuffer(MG_Pipe::MGPipeHandle res) {
|
||||
const Uint64 key = WireBufferKey(res);
|
||||
auto& lookup = m_wireBufferLookup[key % kWireBufferLookupEntries];
|
||||
if (lookup.resource != nullptr && lookup.key == key && lookup.eraseCount == m_wireBufferEraseCount)
|
||||
return lookup.resource;
|
||||
const auto found = m_wireBuffers.find(key);
|
||||
const SizeT slot = res.Slot;
|
||||
if (slot < m_wireBufferLookup.size()) {
|
||||
const auto& lookup = m_wireBufferLookup[slot];
|
||||
if (lookup.resource != nullptr && lookup.gen == res.Gen) return lookup.resource;
|
||||
}
|
||||
const auto found = m_wireBuffers.find(WireBufferKey(res));
|
||||
if (found == m_wireBuffers.end()) return nullptr;
|
||||
lookup = {key, m_wireBufferEraseCount, &found->second};
|
||||
if (slot < kWireBufferLookupMaxSlots) {
|
||||
if (slot >= m_wireBufferLookup.size()) m_wireBufferLookup.resize(slot + 1);
|
||||
m_wireBufferLookup[slot] = {res.Gen, &found->second};
|
||||
}
|
||||
return &found->second;
|
||||
}
|
||||
|
||||
@@ -998,7 +1002,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
DeferWireRelease(std::move(found->second.buffer),
|
||||
found->second.imported ? T0ReleaseSerial(found->second.lastUseSerial)
|
||||
: found->second.lastUseSerial); // P11 B2, as Respecify
|
||||
++m_wireBufferEraseCount;
|
||||
ForgetWireBufferLookup(found->first);
|
||||
m_wireBuffers.erase(found);
|
||||
PublishWireReclaimGauges();
|
||||
}
|
||||
@@ -1170,7 +1174,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// DestroyAllDeferredReleases above emptied the parked list; this destroys the stores the
|
||||
// records still hold, so nothing this arm minted outlives the count.
|
||||
if (!m_wireBuffers.empty()) ++m_wireStoreDestroyEpoch;
|
||||
++m_wireBufferEraseCount;
|
||||
m_wireBufferLookup.clear();
|
||||
m_wireBuffers.clear();
|
||||
m_wireStoreCount = 0;
|
||||
m_wireStoreCountPeak = 0;
|
||||
|
||||
@@ -292,18 +292,21 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
IBufferCopyCommandProvider* m_copyProvider = nullptr;
|
||||
Vector<Vector<VkBufferObject>> m_deferredBufferReleases;
|
||||
std::unordered_map<Uint64, WireBufferResource> m_wireBuffers;
|
||||
// P14: a direct-mapped front for m_wireBuffers (every draw resolves each vertex, index and
|
||||
// uniform buffer). Keyed on WireBufferKey ({slot, gen}, never reused) and believed only at
|
||||
// the erase count it was taken at: a node never moves until erased, and every erase moves
|
||||
// the count.
|
||||
// P14: a slot-indexed front for m_wireBuffers (every draw resolves each vertex, index and
|
||||
// uniform buffer). An entry is believed only for the generation it was taken at ({slot,
|
||||
// gen} is never reused), and the erase of that node clears it: a node never moves until
|
||||
// erased. Slot-indexed rather than direct-mapped because a Minecraft frame draws from a
|
||||
// few thousand live buffers, which a small hashed table mostly missed.
|
||||
struct WireBufferLookup {
|
||||
Uint64 key = 0;
|
||||
Uint64 eraseCount = 0;
|
||||
Uint32 gen = 0;
|
||||
WireBufferResource* resource = nullptr;
|
||||
};
|
||||
static constexpr SizeT kWireBufferLookupEntries = 256;
|
||||
Array<WireBufferLookup, kWireBufferLookupEntries> m_wireBufferLookup{};
|
||||
Uint64 m_wireBufferEraseCount = 0;
|
||||
static constexpr SizeT kWireBufferLookupMaxSlots = SizeT{1} << 20;
|
||||
Vector<WireBufferLookup> m_wireBufferLookup;
|
||||
void ForgetWireBufferLookup(Uint64 key) {
|
||||
const SizeT slot = static_cast<SizeT>(key & 0xffffffffu);
|
||||
if (slot < m_wireBufferLookup.size()) m_wireBufferLookup[slot] = {};
|
||||
}
|
||||
// See DeferredWireRelease. ONE FLAT LIST rather than the per-frame-slot buckets above:
|
||||
// the whole point is that these entries do not wait for a frame slot to come round.
|
||||
Vector<DeferredWireRelease> m_deferredWireReleases;
|
||||
|
||||
@@ -311,7 +311,6 @@
|
||||
if (vaoHandle.Slot >= state.VertexElementsCsos.size()) MagmaWireFatal("vertex-elements-record");
|
||||
const auto& vao = state.VertexElementsCsos[vaoHandle.Slot];
|
||||
if (!vao.Live || vao.Gen != vaoHandle.Gen) MagmaWireFatal("vertex-elements-generation");
|
||||
auto& input = scratch.input;
|
||||
// P7 wave 2 package C, CONTRACT-P7 §3.2: THE `vertex-layout` ROW IS RETIRED AND SPLIT.
|
||||
//
|
||||
// A false is now ONE reason and only one - `buffer-window`, an enabled attribute the
|
||||
@@ -324,9 +323,18 @@
|
||||
// protocol error, not a shape the split path has yet to migrate, so it must not keep
|
||||
// counting against the census of unmigrated refusals (scripts/ci/fatal_census.py
|
||||
// counts the marker literally, which is also why no comment in this package spells it).
|
||||
if (!m_vertexInputStateFactory->BuildWireVertexInput(vao, state,
|
||||
programObj.activeVertexInputLocationMask, input))
|
||||
MagmaWireFatal("vertex-layout-buffer-window");
|
||||
// P14: resolved through the record's own memo (ResolveWireVertexInput), except in a nested
|
||||
// preparation, which builds into its own scratch rather than under the outer draw's feet.
|
||||
const VertexInputStateFactory::BackendVertexInputState* resolvedInput = nullptr;
|
||||
if (&scratch == &m_wireDrawScratch) {
|
||||
resolvedInput = m_vertexInputStateFactory->ResolveWireVertexInput(vao, state,
|
||||
programObj.activeVertexInputLocationMask);
|
||||
} else if (m_vertexInputStateFactory->BuildWireVertexInput(vao, state,
|
||||
programObj.activeVertexInputLocationMask, scratch.input)) {
|
||||
resolvedInput = &scratch.input;
|
||||
}
|
||||
if (resolvedInput == nullptr) MagmaWireFatal("vertex-layout-buffer-window");
|
||||
const auto& input = *resolvedInput;
|
||||
// The seven device/format reasons are now MASKED OUT of the vertex input state, and
|
||||
// this is the monolith arm's pre-flight for them, statement for statement
|
||||
// (VulkanRenderer.cpp's SetupDraw: "Fail loudly rather than render wrong pixels"). An
|
||||
|
||||
Reference in New Issue
Block a user