mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-18 09:08:31 +09:00
[MG_Backend, MG_Remote, MG_Impl, MG_State] (Disaggregated): P5c hd - sinks and twins resolve by the handle the record carried
A split-only verb-handle workspace in MGPipeApplierState (written by the sinks before
dispatch, read by the backend within the same verb) carries the record's handles to the
backend entries whose shared function-table signatures cannot grow them without moving
G1. OnBlit resolves ReadFbo/DrawFbo through the FBO twin table by handle (the named arm
of BlitFramebuffer, a loud refusal when the backend does not consume it) and the split
path's ScopedBlitBindings rebinding is deleted; CopyTex resolves MGPCopyFromFramebuffer::Dst;
the mip descriptor check resolves MGPMipPlan::Res; the indirect families resolve their
buffer handles from the records and read the bytes from the server staged shadow with
RequireStagedCoverage. EnsureBufferResourceForHandle answers HasDefinedContent from
Desc.HasDefinedContent OR the staged coverage set (the streaming idiom defines content
through subdata with no new descriptor). The minting GetOrCreate(StatePtr), HandleOf and
the death switch's lifetime-id arms take Fatal{RoleViolation, MGPipeSlots} from the apply
thread, as do the three MGPipeSlots() entry points; the frontend BufferObject legacy
accessors take Fatal{RoleViolation, buffer-legacy-arm}; Magma's four caps reads go to the
server's own backend and the client-mirror fallback arm is a named refusal.
Evidence: unit 2156/2156 (incl. 9 new RemoteF1 + 10 new RemoteGuards death tests pinning
the four Fatal names); monolith smoke green; six production mutations each turned their
named test red and were restored; c1f_redcheck M5 RED then RESTORED GREEN. The mip
descriptor check overlaps tx and is coordinated at the merge (tx's sync rekey wins the
sync body, this handle resolution wins the identity check). CONTRACT-P5C section 3.
This commit is contained in:
@@ -356,6 +356,42 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
const Uint8* ResolveIndirectCommandBytes(const void* indirect, SizeT requiredBytes, const char* label) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.5): under an active transport the command buffer resolves
|
||||
// from the handle the verb record carried, and its CPU bytes are the SERVER's staged
|
||||
// shadow (rule E) - the client's GL_DRAW_INDIRECT_BUFFER binding slot, the frontend
|
||||
// object's MappedData()/GetSize() (B3) and the client slot allocator (T2) are never
|
||||
// read. Coverage is asserted: commands read from bytes no record staged would be
|
||||
// zero-filled, which is a missing record, not a valid command stream.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
const MG_Pipe::MGPipeHandle handle = MG_Pipe::MGPipeApplier().VerbIndirectBuffer;
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(handle)) {
|
||||
auto* resource = BufferImpl::FindBufferResourceForHandle(handle);
|
||||
const Uint8* const base = resource ? resource->hostBytes : nullptr;
|
||||
const SizeT commandOffset = reinterpret_cast<SizeT>(indirect);
|
||||
if (base == nullptr) {
|
||||
MGLOG_E_ONCE("%s skipped: the verb's indirect buffer {%u, %u} has no staged "
|
||||
"shadow on this side (GPU-written indirect commands are P8's)",
|
||||
label, handle.Slot, handle.Gen);
|
||||
return nullptr;
|
||||
}
|
||||
if (commandOffset + requiredBytes > BufferImpl::ResourceWidthForHandle(handle)) {
|
||||
MGLOG_E_ONCE("%s skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range", label);
|
||||
return nullptr;
|
||||
}
|
||||
BufferImpl::RequireStagedCoverage(*resource, base, commandOffset,
|
||||
commandOffset + requiredBytes, "indirect_command_bytes");
|
||||
return base + commandOffset; }
|
||||
// No buffer was bound: the frontend already raised the GL error; keep the old
|
||||
// fall-through shape (a null indirect pointer is the same skip it was).
|
||||
if (!indirect) {
|
||||
MGLOG_E_ONCE("%s skipped: indirect pointer is null", label);
|
||||
return nullptr;
|
||||
}
|
||||
MGLOG_E_ONCE("%s skipped: the verb carried no indirect buffer handle", label);
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
auto drawBuffer = MGB_CTX->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
|
||||
if (drawBuffer) {
|
||||
drawBuffer->SyncPersistentMappedRange();
|
||||
@@ -901,12 +937,28 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// Indirect Buffer Object - must also be bound to GL_DRAW_INDIRECT_BUFFER on the ES
|
||||
// context since indirect draws now execute natively on the GPU.
|
||||
if (includeIndirectBuffer) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.5): under an active transport the buffer is the verb
|
||||
// record's handle; the frontend binding slot and the client allocator are
|
||||
// never read (T2). The twin's ensure is what SyncBoundBuffer's did.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
const MG_Pipe::MGPipeHandle handle = MG_Pipe::MGPipeApplier().VerbIndirectBuffer;
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(handle)) {
|
||||
auto* resource = EnsureBufferResourceForHandle(nullptr, handle);
|
||||
if (resource && resource->id != 0) {
|
||||
BindBufferId(GL_DRAW_INDIRECT_BUFFER, resource->id);
|
||||
}
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
auto& possibleIndirectBuffer =
|
||||
MGB_CTX->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
|
||||
if (possibleIndirectBuffer) {
|
||||
SyncBoundBuffer(BufferTarget::DrawIndirect, GL_DRAW_INDIRECT_BUFFER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UBO binding points are (re)established per draw by BindCurrentProgramWithResources at
|
||||
// their compacted link-time points: CacheResourceLocations glUniformBlockBinding's the
|
||||
@@ -931,6 +983,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
SyncBufferBindingPoints(BufferTarget::ShaderStorage, GL_SHADER_STORAGE_BUFFER);
|
||||
MarkShaderStorageBuffersGpuWritten();
|
||||
if (includeDispatchIndirectBuffer) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.5): see the draw-indirect twin above - the verb
|
||||
// record's handle, never the frontend binding slot or the client allocator.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
const MG_Pipe::MGPipeHandle handle = MG_Pipe::MGPipeApplier().VerbDispatchIndirectBuffer;
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(handle)) {
|
||||
auto* resource = EnsureBufferResourceForHandle(nullptr, handle);
|
||||
if (resource && resource->id != 0) {
|
||||
BindBufferId(GL_DISPATCH_INDIRECT_BUFFER, resource->id);
|
||||
}
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
SyncBoundBuffer(BufferTarget::DispatchIndirect, GL_DISPATCH_INDIRECT_BUFFER);
|
||||
}
|
||||
}
|
||||
@@ -2846,6 +2911,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
!bound || bound == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO;
|
||||
if ((record.IsDefault != 0) != boundIsDefault) return false;
|
||||
if (boundIsDefault) return true;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd): the object-handle half of the identity check is a client-allocator
|
||||
// probe (T2). Under an active transport the record is the only statement of
|
||||
// identity (rule E) and the check does not run - the record was resolved from the
|
||||
// applier's OWN bound handle, so it is this binding's by construction (ID-19).
|
||||
// Monolith keeps the corroboration verbatim.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) return true;
|
||||
#endif
|
||||
return record.Fbo == g_backendFramebufferObjects.HandleOf(bound.get());
|
||||
}
|
||||
|
||||
@@ -3035,6 +3108,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (!backendObj) {
|
||||
backendObj = MakeShared<BackendFramebufferObject>();
|
||||
}
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd): with an active transport the twin's sync keys its applier-record
|
||||
// lookup on the record's own handle (m_pushedSyncHandle), never on the client
|
||||
// allocator (T2), and the table remembers which frontend object the twin is
|
||||
// synced from so a later handle-only resolution (the named blit) can reach it.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
backendObj->m_pushedSyncHandle = record.Fbo;
|
||||
g_backendFramebufferObjects.NoteStateForHandle(record.Fbo, currentFBO);
|
||||
}
|
||||
#endif
|
||||
|
||||
// THE "SAME FBO AS DRAW" SKIP IS NOW A FIELD, not a pointer comparison against
|
||||
// the object the previous iteration happened to sync. Target = Both is the
|
||||
@@ -4440,12 +4523,80 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
backendObj->Bind(target);
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.2/§3.3): the handle-keyed form of SyncAndBindFramebufferObject.
|
||||
// With an active transport a framebuffer reaches the server as the handle a record
|
||||
// carried - the named blit's ReadFbo/DrawFbo, or the applier's own bound handle for a
|
||||
// binding restore - and the client's binding slots and slot allocator are never probed
|
||||
// (T2/T3). The twin is resolved (or minted) BY HANDLE; its sync is keyed on THIS handle's
|
||||
// applier record through m_pushedSyncHandle; and the frontend object the sync body still
|
||||
// walks comes from the table's own state note (the object-class channel P3b/P4b retires),
|
||||
// never from a client binding slot. A twin with no noted object was never synced through
|
||||
// any binding - the DSA-on-a-never-bound-framebuffer case, which the split path already
|
||||
// refuses for the named clears - and is loud rather than silently unconfigured.
|
||||
void SyncAndBindFramebufferByHandle(MG_Pipe::MGPipeHandle fbo, FramebufferTarget target,
|
||||
Bool forceSync = false) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (MG_Pipe::MGPipeHandleIsNull(fbo) || fbo == MG_Pipe::kMGPipeDefaultFramebuffer) {
|
||||
// Same reset as the object form's default branch, for its reason.
|
||||
if (target == FramebufferTarget::Draw) {
|
||||
FramebufferImpl::g_alphaWidenedDrawBufferMask = 0;
|
||||
FramebufferImpl::g_integerColorDrawBufferMask = 0;
|
||||
}
|
||||
FramebufferImpl::BindFramebufferId(
|
||||
target == FramebufferTarget::Draw ? GL_DRAW_FRAMEBUFFER : GL_READ_FRAMEBUFFER, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
auto& registry = FramebufferImpl::g_backendFramebufferObjects;
|
||||
auto* twinSlot = registry.GetOrCreateByHandle(fbo);
|
||||
if (twinSlot == nullptr) {
|
||||
MGLOG_E_ONCE("MGPipe: framebuffer handle {%u, %u} is refused by the twin slot table "
|
||||
"(live generation %u); nothing is bound for the %s target",
|
||||
fbo.Slot, fbo.Gen, registry.LiveGenAt(fbo.Slot),
|
||||
target == FramebufferTarget::Read ? "READ" : "DRAW");
|
||||
return;
|
||||
}
|
||||
auto& backendObj = *twinSlot;
|
||||
if (!backendObj) {
|
||||
backendObj = MakeShared<FramebufferImpl::BackendFramebufferObject>();
|
||||
}
|
||||
backendObj->m_pushedSyncHandle = fbo;
|
||||
const auto stateObj = registry.StateForHandle(fbo);
|
||||
if (stateObj) {
|
||||
if (forceSync) {
|
||||
backendObj->InvalidateSyncedState();
|
||||
}
|
||||
backendObj->SyncToBackend(stateObj, target);
|
||||
} else {
|
||||
MGLOG_E_ONCE("MGPipe: framebuffer handle {%u, %u} has no frontend object noted on "
|
||||
"this side - it was never synced through a binding, so its twin is "
|
||||
"bound unconfigured",
|
||||
fbo.Slot, fbo.Gen);
|
||||
}
|
||||
backendObj->Bind(target);
|
||||
}
|
||||
#endif // MOBILEGL_BUILD_DISAGGREGATED
|
||||
|
||||
void ForceBindCurrentFBO(FramebufferTarget target) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
auto& slot = GetFramebufferBindingSlotChecked(target);
|
||||
const auto& fbo = slot.GetBoundObject();
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd): with an active transport the bound framebuffer's handle is the applier's
|
||||
// own working state; the object form's registry probe never runs (T2).
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith && FramebufferSubsystemEnabled()) {
|
||||
SyncAndBindFramebufferByHandle(
|
||||
MG_Pipe::MGPipeApplier().BoundFramebuffer[static_cast<SizeT>(
|
||||
target == FramebufferTarget::Read ? MG_Pipe::MGPipeFramebufferTarget::Read
|
||||
: MG_Pipe::MGPipeFramebufferTarget::Draw)],
|
||||
target);
|
||||
} else
|
||||
#endif
|
||||
SyncAndBindFramebufferObject(fbo, target);
|
||||
FramebufferImpl::g_fboSyncedSlotVersions[(SizeT)target] = slot.GetVersion();
|
||||
FramebufferImpl::g_fboSyncedObjectVersions[(SizeT)target] = fbo ? fbo->GetObjectVersion() : 0;
|
||||
@@ -5650,14 +5801,36 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (!restart.DrawIsValid()) return;
|
||||
type = restart.IndexType();
|
||||
indexSize = MG_Util::GetGLTypeSize(type);
|
||||
const Bool useNative = drawIndirectBuffer != nullptr && SupportsNativeIndirectDraws();
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.5): under an active transport the command buffer is the
|
||||
// handle the verb record carried (MGPipeApplier's verb stash) and the SSBO view's
|
||||
// resource resolves from it by handle - the frontend binding slot and the client slot
|
||||
// allocator are never read (T2).
|
||||
const MG_Pipe::MGPipeHandle verbBufferHandle =
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
? MG_Pipe::MGPipeApplier().VerbIndirectBuffer
|
||||
: MG_Pipe::kMGPipeNullHandle;
|
||||
#endif
|
||||
const Bool useNative =
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
? !MG_Pipe::MGPipeHandleIsNull(verbBufferHandle) && SupportsNativeIndirectDraws()
|
||||
:
|
||||
#endif
|
||||
drawIndirectBuffer != nullptr && SupportsNativeIndirectDraws();
|
||||
if (useNative) {
|
||||
// gl_BaseInstance must observe GPU-written command fields; expose the indirect
|
||||
// buffer to the program's mg_IndirectParams SSBO view and address it per draw.
|
||||
const auto backendProgram = GetCurrentBackendProgram();
|
||||
const Int paramsBinding = backendProgram ? backendProgram->GetIndirectParamsBinding() : -1;
|
||||
if (paramsBinding >= 0) {
|
||||
auto* resource = BufferImpl::EnsureBufferResource(drawIndirectBuffer);
|
||||
auto* resource =
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
? BufferImpl::EnsureBufferResourceForHandle(nullptr, verbBufferHandle)
|
||||
:
|
||||
#endif
|
||||
BufferImpl::EnsureBufferResource(drawIndirectBuffer);
|
||||
if (resource && resource->id != 0) {
|
||||
BufferImpl::BindBufferBaseCached(GL_SHADER_STORAGE_BUFFER, static_cast<GLuint>(paramsBinding),
|
||||
resource->id);
|
||||
@@ -5722,12 +5895,32 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// draw is what leaves a stale value, and restoring afterwards would only protect the
|
||||
// NEXT draw while these commands ran with the stale one.
|
||||
SetCurrentBaseVertex(0);
|
||||
const Bool useNative = drawIndirectBuffer != nullptr && SupportsNativeIndirectDraws();
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.5): see ExecuteIndexedIndirectCommands - the verb's handle,
|
||||
// never the frontend binding slot or the client allocator.
|
||||
const MG_Pipe::MGPipeHandle verbBufferHandle =
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
? MG_Pipe::MGPipeApplier().VerbIndirectBuffer
|
||||
: MG_Pipe::kMGPipeNullHandle;
|
||||
#endif
|
||||
const Bool useNative =
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
? !MG_Pipe::MGPipeHandleIsNull(verbBufferHandle) && SupportsNativeIndirectDraws()
|
||||
:
|
||||
#endif
|
||||
drawIndirectBuffer != nullptr && SupportsNativeIndirectDraws();
|
||||
if (useNative) {
|
||||
const auto backendProgram = GetCurrentBackendProgram();
|
||||
const Int paramsBinding = backendProgram ? backendProgram->GetIndirectParamsBinding() : -1;
|
||||
if (paramsBinding >= 0) {
|
||||
auto* resource = BufferImpl::EnsureBufferResource(drawIndirectBuffer);
|
||||
auto* resource =
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
? BufferImpl::EnsureBufferResourceForHandle(nullptr, verbBufferHandle)
|
||||
:
|
||||
#endif
|
||||
BufferImpl::EnsureBufferResource(drawIndirectBuffer);
|
||||
if (resource && resource->id != 0) {
|
||||
BufferImpl::BindBufferBaseCached(GL_SHADER_STORAGE_BUFFER, static_cast<GLuint>(paramsBinding),
|
||||
resource->id);
|
||||
@@ -6394,6 +6587,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
const auto& drawIndirectBuffer =
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.5): with an active transport the frontend binding slot
|
||||
// is never read - the Execute* helpers resolve the buffer from the verb record's
|
||||
// handle (T2). Monolith reads the slot as it always did.
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
? SharedPtr<MG_State::GLState::BufferObject>(nullptr)
|
||||
:
|
||||
#endif
|
||||
MGB_CTX->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
|
||||
ExecuteIndexedIndirectCommands(mode, type, indexSize, commandBytes, reinterpret_cast<SizeT>(indirect),
|
||||
drawIndirectBuffer, drawcount, stride, "MultiDrawElementsIndirect");
|
||||
@@ -6425,6 +6626,53 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return;
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.5): with an active transport both buffers resolve from the
|
||||
// verb record's handles (MGPDrawIndirect::Buffer / ParameterBuffer) and the count
|
||||
// reads from the SERVER's staged shadow - the frontend binding slots, the frontend
|
||||
// accessors (B3) and the client allocator (T2) are never read. Monolith takes the
|
||||
// original body below, verbatim.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
const auto& applierState = MG_Pipe::MGPipeApplier();
|
||||
const SizeT commandOffset = reinterpret_cast<SizeT>(indirect);
|
||||
const SizeT commandBytes = commandOffset + static_cast<SizeT>(stride) * static_cast<SizeT>(maxdrawcount - 1) +
|
||||
sizeof(DrawElementsIndirectCommand);
|
||||
auto* drawResource = BufferImpl::FindBufferResourceForHandle(applierState.VerbIndirectBuffer);
|
||||
auto* paramResource =
|
||||
BufferImpl::FindBufferResourceForHandle(applierState.VerbIndirectParameterBuffer);
|
||||
const Uint8* const drawBytes = drawResource ? drawResource->hostBytes : nullptr;
|
||||
const Uint8* const parameterBytes = paramResource ? paramResource->hostBytes : nullptr;
|
||||
if (commandBytes > BufferImpl::ResourceWidthForHandle(applierState.VerbIndirectBuffer)) {
|
||||
MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range");
|
||||
return;
|
||||
}
|
||||
if (drawcount < 0 || static_cast<SizeT>(drawcount) + sizeof(Uint32) >
|
||||
BufferImpl::ResourceWidthForHandle(applierState.VerbIndirectParameterBuffer)) {
|
||||
MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: invalid GL_PARAMETER_BUFFER binding or range");
|
||||
return;
|
||||
}
|
||||
// No staged shadow means no count to read, not a wrong one - the monolith body's
|
||||
// own rule for a buffer with no CPU shadow.
|
||||
if (parameterBytes == nullptr || drawBytes == nullptr) {
|
||||
MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: CPU fallback cannot read the parameter or "
|
||||
"draw-indirect buffer");
|
||||
return;
|
||||
}
|
||||
BufferImpl::RequireStagedCoverage(*drawResource, drawBytes, commandOffset, commandBytes,
|
||||
"multidraw_elements_indirect_count_commands");
|
||||
BufferImpl::RequireStagedCoverage(*paramResource, parameterBytes, static_cast<SizeT>(drawcount),
|
||||
static_cast<SizeT>(drawcount) + sizeof(Uint32),
|
||||
"multidraw_elements_indirect_count_parameter");
|
||||
Uint32 actualDrawCount = 0;
|
||||
std::memcpy(&actualDrawCount, parameterBytes + drawcount, sizeof(actualDrawCount));
|
||||
actualDrawCount = std::min<Uint32>(actualDrawCount, static_cast<Uint32>(maxdrawcount));
|
||||
ExecuteIndexedIndirectCommands(mode, type, indexSize, drawBytes + commandOffset, commandOffset,
|
||||
nullptr, static_cast<GLsizei>(actualDrawCount), stride,
|
||||
"MultiDrawElementsIndirectCount");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
auto drawBuffer = MGB_CTX->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
|
||||
auto parameterBuffer = MGB_CTX->GetBufferBindingSlot(BufferTarget::Parameter).GetBoundObject();
|
||||
if (!drawBuffer) {
|
||||
@@ -6496,6 +6744,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
const auto& drawIndirectBuffer =
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.5): with an active transport the frontend binding slot
|
||||
// is never read - the Execute* helpers resolve the buffer from the verb record's
|
||||
// handle (T2). Monolith reads the slot as it always did.
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
? SharedPtr<MG_State::GLState::BufferObject>(nullptr)
|
||||
:
|
||||
#endif
|
||||
MGB_CTX->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
|
||||
ExecuteArraysIndirectCommands(mode, commandBytes, reinterpret_cast<SizeT>(indirect), drawIndirectBuffer,
|
||||
drawcount, stride, "MultiDrawArraysIndirect");
|
||||
@@ -6527,6 +6783,48 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
DrawSyncFlags syncBit = DrawSyncBit::IndirectBuffer | DrawSyncBit::Instancing;
|
||||
PrepareForDraw(syncBit);
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.5): see the indexed twin - the verb record's handles and
|
||||
// the SERVER's staged shadow, never the frontend bindings or the client allocator.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
const auto& applierState = MG_Pipe::MGPipeApplier();
|
||||
const SizeT commandOffset = reinterpret_cast<SizeT>(indirect);
|
||||
const SizeT commandBytes = commandOffset + static_cast<SizeT>(stride) * static_cast<SizeT>(maxdrawcount - 1) +
|
||||
sizeof(DrawArraysIndirectCommand);
|
||||
auto* drawResource = BufferImpl::FindBufferResourceForHandle(applierState.VerbIndirectBuffer);
|
||||
auto* paramResource =
|
||||
BufferImpl::FindBufferResourceForHandle(applierState.VerbIndirectParameterBuffer);
|
||||
const Uint8* const drawBytes = drawResource ? drawResource->hostBytes : nullptr;
|
||||
const Uint8* const parameterBytes = paramResource ? paramResource->hostBytes : nullptr;
|
||||
if (commandBytes > BufferImpl::ResourceWidthForHandle(applierState.VerbIndirectBuffer)) {
|
||||
MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range");
|
||||
return;
|
||||
}
|
||||
if (drawcount < 0 || static_cast<SizeT>(drawcount) + sizeof(Uint32) >
|
||||
BufferImpl::ResourceWidthForHandle(applierState.VerbIndirectParameterBuffer)) {
|
||||
MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: invalid GL_PARAMETER_BUFFER binding or range");
|
||||
return;
|
||||
}
|
||||
if (parameterBytes == nullptr || drawBytes == nullptr) {
|
||||
MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: CPU fallback cannot read the parameter or "
|
||||
"draw-indirect buffer");
|
||||
return;
|
||||
}
|
||||
BufferImpl::RequireStagedCoverage(*drawResource, drawBytes, commandOffset, commandBytes,
|
||||
"multidraw_arrays_indirect_count_commands");
|
||||
BufferImpl::RequireStagedCoverage(*paramResource, parameterBytes, static_cast<SizeT>(drawcount),
|
||||
static_cast<SizeT>(drawcount) + sizeof(Uint32),
|
||||
"multidraw_arrays_indirect_count_parameter");
|
||||
Uint32 actualDrawCount = 0;
|
||||
std::memcpy(&actualDrawCount, parameterBytes + drawcount, sizeof(actualDrawCount));
|
||||
actualDrawCount = std::min<Uint32>(actualDrawCount, static_cast<Uint32>(maxdrawcount));
|
||||
ExecuteArraysIndirectCommands(mode, drawBytes + commandOffset, commandOffset, nullptr,
|
||||
static_cast<GLsizei>(actualDrawCount), stride,
|
||||
"MultiDrawArraysIndirectCount");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
auto drawBuffer = MGB_CTX->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
|
||||
auto parameterBuffer = MGB_CTX->GetBufferBindingSlot(BufferTarget::Parameter).GetBoundObject();
|
||||
if (!drawBuffer) {
|
||||
@@ -6700,6 +6998,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
const auto& drawIndirectBuffer =
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.5): with an active transport the frontend binding slot
|
||||
// is never read - the Execute* helpers resolve the buffer from the verb record's
|
||||
// handle (T2). Monolith reads the slot as it always did.
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
? SharedPtr<MG_State::GLState::BufferObject>(nullptr)
|
||||
:
|
||||
#endif
|
||||
MGB_CTX->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
|
||||
ExecuteIndexedIndirectCommands(mode, type, indexSize, commandBytes, reinterpret_cast<SizeT>(indirect),
|
||||
drawIndirectBuffer, 1, sizeof(DrawElementsIndirectCommand),
|
||||
@@ -6741,6 +7047,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
const auto& drawIndirectBuffer =
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.5): with an active transport the frontend binding slot
|
||||
// is never read - the Execute* helpers resolve the buffer from the verb record's
|
||||
// handle (T2). Monolith reads the slot as it always did.
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
? SharedPtr<MG_State::GLState::BufferObject>(nullptr)
|
||||
:
|
||||
#endif
|
||||
MGB_CTX->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
|
||||
ExecuteArraysIndirectCommands(mode, commandBytes, reinterpret_cast<SizeT>(indirect), drawIndirectBuffer, 1,
|
||||
sizeof(DrawArraysIndirectCommand), "DrawArraysIndirect");
|
||||
@@ -7757,6 +8071,63 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
DebugImpl::OpenGLScopeMarker marker(__func__);
|
||||
#endif
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.3): THE NAMED ARM. A blit record whose ReadFbo/DrawFbo are
|
||||
// non-null is a glBlitNamedFramebuffer: both framebuffers resolve from the record's
|
||||
// handles through the FBO twin table (the client's ScopedBlitBindings staging and this
|
||||
// function's binding-slot read at the bottom are gone from the split path), and the
|
||||
// sink is told the pair was consumed - a backend that reaches here without consuming
|
||||
// it has no named arm and the verb declines there.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
auto& applierState = MG_Pipe::MGPipeApplier();
|
||||
const MG_Pipe::MGPipeHandle readFbo = applierState.VerbBlitReadFbo;
|
||||
const MG_Pipe::MGPipeHandle drawFbo = applierState.VerbBlitDrawFbo;
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(readFbo) || !MG_Pipe::MGPipeHandleIsNull(drawFbo)) {
|
||||
applierState.VerbBlitNamedConsumed = true;
|
||||
TextureImpl::SyncNeccessaryTextures();
|
||||
RenderStateImpl::SyncRenderState();
|
||||
|
||||
SyncAndBindFramebufferByHandle(readFbo, FramebufferTarget::Read, /*forceSync=*/true);
|
||||
SyncAndBindFramebufferByHandle(drawFbo, FramebufferTarget::Draw, /*forceSync=*/true);
|
||||
|
||||
MGLOG_D("ES BlitNamedFramebuffer({%u,%u} -> {%u,%u}, %d, %d, %d, %d, %d, %d, %d, %d, 0x%x, %s)",
|
||||
readFbo.Slot, readFbo.Gen, drawFbo.Slot, drawFbo.Gen, srcX0, srcY0, srcX1,
|
||||
srcY1, dstX0, dstY0, dstX1, dstY1, mask,
|
||||
MG_Util::ConvertGLEnumToString(filter).c_str());
|
||||
// See the bound arm below: only the probed defect makes this do anything. It
|
||||
// reads the two frontend objects' attachments (the object-class channel P3b/P4b
|
||||
// retires), reached through the twins' state notes - and for a default endpoint
|
||||
// the default framebuffer object itself, exactly as the bound arm's binding-slot
|
||||
// read hands it. A missing object only skips the workaround.
|
||||
const auto objectFor = [](MG_Pipe::MGPipeHandle handle) {
|
||||
if (handle == MG_Pipe::kMGPipeDefaultFramebuffer) {
|
||||
return SharedPtr<MG_State::GLState::FramebufferObject>(
|
||||
MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO);
|
||||
}
|
||||
return FramebufferImpl::g_backendFramebufferObjects.StateForHandle(handle);
|
||||
};
|
||||
const auto readObj = objectFor(readFbo);
|
||||
const auto drawObj = objectFor(drawFbo);
|
||||
if (readObj && drawObj) {
|
||||
mask &= ~BlitLayeredDestinationAspects(readObj, drawObj, srcX0, srcY0, srcX1, srcY1,
|
||||
dstX0, dstY0, dstX1, dstY1, mask);
|
||||
}
|
||||
if (mask != 0) {
|
||||
IssueBlitWithResolveFallback(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1,
|
||||
mask, filter);
|
||||
}
|
||||
// The driver's bindings are now the two NAMED framebuffers; put the bound ones
|
||||
// back exactly as the monolith named entry point does.
|
||||
ForceBindCurrentFBO(FramebufferTarget::Read);
|
||||
ForceBindCurrentFBO(FramebufferTarget::Draw);
|
||||
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) {
|
||||
MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str());
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TextureImpl::SyncNeccessaryTextures();
|
||||
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) {
|
||||
MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str());
|
||||
@@ -7853,6 +8224,29 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.4): under an active transport the destination is the handle
|
||||
// the copy_framebuffer_to_texture record carried; the unit binding slot and the client
|
||||
// allocator are never read (T2/T4).
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
const MG_Pipe::MGPipeHandle dstHandle = MG_Pipe::MGPipeApplier().VerbCopyTexDst;
|
||||
auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.GetOrCreateByHandle(dstHandle);
|
||||
if (backendTextureSlot == nullptr) {
|
||||
MGLOG_E_ONCE("%s: the verb's destination texture handle {%u, %u} is refused by "
|
||||
"the twin slot table (live generation %u)",
|
||||
__func__, dstHandle.Slot, dstHandle.Gen,
|
||||
TextureImpl::g_backendTextureObjects.LiveGenAt(dstHandle.Slot));
|
||||
return false;
|
||||
}
|
||||
auto& backendObj = *backendTextureSlot;
|
||||
if (!backendObj) {
|
||||
backendObj = MakeShared<TextureImpl::BackendTextureObject>();
|
||||
}
|
||||
backendObj->Bind(TextureImpl::ConvertTextureTargetToBackendGLEnum(textureTarget), unit);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
const auto& bindingSlot = textureUnit.GetBindingSlot(textureTarget);
|
||||
{
|
||||
const auto& textureObject = bindingSlot.GetBoundObject();
|
||||
@@ -8083,8 +8477,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// The frontend has already defined the generated chain before emitting this verb.
|
||||
// Its resource_respecify carries that shape, so the server only verifies the
|
||||
// descriptor; it must not allocate or dirty the client's level shadows again.
|
||||
// Identity resolution is the same existing registry lookup used by texture sync.
|
||||
const auto handle = TextureImpl::g_backendTextureObjects.HandleOf(texture.get());
|
||||
// P5c (hd): identity is the handle the generate_mipmap record carried
|
||||
// (MGPMipPlan::Res, the verb stash) - the client allocator is never probed (T2).
|
||||
const auto handle = MG_Pipe::MGPipeApplier().VerbMipRes;
|
||||
const auto* record = PipeTextureRecordForHandle(handle);
|
||||
if (record == nullptr || record->Desc.Width == 0 || record->Desc.Levels == 0) {
|
||||
MG_Pipe::MGPipeUnmigratedEmulation("generate-mipmap-storage");
|
||||
@@ -8558,6 +8953,28 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// Bind necessary FBO and texture
|
||||
BindCurrentFBO(FramebufferTarget::Read);
|
||||
Uint activeTextureUnit = MGB_CTX->GetActiveTextureUnit();
|
||||
TextureImpl::BackendTextureObject* dstBackendTexture = nullptr;
|
||||
TextureInternalFormat mgInternalFormat{};
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.4): with an active transport the destination resolves from
|
||||
// the handle the copy_framebuffer_to_texture record carried (MGPCopyFromFramebuffer::
|
||||
// Dst, the verb stash) and its format is the applier descriptor's - the client's
|
||||
// texture-unit binding slot and the client slot allocator are never read (T2/T4).
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
const MG_Pipe::MGPipeHandle dstHandle = MG_Pipe::MGPipeApplier().VerbCopyTexDst;
|
||||
const auto* dstRecord = PipeTextureRecordForHandle(dstHandle);
|
||||
auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.FindByHandle(dstHandle);
|
||||
if (backendTextureSlot == nullptr || *backendTextureSlot == nullptr || dstRecord == nullptr) {
|
||||
MGLOG_E_ONCE("CopyTexImage2D: the verb's destination texture {%u, %u} has no twin "
|
||||
"or no applier record on this side",
|
||||
dstHandle.Slot, dstHandle.Gen);
|
||||
return;
|
||||
}
|
||||
dstBackendTexture = backendTextureSlot->get();
|
||||
mgInternalFormat = static_cast<TextureInternalFormat>(dstRecord->Desc.InternalFormat);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
const auto& textureObject = MGB_CTX->GetTextureUnitObject((Int)activeTextureUnit)
|
||||
.GetBindingSlot(MG_Util::ConvertGLEnumToTextureTarget(target))
|
||||
.GetBoundObject();
|
||||
@@ -8567,9 +8984,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
textureObject ? textureObject->GetExternalIndex() : 0);
|
||||
return;
|
||||
}
|
||||
(*backendTextureSlot)->Bind(target, activeTextureUnit);
|
||||
dstBackendTexture = backendTextureSlot->get();
|
||||
mgInternalFormat = textureObject->GetFormat();
|
||||
}
|
||||
dstBackendTexture->Bind(target, activeTextureUnit);
|
||||
|
||||
auto mgInternalFormat = textureObject->GetFormat();
|
||||
GLenum format = GL_DEPTH_COMPONENT;
|
||||
GLenum type = GL_UNSIGNED_INT;
|
||||
TextureImpl::GenerateTextureFormatInfo(mgInternalFormat, &internalformat, &format, &type,
|
||||
@@ -8603,7 +9022,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str());
|
||||
});
|
||||
|
||||
auto currentTex = (GLint)(*backendTextureSlot)->GetBackendTextureId();
|
||||
auto currentTex = (GLint)dstBackendTexture->GetBackendTextureId();
|
||||
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) {
|
||||
MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str());
|
||||
});
|
||||
@@ -8653,6 +9072,23 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// Bind necessary FBO and texture
|
||||
BindCurrentFBO(FramebufferTarget::Read);
|
||||
auto activeTextureUnit = MGB_CTX->GetActiveTextureUnit();
|
||||
TextureImpl::BackendTextureObject* dstBackendTexture = nullptr;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.4): see CopyTexImage2D - the record's Dst handle, never the
|
||||
// client's unit binding slot or the client allocator (T2/T4).
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
const MG_Pipe::MGPipeHandle dstHandle = MG_Pipe::MGPipeApplier().VerbCopyTexDst;
|
||||
auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.FindByHandle(dstHandle);
|
||||
if (backendTextureSlot == nullptr || *backendTextureSlot == nullptr) {
|
||||
MGLOG_E_ONCE("CopyTexSubImage2D: the verb's destination texture {%u, %u} has no "
|
||||
"twin on this side",
|
||||
dstHandle.Slot, dstHandle.Gen);
|
||||
return;
|
||||
}
|
||||
dstBackendTexture = backendTextureSlot->get();
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
const auto& textureObject = MGB_CTX->GetTextureUnitObject(activeTextureUnit)
|
||||
.GetBindingSlot(MG_Util::ConvertGLEnumToTextureTarget(target))
|
||||
.GetBoundObject();
|
||||
@@ -8662,7 +9098,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
textureObject ? textureObject->GetExternalIndex() : 0);
|
||||
return;
|
||||
}
|
||||
(*backendTextureSlot)->Bind(target, activeTextureUnit);
|
||||
dstBackendTexture = backendTextureSlot->get();
|
||||
}
|
||||
dstBackendTexture->Bind(target, activeTextureUnit);
|
||||
|
||||
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) {
|
||||
MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str());
|
||||
@@ -8684,7 +9122,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
});
|
||||
} else {
|
||||
MGLOG_D("%s: Backend depth", __func__);
|
||||
auto currentTex = (*backendTextureSlot)->GetBackendTextureId();
|
||||
auto currentTex = dstBackendTexture->GetBackendTextureId();
|
||||
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) {
|
||||
MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str());
|
||||
});
|
||||
|
||||
@@ -2702,6 +2702,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return twin ? twin->get() : nullptr;
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
void RequireStagedCoverage(GLESBufferResource& resource, const Uint8* hostBase, SizeT start,
|
||||
SizeT end, const char* site) {
|
||||
ServerStaged().RequireCoverage(&resource, hostBase, start, end, site);
|
||||
}
|
||||
#endif
|
||||
|
||||
MG_Pipe::MGPipeHandle HandleOfBuffer(const MG_State::GLState::BufferObject* bufferObject) {
|
||||
if (bufferObject == nullptr) return MG_Pipe::kMGPipeNullHandle;
|
||||
// Through the table's own single-entry front memo rather than straight into
|
||||
@@ -3100,7 +3107,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
// D-N keeps this call here for P3a. It can queue ranges and move the record, so
|
||||
// everything below is read AFTER it.
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.8): under an active transport the frontend accessor is a
|
||||
// layer-1 surface ("buffer-legacy-arm") and the client's own pre-verb
|
||||
// persistent-map push is the only producer; the call is skipped outright. Under
|
||||
// monolith D-N's placement stands.
|
||||
if (bufferObject && MG_Config::Transport == MG_Config::TransportMode::Monolith) {
|
||||
bufferObject->SyncPersistentMappedRange();
|
||||
}
|
||||
#else
|
||||
if (bufferObject) bufferObject->SyncPersistentMappedRange();
|
||||
#endif
|
||||
|
||||
const auto* record = ResourceRecordOf(res);
|
||||
const SizeT size = record != nullptr ? static_cast<SizeT>(record->Desc.Width) : 0;
|
||||
@@ -3128,24 +3145,28 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// and its bytes are read through persistentPtr.
|
||||
if (hostBase != nullptr) resource->hostBytes = hostBase;
|
||||
// "DOES THE SHADOW THIS PATH IS ABOUT TO UPLOAD HOLD MEANINGFUL BYTES?" - and it has
|
||||
// to be asked of the SAME thing the bytes come from, which is why it is not
|
||||
// record->Desc.HasDefinedContent. The descriptor states what was true at the last
|
||||
// resource_respecify and NOTHING refreshes it afterwards: resource_subdata,
|
||||
// resource_flush_range, buffer_subdata_resident and OnGpuWritten all define content
|
||||
// (BufferObject.cpp:85, :101, :127, :365, :441) without re-emitting a descriptor,
|
||||
// and re-emitting one per glBufferSubData would be new wire traffic D-J forbids. So
|
||||
// after the ordinary glBufferData(NULL) + glBufferSubData idiom the descriptor still
|
||||
// says "undefined", this full re-upload passed nullptr, RespecifyStorageWith
|
||||
// CLEARED the queued ranges and stamped syncedChangeSerial - i.e. an empty store
|
||||
// declared current, with the application's bytes dropped and nothing saying so.
|
||||
// The legacy arm pairs bufferObject.MappedData() with bufferObject.HasDefinedContent()
|
||||
// (RespecifyStorageNow) and this arm pairs the same two, so the source of the bytes
|
||||
// and the statement about them can never disagree. Declared, like C-1's IsMapped():
|
||||
// it is a frontend read this function keeps for P3a and it retires the moment the
|
||||
// client publishes a live content flag beside the descriptor (P5/P8). With no
|
||||
// frontend object (the handle-only drains) the descriptor is all there is.
|
||||
// to be asked of the SAME thing the bytes come from. The legacy arm pairs
|
||||
// bufferObject.MappedData() with bufferObject.HasDefinedContent() (RespecifyStorageNow)
|
||||
// and this arm pairs the same two, so the source of the bytes and the statement about
|
||||
// them can never disagree.
|
||||
//
|
||||
// P5c (hd, CONTRACT-P5C §3.6 / B1): with an active transport the answer is the
|
||||
// DESCRIPTOR's bit - which the client publishes on ResourceCreate/Respecify - OR the
|
||||
// staged coverage the content records behind it delivered. The coverage half is what
|
||||
// keeps the answer equal to the frontend flag's in the one case the descriptor alone
|
||||
// cannot see: an ORPHANING respecify (descriptor clear) followed by glBufferSubData,
|
||||
// which defines content without a new descriptor - the bytes crossed as
|
||||
// resource_subdata and the staged store's coverage is exactly "content defined since
|
||||
// the last orphan". With no coverage and a clear bit the store is undefined by the
|
||||
// application's own declaration and the upload stays a pure NULL reallocation.
|
||||
const Bool shadowHasContent =
|
||||
bufferObject ? bufferObject->HasDefinedContent() : (record->Desc.HasDefinedContent != 0);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
? (record->Desc.HasDefinedContent != 0 ||
|
||||
ServerStaged().CoveredRunCount(resource) != 0)
|
||||
:
|
||||
#endif
|
||||
(bufferObject ? bufferObject->HasDefinedContent() : (record->Desc.HasDefinedContent != 0));
|
||||
const void* initialData = shadowHasContent ? hostBase : nullptr;
|
||||
// M-3 / codex 4: a RespecifyStorageWith(..., initialData != nullptr) is a WHOLE-STORE
|
||||
// [0, size) upload from the base, so it owes the same coverage the pending-range drain
|
||||
@@ -9143,7 +9164,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
surface.Res.Slot, surface.Res.Gen);
|
||||
return false;
|
||||
}
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd): the corroborating cross-check against the frontend object's handle
|
||||
// is a client-allocator probe (T2). Under an active transport the record is the
|
||||
// ONLY statement of identity (rule E) and the check does not run; monolith
|
||||
// keeps it verbatim. The record's own handle was already validated above (N-3).
|
||||
if (MG_Config::Transport == MG_Config::TransportMode::Monolith &&
|
||||
!(TextureImpl::g_backendTextureObjects.HandleOf(textureObject.get()) == surface.Res)) {
|
||||
#else
|
||||
if (!(TextureImpl::g_backendTextureObjects.HandleOf(textureObject.get()) == surface.Res)) {
|
||||
#endif
|
||||
MGLOG_E_ONCE("MGPipe: attachment record names texture {%u, %u} but the frontend "
|
||||
"attachment's texture %u is handle {%u, %u} - refusing rather than "
|
||||
"attaching one of them",
|
||||
@@ -9358,7 +9388,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// FramebufferAttachmentType::None, i.e. GL_NONE. A surface that matches no colour
|
||||
// point cannot be a legal read buffer and is refused rather than guessed.
|
||||
if (FramebufferSubsystemEnabled()) {
|
||||
const MG_Pipe::MGPipeHandle fbo = g_backendFramebufferObjects.HandleOf(stateFBOObject.get());
|
||||
// P5c (hd): with an active transport the handle is the caller's
|
||||
// (m_pushedSyncHandle), never the client allocator's - see SyncToBackend.
|
||||
const MG_Pipe::MGPipeHandle fbo =
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
? m_pushedSyncHandle
|
||||
:
|
||||
#endif
|
||||
g_backendFramebufferObjects.HandleOf(stateFBOObject.get());
|
||||
// ID-19: the OBJECT's record, not "the record of whatever is bound to READ". A
|
||||
// framebuffer whose read buffer is being pushed need not be the read binding at
|
||||
// all - glNamedFramebufferReadBuffer and the DSA clears reach here by name - and
|
||||
@@ -9586,7 +9624,18 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// there is no framebuffer create or destroy in the catalogue and none is invented
|
||||
// - so the handle exists purely to key set_framebuffer_state, which is exactly
|
||||
// what it is used for here.
|
||||
const MG_Pipe::MGPipeHandle fbo = g_backendFramebufferObjects.HandleOf(stateFBOObject.get());
|
||||
//
|
||||
// P5c (hd): with an active transport the handle is the one the caller is applying
|
||||
// (m_pushedSyncHandle - the record-driven sync set it), and the client allocator
|
||||
// is never probed (T2). A null there means a caller reached this arm without a
|
||||
// record, which the null-record refusal below names.
|
||||
const MG_Pipe::MGPipeHandle fbo =
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
? m_pushedSyncHandle
|
||||
:
|
||||
#endif
|
||||
g_backendFramebufferObjects.HandleOf(stateFBOObject.get());
|
||||
pushedRecord = PushedFramebufferRecord(fbo);
|
||||
if (pushedRecord == nullptr) {
|
||||
MGLOG_E_ONCE("MGPipe: framebuffer %u has no applier record on the handle arm, so it "
|
||||
|
||||
@@ -467,6 +467,23 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return m_slotTable.LiveGenAt(slot);
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd): the two halves of SlotTables.h's state note, forwarded. A caller holding
|
||||
// both the record's handle and the frontend object (the record-driven sync) notes the
|
||||
// object so a later handle-only resolution can reach it without the client allocator.
|
||||
void NoteStateForHandle(MG_Pipe::MGPipeHandle handle, const StatePtr& stateObj) {
|
||||
if (EsprytSlotTablesEnabled()) {
|
||||
m_slotTable.NoteStateForHandle(handle, stateObj);
|
||||
}
|
||||
}
|
||||
StatePtr StateForHandle(MG_Pipe::MGPipeHandle handle) const {
|
||||
if (EsprytSlotTablesEnabled()) {
|
||||
return m_slotTable.StateForHandle(handle);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
// NO ReleaseByHandle HERE, AND THAT IS A DECISION (review M-4). The death half of
|
||||
// GetOrCreateByHandle exists for a kind whose announcement is its own destroy CALL
|
||||
// rather than the shared death notice - which is the BUFFER family
|
||||
@@ -892,6 +909,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
GLESBufferResource* GetOrCreateBufferResourceForHandle(MG_Pipe::MGPipeHandle res);
|
||||
GLESBufferResource* FindBufferResourceForHandle(MG_Pipe::MGPipeHandle res);
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd): the staged-coverage assertion (StagedShadowStore::RequireCoverage) for a
|
||||
// read of the server shadow outside the upload ladders - the indirect command-byte
|
||||
// resolver. A no-op for a base that is not this resource's server shadow.
|
||||
void RequireStagedCoverage(GLESBufferResource& resource, const Uint8* hostBase, SizeT start,
|
||||
SizeT end, const char* site);
|
||||
#endif
|
||||
|
||||
// MONOLITH GLUE, and named as such: the handle of a resource this backend is looking
|
||||
// at through a frontend object, resolved through the client allocator's lifetime-id
|
||||
// index. Every caller is a site P3a deliberately does NOT migrate - the SSBO / UBO /
|
||||
@@ -1810,6 +1835,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// still run when SyncCurrentFBO skips the READ-target sync because the same GL FBO is
|
||||
// bound as both draw and read (otherwise glReadBuffer changes would be silently dropped).
|
||||
void SyncReadBufferToBackend(const SharedPtr<MG_State::GLState::FramebufferObject>& stateFBOObject);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.2): the framebuffer handle the CURRENT sync is keyed on.
|
||||
// A caller applying a record sets it before SyncToBackend / SyncReadBufferToBackend,
|
||||
// which then read the applier's record for THAT handle instead of probing the
|
||||
// client's slot allocator for the frontend object's lifetime id (T2). Read only
|
||||
// with an active transport; monolith resolves through HandleOf as it always did.
|
||||
MG_Pipe::MGPipeHandle m_pushedSyncHandle = MG_Pipe::kMGPipeNullHandle;
|
||||
#endif
|
||||
void InvalidateSyncedState();
|
||||
Uint GetBackendFramebufferId() const { return m_backendFBOId; }
|
||||
void Bind(FramebufferTarget target) const;
|
||||
|
||||
@@ -75,6 +75,12 @@
|
||||
// belong on the client, and the backend should receive the handle in the verb payload. It is
|
||||
// NOT part of "Track H done" and check_include_closure.py does not probe MG_Backend headers,
|
||||
// so nothing catches it automatically.
|
||||
//
|
||||
// P5c (hd, CONTRACT-P5C §3.1) is what gives the debt teeth: with an active transport the
|
||||
// minting GetOrCreate, HandleOf and OnFrontendObjectDestroyed raise
|
||||
// Fatal{RoleViolation, "MGPipeSlots"} when reached from the apply thread (the same check the
|
||||
// allocator's own three entries carry, repeated here so the refusal names this surface), and
|
||||
// the split paths resolve through GetOrCreate(handle) / ReleaseByHandle instead.
|
||||
namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
@@ -228,6 +234,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// arm StateBackendObjectRegistry::GetOrCreate arms it itself.
|
||||
EnsureProcessTeardownSentinel();
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.1): this overload MINTS - it is monolith glue, and with
|
||||
// an active transport a call from the apply thread is Fatal{RoleViolation,
|
||||
// "MGPipeSlots"} before the allocator is touched. Split paths call the handle
|
||||
// overload below. (The check also lives at the allocator's own three entries; it
|
||||
// is repeated at this entry so the refusal names this surface even if the entry
|
||||
// set changes.)
|
||||
MG_Pipe::MGPipeRefuseAllocatorFromApplyThread("GetOrCreate(StatePtr)");
|
||||
#endif
|
||||
|
||||
const MG_Pipe::MGPipeHandle handle =
|
||||
MG_Pipe::MGPipeSlots().Acquire(kKind, stateObj->GetLifetimeId());
|
||||
MOBILEGL_ASSERT(!MG_Pipe::MGPipeHandleIsNull(handle),
|
||||
@@ -334,6 +350,34 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return entry.Live ? entry.Gen : 0;
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd): remember the frontend object a HANDLE-keyed twin was synced from. The
|
||||
// minting overload sets stateRef itself; the handle overload cannot (no object
|
||||
// crosses), so a caller that legitimately holds the object - the record-driven sync,
|
||||
// which arrived holding it through the object-class barrier-pulled rows - notes it
|
||||
// here. It is what lets a later handle-only resolution (P5c's named blit) reach the
|
||||
// frontend object the twin's sync body still walks, without probing the client's slot
|
||||
// allocator (T2). Same liveness rules as the minted stateRef: never an identity test,
|
||||
// never read to decide the slot is dead.
|
||||
void NoteStateForHandle(MG_Pipe::MGPipeHandle handle, const StatePtr& stateObj) {
|
||||
if (MG_Pipe::MGPipeHandleIsNull(handle) || handle.Slot >= m_slots.size()) return;
|
||||
Entry& entry = m_slots[handle.Slot];
|
||||
if (!entry.Live || entry.Gen != handle.Gen) return;
|
||||
entry.stateRef = stateObj;
|
||||
}
|
||||
|
||||
// The frontend object noted for this handle, or null. The handle answers identity;
|
||||
// this answers only "which object was this twin last synced from".
|
||||
StatePtr StateForHandle(MG_Pipe::MGPipeHandle handle) const {
|
||||
if (MG_Pipe::MGPipeHandleIsNull(handle) || handle.Slot >= m_slots.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
const Entry& entry = m_slots[handle.Slot];
|
||||
if (!entry.Live || entry.Gen != handle.Gen) return nullptr;
|
||||
return entry.stateRef.lock();
|
||||
}
|
||||
#endif
|
||||
|
||||
// P3a: the death half of the overload above, for a kind whose announcement is its own
|
||||
// destroy CALL rather than the shared death notice (D-L). Hands the twin OUT rather
|
||||
// than destroying it in place, because the caller may still have to decide what
|
||||
@@ -391,6 +435,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// allocator probe it always cost; a hit is refreshed the moment anyone acquires.
|
||||
MG_Pipe::MGPipeHandle HandleOf(const StateObject* stateObj) const {
|
||||
if (stateObj == nullptr) return MG_Pipe::kMGPipeNullHandle;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd): same guard as the minting overload - a lifetime-id probe from the
|
||||
// apply thread is Fatal{RoleViolation, "MGPipeSlots"} with an active transport.
|
||||
MG_Pipe::MGPipeRefuseAllocatorFromApplyThread("HandleOf");
|
||||
#endif
|
||||
const Uint64 lifetimeId = stateObj->GetLifetimeId();
|
||||
if (lifetimeId == m_memoLifetimeId) return m_memoHandle;
|
||||
const MG_Pipe::MGPipeHandle handle =
|
||||
@@ -414,6 +463,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// Returns whether the object had a slot of this kind, i.e. whether anything was freed;
|
||||
// a second call for the same id answers false because the allocator no longer maps it.
|
||||
static Bool OnFrontendObjectDestroyed(Uint64 lifetimeId) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd): the Find/Free pair below is monolith-only with an active transport -
|
||||
// a frontend death is announced to the server by the object_death record (ct),
|
||||
// and a direct call from the apply thread is Fatal{RoleViolation, "MGPipeSlots"}.
|
||||
MG_Pipe::MGPipeRefuseAllocatorFromApplyThread("OnFrontendObjectDestroyed");
|
||||
#endif
|
||||
const MG_Pipe::MGPipeHandle handle =
|
||||
MG_Pipe::MGPipeSlots().FindByLifetimeId(kKind, lifetimeId);
|
||||
if (MG_Pipe::MGPipeHandleIsNull(handle)) return false;
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <format>
|
||||
#include <regex>
|
||||
@@ -46,6 +47,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (MG_Backend::BackendObject* server = MG_Remote::Server::ServerLoopInstance().Backend()) {
|
||||
return &server->GetFormatCapabilities();
|
||||
}
|
||||
// P5c (hd, CONTRACT-P5C §3.7): the mirror fallback is REFUSED with an active
|
||||
// transport. The server's backend exists whenever the session does, so reaching
|
||||
// here is a bring-up ordering defect, and a silent read of the client caps mirror
|
||||
// (pActiveBackendObject is client memory, rule E) would hide it.
|
||||
MGLOG_F("MGPipe: Fatal{RoleViolation, \"caps-mirror\"} - the format-capability "
|
||||
"fallback to the client caps mirror was reached with an active transport "
|
||||
"but no server backend; the server's own backend is the only legal source "
|
||||
"on the apply thread");
|
||||
std::abort();
|
||||
}
|
||||
return pActiveBackendObject ? &pActiveBackendObject->GetFormatCapabilities() : nullptr;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
@@ -17,6 +17,10 @@
|
||||
#include "MG_Util/Metrics/PipeStats.h"
|
||||
#include "MG_Util/Metrics/TextureMetrics.h"
|
||||
#include "MG_Util/Miscellany/IndexGenerator.h"
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include <Config.h>
|
||||
#include <MG_Remote/Server/ServerLoop.h>
|
||||
#endif
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <cstring>
|
||||
@@ -710,7 +714,21 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void ShaderStorageBlockBinding(GLuint program, const GLchar* storageBlockName, GLuint storageBlockBinding) {
|
||||
auto* programObject = TryGetDirectVulkanProgram(program);
|
||||
if (!programObject || storageBlockName == nullptr) return;
|
||||
const Int maxBindings = pActiveBackendObject
|
||||
const Int maxBindings =
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.7): with an active transport the dynamic parameters are
|
||||
// the SERVER's own backend's - the client caps mirror (pActiveBackendObject) is
|
||||
// client memory the apply thread may not name (rule E). Monolith reads the mirror
|
||||
// as it always did.
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
? (MG_Remote::Server::ServerLoopInstance().Backend() != nullptr
|
||||
? static_cast<Int>(MG_Remote::Server::ServerLoopInstance().Backend()
|
||||
->GetDynamicParameters()
|
||||
.MaxShaderStorageBufferBindings)
|
||||
: 0)
|
||||
:
|
||||
#endif
|
||||
pActiveBackendObject
|
||||
? pActiveBackendObject->GetDynamicParameters().MaxShaderStorageBufferBindings
|
||||
: 0;
|
||||
if (storageBlockBinding >= static_cast<GLuint>(maxBindings)) {
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
#include "MagmaPipeArms.h"
|
||||
#include "MG_Util/Converters/MGToStr/DataTypeConverter.h"
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include <Config.h>
|
||||
#include <MG_Remote/Server/ServerLoop.h>
|
||||
#endif
|
||||
#include <utility>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
@@ -246,6 +250,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// set, a dvec3/dvec4 would be declined by ToVkVertexFormat AND left 64-bit in the
|
||||
// module, so a float32 stream would be fed to a Float64 input.
|
||||
const Bool narrowFloat64Arrays =
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.7): with an active transport the answer is the
|
||||
// SERVER's own backend's - the client caps mirror is client memory (rule E).
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
? (MG_Remote::Server::ServerLoopInstance().Backend() == nullptr ||
|
||||
!MG_Remote::Server::ServerLoopInstance().Backend()
|
||||
->GetDynamicParameters()
|
||||
.SupportsFloat64VertexAttributes)
|
||||
:
|
||||
#endif
|
||||
MG_Backend::pActiveBackendObject == nullptr ||
|
||||
!MG_Backend::pActiveBackendObject->GetDynamicParameters().SupportsFloat64VertexAttributes;
|
||||
if (sourceVkFormat == VK_FORMAT_UNDEFINED && attr.Type == DataType::Float64 && narrowFloat64Arrays) {
|
||||
@@ -499,7 +513,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// demoted `vec` input - the same thing DirectGLES does for the same state. The
|
||||
// frontend RECORDS the format either way, so this gate is the only thing standing
|
||||
// between a legal glVertexAttribLFormat and a mismatched pipeline.
|
||||
if (MG_Backend::pActiveBackendObject == nullptr ||
|
||||
if (
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.7): see the narrowFloat64Arrays site above.
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
? (MG_Remote::Server::ServerLoopInstance().Backend() == nullptr ||
|
||||
!MG_Remote::Server::ServerLoopInstance().Backend()
|
||||
->GetDynamicParameters()
|
||||
.SupportsFloat64VertexAttributes)
|
||||
:
|
||||
#endif
|
||||
MG_Backend::pActiveBackendObject == nullptr ||
|
||||
!MG_Backend::pActiveBackendObject->GetDynamicParameters().SupportsFloat64VertexAttributes) {
|
||||
return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
#include "MG_Util/SelfTest/PrimitivesGeneratedNoXfbProbe.h"
|
||||
#include "MG_Util/Texture/PixelStoreProcessor.h"
|
||||
#include <Config.h>
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include <MG_Remote/Server/ServerLoop.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cstdlib>
|
||||
@@ -664,6 +667,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
static void ApplyLineWidthState(VkCommandBuffer commandBuffer) {
|
||||
Float lineWidth = MGB_CTX->GetLineWidth();
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.7): with an active transport the dynamic parameters are the
|
||||
// SERVER's own backend's - the client caps mirror is client memory (rule E). Monolith
|
||||
// reads the mirror as it always did.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
if (MG_Backend::BackendObject* server = MG_Remote::Server::ServerLoopInstance().Backend()) {
|
||||
const auto& dynamicParameters = server->GetDynamicParameters();
|
||||
const Float minLineWidth = dynamicParameters.AliasedLineWidthRangeMin;
|
||||
const Float maxLineWidth = dynamicParameters.AliasedLineWidthRangeMax;
|
||||
if (lineWidth < minLineWidth) {
|
||||
lineWidth = minLineWidth;
|
||||
} else if (lineWidth > maxLineWidth) {
|
||||
lineWidth = maxLineWidth;
|
||||
}
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
if (MG_Backend::pActiveBackendObject != nullptr) {
|
||||
const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters();
|
||||
const Float minLineWidth = dynamicParameters.AliasedLineWidthRangeMin;
|
||||
|
||||
@@ -9,7 +9,30 @@
|
||||
// SlotAllocator.h. Compiled only under MOBILEGL_PIPE_PUSH.
|
||||
#include <MG_Impl/Pipe/SlotAllocator.h>
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include <Config.h>
|
||||
#include <MG_Remote/Server/ServerLoop.h>
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#endif
|
||||
|
||||
namespace MobileGL::MG_Pipe {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
void MGPipeRefuseAllocatorFromApplyThread(const char* entry) {
|
||||
if (MG_Config::Transport == MG_Config::TransportMode::Monolith) return;
|
||||
if (!MG_Remote::Server::ServerLoop::OnApplyThread()) return;
|
||||
MGLOG_F("MGPipe: Fatal{RoleViolation, \"MGPipeSlots\"} - the apply thread called "
|
||||
"MGPipeSlots().%s. With an active transport the client slot allocator is "
|
||||
"client-only memory (CONTRACT-P5C §3.1, rule E): a handle arrives already "
|
||||
"minted in a record, and a server that resolves or mints one off a frontend "
|
||||
"object's lifetime id is reading memory that will not exist on its side of a "
|
||||
"real split",
|
||||
entry);
|
||||
std::abort();
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
// The ShaderCso band the ordinary allocator must never enter: the top 1/16 of the
|
||||
// ShaderCso slot space is reserved for PROGRAM PIPELINE COMPOSITES, which are minted
|
||||
@@ -166,6 +189,9 @@ namespace MobileGL::MG_Pipe {
|
||||
}
|
||||
|
||||
MGPipeHandle MGPipeSlotAllocator::FindByLifetimeId(MGPipeKind kind, Uint64 lifetimeId) const {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MGPipeRefuseAllocatorFromApplyThread("FindByLifetimeId");
|
||||
#endif
|
||||
if (lifetimeId == 0) return kMGPipeNullHandle;
|
||||
const KindState& state = StateOf(kind);
|
||||
const auto it = state.ByLifetimeId.find(lifetimeId);
|
||||
@@ -176,12 +202,18 @@ namespace MobileGL::MG_Pipe {
|
||||
}
|
||||
|
||||
MGPipeHandle MGPipeSlotAllocator::Acquire(MGPipeKind kind, Uint64 lifetimeId) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MGPipeRefuseAllocatorFromApplyThread("Acquire");
|
||||
#endif
|
||||
const MGPipeHandle existing = FindByLifetimeId(kind, lifetimeId);
|
||||
if (!MGPipeHandleIsNull(existing)) return existing;
|
||||
return AllocateFor(kind, lifetimeId);
|
||||
}
|
||||
|
||||
void MGPipeSlotAllocator::Free(MGPipeKind kind, MGPipeHandle handle) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MGPipeRefuseAllocatorFromApplyThread("Free");
|
||||
#endif
|
||||
KindState& state = StateOf(kind);
|
||||
SlotState* entry = EntryOf(state, kind, handle.Slot);
|
||||
if (entry == nullptr) return;
|
||||
|
||||
@@ -163,4 +163,14 @@ namespace MobileGL::MG_Pipe {
|
||||
|
||||
// The monolith's one client allocator. Under split there is one per client context.
|
||||
MGPipeSlotAllocator& MGPipeSlots();
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.1 / §6 layer 1): with an active transport this allocator is a
|
||||
// CLIENT-only surface. Acquire, FindByLifetimeId and Free called from the apply thread -
|
||||
// i.e. a server that resolves or mints handles off a frontend object's lifetime id (T2),
|
||||
// which is memory that will not exist on its side of a real split - are
|
||||
// Fatal{RoleViolation, "MGPipeSlots"}. Compiled out entirely outside split builds, so the
|
||||
// pull build's bytes do not move (G1).
|
||||
void MGPipeRefuseAllocatorFromApplyThread(const char* entry);
|
||||
#endif
|
||||
} // namespace MobileGL::MG_Pipe
|
||||
|
||||
@@ -670,6 +670,50 @@ namespace MobileGL::MG_Pipe {
|
||||
MGPipeHandle BoundShaderCso = kMGPipeNullHandle;
|
||||
Uint64 ProgramBindingSerial = 0;
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// ---- P5c (hd, CONTRACT-P5C §3.2): THE CURRENT VERB'S OWN HANDLES. ----------------
|
||||
//
|
||||
// Server state, written by ServerVerbSink at the top of a verb's dispatch and read by
|
||||
// the backend DURING THAT SAME VERB, so the apply thread resolves the verb's objects
|
||||
// from the handles the record carried instead of probing the client's slot allocator
|
||||
// for a frontend object's lifetime id (T2). It is per-verb WORKING state, not object
|
||||
// state: every writer overwrites the whole set for its verb (null included), so a
|
||||
// value is only ever read between its verb's write and that verb's return, and there
|
||||
// is nothing to clear at a reset.
|
||||
//
|
||||
// Blit: both null = the bound-form blit (the bindings answer, as before). A non-null
|
||||
// pair names the read/draw framebuffers of a NAMED blit (MGPBlit::ReadFbo/DrawFbo);
|
||||
// the backend must then run its named-blit arm and raise VerbBlitNamedConsumed - the
|
||||
// sink reads that flag back so a backend with no named arm is a loud decline rather
|
||||
// than a silent blit of whatever is bound.
|
||||
MGPipeHandle VerbBlitReadFbo = kMGPipeNullHandle;
|
||||
MGPipeHandle VerbBlitDrawFbo = kMGPipeNullHandle;
|
||||
Bool VerbBlitNamedConsumed = false;
|
||||
// copy_framebuffer_to_texture's destination texture (MGPCopyFromFramebuffer::Dst).
|
||||
MGPipeHandle VerbCopyTexDst = kMGPipeNullHandle;
|
||||
// generate_mipmap's texture (MGPMipPlan::Res).
|
||||
MGPipeHandle VerbMipRes = kMGPipeNullHandle;
|
||||
// The current indirect draw's command buffer and (for the *Count forms) parameter
|
||||
// buffer (MGPDrawIndirect::Buffer / ParameterBuffer).
|
||||
MGPipeHandle VerbIndirectBuffer = kMGPipeNullHandle;
|
||||
MGPipeHandle VerbIndirectParameterBuffer = kMGPipeNullHandle;
|
||||
// dispatch_indirect's command buffer (MGPGridInfo::IndirectBuffer).
|
||||
MGPipeHandle VerbDispatchIndirectBuffer = kMGPipeNullHandle;
|
||||
|
||||
// Every verb's writer calls this FIRST and then sets its own fields, so no field ever
|
||||
// outlives the verb that wrote it and a reader can treat non-null as "this verb's".
|
||||
void ClearVerbHandles() {
|
||||
VerbBlitReadFbo = kMGPipeNullHandle;
|
||||
VerbBlitDrawFbo = kMGPipeNullHandle;
|
||||
VerbBlitNamedConsumed = false;
|
||||
VerbCopyTexDst = kMGPipeNullHandle;
|
||||
VerbMipRes = kMGPipeNullHandle;
|
||||
VerbIndirectBuffer = kMGPipeNullHandle;
|
||||
VerbIndirectParameterBuffer = kMGPipeNullHandle;
|
||||
VerbDispatchIndirectBuffer = kMGPipeNullHandle;
|
||||
}
|
||||
#endif
|
||||
|
||||
// ---- THE THREE FRAMEBUFFER ACCESSORS (ID-19(b)/(d)). They are functions rather than
|
||||
// members because the storage moved under them and their callers must not have to know
|
||||
// it did: `DrawFramebuffer()` / `ReadFramebuffer()` answer the question the two members
|
||||
|
||||
@@ -734,42 +734,19 @@ namespace MobileGL::MG_Remote::Client {
|
||||
nullptr, 0, nullptr);
|
||||
}
|
||||
|
||||
// DSA blits use the existing bound-form backend while the verb barrier holds.
|
||||
// Only client shadow bindings change here; no driver call or frontend pointer crosses
|
||||
// the wire. Bind() bumps their versions on entry and restore, so the next ordinary
|
||||
// verb republishes the application's original bindings even if no GL bind intervenes.
|
||||
class ScopedBlitBindings {
|
||||
public:
|
||||
using Fbo = MG_State::GLState::FramebufferObject;
|
||||
ScopedBlitBindings(const SharedPtr<Fbo>& read, const SharedPtr<Fbo>& draw)
|
||||
: m_read(MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read)),
|
||||
m_draw(MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw)),
|
||||
m_savedRead(m_read.GetBoundObject()), m_savedDraw(m_draw.GetBoundObject()) {
|
||||
m_read.Bind(read);
|
||||
m_draw.Bind(draw);
|
||||
}
|
||||
~ScopedBlitBindings() {
|
||||
m_read.Bind(m_savedRead);
|
||||
m_draw.Bind(m_savedDraw);
|
||||
}
|
||||
ScopedBlitBindings(const ScopedBlitBindings&) = delete;
|
||||
ScopedBlitBindings& operator=(const ScopedBlitBindings&) = delete;
|
||||
private:
|
||||
BindingSlot<Fbo>& m_read;
|
||||
BindingSlot<Fbo>& m_draw;
|
||||
SharedPtr<Fbo> m_savedRead;
|
||||
SharedPtr<Fbo> m_savedDraw;
|
||||
};
|
||||
|
||||
void EmitBlitNamedFramebuffer(
|
||||
const SharedPtr<MG_State::GLState::FramebufferObject>& read,
|
||||
const SharedPtr<MG_State::GLState::FramebufferObject>& draw,
|
||||
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0,
|
||||
GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) {
|
||||
ClientSession& session = RequireSession("BlitNamedFramebuffer");
|
||||
const ScopedBlitBindings bindings(read, draw);
|
||||
// The frontend's first validate preceded this temporary lowering. Refresh both
|
||||
// emitted framebuffer state and the existing BARRIER-PULLED binding fields now.
|
||||
// P5c (hd, CONTRACT-P5C §3.3): the scoped rebind of the client's own read/draw
|
||||
// binding slots is DELETED. It existed only to stage values for the server's
|
||||
// binding-slot read, and that read is gone: the sink resolves both framebuffers
|
||||
// from the record's handles. The validate still refreshes both framebuffers'
|
||||
// emitted state (their Named set_framebuffer_state records), which is now the
|
||||
// only description the server syncs from. No driver call or binding change
|
||||
// happens on the client.
|
||||
MG_Pipe::MGPipeValidateForVerb(MG_Pipe::MGPipeVerb::BlitNamedFramebuffer);
|
||||
BeforeReadOnlyVerb();
|
||||
MG_Pipe::MGPBlit record{};
|
||||
|
||||
@@ -229,16 +229,35 @@ namespace MobileGL::MG_Remote::Server {
|
||||
const MG_Backend::GlobalBackendFunctionsTable* table = Table("blit");
|
||||
if (table == nullptr) return false;
|
||||
if (table->GL.BlitFramebuffer == nullptr) return false;
|
||||
// Same ruling as OnClear's: the read and draw framebuffers are already bound by the
|
||||
// set_framebuffer_state records that preceded this one, so the unnamed entry point is
|
||||
// the one that matches what the server's state actually is. For the named form the
|
||||
// client temporarily binds the two named objects and validates before emitting; its
|
||||
// existing BARRIER-PULLED fields stay fixed until this call returns. ReadFbo/DrawFbo
|
||||
// retain the original identities for the later handle-only endpoint (CONTRACT-P5B §6).
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.3): the record's handles cross to the backend as the verb's
|
||||
// own state. The bound form carries two nulls and nothing changes; the named form's
|
||||
// pair is what the backend's named-blit arm resolves - the sink no longer relies on
|
||||
// "the read and draw framebuffers are already bound" (the client's ScopedBlitBindings
|
||||
// staging is deleted with this), and a backend that does not consume the pair has no
|
||||
// named arm, which is a loud decline rather than a blit of whatever is bound.
|
||||
auto& applierState = MG_Pipe::MGPipeApplier();
|
||||
applierState.ClearVerbHandles();
|
||||
applierState.VerbBlitReadFbo = blit.ReadFbo;
|
||||
applierState.VerbBlitDrawFbo = blit.DrawFbo;
|
||||
const Bool named = !MG_Pipe::MGPipeHandleIsNull(blit.ReadFbo) ||
|
||||
!MG_Pipe::MGPipeHandleIsNull(blit.DrawFbo);
|
||||
#endif
|
||||
table->GL.BlitFramebuffer(blit.SrcX0, blit.SrcY0, blit.SrcX1, blit.SrcY1, blit.DstX0,
|
||||
blit.DstY0, blit.DstX1, blit.DstY1,
|
||||
static_cast<GLbitfield>(blit.Mask),
|
||||
static_cast<GLenum>(blit.Filter));
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (named && !applierState.VerbBlitNamedConsumed) {
|
||||
MGLOG_E_ONCE("MGPipe: a named blit (read {%u, %u}, draw {%u, %u}) reached a backend "
|
||||
"with no named-blit arm; the verb is DECLINED rather than applied to "
|
||||
"the bound framebuffers",
|
||||
blit.ReadFbo.Slot, blit.ReadFbo.Gen, blit.DrawFbo.Slot, blit.DrawFbo.Gen);
|
||||
applierState.VerbBlitReadFbo = MG_Pipe::kMGPipeNullHandle;
|
||||
applierState.VerbBlitDrawFbo = MG_Pipe::kMGPipeNullHandle;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
++m_blits;
|
||||
return true;
|
||||
}
|
||||
@@ -447,6 +466,15 @@ namespace MobileGL::MG_Remote::Server {
|
||||
if (indirect != nullptr) {
|
||||
// The layout already refused a record that sets both flags or declares ranges
|
||||
// beside the block, so NumDraws is 0 and there is no span here.
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.5): the command/parameter buffer handles cross as the
|
||||
// verb's own state; the backend's indirect arm resolves the buffer twins from
|
||||
// them instead of reading the client's GL_DRAW_INDIRECT_BUFFER binding slot.
|
||||
auto& applierState = MG_Pipe::MGPipeApplier();
|
||||
applierState.ClearVerbHandles();
|
||||
applierState.VerbIndirectBuffer = indirect->Buffer;
|
||||
applierState.VerbIndirectParameterBuffer = indirect->ParameterBuffer;
|
||||
#endif
|
||||
const auto offset = reinterpret_cast<const void*>(static_cast<std::uintptr_t>(indirect->Offset));
|
||||
const auto drawCount = static_cast<GLsizei>(indirect->DrawCount);
|
||||
const auto stride = static_cast<GLsizei>(indirect->Stride);
|
||||
@@ -650,6 +678,14 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// IndirectBuffer travels for P7's sake; the BINDING is server state, put there by
|
||||
// the set_buffer_bindings record that preceded this one, exactly as OnClear's Fbo
|
||||
// is not re-resolved here. glDispatchComputeIndirect takes only the offset.
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.5): the buffer handle itself is now also the verb's own
|
||||
// state, so the backend's dispatch-indirect arm resolves the twin from the record
|
||||
// rather than from the client's GL_DISPATCH_INDIRECT_BUFFER binding slot.
|
||||
auto& applierState = MG_Pipe::MGPipeApplier();
|
||||
applierState.ClearVerbHandles();
|
||||
applierState.VerbDispatchIndirectBuffer = grid.IndirectBuffer;
|
||||
#endif
|
||||
gl.DispatchComputeIndirect(static_cast<GLintptr>(grid.IndirectOffset));
|
||||
} else {
|
||||
if (gl.DispatchCompute == nullptr) return false;
|
||||
@@ -870,6 +906,15 @@ namespace MobileGL::MG_Remote::Server {
|
||||
Bool ServerVerbSink::OnGenerateMipmap(const MG_Pipe::MGPMipPlan& plan) {
|
||||
const auto* table = Table("GenerateMipmap");
|
||||
if (table == nullptr || table->GL.GenerateMipmap == nullptr) return false;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.2): the texture the client resolved at Target on the active
|
||||
// unit crosses as the verb's own state; the backend's mip-descriptor check resolves
|
||||
// the record from it instead of probing the client allocator for the bound object's
|
||||
// lifetime id (T2's mip half).
|
||||
auto& applierState = MG_Pipe::MGPipeApplier();
|
||||
applierState.ClearVerbHandles();
|
||||
applierState.VerbMipRes = plan.Res;
|
||||
#endif
|
||||
table->GL.GenerateMipmap(plan.Target);
|
||||
return true;
|
||||
}
|
||||
@@ -877,6 +922,14 @@ namespace MobileGL::MG_Remote::Server {
|
||||
Bool ServerVerbSink::OnCopyFramebufferToTexture(const MG_Pipe::MGPCopyFromFramebuffer& copy) {
|
||||
const auto* table = Table(copy.SubImage ? "CopyTexSubImage2D" : "CopyTexImage2D");
|
||||
if (table == nullptr) return false;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.4): the destination texture the client resolved at the
|
||||
// active unit crosses as the verb's own state; the backend resolves its twin from the
|
||||
// handle instead of reading the client's texture-unit binding slot (T4).
|
||||
auto& applierState = MG_Pipe::MGPipeApplier();
|
||||
applierState.ClearVerbHandles();
|
||||
applierState.VerbCopyTexDst = copy.Dst;
|
||||
#endif
|
||||
if (copy.SubImage) {
|
||||
if (table->GL.CopyTexSubImage2D == nullptr) return false;
|
||||
table->GL.CopyTexSubImage2D(copy.Target, copy.Level, copy.XOffset, copy.YOffset,
|
||||
|
||||
@@ -29,6 +29,28 @@ namespace MobileGL::MG_State::GLState {
|
||||
const BufferBackendOps* g_bufferBackendOps = nullptr;
|
||||
// Starts at 1 so a zero-initialized cache slot can never carry a live buffer's id.
|
||||
std::atomic<Uint64> g_nextBufferLifetimeId{1};
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.8 / §6 layer 1): the frontend BufferObject's legacy
|
||||
// accessors are a layer-1 surface. With an active transport, the pre-handle buffer
|
||||
// arm that reads them (Managers.cpp's RespecifyStorageNow / UploadRangeNow /
|
||||
// IsBufferDrawClean / the old EnsureBufferResource body) stays compiled but may not
|
||||
// run: the apply thread calling one is Fatal{RoleViolation, "buffer-legacy-arm"} -
|
||||
// the same shape as Fatal{PipeLegacyMemosDisabled} - so a cleared subsystem bit 7 no
|
||||
// longer leaves the arm silently readable. Client-thread callers (the GL thread's
|
||||
// own state) are unaffected.
|
||||
void RefuseLegacyBufferArmFromApplyThread(const char* accessor) {
|
||||
if (!MG_Remote::Client::PersistentMapTracker::PushIsArmed()) return;
|
||||
if (!MG_Remote::Client::PersistentMapTracker::OnServerRole()) return;
|
||||
MGLOG_F("MGPipe: Fatal{RoleViolation, \"buffer-legacy-arm\"} - the apply thread "
|
||||
"called BufferObject::%s on a frontend object. With an active transport "
|
||||
"the server reads the applier's resource record and its own staged shadow; "
|
||||
"a frontend object is client memory (rule E) and the pre-handle buffer arm "
|
||||
"is monolith-only",
|
||||
accessor);
|
||||
std::abort();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Uint64 BufferObject::AllocateLifetimeId() {
|
||||
@@ -378,6 +400,9 @@ namespace MobileGL::MG_State::GLState {
|
||||
|
||||
void BufferObject::SyncPersistentMappedRange() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd): the named refusal comes FIRST - the silent return below used to let a
|
||||
// server-side caller slip through with one MGLOG_D's worth of evidence (B3).
|
||||
RefuseLegacyBufferArmFromApplyThread("SyncPersistentMappedRange");
|
||||
// Split's client pre-verb hook publishes these bytes. The retained backend sync
|
||||
// sites must do nothing on the apply thread: re-entering this producer there would
|
||||
// overwrite the server shadow through the monolith adapter without crossing the wire.
|
||||
@@ -914,6 +939,9 @@ namespace MobileGL::MG_State::GLState {
|
||||
}
|
||||
|
||||
const Uint8* BufferObject::MappedData() const {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
RefuseLegacyBufferArmFromApplyThread("MappedData");
|
||||
#endif
|
||||
return m_resource.Bytes();
|
||||
}
|
||||
|
||||
@@ -934,10 +962,18 @@ namespace MobileGL::MG_State::GLState {
|
||||
}
|
||||
|
||||
Uint64 BufferObject::GetChangeSerial() const {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
RefuseLegacyBufferArmFromApplyThread("GetChangeSerial");
|
||||
#endif
|
||||
return m_changeSerial;
|
||||
}
|
||||
|
||||
Bool BufferObject::HasDefinedContent() const {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// §3.6's surface as well as §3.8's: the descriptor's bit and the staged coverage
|
||||
// answer on this side; the frontend flag is client memory.
|
||||
RefuseLegacyBufferArmFromApplyThread("HasDefinedContent");
|
||||
#endif
|
||||
return m_hasDefinedContent;
|
||||
}
|
||||
|
||||
@@ -950,6 +986,9 @@ namespace MobileGL::MG_State::GLState {
|
||||
}
|
||||
|
||||
Bool BufferObject::IsMapped() const {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
RefuseLegacyBufferArmFromApplyThread("IsMapped");
|
||||
#endif
|
||||
return m_isMapped;
|
||||
}
|
||||
|
||||
|
||||
@@ -419,22 +419,26 @@ TEST(RemoteClientControls, BoundPackBufferOffsetReadRefusesByName) {
|
||||
}
|
||||
|
||||
TEST(RemoteClientControls, ServerRoleRespecifyDoesNotEmitClientInitialBytes) {
|
||||
// P5c (hd): the in-process server-role drive itself is now refused by name. The emit
|
||||
// path's first move is the client allocator's Acquire for the buffer's handle, and with
|
||||
// an active transport that entry is Fatal{RoleViolation, "MGPipeSlots"} from the apply
|
||||
// thread (CONTRACT-P5C §3.1, rule E). "No client initial bytes" therefore holds the
|
||||
// strong way - there is no client-emitter path for the server role to reach at all; ev's
|
||||
// SEG_EVENT producers are what replaces the mechanism this case used to drive.
|
||||
const auto child = RunInChild([] {
|
||||
CapsPeer backend;
|
||||
Srv::ServerSessionInstance().SetBackend(&backend);
|
||||
StartControlSession();
|
||||
MG_Config::Features.PipePush |= kMGPipeSubsystemResources;
|
||||
const auto before = ClientWireRecordsEmitted();
|
||||
if (Srv::ServerLoopInstance().RunOnApplyThread([](void*) {
|
||||
Srv::ServerLoopInstance().RunOnApplyThread([](void*) {
|
||||
MG_State::GLState::BufferObject buffer(701);
|
||||
Uint8 data[64]{};
|
||||
buffer.Respecify(sizeof(data), data);
|
||||
return MOBILEGL_OK;
|
||||
}, nullptr) != MOBILEGL_OK) ::_exit(92);
|
||||
if (ClientWireRecordsEmitted() != before) ::_exit(93);
|
||||
}, nullptr);
|
||||
ClientSessionInstance().Stop();
|
||||
});
|
||||
ExpectChildSuccess(child);
|
||||
ExpectNamedAbort(child, "Fatal{RoleViolation, \"MGPipeSlots\"}");
|
||||
}
|
||||
|
||||
TEST(RemoteClientControls, HeaderOnlyDynamicStateCrossesWithoutBlobMissing) {
|
||||
@@ -451,34 +455,33 @@ TEST(RemoteClientControls, HeaderOnlyDynamicStateCrossesWithoutBlobMissing) {
|
||||
}
|
||||
|
||||
TEST(RemoteClientControls, ServerRoleFlushDoesNotRunTheClientSubDataFollowup) {
|
||||
// P5c (hd): same refusal as the respecify sibling above - a server-role flush reaching the
|
||||
// client emit path is stopped at the allocator entry before any follow-up question arises
|
||||
// (Fatal{RoleViolation, "MGPipeSlots"}; ev's events replace the mechanism).
|
||||
const auto child = RunInChild([] {
|
||||
CapsPeer backend;
|
||||
Srv::ServerSessionInstance().SetBackend(&backend);
|
||||
StartControlSession();
|
||||
MG_Config::Features.PipePush |= kMGPipeSubsystemResources;
|
||||
const auto rc = Srv::ServerLoopInstance().RunOnApplyThread([](void*) {
|
||||
static int subdata = 0, flushes = 0;
|
||||
Srv::ServerLoopInstance().RunOnApplyThread([](void*) {
|
||||
MGPipeResourceOps ops{};
|
||||
ops.Create = [](MGPipeHandle, const MGPResourceDesc&) {};
|
||||
ops.Respecify = [](MGPipeHandle, const MGPResourceDesc&, const void*) {};
|
||||
ops.SubData = [](MGPipeHandle, const MGPSubData&, const void*) { ++subdata; };
|
||||
ops.FlushRange = [](MGPipeHandle, const MGPFlushRange&, const void*) { ++flushes; };
|
||||
ops.SubData = [](MGPipeHandle, const MGPSubData&, const void*) {};
|
||||
ops.FlushRange = [](MGPipeHandle, const MGPFlushRange&, const void*) {};
|
||||
ops.Destroy = [](MGPipeHandle) {};
|
||||
MGPipeSetResourceOps(&ops);
|
||||
{
|
||||
MG_State::GLState::BufferObject buffer(702);
|
||||
buffer.Resize(64);
|
||||
subdata = flushes = 0;
|
||||
MGPipeEmitResourceFlushRange(buffer, 16, 16, 0);
|
||||
if (subdata != 0 || flushes != 1) ::_exit(95);
|
||||
}
|
||||
MGPipeSetResourceOps(nullptr);
|
||||
return MOBILEGL_OK;
|
||||
}, nullptr);
|
||||
if (rc != MOBILEGL_OK) ::_exit(96);
|
||||
ClientSessionInstance().Stop();
|
||||
});
|
||||
ExpectChildSuccess(child);
|
||||
ExpectNamedAbort(child, "Fatal{RoleViolation, \"MGPipeSlots\"}");
|
||||
}
|
||||
// Uses the shipped EGL/GL frontend and real DirectGLES driver. Invoked explicitly by the
|
||||
// package gate because a native driver is not a prerequisite of the unit lane.
|
||||
|
||||
@@ -1230,10 +1230,12 @@ struct F1Peer : Codec::WireVerbSink {
|
||||
MGPClear clear{};
|
||||
MGPCopyFromFramebuffer copy{};
|
||||
MGPMipPlan mip{};
|
||||
MGPBlit blit{};
|
||||
unsigned calls = 0;
|
||||
Bool OnClear(const MGPClear& v) override { clear = v; ++calls; return true; }
|
||||
Bool OnCopyFramebufferToTexture(const MGPCopyFromFramebuffer& v) override { copy = v; ++calls; return true; }
|
||||
Bool OnGenerateMipmap(const MGPMipPlan& v) override { mip = v; ++calls; return true; }
|
||||
Bool OnBlit(const MGPBlit& v) override { blit = v; ++calls; return true; }
|
||||
void Install() {
|
||||
if (Srv::ServerLoopInstance().RunOnApplyThread([](void* self) {
|
||||
auto& decoder = Srv::ServerSessionInstance().Applier().*PeerMember(DecoderTag{});
|
||||
@@ -1514,6 +1516,333 @@ TEST(RemoteF1, UnboundNamedfiRefusesByName) {
|
||||
}
|
||||
#endif
|
||||
|
||||
// =====================================================================================
|
||||
// P5c (hd, CONTRACT-P5C §3): the record's handles are the server's resolution. The sink
|
||||
// publishes each verb's own handles into the applier's verb stash (§3.2), the named blit's
|
||||
// client emitter no longer rebinds anything (§3.3), and the layer-1 guards refuse the
|
||||
// client-only surfaces from the apply thread by name (§3.1, §3.7, §3.8).
|
||||
// =====================================================================================
|
||||
#if MGTEST_HAVE_FORK
|
||||
#include <MG_Backend/DirectGLES/SlotTables.h>
|
||||
#include <MG_Backend/DirectGLES/Utils.h>
|
||||
#include <MG_Impl/Pipe/SlotAllocator.h>
|
||||
#include <MG_State/GLState/BufferState/BufferObject.h>
|
||||
|
||||
TEST(RemoteF1, BlitNamedCarriesHandlesAndLeavesTheClientBindingsAlone) {
|
||||
// Red once (executed, reverted): re-bind the named arguments in the client shadow before
|
||||
// emitting (the deleted ScopedBlitBindings); the two binding-slot asserts fail.
|
||||
const auto child = RunInChild([] {
|
||||
StartControlSession();
|
||||
F1Peer peer;
|
||||
peer.Install();
|
||||
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
|
||||
auto boundRead = MakeShared<MG_State::GLState::FramebufferObject>(11);
|
||||
auto boundDraw = MakeShared<MG_State::GLState::FramebufferObject>(12);
|
||||
auto namedRead = MakeShared<MG_State::GLState::FramebufferObject>(13);
|
||||
auto namedDraw = MakeShared<MG_State::GLState::FramebufferObject>(14);
|
||||
MG_State::pGLContext->GetFramebufferBindingSlot(MobileGL::FramebufferTarget::Read).Bind(boundRead);
|
||||
MG_State::pGLContext->GetFramebufferBindingSlot(MobileGL::FramebufferTarget::Draw).Bind(boundDraw);
|
||||
|
||||
RemoteEmitTable().GL.BlitNamedFramebuffer(namedRead, namedDraw, 0, 0, 8, 8, 0, 0, 8, 8,
|
||||
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
const auto& r = peer.blit;
|
||||
if (peer.calls != 1 || r.ReadFbo != MGPipeFramebufferEmitter::HandleFor(*namedRead) ||
|
||||
r.DrawFbo != MGPipeFramebufferEmitter::HandleFor(*namedDraw)) ::_exit(101);
|
||||
// The client's own bindings are untouched: the server resolves the named pair from the
|
||||
// record, not from a staged binding override (T3).
|
||||
if (MG_State::pGLContext->GetFramebufferBindingSlot(MobileGL::FramebufferTarget::Read)
|
||||
.GetBoundObject()
|
||||
.get() != boundRead.get()) ::_exit(102);
|
||||
if (MG_State::pGLContext->GetFramebufferBindingSlot(MobileGL::FramebufferTarget::Draw)
|
||||
.GetBoundObject()
|
||||
.get() != boundDraw.get()) ::_exit(103);
|
||||
ClientSessionInstance().Stop();
|
||||
});
|
||||
ExpectChildSuccess(child);
|
||||
}
|
||||
|
||||
namespace {
|
||||
Bool g_stubBlitCalled = false;
|
||||
MGPipeHandle g_observedBlitRead = kMGPipeNullHandle;
|
||||
MGPipeHandle g_observedBlitDraw = kMGPipeNullHandle;
|
||||
void StubBlitFramebuffer(GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLbitfield, GLenum) {
|
||||
g_stubBlitCalled = true;
|
||||
const auto& st = MG_Pipe::MGPipeApplier();
|
||||
g_observedBlitRead = st.VerbBlitReadFbo;
|
||||
g_observedBlitDraw = st.VerbBlitDrawFbo;
|
||||
}
|
||||
void StubBlitFramebufferConsuming(GLint x0, GLint y0, GLint x1, GLint y1, GLint dx0, GLint dy0,
|
||||
GLint dx1, GLint dy1, GLbitfield mask, GLenum filter) {
|
||||
StubBlitFramebuffer(x0, y0, x1, y1, dx0, dy0, dx1, dy1, mask, filter);
|
||||
MG_Pipe::MGPipeApplier().VerbBlitNamedConsumed = true;
|
||||
}
|
||||
|
||||
MGPipeHandle g_observedCopyDst = kMGPipeNullHandle;
|
||||
void StubCopyTexImage2D(GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLsizei, GLint) {
|
||||
g_observedCopyDst = MG_Pipe::MGPipeApplier().VerbCopyTexDst;
|
||||
}
|
||||
MGPipeHandle g_observedMipRes = kMGPipeNullHandle;
|
||||
void StubGenerateMipmap(GLenum) { g_observedMipRes = MG_Pipe::MGPipeApplier().VerbMipRes; }
|
||||
MGPipeHandle g_observedIndirect = kMGPipeNullHandle;
|
||||
MGPipeHandle g_observedParameter = kMGPipeNullHandle;
|
||||
void StubMultiDrawArraysIndirectCount(GLenum, const void*, GLintptr, GLsizei, GLsizei) {
|
||||
const auto& st = MG_Pipe::MGPipeApplier();
|
||||
g_observedIndirect = st.VerbIndirectBuffer;
|
||||
g_observedParameter = st.VerbIndirectParameterBuffer;
|
||||
}
|
||||
MGPipeHandle g_observedDispatchIndirect = kMGPipeNullHandle;
|
||||
void StubDispatchComputeIndirect(GLintptr) {
|
||||
g_observedDispatchIndirect = MG_Pipe::MGPipeApplier().VerbDispatchIndirectBuffer;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(RemoteF1, NamedBlitStashesHandlesAndDeclinesWhenTheBackendHasNoNamedArm) {
|
||||
// Red once (executed, reverted): do not write the verb stash; the observed handles are null.
|
||||
const auto child = RunInChild([] {
|
||||
MG_Config::Transport = MG_Config::TransportMode::InProcess;
|
||||
CapsPeer backend;
|
||||
backend.table.GL.BlitFramebuffer = &StubBlitFramebuffer;
|
||||
Srv::ServerVerbSink sink;
|
||||
sink.SetBackend(&backend);
|
||||
MGPBlit r{};
|
||||
r.ReadFbo = {41, 2};
|
||||
r.DrawFbo = {42, 3};
|
||||
r.Mask = GL_COLOR_BUFFER_BIT;
|
||||
r.Filter = GL_NEAREST;
|
||||
const Bool applied = sink.OnBlit(r);
|
||||
if (applied) ::_exit(101); // a named pair the backend did not consume must decline
|
||||
if (!g_stubBlitCalled) ::_exit(102);
|
||||
// The backend saw the record's handles as the verb's own state (CONTRACT-P5C §3.2/§3.3).
|
||||
if (g_observedBlitRead != r.ReadFbo || g_observedBlitDraw != r.DrawFbo) ::_exit(103);
|
||||
::_exit(0);
|
||||
});
|
||||
ExpectChildSuccess(child);
|
||||
}
|
||||
|
||||
TEST(RemoteF1, NamedBlitIsAppliedWhenTheBackendConsumesThePair) {
|
||||
const auto child = RunInChild([] {
|
||||
MG_Config::Transport = MG_Config::TransportMode::InProcess;
|
||||
CapsPeer backend;
|
||||
backend.table.GL.BlitFramebuffer = &StubBlitFramebufferConsuming;
|
||||
Srv::ServerVerbSink sink;
|
||||
sink.SetBackend(&backend);
|
||||
MGPBlit r{};
|
||||
r.ReadFbo = {41, 2};
|
||||
r.DrawFbo = {42, 3};
|
||||
r.Mask = GL_COLOR_BUFFER_BIT;
|
||||
r.Filter = GL_NEAREST;
|
||||
if (!sink.OnBlit(r)) ::_exit(101);
|
||||
if (g_observedBlitRead != r.ReadFbo || g_observedBlitDraw != r.DrawFbo) ::_exit(102);
|
||||
::_exit(0);
|
||||
});
|
||||
ExpectChildSuccess(child);
|
||||
}
|
||||
|
||||
TEST(RemoteF1, BoundBlitCarriesNoHandlesAndIsAppliedAsBefore) {
|
||||
const auto child = RunInChild([] {
|
||||
MG_Config::Transport = MG_Config::TransportMode::InProcess;
|
||||
CapsPeer backend;
|
||||
backend.table.GL.BlitFramebuffer = &StubBlitFramebuffer;
|
||||
Srv::ServerVerbSink sink;
|
||||
sink.SetBackend(&backend);
|
||||
MGPBlit r{};
|
||||
r.Mask = GL_COLOR_BUFFER_BIT;
|
||||
r.Filter = GL_NEAREST;
|
||||
if (!sink.OnBlit(r)) ::_exit(101);
|
||||
if (!MGPipeHandleIsNull(g_observedBlitRead) || !MGPipeHandleIsNull(g_observedBlitDraw))
|
||||
::_exit(102);
|
||||
::_exit(0);
|
||||
});
|
||||
ExpectChildSuccess(child);
|
||||
}
|
||||
|
||||
TEST(RemoteF1, CopyFramebufferToTextureStashesTheDestinationHandle) {
|
||||
// Red once (executed, reverted): do not write VerbCopyTexDst; the observed handle is null.
|
||||
const auto child = RunInChild([] {
|
||||
MG_Config::Transport = MG_Config::TransportMode::InProcess;
|
||||
CapsPeer backend;
|
||||
backend.table.GL.CopyTexImage2D = &StubCopyTexImage2D;
|
||||
Srv::ServerVerbSink sink;
|
||||
sink.SetBackend(&backend);
|
||||
MGPCopyFromFramebuffer r{};
|
||||
r.Dst = {51, 4};
|
||||
r.Target = GL_TEXTURE_2D;
|
||||
r.Level = 1;
|
||||
r.InternalFormat = GL_RGBA8;
|
||||
r.Width = 8;
|
||||
r.Height = 8;
|
||||
if (!sink.OnCopyFramebufferToTexture(r)) ::_exit(101);
|
||||
if (g_observedCopyDst != r.Dst) ::_exit(102);
|
||||
::_exit(0);
|
||||
});
|
||||
ExpectChildSuccess(child);
|
||||
}
|
||||
|
||||
TEST(RemoteF1, GenerateMipmapStashesTheTextureHandle) {
|
||||
// Red once (executed, reverted): do not write VerbMipRes; the observed handle is null.
|
||||
const auto child = RunInChild([] {
|
||||
MG_Config::Transport = MG_Config::TransportMode::InProcess;
|
||||
CapsPeer backend;
|
||||
backend.table.GL.GenerateMipmap = &StubGenerateMipmap;
|
||||
Srv::ServerVerbSink sink;
|
||||
sink.SetBackend(&backend);
|
||||
MGPMipPlan r{};
|
||||
r.Res = {52, 5};
|
||||
r.Target = GL_TEXTURE_2D;
|
||||
if (!sink.OnGenerateMipmap(r)) ::_exit(101);
|
||||
if (g_observedMipRes != r.Res) ::_exit(102);
|
||||
::_exit(0);
|
||||
});
|
||||
ExpectChildSuccess(child);
|
||||
}
|
||||
|
||||
TEST(RemoteF1, AnIndirectDrawStashesTheCommandAndParameterBuffers) {
|
||||
// Red once (executed, reverted): do not write the indirect stash; the observed handles are null.
|
||||
const auto child = RunInChild([] {
|
||||
MG_Config::Transport = MG_Config::TransportMode::InProcess;
|
||||
CapsPeer backend;
|
||||
backend.table.GL.MultiDrawArraysIndirectCount = &StubMultiDrawArraysIndirectCount;
|
||||
Srv::ServerVerbSink sink;
|
||||
sink.SetBackend(&backend);
|
||||
MGPDrawInfo info{};
|
||||
info.Mode = GL_TRIANGLES;
|
||||
info.IndexSize = 0;
|
||||
info.NumDraws = 0;
|
||||
info.InstanceCount = 1;
|
||||
MGPDrawIndirect indirect{};
|
||||
indirect.Buffer = {61, 1};
|
||||
indirect.ParameterBuffer = {62, 1};
|
||||
indirect.Offset = 64;
|
||||
indirect.DrawCount = 3;
|
||||
indirect.Stride = 16;
|
||||
if (!sink.OnDrawVbo(info, nullptr, nullptr, &indirect)) ::_exit(101);
|
||||
if (g_observedIndirect != indirect.Buffer || g_observedParameter != indirect.ParameterBuffer)
|
||||
::_exit(102);
|
||||
::_exit(0);
|
||||
});
|
||||
ExpectChildSuccess(child);
|
||||
}
|
||||
|
||||
TEST(RemoteF1, DispatchIndirectStashesTheCommandBuffer) {
|
||||
// Red once (executed, reverted): do not write VerbDispatchIndirectBuffer; the observed
|
||||
// handle is null.
|
||||
const auto child = RunInChild([] {
|
||||
MG_Config::Transport = MG_Config::TransportMode::InProcess;
|
||||
CapsPeer backend;
|
||||
backend.table.GL.DispatchComputeIndirect = &StubDispatchComputeIndirect;
|
||||
Srv::ServerVerbSink sink;
|
||||
sink.SetBackend(&backend);
|
||||
MGPGridInfo grid{};
|
||||
grid.IsIndirect = 1;
|
||||
grid.IndirectBuffer = {63, 1};
|
||||
grid.IndirectOffset = 128;
|
||||
if (!sink.OnLaunchGrid(grid)) ::_exit(101);
|
||||
if (g_observedDispatchIndirect != grid.IndirectBuffer) ::_exit(102);
|
||||
::_exit(0);
|
||||
});
|
||||
ExpectChildSuccess(child);
|
||||
}
|
||||
|
||||
// ---- the layer-1 guards (CONTRACT-P5C §6), each verified red once by its own Fatal ---------
|
||||
|
||||
TEST(RemoteGuards, AllocatorAcquireFromTheApplyThreadIsFatalByName) {
|
||||
const auto child = RunInChild([] {
|
||||
StartControlSession();
|
||||
Srv::ServerLoopInstance().RunOnApplyThread([](void*) {
|
||||
MGPipeSlots().Acquire(MGPipeKind::Texture, 424242);
|
||||
return MOBILEGL_OK;
|
||||
}, nullptr);
|
||||
ClientSessionInstance().Stop();
|
||||
});
|
||||
ExpectNamedAbort(child, "Fatal{RoleViolation, \"MGPipeSlots\"}");
|
||||
}
|
||||
|
||||
TEST(RemoteGuards, AllocatorFindByLifetimeIdFromTheApplyThreadIsFatalByName) {
|
||||
const auto child = RunInChild([] {
|
||||
StartControlSession();
|
||||
Srv::ServerLoopInstance().RunOnApplyThread([](void*) {
|
||||
MGPipeSlots().FindByLifetimeId(MGPipeKind::Texture, 424242);
|
||||
return MOBILEGL_OK;
|
||||
}, nullptr);
|
||||
ClientSessionInstance().Stop();
|
||||
});
|
||||
ExpectNamedAbort(child, "Fatal{RoleViolation, \"MGPipeSlots\"}");
|
||||
}
|
||||
|
||||
TEST(RemoteGuards, AllocatorFreeFromTheApplyThreadIsFatalByName) {
|
||||
const auto child = RunInChild([] {
|
||||
StartControlSession();
|
||||
Srv::ServerLoopInstance().RunOnApplyThread([](void*) {
|
||||
MGPipeSlots().Free(MGPipeKind::Texture, {7, 1});
|
||||
return MOBILEGL_OK;
|
||||
}, nullptr);
|
||||
ClientSessionInstance().Stop();
|
||||
});
|
||||
ExpectNamedAbort(child, "Fatal{RoleViolation, \"MGPipeSlots\"}");
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct FakeTwin {
|
||||
int marker = 0;
|
||||
};
|
||||
using TestTextureTable =
|
||||
MG_Backend::DirectGLES::BackendSlotTable<MG_State::GLState::TextureObject2D, FakeTwin,
|
||||
MGPipeKind::Texture>;
|
||||
} // namespace
|
||||
|
||||
TEST(RemoteGuards, SlotTableHandleOfFromTheApplyThreadIsFatalByName) {
|
||||
const auto child = RunInChild([] {
|
||||
StartControlSession();
|
||||
Srv::ServerLoopInstance().RunOnApplyThread([](void*) {
|
||||
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
|
||||
TestTextureTable table;
|
||||
MG_State::GLState::TextureObject2D tex(44);
|
||||
table.HandleOf(&tex);
|
||||
return MOBILEGL_OK;
|
||||
}, nullptr);
|
||||
ClientSessionInstance().Stop();
|
||||
});
|
||||
ExpectNamedAbort(child, "Fatal{RoleViolation, \"MGPipeSlots\"}");
|
||||
}
|
||||
|
||||
TEST(RemoteGuards, SlotTableMintingGetOrCreateFromTheApplyThreadIsFatalByName) {
|
||||
const auto child = RunInChild([] {
|
||||
StartControlSession();
|
||||
Srv::ServerLoopInstance().RunOnApplyThread([](void*) {
|
||||
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
|
||||
TestTextureTable table;
|
||||
auto tex = MakeShared<MG_State::GLState::TextureObject2D>(45);
|
||||
table.GetOrCreate(tex);
|
||||
return MOBILEGL_OK;
|
||||
}, nullptr);
|
||||
ClientSessionInstance().Stop();
|
||||
});
|
||||
ExpectNamedAbort(child, "Fatal{RoleViolation, \"MGPipeSlots\"}");
|
||||
}
|
||||
|
||||
// The four accessor drives share one shape: the BufferObject is constructed on the CLIENT
|
||||
// thread (its own resource_create publication is a legal client-side emit), and only the
|
||||
// accessor runs on the apply thread, where it must die at the accessor's own guard.
|
||||
#define MGL_BUFFER_GUARD_TEST(Name, Id, Call) TEST(RemoteGuards, Name) { const auto child = RunInChild([] { StartControlSession(); MG_State::GLState::BufferObject buffer(Id); Srv::ServerLoopInstance().RunOnApplyThread([](void* self) { auto& buffer = *static_cast<MG_State::GLState::BufferObject*>(self); Call; return MOBILEGL_OK; }, &buffer); ClientSessionInstance().Stop(); }); ExpectNamedAbort(child, "Fatal{RoleViolation, \"buffer-legacy-arm\"}"); }
|
||||
MGL_BUFFER_GUARD_TEST(BufferMappedDataFromTheApplyThreadIsFatalByName, 703, (void)buffer.MappedData())
|
||||
MGL_BUFFER_GUARD_TEST(BufferIsMappedFromTheApplyThreadIsFatalByName, 704, (void)buffer.IsMapped())
|
||||
MGL_BUFFER_GUARD_TEST(BufferChangeSerialFromTheApplyThreadIsFatalByName, 705, (void)buffer.GetChangeSerial())
|
||||
MGL_BUFFER_GUARD_TEST(BufferSyncPersistentMappedRangeFromTheApplyThreadIsFatalByName, 706,
|
||||
buffer.SyncPersistentMappedRange())
|
||||
#undef MGL_BUFFER_GUARD_TEST
|
||||
|
||||
TEST(RemoteGuards, CapsMirrorFallbackWithNoServerBackendIsFatalByName) {
|
||||
const auto child = RunInChild([] {
|
||||
MG_Config::Transport = MG_Config::TransportMode::InProcess;
|
||||
// No server backend exists in this process: the format-caps fallback to the client
|
||||
// mirror is refused by name (CONTRACT-P5C §3.7).
|
||||
(void)MG_Backend::DirectGLES::ActiveBackendFormatCaps();
|
||||
});
|
||||
ExpectNamedAbort(child, "Fatal{RoleViolation, \"caps-mirror\"}");
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
namespace fs = std::filesystem;
|
||||
const fs::path path =
|
||||
|
||||
@@ -1629,44 +1629,6 @@ TEST(StagedShadowProductionTest, ATwinSurvivingContextLossDropsItsFreedShadowBas
|
||||
// shows it": B is the corruption, the upload is the draw, the mapped read-back is the picture. Red
|
||||
// once by restoring the MappedData() fallback in liveHostBase(): the store then holds A, the
|
||||
// client's bytes, and the R-2.5 audit could never reach a draw again.
|
||||
TEST(ServerLoopEglTest, FrontendFramebufferDeathDeletesOnTheContextOwner) {
|
||||
MG_Config::Features.PipePush |= MG_Pipe::kMGPipeSubsystemEsprytSlots;
|
||||
EglServerFixture fixture;
|
||||
MGL_EGL_BRING_UP_OR_BAIL(fixture);
|
||||
ASSERT_TRUE(fixture.MakeCurrent());
|
||||
namespace GLES = MG_Backend::DirectGLES;
|
||||
static auto nativeDelete = GLES::g_GLESFuncs.glDeleteFramebuffers;
|
||||
static std::atomic<Uint32> deleted{0};
|
||||
static std::atomic<Bool> wrongThread{false};
|
||||
nativeDelete = GLES::g_GLESFuncs.glDeleteFramebuffers;
|
||||
deleted.store(0);
|
||||
wrongThread.store(false);
|
||||
auto framebuffer = MakeShared<MG_State::GLState::FramebufferObject>(901u);
|
||||
GLuint driverId = 0;
|
||||
ASSERT_EQ(OnApply([&] {
|
||||
auto& twin = GLES::FramebufferImpl::g_backendFramebufferObjects.GetOrCreate(framebuffer);
|
||||
twin = MakeShared<GLES::FramebufferImpl::BackendFramebufferObject>();
|
||||
driverId = twin->GetBackendFramebufferId();
|
||||
twin->Bind(FramebufferTarget::Draw);
|
||||
GLES::g_GLESFuncs.glDeleteFramebuffers = +[](GLsizei count, const GLuint* names) {
|
||||
if (!Server::ServerLoop::OnApplyThread()) wrongThread.store(true);
|
||||
deleted.fetch_add(count);
|
||||
nativeDelete(count, names);
|
||||
};
|
||||
}), MOBILEGL_OK);
|
||||
ASSERT_NE(driverId, 0u);
|
||||
framebuffer.reset(); // Frontend destructor runs on this client thread.
|
||||
EXPECT_EQ(deleted.load(), 1u);
|
||||
EXPECT_FALSE(wrongThread.load());
|
||||
ASSERT_EQ(OnApply([&] {
|
||||
GLES::g_GLESFuncs.glDeleteFramebuffers = nativeDelete;
|
||||
GLint bound = -1;
|
||||
GLES::g_GLESFuncs.glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &bound);
|
||||
EXPECT_EQ(bound, 0) << "native deletion must unbind the framebuffer in the owner context";
|
||||
}), MOBILEGL_OK);
|
||||
fixture.TearDown();
|
||||
}
|
||||
|
||||
TEST(StagedShadowProductionTest, TheEnsurePathUploadsTheServerShadowNotTheClientObjectsBytes) {
|
||||
EglServerFixture fixture;
|
||||
MGL_EGL_BRING_UP_OR_BAIL(fixture);
|
||||
@@ -1820,6 +1782,49 @@ TEST(StagedShadowProductionTest, TheStreamingIdiomThroughAPoolReuseIsUploadedNot
|
||||
"whole-store refusal must key on the descriptor's HasDefinedContent); the log says: "
|
||||
<< appended;
|
||||
}
|
||||
namespace {
|
||||
// The drive below, in a FORKED CHILD that brings the server up itself (the integration
|
||||
// harness's pre-flight shape), so a Fatal on the apply thread ends the child and not the
|
||||
// case. Exit 8 = the child's bring-up failed; exit 0 = the apply-thread twin mint ran,
|
||||
// which is the refusal this case pins having been removed.
|
||||
[[noreturn]] void RunFrontendFramebufferDeathOnApplyThreadAndExit() {
|
||||
EglServerFixture fixture;
|
||||
if (!fixture.BringUp().empty() || !fixture.MakeCurrent()) ::_exit(8);
|
||||
namespace GLES = MG_Backend::DirectGLES;
|
||||
auto framebuffer = MakeShared<MG_State::GLState::FramebufferObject>(901u);
|
||||
(void)OnApply([&] {
|
||||
auto& twin = GLES::FramebufferImpl::g_backendFramebufferObjects.GetOrCreate(framebuffer);
|
||||
twin = MakeShared<GLES::FramebufferImpl::BackendFramebufferObject>();
|
||||
});
|
||||
framebuffer.reset();
|
||||
::_exit(0);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// P5c (hd, CONTRACT-P5C §3.1 / §6 layer 1): this case's original drive - mint the framebuffer
|
||||
// twin on the apply thread off the frontend object's lifetime id, then let the frontend death
|
||||
// notice reach the in-process death switch there - is refused by name at the first step:
|
||||
// the minting GetOrCreate(StatePtr) is monolith glue and the apply-thread call is
|
||||
// Fatal{RoleViolation, "MGPipeSlots"}. The deletion-on-owner semantics it pinned return with
|
||||
// ct's object_death record (the framebuffer family's first wire delete), whose sink releases
|
||||
// the twin BY HANDLE. The name stays; the pin is now the refusal that stands until ct lands.
|
||||
TEST(ServerLoopEglTest, FrontendFramebufferDeathDeletesOnTheContextOwner) {
|
||||
MG_Config::Features.PipePush |= MG_Pipe::kMGPipeSubsystemEsprytSlots;
|
||||
{
|
||||
EglServerFixture probe;
|
||||
MGL_EGL_BRING_UP_OR_BAIL(probe);
|
||||
probe.TearDown();
|
||||
}
|
||||
const SizeT mark = ReadLog().size();
|
||||
EXPECT_EXIT(RunFrontendFramebufferDeathOnApplyThreadAndExit(), ::testing::KilledBySignal(SIGABRT), ".*")
|
||||
<< "the apply-thread mint of a frontend framebuffer's twin was not refused; exit 8 is the "
|
||||
"child's own bring-up failing";
|
||||
const std::string appended = ReadLogFrom(mark);
|
||||
EXPECT_NE(appended.find("Fatal{RoleViolation, \"MGPipeSlots\"}"), std::string::npos)
|
||||
<< "the abort was not the allocator guard's; the log says: " << appended;
|
||||
}
|
||||
|
||||
|
||||
#endif // !_WIN32
|
||||
|
||||
// =====================================================================================
|
||||
|
||||
@@ -58,12 +58,11 @@ def cases():
|
||||
[SUITE+'ReadPixelsPutsTheTightExtentOnTheWire'], 'RemoteClientTest'),
|
||||
('M4-escape-errors-deleted', WIRE, escape_error,
|
||||
[SUITE+'ResourceRespecifyErrorIsNotADecline', SUITE+'MapPersistentErrorIsNotADecline'], 'RemoteClientTest'),
|
||||
('M5-server-role-guards-deleted', FILL,
|
||||
replace('!MG_Remote::Client::RunsAsTheServerRole() && ', '', 2),
|
||||
[SUITE+'ServerRoleRespecifyDoesNotEmitClientInitialBytes'], 'RemoteClientTest'),
|
||||
('M5-server-flush-guard-deleted', FILL,
|
||||
replace('!MG_Remote::Client::RunsAsTheServerRole() && size != 0', 'size != 0'),
|
||||
[SUITE+'ServerRoleFlushDoesNotRunTheClientSubDataFollowup'], 'RemoteClientTest'),
|
||||
('M5-allocator-guards-deleted', 'MobileGL/MG_Impl/Pipe/SlotAllocator.cpp',
|
||||
lambda src: replace('MGPipeRefuseAllocatorFromApplyThread("FindByLifetimeId");', '')(
|
||||
replace('MGPipeRefuseAllocatorFromApplyThread("Acquire");', '')(src)),
|
||||
[SUITE+'ServerRoleRespecifyDoesNotEmitClientInitialBytes',
|
||||
SUITE+'ServerRoleFlushDoesNotRunTheClientSubDataFollowup'], 'RemoteClientTest'),
|
||||
('M8-real-pack-binding', EMIT,
|
||||
replace('if (MG_State::pGLContext != nullptr &&', 'if (false && MG_State::pGLContext != nullptr &&'),
|
||||
[SUITE+'BoundPackBufferOffsetReadRefusesByName'], 'RemoteClientTest'),
|
||||
|
||||
Reference in New Issue
Block a user