From 2de0d6dd254e98210509d2c8c6fbc11955cee0f8 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 4 Oct 2026 04:54:54 -0400 Subject: [PATCH] [Perf] (Pipe): reuse client-array buffers instead of a blocking create per draw, and copy contiguous ranges in one go --- MobileGL/MG_Impl/Pipe/ClientFetchPlan.h | 4 +++- MobileGL/MG_Impl/Pipe/OwnedDrawInputs.h | 24 +++++++++++++++---- MobileGL/MG_State/GLState/ShareGroupState.cpp | 22 +++++++++++++++++ MobileGL/MG_State/GLState/ShareGroupState.h | 9 +++++++ 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/MobileGL/MG_Impl/Pipe/ClientFetchPlan.h b/MobileGL/MG_Impl/Pipe/ClientFetchPlan.h index 4c02a941..34cf3a76 100644 --- a/MobileGL/MG_Impl/Pipe/ClientFetchPlan.h +++ b/MobileGL/MG_Impl/Pipe/ClientFetchPlan.h @@ -234,7 +234,9 @@ namespace MobileGL::MG_Pipe { m_vertices.push_back(static_cast(vertex)); } } - std::sort(m_vertices.begin(), m_vertices.end()); + // An array draw's single range arrives ascending already; sorting it again was most of + // the client's time on a client-array draw. + if (!std::is_sorted(m_vertices.begin(), m_vertices.end())) std::sort(m_vertices.begin(), m_vertices.end()); m_vertices.erase(std::unique(m_vertices.begin(), m_vertices.end()), m_vertices.end()); return true; } diff --git a/MobileGL/MG_Impl/Pipe/OwnedDrawInputs.h b/MobileGL/MG_Impl/Pipe/OwnedDrawInputs.h index 8fc2982e..97502699 100644 --- a/MobileGL/MG_Impl/Pipe/OwnedDrawInputs.h +++ b/MobileGL/MG_Impl/Pipe/OwnedDrawInputs.h @@ -34,6 +34,12 @@ namespace MobileGL::MG_Pipe { MGPipeVertexInputEmitterInstance().EmitVertexBuffers(m_context, m_baseInstance); if (m_indexBindingChanged) MGPipeVertexInputEmitterInstance().EmitIndexBuffer(m_context); + // The bindings no longer name them: the buffers go back to the share group for the + // next client-array draw. Their next respecify orphans the store this draw reads. + auto& group = *m_context.GetShareGroup(); + for (auto& buffer : m_vertexBuffers) + if (buffer) group.ReturnClientArrayBuffer(std::move(buffer)); + if (m_indexBuffer) group.ReturnClientArrayBuffer(std::move(m_indexBuffer)); } Bool Prepare(MGPDrawInfo& info, const MGPDrawRange* ranges, Uint32 rangeCount, @@ -46,7 +52,8 @@ namespace MobileGL::MG_Pipe { // Start remains an element offset, so base vertex and draw IDs are unchanged. if (clientIndexBytes != 0) { if (!clientIndices || clientIndexBytes > std::numeric_limits::max()) return false; - m_indexBuffer = MakeOwnedBuffer(clientIndices, static_cast(clientIndexBytes), BufferTarget::Index); + m_indexBuffer = MakeOwnedBuffer(m_context, clientIndices, static_cast(clientIndexBytes), + BufferTarget::Index); info.IndexResource = MGPipeResourceTrackerInstance().Find(*m_indexBuffer); info.Flags &= ~static_cast(kDrawHasUserIndices); MGPIndexBuffer binding{}; @@ -115,8 +122,14 @@ namespace MobileGL::MG_Pipe { if (attribute.Offset == 0 || byteCount > std::numeric_limits::max() - attribute.Offset) return false; const auto* source = reinterpret_cast(attribute.Offset); + const Uint64 first = referenced.front(); if (stride == 0) { std::memcpy(bytes.data(), source, elementBytes); + } else if (last - first + 1 == referenced.size()) { + // Contiguous elements (every array draw, most indexed ones): the vertex + // records from the first fetched one to the end of the last, in one copy. + const SizeT begin = static_cast(first * stride); + std::memcpy(bytes.data() + begin, source + begin, byteCount - begin); } else { // Only dereference fetched elements: sparse indices and first>0 // do not grant permission to read intervening application memory. @@ -126,7 +139,7 @@ namespace MobileGL::MG_Pipe { } } } - m_vertexBuffers[location] = MakeOwnedBuffer(bytes.data(), bytes.size(), BufferTarget::Vertex); + m_vertexBuffers[location] = MakeOwnedBuffer(m_context, bytes.data(), bytes.size(), BufferTarget::Vertex); handles[location] = MGPipeResourceTrackerInstance().Find(*m_vertexBuffers[location]); } MGPipeVertexInputEmitterInstance().EmitVertexBuffers(m_context, info.StartInstance, &handles); @@ -173,8 +186,11 @@ namespace MobileGL::MG_Pipe { return attribute.Size > 0 && attribute.Size <= 4 ? component * attribute.Size : 0; } - static UniquePtr MakeOwnedBuffer(const void* bytes, SizeT size, BufferTarget target) { - auto buffer = MakeUnique(0); + static UniquePtr MakeOwnedBuffer(Context& context, const void* bytes, SizeT size, BufferTarget target) { + // A kept buffer is already a published resource: respecifying it is fire-and-forget, + // where a new one costs a blocking create (and a destroy after the draw). + auto buffer = context.GetShareGroup()->TakeClientArrayBuffer(); + if (!buffer) buffer = MakeUnique(0); const auto handle = MGPipeResourceTrackerInstance().Find(*buffer); MGPipeResourceTrackerInstance().NoteBoundAs(handle, target); buffer->SetUsage(BufferUsage::StreamDraw); diff --git a/MobileGL/MG_State/GLState/ShareGroupState.cpp b/MobileGL/MG_State/GLState/ShareGroupState.cpp index 78528f0e..a87a2fa1 100644 --- a/MobileGL/MG_State/GLState/ShareGroupState.cpp +++ b/MobileGL/MG_State/GLState/ShareGroupState.cpp @@ -187,4 +187,26 @@ namespace MobileGL::MG_State::GLState { Bool ShareGroupState::ValidateRenderbufferObject(Uint index) const { return m_renderbufferObjects.find(index) != m_renderbufferObjects.end(); } + UniquePtr ShareGroupState::TakeClientArrayBuffer() { + const std::lock_guard lock(m_clientArrayBuffersMutex); + if (m_clientArrayBuffers.empty()) return nullptr; + UniquePtr buffer = std::move(m_clientArrayBuffers.back()); + m_clientArrayBuffers.pop_back(); + return buffer; + } + + void ShareGroupState::ReturnClientArrayBuffer(UniquePtr buffer) { + // Enough for a draw's every attribute plus its indices; more is never in use at once. + constexpr SizeT kKeptClientArrayBuffers = 48; + if (!buffer) return; + { + const std::lock_guard lock(m_clientArrayBuffersMutex); + if (m_clientArrayBuffers.size() < kKeptClientArrayBuffers) { + m_clientArrayBuffers.push_back(std::move(buffer)); + return; + } + } + // A surplus buffer is destroyed outside the lock (its destructor emits). + } + } // namespace MobileGL::MG_State::GLState diff --git a/MobileGL/MG_State/GLState/ShareGroupState.h b/MobileGL/MG_State/GLState/ShareGroupState.h index 2f0785b0..1082c48a 100644 --- a/MobileGL/MG_State/GLState/ShareGroupState.h +++ b/MobileGL/MG_State/GLState/ShareGroupState.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include "BufferState/BufferObject.h" #include "ProgramState/ProgramState.h" @@ -67,6 +68,12 @@ namespace MobileGL::MG_State::GLState { ProgramState& Programs() { return m_programState; } const ProgramState& Programs() const { return m_programState; } + // Unnamed buffers that carried a draw's client-memory arrays, kept for the next such draw + // so it respecifies one of them (a non-blocking record) instead of creating a resource + // (a round trip) and destroying it again. Buffers are share-group objects, hence here. + UniquePtr TakeClientArrayBuffer(); + void ReturnClientArrayBuffer(UniquePtr buffer); + private: Uint64 m_token = 0; UnorderedMap> m_bufferObjects; @@ -77,5 +84,7 @@ namespace MobileGL::MG_State::GLState { IndexGenerator m_renderbufferNames{1024, 1}; SamplerState m_samplerState; ProgramState m_programState; + std::mutex m_clientArrayBuffersMutex; + Vector> m_clientArrayBuffers; }; } // namespace MobileGL::MG_State::GLState