mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 20:28:32 +09:00
[Refactor] (Espryt): drive the driver VAO from the pushed vertex-elements record and retire the wrapping index-slot version and its identity patch
This commit is contained in:
@@ -17,6 +17,10 @@
|
||||
#include <MG_Util/Metrics/TextureMetrics.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_Pipe/PipeInputsSwitch.h>
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P3a: the applier's vertex-input records the re-keyed draw-buffer memo is validated against.
|
||||
#include <MG_Pipe/PipeApply.h>
|
||||
#endif
|
||||
#include <MG_State/GLState/ErrorState/Error.h>
|
||||
#include <MG_State/GLState/TextureState/TextureObjectBuffer.h>
|
||||
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
|
||||
@@ -574,6 +578,93 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
BindBufferId(glTarget, backendResource->id);
|
||||
}
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// The handle arm of the resolved-draw-buffers memo (D-G4). Two substitutions and
|
||||
// nothing else:
|
||||
//
|
||||
// validity the frontend VAO's wrapping configuration version is replaced by the
|
||||
// bound vertex-elements CSO ({slot, gen} AND its server-owned content
|
||||
// serial) plus the vertex-buffer set's own serial - so an emission the
|
||||
// suppressor let through is what re-opens the memo, not a counter the
|
||||
// backend reads out of the frontend;
|
||||
// identity an entry names its resource by handle and the clean probe compares that,
|
||||
// instead of a raw frontend address that a successor object can reproduce.
|
||||
//
|
||||
// The WALK itself still reads the frontend VAO's attributes, and deliberately: the
|
||||
// buffers a draw needs ensured is a pull site P3a does not migrate (dirty bits 15-17
|
||||
// are P4b's and the resolution move is P8's), and EnsureBufferResource still owes
|
||||
// BufferObject::SyncPersistentMappedRange one call (D-N).
|
||||
void SyncVaoAttributeBuffersByHandle(const SharedPtr<MG_State::GLState::VertexArrayObject>& currentVAOObject,
|
||||
VertexArrayImpl::BackendVertexArrayObject::ResolvedDrawBuffers* memo,
|
||||
Uint64 bufferEpoch) {
|
||||
const auto& st = MG_Pipe::MGPipeApplier();
|
||||
const MG_Pipe::MGPipeHandle elements = st.BoundVertexElements;
|
||||
Uint64 elementsSerial = 0;
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(elements) && elements.Slot < st.VertexElementsCsos.size()) {
|
||||
const auto& record = st.VertexElementsCsos[elements.Slot];
|
||||
if (record.Live && record.Gen == elements.Gen) elementsSerial = record.ContentSerial;
|
||||
}
|
||||
const Uint64 buffersSerial = st.VertexBuffersSerial;
|
||||
|
||||
if (memo && memo->valid && memo->elementsHandle == elements &&
|
||||
memo->elementsSerial == elementsSerial && memo->buffersSerial == buffersSerial) {
|
||||
if (memo->vboCleanEpoch != bufferEpoch) {
|
||||
Bool allClean = true;
|
||||
for (Uint i = 0; i < memo->count; ++i) {
|
||||
auto& entry = memo->entries[i];
|
||||
if (IsBufferDrawCleanByHandle(entry.handle, entry.resource)) continue;
|
||||
allClean = false;
|
||||
entry.resource =
|
||||
EnsureBufferResource(currentVAOObject->GetAttribute(entry.attribIndex).Buffer);
|
||||
}
|
||||
memo->vboCleanEpoch = allClean ? bufferEpoch : 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Full walk, once per distinct buffer, rebuilding the memo as it goes.
|
||||
MG_State::GLState::BufferObject* syncedBuffers[MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS];
|
||||
Uint syncedBufferCount = 0;
|
||||
const auto& allAttributes = currentVAOObject->GetAllAttributes();
|
||||
for (Uint attribIndex = 0; attribIndex < allAttributes.size(); ++attribIndex) {
|
||||
const auto& attrib = allAttributes[attribIndex];
|
||||
if (!attrib.Enabled) continue;
|
||||
const auto& bufferObject = attrib.Buffer;
|
||||
if (!bufferObject) continue;
|
||||
|
||||
auto* const bufferKey = bufferObject.get();
|
||||
Bool alreadySynced = false;
|
||||
for (Uint i = 0; i < syncedBufferCount; ++i) {
|
||||
if (syncedBuffers[i] == bufferKey) {
|
||||
alreadySynced = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (alreadySynced) continue;
|
||||
|
||||
auto* resource = EnsureBufferResource(bufferObject);
|
||||
if (memo) {
|
||||
auto& entry = memo->entries[syncedBufferCount];
|
||||
entry.frontend = bufferKey;
|
||||
entry.attribIndex = static_cast<Uint8>(attribIndex);
|
||||
entry.resource = resource;
|
||||
entry.handle = HandleOfBuffer(bufferKey);
|
||||
}
|
||||
syncedBuffers[syncedBufferCount++] = bufferKey;
|
||||
}
|
||||
if (memo) {
|
||||
memo->count = syncedBufferCount;
|
||||
memo->elementsHandle = elements;
|
||||
memo->elementsSerial = elementsSerial;
|
||||
memo->buffersSerial = buffersSerial;
|
||||
memo->valid = true;
|
||||
// Rebuilt via EnsureBufferResource, not probed clean: the next probe pass
|
||||
// stamps the epoch.
|
||||
memo->vboCleanEpoch = 0;
|
||||
}
|
||||
}
|
||||
#endif // MOBILEGL_PIPE_PUSH
|
||||
|
||||
// `vaoConfigVersion` is the caller's early read of currentVAOObject->GetConfigVersion():
|
||||
// the VAO's config fields live on a cache line the draw path touches nowhere else, and
|
||||
// cycling section VAOs makes that a guaranteed miss - reading it at the top of
|
||||
@@ -613,6 +704,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
const Uint64 bufferEpoch = CurrentBufferMutationEpoch();
|
||||
auto* memo = vaoTwin ? &vaoTwin->GetResolvedDrawBuffersMemo() : nullptr;
|
||||
const Uint32 configVersion = vaoConfigVersion;
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
if (VertexInputSubsystemEnabled()) {
|
||||
SyncVaoAttributeBuffersByHandle(currentVAOObject, memo, bufferEpoch);
|
||||
} else
|
||||
#endif
|
||||
if (memo && memo->valid && memo->configVersion == configVersion) {
|
||||
if (memo->vboCleanEpoch != bufferEpoch) {
|
||||
Bool allClean = true;
|
||||
@@ -683,6 +779,28 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// rebind another buffer with no epoch (and no config-version) move,
|
||||
// so the identity compare always runs; only the clean PROBE is
|
||||
// elided while the stamp holds.
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
if (ResourceSubsystemEnabled()) {
|
||||
// Same three cases, with the identity re-keyed off the raw frontend
|
||||
// address onto the resource's {slot, gen} (D-G4).
|
||||
const MG_Pipe::MGPipeHandle iboHandle = HandleOfBuffer(possibleIBO.get());
|
||||
if (memo && memo->iboHandle == iboHandle && memo->iboCleanEpoch == bufferEpoch) {
|
||||
// probed fully clean at this epoch; nothing can have dirtied it
|
||||
} else if (memo && memo->iboHandle == iboHandle &&
|
||||
IsBufferDrawCleanByHandle(iboHandle, memo->iboResource)) {
|
||||
memo->iboCleanEpoch = bufferEpoch;
|
||||
} else {
|
||||
auto* resource = EnsureBufferResource(possibleIBO);
|
||||
if (memo) {
|
||||
memo->iboHandle = iboHandle;
|
||||
memo->iboFrontend = possibleIBO.get();
|
||||
memo->iboResource = resource;
|
||||
// Repaired, not probed clean: stamp on the next clean probe.
|
||||
memo->iboCleanEpoch = 0;
|
||||
}
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
if (memo && memo->iboFrontend == possibleIBO.get() && memo->iboCleanEpoch == bufferEpoch) {
|
||||
// probed fully clean at this epoch; nothing can have dirtied it
|
||||
} else if (memo && memo->iboFrontend == possibleIBO.get() &&
|
||||
@@ -5140,14 +5258,27 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
// The emulated shift has to be in place before PrepareForDraw, because that is what syncs the
|
||||
// VAO; a zero here is what un-shifts the arrays for the next ordinary draw.
|
||||
//
|
||||
// P3a (D-H2): this is the LEGACY arm's carrier. On the handle arm the draw's raw base
|
||||
// instance rides in MGPVertexBuffers::BaseInstance - a ContentHash input, so a base-instance
|
||||
// change that moves no buffer is still emitted rather than suppressed - and the server
|
||||
// decides whether to shift, because emulation ownership is the server's. The scopes below
|
||||
// are therefore compiled only where the pre-handle arm is (a pull build always).
|
||||
inline Uint32 EmulatedFetchBaseInstance(GLuint baseinstance) {
|
||||
return UseNativeBaseInstance() ? 0u : static_cast<Uint32>(baseinstance);
|
||||
}
|
||||
|
||||
#if MOBILEGL_PIPE_LEGACY_MEMOS
|
||||
#define MGL_SCOPED_FETCH_BASE_INSTANCE(name, value) \
|
||||
const VertexArrayImpl::ScopedFetchBaseInstance name(value)
|
||||
#else
|
||||
#define MGL_SCOPED_FETCH_BASE_INSTANCE(name, value) ((void)(value))
|
||||
#endif
|
||||
|
||||
void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount, GLint basevertex, GLuint baseinstance) {
|
||||
DrawSyncFlags syncBit = DrawSyncBit::IndexBuffer | DrawSyncBit::Instancing;
|
||||
const VertexArrayImpl::ScopedFetchBaseInstance fetchScope(EmulatedFetchBaseInstance(baseinstance));
|
||||
MGL_SCOPED_FETCH_BASE_INSTANCE(fetchScope, EmulatedFetchBaseInstance(baseinstance));
|
||||
PrepareForDraw(syncBit);
|
||||
const ScopedRestartIndexSubstitution restart(type, count, indices);
|
||||
if (!restart.DrawIsValid()) return;
|
||||
@@ -5184,7 +5315,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount, GLuint baseinstance) {
|
||||
DrawSyncFlags syncBit = DrawSyncBit::IndexBuffer | DrawSyncBit::Instancing;
|
||||
const VertexArrayImpl::ScopedFetchBaseInstance fetchScope(EmulatedFetchBaseInstance(baseinstance));
|
||||
MGL_SCOPED_FETCH_BASE_INSTANCE(fetchScope, EmulatedFetchBaseInstance(baseinstance));
|
||||
PrepareForDraw(syncBit);
|
||||
const ScopedRestartIndexSubstitution restart(type, count, indices);
|
||||
if (!restart.DrawIsValid()) return;
|
||||
@@ -5236,7 +5367,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
|
||||
GLuint baseinstance) {
|
||||
DrawSyncFlags syncBit = DrawSyncBit::Instancing;
|
||||
const VertexArrayImpl::ScopedFetchBaseInstance fetchScope(EmulatedFetchBaseInstance(baseinstance));
|
||||
MGL_SCOPED_FETCH_BASE_INSTANCE(fetchScope, EmulatedFetchBaseInstance(baseinstance));
|
||||
PrepareForDraw(syncBit);
|
||||
SetCurrentBaseInstance(baseinstance);
|
||||
ForEachViewportRoutingPass([&] {
|
||||
|
||||
@@ -2242,6 +2242,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
bufferObject->GetLifetimeId());
|
||||
}
|
||||
|
||||
SizeT ResourceWidthForHandle(MG_Pipe::MGPipeHandle res) { return ResourceWidthOf(res); }
|
||||
|
||||
void MarkBufferGpuWritten(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject) {
|
||||
if (!bufferObject) return;
|
||||
if (!ResourceSubsystemEnabled()) {
|
||||
@@ -3309,8 +3311,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
g_GLESFuncs.glVertexBindingDivisor != nullptr;
|
||||
}
|
||||
|
||||
#if MOBILEGL_PIPE_LEGACY_MEMOS
|
||||
// Draw state, not VAO state: set by the baseInstance draw entry points around
|
||||
// PrepareForDraw and back to zero as soon as the draw is issued.
|
||||
// PrepareForDraw and back to zero as soon as the draw is issued. The legacy arm's
|
||||
// carrier; the handle arm's is MGPipeApplierState::VertexFetchBaseInstance (D-H2).
|
||||
Uint32 g_pendingFetchBaseInstance = 0;
|
||||
|
||||
void SetPendingFetchBaseInstance(Uint32 baseInstance) {
|
||||
@@ -3320,6 +3324,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Uint32 GetPendingFetchBaseInstance() {
|
||||
return g_pendingFetchBaseInstance;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
Bool BackendUsesNativeBaseInstance() { return g_GLESCapabilities.SupportsBaseInstance; }
|
||||
#endif
|
||||
|
||||
// The "+ baseInstance" of GL's instanced-array element index, expressed as a byte shift
|
||||
// of the array's own offset. Only divisor'd arrays step per instance, so only they move.
|
||||
@@ -3372,11 +3381,123 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return true;
|
||||
}
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// ---- the handle arm's small helpers -------------------------------------------
|
||||
//
|
||||
// Each one is its object-shaped counterpart above with the frontend reads replaced by
|
||||
// the two wire views, and nothing else. Divisor is deliberately NOT on the attribute
|
||||
// view: it is resolved per binding point and rides in MGPVertexBuffer::Divisor, which
|
||||
// is where glVertexAttribDivisor reads it (D-G2).
|
||||
|
||||
// The entry of the applied set that feeds this attribute. The client emits one entry
|
||||
// per enabled attribute with BindingIndex == the attribute index, so the positional
|
||||
// slot is the answer in every real record; the scan behind it is what keeps a record
|
||||
// that numbers its entries differently correct rather than silently misfed.
|
||||
const MG_Pipe::MGPVertexBuffer* VertexBufferForBindingIndex(const MG_Pipe::MGPipeApplierState& st,
|
||||
Uint32 bindingIndex) {
|
||||
if (st.VertexBufferCount == 0) return nullptr;
|
||||
const Uint32 begin = st.VertexBufferStart;
|
||||
const Uint32 end = begin + st.VertexBufferCount;
|
||||
if (bindingIndex >= begin && bindingIndex < end &&
|
||||
bindingIndex < MG_Pipe::kMGPipeMaxVertexAttribs &&
|
||||
st.VertexBuffers[bindingIndex].BindingIndex == bindingIndex) {
|
||||
return &st.VertexBuffers[bindingIndex];
|
||||
}
|
||||
for (Uint32 i = begin; i < end && i < MG_Pipe::kMGPipeMaxVertexAttribs; ++i) {
|
||||
if (st.VertexBuffers[i].BindingIndex == bindingIndex) return &st.VertexBuffers[i];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// BaseInstanceByteShift, on the wire views. baseInstance is added to the ELEMENT index,
|
||||
// so the divisor does not appear here; a resolved stride of zero never advances and the
|
||||
// arithmetic already yields zero for it.
|
||||
inline SizeT BaseInstanceByteShiftWire(Int32 stride, Uint32 divisor, Uint32 baseInstance) {
|
||||
if (baseInstance == 0 || divisor == 0) return 0;
|
||||
return static_cast<SizeT>(baseInstance) * static_cast<SizeT>(stride);
|
||||
}
|
||||
|
||||
// BindAttributeBuffer, resolving the driver id from the slot table instead of from the
|
||||
// attribute's SharedPtr. It does NOT ensure storage: on this arm the draw's buffers
|
||||
// were ensured by SyncNeccessaryBuffers earlier in the same PrepareForDraw, which is
|
||||
// the one place that still holds the frontend objects (a pull site P4b/P8 own).
|
||||
inline Bool BindAttributeBufferByHandle(MG_Pipe::MGPipeHandle res) {
|
||||
if (MG_Pipe::MGPipeHandleIsNull(res)) {
|
||||
MGLOG_W_ONCE("Attribute has no bound buffer, skipping.");
|
||||
return false;
|
||||
}
|
||||
auto* backendResource = BufferImpl::FindBufferResourceForHandle(res);
|
||||
if (!backendResource || backendResource->id == 0) {
|
||||
MGLOG_E_ONCE("No backend buffer found for attribute's buffer, cannot bind attribute.");
|
||||
return false;
|
||||
}
|
||||
BufferImpl::BindBufferId(GL_ARRAY_BUFFER, backendResource->id);
|
||||
return true;
|
||||
}
|
||||
|
||||
// SyncZeroStrideAttribute on the wire views: the one spelling that can carry a resolved
|
||||
// stride of zero, which glVertexAttribPointer's zero means the opposite of.
|
||||
inline Bool SyncZeroStrideAttributeByHandle(Uint attribIndex, const MGPVertexAttribWire& attrib,
|
||||
const MG_Pipe::MGPVertexBuffer& binding) {
|
||||
if (MG_Pipe::MGPipeHandleIsNull(binding.Res)) {
|
||||
MGLOG_W_ONCE("Zero-stride attribute %u has no bound buffer, skipping.", attribIndex);
|
||||
return false;
|
||||
}
|
||||
auto* backendResource = BufferImpl::FindBufferResourceForHandle(binding.Res);
|
||||
if (!backendResource || backendResource->id == 0) {
|
||||
MGLOG_E_ONCE("No backend buffer for zero-stride attribute %u, cannot bind it.", attribIndex);
|
||||
return false;
|
||||
}
|
||||
if (!attrib.IsInteger) {
|
||||
const GLint glSize = attrib.IsBgra ? static_cast<GLint>(GL_BGRA) : static_cast<GLint>(attrib.Size);
|
||||
g_GLESFuncs.glVertexAttribFormat(attribIndex, glSize,
|
||||
MG_Util::ConvertDataTypeToGLEnum(static_cast<DataType>(attrib.Type)),
|
||||
attrib.Normalized ? GL_TRUE : GL_FALSE, 0);
|
||||
} else {
|
||||
g_GLESFuncs.glVertexAttribIFormat(attribIndex, static_cast<GLint>(attrib.Size),
|
||||
MG_Util::ConvertDataTypeToGLEnum(static_cast<DataType>(attrib.Type)),
|
||||
0);
|
||||
}
|
||||
g_GLESFuncs.glVertexAttribBinding(attribIndex, attribIndex);
|
||||
// The resolved offset goes on the binding point, not into a relative offset (the
|
||||
// relative offset is capped by GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET, a buffer
|
||||
// offset is not). BindBufferId is bypassed deliberately.
|
||||
g_GLESFuncs.glBindVertexBuffer(attribIndex, backendResource->id,
|
||||
static_cast<GLintptr>(attrib.Offset), 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// The record the applier holds for the bound vertex-elements CSO, or null.
|
||||
const MG_Pipe::MGPipeVertexElementsRecord* BoundVertexElementsRecord(
|
||||
const MG_Pipe::MGPipeApplierState& st) {
|
||||
const MG_Pipe::MGPipeHandle cso = st.BoundVertexElements;
|
||||
if (MG_Pipe::MGPipeHandleIsNull(cso)) return nullptr;
|
||||
if (cso.Slot >= st.VertexElementsCsos.size()) return nullptr;
|
||||
const auto& record = st.VertexElementsCsos[cso.Slot];
|
||||
if (!record.Live || record.Gen != cso.Gen) return nullptr;
|
||||
return &record;
|
||||
}
|
||||
#endif // MOBILEGL_PIPE_PUSH
|
||||
|
||||
void BackendVertexArrayObject::SyncToBackend(
|
||||
const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
if (BufferImpl::VertexInputSubsystemEnabled()) {
|
||||
// Everything this arm needs is in the applier's records; the frontend VAO is
|
||||
// not read at all, which is the whole point of the conversion.
|
||||
SyncToBackendFromApplier();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if !MOBILEGL_PIPE_LEGACY_MEMOS
|
||||
(void)stateVAOObject;
|
||||
MGLOG_E_ONCE("MGPipe: the vertex-input subsystem bit is clear and MOBILEGL_PIPE_LEGACY_MEMOS=0 "
|
||||
"removed the pre-handle VAO sync, so this configuration has no arm at all");
|
||||
return;
|
||||
#else
|
||||
if (!stateVAOObject) {
|
||||
MGLOG_E_ONCE("State VAO object is null, cannot sync to backend.");
|
||||
return;
|
||||
@@ -3602,8 +3723,205 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
m_syncedFetchBaseInstance = fetchBaseInstance;
|
||||
}
|
||||
m_syncedBufferIdGeneration = currentBufferIdGeneration;
|
||||
#endif // MOBILEGL_PIPE_LEGACY_MEMOS
|
||||
}
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// The same function, driven by the applier's records (D-G4). Every branch of the walk
|
||||
// survives - the enable/disable block, the fp64 narrowing with its Adreno disable, the
|
||||
// zero-stride binding-API path, the attribute-buffer bind, the BGRA refusal probe, the
|
||||
// pointer/IPointer at the shifted fetch offset and the divisor - and the gate is
|
||||
// re-keyed onto three server-owned MGGen counters plus the CSO's {slot, gen}:
|
||||
//
|
||||
// m_syncedElementsHandle + m_syncedElementsSerial <- config version + the whole
|
||||
// per-attribute version array
|
||||
// m_syncedVertexBuffersSerial <- (new) the buffer set's own
|
||||
// m_syncedIndexSerial <- wrapping Uint16 + identity patch
|
||||
// m_syncedBufferIdGeneration <- UNCHANGED, server-local, and
|
||||
// still the only thing that
|
||||
// catches a driver-id re-mint no
|
||||
// counter on either side moves
|
||||
// m_hasConvertedFloat64Attribute <- UNCHANGED: a narrowed stream is
|
||||
// derived from buffer CONTENT,
|
||||
// which no serial covers
|
||||
// m_syncedFetchBaseInstance <- no longer in the gate as a
|
||||
// DIRTY input of its own (a base
|
||||
// instance change is a
|
||||
// ContentHash input and so moves
|
||||
// VertexBuffersSerial), but kept
|
||||
// as the record of what was last
|
||||
// EMITTED, which is what the next
|
||||
// sync has to correct
|
||||
void BackendVertexArrayObject::SyncToBackendFromApplier() {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
const MG_Pipe::MGPipeApplierState& st = MG_Pipe::MGPipeApplier();
|
||||
const auto* rec = BoundVertexElementsRecord(st);
|
||||
if (rec == nullptr) {
|
||||
MGLOG_E_ONCE("MGPipe: no vertex-elements record is bound, so the driver VAO cannot be "
|
||||
"configured - nothing emitted create_vertex_elements/bind_vertex_elements");
|
||||
return;
|
||||
}
|
||||
|
||||
const Uint64 currentBufferIdGeneration = BufferImpl::g_bufferBackendIdGeneration;
|
||||
const Bool bufferIdsRemitted = m_syncedBufferIdGeneration != currentBufferIdGeneration;
|
||||
const Bool attributesDirty = bufferIdsRemitted || !m_hasSyncedElements ||
|
||||
!(m_syncedElementsHandle == st.BoundVertexElements) ||
|
||||
m_syncedElementsSerial != rec->ContentSerial ||
|
||||
m_syncedVertexBuffersSerial != st.VertexBuffersSerial;
|
||||
const Bool indexBufferDirty = bufferIdsRemitted || m_syncedIndexSerial != st.IndexBufferSerial;
|
||||
// Emulation is server-owned: the client sends the draw's RAW base instance and never
|
||||
// learns the answer. Applied here as well as in the applier so the decision is the
|
||||
// same whichever side resolved it first - it is idempotent.
|
||||
const Uint32 fetchBaseInstance =
|
||||
BackendUsesNativeBaseInstance() ? 0u : st.VertexFetchBaseInstance;
|
||||
const Bool baseInstanceDirty = m_syncedFetchBaseInstance != fetchBaseInstance;
|
||||
const Bool emitAttributes = attributesDirty || baseInstanceDirty || m_hasConvertedFloat64Attribute;
|
||||
if (!emitAttributes && !indexBufferDirty) {
|
||||
return;
|
||||
}
|
||||
m_hasConvertedFloat64Attribute = false;
|
||||
|
||||
Bind();
|
||||
|
||||
const Uint32 attributeCount =
|
||||
std::min<Uint32>(rec->AttributeCount, MG_Pipe::kMGPipeMaxVertexAttribs);
|
||||
for (Uint attribIndex = 0; attribIndex < attributeCount && emitAttributes; ++attribIndex) {
|
||||
const MGPVertexAttribWire& attrib = rec->Attributes[attribIndex];
|
||||
const MG_Pipe::MGPVertexBuffer* binding =
|
||||
VertexBufferForBindingIndex(st, attrib.BindingIndex);
|
||||
const Uint32 divisor = binding != nullptr ? binding->Divisor : 0u;
|
||||
|
||||
// The enable/disable block. On this arm there is no per-attribute version to
|
||||
// compare: the applier's Attributes[] IS what was last pushed, so a moved
|
||||
// ContentSerial means re-emit and an unchanged one means the early-out above
|
||||
// already returned.
|
||||
if (attrib.Enabled) {
|
||||
g_GLESFuncs.glEnableVertexAttribArray(attribIndex);
|
||||
} else {
|
||||
g_GLESFuncs.glDisableVertexAttribArray(attribIndex);
|
||||
}
|
||||
|
||||
// The fp64 narrowing, verbatim in behaviour including the Adreno workaround:
|
||||
// when no float32 stream can be built the array is DISABLED rather than left
|
||||
// enabled with no pointer, which is what the driver turns into a SIGSEGV inside
|
||||
// the next draw (KHR-GL43.vertex_attrib_binding.basic-input-case4). IsLong is
|
||||
// carried separately from Type == Float64 on the wire precisely so this test
|
||||
// can still tell the two apart.
|
||||
if (attrib.IsLong || attrib.Type == static_cast<Uint32>(DataType::Float64)) {
|
||||
if (attrib.Enabled && attrib.Type == static_cast<Uint32>(DataType::Float64) &&
|
||||
binding != nullptr &&
|
||||
SyncFloat64AttributeAsFloat32ByHandle(attribIndex, attrib, *binding, fetchBaseInstance)) {
|
||||
m_hasConvertedFloat64Attribute = true;
|
||||
// Explicit, not redundant: an earlier walk that could not build the
|
||||
// stream disabled this array.
|
||||
g_GLESFuncs.glEnableVertexAttribArray(attribIndex);
|
||||
g_GLESFuncs.glVertexAttribDivisor(attribIndex, divisor);
|
||||
continue;
|
||||
}
|
||||
if (attrib.Enabled) {
|
||||
MGLOG_W_ONCE("DirectGLES: vertex attribute %u is a 64-bit (GL_DOUBLE) array whose source "
|
||||
"stream could not be narrowed to float32 - disabling the array",
|
||||
attribIndex);
|
||||
}
|
||||
g_GLESFuncs.glDisableVertexAttribArray(attribIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!attrib.Enabled || binding == nullptr) continue;
|
||||
|
||||
// A resolved stride of zero is the binding model's "never advance" and
|
||||
// glVertexAttribPointer cannot say it - its zero means "tightly packed", i.e.
|
||||
// the opposite. ES 3.1's binding-point API can.
|
||||
if (attrib.Stride == 0 && HasVertexBindingApi()) {
|
||||
if (!SyncZeroStrideAttributeByHandle(attribIndex, attrib, *binding)) {
|
||||
continue;
|
||||
}
|
||||
// No shift here on purpose: a zero stride never advances.
|
||||
g_GLESFuncs.glVertexBindingDivisor(attribIndex, divisor);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!BindAttributeBufferByHandle(binding->Res)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// GL_BGRA as a vertex SIZE is desktop-only and ES rejects it, which leaves the
|
||||
// array ENABLED with no pointer - and the Adreno driver then dereferences null
|
||||
// inside the next draw (KHR-GL43.vertex_attrib_binding.basic-input-case5). So
|
||||
// the refusal is observed and the array disabled. Deliberately ONLY this
|
||||
// format: the per-draw sync must not grow a glGetError round trip for the
|
||||
// formats real applications use.
|
||||
const Bool formatMayBeRefused = attrib.IsBgra != 0;
|
||||
if (formatMayBeRefused) {
|
||||
while (g_GLESFuncs.glGetError() != GL_NO_ERROR) {
|
||||
} // start from a clean slate so the check below is about THIS call
|
||||
}
|
||||
|
||||
const SizeT fetchOffset = static_cast<SizeT>(attrib.Offset) +
|
||||
BaseInstanceByteShiftWire(attrib.Stride, divisor, fetchBaseInstance);
|
||||
const GLenum glType = MG_Util::ConvertDataTypeToGLEnum(static_cast<DataType>(attrib.Type));
|
||||
|
||||
if (!attrib.IsInteger) {
|
||||
const GLint glSize = attrib.IsBgra ? static_cast<GLint>(GL_BGRA) : static_cast<GLint>(attrib.Size);
|
||||
g_GLESFuncs.glVertexAttribPointer(attribIndex, glSize, glType,
|
||||
attrib.Normalized ? GL_TRUE : GL_FALSE, attrib.Stride,
|
||||
(const void*)fetchOffset);
|
||||
} else {
|
||||
g_GLESFuncs.glVertexAttribIPointer(attribIndex, static_cast<GLint>(attrib.Size), glType,
|
||||
attrib.Stride, (const void*)fetchOffset);
|
||||
}
|
||||
|
||||
if (formatMayBeRefused && g_GLESFuncs.glGetError() != GL_NO_ERROR) {
|
||||
MGLOG_W_ONCE("DirectGLES: the driver refused the vertex format of attribute %u "
|
||||
"(size=%d bgra=%d type=%s) - disabling the array so the draw cannot "
|
||||
"fetch through a pointer the driver never accepted",
|
||||
attribIndex, static_cast<int>(attrib.Size), attrib.IsBgra ? 1 : 0,
|
||||
MG_Util::ConvertGLEnumToString(glType).c_str());
|
||||
g_GLESFuncs.glDisableVertexAttribArray(attribIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
g_GLESFuncs.glVertexAttribDivisor(attribIndex, divisor);
|
||||
}
|
||||
|
||||
if (indexBufferDirty) {
|
||||
Bool indexBufferSynced = false;
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(st.IndexBuffer.Res)) {
|
||||
auto* backendResource = BufferImpl::FindBufferResourceForHandle(st.IndexBuffer.Res);
|
||||
if (backendResource && backendResource->id != 0) {
|
||||
BufferImpl::BindBufferId(GL_ELEMENT_ARRAY_BUFFER, backendResource->id);
|
||||
indexBufferSynced = true;
|
||||
} else {
|
||||
MGLOG_W_ONCE("No backend buffer found for index buffer binding, cannot bind index buffer.");
|
||||
}
|
||||
} else {
|
||||
g_GLESFuncs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
indexBufferSynced = true;
|
||||
}
|
||||
if (indexBufferSynced) {
|
||||
// The two element-array restore scopes (the restart substitution's and
|
||||
// MultiDrawImpl's) put the DRIVER id back without touching this serial,
|
||||
// which is correct: the serial records what the APPLIER last said, and
|
||||
// those scopes restored exactly what the applier said.
|
||||
m_syncedIndexSerial = st.IndexBufferSerial;
|
||||
}
|
||||
}
|
||||
|
||||
if (attributesDirty) {
|
||||
m_syncedElementsHandle = st.BoundVertexElements;
|
||||
m_syncedElementsSerial = rec->ContentSerial;
|
||||
m_syncedVertexBuffersSerial = st.VertexBuffersSerial;
|
||||
m_hasSyncedElements = true;
|
||||
}
|
||||
if (emitAttributes) {
|
||||
m_syncedFetchBaseInstance = fetchBaseInstance;
|
||||
}
|
||||
m_syncedBufferIdGeneration = currentBufferIdGeneration;
|
||||
}
|
||||
#endif // MOBILEGL_PIPE_PUSH
|
||||
|
||||
void BackendVertexArrayObject::SyncClientSideAttributesForDrawArrays(
|
||||
const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject, GLint first, GLsizei count) {
|
||||
if (!stateVAOObject || count <= 0 || first < 0) {
|
||||
@@ -3830,6 +4148,104 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return true;
|
||||
}
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
Bool BackendVertexArrayObject::SyncFloat64AttributeAsFloat32ByHandle(
|
||||
Uint attribIndex, const MGPVertexAttribWire& attrib, const MG_Pipe::MGPVertexBuffer& binding,
|
||||
Uint32 fetchBaseInstance) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (attribIndex >= m_convertedAttributeBufferIds.size() || attrib.Size < 1 || attrib.Size > 4) {
|
||||
return false;
|
||||
}
|
||||
if (MG_Pipe::MGPipeHandleIsNull(binding.Res)) {
|
||||
// A client-memory 64-bit array is narrowed on the draw path instead, which is
|
||||
// the only place its fetch range is known.
|
||||
return false;
|
||||
}
|
||||
auto* resource = BufferImpl::FindBufferResourceForHandle(binding.Res);
|
||||
if (resource == nullptr) return false;
|
||||
|
||||
// WHAT IS NOT HERE, recorded rather than hidden: the legacy arm opens with
|
||||
// bufferObject->SyncGpuWrites(), one of the eleven Espryt SyncPersistentMappedRange
|
||||
// / SyncGpuWrites sites D-N keeps where they are for P3a. It cannot be made from a
|
||||
// handle - the server has no inverse map to a frontend object, by design - so on
|
||||
// this arm a 64-bit array whose SOURCE buffer was written by a shader and not yet
|
||||
// pulled back narrows stale bytes. P8 is what closes it, by moving the pull to the
|
||||
// client where the object lives; until then this is the one behavioural difference
|
||||
// between the two arms and it is confined to fp64 vertex arrays fed by
|
||||
// shader-written buffers.
|
||||
const Uint8* const sourceBase = resource->hostBytes;
|
||||
const SizeT sourceSize = BufferImpl::ResourceWidthForHandle(binding.Res);
|
||||
if (sourceBase == nullptr || attrib.Offset >= sourceSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const SizeT componentCount = static_cast<SizeT>(attrib.Size);
|
||||
const SizeT sourceElementSize = componentCount * sizeof(Double);
|
||||
const SizeT available = sourceSize - static_cast<SizeT>(attrib.Offset);
|
||||
if (available < sourceElementSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Bool neverAdvances = attrib.Stride <= 0;
|
||||
const SizeT sourceStride = neverAdvances ? sourceElementSize : static_cast<SizeT>(attrib.Stride);
|
||||
const SizeT elementCount = neverAdvances ? 1 : ((available - sourceElementSize) / sourceStride) + 1;
|
||||
|
||||
const SizeT firstElement = (fetchBaseInstance != 0 && binding.Divisor != 0 && !neverAdvances)
|
||||
? static_cast<SizeT>(fetchBaseInstance)
|
||||
: 0;
|
||||
if (firstElement >= elementCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Uint& convertedBufferId = m_convertedAttributeBufferIds[attribIndex];
|
||||
if (convertedBufferId == 0) {
|
||||
g_GLESFuncs.glGenBuffers(1, &convertedBufferId);
|
||||
if (convertedBufferId == 0) {
|
||||
MGLOG_E_ONCE("Failed to create the float32 scratch buffer for the 64-bit vertex array at "
|
||||
"attribute %u.",
|
||||
attribIndex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// NO MEMO YET on this arm: its key is the source buffer's identity plus its change
|
||||
// serial, and re-keying ConvertedFloat64Stream onto the buffer's {slot, gen} is the
|
||||
// next commit's whole subject. Until then every walk reconverts, which is correct
|
||||
// and slower on a path that only 64-bit vertex arrays reach.
|
||||
Vector<Float> converted;
|
||||
NarrowDoubleStreamToFloat32(sourceBase + attrib.Offset, sourceStride, componentCount, elementCount,
|
||||
converted);
|
||||
BufferImpl::BindBufferId(GL_ARRAY_BUFFER, convertedBufferId);
|
||||
g_GLESFuncs.glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(converted.size() * sizeof(Float)),
|
||||
converted.data(), GL_STREAM_DRAW);
|
||||
if (MG_Util::PipeStats::Enabled()) {
|
||||
MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageVertexClient,
|
||||
static_cast<Uint64>(converted.size() * sizeof(Float)));
|
||||
}
|
||||
|
||||
const SizeT convertedElementSize = componentCount * sizeof(Float);
|
||||
if (neverAdvances) {
|
||||
// Only the binding-point API can say "stride 0".
|
||||
if (!HasVertexBindingApi()) {
|
||||
return false;
|
||||
}
|
||||
g_GLESFuncs.glVertexAttribFormat(attribIndex, static_cast<GLint>(attrib.Size), GL_FLOAT, GL_FALSE, 0);
|
||||
g_GLESFuncs.glVertexAttribBinding(attribIndex, attribIndex);
|
||||
g_GLESFuncs.glBindVertexBuffer(attribIndex, convertedBufferId, 0, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
BufferImpl::BindBufferId(GL_ARRAY_BUFFER, convertedBufferId);
|
||||
// `normalized` is deliberately GL_FALSE rather than attrib.Normalized: GL ignores it
|
||||
// for floating-point array types, and honouring it would scale the fetched values.
|
||||
g_GLESFuncs.glVertexAttribPointer(attribIndex, static_cast<GLint>(attrib.Size), GL_FLOAT, GL_FALSE,
|
||||
static_cast<GLsizei>(convertedElementSize),
|
||||
(const void*)(firstElement * convertedElementSize));
|
||||
return true;
|
||||
}
|
||||
#endif // MOBILEGL_PIPE_PUSH
|
||||
|
||||
TwinRegistry<MG_State::GLState::VertexArrayObject, BackendVertexArrayObject, MG_Pipe::MGPipeKind::VertexElementsCso>
|
||||
g_backendVertexArrayObjects;
|
||||
} // namespace VertexArrayImpl
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
||||
#include "SlotTables.h"
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P3a: the vertex-input payload views the handle arm of the VAO twin consumes.
|
||||
#include <MG_Pipe/MGPipeTypes.h>
|
||||
#endif
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectGLES {
|
||||
String EmulateBaseInstanceInVertexShader(String source, GLenum shaderType);
|
||||
@@ -732,6 +736,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// The legacy arm keeps calling BufferObject::MarkGpuWritten directly, and the pull
|
||||
// build never sees this function at all (G1).
|
||||
void MarkBufferGpuWritten(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject);
|
||||
|
||||
// The applier's stored extent for this resource, 0 when it has no record. The one
|
||||
// thing outside BufferImpl that needs it is the fp64 narrowing, whose source extent
|
||||
// used to be BufferObject::GetSize().
|
||||
SizeT ResourceWidthForHandle(MG_Pipe::MGPipeHandle res);
|
||||
#endif
|
||||
|
||||
// Registered as the frontend's BufferBackendOps at backend init and on
|
||||
@@ -938,6 +947,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
MG_State::GLState::BufferObject* frontend = nullptr;
|
||||
BufferImpl::GLESBufferResource* resource = nullptr;
|
||||
Uint8 attribIndex = 0;
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P3a re-key: the entry's identity on the handle arm. A {slot, gen} cannot
|
||||
// be reproduced by a recycled heap address, so the clean probe compares
|
||||
// this instead of the raw frontend pointer and never has to ask the
|
||||
// allocator for it again mid-draw.
|
||||
MG_Pipe::MGPipeHandle handle = MG_Pipe::kMGPipeNullHandle;
|
||||
#endif
|
||||
};
|
||||
Bool valid = false;
|
||||
Uint32 configVersion = 0;
|
||||
@@ -945,6 +961,18 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Array<Entry, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS> entries;
|
||||
MG_State::GLState::BufferObject* iboFrontend = nullptr;
|
||||
BufferImpl::GLESBufferResource* iboResource = nullptr;
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P3a re-key of the memo's validity key: on the handle arm the frontend VAO's
|
||||
// wrapping configuration version is replaced by the bound vertex-elements CSO
|
||||
// (identity AND its server-owned content serial) plus the vertex-buffer set's
|
||||
// own serial - three monotone Uint64s and a {slot, gen}, no wrap and no
|
||||
// identity patch. The IBO entry keeps its separate key for the same reason it
|
||||
// always had one: the index slot is not part of the configuration (D5).
|
||||
MG_Pipe::MGPipeHandle elementsHandle = MG_Pipe::kMGPipeNullHandle;
|
||||
Uint64 elementsSerial = 0;
|
||||
Uint64 buffersSerial = 0;
|
||||
MG_Pipe::MGPipeHandle iboHandle = MG_Pipe::kMGPipeNullHandle;
|
||||
#endif
|
||||
// Buffer-mutation epoch (BufferImpl::CurrentBufferMutationEpoch) at which
|
||||
// the LAST probe pass found every entry / the IBO clean; 0 = not stamped
|
||||
// (epochs start at 1). While a stamp matches the pre-pass epoch read, the
|
||||
@@ -983,6 +1011,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Bool SyncFloat64AttributeAsFloat32(Uint attribIndex, const MG_State::GLState::VertexAttribute& attrib,
|
||||
Uint32 fetchBaseInstance);
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// The handle arm of the whole vertex-elements half. Everything it needs arrives in
|
||||
// the applier's records - the bound CSO's two views, the vertex-buffer set, the
|
||||
// index buffer and the resolved fetch base instance - so it takes no argument at
|
||||
// all and touches no frontend type. The legacy arm above it is unchanged and both
|
||||
// compile in every push build (ARCHITECTURE.md 9.6).
|
||||
void SyncToBackendFromApplier();
|
||||
// Same narrowing, same memo, same Adreno disable; the source bytes are the shadow
|
||||
// base the resource call carried and the memo key is the buffer's {slot, gen}.
|
||||
Bool SyncFloat64AttributeAsFloat32ByHandle(Uint attribIndex, const MGPVertexAttribWire& attrib,
|
||||
const MG_Pipe::MGPVertexBuffer& binding,
|
||||
Uint32 fetchBaseInstance);
|
||||
#endif
|
||||
|
||||
// What the converted float32 stream in m_convertedAttributeBufferIds[i] was built
|
||||
// from. A hit skips the CPU conversion and the re-upload; the buffer's change serial
|
||||
// is part of the key, so a glBufferSubData into the source invalidates it.
|
||||
@@ -1011,6 +1053,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// version early-out in SyncToBackend must not be trusted while it is set.
|
||||
Bool m_hasConvertedFloat64Attribute = false;
|
||||
Bool m_isInitialized = false;
|
||||
#if MOBILEGL_PIPE_LEGACY_MEMOS
|
||||
// ---- the pre-handle memo set (ARCHITECTURE.md 9.6) -------------------------
|
||||
// Retired by P3a on the handle arm and kept compiled here so the A/B is real: a
|
||||
// cleared subsystem bit runs THESE, not a re-keyed twin wearing their names. A
|
||||
// pull build forces MOBILEGL_PIPE_LEGACY_MEMOS ON, so sizeof(this) does not move
|
||||
// and no symbol resizes (G1).
|
||||
Uint16 m_syncedIndexBufferVersion = 0;
|
||||
// Identity of the buffer the version above was stamped against. Raw and never
|
||||
// dereferenced: the slot version is a wrapping Uint16 (see the ResolvedDrawBuffers
|
||||
@@ -1026,6 +1074,28 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Uint32 m_syncedConfigVersion = 0;
|
||||
Array<MG_State::GLState::VertexAttributeVersion, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS>
|
||||
m_syncedAttributeVersions;
|
||||
#endif // MOBILEGL_PIPE_LEGACY_MEMOS
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// ---- what replaces them on the handle arm (D-G4) ---------------------------
|
||||
// The bound vertex-elements CSO this twin last emitted, and the applier's
|
||||
// server-owned content serial for it. Together they replace
|
||||
// m_hasSyncedConfigVersion + m_syncedConfigVersion AND the whole per-attribute
|
||||
// version array: the applier's stored Attributes[] IS what was last pushed, so a
|
||||
// per-attribute compare has nothing left to prove and the walk re-emits.
|
||||
MG_Pipe::MGPipeHandle m_syncedElementsHandle = MG_Pipe::kMGPipeNullHandle;
|
||||
Uint64 m_syncedElementsSerial = 0;
|
||||
Bool m_hasSyncedElements = false;
|
||||
// The vertex-buffer set's own serial. Not in D-G4's table, and it has to be here:
|
||||
// set_vertex_buffers is an independent call carrying the buffer identities, the
|
||||
// offsets and the divisors this twin BAKES into the driver VAO, so a set that
|
||||
// moved while the format did not must still re-emit them.
|
||||
Uint64 m_syncedVertexBuffersSerial = 0;
|
||||
// Replaces m_syncedIndexBufferVersion (a wrapping Uint16) AND
|
||||
// m_syncedIndexBufferObject (the raw identity patch that closed its wrap hole):
|
||||
// one monotone Uint64, no wrap, nothing to patch. This is the Track H re-key
|
||||
// ARCHITECTURE.md 9.5 counts.
|
||||
Uint64 m_syncedIndexSerial = 0;
|
||||
#endif
|
||||
// Byte shift currently baked into the instanced arrays' offsets by the baseInstance
|
||||
// emulation (see SetPendingFetchBaseInstance). It is draw state, not VAO state, so it
|
||||
// is deliberately NOT covered by the config version: the frontend never bumps for it.
|
||||
@@ -1055,6 +1125,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// instanced array at element "floor(instance / divisor) + baseInstance", and ES has no
|
||||
// way to say the "+ baseInstance" part - so it is folded into the attribute's own byte
|
||||
// offset (baseInstance * stride) for every divisor'd array, which is exactly equivalent.
|
||||
//
|
||||
// P3a RETIRES THE AMBIENT GLOBAL (D-H2): an ambient process global cannot cross a
|
||||
// pushed boundary, so on the handle arm the draw's RAW base instance rides in
|
||||
// MGPVertexBuffers::BaseInstance and the SERVER decides whether to shift - the answer
|
||||
// lands in MGPipeApplierState::VertexFetchBaseInstance and the VAO sync reads it there.
|
||||
// The three declarations below and the three scopes in DirectGLES.cpp are the legacy
|
||||
// arm's, kept compiled because a cleared subsystem bit has to run a real pre-handle
|
||||
// path and because removing them would delete two symbols from the PULL build (G1).
|
||||
#if MOBILEGL_PIPE_LEGACY_MEMOS
|
||||
// Must be set BEFORE PrepareForDraw so the VAO sync sees it, and cleared after the draw
|
||||
// so the next one refetches from element 0; ScopedFetchBaseInstance does both.
|
||||
void SetPendingFetchBaseInstance(Uint32 baseInstance);
|
||||
@@ -1067,6 +1146,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
ScopedFetchBaseInstance(const ScopedFetchBaseInstance&) = delete;
|
||||
ScopedFetchBaseInstance& operator=(const ScopedFetchBaseInstance&) = delete;
|
||||
};
|
||||
#endif
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// The server-owned half of the same decision, and the reason the client never
|
||||
// pre-shifts an offset: emulation ownership is the server's (ARCHITECTURE.md 5.7).
|
||||
// True when the driver applies baseInstance to the vertex fetch itself, in which case
|
||||
// the attribute-offset emulation must stay out of the way. Applied to whatever the
|
||||
// applier stored, so the answer is the same whichever side resolved it first.
|
||||
Bool BackendUsesNativeBaseInstance();
|
||||
#endif
|
||||
} // namespace VertexArrayImpl
|
||||
|
||||
namespace TextureImpl {
|
||||
|
||||
Reference in New Issue
Block a user