[Perf] (Pipe): reuse client-array buffers instead of a blocking create per draw, and copy contiguous ranges in one go

This commit is contained in:
2026-10-04 04:54:54 -04:00
parent 66b47a101b
commit 2de0d6dd25
4 changed files with 54 additions and 5 deletions
+3 -1
View File
@@ -234,7 +234,9 @@ namespace MobileGL::MG_Pipe {
m_vertices.push_back(static_cast<Uint64>(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;
}
+20 -4
View File
@@ -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<Uint32>::max()) return false;
m_indexBuffer = MakeOwnedBuffer(clientIndices, static_cast<SizeT>(clientIndexBytes), BufferTarget::Index);
m_indexBuffer = MakeOwnedBuffer(m_context, clientIndices, static_cast<SizeT>(clientIndexBytes),
BufferTarget::Index);
info.IndexResource = MGPipeResourceTrackerInstance().Find(*m_indexBuffer);
info.Flags &= ~static_cast<Uint8>(kDrawHasUserIndices);
MGPIndexBuffer binding{};
@@ -115,8 +122,14 @@ namespace MobileGL::MG_Pipe {
if (attribute.Offset == 0 || byteCount > std::numeric_limits<SizeT>::max() - attribute.Offset)
return false;
const auto* source = reinterpret_cast<const Uint8*>(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<SizeT>(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<Buffer> MakeOwnedBuffer(const void* bytes, SizeT size, BufferTarget target) {
auto buffer = MakeUnique<Buffer>(0);
static UniquePtr<Buffer> 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<Buffer>(0);
const auto handle = MGPipeResourceTrackerInstance().Find(*buffer);
MGPipeResourceTrackerInstance().NoteBoundAs(handle, target);
buffer->SetUsage(BufferUsage::StreamDraw);
@@ -187,4 +187,26 @@ namespace MobileGL::MG_State::GLState {
Bool ShareGroupState::ValidateRenderbufferObject(Uint index) const {
return m_renderbufferObjects.find(index) != m_renderbufferObjects.end();
}
UniquePtr<BufferObject> ShareGroupState::TakeClientArrayBuffer() {
const std::lock_guard<std::mutex> lock(m_clientArrayBuffersMutex);
if (m_clientArrayBuffers.empty()) return nullptr;
UniquePtr<BufferObject> buffer = std::move(m_clientArrayBuffers.back());
m_clientArrayBuffers.pop_back();
return buffer;
}
void ShareGroupState::ReturnClientArrayBuffer(UniquePtr<BufferObject> 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<std::mutex> 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
@@ -8,6 +8,7 @@
#pragma once
#include <Includes.h>
#include <mutex>
#include <MG_Util/Miscellany/IndexGenerator.h>
#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<BufferObject> TakeClientArrayBuffer();
void ReturnClientArrayBuffer(UniquePtr<BufferObject> buffer);
private:
Uint64 m_token = 0;
UnorderedMap<Uint, SharedPtr<BufferObject>> m_bufferObjects;
@@ -77,5 +84,7 @@ namespace MobileGL::MG_State::GLState {
IndexGenerator<Uint> m_renderbufferNames{1024, 1};
SamplerState m_samplerState;
ProgramState m_programState;
std::mutex m_clientArrayBuffersMutex;
Vector<UniquePtr<BufferObject>> m_clientArrayBuffers;
};
} // namespace MobileGL::MG_State::GLState