Files
MobileGL/MobileGL/MG_Impl/Pipe/OwnedDrawInputs.h
T
swung0x48 62bfe4615d [MG_Impl, MG_Backend] (Disaggregated): the monolith arm fetches the client arrays a draw names
The wire arm snapshots the elements a draw fetches out of the application's own
vertex/index memory (beba0256's MGPipeOwnedDrawInputs). The monolith arm's upload
answered only the non-indexed family's contiguous (first, count) run, so an
INDEXED or INDIRECT draw over a client-memory array staged nothing: the enabled
array kept whatever the ES context last held for it - normally disabled, i.e. every
vertex reading the generic current value - and the draw painted nothing with no GL
error anywhere.

ClientFetchPlan.h is that question factored out once (the fetches, the referenced
vertices, the per-divisor element sets) so both arms answer it from one place;
OwnedDrawInputs now calls it and keeps its behaviour byte for byte.

DirectGLES: SyncClientSideAttributesForDraw stages the sparse snapshot for a draw
whose elements the (first, count) run does not describe, driven by the plan; the
indexed entry points and both indirect executors ask for it, and the arrays path
learns the baseInstance shift a divisor'd client array needs.

W3: DirectGLES' six monolith failures are the four indexed/indirect shapes.
2026-09-20 20:47:50 -04:00

195 lines
10 KiB
C++

// MobileGL - MobileGL/MG_Impl/Pipe/OwnedDrawInputs.h
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
//
// Client memory becomes ordinary owned buffer resources before a draw.
#pragma once
#if MOBILEGL_BUILD_DISAGGREGATED && MOBILEGL_PIPE_PUSH
#include <MG_Impl/Pipe/ClientFetchPlan.h>
#include <MG_Impl/Pipe/VertexInputEmit.h>
#include <MG_State/GLState/BufferState/BufferObject.h>
#include <algorithm>
#include <cstring>
#include <limits>
namespace MobileGL::MG_Pipe {
// These private buffers have no GL name or frontend binding. Their normal
// resource records own the bytes on the server, and normal resource retirement
// keeps an already submitted draw alive after this client-side scope ends.
// Restore the application's wire bindings before destroying the private handles.
class MGPipeOwnedDrawInputs {
public:
using Buffer = MG_State::GLState::BufferObject;
using Context = MG_State::GLState::GLContext;
explicit MGPipeOwnedDrawInputs(Context& context) : m_context(context) {}
~MGPipeOwnedDrawInputs() {
if (m_vertexBindingsChanged)
MGPipeVertexInputEmitterInstance().EmitVertexBuffers(m_context, m_baseInstance);
if (m_indexBindingChanged)
MGPipeVertexInputEmitterInstance().EmitIndexBuffer(m_context);
}
Bool Prepare(MGPDrawInfo& info, const MGPDrawRange* ranges, Uint32 rangeCount,
const void* clientIndices, Uint64 clientIndexBytes, const MGPDrawIndirect* indirect) {
m_baseInstance = info.StartInstance;
const auto& vao = m_context.GetBoundVertexArray();
if (!vao) return (info.Flags & kDrawClientArrays) == 0 && clientIndexBytes == 0;
// A single owned EBO also represents flattened MultiDraw client indices.
// 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);
info.IndexResource = MGPipeResourceTrackerInstance().Find(*m_indexBuffer);
info.Flags &= ~static_cast<Uint8>(kDrawHasUserIndices);
MGPIndexBuffer binding{};
binding.Res = info.IndexResource;
binding.IndexSize = info.IndexSize;
MGPipeRouteSetIndexBuffer(binding);
m_indexBindingChanged = true;
}
if ((info.Flags & kDrawClientArrays) == 0) return true;
const auto& attributes = vao->GetAllAttributes();
Bool needsVertexIndices = false;
for (const auto& attribute : attributes)
needsVertexIndices |= attribute.Enabled && !attribute.Buffer && attribute.Divisor == 0;
// The element, command and parameter buffers are read through one reader for
// WHICH elements this draw fetches, and that question lives in
// ClientFetchPlan.h - the monolith arm asks the same one of the same bytes.
const SharedPtr<Buffer> elementBuffer = vao->GetIndexBufferBindingSlot().GetBoundObject();
const SharedPtr<Buffer> commandBuffer =
m_context.GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
const SharedPtr<Buffer> parameterBuffer =
m_context.GetBufferBindingSlot(BufferTarget::Parameter).GetBoundObject();
ByteSources sources{&elementBuffer, &commandBuffer, &parameterBuffer};
MGPipeClientDrawInputs inputs{};
inputs.IndexSize = info.IndexSize;
inputs.Ranges = ranges;
inputs.RangeCount = rangeCount;
inputs.InstanceCount = info.InstanceCount;
inputs.BaseInstance = info.StartInstance;
inputs.PrimitiveRestart = (info.Flags & kDrawPrimitiveRestart) != 0;
const Bool fixedRestart = m_context.IsCapabilityEnabled(CapabilityInput::PrimitiveRestartFixedIndex);
inputs.RestartIndex = fixedRestart
? (info.IndexSize == 1 ? 0xffu : info.IndexSize == 2 ? 0xffffu : 0xffffffffu)
: info.RestartIndex;
inputs.ClientIndices = clientIndices;
inputs.ClientIndexBytes = clientIndexBytes;
inputs.WantVertices = needsVertexIndices;
MGPipeClientIndirect indirectInputs{};
if (indirect != nullptr) {
indirectInputs.Offset = indirect->Offset;
indirectInputs.ParameterOffset = indirect->ParameterOffset;
indirectInputs.Stride = indirect->Stride;
indirectInputs.DrawCount = indirect->DrawCount;
indirectInputs.HasParameterBuffer = !MGPipeHandleIsNull(indirect->ParameterBuffer);
inputs.Indirect = &indirectInputs;
}
if (!m_plan.Build(inputs, &ReadBytes, &sources)) return false;
Array<MGPipeHandle, kMGPipeMaxVertexAttribs> handles{};
Vector<Uint64> referenced;
for (SizeT location = 0; location < attributes.size(); ++location) {
const auto& attribute = attributes[location];
if (!attribute.Enabled || attribute.Buffer) continue;
const SizeT elementBytes = AttributeBytes(attribute);
if (!elementBytes || attribute.Stride < 0) return false;
if (!m_plan.Elements(static_cast<Uint32>(attribute.Divisor), referenced)) return false;
const Uint64 stride = static_cast<Uint32>(attribute.Stride);
const Uint64 last = referenced.empty() ? 0 : referenced.back();
if (stride != 0 && last > (std::numeric_limits<Uint32>::max() - elementBytes) / stride)
return false;
const SizeT byteCount = static_cast<SizeT>(last * stride + elementBytes);
Vector<Uint8> bytes(byteCount, 0);
if (!referenced.empty()) {
if (attribute.Offset == 0 || byteCount > std::numeric_limits<SizeT>::max() - attribute.Offset)
return false;
const auto* source = reinterpret_cast<const Uint8*>(attribute.Offset);
if (stride == 0) {
std::memcpy(bytes.data(), source, elementBytes);
} else {
// Only dereference fetched elements: sparse indices and first>0
// do not grant permission to read intervening application memory.
for (const Uint64 vertex : referenced) {
const SizeT offset = static_cast<SizeT>(vertex * stride);
std::memcpy(bytes.data() + offset, source + offset, elementBytes);
}
}
}
m_vertexBuffers[location] = MakeOwnedBuffer(bytes.data(), bytes.size(), BufferTarget::Vertex);
handles[location] = MGPipeResourceTrackerInstance().Find(*m_vertexBuffers[location]);
}
MGPipeVertexInputEmitterInstance().EmitVertexBuffers(m_context, info.StartInstance, &handles);
m_vertexBindingsChanged = true;
info.Flags &= ~static_cast<Uint8>(kDrawClientArrays);
return true;
}
private:
// The three buffers one draw can fetch bytes out of, and the reader that resolves
// them. `user` is a ByteSources owned by the caller's scope, so nothing here outlives
// the Prepare that built it.
struct ByteSources {
const SharedPtr<Buffer>* Elements = nullptr;
const SharedPtr<Buffer>* Commands = nullptr;
const SharedPtr<Buffer>* Parameters = nullptr;
};
static Bool ReadBytes(void* user, MGPipeClientBufferKind kind, Uint64 offset, SizeT size, void* destination) {
const auto& sources = *static_cast<const ByteSources*>(user);
switch (kind) {
case MGPipeClientBufferKind::Element:
return MGPipeReadBufferShadow(*sources.Elements, offset, size, destination);
case MGPipeClientBufferKind::IndirectCommand:
return MGPipeReadBufferShadow(*sources.Commands, offset, size, destination);
case MGPipeClientBufferKind::IndirectCount:
return MGPipeReadBufferShadow(*sources.Parameters, offset, size, destination);
}
return false;
}
static SizeT AttributeBytes(const MG_State::GLState::VertexAttribute& attribute) {
if (attribute.IsBgra || attribute.Type == DataType::Int2101010Rev ||
attribute.Type == DataType::Uint2101010Rev) return 4;
SizeT component = 0;
switch (attribute.Type) {
case DataType::Int8: case DataType::Uint8: component = 1; break;
case DataType::Int16: case DataType::Uint16: case DataType::Float16: component = 2; break;
case DataType::Int32: case DataType::Uint32: case DataType::Float32: case DataType::Fixed32:
component = 4; break;
case DataType::Float64: component = 8; break;
default: return 0;
}
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);
const auto handle = MGPipeResourceTrackerInstance().Find(*buffer);
MGPipeResourceTrackerInstance().NoteBoundAs(handle, target);
buffer->SetUsage(BufferUsage::StreamDraw);
buffer->Respecify(size, bytes);
return buffer;
}
Context& m_context;
MGPipeClientFetchPlan m_plan;
Array<UniquePtr<Buffer>, kMGPipeMaxVertexAttribs> m_vertexBuffers{};
UniquePtr<Buffer> m_indexBuffer;
Uint32 m_baseInstance = 0;
Bool m_vertexBindingsChanged = false;
Bool m_indexBindingChanged = false;
};
} // namespace MobileGL::MG_Pipe
#endif