mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-18 09:08:31 +09:00
Merge branch 'p5c-hd' into feat/disaggregated (P5c hd: handle-keyed resolution)
Conflict: VulkanRenderer.cpp's disaggregated include block took both halves.
Merge coordination (integrator, with the evidence inline):
- hd's Fatal{RoleViolation, MGPipeSlots} guard as landed was kind-blind: it fired on the
G6 frontend-keyed twin registry (texture/VAO/program/sampler resolver probes - P3b/P4b's
to rekey) and on the families whose handle-carrying records the client does not emit
until P4b (the buffer binding-point ensures, the GPU-written announcement). Integration-
split went 104/107 red at the first Clear. Two NAMED, scoped exemptions keep the guard's
teeth for every other caller (SlotAllocator.{h,cpp}): MGPipeReverseAnnouncementScope
(buffer ensure + announcement family, P4b/P7) and MGPipeFrontendKeyedRegistryScope
(the G6 registry family, P3b/P4b; also the mailbox death switch until ct's object_death
record retires it). ~30 resolver sites wrapped, each naming its debt in the comment.
- ScopedRestartIndexSubstitution needed NO exemption: the EBO resolves from the record's
IndexBuffer.Res and the bytes read from the server staged shadow - CONTRACT-P5B d1's
own sentence, implemented.
- Magma's named blit takes a G6 consume arm (frontend FBOs resolved by lifetime id inside
the scope, VerbBlitNamedConsumed set, loud refusal kept for a failed resolution), so the
two DirectVulkan.Split.NamedBlit correctness cases stay green; Magma's hidden blit and
depth-mipmap resources' teardown probes are wrapped and named (P7: give them non-frontend
storage).
- MG_State gains GLContext::FindFramebufferObjectByLifetimeId (behaviour-neutral accessor
for the arm above).
Gates on the merged tree: unit 2166/2166; integration-split 107/107 (2 design skips);
hd's RemoteGuards death tests still abort the unwrapped paths by name; PrimitiveRestart
7/7; the two Magma named-blit cases render. CONTRACT-P5C sections 3, 5.4.
This commit is contained in:
@@ -362,6 +362,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();
|
||||
@@ -907,12 +943,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
|
||||
@@ -937,6 +989,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);
|
||||
}
|
||||
}
|
||||
@@ -1452,6 +1517,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named debt inside
|
||||
// the scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
if (EsprytSlotTablesEnabled()) {
|
||||
// No memo on this arm. The memo existed to turn the registry's hash probe into
|
||||
@@ -1569,6 +1639,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Bool imageBindableStorageRequired) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): this sync still arrives holding the FRONTEND object
|
||||
// (the object-class rows) and resolves its twin by frontend identity - the
|
||||
// frontend-keyed registry P3b/P4b rekeys onto handles. The probe (and the mint on
|
||||
// a first sync) is named debt inside the scope, not an unwrapped violation.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
auto* backendTextureSlot = g_backendTextureObjects.Find(textureObject.get());
|
||||
auto& backendSlot = backendTextureSlot ? *backendTextureSlot
|
||||
@@ -2418,6 +2495,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
static const MG_Pipe::MGPImageView* ResolveShaderImageRecord(
|
||||
Uint unit, const MG_State::GLState::ITextureObject* boundTexture) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): the image seam's identity check below resolves the
|
||||
// bound texture's handle by frontend identity - frontend-keyed twin resolution,
|
||||
// named debt inside the scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
// P4a decline-site I1: M - the mask says this family is not switched on, and it
|
||||
// IS D's SamplerSubsystemEnabled() now. Silent, confirmed at the verification
|
||||
// round - and it is what keeps I2 below quiet on a mask that never armed images.
|
||||
@@ -2852,6 +2935,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());
|
||||
}
|
||||
|
||||
@@ -3041,6 +3132,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
|
||||
@@ -3109,6 +3210,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
MG_State::GLState::FramebufferObject* lastUpdatedFBO = nullptr;
|
||||
|
||||
for (auto& target : fboTargets) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): the twin resolutions this iteration may run
|
||||
// below are frontend-keyed, named debt inside the scope - P3b/P4b rekeys the
|
||||
// registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
auto& slot = GetFramebufferBindingSlotChecked(target);
|
||||
auto& currentFBO = slot.GetBoundObject();
|
||||
|
||||
@@ -4036,6 +4143,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// Anything else and the frontend's own MapUBO answers, exactly as it does today.
|
||||
static const MG_Pipe::MGPipeShaderCsoRecord* ResolveGlobalConstantsRecord(
|
||||
const MG_State::GLState::ProgramObject* program) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): the program's handle is resolved by frontend
|
||||
// identity below - frontend-keyed twin resolution, named debt inside the scope -
|
||||
// P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
// P4a decline-site P1: M - the mask says this family is not switched on, or there
|
||||
// is no current program at all; it IS D's ProgramSubsystemEnabled() now. Silent,
|
||||
// confirmed at the verification round.
|
||||
@@ -4122,6 +4235,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
void SyncCurrentProgram(const SharedPtr<MG_State::GLState::ProgramObject>& currentProgram) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): the twin resolution below still keys on the frontend
|
||||
// program object - named debt inside the scope - P3b/P4b rekeys the registry onto
|
||||
// handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
g_backendProgramObjects.CollectGarbageIfNeeded();
|
||||
SamplerImpl::g_backendSamplerObjects.CollectGarbageIfNeeded();
|
||||
@@ -4374,6 +4493,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// and the twin memo replaces even that with an array probe on the steady path.
|
||||
const auto& currentFBO = slot.GetBoundObject();
|
||||
if (currentFBO && currentFBO != MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named debt inside
|
||||
// the scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
FramebufferImpl::BackendFramebufferObject* twin = nullptr;
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
if (EsprytSlotTablesEnabled()) {
|
||||
@@ -4446,12 +4570,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;
|
||||
@@ -4642,6 +4834,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
// Bind texture object
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named debt
|
||||
// inside the scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get());
|
||||
if (!backendTextureSlot || !*backendTextureSlot) {
|
||||
fullyResolved = false;
|
||||
@@ -4704,6 +4901,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
static SamplerImpl::BackendSamplerObject* ResolveUnitSamplerBackend(
|
||||
Int unit, const SharedPtr<MG_State::GLState::SamplerObject>& samplerObject) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named debt inside the
|
||||
// scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
auto& memo = g_unitSamplerLookupMemos[static_cast<SizeT>(unit)];
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
if (EsprytSlotTablesEnabled()) {
|
||||
@@ -5032,6 +5234,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (currentProgram && currentProgram->GetLinkStatus() && currentProgram->GetSpirvStatus()) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedNC("BindCurrentProgram", TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): the program/sampler twin resolutions below still key
|
||||
// on frontend objects - named debt inside the scope - P3b/P4b rekeys the registry
|
||||
// onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
// The twin SyncCurrentProgram just resolved for this draw; the registry
|
||||
// Find only runs if the stash somehow does not match (defensive fallback).
|
||||
@@ -5389,6 +5597,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (PrgramImpl::g_currentDrawFrontendProgram == currentProgram.get()) {
|
||||
return PrgramImpl::g_currentDrawBackendProgram;
|
||||
}
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named debt inside the
|
||||
// scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
if (auto* backendProgramSlot = PrgramImpl::g_backendProgramObjects.Find(currentProgram.get())) {
|
||||
return backendProgramSlot->get();
|
||||
}
|
||||
@@ -5656,14 +5869,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);
|
||||
@@ -5728,12 +5963,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);
|
||||
@@ -5819,6 +6074,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named debt inside the
|
||||
// scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
auto* backendProgramSlot = PrgramImpl::g_backendProgramObjects.Find(programObject.get());
|
||||
auto& backendObj =
|
||||
backendProgramSlot ? *backendProgramSlot : PrgramImpl::g_backendProgramObjects.GetOrCreate(programObject);
|
||||
@@ -6163,13 +6423,88 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
const SizeT sourceIndexSize = MG_Util::GetGLTypeSize(indexType);
|
||||
const Uint32 fixedMax = MG_Util::FixedRestartIndexForGLType(indexType);
|
||||
const Uint32 applicationRestartIndex = MGB_CTX->GetPrimitiveRestartIndex();
|
||||
const auto& indexBuffer = BoundElementArrayBuffer();
|
||||
|
||||
const Uint8* source = nullptr;
|
||||
SizeT indexCount = 0;
|
||||
SizeT sourceByteOffset = 0;
|
||||
Bool hasElementBuffer = false;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// -1: monolith, or this arm never resolved a backend id, and the tail asks
|
||||
// BoundElementArrayBufferId() exactly as it always did.
|
||||
Int serverElementBinding = -1;
|
||||
#endif
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.5): with an active transport this constructor runs on the
|
||||
// apply thread, where the frontend BufferObject's legacy accessors are Fatal by name
|
||||
// (BufferObject.cpp's "buffer-legacy-arm" refusal). The element buffer therefore
|
||||
// resolves from the applier's set_index_buffer state (T2) and the bytes read from the
|
||||
// server's own staged shadow - "the backend's ScopedRestartIndexSubstitution reads the
|
||||
// index bytes it needs on its side" (CONTRACT-P5B d1), the same arm
|
||||
// MultiDrawElementsIndirectCount takes for its command buffer. A draw whose record
|
||||
// carried client indices (no index buffer bound) takes the client-pointer arm below,
|
||||
// `indices` already resolved through the server's segment resolver. Monolith takes
|
||||
// the original body, verbatim.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
const auto& applierState = MG_Pipe::MGPipeApplier();
|
||||
const MG_Pipe::MGPipeHandle elements = applierState.IndexBuffer.Res;
|
||||
serverElementBinding = 0;
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(elements)) {
|
||||
auto* elementResource = BufferImpl::FindBufferResourceForHandle(elements);
|
||||
// The WHOLE buffer is rewritten, not just this draw's range, so that every
|
||||
// index keeps its position: an indirect draw's firstIndex lives in GPU memory
|
||||
// and cannot be adjusted from here. It is an ELEMENT index, so it survives
|
||||
// widening unchanged.
|
||||
const SizeT sizeBytes = BufferImpl::ResourceWidthForHandle(elements);
|
||||
if (sizeBytes < sourceIndexSize) {
|
||||
return; // Nothing to restart on; let the driver see the draw unchanged.
|
||||
}
|
||||
if (sizeBytes > kMaxRestartRewriteBytes) {
|
||||
MGLOG_E_ONCE("Draw skipped: GL_PRIMITIVE_RESTART with restart index %u needs the %zu-byte element "
|
||||
"array buffer rewritten every draw, which is past the %zu-byte ceiling. Use "
|
||||
"GL_PRIMITIVE_RESTART_FIXED_INDEX, or set glPrimitiveRestartIndex to the all-ones "
|
||||
"value of the index type.",
|
||||
applicationRestartIndex, sizeBytes, kMaxRestartRewriteBytes);
|
||||
m_valid = false;
|
||||
return;
|
||||
}
|
||||
// The staged shadow is the source of truth for CPU reads on this side: a
|
||||
// persistent map's blocks and a shader write's writeback were consumed by the
|
||||
// applier before this verb ran.
|
||||
const Uint8* hostBytes = elementResource ? elementResource->hostBytes : nullptr;
|
||||
if (hostBytes == nullptr) {
|
||||
MGLOG_E_ONCE("Draw skipped: GL_PRIMITIVE_RESTART with restart index %u needs a CPU-readable copy of "
|
||||
"the bound element array buffer and none is available.",
|
||||
applicationRestartIndex);
|
||||
m_valid = false;
|
||||
return;
|
||||
}
|
||||
// The M-3 rule for a whole-store reader (Managers.cpp:3238): for a store whose
|
||||
// content the application SUPPLIED a coverage gap is a missing record and is
|
||||
// Fatal by name; for one it ORPHANED the gap is its own undefined content and
|
||||
// reading the shadow's zero-fill is exactly what the monolith arm's
|
||||
// MappedData() answers.
|
||||
const MG_Pipe::MGPipeResourceRecord* elementRecord = nullptr;
|
||||
if (elements.Slot < applierState.Resources.size()) {
|
||||
const auto& candidate = applierState.Resources[elements.Slot];
|
||||
if (candidate.Live && candidate.Gen == elements.Gen) elementRecord = &candidate;
|
||||
}
|
||||
if (elementRecord != nullptr && elementRecord->Desc.HasDefinedContent != 0) {
|
||||
BufferImpl::RequireStagedCoverage(*elementResource, hostBytes, 0, sizeBytes,
|
||||
"primitive_restart_substitution");
|
||||
}
|
||||
hasElementBuffer = true;
|
||||
serverElementBinding = static_cast<Int>(elementResource->id);
|
||||
source = hostBytes;
|
||||
indexCount = sizeBytes / sourceIndexSize;
|
||||
sourceByteOffset = reinterpret_cast<SizeT>(indices);
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
const auto& indexBuffer = BoundElementArrayBuffer();
|
||||
if (indexBuffer) {
|
||||
hasElementBuffer = true;
|
||||
// The WHOLE buffer is rewritten, not just this draw's range, so that every index
|
||||
// keeps its position: an indirect draw's firstIndex lives in GPU memory and cannot be
|
||||
// adjusted from here. It is an ELEMENT index, so it survives widening unchanged.
|
||||
@@ -6200,7 +6535,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
indexCount = sizeBytes / sourceIndexSize;
|
||||
sourceByteOffset = reinterpret_cast<SizeT>(indices);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
if (!hasElementBuffer) {
|
||||
// No element array buffer: `indices` is a client pointer, so only the draw's own
|
||||
// range is readable and an indirect draw has nothing to read at all.
|
||||
if (count <= 0 || indices == nullptr || sourceIndexSize == 0) {
|
||||
@@ -6254,14 +6591,22 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
m_valid = false;
|
||||
return;
|
||||
}
|
||||
m_previousBinding = 0;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (serverElementBinding >= 0) {
|
||||
m_previousBinding = static_cast<Uint>(serverElementBinding);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
m_previousBinding = BoundElementArrayBufferId();
|
||||
}
|
||||
BufferImpl::BindBufferId(GL_ELEMENT_ARRAY_BUFFER, g_restartIndices.id);
|
||||
m_substituted = true;
|
||||
m_indexType = destinationType;
|
||||
// The rewritten copy starts at byte 0 of the scratch buffer and holds one
|
||||
// destination-width element per source element, so an EBO-sourced draw keeps its ELEMENT
|
||||
// offset (rescaled to the new width) and a client-memory draw reads from the front.
|
||||
m_indices = indexBuffer
|
||||
m_indices = hasElementBuffer
|
||||
? reinterpret_cast<const void*>((sourceByteOffset / sourceIndexSize) * destinationIndexSize)
|
||||
: nullptr;
|
||||
}
|
||||
@@ -6292,6 +6637,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
PrepareForDraw(syncBit);
|
||||
const auto& currentVAO = MGB_CTX->GetBoundVertexArray();
|
||||
if (currentVAO) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named debt inside
|
||||
// the scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
auto* backendVAOSlot = VertexArrayImpl::g_backendVertexArrayObjects.Find(currentVAO.get());
|
||||
if (backendVAOSlot && *backendVAOSlot) {
|
||||
(*backendVAOSlot)->SyncClientSideAttributesForDrawArrays(currentVAO, first, count);
|
||||
@@ -6333,6 +6683,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
// Client-side arrays are uploaded per sub-draw range, like the single DrawArrays path.
|
||||
if (currentVAO) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named debt
|
||||
// inside the scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
auto* backendVAOSlot = VertexArrayImpl::g_backendVertexArrayObjects.Find(currentVAO.get());
|
||||
if (backendVAOSlot && *backendVAOSlot) {
|
||||
(*backendVAOSlot)->SyncClientSideAttributesForDrawArrays(currentVAO, first[i], count[i]);
|
||||
@@ -6400,6 +6755,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");
|
||||
@@ -6431,6 +6794,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) {
|
||||
@@ -6502,6 +6912,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");
|
||||
@@ -6533,6 +6951,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) {
|
||||
@@ -6706,6 +7166,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),
|
||||
@@ -6747,6 +7215,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");
|
||||
@@ -7763,6 +8239,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());
|
||||
@@ -7859,6 +8392,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();
|
||||
@@ -8089,8 +8645,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");
|
||||
@@ -8188,6 +8745,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return;
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named debt inside
|
||||
// the scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(texture.get());
|
||||
if (!backendTextureSlot || !*backendTextureSlot) {
|
||||
return;
|
||||
@@ -8508,6 +9070,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// The record resolution is the same registry lookup texture sync and
|
||||
// EnsureGenerateMipmapStorageAllocated's disaggregated arm already make.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named debt inside
|
||||
// the scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
const auto pushedHandle = TextureImpl::g_backendTextureObjects.HandleOf(texture.get());
|
||||
const auto* pushedRecord = PipeTextureRecordForHandle(pushedHandle);
|
||||
if (pushedRecord == nullptr || pushedRecord->Desc.Width == 0 || pushedRecord->Desc.Levels == 0) {
|
||||
@@ -8560,6 +9125,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// same §1 extent derivation, same registry resolution, same refusal when the handle
|
||||
// arm has no record to read.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named debt inside
|
||||
// the scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
const auto pushedHandle = TextureImpl::g_backendTextureObjects.HandleOf(texture.get());
|
||||
const auto* pushedRecord = PipeTextureRecordForHandle(pushedHandle);
|
||||
if (pushedRecord == nullptr || pushedRecord->Desc.Width == 0 || pushedRecord->Desc.Levels == 0) {
|
||||
@@ -8623,6 +9191,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();
|
||||
@@ -8632,9 +9222,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,
|
||||
@@ -8668,7 +9260,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());
|
||||
});
|
||||
@@ -8718,6 +9310,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();
|
||||
@@ -8727,7 +9336,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());
|
||||
@@ -8749,7 +9360,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());
|
||||
});
|
||||
@@ -8992,6 +9603,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
static SharedPtr<RenderbufferImpl::BackendRenderbufferObject> SyncRenderbufferObjectToBackend(
|
||||
const SharedPtr<MG_State::GLState::RenderbufferObject>& renderbufferObject) {
|
||||
if (!renderbufferObject) return nullptr;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution (and the mint on a first
|
||||
// sync), named debt inside the scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
SharedPtr<RenderbufferImpl::BackendRenderbufferObject> backendRenderbufferObject;
|
||||
if (auto* slot = RenderbufferImpl::g_backendRenderbufferObjects.Find(renderbufferObject.get())) {
|
||||
backendRenderbufferObject = *slot;
|
||||
@@ -9321,6 +9937,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
auto& programObject = MGB_CTX->GetProgramObject(program);
|
||||
if (!programObject) return;
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named debt inside the
|
||||
// scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
auto* backendProgramSlot = PrgramImpl::g_backendProgramObjects.Find(programObject.get());
|
||||
if (!backendProgramSlot || !*backendProgramSlot) return;
|
||||
auto& backendObj = *backendProgramSlot;
|
||||
@@ -11171,6 +11792,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
MGLOG_D("GetTexImage: bound texture object = %p (name=%u)", textureObject.get(),
|
||||
textureObject ? textureObject->GetExternalIndex() : 0);
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named debt inside the
|
||||
// scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get());
|
||||
|
||||
if (!backendTextureSlot || !*backendTextureSlot) {
|
||||
|
||||
@@ -220,6 +220,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}, &death);
|
||||
return;
|
||||
}
|
||||
// P5c merge coordination: while the death notice still rides the mailbox it lands
|
||||
// HERE, on the apply thread, and every arm's DestroyByLifetimeId probes the client
|
||||
// allocator. That hop is exactly what ct's object_death record replaces (the sink
|
||||
// then resolves by the record's handle and the scope dies with the mailbox); until
|
||||
// ct lands the probes are named debt inside the G6 scope, not unwrapped violations.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
switch (kind) {
|
||||
case MG_Pipe::MGPipeKind::Texture:
|
||||
@@ -2841,6 +2847,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
|
||||
@@ -2851,6 +2864,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// allocator probe on a miss - and remembers the answer, which the minting overload
|
||||
// was previously the only writer of. A null answer is deliberately never memoised
|
||||
// (see the comment at HandleOf).
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c merge coordination (CONTRACT-P5C §3.1's named exemption): every caller of
|
||||
// this function is a site whose handle-carrying records (set_shader_buffers /
|
||||
// set_stream_output_targets) the client does not emit until P4b, so the probe
|
||||
// below runs inside the scoped exemption. The scope, not a silent guard removal,
|
||||
// is what keeps the debt named and greppable.
|
||||
const MG_Pipe::MGPipeReverseAnnouncementScope reverseAnnouncement;
|
||||
#endif
|
||||
return g_backendBufferResources.HandleOf(bufferObject);
|
||||
}
|
||||
|
||||
@@ -3245,7 +3266,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;
|
||||
@@ -3273,24 +3304,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
|
||||
@@ -4438,6 +4473,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// MGPipeSlots().FindByLifetimeId, which is a hash lookup. HandleOf answers
|
||||
// identically - the same allocator probe on a miss - and remembers the answer; a
|
||||
// null answer is deliberately never memoised (SlotTables.h, at HandleOf).
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named debt inside
|
||||
// the scope - P3b/P4b rekeys the sampler-view registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
return g_backendSamplerViews.HandleOf(textureObject);
|
||||
}
|
||||
} // namespace SamplerViewImpl
|
||||
@@ -5769,7 +5809,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// because that is what this arm consumes.
|
||||
Bool markedRemintPull = false;
|
||||
const MG_Pipe::MGPipeHandle rearmRes =
|
||||
TextureResourceSubsystemEnabled() ? g_backendTextureObjects.HandleOf(stateTextureObject.get())
|
||||
TextureResourceSubsystemEnabled()
|
||||
? [&]() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed resolution, named
|
||||
// debt inside the scope - P3b/P4b rekeys the registry.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
return g_backendTextureObjects.HandleOf(stateTextureObject.get());
|
||||
}()
|
||||
: MG_Pipe::kMGPipeNullHandle;
|
||||
const Uint32 rearmResourceTarget =
|
||||
MG_Pipe::MGPipeResourceTargetForTextureTarget(stateTextureObject->GetTarget());
|
||||
@@ -6932,6 +6980,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
const MG_Pipe::MGPipeResourceRecord* pushedStorage = nullptr;
|
||||
MG_Pipe::MGPipeHandle pushedRes = MG_Pipe::kMGPipeNullHandle;
|
||||
if (TextureResourceSubsystemEnabled()) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): the sync arrives holding the frontend object
|
||||
// (the object-class rows) and resolves its handle by frontend identity -
|
||||
// named debt inside the scope, P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
pushedRes = g_backendTextureObjects.HandleOf(stateTextureObject.get());
|
||||
pushedStorage = PipeTextureRecordForHandle(pushedRes);
|
||||
if (pushedStorage == nullptr) {
|
||||
@@ -8279,6 +8333,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// object this backend still arrives holding. Under a real split the handle rides in
|
||||
// the payload; the client mints it off this object's lifetime id, so the allocator
|
||||
// resolves it here through the table's own single-entry front memo.
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed resolution, named debt inside the
|
||||
// scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
const MG_Pipe::MGPipeHandle res = g_backendTextureObjects.HandleOf(stateTextureObject.get());
|
||||
const auto* record = PipeTextureRecordForHandle(res);
|
||||
if (record == nullptr) {
|
||||
@@ -8510,6 +8569,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
const MG_Pipe::MGPipeResourceRecord* BackendTextureObject::ResolvePushedTextureParams(
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed resolution, named debt inside the
|
||||
// scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
const MG_Pipe::MGPipeHandle res = g_backendTextureObjects.HandleOf(stateTextureObject.get());
|
||||
const auto* record = PipeTextureRecordForHandle(res);
|
||||
if (record == nullptr) {
|
||||
@@ -9044,6 +9108,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (attachmentObject.IsTexture()) {
|
||||
const auto& textureObject = attachmentObject.GetTexture();
|
||||
SharedPtr<TextureImpl::BackendTextureObject> backendTextureObject;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution (and the mint on
|
||||
// a first attach), named debt inside the scope - P3b/P4b rekeys the registry.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
if (auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get())) {
|
||||
backendTextureObject = *backendTextureSlot;
|
||||
} else {
|
||||
@@ -9093,6 +9162,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
} else if (attachmentObject.IsRenderbuffer()) {
|
||||
const auto& renderbufferObject = attachmentObject.GetRenderbuffer();
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution (and the mint on
|
||||
// a first attach), named debt inside the scope - P3b/P4b rekeys the registry.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
SharedPtr<RenderbufferImpl::BackendRenderbufferObject> backendRenderbufferObject;
|
||||
if (auto* backendRenderbufferSlot =
|
||||
RenderbufferImpl::g_backendRenderbufferObjects.Find(renderbufferObject.get())) {
|
||||
@@ -9405,7 +9479,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",
|
||||
@@ -9477,6 +9560,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
surface.Res.Slot, surface.Res.Gen);
|
||||
return false;
|
||||
}
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): the corroborating cross-check below resolves the
|
||||
// frontend renderbuffer's handle by frontend identity - named debt inside the
|
||||
// scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
if (!(RenderbufferImpl::g_backendRenderbufferObjects.HandleOf(renderbufferObject.get()) ==
|
||||
surface.Res)) {
|
||||
MGLOG_E_ONCE("MGPipe: attachment record names renderbuffer {%u, %u} but the frontend "
|
||||
@@ -9620,7 +9709,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
|
||||
@@ -9848,7 +9945,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 "
|
||||
@@ -10126,6 +10234,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// Verify that the backend object's name and parameters match the frontend attachment state
|
||||
if (attachmentObject.IsTexture()) {
|
||||
const auto& textureObject = attachmentObject.GetTexture();
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named
|
||||
// debt inside the scope - P3b/P4b rekeys the registry.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get());
|
||||
MOBILEGL_ASSERT(backendTextureSlot != nullptr && *backendTextureSlot != nullptr,
|
||||
"No backend texture found while framebuffer reports texture attachment.");
|
||||
@@ -10142,6 +10255,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
"Attachment texture level mismatch between GLES and state object.");
|
||||
} else if (attachmentObject.IsRenderbuffer()) {
|
||||
const auto& renderbufferObject = attachmentObject.GetRenderbuffer();
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named
|
||||
// debt inside the scope - P3b/P4b rekeys the registry.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
auto* backendRboSlot =
|
||||
RenderbufferImpl::g_backendRenderbufferObjects.Find(renderbufferObject.get());
|
||||
MOBILEGL_ASSERT(
|
||||
@@ -12682,6 +12800,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// stateProgramObject. The verify build is where the codec is exercised, by
|
||||
// serialising, deserialising and field-comparing before storing.
|
||||
if (ProgramSubsystemEnabled()) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): the program's ShaderCso handle is resolved by
|
||||
// frontend identity below - frontend-keyed twin resolution, named debt inside
|
||||
// the scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
// MONOLITH GLUE: the ShaderCso handle of a program this backend still arrives
|
||||
// holding. The reader hides the composite band, so a program-pipeline composite
|
||||
// - which the server must never learn is one - resolves through the same call.
|
||||
@@ -13000,6 +13124,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// the values are taken from the object, which in monolith are the very
|
||||
// values the client content-addressed, so the picture stays right while
|
||||
// the gap is visible rather than silent.
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): the registration probe below resolves the
|
||||
// sampler's handle by frontend identity - frontend-keyed twin resolution,
|
||||
// named debt inside the scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(
|
||||
g_backendSamplerObjects.HandleOf(stateSamplerObject.get()))) {
|
||||
MGLOG_E_ONCE("MGPipe: sampler %u was synced without its unit's SamplerCso handle, so "
|
||||
@@ -13291,6 +13421,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
const MG_Pipe::MGPipeResourceRecord* pushedRecord = nullptr;
|
||||
if (TextureResourceSubsystemEnabled()) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): the renderbuffer's handle is resolved by
|
||||
// frontend identity below - frontend-keyed twin resolution, named debt inside
|
||||
// the scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
// MONOLITH GLUE, named as such: the Renderbuffer handle of an object this
|
||||
// backend still arrives holding. Under a real split it rides in the payload.
|
||||
const MG_Pipe::MGPipeHandle res = g_backendRenderbufferObjects.HandleOf(stateRBOObject.get());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,9 @@
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (T5 / tx): the server's staged-texture shadow GenerateMipmap defines its chain on.
|
||||
#include <MG_Remote/Server/StagedTextureStore.h>
|
||||
#include <MG_Remote/Server/ServerLoop.h>
|
||||
// P5c (G6): the named-blit arm's endpoint resolution runs inside the frontend-keyed scope.
|
||||
#include <MG_Impl/Pipe/SlotAllocator.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
@@ -672,6 +675,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;
|
||||
@@ -4522,6 +4542,17 @@ void main() {
|
||||
}
|
||||
|
||||
void VulkanRenderer::ShutdownBlitResources() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §3.1's named exemption): the hidden blit program and samplers
|
||||
// are FRONTEND objects the server backend created on the apply thread, and under an
|
||||
// active transport they die here on that same thread. Their destructors run the client
|
||||
// death helper, whose lifetime-id probe is the frontend-keyed registry family - it
|
||||
// resolves to nothing (no handle was ever minted for these objects) and routes no
|
||||
// delete, so the scope admits the probe as named debt rather than letting the guard
|
||||
// Fatal at server teardown. P7 gives these resources storage that is not a frontend
|
||||
// object.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
m_blitResources = {};
|
||||
}
|
||||
|
||||
@@ -4596,6 +4627,12 @@ void main() {
|
||||
}
|
||||
|
||||
void VulkanRenderer::ShutdownDepthMipmapResources() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// Same shape as ShutdownBlitResources above: the hidden depth-mipmap program is a
|
||||
// frontend object created and destroyed by the server backend on the apply thread, and
|
||||
// its destructor's lifetime-id probe is admitted here as named debt.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
m_depthMipmapResources = {};
|
||||
}
|
||||
|
||||
@@ -8928,6 +8965,53 @@ void main() {
|
||||
void VulkanRenderer::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||
GLbitfield mask, GLenum filter) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §3.3/§5.4): MAGMA'S NAMED ARM. A blit record whose
|
||||
// ReadFbo/DrawFbo are non-null is a glBlitNamedFramebuffer: both framebuffers resolve
|
||||
// from the record's handles, and the sink is told the pair was consumed - a backend
|
||||
// that leaves the flag clear has no named arm and the verb declines there, loudly.
|
||||
// Magma has no FBO twin registry (it reads the frontend object wherever it syncs), so
|
||||
// the resolution here is frontend-keyed - the handle was minted over the frontend
|
||||
// object's lifetime id, and the two probes below (the client allocator's slot entry
|
||||
// and the frontend context's framebuffer pool) are named debt inside the scope, the
|
||||
// same shape as Espryt's StateForHandle arm: P3b/P4b retire it by carrying the object
|
||||
// identity in the record.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
auto& applierState = MG_Pipe::MGPipeApplier();
|
||||
const MG_Pipe::MGPipeHandle readHandle = applierState.VerbBlitReadFbo;
|
||||
const MG_Pipe::MGPipeHandle drawHandle = applierState.VerbBlitDrawFbo;
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(readHandle) || !MG_Pipe::MGPipeHandleIsNull(drawHandle)) {
|
||||
const auto resolveEndpoint = [](MG_Pipe::MGPipeHandle handle)
|
||||
-> SharedPtr<MG_State::GLState::FramebufferObject> {
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
if (handle == MG_Pipe::kMGPipeDefaultFramebuffer) {
|
||||
return MG_State::pGLContext ? MG_State::pGLContext->GetFramebufferObject(0) : nullptr;
|
||||
}
|
||||
if (!MG_Pipe::MGPipeSlots().IsLive(MG_Pipe::MGPipeKind::Framebuffer, handle)) {
|
||||
return nullptr;
|
||||
}
|
||||
const Uint64 lifetimeId =
|
||||
MG_Pipe::MGPipeSlots().LifetimeIdOfSlot(MG_Pipe::MGPipeKind::Framebuffer, handle.Slot);
|
||||
if (lifetimeId == 0 || MG_State::pGLContext == nullptr) return nullptr;
|
||||
return MG_State::pGLContext->FindFramebufferObjectByLifetimeId(lifetimeId);
|
||||
};
|
||||
auto readFbo = resolveEndpoint(readHandle);
|
||||
auto drawFbo = resolveEndpoint(drawHandle);
|
||||
if (!readFbo || !drawFbo) {
|
||||
// Leave the pair UNCONSUMED: the sink's decline is the loud answer a
|
||||
// missing endpoint deserves, not a silent blit of whatever is bound.
|
||||
MGLOG_E_ONCE("MGPipe: Magma's named blit could not resolve an endpoint (read {%u, %u}, draw "
|
||||
"{%u, %u}) to a frontend framebuffer; the verb declines at the sink",
|
||||
readHandle.Slot, readHandle.Gen, drawHandle.Slot, drawHandle.Gen);
|
||||
return;
|
||||
}
|
||||
applierState.VerbBlitNamedConsumed = true;
|
||||
BlitNamedFramebuffer(readFbo, drawFbo, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0,
|
||||
dstX1, dstY1, mask, filter);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
auto readFbo = MGB_CTX->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject();
|
||||
auto drawFbo = MGB_CTX->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
|
||||
BlitNamedFramebuffer(readFbo, drawFbo, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
|
||||
|
||||
@@ -714,8 +714,16 @@ namespace MobileGL::MG_Pipe {
|
||||
bufferObject->MarkGpuWritten();
|
||||
return;
|
||||
}
|
||||
const MGPipeHandle res =
|
||||
MGPipeSlots().FindByLifetimeId(MGPipeKind::Buffer, bufferObject->GetLifetimeId());
|
||||
const MGPipeHandle res = [&]() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// CONTRACT-P5C §3.1's named exemption, Magma's half: the binding records that
|
||||
// would carry this buffer's handle are P4b's to emit and Magma's server-side
|
||||
// binding table is P7's, so until then the probe runs inside the scope - the
|
||||
// debt's named, greppable form rather than a silent guard removal.
|
||||
const MGPipeReverseAnnouncementScope reverseAnnouncement;
|
||||
#endif
|
||||
return MGPipeSlots().FindByLifetimeId(MGPipeKind::Buffer, bufferObject->GetLifetimeId());
|
||||
}();
|
||||
if (MGPipeHandleIsNull(res)) {
|
||||
MGLOG_E_ONCE("MGPipe: no handle for the GPU-write announcement of buffer %u - the "
|
||||
"reverse channel is installed but the mint is missing, so the mark "
|
||||
|
||||
@@ -9,7 +9,67 @@
|
||||
// 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;
|
||||
// The named exemption of CONTRACT-P5C §3.1: the sites whose handle-carrying records
|
||||
// are not emitted yet (P4b/P7) probe read-only inside the scope. The G6
|
||||
// frontend-keyed registry family (P3b/P4b) does the same inside its own scope.
|
||||
if (MGPipeReverseAnnouncementScope::Active()) return;
|
||||
if (MGPipeFrontendKeyedRegistryScope::Active()) 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();
|
||||
}
|
||||
|
||||
namespace {
|
||||
thread_local Uint32 g_reverseAnnouncementScopeDepth = 0;
|
||||
}
|
||||
|
||||
MGPipeReverseAnnouncementScope::MGPipeReverseAnnouncementScope() {
|
||||
++g_reverseAnnouncementScopeDepth;
|
||||
}
|
||||
|
||||
MGPipeReverseAnnouncementScope::~MGPipeReverseAnnouncementScope() {
|
||||
--g_reverseAnnouncementScopeDepth;
|
||||
}
|
||||
|
||||
Bool MGPipeReverseAnnouncementScope::Active() {
|
||||
return g_reverseAnnouncementScopeDepth != 0;
|
||||
}
|
||||
|
||||
namespace {
|
||||
thread_local Uint32 g_frontendKeyedRegistryScopeDepth = 0;
|
||||
}
|
||||
|
||||
MGPipeFrontendKeyedRegistryScope::MGPipeFrontendKeyedRegistryScope() {
|
||||
++g_frontendKeyedRegistryScopeDepth;
|
||||
}
|
||||
|
||||
MGPipeFrontendKeyedRegistryScope::~MGPipeFrontendKeyedRegistryScope() {
|
||||
--g_frontendKeyedRegistryScopeDepth;
|
||||
}
|
||||
|
||||
Bool MGPipeFrontendKeyedRegistryScope::Active() {
|
||||
return g_frontendKeyedRegistryScopeDepth != 0;
|
||||
}
|
||||
#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 +226,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 +239,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,47 @@ 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);
|
||||
|
||||
// The NAMED EXEMPTION to the rule above (CONTRACT-P5C §3.1): the family of sites whose
|
||||
// handle-carrying records the client does not EMIT yet. set_shader_buffers and
|
||||
// set_stream_output_targets exist in the catalogue but are P4b's to emit
|
||||
// (SetHashSuppressor.h says so), so the buffer binding-point ensures and the GPU-written
|
||||
// announcement they feed have no record handle to resolve from today. Inside this scope
|
||||
// ONE read-only lifetime-id probe stays legal; the scope is the debt's measurable,
|
||||
// greppable form, and it retires with P4b's emission (Espryt) and P7's server-side
|
||||
// binding table (Magma). Every other apply-thread allocator access stays Fatal.
|
||||
class MGPipeReverseAnnouncementScope {
|
||||
public:
|
||||
MGPipeReverseAnnouncementScope();
|
||||
~MGPipeReverseAnnouncementScope();
|
||||
MGPipeReverseAnnouncementScope(const MGPipeReverseAnnouncementScope&) = delete;
|
||||
MGPipeReverseAnnouncementScope& operator=(const MGPipeReverseAnnouncementScope&) = delete;
|
||||
static Bool Active();
|
||||
};
|
||||
|
||||
// The SECOND named exemption family (CONTRACT-P5C §5.4): the frontend-keyed twin
|
||||
// registry (audit row G6). The texture / sampler-view / FBO-legacy HandleOf probes are
|
||||
// how the server answers "which twin is this frontend object" while the registry is
|
||||
// keyed by frontend identity - server-PRIVATE state whose rekey onto handles is
|
||||
// P3b/P4b's, not P5c's. A probe inside this scope stays a read-only, barrier-held debt;
|
||||
// wrapping a NEW site in it is the greppable act of naming that debt, and an unwrapped
|
||||
// probe from the apply thread is still Fatal{RoleViolation, "MGPipeSlots"}.
|
||||
class MGPipeFrontendKeyedRegistryScope {
|
||||
public:
|
||||
MGPipeFrontendKeyedRegistryScope();
|
||||
~MGPipeFrontendKeyedRegistryScope();
|
||||
MGPipeFrontendKeyedRegistryScope(const MGPipeFrontendKeyedRegistryScope&) = delete;
|
||||
MGPipeFrontendKeyedRegistryScope& operator=(const MGPipeFrontendKeyedRegistryScope&) = delete;
|
||||
static Bool Active();
|
||||
};
|
||||
#endif
|
||||
} // namespace MobileGL::MG_Pipe
|
||||
|
||||
@@ -693,6 +693,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1212,6 +1212,12 @@ namespace MobileGL::MG_State {
|
||||
return m_framebufferState.ValidateFramebufferObject(index);
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
SharedPtr<FramebufferObject> GLContext::FindFramebufferObjectByLifetimeId(Uint64 lifetimeId) const {
|
||||
return m_framebufferState.FindFramebufferObjectByLifetimeId(lifetimeId);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Sampler
|
||||
void GLContext::GenSamplerNames(Uint number, Vector<Uint>& samplers) {
|
||||
m_samplerState.GenerateNames(number, samplers);
|
||||
|
||||
@@ -544,6 +544,11 @@ namespace MobileGL {
|
||||
// Framebuffer
|
||||
void GenFramebufferNames(Uint number, Vector<Uint>& framebuffers);
|
||||
const SharedPtr<FramebufferObject>& GetFramebufferObject(Uint index);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// See FramebufferState::FindFramebufferObjectByLifetimeId - the named-blit
|
||||
// consumer's handle-to-frontend resolution on a backend with no FBO twins.
|
||||
SharedPtr<FramebufferObject> FindFramebufferObjectByLifetimeId(Uint64 lifetimeId) const;
|
||||
#endif
|
||||
BindingSlot<FramebufferObject>& GetFramebufferBindingSlot(FramebufferTarget target);
|
||||
const SharedPtr<FramebufferObject>& CreateFramebufferObject(Uint index);
|
||||
void MarkFramebufferObjectForDeletion(Uint index);
|
||||
|
||||
@@ -78,4 +78,15 @@ namespace MobileGL::MG_State::GLState {
|
||||
Bool FramebufferState::ValidateFramebufferObject(Uint index) const {
|
||||
return m_framebufferObjects.find(index) != m_framebufferObjects.end();
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
SharedPtr<FramebufferObject> FramebufferState::FindFramebufferObjectByLifetimeId(Uint64 lifetimeId) const {
|
||||
for (const auto& [index, framebufferObject] : m_framebufferObjects) {
|
||||
if (framebufferObject && framebufferObject->GetLifetimeId() == lifetimeId) {
|
||||
return framebufferObject;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
} // namespace MobileGL::MG_State::GLState
|
||||
|
||||
@@ -25,6 +25,16 @@ namespace MobileGL::MG_State::GLState {
|
||||
Bool ValidateName(Uint index) const;
|
||||
Bool ValidateFramebufferObject(Uint index) const;
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §3.3/§5.4): the reverse of HandleFor() for a server that holds
|
||||
// only the handle - the named-blit consumer on a backend with no FBO twin registry
|
||||
// (Magma) resolves the verb's ReadFbo/DrawFbo to the frontend object by the lifetime
|
||||
// id the handle was minted over. Named debt, reached inside
|
||||
// MGPipeFrontendKeyedRegistryScope; P3b/P4b retire it by carrying the object identity
|
||||
// in the record. nullptr when no live framebuffer owns the id.
|
||||
SharedPtr<FramebufferObject> FindFramebufferObjectByLifetimeId(Uint64 lifetimeId) const;
|
||||
#endif
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P2 brief D4: "did the attachment set or the default geometry of ANY framebuffer
|
||||
// move". It does NOT cover a BIND - a bind writes a BindingSlot, not the object - so
|
||||
|
||||
@@ -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