mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[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.
This commit is contained in:
@@ -2422,6 +2422,153 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// `static` on purpose (G1): the pull build must not gain an exported name for a
|
||||
// function that only re-homes two identical inline blocks the two DrawArrays entry
|
||||
// points used to carry.
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// ---- W3: THE MONOLITH ARM'S CLIENT-MEMORY SNAPSHOT, PER DRAW -------------------------
|
||||
//
|
||||
// A client-memory attribute is uploaded HERE because its bytes have no store on this side
|
||||
// to bind. SyncClientSideVertexArraysForDrawArrays below does that for the non-indexed
|
||||
// family, whose fetched elements are exactly the contiguous (first, count) run. Every
|
||||
// OTHER draw shape fetches by index and by instance, so its elements have to come from
|
||||
// MGPipeClientFetchPlan - the same answer the wire arm's owned snapshot is built from
|
||||
// (MG_Impl/Pipe/OwnedDrawInputs.h, which is why the two arms agree by construction).
|
||||
//
|
||||
// WHAT WAS MISSING BEFORE THIS: an indexed draw over a client array uploaded nothing at
|
||||
// all. The ES context kept whatever the last VBO-backed attribute had left for that
|
||||
// index - normally the array DISABLED, i.e. every vertex reading the generic current
|
||||
// value - so the draw painted nothing, with no GL error anywhere to say why.
|
||||
struct ClientSnapshotSources {
|
||||
const SharedPtr<MG_State::GLState::BufferObject>* ElementBuffer = nullptr;
|
||||
};
|
||||
|
||||
Bool ReadClientSnapshotBytes(void* user, MG_Pipe::MGPipeClientBufferKind kind, Uint64 offset, SizeT size,
|
||||
void* destination) {
|
||||
if (kind != MG_Pipe::MGPipeClientBufferKind::Element) return false;
|
||||
const auto& buffer = *static_cast<const ClientSnapshotSources*>(user)->ElementBuffer;
|
||||
if (!buffer || offset > buffer->GetSize() || size > buffer->GetSize() - offset) return false;
|
||||
// A shader-written index stream is exactly the case this snapshot exists for, so the
|
||||
// object's shadow is reconciled before it is read (SyncGpuWrites is a no-op otherwise).
|
||||
buffer->SyncGpuWrites();
|
||||
buffer->DownloadSubData(destination, static_cast<SizeT>(offset), size);
|
||||
return true;
|
||||
}
|
||||
|
||||
// The draw's own fetch, spelled as the range record spells it: `indexSize` 0 is arrays,
|
||||
// `elementStart` is the first element (an element offset into the bound element buffer, or
|
||||
// 0 for the application's own index array), and `clientIndices` names that array when
|
||||
// there is one. FALSE means the caller must SKIP the draw: the elements it would fetch are
|
||||
// not in the store the driver is about to read, and issuing it anyway is a wrong picture
|
||||
// rather than an error.
|
||||
Bool SyncClientSideVertexArraysForFetch(Uint8 indexSize, Uint32 elementStart, GLsizei count,
|
||||
GLsizei instanceCount, GLint baseVertex, Uint32 baseInstance,
|
||||
const void* clientIndices, Uint64 clientIndexBytes) {
|
||||
if (count <= 0 || instanceCount <= 0) return true;
|
||||
const auto& currentVAO = MGB_CTX->GetBoundVertexArray();
|
||||
if (!currentVAO) return true;
|
||||
|
||||
// A draw whose attributes all come from buffers has nothing to snapshot, and asking
|
||||
// the plan would read the index stream to answer a question nobody asked.
|
||||
Bool clientArrays = false;
|
||||
Bool clientVertexRate = false;
|
||||
const auto& attributes = currentVAO->GetAllAttributes();
|
||||
for (SizeT i = 0; i < attributes.size(); ++i) {
|
||||
if (!attributes[i].Enabled || attributes[i].Buffer) continue;
|
||||
clientArrays = true;
|
||||
clientVertexRate |= attributes[i].Divisor == 0;
|
||||
}
|
||||
if (!clientArrays) return true;
|
||||
|
||||
auto* twin = ResolveVaoTwin(currentVAO);
|
||||
if (twin == nullptr) return false;
|
||||
|
||||
const SharedPtr<MG_State::GLState::BufferObject> elementBuffer =
|
||||
currentVAO->GetIndexBufferBindingSlot().GetBoundObject();
|
||||
ClientSnapshotSources sources{&elementBuffer};
|
||||
|
||||
MG_Pipe::MGPDrawRange range{elementStart, static_cast<Uint32>(count), baseVertex};
|
||||
MG_Pipe::MGPipeClientDrawInputs inputs{};
|
||||
inputs.IndexSize = indexSize;
|
||||
inputs.Ranges = ⦥
|
||||
inputs.RangeCount = 1;
|
||||
inputs.InstanceCount = static_cast<Uint32>(instanceCount);
|
||||
inputs.BaseInstance = baseInstance;
|
||||
inputs.ClientIndices = clientIndices;
|
||||
inputs.ClientIndexBytes = clientIndexBytes;
|
||||
inputs.WantVertices = clientVertexRate;
|
||||
// The driver does its own restart handling; the plan has to know only that the restart
|
||||
// value is not a vertex, or the snapshot would stage an element no primitive fetches.
|
||||
inputs.PrimitiveRestart = MGB_CTX->IsCapabilityEnabled(CapabilityInput::PrimitiveRestart) ||
|
||||
MGB_CTX->IsCapabilityEnabled(CapabilityInput::PrimitiveRestartFixedIndex);
|
||||
inputs.RestartIndex = MGB_CTX->IsCapabilityEnabled(CapabilityInput::PrimitiveRestartFixedIndex)
|
||||
? (indexSize == 1 ? 0xffu : indexSize == 2 ? 0xffffu : 0xffffffffu)
|
||||
: MGB_CTX->GetPrimitiveRestartIndex();
|
||||
|
||||
MG_Pipe::MGPipeClientFetchPlan plan;
|
||||
if (!plan.Build(inputs, &ReadClientSnapshotBytes, &sources)) return false;
|
||||
return twin->SyncClientSideAttributesForDraw(currentVAO, plan, baseInstance);
|
||||
}
|
||||
|
||||
// The indirect executors' form: the command's own words ARE the draw's fetch, and a
|
||||
// command block a shader wrote this frame is exactly what these shapes exist for - so the
|
||||
// command buffer's shadow is reconciled before it is read, and `commandBytes` (the
|
||||
// caller's possibly stale copy) is only the fallback for a client-authored block.
|
||||
Bool SyncClientSideVertexArraysForIndirectFetch(const Uint8* commandBytes, SizeT commandOffset,
|
||||
GLsizei stride, GLsizei index, Uint8 indexSize,
|
||||
const SharedPtr<MG_State::GLState::BufferObject>& commandBuffer) {
|
||||
const Uint8* source = commandBytes;
|
||||
if (commandBuffer) {
|
||||
commandBuffer->SyncGpuWrites();
|
||||
const Uint8* mapped = commandBuffer->MappedData();
|
||||
if (mapped == nullptr) return false;
|
||||
source = mapped + commandOffset;
|
||||
}
|
||||
const SizeT at = static_cast<SizeT>(index) * static_cast<SizeT>(stride);
|
||||
if (indexSize == 0) {
|
||||
DrawArraysIndirectCommand cmd{};
|
||||
std::memcpy(&cmd, source + at, sizeof(cmd));
|
||||
return SyncClientSideVertexArraysForFetch(0, cmd.first, static_cast<GLsizei>(cmd.count),
|
||||
static_cast<GLsizei>(cmd.instanceCount), 0, cmd.baseInstance,
|
||||
nullptr, 0);
|
||||
}
|
||||
DrawElementsIndirectCommand cmd{};
|
||||
std::memcpy(&cmd, source + at, sizeof(cmd));
|
||||
return SyncClientSideVertexArraysForFetch(indexSize, cmd.firstIndex, static_cast<GLsizei>(cmd.count),
|
||||
static_cast<GLsizei>(cmd.instanceCount), cmd.baseVertex,
|
||||
static_cast<Uint32>(cmd.baseInstance), nullptr, 0);
|
||||
}
|
||||
|
||||
// The indexed entry points' form. `indices` is a byte offset into the bound element buffer
|
||||
// when one is bound and the application's own array otherwise, which is the same
|
||||
// distinction every one of them already makes when it issues the draw.
|
||||
Bool SyncClientSideVertexArraysForIndexedFetch(GLenum type, GLsizei count, const void* indices,
|
||||
GLsizei instanceCount, GLint baseVertex, GLuint baseInstance) {
|
||||
if (count <= 0 || instanceCount <= 0) return true;
|
||||
const auto& currentVAO = MGB_CTX->GetBoundVertexArray();
|
||||
if (!currentVAO) return true;
|
||||
const Uint8 indexSize = static_cast<Uint8>(MG_Util::GetGLTypeSize(type));
|
||||
if (indexSize == 0) return true;
|
||||
|
||||
const Bool clientIndices = !currentVAO->GetIndexBufferBindingSlot().GetBoundObject();
|
||||
Uint32 elementStart = 0;
|
||||
Uint64 clientIndexBytes = 0;
|
||||
if (clientIndices) {
|
||||
if (indices == nullptr) return false;
|
||||
clientIndexBytes = static_cast<Uint64>(count) * indexSize;
|
||||
} else {
|
||||
const Uint64 byteOffset = reinterpret_cast<Uint64>(indices);
|
||||
if (byteOffset % indexSize != 0 ||
|
||||
byteOffset / indexSize > std::numeric_limits<Uint32>::max()) {
|
||||
MGLOG_E_ONCE("An indexed draw over a client-memory vertex array names byte offset %llu, "
|
||||
"which is not a whole number of %u-byte indices; the draw is skipped",
|
||||
static_cast<unsigned long long>(byteOffset), static_cast<Uint>(indexSize));
|
||||
return false;
|
||||
}
|
||||
elementStart = static_cast<Uint32>(byteOffset / indexSize);
|
||||
}
|
||||
return SyncClientSideVertexArraysForFetch(indexSize, elementStart, count, instanceCount, baseVertex,
|
||||
baseInstance, clientIndices ? indices : nullptr,
|
||||
clientIndexBytes);
|
||||
}
|
||||
#endif
|
||||
static void SyncClientSideVertexArraysForDrawArrays(GLint first, GLsizei count) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (BufferImpl::VertexInputReadsRecords()) {
|
||||
@@ -8269,6 +8416,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
SetCurrentBaseVertex(cmd.baseVertex);
|
||||
}
|
||||
ForEachViewportRoutingPass([&] {
|
||||
if (!VertexArrayImpl::SyncClientSideVertexArraysForIndirectFetch(
|
||||
commandBytes, commandOffset, stride, i, static_cast<Uint8>(indexSize), drawIndirectBuffer))
|
||||
return;
|
||||
g_GLESFuncs.glDrawElementsIndirect(mode, type, reinterpret_cast<const void*>(cmdByteOffset));
|
||||
});
|
||||
}
|
||||
@@ -8284,6 +8434,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
SetCurrentBaseVertex(cmd.baseVertex);
|
||||
const auto indexByteOffset = static_cast<SizeT>(cmd.firstIndex) * indexSize;
|
||||
ForEachViewportRoutingPass([&] {
|
||||
if (!VertexArrayImpl::SyncClientSideVertexArraysForIndirectFetch(
|
||||
commandBytes, commandOffset, stride, i, static_cast<Uint8>(indexSize), drawIndirectBuffer))
|
||||
continue;
|
||||
g_GLESFuncs.glDrawElementsInstancedBaseVertex(
|
||||
mode, static_cast<GLsizei>(cmd.count), type, reinterpret_cast<const GLvoid*>(indexByteOffset),
|
||||
static_cast<GLsizei>(cmd.instanceCount), cmd.baseVertex);
|
||||
@@ -8347,6 +8500,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
SetCurrentBaseInstance(cmd.baseInstance);
|
||||
}
|
||||
ForEachViewportRoutingPass([&] {
|
||||
if (!VertexArrayImpl::SyncClientSideVertexArraysForIndirectFetch(
|
||||
commandBytes, commandOffset, stride, i, 0, drawIndirectBuffer))
|
||||
return;
|
||||
g_GLESFuncs.glDrawArraysIndirect(mode, reinterpret_cast<const void*>(cmdByteOffset));
|
||||
});
|
||||
}
|
||||
@@ -8360,6 +8516,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
SetCurrentDrawID(static_cast<Uint32>(i));
|
||||
SetCurrentBaseInstance(cmd.baseInstance);
|
||||
ForEachViewportRoutingPass([&] {
|
||||
if (!VertexArrayImpl::SyncClientSideVertexArraysForIndirectFetch(
|
||||
commandBytes, commandOffset, stride, i, 0, drawIndirectBuffer))
|
||||
continue;
|
||||
g_GLESFuncs.glDrawArraysInstanced(mode, static_cast<GLint>(cmd.first),
|
||||
static_cast<GLsizei>(cmd.count),
|
||||
static_cast<GLsizei>(cmd.instanceCount));
|
||||
@@ -9045,6 +9204,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) {
|
||||
if (!VertexArrayImpl::SyncClientSideVertexArraysForIndexedFetch(type, count, indices, 1, 0, 0)) return;
|
||||
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER
|
||||
DebugImpl::OpenGLScopeMarker marker(__func__);
|
||||
#endif
|
||||
@@ -9070,6 +9230,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex) {
|
||||
if (!VertexArrayImpl::SyncClientSideVertexArraysForIndexedFetch(type, count, indices, 1, basevertex, 0)) return;
|
||||
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER
|
||||
DebugImpl::OpenGLScopeMarker marker(__func__);
|
||||
#endif
|
||||
@@ -9441,6 +9602,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||
const void* indices, GLint basevertex) {
|
||||
if (!VertexArrayImpl::SyncClientSideVertexArraysForIndexedFetch(type, count, indices, 1, basevertex, 0)) return;
|
||||
DrawSyncFlags syncBit = DrawSyncBit::IndexBuffer;
|
||||
PrepareForDraw(syncBit);
|
||||
const ScopedRestartIndexSubstitution restart(type, count, indices);
|
||||
@@ -9454,6 +9616,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices) {
|
||||
if (!VertexArrayImpl::SyncClientSideVertexArraysForIndexedFetch(type, count, indices, 1, 0, 0)) return;
|
||||
DrawSyncFlags syncBit = DrawSyncBit::IndexBuffer;
|
||||
PrepareForDraw(syncBit);
|
||||
const ScopedRestartIndexSubstitution restart(type, count, indices);
|
||||
@@ -9491,6 +9654,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount, GLint basevertex, GLuint baseinstance) {
|
||||
if (!VertexArrayImpl::SyncClientSideVertexArraysForIndexedFetch(type, count, indices, instancecount, basevertex,
|
||||
baseinstance)) return;
|
||||
DrawSyncFlags syncBit = DrawSyncBit::IndexBuffer | DrawSyncBit::Instancing;
|
||||
MGL_SCOPED_FETCH_BASE_INSTANCE(fetchScope, EmulatedFetchBaseInstance(baseinstance));
|
||||
PrepareForDraw(syncBit);
|
||||
@@ -9514,6 +9679,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
void DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount, GLint basevertex) {
|
||||
if (!VertexArrayImpl::SyncClientSideVertexArraysForIndexedFetch(type, count, indices, instancecount, basevertex,
|
||||
0)) return;
|
||||
DrawSyncFlags syncBit = DrawSyncBit::IndexBuffer | DrawSyncBit::Instancing;
|
||||
PrepareForDraw(syncBit);
|
||||
const ScopedRestartIndexSubstitution restart(type, count, indices);
|
||||
@@ -9528,6 +9695,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount, GLuint baseinstance) {
|
||||
if (!VertexArrayImpl::SyncClientSideVertexArraysForIndexedFetch(type, count, indices, instancecount, 0,
|
||||
baseinstance)) return;
|
||||
DrawSyncFlags syncBit = DrawSyncBit::IndexBuffer | DrawSyncBit::Instancing;
|
||||
MGL_SCOPED_FETCH_BASE_INSTANCE(fetchScope, EmulatedFetchBaseInstance(baseinstance));
|
||||
PrepareForDraw(syncBit);
|
||||
@@ -9546,6 +9715,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount) {
|
||||
if (!VertexArrayImpl::SyncClientSideVertexArraysForIndexedFetch(type, count, indices, instancecount, 0, 0))
|
||||
return;
|
||||
DrawSyncFlags syncBit = DrawSyncBit::IndexBuffer | DrawSyncBit::Instancing;
|
||||
PrepareForDraw(syncBit);
|
||||
const ScopedRestartIndexSubstitution restart(type, count, indices);
|
||||
|
||||
@@ -5545,8 +5545,119 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
#endif // MOBILEGL_PIPE_PUSH
|
||||
|
||||
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// THE CLIENT-ARRAY UPLOAD FOR A DRAW WHOSE FETCHED ELEMENTS (first, count) DOES NOT
|
||||
// DESCRIBE. SyncClientSideAttributesForDrawArrays below answers the non-indexed family
|
||||
// exactly and nothing else: an indexed draw reads the elements its INDICES name, an
|
||||
// instanced draw reads one element per instance (shifted by baseInstance and divided by
|
||||
// divisor), and the indirect forms read a count and a first element that live in GPU
|
||||
// memory. Until this function existed those draws took NO upload at all - the enabled
|
||||
// client attribute kept whatever the ES context last held for it (normally: disabled, so
|
||||
// every vertex read the generic current value), and an indexed draw over a client array
|
||||
// painted nothing.
|
||||
//
|
||||
// `plan` is MGPipeClientFetchPlan's answer for this very draw, which is the same
|
||||
// arithmetic the wire arm snapshots from (MG_Impl/Pipe/OwnedDrawInputs.h) - the two arms
|
||||
// fetch the same elements by construction rather than by inspection.
|
||||
//
|
||||
// THE UPLOAD IS SPARSE: only the fetched elements are dereferenced, and the gaps between
|
||||
// them are zero-filled. Reading the intervening application memory would be legal here -
|
||||
// one thread, one address space - but it is not needed, and a draw naming element 0 and
|
||||
// element 2^20 must not turn into an 8 MiB read of somebody else's heap.
|
||||
//
|
||||
// False means the draw must be SKIPPED: the elements it would fetch are not in the store
|
||||
// the driver is about to read, and issuing it anyway is a wrong picture, not an error.
|
||||
Bool BackendVertexArrayObject::SyncClientSideAttributesForDraw(
|
||||
const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject,
|
||||
const MG_Pipe::MGPipeClientFetchPlan& plan, Uint32 fetchBaseInstance) {
|
||||
if (!stateVAOObject) return true;
|
||||
|
||||
Bind();
|
||||
|
||||
const auto& allAttributes = stateVAOObject->GetAllAttributes();
|
||||
Vector<Uint64> elements;
|
||||
for (Uint attribIndex = 0; attribIndex < allAttributes.size(); ++attribIndex) {
|
||||
const auto& attrib = allAttributes[attribIndex];
|
||||
if (!attrib.Enabled || attrib.Buffer) continue;
|
||||
|
||||
// The same outcome SyncToBackend reaches for a 64-bit client array it cannot
|
||||
// narrow: the enabled array is DROPPED (the attribute reads its generic current
|
||||
// value) rather than left enabled with no pointer behind it, which is the Adreno
|
||||
// null-deref. The narrowing stream itself stays the arrays path's answer.
|
||||
if (attrib.IsLong || attrib.Type == DataType::Float64) {
|
||||
g_GLESFuncs.glDisableVertexAttribArray(attribIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!plan.Elements(static_cast<Uint32>(attrib.Divisor), elements)) return false;
|
||||
if (elements.empty()) continue;
|
||||
|
||||
const auto* source = reinterpret_cast<const Uint8*>(attrib.Offset);
|
||||
const SizeT elementSize = GetAttributeByteSize(attrib.Type, attrib.Size, attrib.IsBgra);
|
||||
if (source == nullptr || elementSize == 0 || attrib.Size <= 0) {
|
||||
MGLOG_E_ONCE("Client attribute %u has no readable element size (%zu); the draw is skipped",
|
||||
attribIndex, elementSize);
|
||||
return false;
|
||||
}
|
||||
const SizeT stride = attrib.Stride > 0 ? static_cast<SizeT>(attrib.Stride) : elementSize;
|
||||
const Uint64 last = elements.back();
|
||||
if (stride != 0 && last > (std::numeric_limits<SizeT>::max() - elementSize) / stride) return false;
|
||||
const SizeT byteCount = static_cast<SizeT>(last * stride + elementSize);
|
||||
|
||||
auto& bufferId = m_clientAttributeBufferIds[attribIndex];
|
||||
if (bufferId == 0) {
|
||||
g_GLESFuncs.glGenBuffers(1, &bufferId);
|
||||
if (bufferId == 0) {
|
||||
MGLOG_E_ONCE("Failed to create client-side vertex attribute upload buffer.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Vector<Uint8> snapshot(byteCount, 0);
|
||||
for (const Uint64 element : elements) {
|
||||
const SizeT offset = static_cast<SizeT>(element * stride);
|
||||
std::memcpy(snapshot.data() + offset, source + offset, elementSize);
|
||||
}
|
||||
|
||||
BufferImpl::BindBufferId(GL_ARRAY_BUFFER, bufferId);
|
||||
g_GLESFuncs.glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(snapshot.size()),
|
||||
snapshot.data(), GL_STREAM_DRAW);
|
||||
if (MG_Util::PipeStats::Enabled()) {
|
||||
MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageVertexClient,
|
||||
static_cast<Uint64>(snapshot.size()));
|
||||
}
|
||||
|
||||
// Element 0 of a client array sits at its buffer's byte 0, so a divisor'd array's
|
||||
// baseInstance shift is BaseInstanceByteShift - the same arithmetic SyncToBackend
|
||||
// applies to a VBO-backed array - and it rides in the pointer argument.
|
||||
const void* const shift =
|
||||
reinterpret_cast<const void*>(BaseInstanceByteShift(attrib, fetchBaseInstance));
|
||||
if (!attrib.IsInteger) {
|
||||
const GLint glSize = attrib.IsBgra ? static_cast<GLint>(GL_BGRA) : attrib.Size;
|
||||
g_GLESFuncs.glVertexAttribPointer(attribIndex, glSize,
|
||||
MG_Util::ConvertDataTypeToGLEnum(attrib.Type),
|
||||
attrib.Normalized ? GL_TRUE : GL_FALSE,
|
||||
static_cast<GLsizei>(stride), shift);
|
||||
} else {
|
||||
g_GLESFuncs.glVertexAttribIPointer(attribIndex, attrib.Size,
|
||||
MG_Util::ConvertDataTypeToGLEnum(attrib.Type),
|
||||
static_cast<GLsizei>(stride), shift);
|
||||
}
|
||||
// The divisor is NOT optional here. SyncToBackend's own glVertexAttribDivisor sits
|
||||
// behind BindAttributeBuffer, which a client-memory attribute never passes, so
|
||||
// until this line the ES context's divisor for this index is whatever the last
|
||||
// VBO-backed attribute left there.
|
||||
g_GLESFuncs.glVertexAttribDivisor(attribIndex, attrib.Divisor);
|
||||
g_GLESFuncs.glEnableVertexAttribArray(attribIndex);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
void BackendVertexArrayObject::SyncClientSideAttributesForDrawArrays(
|
||||
const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject, GLint first, GLsizei count) {
|
||||
const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject, GLint first, GLsizei count,
|
||||
Uint32 fetchBaseInstance) {
|
||||
if (!stateVAOObject || count <= 0 || first < 0) {
|
||||
return;
|
||||
}
|
||||
@@ -5606,7 +5717,18 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// forwarded here either.
|
||||
g_GLESFuncs.glVertexAttribPointer(attribIndex, attrib.Size, GL_FLOAT, GL_FALSE,
|
||||
static_cast<GLsizei>(componentCount * sizeof(Float)),
|
||||
nullptr);
|
||||
// The converted stream is tightly packed, so a divisor'd array's
|
||||
// baseInstance shift is a whole number of CONVERTED elements -
|
||||
// not BaseInstanceByteShift's source-stride arithmetic.
|
||||
reinterpret_cast<const void*>(
|
||||
attrib.Divisor == 0 ? 0u : static_cast<SizeT>(fetchBaseInstance) *
|
||||
componentCount *
|
||||
sizeof(Float)));
|
||||
// glVertexAttribPointer's final argument IS that shift. A client array
|
||||
// declared through the pointer API keeps element 0 at the buffer's byte 0, so
|
||||
// the shift rides in the pointer argument the call above passes.
|
||||
// The float64 narrowing itself is shared with SyncToBackend's conversion
|
||||
// branch; only the fetch RANGE differs, and the draw's is the accurate one.
|
||||
g_GLESFuncs.glEnableVertexAttribArray(attribIndex);
|
||||
continue;
|
||||
}
|
||||
@@ -5641,11 +5763,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
const GLint glSize = attrib.IsBgra ? static_cast<GLint>(GL_BGRA) : attrib.Size;
|
||||
g_GLESFuncs.glVertexAttribPointer(
|
||||
attribIndex, glSize, MG_Util::ConvertDataTypeToGLEnum(attrib.Type),
|
||||
attrib.Normalized ? GL_TRUE : GL_FALSE, static_cast<GLsizei>(stride), nullptr);
|
||||
attrib.Normalized ? GL_TRUE : GL_FALSE, static_cast<GLsizei>(stride),
|
||||
// A client array's element 0 sits at its buffer's byte 0, so the
|
||||
// divisor'd arrays' baseInstance shift is exactly BaseInstanceByteShift -
|
||||
// the same arithmetic SyncToBackend applies to a VBO-backed array.
|
||||
reinterpret_cast<const void*>(BaseInstanceByteShift(attrib, fetchBaseInstance)));
|
||||
} else {
|
||||
g_GLESFuncs.glVertexAttribIPointer(attribIndex, attrib.Size,
|
||||
MG_Util::ConvertDataTypeToGLEnum(attrib.Type),
|
||||
static_cast<GLsizei>(stride), nullptr);
|
||||
static_cast<GLsizei>(stride),
|
||||
// See the pointer call above: the divisor'd
|
||||
// arrays' baseInstance shift is the pointer.
|
||||
reinterpret_cast<const void*>(
|
||||
BaseInstanceByteShift(attrib, fetchBaseInstance)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P3a: the vertex-input payload views the handle arm of the VAO twin consumes.
|
||||
#include <MG_Pipe/MGPipeTypes.h>
|
||||
// The client-memory fetch plan: which elements of an application-owned array a draw reads.
|
||||
// The monolith arm answers it here (SyncClientSideAttributesForDraw) and the wire arm asks
|
||||
// the same question of the same header (MG_Impl/Pipe/OwnedDrawInputs.h).
|
||||
#include <MG_Impl/Pipe/ClientFetchPlan.h>
|
||||
// P4a: the RECORDS the five re-keyed twins read instead of the frontend object. The readers
|
||||
// below hand back pointers to them, and MGPipeResourceRecord::PendingUpload is a nested type,
|
||||
// so a forward declaration would not do. Push-only, like everything else P4a adds to this
|
||||
@@ -1339,7 +1343,23 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
void SyncToBackendFromApplier();
|
||||
#endif
|
||||
void SyncClientSideAttributesForDrawArrays(
|
||||
const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject, GLint first, GLsizei count);
|
||||
const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject, GLint first, GLsizei count,
|
||||
Uint32 fetchBaseInstance = 0);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// THE SAME UPLOAD FOR A DRAW WHOSE FETCHED ELEMENTS THE (first, count) RANGE DOES NOT
|
||||
// DESCRIBE: an indexed draw reads the elements its indices name, an instanced one one
|
||||
// element per instance, and the indirect forms a range that lives in GPU memory.
|
||||
// `plan` is MGPipeClientFetchPlan's answer for this very draw, so this arm and the
|
||||
// wire arm's owned snapshot fetch the same elements by construction.
|
||||
//
|
||||
// False when a client array could not be snapshotted (an index or command block this
|
||||
// side cannot read, a fetch range that cannot be represented). The caller must then
|
||||
// SKIP the draw: issuing it would have the shader read whatever the ES context last
|
||||
// held for that attribute, which is a wrong picture rather than an error.
|
||||
Bool SyncClientSideAttributesForDraw(
|
||||
const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject,
|
||||
const MG_Pipe::MGPipeClientFetchPlan& plan, Uint32 fetchBaseInstance);
|
||||
#endif
|
||||
Uint GetBackendVertexArrayId() const { return m_backendVAOId; }
|
||||
void Bind() const;
|
||||
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
// MobileGL - MobileGL/MG_Impl/Pipe/ClientFetchPlan.h
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
//
|
||||
// WHICH ELEMENTS OF A CLIENT-MEMORY ARRAY A DRAW ACTUALLY FETCHES.
|
||||
//
|
||||
// A client-memory vertex array has no extent the object could report, so "how much of it
|
||||
// has to exist before this draw can run" is a question only the DRAW can answer: the
|
||||
// index bytes it will read, the instance count and base instance it shifts by, and - for
|
||||
// the indirect forms - the command block a shader may have written moments earlier.
|
||||
//
|
||||
// Two roles ask it and for the same reason. The wire arm asks it to snapshot the fetched
|
||||
// elements into owned buffers before the draw crosses (MGPipeOwnedDrawInputs), and the
|
||||
// monolith arm asks it to size the per-draw upload the backend has always done from the
|
||||
// application's own pointer (DirectGLES' SyncClientSideAttributesForDraw*, Magma's
|
||||
// UploadAndBindVertexBuffers). The question is identical, so the answer lives here once:
|
||||
// two copies of this arithmetic would be two chances to disagree about which vertices a
|
||||
// draw reads, and disagreeing is a wrong picture rather than an error.
|
||||
//
|
||||
// WHAT IT DELIBERATELY DOES NOT DO: dereference application memory. The plan names element
|
||||
// INDICES; reading the bytes behind them is the caller's, because only the caller knows
|
||||
// whether the elements are read from the client's own array or out of a store the server
|
||||
// owns.
|
||||
#pragma once
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED && MOBILEGL_PIPE_PUSH
|
||||
#include <MG_Pipe/MGPipeTypes.h>
|
||||
#include <MG_State/GLState/BufferState/BufferObject.h>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
namespace MobileGL::MG_Pipe {
|
||||
|
||||
// The indirect block as a role that reads GL state can state it: the byte geometry plus
|
||||
// whether the GL_PARAMETER_BUFFER of the *IndirectCount forms is bound. MGPDrawIndirect's
|
||||
// two handle fields are the wire's identity and are meaningless to the monolith arm, which
|
||||
// resolves the very same bytes out of the frontend binding slots.
|
||||
struct MGPipeClientIndirect {
|
||||
Uint64 Offset = 0;
|
||||
Uint64 ParameterOffset = 0;
|
||||
Uint32 Stride = 0;
|
||||
Uint32 DrawCount = 0;
|
||||
Bool HasParameterBuffer = false;
|
||||
};
|
||||
|
||||
// The draw's own shape, spelled the way the range record spells it: arrays carry Start =
|
||||
// first and no index size, indexed carries Start = the element offset into the element
|
||||
// buffer and IndexBias = base vertex.
|
||||
struct MGPipeClientDrawInputs {
|
||||
Uint8 IndexSize = 0;
|
||||
const MGPDrawRange* Ranges = nullptr;
|
||||
Uint32 RangeCount = 0;
|
||||
Uint32 InstanceCount = 1;
|
||||
Uint32 BaseInstance = 0;
|
||||
// True when primitive restart is live and RestartIndex is the value the index stream
|
||||
// restarts on. A restart index is a marker, not a vertex: it is what the mono arm's
|
||||
// restart substitution rewrites around, and counting it as a fetched element would
|
||||
// copy an element no primitive ever reads.
|
||||
Bool PrimitiveRestart = false;
|
||||
Uint32 RestartIndex = 0;
|
||||
// The application's own index array, when no element buffer is bound. Null with
|
||||
// IndexSize != 0 means "the indices come from the bound element buffer", i.e. through
|
||||
// the reader.
|
||||
const void* ClientIndices = nullptr;
|
||||
Uint64 ClientIndexBytes = 0;
|
||||
const MGPipeClientIndirect* Indirect = nullptr;
|
||||
// Whether a vertex-rate (divisor == 0) client array is present. A draw whose client
|
||||
// arrays are all instance-rate never has to read the index stream to answer "which
|
||||
// elements", so the element buffer is left alone (and the reader is never called with
|
||||
// BufferKind::Element).
|
||||
Bool WantVertices = false;
|
||||
};
|
||||
|
||||
// Where a byte request is aimed. Both kinds are buffers a GL context names; they are
|
||||
// distinguished because the two roles resolve them from different places and a reader
|
||||
// that answered one for the other would size an upload off the wrong bytes.
|
||||
enum class MGPipeClientBufferKind : Uint8 {
|
||||
Element, // the bound GL_ELEMENT_ARRAY_BUFFER, for the index stream
|
||||
IndirectCommand,// the bound GL_DRAW_INDIRECT_BUFFER, for the command block
|
||||
IndirectCount, // the bound GL_PARAMETER_BUFFER, for an *IndirectCount form's count
|
||||
};
|
||||
|
||||
// Reads `size` bytes at `offset` of the named buffer on the CPU. False when the bytes do
|
||||
// not exist on this side, which is a refusal rather than a guess.
|
||||
using MGPipeClientBufferReader = Bool (*)(void* user, MGPipeClientBufferKind kind, Uint64 offset, SizeT size,
|
||||
void* destination);
|
||||
|
||||
// One sub-draw's fetch: the element range it reads, how many instances it runs, and the
|
||||
// raw base instance it shifts by.
|
||||
struct MGPipeClientFetch {
|
||||
MGPDrawRange Range;
|
||||
Uint32 Instances = 1;
|
||||
Uint32 BaseInstance = 0;
|
||||
};
|
||||
|
||||
// The reader for a buffer whose bytes this side owns - the wire client's staged shadow and
|
||||
// the monolith arm's frontend object are both read this way. Reconciles a pending GPU write
|
||||
// first, because a shader-written index or command block is exactly the case this plan
|
||||
// exists to handle.
|
||||
inline Bool MGPipeReadBufferShadow(const SharedPtr<MG_State::GLState::BufferObject>& buffer, Uint64 offset,
|
||||
SizeT size, void* destination) {
|
||||
if (!buffer || offset > buffer->GetSize() || size > buffer->GetSize() - offset) return false;
|
||||
buffer->SyncGpuWrites();
|
||||
buffer->DownloadSubData(destination, static_cast<SizeT>(offset), size);
|
||||
return true;
|
||||
}
|
||||
|
||||
class MGPipeClientFetchPlan {
|
||||
public:
|
||||
// False when the draw's fetched elements cannot be determined - an index or command
|
||||
// block this side cannot read, an index stream that leaves the client array, an index
|
||||
// that runs below the base vertex. A caller must then refuse the draw rather than
|
||||
// snapshot a guess: the elements it would leave out are the ones the draw reads.
|
||||
Bool Build(const MGPipeClientDrawInputs& inputs, MGPipeClientBufferReader reader, void* user) {
|
||||
m_inputs = inputs;
|
||||
m_fetches.clear();
|
||||
m_vertices.clear();
|
||||
if (inputs.Indirect != nullptr) {
|
||||
if (!ReadIndirectFetches(inputs, reader, user)) return false;
|
||||
} else {
|
||||
m_fetches.reserve(inputs.RangeCount);
|
||||
for (Uint32 i = 0; i < inputs.RangeCount; ++i) {
|
||||
m_fetches.push_back({inputs.Ranges[i], inputs.InstanceCount, inputs.BaseInstance});
|
||||
}
|
||||
}
|
||||
if (!inputs.WantVertices) return true;
|
||||
return ReadVertices(inputs, reader, user);
|
||||
}
|
||||
|
||||
// The vertex elements the draw fetches through a vertex-rate array, ascending and
|
||||
// unique. Only meaningful after a Build that wanted them.
|
||||
const Vector<Uint64>& Vertices() const { return m_vertices; }
|
||||
|
||||
// The elements an attribute of `divisor` fetches: the vertex-rate set for divisor 0,
|
||||
// and one element per INSTANCE (baseInstance + instance / divisor) otherwise. Sorted
|
||||
// and unique, and `out` is left empty for a draw that fetches nothing.
|
||||
Bool Elements(Uint32 divisor, Vector<Uint64>& out) const {
|
||||
out.clear();
|
||||
if (divisor == 0) {
|
||||
// Vertex rate: every instance reads the same vertices, and the collection above
|
||||
// already deduplicated them across the draw's sub-ranges.
|
||||
out = m_vertices;
|
||||
return true;
|
||||
}
|
||||
for (const auto& fetch : m_fetches) {
|
||||
if (!fetch.Range.Count || !fetch.Instances) continue;
|
||||
const Uint64 first = fetch.BaseInstance;
|
||||
const Uint64 last = first + (fetch.Instances - 1u) / divisor;
|
||||
for (Uint64 at = first; at <= last; ++at) out.push_back(at);
|
||||
}
|
||||
std::sort(out.begin(), out.end());
|
||||
out.erase(std::unique(out.begin(), out.end()), out.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
const Vector<MGPipeClientFetch>& Fetches() const { return m_fetches; }
|
||||
|
||||
private:
|
||||
Bool ReadIndirectFetches(const MGPipeClientDrawInputs& inputs, MGPipeClientBufferReader reader, void* user) {
|
||||
const MGPipeClientIndirect& indirect = *inputs.Indirect;
|
||||
Uint32 count = indirect.DrawCount;
|
||||
if (indirect.HasParameterBuffer) {
|
||||
Uint32 actual = 0;
|
||||
if (reader == nullptr ||
|
||||
!reader(user, MGPipeClientBufferKind::IndirectCount, indirect.ParameterOffset, sizeof(actual),
|
||||
&actual)) {
|
||||
return false;
|
||||
}
|
||||
count = std::min(count, actual);
|
||||
}
|
||||
const Uint64 commandBytes = inputs.IndexSize ? 5 * sizeof(Uint32) : 4 * sizeof(Uint32);
|
||||
const Uint64 stride = indirect.Stride ? indirect.Stride : commandBytes;
|
||||
m_fetches.reserve(count);
|
||||
for (Uint32 i = 0; i < count; ++i) {
|
||||
const Uint64 displacement = static_cast<Uint64>(i) * stride;
|
||||
if (displacement > std::numeric_limits<Uint64>::max() - indirect.Offset) return false;
|
||||
Uint32 words[5]{};
|
||||
if (reader == nullptr ||
|
||||
!reader(user, MGPipeClientBufferKind::IndirectCommand, indirect.Offset + displacement,
|
||||
commandBytes, words)) {
|
||||
return false;
|
||||
}
|
||||
Int32 bias = 0;
|
||||
if (inputs.IndexSize) std::memcpy(&bias, &words[3], sizeof(bias));
|
||||
m_fetches.push_back({{words[2], words[0], bias}, words[1], words[inputs.IndexSize ? 4 : 3]});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// The index stream's own answer. Arrays are contiguous by construction; an indexed
|
||||
// draw has to read the indices to learn which vertices it names, which is the whole
|
||||
// reason a GPU-written element buffer makes this a readback rather than arithmetic.
|
||||
Bool ReadVertices(const MGPipeClientDrawInputs& inputs, MGPipeClientBufferReader reader, void* user) {
|
||||
for (const auto& fetch : m_fetches) {
|
||||
if (!fetch.Range.Count || !fetch.Instances) continue;
|
||||
if (!inputs.IndexSize) {
|
||||
for (Uint64 n = 0; n < fetch.Range.Count; ++n)
|
||||
m_vertices.push_back(static_cast<Uint64>(fetch.Range.Start) + n);
|
||||
continue;
|
||||
}
|
||||
const Uint64 offset = static_cast<Uint64>(fetch.Range.Start) * inputs.IndexSize;
|
||||
const Uint64 size = static_cast<Uint64>(fetch.Range.Count) * inputs.IndexSize;
|
||||
if (size > std::numeric_limits<SizeT>::max()) return false;
|
||||
Vector<Uint8> indexBytes;
|
||||
const Uint8* data = nullptr;
|
||||
if (inputs.ClientIndices != nullptr) {
|
||||
if (offset > inputs.ClientIndexBytes || size > inputs.ClientIndexBytes - offset) return false;
|
||||
data = static_cast<const Uint8*>(inputs.ClientIndices) + static_cast<SizeT>(offset);
|
||||
} else {
|
||||
indexBytes.resize(static_cast<SizeT>(size));
|
||||
if (reader == nullptr ||
|
||||
!reader(user, MGPipeClientBufferKind::Element, offset, indexBytes.size(),
|
||||
indexBytes.data())) {
|
||||
return false;
|
||||
}
|
||||
data = indexBytes.data();
|
||||
}
|
||||
for (Uint32 n = 0; n < fetch.Range.Count; ++n) {
|
||||
Uint32 index = 0;
|
||||
std::memcpy(&index, data + static_cast<SizeT>(n) * inputs.IndexSize, inputs.IndexSize);
|
||||
if (inputs.PrimitiveRestart && index == inputs.RestartIndex) continue;
|
||||
const Int64 vertex = static_cast<Int64>(index) + fetch.Range.IndexBias;
|
||||
if (vertex < 0) return false;
|
||||
m_vertices.push_back(static_cast<Uint64>(vertex));
|
||||
}
|
||||
}
|
||||
std::sort(m_vertices.begin(), m_vertices.end());
|
||||
m_vertices.erase(std::unique(m_vertices.begin(), m_vertices.end()), m_vertices.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
MGPipeClientDrawInputs m_inputs{};
|
||||
Vector<MGPipeClientFetch> m_fetches;
|
||||
Vector<Uint64> m_vertices;
|
||||
};
|
||||
} // namespace MobileGL::MG_Pipe
|
||||
#endif // MOBILEGL_BUILD_DISAGGREGATED && MOBILEGL_PIPE_PUSH
|
||||
@@ -10,6 +10,7 @@
|
||||
#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>
|
||||
@@ -61,43 +62,56 @@ namespace MobileGL::MG_Pipe {
|
||||
for (const auto& attribute : attributes)
|
||||
needsVertexIndices |= attribute.Enabled && !attribute.Buffer && attribute.Divisor == 0;
|
||||
|
||||
Vector<Fetch> fetches;
|
||||
// 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, ¶meterBuffer};
|
||||
|
||||
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) {
|
||||
if (!ReadIndirectFetches(info.IndexSize, *indirect, fetches)) return false;
|
||||
} else {
|
||||
fetches.reserve(rangeCount);
|
||||
for (Uint32 i = 0; i < rangeCount; ++i)
|
||||
fetches.push_back({ranges[i], info.InstanceCount, info.StartInstance});
|
||||
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;
|
||||
|
||||
Vector<Uint64> vertices;
|
||||
if (needsVertexIndices && !ReadVertexIndices(info, fetches, clientIndices, clientIndexBytes, vertices))
|
||||
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;
|
||||
Vector<Uint64> instances;
|
||||
const Vector<Uint64>* referenced = &vertices;
|
||||
if (attribute.Divisor != 0) {
|
||||
for (const auto& fetch : fetches) {
|
||||
if (!fetch.Range.Count || !fetch.Instances) continue;
|
||||
const Uint64 first = fetch.BaseInstance;
|
||||
const Uint64 last = first + (fetch.Instances - 1u) / attribute.Divisor;
|
||||
for (Uint64 at = first; at <= last; ++at) instances.push_back(at);
|
||||
}
|
||||
SortUnique(instances);
|
||||
referenced = &instances;
|
||||
}
|
||||
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();
|
||||
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 (!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);
|
||||
@@ -106,7 +120,7 @@ namespace MobileGL::MG_Pipe {
|
||||
} else {
|
||||
// Only dereference fetched elements: sparse indices and first>0
|
||||
// do not grant permission to read intervening application memory.
|
||||
for (const Uint64 vertex : *referenced) {
|
||||
for (const Uint64 vertex : referenced) {
|
||||
const SizeT offset = static_cast<SizeT>(vertex * stride);
|
||||
std::memcpy(bytes.data() + offset, source + offset, elementBytes);
|
||||
}
|
||||
@@ -122,12 +136,28 @@ namespace MobileGL::MG_Pipe {
|
||||
}
|
||||
|
||||
private:
|
||||
struct Fetch {
|
||||
MGPDrawRange Range;
|
||||
Uint32 Instances;
|
||||
Uint32 BaseInstance;
|
||||
// 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;
|
||||
@@ -143,11 +173,6 @@ namespace MobileGL::MG_Pipe {
|
||||
return attribute.Size > 0 && attribute.Size <= 4 ? component * attribute.Size : 0;
|
||||
}
|
||||
|
||||
static void SortUnique(Vector<Uint64>& values) {
|
||||
std::sort(values.begin(), values.end());
|
||||
values.erase(std::unique(values.begin(), values.end()), values.end());
|
||||
}
|
||||
|
||||
static UniquePtr<Buffer> MakeOwnedBuffer(const void* bytes, SizeT size, BufferTarget target) {
|
||||
auto buffer = MakeUnique<Buffer>(0);
|
||||
const auto handle = MGPipeResourceTrackerInstance().Find(*buffer);
|
||||
@@ -157,82 +182,8 @@ namespace MobileGL::MG_Pipe {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static Bool ReadBuffer(const SharedPtr<Buffer>& buffer, Uint64 offset, SizeT size, void* destination) {
|
||||
if (!buffer || offset > buffer->GetSize() || size > buffer->GetSize() - offset) return false;
|
||||
// This waits for the actual server writeback when an earlier GPU draw,
|
||||
// dispatch, or capture produced the index/command bytes.
|
||||
buffer->SyncGpuWrites();
|
||||
buffer->DownloadSubData(destination, static_cast<SizeT>(offset), size);
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool ReadIndirectFetches(Uint8 indexSize, const MGPDrawIndirect& indirect, Vector<Fetch>& fetches) {
|
||||
const auto& commands = m_context.GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
|
||||
Uint32 count = indirect.DrawCount;
|
||||
if (!MGPipeHandleIsNull(indirect.ParameterBuffer)) {
|
||||
const auto& parameter = m_context.GetBufferBindingSlot(BufferTarget::Parameter).GetBoundObject();
|
||||
Uint32 actual = 0;
|
||||
if (!ReadBuffer(parameter, indirect.ParameterOffset, sizeof(actual), &actual)) return false;
|
||||
count = std::min(count, actual);
|
||||
}
|
||||
const Uint64 commandBytes = indexSize ? 5 * sizeof(Uint32) : 4 * sizeof(Uint32);
|
||||
const Uint64 stride = indirect.Stride ? indirect.Stride : commandBytes;
|
||||
fetches.reserve(count);
|
||||
for (Uint32 i = 0; i < count; ++i) {
|
||||
const Uint64 displacement = static_cast<Uint64>(i) * stride;
|
||||
if (displacement > std::numeric_limits<Uint64>::max() - indirect.Offset) return false;
|
||||
Uint32 words[5]{};
|
||||
if (!ReadBuffer(commands, indirect.Offset + displacement, commandBytes, words)) return false;
|
||||
Int32 bias = 0;
|
||||
if (indexSize) std::memcpy(&bias, &words[3], sizeof(bias));
|
||||
fetches.push_back({{words[2], words[0], bias}, words[1], words[indexSize ? 4 : 3]});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool ReadVertexIndices(const MGPDrawInfo& info, const Vector<Fetch>& fetches,
|
||||
const void* clientIndices, Uint64 clientIndexBytes, Vector<Uint64>& vertices) {
|
||||
const auto& vao = m_context.GetBoundVertexArray();
|
||||
const auto& elementBuffer = vao->GetIndexBufferBindingSlot().GetBoundObject();
|
||||
const Bool restart = (info.Flags & kDrawPrimitiveRestart) != 0;
|
||||
const Bool fixedRestart = m_context.IsCapabilityEnabled(CapabilityInput::PrimitiveRestartFixedIndex);
|
||||
const Uint32 restartIndex = fixedRestart
|
||||
? (info.IndexSize == 1 ? 0xffu : info.IndexSize == 2 ? 0xffffu : 0xffffffffu)
|
||||
: info.RestartIndex;
|
||||
for (const auto& fetch : fetches) {
|
||||
if (!fetch.Range.Count || !fetch.Instances) continue;
|
||||
if (!info.IndexSize) {
|
||||
for (Uint64 n = 0; n < fetch.Range.Count; ++n)
|
||||
vertices.push_back(static_cast<Uint64>(fetch.Range.Start) + n);
|
||||
continue;
|
||||
}
|
||||
const Uint64 offset = static_cast<Uint64>(fetch.Range.Start) * info.IndexSize;
|
||||
const Uint64 size = static_cast<Uint64>(fetch.Range.Count) * info.IndexSize;
|
||||
if (size > std::numeric_limits<SizeT>::max()) return false;
|
||||
Vector<Uint8> indexBytes;
|
||||
const Uint8* data = nullptr;
|
||||
if (clientIndices != nullptr) {
|
||||
if (offset > clientIndexBytes || size > clientIndexBytes - offset) return false;
|
||||
data = static_cast<const Uint8*>(clientIndices) + static_cast<SizeT>(offset);
|
||||
} else {
|
||||
indexBytes.resize(static_cast<SizeT>(size));
|
||||
if (!ReadBuffer(elementBuffer, offset, indexBytes.size(), indexBytes.data())) return false;
|
||||
data = indexBytes.data();
|
||||
}
|
||||
for (Uint32 n = 0; n < fetch.Range.Count; ++n) {
|
||||
Uint32 index = 0;
|
||||
std::memcpy(&index, data + static_cast<SizeT>(n) * info.IndexSize, info.IndexSize);
|
||||
if (restart && index == restartIndex) continue;
|
||||
const Int64 vertex = static_cast<Int64>(index) + fetch.Range.IndexBias;
|
||||
if (vertex < 0) return false;
|
||||
vertices.push_back(static_cast<Uint64>(vertex));
|
||||
}
|
||||
}
|
||||
SortUnique(vertices);
|
||||
return true;
|
||||
}
|
||||
|
||||
Context& m_context;
|
||||
MGPipeClientFetchPlan m_plan;
|
||||
Array<UniquePtr<Buffer>, kMGPipeMaxVertexAttribs> m_vertexBuffers{};
|
||||
UniquePtr<Buffer> m_indexBuffer;
|
||||
Uint32 m_baseInstance = 0;
|
||||
|
||||
Reference in New Issue
Block a user