[Feat] (P13 W4b/W4c): Espryt monolith takes the record arm for textures, readbacks, framebuffers, units, images, programs and XFB

This commit is contained in:
2026-10-06 08:14:54 -04:00
parent a7409e1e9a
commit 2af4728e70
22 changed files with 520 additions and 187 deletions
+45 -45
View File
@@ -194,7 +194,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// push-monolith build keeps its frontend arms token for token, and a mask that never
// armed framebuffers has no record to decline from.
static Bool FramebufferRecordArmIsMandatory() {
return MG_Config::Transport != MG_Config::TransportMode::Monolith && FramebufferSubsystemEnabled();
return MG_Config::DataArmIsRecord() && FramebufferSubsystemEnabled();
}
[[noreturn]] static void RefuseFramebufferBindingSlotRead() {
MG_Pipe::MGPipeInputPoisonFatalForVerb(MG_Pipe::MGPipeInputField::GetFramebufferBindingSlot,
@@ -235,7 +235,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// MGPipeResourceOps, and this is neither.
SamplerImpl::BackendSamplerObject* GetRawDepthFetchSampler() {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
static UniquePtr<SamplerImpl::BackendSamplerObject> native;
static Uint generation = 0;
if (generation != g_backendContextGeneration) {
@@ -1585,7 +1585,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
Int g_activeXfbRole = -1;
RoleXfbState& ActiveXfbState() {
const Int role = MG_Config::Transport != MG_Config::TransportMode::Monolith;
const Int role = MG_Config::DataArmIsRecord();
auto& state = g_roleXfbState[role];
if (state.NativeGeneration != g_backendContextGeneration) {
state = {};
@@ -1602,7 +1602,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
XfbObjectState& CurrentXfb() {
#if MOBILEGL_BUILD_DISAGGREGATED
auto& state = ActiveXfbState();
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
const Uint64 lifetime = MG_Pipe::MGPipeApplier().BoundStreamOutputLifetimeId;
if (state.Current != lifetime || state.Cached == nullptr || state.NeedsBind)
BindTransformFeedback(0); // identity is the applier's lifetime, not this argument
@@ -1684,7 +1684,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (g_GLESFuncs.glMapBufferRange != nullptr && g_GLESFuncs.glUnmapBuffer != nullptr) {
for (const auto& target : targets) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport == MG_Config::TransportMode::Monolith)
if (!MG_Config::DataArmIsRecord())
#endif
if (!target.buffer) continue;
#if MOBILEGL_BUILD_DISAGGREGATED
@@ -1692,7 +1692,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// memory (rule E), so the persistence question is the server
// resource's and the captured bytes go back as a writeback EVENT -
// WritebackFromBackend from the apply thread is the R1/R2 shape.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
const MG_Pipe::MGPipeHandle res = target.handle;
auto* resource = BufferImpl::FindBufferResourceForHandle(res);
if (resource == nullptr) continue;
@@ -1788,7 +1788,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// direct binding (which produces a wrong layout, but is what happened before).
Bool BindScatterCaptureBuffer(SizeT packedStride, SizeT capacityVertices) {
#if MOBILEGL_BUILD_DISAGGREGATED
const Bool server = MG_Config::Transport != MG_Config::TransportMode::Monolith;
const Bool server = MG_Config::DataArmIsRecord();
auto& g_scatterBufferId = server ? CurrentXfb().serverScatterBuffer : ActiveXfbState().ScatterBuffer;
auto& g_scatterBufferSize = server ? CurrentXfb().serverScatterSize : ActiveXfbState().ScatterSize;
#endif
@@ -1829,7 +1829,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// application had put there - which is the whole point of the feature.
void ScatterCapturedRecords(XfbObjectState& xfb) {
#if MOBILEGL_BUILD_DISAGGREGATED
auto& g_scatterBufferId = MG_Config::Transport != MG_Config::TransportMode::Monolith
auto& g_scatterBufferId = MG_Config::DataArmIsRecord()
? xfb.serverScatterBuffer : ActiveXfbState().ScatterBuffer;
#endif
#if MOBILEGL_BUILD_DISAGGREGATED
@@ -1874,7 +1874,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
for (SizeT targetIndex = 0; targetIndex < xfb.targets.size(); ++targetIndex) {
const auto& target = xfb.targets[targetIndex];
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport == MG_Config::TransportMode::Monolith)
if (!MG_Config::DataArmIsRecord())
#endif
if (!target.buffer) continue;
// By BUFFER index, not by position in the compacted list - see XfbCaptureTarget.
@@ -1890,7 +1890,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// poke into client memory.
MG_Pipe::MGPipeHandle splitRes = MG_Pipe::kMGPipeNullHandle;
BufferImpl::GLESBufferResource* splitResource = nullptr;
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
splitRes = target.handle;
splitResource = BufferImpl::FindBufferResourceForHandle(splitRes);
if (splitResource == nullptr) {
@@ -1948,7 +1948,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
if (MG_Pipe::gMGPipeCallbacks.OnBufferWriteback != nullptr) {
// Sliced for the reason the readback path above is sliced: the bytes
// are INLINE in a SEG_EVENT record and the ring refuses a record wider
@@ -2029,7 +2029,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
#if MOBILEGL_BUILD_DISAGGREGATED
XfbProgramSource program;
const MG_Pipe::MGPStreamOutputBegin* span = nullptr;
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
const auto& state = MG_Pipe::MGPipeApplier();
const auto found = state.StreamOutputSpans.find(state.BoundStreamOutputLifetimeId);
if (found != state.StreamOutputSpans.end()) {
@@ -2063,7 +2063,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const SizeT bufferCount = program->GetTransformFeedbackBufferCount();
for (SizeT i = 0; i < bufferCount; ++i) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
if (!span || i >= 4) continue;
const auto& range = span->Targets[i];
if (MG_Pipe::MGPipeHandleIsNull(range.Res) || range.Size == 0) continue;
@@ -2090,7 +2090,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport == MG_Config::TransportMode::Monolith)
if (!MG_Config::DataArmIsRecord())
#endif
BufferImpl::SyncTransformFeedbackBindingPoints(bufferCount);
@@ -2236,7 +2236,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
auto& g_currentXfbState = roleState.Cached;
auto& g_xfbObjects = roleState.Objects;
auto& g_currentXfbName = roleState.Current;
const Bool server = MG_Config::Transport != MG_Config::TransportMode::Monolith;
const Bool server = MG_Config::DataArmIsRecord();
const Uint64 key = server ? MG_Pipe::MGPipeApplier().BoundStreamOutputLifetimeId : name;
roleState.NeedsBind = false;
#endif
@@ -2281,7 +2281,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
auto& g_currentXfbState = roleState.Cached;
auto& g_xfbObjects = roleState.Objects;
auto& g_currentXfbName = roleState.Current;
const Bool server = MG_Config::Transport != MG_Config::TransportMode::Monolith;
const Bool server = MG_Config::DataArmIsRecord();
const Uint64 key = server ? MG_Pipe::MGPipeApplier().VerbDeleteStreamOutputLifetimeId : name;
// A generated but never-bound name has no native object or lifetime.
if (server && key == 0) return;
@@ -3209,7 +3209,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// right. Ruling 19 makes the missing window unrepresentable at the sink; this is
// the same statement one level down, for the backend that would have papered over
// it. Under monolith the fall-back is correct and stays.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith && maxTouchedUnit >= 0) {
if (MG_Config::DataArmIsRecord() && maxTouchedUnit >= 0) {
// The windows are the PEER's records, so in a served session this ends THAT
// session (the in-process display server serves every client from one
// process; an abort here took the compositor down with the offender). The
@@ -3348,7 +3348,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// texture-resource record, and half of each is not an arm. The push-monolith build keeps
// the frontend walk token for token - there is no server to answer from there.
static Bool UnitTexturesByHandle() {
return MG_Config::Transport != MG_Config::TransportMode::Monolith && SamplerSubsystemEnabled() &&
return MG_Config::DataArmIsRecord() && SamplerSubsystemEnabled() &&
TextureResourceSubsystemEnabled();
}
@@ -4250,7 +4250,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// reach of an apply thread entirely.
void MarkWritableImageBufferTexturesGpuWritten() {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
// P8-C: the SERVER's twin of the buffer behind each writable buffer image, for
// this side's CPU readers (SplitHostBytesForCpuRead). Every read is a record:
// the unit set SyncImageTextureBinding(view) tracked, the applier's image view,
@@ -4365,7 +4365,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
static_cast<Uint32>(g_imageUnitHighWaterMark)),
static_cast<Uint32>(unitCount));
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
for (Uint32 unit = 0; unit < end; ++unit) {
if (unit >= st.BoundShaderImages.size()) break;
// The union can include a slot no set_shader_images
@@ -4392,7 +4392,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// g_imageUnitHighWaterMark is raised only by the funnel above, so if no set has
// ever arrived no unit has ever been given an image, and re-binding 0 on units that
// never held one is the no-op the comment above already proved.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) return;
if (MG_Config::DataArmIsRecord()) return;
#endif
for (Uint unit = 0; unit < unitCount; ++unit) {
SyncImageTextureBinding(unit);
@@ -4463,7 +4463,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
void SyncImageTextureBindingsForDraw(const DrawTextureSyncKeys& keys) {
if (g_imageUnitHighWaterMark == 0) return;
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
const auto& st = MG_Pipe::MGPipeApplier();
if (g_imageSweepValid && g_imageSweepShaderImagesSerial == st.ShaderImagesSerial &&
g_imageSweepTextureShutterSerial == st.TextureShutterSerial &&
@@ -4616,7 +4616,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// 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;
if (MG_Config::DataArmIsRecord()) return true;
#endif
return record.Fbo == g_backendFramebufferObjects.HandleOf(bound.get());
}
@@ -6933,7 +6933,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// the window rule (§5.3) makes the case it exists for UNREPRESENTABLE: a unit whose
// BoundSamplerStates[u] is null while the frontend held a sampler is a MISSING RECORD,
// not a unit to be served from the client's object.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"BindSamplerStates.Count\"} - unit %d holds a "
"frontend sampler object that the applied bind_sampler_states window does not "
"describe. Minting a twin for it would probe the client's allocator from the "
@@ -7043,7 +7043,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// P5e (tx2), CONTRACT-P5E §5.3: the same refusal CurrentUnitBindingsEpoch makes -
// the frontend walk below reads GetTextureUnitObject per unit, which an active
// transport may not do for a record whose client has moved on.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"BindSamplerStates.Count\"} - a draw "
"touches units 0..%d and no bind_sampler_states has ever been applied "
"while the sampler subsystem bit is set; the pre-handle sampler walk "
@@ -10995,7 +10995,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// 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) {
if (MG_Config::DataArmIsRecord()) {
auto& applierState = MG_Pipe::MGPipeApplier();
const MG_Pipe::MGPipeHandle readFbo = applierState.VerbBlitReadFbo;
const MG_Pipe::MGPipeHandle drawFbo = applierState.VerbBlitDrawFbo;
@@ -11160,7 +11160,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// 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) {
if (MG_Config::DataArmIsRecord()) {
const MG_Pipe::MGPipeHandle dstHandle = MG_Pipe::MGPipeApplier().VerbCopyTexDst;
auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.GetOrCreateByHandle(dstHandle);
if (backendTextureSlot == nullptr) {
@@ -12350,7 +12350,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// 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) {
if (MG_Config::DataArmIsRecord()) {
const MG_Pipe::MGPipeHandle dstHandle = MG_Pipe::MGPipeApplier().VerbCopyTexDst;
const auto* dstRecord = PipeTextureRecordForHandle(dstHandle);
// P5e (tx2): SYNCED, not merely looked up. The destination of a copy is not sampled
@@ -12474,7 +12474,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
#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) {
if (MG_Config::DataArmIsRecord()) {
const MG_Pipe::MGPipeHandle dstHandle = MG_Pipe::MGPipeApplier().VerbCopyTexDst;
// P5e (tx2): synced by handle - see CopyTexImage2D's note. A copy destination is not
// in the sampler-view window, so nothing else on this path would build its twin.
@@ -12691,7 +12691,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// whose level the driver cannot read has no answer here.
if (!MG_Pipe::MGPipeHandleIsNull(record.Desc.ViewOf)) {
declined = "a view has no staged level of its own";
} else if (!store.CopiesIntoServerStorage()) {
} else if (!store.Holds()) {
declined = "this arm keeps no staged store";
} else if (driverWrittenBefore) {
declined = "the driver has written the texture since its bytes crossed";
@@ -12914,7 +12914,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// so that the unit it reads is the unit the call was made on. With the unit read gone the
// wait has nothing left to protect, which is what lets PipeCalls.def's WaitClass column
// for GenerateMipmap move to kWaitNone.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
const auto mipRes = MG_Pipe::MGPipeApplier().VerbMipRes;
// P8-SE: whether the staged store still stands for the texture is read BEFORE the note
// below sets the mark, or the CPU arm's store route could never answer at all.
@@ -13092,7 +13092,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
GLint z, GLESCopyImageEndpoint& out) {
if (endpoint.IsRenderbuffer()) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
if (!PipeRenderbufferRecordForHandle(endpoint.RenderbufferHandle)) return false;
auto* slot = RenderbufferImpl::g_backendRenderbufferObjects.GetOrCreateByHandle(endpoint.RenderbufferHandle);
if (!slot) return false;
@@ -13122,7 +13122,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// reaches here - but the assertion that says so is compiled out of a release build, and
// SyncTextureObjectToBackend would register a null state object.
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
out.texture = TextureImpl::SyncTextureToBackendByHandle(endpoint.TextureHandle);
} else
#endif
@@ -13148,7 +13148,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
static TextureInternalFormat GetCopyImageEndpointFormat(const CopyImageEndpoint& endpoint) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
const auto* record = endpoint.IsRenderbuffer()
? PipeRenderbufferRecordForHandle(endpoint.RenderbufferHandle)
: PipeTextureRecordForHandle(endpoint.TextureHandle);
@@ -13207,7 +13207,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// and the arm is the TRANSPORT and not the build - build-split runs its unit and
// integration-gpu lanes under MOBILEGL_TRANSPORT=monolith, where this mirror is on an
// ordinary correct path and must still run.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) return;
if (MG_Config::DataArmIsRecord()) return;
#endif
// P4a (D-M) named this site `copy-image-shadow-mirror` with MGPipeUnmigratedEmulation.
// P8-SE removed the call: the return above means no transport reaches it, and on a
@@ -13306,7 +13306,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// renderbuffer, a cube or 1D-array endpoint, a source the driver has written, a level whose
// bytes did not all cross) it is marked driver-written instead, so it declines rather than
// answering with stale bytes. Nothing moves to the client (ROADMAP P8's original row).
if (MG_Config::Transport != MG_Config::TransportMode::Monolith && handleArmDestination) {
if (MG_Config::DataArmIsRecord() && handleArmDestination) {
const Bool followed =
!srcEndpoint.IsRenderbuffer() && !MG_Pipe::MGPipeHandleIsNull(srcEndpoint.TextureHandle) &&
TextureImpl::FollowCopyImageInStagedStore(
@@ -13391,7 +13391,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MOBILEGL_ASSERT(false, "glCopyImageSubData failed after frontend validation accepted the request.");
#if MOBILEGL_BUILD_DISAGGREGATED
// P8-E: the store already made the move the driver just refused.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith && handleArmDestination) {
if (MG_Config::DataArmIsRecord() && handleArmDestination) {
TextureImpl::NoteDriverSideTextureWriteByHandle(dstEndpoint.TextureHandle);
}
#endif
@@ -13419,7 +13419,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// the bind from the record; reading the frontend image binding here is the
// GetImageTextureBinding row that rule F forbids, and `bind_shader_image` is not a
// barriered row.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
TextureImpl::NoteImageUnitBoundWithoutReadingTheFrontend(unit, texture != 0);
return;
}
@@ -13466,7 +13466,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
void ShaderStorageBlockBinding(GLuint program, const GLchar* storageBlockName, GLuint storageBlockBinding) {
if (!storageBlockName) return;
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
const auto handle = MG_Pipe::MGPipeApplier().VerbStorageBlockProgram;
auto* slot = PrgramImpl::g_backendProgramObjects.FindByHandle(handle);
if (slot && *slot && (*slot)->GetBackendProgramId()) {
@@ -13687,7 +13687,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const SizeT packedSize = dstOffset + static_cast<SizeT>(height - 1) * dstRowStride + rowBytes;
const auto& pixelPackBufferObject =
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Config::Transport != MG_Config::TransportMode::Monolith
MG_Config::DataArmIsRecord()
? SplitReadbackPackBuffer() :
#endif
MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
@@ -14708,7 +14708,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
const auto& pixelPackBufferObject =
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Config::Transport != MG_Config::TransportMode::Monolith
MG_Config::DataArmIsRecord()
? SplitReadbackPackBuffer() :
#endif
MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
@@ -14961,7 +14961,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// (P8-SE dropped copy-image-shadow-mirror, a site no transport reached). Only this call
// site is retired here, and only because its own premise did not hold (texture-remint-pull
// went the same way in P9 W2, and P8-B the two split generate-mipmap names, likewise).
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
if (shadow == nullptr) {
MGLOG_E_ONCE("GetTexImage: no level shadow on this side for target=0x%x level=%d, and under an "
"active transport there is no client shadow to fall back to - the read is "
@@ -14979,7 +14979,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MG_Pipe::MGPipeUnmigratedEmulation("get-tex-image-shadow");
const auto& pixelPackBufferObject =
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Config::Transport != MG_Config::TransportMode::Monolith
MG_Config::DataArmIsRecord()
? SplitReadbackPackBuffer() :
#endif
MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
@@ -15302,7 +15302,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// capturing subsequent client-memory readbacks into it).
auto& pixelPackBufferObject =
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Config::Transport != MG_Config::TransportMode::Monolith
MG_Config::DataArmIsRecord()
? SplitReadbackPackBuffer() :
#endif
MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
@@ -15749,7 +15749,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// on every exit path, so a later readback can never land in a stale PBO.
auto& pixelPackBufferObject =
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Config::Transport != MG_Config::TransportMode::Monolith
MG_Config::DataArmIsRecord()
? SplitReadbackPackBuffer() :
#endif
MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
+67 -21
View File
@@ -2247,6 +2247,46 @@ namespace MobileGL::MG_Backend::DirectGLES {
resource->syncedChangeSerial = ResourceSerialOf(res);
return;
}
// P13 W4b: A SERVER-ORIGINATED WRITE INTO AN ALIASING STORE (monolith's record arm)
// lands in the GL store, never in the base: the bytes are the sink's scratch, and the
// base is the frontend's shadow, which this side does not write. Queued uploads land
// first, so they cannot later overwrite these bytes from the stale shadow; the store
// is then marked GPU-written, which sends the frontend (its own pack-buffer mark) and
// every CPU reader here (SplitHostBytesForCpuRead) back to the GL store for them.
if (MG_Pipe::MGPipeApplyingServerOriginatedWrite() && MG_Config::RecordArmAliasesFrontend() &&
bytes != nullptr && size != 0) {
// The store must exist to take them: a pack buffer is often never drawn from, so
// nothing else has built it. Only a store that does not exist yet is ensured: the
// applier has already counted THIS write into the record's serial, so the ensure
// of a built store would read it as an untracked mutation and re-upload the
// shadow over the runs this landing wrote before it.
if (CanTouchGLNow() && (resource->id == 0 || !resource->storageInitialized ||
resource->pendingRespecify ||
resource->contextGeneration != g_bufferContextGeneration)) {
resource = EnsureBufferResourceForHandle(nullptr, res);
}
if (resource != nullptr && CanTouchGLNow() && resource->id != 0 && resource->storageInitialized &&
!resource->persistentMapped && !resource->pendingRespecify &&
resource->contextGeneration == g_bufferContextGeneration &&
StorageMatchesSize(*resource, ResourceWidthOf(res))) {
FlushPendingRangesFrom(*resource, resource->hostBytes, ResourceWidthOf(res));
BindBufferId(TempBufferTarget, resource->id);
g_GLESFuncs.glBufferSubData(TempBufferTarget, static_cast<GLintptr>(offset),
static_cast<GLsizeiptr>(size), bytes);
// The store holds everything the record now says (the applier bumped its
// serial for this write): without the stamp the next ensure would see a
// newer serial and re-upload the shadow over the bytes that just landed.
resource->syncedChangeSerial = ResourceSerialOf(res);
} else if (resource != nullptr) {
const std::lock_guard<std::mutex> lock(resource->pendingMutex);
auto& write = resource->pendingResidentWrites.emplace_back();
write.offset = offset;
const auto* source = static_cast<const Uint8*>(bytes);
write.bytes.assign(source, source + size);
}
if (resource != nullptr) resource->serverGpuWritten = true;
return;
}
#endif
// M-2: UNDER pendingMutex, because this line runs BEFORE the CanTouchGLNow()
// test below - i.e. on the arm D-A2 deliberately keeps reachable off the render
@@ -3093,7 +3133,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// the level image when the record carries a piece of the level rather than the
// whole of it (fix A2), which is why it is read here now.
auto& store = MG_Remote::Server::ServerStagedTexture();
if (!store.CopiesIntoServerStorage()) return;
// P13 W4b: an aliasing store (monolith's record arm) adopts too - it records where
// the run sits in the frontend's level shadow instead of copying it.
if (!store.Holds()) return;
// The applier's gate has already faulted every shape that reaches here without
// bytes, and under split the codec declared the run's length (Blob.Size) -
// TextureEmit.h's "the bytes this record declares ARE the level shadow".
@@ -3132,19 +3174,21 @@ namespace MobileGL::MG_Backend::DirectGLES {
// RegionCount == 0 IS the whole-level spelling - "the run is the level shadow",
// beginning at the level's first byte - so it is adopted with the spelling that
// replaces the level; every other record names a RUN of the level by its own box.
// A client record's run: under monolith's record arm it IS the frontend level shadow,
// the one source an aliasing store keeps by address.
if (record.RegionCount == 0) {
store.Adopt(key, uploadTarget, record.Level, extent, bytes, runBytes);
store.Adopt(key, uploadTarget, record.Level, extent, bytes, runBytes, /*frontendShadow=*/true);
return;
}
store.AdoptRun(key, uploadTarget, record.Level, extent,
MG_Remote::Server::StagedTextureRunImageOffset(record, regions), bytes,
runBytes);
runBytes, /*frontendShadow=*/true);
}
void Ops_H_TextureRespecify(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPResourceDesc& desc,
const MG_Pipe::MGPRespecifiedLevel* level) {
auto& store = MG_Remote::Server::ServerStagedTexture();
if (!store.CopiesIntoServerStorage()) return;
if (!store.Holds()) return;
const Uint64 key = MG_Remote::Server::StagedTextureStore::KeyForHandle(res);
if (level != nullptr) {
// ONE glTexImage*D redefined one level: its exact extent crossed with the
@@ -5044,7 +5088,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
"it) and MOBILEGL_PIPE_LEGACY_MEMOS=0 disables the pre-handle g_fboSynced* arm");
}
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith &&
if (MG_Config::DataArmIsRecord() &&
verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) {
StopOnTransportWithoutRecordArm(
"kMGPipeSubsystemFramebuffer (bit 9)",
@@ -5089,7 +5133,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
"cheap-gate trio");
}
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith &&
if (MG_Config::DataArmIsRecord() &&
verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) {
StopOnTransportWithoutRecordArm(
"kMGPipeSubsystemTextureResources (bit 10)",
@@ -5125,7 +5169,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
"arm and SamplerPassMemo's raw-pointer rows");
}
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith &&
if (MG_Config::DataArmIsRecord() &&
verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) {
StopOnTransportWithoutRecordArm(
"kMGPipeSubsystemSamplers (bit 11)",
@@ -5154,7 +5198,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
"MOBILEGL_PIPE_LEGACY_MEMOS=0 disables g_programTwinLookupMemo");
}
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith &&
if (MG_Config::DataArmIsRecord() &&
verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) {
StopOnTransportWithoutRecordArm(
"kMGPipeSubsystemPrograms (bit 12)",
@@ -6501,7 +6545,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
return;
}
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
// P13 W4b (defect h): monolith's record arm takes the body too; its old arm only
// cleared m_isInitialized and replayed the frontend shadow at the next sync.
if (MG_Config::DataArmIsRecord()) {
const auto format = static_cast<TextureInternalFormat>(record.Desc.InternalFormat);
// ES image binding needs immutable storage, but an already
// immutable core-format allocation needs no replacement. Keep
@@ -7806,7 +7852,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// re-open ID-107's hole somewhere else.
#if MOBILEGL_BUILD_DISAGGREGATED
#define MGB_TEXTURE_RECORD_ARM_SELECTED() \
(MG_Config::Transport != MG_Config::TransportMode::Monolith && TextureResourceSubsystemEnabled())
(MG_Config::DataArmIsRecord() && TextureResourceSubsystemEnabled())
#else
#define MGB_TEXTURE_RECORD_ARM_SELECTED() (false)
#endif
@@ -8092,7 +8138,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// monolith). MGB_STAGED_TEXTURE_LIVE is the runtime discriminator; pushedStorage/pushedRes
// are the function's own locals. All are #undef'd with the rest after the function.
#define MGB_STAGED_TEXTURE_LIVE \
(pushedStorage != nullptr && MG_Remote::Server::ServerStagedTexture().CopiesIntoServerStorage())
(pushedStorage != nullptr && MG_Remote::Server::ServerStagedTexture().Holds())
#define MGB_TEXTURE_TARGET(obj) \
(MGB_STAGED_TEXTURE_LIVE ? BufferImpl::StagedTextureTargetForPipeTarget(pushedStorage->Desc.Target) \
: (obj)->GetTarget())
@@ -8450,12 +8496,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Ruling 1's arm: the push-monolith build keeps the frontend test below token for
// token, because there the two answers are the same answer and the verify comparator
// needs the frontend arm.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith && pushedStorage != nullptr &&
if (MG_Config::DataArmIsRecord() && pushedStorage != nullptr &&
!MG_Pipe::MGPipeHandleIsNull(pushedStorage->Desc.ViewOf)) {
SyncTextureViewToBackendByRecord(pushedRes, *pushedStorage, stateTextureObject);
return;
}
if (MG_Config::Transport != MG_Config::TransportMode::Monolith && pushedStorage != nullptr) {
if (MG_Config::DataArmIsRecord() && pushedStorage != nullptr) {
// Not a view: fall through to the storage body with the frontend test skipped.
} else
#endif
@@ -8510,7 +8556,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// P5e (tx2), ruling 1's arm: under a transport the re-dirty half is a NAMED
// refusal at its entry (§5.2) instead of a walk of the client's level shadows;
// the push-monolith build keeps the frontend transition exactly as it is.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
RequireImageBindableStorageByHandle(pushedRes, *pushedStorage);
} else
#endif
@@ -11238,7 +11284,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// because tx2's record arm inside SyncMipmapsToBackend is itself selected by
// `Transport != Monolith` and cannot answer here.
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
backendTextureObject->SyncMipmapsToBackendByHandle(surface.Res);
// From here on every draw, clear and blit into this framebuffer can write the
// texture behind the staged shadow's back (the frontend arm's note, by handle).
@@ -11327,7 +11373,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// under a transport there is no application on this side to report it to. On the
// push-monolith arm there is, so the monolith arm keeps the object form.
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
backendRenderbufferObject->SyncToBackendByHandle(surface.Res);
} else
#endif
@@ -11379,7 +11425,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// the storage holds whatever the draw wrote there, so the readback has to overwrite it.
Bool IsAlphaWidenedFallbackReadAttachment() {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
const auto* record = MG_Pipe::MGPipeApplier().ReadFramebuffer();
return record && IsAlphaWidenedColorSurface(record->ReadSurface);
}
@@ -11393,7 +11439,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bool IsFixedPointFallbackReadAttachment() {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
const auto* record = MG_Pipe::MGPipeApplier().ReadFramebuffer();
return record && record->ReadSurface.Kind != MG_Pipe::kMGPipeSurfaceKindNone &&
IsSnormFormat(static_cast<TextureInternalFormat>(record->ReadSurface.InternalFormat));
@@ -11566,7 +11612,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// (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
MG_Config::DataArmIsRecord()
? m_pushedSyncHandle
:
#endif
@@ -11744,7 +11790,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// record, which the null-record refusal below names.
const MG_Pipe::MGPipeHandle fbo =
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Config::Transport != MG_Config::TransportMode::Monolith
MG_Config::DataArmIsRecord()
? m_pushedSyncHandle
:
#endif
@@ -13057,7 +13103,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// it - MGPImageView::InternalFormat is that same GLenum, copied by the client
// out of the unit's own binding. A unit the applier has never been told about
// answers 0, which is what an unbound unit answers on the frontend arm too.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
const auto& st = MG_Pipe::MGPipeApplier();
if (static_cast<SizeT>(unit) >= st.BoundShaderImages.size()) return 0;
return static_cast<Uint>(st.BoundShaderImages[static_cast<SizeT>(unit)].InternalFormat);
+1 -1
View File
@@ -794,7 +794,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// ProgramSubsystemEnabled() already latches, so this is one load and one test, and a latch
// here would only hide which half answered.
inline Bool ProgramHandleArm() {
return MG_Config::Transport != MG_Config::TransportMode::Monolith && ProgramSubsystemEnabled();
return MG_Config::DataArmIsRecord() && ProgramSubsystemEnabled();
}
// ---- P4a: what the twins read INSTEAD of the frontend object ----
+1 -1
View File
@@ -2363,7 +2363,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
GLsizei sliceCount, void* pixels, Bool applyPackImageParams, FillRow&& fillRow) {
const auto& pixelPackBufferObject =
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Config::Transport != MG_Config::TransportMode::Monolith
MG_Config::DataArmIsRecord()
? SplitReadbackPackBuffer() :
#endif
MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
@@ -361,7 +361,7 @@
const SizeT texels = static_cast<SizeT>(std::max(extent.x(), 0)) * static_cast<SizeT>(std::max(extent.y(), 0)) *
static_cast<SizeT>(std::max(extent.z(), 0));
const char* declined = nullptr;
if (!store.CopiesIntoServerStorage()) {
if (!store.Holds()) {
declined = "this arm keeps no staged store";
} else if (store.IsDriverWritten(key)) {
declined = "the driver has written the texture since its bytes crossed";
@@ -1344,7 +1344,7 @@ namespace MobileGL::MG_Impl::GLImpl {
// BeginXfbCaptureForDraw does mark the capture targets - where the fence at the
// caller still runs, so the readback it emits is pure new work on the monolith
// path and integration-gpu cannot see it.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) buffer->SyncGpuWrites();
if (MG_Config::DataArmIsRecord()) buffer->SyncGpuWrites();
#endif
const Range1D range = bindingPoint.GetRange();
const Uint8* mapped = buffer->MappedData();
@@ -50,7 +50,7 @@
// read as it was: its ReadPixels maps the driver PBO back into the shadow inside the call, and the
// pull build compiles this to nothing (G1).
#if MOBILEGL_BUILD_DISAGGREGATED
#define MGL_SYNC_PIXEL_UNPACK_SOURCE(buffer) do { if (MG_Config::Transport != MG_Config::TransportMode::Monolith) (buffer)->SyncGpuWrites(); } while (0)
#define MGL_SYNC_PIXEL_UNPACK_SOURCE(buffer) do { if (MG_Config::DataArmIsRecord()) (buffer)->SyncGpuWrites(); } while (0)
#else
#define MGL_SYNC_PIXEL_UNPACK_SOURCE(buffer) ((void)0)
#endif
@@ -6122,7 +6122,7 @@ namespace MobileGL::MG_Impl::GLImpl {
viewObject->SetSamples(storageOwner->GetSamples());
viewObject->SetFixedSampleLocations(storageOwner->HasFixedSampleLocations());
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith)
if (MG_Config::DataArmIsRecord())
MG_Pipe::MGPipeEmitSamplerViewCreate(*viewObject);
#endif
}
@@ -6587,7 +6587,7 @@ namespace MobileGL::MG_Impl::GLImpl {
TextureUploadTarget uploadTarget, GLint level, GLenum format,
GLenum type, GLsizei bufSize, void* pixels, const char* caller) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
MGP_FILL(GetTextureImage);
MG_Backend::gBackendFunctionsTable.GL.GetTextureImage(textureObject, uploadTarget, level, format, type,
bufSize, pixels);
@@ -6616,7 +6616,7 @@ namespace MobileGL::MG_Impl::GLImpl {
auto uploadTarget = GetPrimaryUploadTarget(textureObject);
#if MOBILEGL_BUILD_DISAGGREGATED
// Unlike glGetTexImage's face target, this DSA call reads all six faces.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith &&
if (MG_Config::DataArmIsRecord() &&
textureObject->GetTarget() == TextureTarget::TextureCubeMap)
uploadTarget = TextureUploadTarget::Unknown;
#endif
@@ -6881,7 +6881,7 @@ namespace MobileGL::MG_Impl::GLImpl {
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) {
if (!GetTexImage_State(target, level, format, type, pixels)) return;
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
GetTexImage_Backend(target, level, format, type, pixels);
return;
}
+1 -1
View File
@@ -2261,7 +2261,7 @@ namespace MobileGL::MG_Pipe {
Bool PipeInputs::HasOpenTransformFeedbackSpan(Uint64 lifetimeId) const {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
return lifetimeId != 0 && MGPipeApplier().StreamOutputSpans.count(lifetimeId) != 0;
}
#endif
+3
View File
@@ -1585,6 +1585,9 @@ namespace MobileGL::MG_Pipe {
Bool pieceAccepted = true;
if constexpr (MGPipeTextureRecordsReachTheApplier()) {
dispatched = true;
// P13 W4b: monolith's record arm keeps a staged-texture store that ALIASES the
// level shadow, and it needs the run's length the way a split server does.
if (MG_Config::RecordArmAliasesFrontend()) m_lastSubData.Blob.Size = runBytes;
// `runBytes` closes CONTRACT-P5 table 1 row 7's open half. The record still
// declares Blob.Size 0 on the monolith arm - where the applier reads the
// companion pointer and the destination box bounds the write - but under split
@@ -172,6 +172,8 @@ void main() { o_color = gl_FragCoord.y < u_split ? u_bottom : u_top; }
// knob runs it anyway, which is how each defect is shown red and how dev's fix is checked.
bool MonolithDefect(bool onEspryt, bool onMagma) const {
if (m_wire || std::getenv(kRunMonolithDefects) != nullptr) return false;
// P13 W4b: a monolith on the record arm runs the wire's generation, so it meets none.
if (PeekSplitRuntime().dataArmIsRecord) return false;
return m_espryt ? onEspryt : onMagma;
}
static constexpr const char* kM1 =
+10
View File
@@ -3946,6 +3946,16 @@ namespace MobileGL::MG_Pipe {
// build would turn 1117 integration cases red for running code that is correct in the role
// they run it in. None of the five is on P5's reduced path, so under a real transport this
// costs nothing and catches a lot.
namespace {
thread_local Bool tl_serverOriginatedWrite = false;
} // namespace
Bool MGPipeApplyingServerOriginatedWrite() { return tl_serverOriginatedWrite; }
MGPipeServerOriginatedWriteScope::MGPipeServerOriginatedWriteScope() : m_previous(tl_serverOriginatedWrite) {
tl_serverOriginatedWrite = true;
}
MGPipeServerOriginatedWriteScope::~MGPipeServerOriginatedWriteScope() { tl_serverOriginatedWrite = m_previous; }
void MGPipeUnmigratedEmulation(const char* name) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
+19
View File
@@ -1440,6 +1440,25 @@ namespace MobileGL::MG_Pipe {
// name a TEXTURE target: Buffer, Renderbuffer and anything at or above
// MGPipeResourceTarget::Count are Fatal{ProtocolCorruption} rather than an upload onto
// whatever object holds that slot in the texture slot space.
// P13 W4b: A WRITE THE SERVER PRODUCED, going through the ordinary resource_subdata gate - the
// pack-buffer landing of a readback (PipeApplier.cpp LandReadbackInBuffer) is the one producer.
// Its bytes are the server's own scratch and live for the call only, so a store that ALIASES
// the frontend's shadow (monolith's record arm) must not take them as its base: it lands them
// in the GL store and marks it GPU-written instead, which is how the frontend and every CPU
// reader learn of them (SyncGpuWrites / SplitHostBytesForCpuRead). A copying store takes them
// as it always has. Thread-local; scoped by the producer.
Bool MGPipeApplyingServerOriginatedWrite();
class MGPipeServerOriginatedWriteScope {
public:
MGPipeServerOriginatedWriteScope();
~MGPipeServerOriginatedWriteScope();
MGPipeServerOriginatedWriteScope(const MGPipeServerOriginatedWriteScope&) = delete;
MGPipeServerOriginatedWriteScope& operator=(const MGPipeServerOriginatedWriteScope&) = delete;
private:
Bool m_previous;
};
Bool MGPipeApplyResourceSubData(const MGPSubData& record, const void* bytes,
const MGPSubRegion* regions = nullptr);
// buffer_subdata_resident: same shape; `bytes` is the application's staging store and is
+17
View File
@@ -32,6 +32,8 @@
#include <MG_Util/Debug/Log.h>
#include <Config.h>
#include <MG_State/GLState/ProgramState/ProgramArtifactsCodec.h>
#include <cstdint>
#include <cstdlib>
@@ -308,6 +310,21 @@ namespace MobileGL::MG_Pipe {
// the monolith twin reads GetLinkedShaderStages() off the frontend object it is
// handed. Named and discarded rather than left out of the signature, so the two
// arms stay one row.
#if MOBILEGL_BUILD_DISAGGREGATED
// P13 W4c: MONOLITH'S RECORD ARM READS THE RECORD'S ARCHIVE (ProgramHandleArm), so it
// gets one: the link's snapshot, taken here, once per link - the same thing the wire
// decodes into the record, without the codec. A copy rather than a borrow, because the
// record is a snapshot of THIS link and the frontend object's artefacts are replaced
// by the next one while the record may still be in use.
if (MG_Config::RecordArmAliasesFrontend() && link != nullptr && spirv != nullptr) {
auto archive = MakeShared<MG_State::GLState::ProgramArchive>();
archive->Link = *link;
archive->Spirv = *spirv;
if (linkedStages != nullptr) archive->LinkedStages.assign(linkedStages, linkedStages + linkedStageCount);
MGPipeApplyCreateShaderState(*desc, link, spirv, std::move(archive));
return;
}
#endif
(void)linkedStages;
(void)linkedStageCount;
// P5e (pg): NO ARCHIVE ON THIS ARM, and the null is the arm selection rather than a
+212 -62
View File
@@ -261,22 +261,46 @@ namespace MobileGL::MG_Remote::Client {
return sink;
}
// The session a ported verb emits through - or null, which is the monolith port.
ClientSession* VerbSessionFor(const char* slot) {
if (g_monolithVerbPort) return nullptr;
return &RequireSession(slot);
}
// The answer of a reply row on the port: written straight into the caller's buffer, which
// is what the session's reply slot would have been copied into.
class MonolithReplySink final : public Wire::ReplySink {
public:
MonolithReplySink(void* out, Uint64 capacity) : m_out(out), m_capacity(capacity) {}
void PostReply(Uint64, Int32 status, const void* bytes, Uint64 size) override {
m_posted = true;
m_status = status;
m_size = size;
if (status == kStatusOk && bytes != nullptr && m_out != nullptr && size != 0) {
std::memcpy(m_out, bytes, static_cast<SizeT>(std::min(size, m_capacity)));
}
}
Bool Posted() const { return m_posted; }
Int32 Status() const { return m_status; }
Uint64 Size() const { return m_size; }
void ApplyOnMonolithPort(MG_Pipe::MGPWireOp op, const void* payload, const Wire::WireTail* tails,
Uint32 tailCount) {
private:
void* m_out;
Uint64 m_capacity;
Bool m_posted = false;
Int32 m_status = kStatusError;
Uint64 m_size = 0;
};
// The port's reply cap: one band answers any read a process can hold (PlanReadbackBands
// still cuts a row wider than this, as it would for a slot).
constexpr Uint32 kMonolithMaxReplyBytes = 0x7fffffffu;
Bool ApplyOnMonolithPort(MG_Pipe::MGPWireOp op, const void* payload, const Wire::WireTail* tails,
Uint32 tailCount, Wire::ReplySink* replies) {
Server::ServerVerbSink& sink = MonolithVerbSink();
MG_Backend::BackendObject* backend = MG_Backend::pActiveBackendObject.get();
if (sink.Backend() != backend) sink.SetBackend(backend);
Bool applied = false;
const char* row = nullptr;
if (sink.Backend() != backend) {
sink.SetBackend(backend);
sink.SetMaxReplyBytes(kMonolithMaxReplyBytes);
}
using Op = MG_Pipe::MGPWireOp;
switch (op) {
case MG_Pipe::MGPWireOp::DrawVbo: {
row = "draw_vbo";
case Op::DrawVbo: {
const auto& info = *static_cast<const MG_Pipe::MGPDrawInfo*>(payload);
const auto* ranges = (tailCount > 0 && tails[0].Size != 0)
? static_cast<const MG_Pipe::MGPDrawRange*>(tails[0].Bytes)
@@ -284,42 +308,132 @@ namespace MobileGL::MG_Remote::Client {
const auto* indirect = ((info.Flags & MG_Pipe::kDrawIsIndirect) != 0 && tailCount > 1)
? static_cast<const MG_Pipe::MGPDrawIndirect*>(tails[1].Bytes)
: nullptr;
applied = sink.OnDrawVbo(info, ranges, nullptr, indirect);
break;
return sink.OnDrawVbo(info, ranges, nullptr, indirect);
}
case Op::LaunchGrid:
return sink.OnLaunchGrid(*static_cast<const MG_Pipe::MGPGridInfo*>(payload));
// ---- W4b: the texture family's verbs ----
case Op::GenerateMipmap:
return sink.OnGenerateMipmap(*static_cast<const MG_Pipe::MGPMipPlan*>(payload));
case Op::CopyFramebufferToTexture:
return sink.OnCopyFramebufferToTexture(*static_cast<const MG_Pipe::MGPCopyFromFramebuffer*>(payload));
case Op::ResourceCopyRegion:
return sink.OnResourceCopyRegion(*static_cast<const MG_Pipe::MGPCopyRegion*>(payload));
// ---- W4b: the readbacks. The reply rows answer through `replies`, which writes the
// caller's buffer; the pack-buffer rows land in the buffer through the applier.
case Op::ReadPixels:
return sink.OnReadPixels(*static_cast<const MG_Pipe::MGPReadbackInfo*>(payload), 0, replies);
case Op::GetTextureImage:
return sink.OnGetTextureImage(*static_cast<const MG_Pipe::MGPReadbackInfo*>(payload), 0, replies);
case Op::ReadPixelsToBuffer:
return sink.OnReadPixelsToBuffer(*static_cast<const MG_Pipe::MGPReadbackToBuffer*>(payload));
case Op::GetTextureImageToBuffer:
return sink.OnGetTextureImageToBuffer(*static_cast<const MG_Pipe::MGPReadbackToBuffer*>(payload));
// ---- W4c: framebuffers, image units, storage-block bindings ----
case Op::Clear:
return sink.OnClear(*static_cast<const MG_Pipe::MGPClear*>(payload));
case Op::Blit:
return sink.OnBlit(*static_cast<const MG_Pipe::MGPBlit*>(payload));
case Op::BindShaderImage:
return sink.OnBindShaderImage(*static_cast<const MG_Pipe::MGPImageBind*>(payload));
// ---- W4a's XFB half: the capture span and its object, by lifetime id ----
case Op::BeginStreamOutput:
return sink.OnBeginStreamOutput(*static_cast<const MG_Pipe::MGPStreamOutputBegin*>(payload));
case Op::EndStreamOutput:
return sink.OnEndStreamOutput(*static_cast<const MG_Pipe::MGPXfbAccounting*>(payload));
case Op::PauseStreamOutput:
return sink.OnPauseStreamOutput(*static_cast<const MG_Pipe::MGPStreamOutputControl*>(payload));
case Op::ResumeStreamOutput:
return sink.OnResumeStreamOutput(*static_cast<const MG_Pipe::MGPStreamOutputControl*>(payload));
case Op::BindStreamOutput:
return sink.OnBindStreamOutput(*static_cast<const MG_Pipe::MGPStreamOutputBind*>(payload));
case Op::DeleteStreamOutput:
return sink.OnDeleteStreamOutput(*static_cast<const MG_Pipe::MGPStreamOutputBind*>(payload));
case Op::SetStorageBlockBinding: {
// The name is the emitter's own NUL-terminated string (VerbChannel::StageBytes on
// the port names it by address), alive for this call.
const auto& record = *static_cast<const MG_Pipe::MGPStorageBlockBinding*>(payload);
const auto* name = reinterpret_cast<const char*>(static_cast<std::uintptr_t>(record.Name.Offset));
return sink.OnSetStorageBlockBinding(record, name);
}
case MG_Pipe::MGPWireOp::LaunchGrid:
row = "launch_grid";
applied = sink.OnLaunchGrid(*static_cast<const MG_Pipe::MGPGridInfo*>(payload));
break;
default:
// A slot InstallMonolithVerbPort routes here whose row this switch does not know:
// the two lists drifted, and falling through to the driver would be the very
// "ran the other arm and looked fine" this port exists to end.
UnmigratedVerbFatal("MonolithVerbPort");
}
if (!applied) {
MGLOG_E_ONCE("MGPipe: the monolith verb port's %s was declined by the sink; nothing was "
"drawn or dispatched",
row);
}
}
// The emission of a ported verb: the session's, or the port's.
void EmitVerbTails(ClientSession* session, MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes,
const Wire::WireTail* tails, Uint32 tailCount) {
if (session != nullptr) {
session->EmitAndWaitTails(op, payload, payloadBytes, tails, tailCount, nullptr, 0, nullptr);
return;
// WHERE A PORTED EMITTER'S RECORD GOES: the session's ring, or - on monolith's record arm -
// the in-process sink above. Spelled like the session (EmitAndWait / EmitAndWaitTails /
// MaxReplyBytes / ...) so an emitter reads the same either way.
class VerbChannel {
public:
explicit VerbChannel(ClientSession* session) : m_session(session) {}
Uint64 EmitAndWait(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes,
const void* varTail, Uint64 varTailBytes, void* replyOut, Uint64 replyBytes,
Int32* statusOut, Uint64* replySizeOut = nullptr) {
if (m_session != nullptr) {
return m_session->EmitAndWait(op, payload, payloadBytes, varTail, varTailBytes, replyOut,
replyBytes, statusOut, replySizeOut);
}
const Wire::WireTail tail{varTail, varTailBytes};
Port(op, payload, &tail, varTail != nullptr ? 1u : 0u, replyOut, replyBytes, statusOut, replySizeOut);
return 0;
}
ApplyOnMonolithPort(op, payload, tails, tailCount);
}
void EmitVerbNoTail(ClientSession* session, MG_Pipe::MGPWireOp op, const void* payload,
Uint64 payloadBytes) {
if (session != nullptr) {
session->EmitAndWait(op, payload, payloadBytes, nullptr, 0, nullptr, 0, nullptr);
return;
Uint64 EmitAndWaitTails(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes,
const Wire::WireTail* tails, Uint32 tailCount, void* replyOut, Uint64 replyBytes,
Int32* statusOut, Uint64* replySizeOut = nullptr) {
if (m_session != nullptr) {
return m_session->EmitAndWaitTails(op, payload, payloadBytes, tails, tailCount, replyOut,
replyBytes, statusOut, replySizeOut);
}
Port(op, payload, tails, tailCount, replyOut, replyBytes, statusOut, replySizeOut);
return 0;
}
ApplyOnMonolithPort(op, payload, nullptr, 0);
Uint32 MaxReplyBytes() const {
return m_session != nullptr ? m_session->MaxReplyBytes() : kMonolithMaxReplyBytes;
}
void RequireReadPixelsReplyFits(Uint32 width, Uint32 height, Uint32 format, Uint32 type,
Uint64 bytes) const {
if (m_session != nullptr) m_session->RequireReadPixelsReplyFits(width, height, format, type, bytes);
}
// A blob the record names by reference: staged in SEG_STAGE under a session, its own
// address on the port (kMGHostSpanSegNone, the monolith spelling MGPipeHostBytes reads).
MG_Pipe::MGPBlobRef StageBytes(const void* bytes, Uint64 size) {
if (m_session != nullptr) return m_session->Encoder().StageBytes(bytes, size);
return MG_Pipe::MGPBlobRef{static_cast<Uint64>(reinterpret_cast<std::uintptr_t>(bytes)), size,
MG_Pipe::kMGHostSpanSegNone, 0};
}
private:
void Port(MG_Pipe::MGPWireOp op, const void* payload, const Wire::WireTail* tails, Uint32 tailCount,
void* replyOut, Uint64 replyBytes, Int32* statusOut, Uint64* replySizeOut) {
MonolithReplySink replies(replyOut, replyBytes);
const Bool applied = ApplyOnMonolithPort(op, payload, tails, tailCount, &replies);
// A sink that declined without answering is the decoder's DECLINED; an answer it
// posted is the answer, whatever the return.
const Int32 status = replies.Posted()
? replies.Status()
: (applied ? Wire::ReplySink::kStatusOk : Wire::ReplySink::kStatusDeclined);
if (statusOut != nullptr) *statusOut = status;
if (replySizeOut != nullptr) *replySizeOut = replies.Posted() ? replies.Size() : 0;
if (!applied && replyOut == nullptr) {
MGLOG_E_ONCE("MGPipe: the monolith verb port's op %u was declined by the sink; nothing was "
"drawn, dispatched or changed",
static_cast<unsigned>(op));
}
}
ClientSession* m_session;
};
// The channel a ported emitter uses: the port when monolith's record arm installed it
// (only the ported slots reach an emitter there), the active session otherwise.
VerbChannel ChannelFor(const char* slot) {
if (g_monolithVerbPort) return VerbChannel(nullptr);
return VerbChannel(&RequireSession(slot));
}
// =============================================================================
@@ -327,7 +441,7 @@ namespace MobileGL::MG_Remote::Client {
// =============================================================================
void EmitClear(GLbitfield mask) {
ClientSession& session = RequireSession("Clear");
VerbChannel session = ChannelFor("Clear");
BeforeReadOnlyVerb();
if (g_dropClearEmission) {
@@ -359,7 +473,7 @@ namespace MobileGL::MG_Remote::Client {
void EmitF1Clear(const char* slot, MG_Pipe::MGPipeHandle fbo, GLenum buffer,
GLint drawbuffer, Uint8 valueClass, const void* value,
GLfloat depth = 0, GLint stencil = 0) {
auto& session = RequireSession(slot);
VerbChannel session = ChannelFor(slot);
BeforeReadOnlyVerb();
MG_Pipe::MGPClear record{};
record.Fbo = fbo;
@@ -435,7 +549,7 @@ namespace MobileGL::MG_Remote::Client {
}
void EmitF1Copy(GLenum target, GLint level, GLenum format, GLint x, GLint y,
GLsizei width, GLsizei height, GLint xoffset, GLint yoffset, Bool subImage) {
auto& session = RequireSession(subImage ? "CopyTexSubImage2D" : "CopyTexImage2D");
VerbChannel session = ChannelFor(subImage ? "CopyTexSubImage2D" : "CopyTexImage2D");
BeforeReadOnlyVerb();
MG_Pipe::MGPCopyFromFramebuffer record{};
record.Dst = MG_Pipe::MGPipeTextureEmitterInstance().FindTexture(*F1BoundTexture(target));
@@ -458,7 +572,7 @@ namespace MobileGL::MG_Remote::Client {
EmitF1Copy(target, level, 0, x, y, width, height, xoffset, yoffset, true);
}
void EmitGenerateMipmap(GLenum target) {
auto& session = RequireSession("GenerateMipmap");
VerbChannel session = ChannelFor("GenerateMipmap");
BeforeReadOnlyVerb();
const auto& texture = F1BoundTexture(target);
MG_Pipe::MGPMipPlan record{};
@@ -602,7 +716,7 @@ namespace MobileGL::MG_Remote::Client {
const MG_Pipe::MGPDrawRange* ranges, Uint32 numDraws,
const void* clientIndices, Uint64 clientIndexBytes,
const MG_Pipe::MGPDrawIndirect* indirect) {
ClientSession* session = VerbSessionFor(slot);
VerbChannel session = ChannelFor(slot);
PersistentMapTracker::Instance().PushDrawConsumers();
MG_Pipe::MGPipeDrainDeferredDestroys();
// Snapshot before marking THIS draw's potential GPU writes: resolving
@@ -637,7 +751,8 @@ namespace MobileGL::MG_Remote::Client {
tails[1] = {indirect, sizeof(*indirect)};
tailCount = 2;
}
EmitVerbTails(session, MG_Pipe::MGPWireOp::DrawVbo, &info, sizeof(info), tails, tailCount);
session.EmitAndWaitTails(MG_Pipe::MGPWireOp::DrawVbo, &info, sizeof(info), tails,
tailCount, nullptr, 0, nullptr);
}
// ---- the single-draw indexed family: DrawElements, DrawElementsBaseVertex, the two
@@ -849,7 +964,7 @@ namespace MobileGL::MG_Remote::Client {
void EmitBlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0,
GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask,
GLenum filter) {
ClientSession& session = RequireSession("BlitFramebuffer");
VerbChannel session = ChannelFor("BlitFramebuffer");
BeforeReadOnlyVerb();
MG_Pipe::MGPBlit record{};
@@ -877,7 +992,7 @@ namespace MobileGL::MG_Remote::Client {
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");
VerbChannel session = ChannelFor("BlitNamedFramebuffer");
// 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
@@ -1035,7 +1150,7 @@ namespace MobileGL::MG_Remote::Client {
void EmitReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
GLenum type, void* pixels) {
ClientSession& session = RequireSession("ReadPixels");
VerbChannel session = ChannelFor("ReadPixels");
// A bound PACK buffer makes pixels an offset. The owned reply is
// packed on this thread and only the requested rows are uploaded.
@@ -1342,7 +1457,7 @@ namespace MobileGL::MG_Remote::Client {
// same maps twice per dispatch and inflate b1's per-row counters.
void EmitBindImageTexture(GLuint unit, GLuint texture, GLint level, GLboolean layered,
GLint layer, GLenum access, GLenum format) {
ClientSession& session = RequireSession("BindImageTexture");
VerbChannel session = ChannelFor("BindImageTexture");
MG_Pipe::MGPImageBind record{};
// The unit's binding is the frontend's and has just been written by the caller, so
@@ -1375,7 +1490,7 @@ namespace MobileGL::MG_Remote::Client {
// carried both calls before its Fatal precisely so that the package which flipped this
// slot would inherit a call site that was already correct.
void EmitDispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ) {
ClientSession* session = VerbSessionFor("DispatchCompute");
VerbChannel session = ChannelFor("DispatchCompute");
PersistentMapTracker::Instance().PushDrawConsumers();
MarkGpuWritesForDispatch();
@@ -1389,11 +1504,12 @@ namespace MobileGL::MG_Remote::Client {
record.IndirectBuffer = MG_Pipe::kMGPipeNullHandle;
record.IndirectOffset = 0;
record.IsIndirect = 0;
EmitVerbNoTail(session, MG_Pipe::MGPWireOp::LaunchGrid, &record, sizeof(record));
session.EmitAndWait(MG_Pipe::MGPWireOp::LaunchGrid, &record, sizeof(record), nullptr, 0,
nullptr, 0, nullptr);
}
void EmitDispatchComputeIndirect(GLintptr indirect) {
ClientSession* session = VerbSessionFor("DispatchComputeIndirect");
VerbChannel session = ChannelFor("DispatchComputeIndirect");
PersistentMapTracker::Instance().PushDrawConsumers();
MarkGpuWritesForDispatch();
@@ -1414,7 +1530,8 @@ namespace MobileGL::MG_Remote::Client {
MG_Pipe::MGPipeKind::Buffer, bound->GetLifetimeId());
}
}
EmitVerbNoTail(session, MG_Pipe::MGPWireOp::LaunchGrid, &record, sizeof(record));
session.EmitAndWait(MG_Pipe::MGPWireOp::LaunchGrid, &record, sizeof(record), nullptr, 0,
nullptr, 0, nullptr);
}
// glMemoryBarrier / glMemoryBarrierByRegion. The bits cross VERBATIM: the frontend has
@@ -1427,7 +1544,7 @@ namespace MobileGL::MG_Remote::Client {
// No pre-verb hook: a barrier orders memory the GPU already holds. It starts no shader
// and reads no mapped buffer.
void EmitMemoryBarrier(GLbitfield barriers) {
ClientSession& session = RequireSession("MemoryBarrier");
VerbChannel session = ChannelFor("MemoryBarrier");
MG_Pipe::MGPMemoryBarrier record{};
record.Bits = static_cast<Uint32>(barriers);
record.ByRegion = 0;
@@ -1436,7 +1553,7 @@ namespace MobileGL::MG_Remote::Client {
}
void EmitMemoryBarrierByRegion(GLbitfield barriers) {
ClientSession& session = RequireSession("MemoryBarrierByRegion");
VerbChannel session = ChannelFor("MemoryBarrierByRegion");
MG_Pipe::MGPMemoryBarrier record{};
record.Bits = static_cast<Uint32>(barriers);
record.ByRegion = 1;
@@ -1451,7 +1568,7 @@ namespace MobileGL::MG_Remote::Client {
const MG_Backend::CopyImageEndpoint& dst, GLenum dstTarget,
GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) {
ClientSession& session = RequireSession("CopyImageSubData");
VerbChannel session = ChannelFor("CopyImageSubData");
// The GL target selects the texture or renderbuffer handle namespace.
MG_Pipe::MGPCopyRegion record{};
@@ -1493,7 +1610,7 @@ namespace MobileGL::MG_Remote::Client {
// (BackendObject.h:216-221) - the name is the one coordinate all three agree on.
void EmitShaderStorageBlockBinding(GLuint program, const GLchar* storageBlockName,
GLuint storageBlockBinding) {
ClientSession& session = RequireSession("ShaderStorageBlockBinding");
VerbChannel session = ChannelFor("ShaderStorageBlockBinding");
// The backend slot's own first line (DirectGLES.cpp:9201), kept here so a null name
// never becomes a zero-size blob - which rule A forbids spelling at all.
if (storageBlockName == nullptr) return;
@@ -1520,7 +1637,7 @@ namespace MobileGL::MG_Remote::Client {
// decoder re-terminates into a bounded local and refuses a run whose last byte is
// not NUL, so the two sides agree on where the name ends.
const Uint64 nameBytes = static_cast<Uint64>(std::strlen(storageBlockName)) + 1ull;
record.Name = session.Encoder().StageBytes(storageBlockName, nameBytes);
record.Name = session.StageBytes(storageBlockName, nameBytes);
session.EmitAndWait(MG_Pipe::MGPWireOp::SetStorageBlockBinding, &record, sizeof(record),
nullptr, 0, nullptr, 0, nullptr);
}
@@ -1548,7 +1665,7 @@ namespace MobileGL::MG_Remote::Client {
// this state changes at the capture-span boundary, so it rides Begin itself.
void EmitBeginTransformFeedback(GLenum primitiveMode) {
ClientSession& session = RequireSession("BeginTransformFeedback");
VerbChannel session = ChannelFor("BeginTransformFeedback");
BeforeReadOnlyVerb();
MG_Pipe::MGPStreamOutputBegin record{};
@@ -1578,7 +1695,7 @@ namespace MobileGL::MG_Remote::Client {
}
void EmitEndTransformFeedback() {
ClientSession& session = RequireSession("EndTransformFeedback");
VerbChannel session = ChannelFor("EndTransformFeedback");
BeforeReadOnlyVerb();
MG_Pipe::MGPXfbAccounting record{};
@@ -1598,7 +1715,7 @@ namespace MobileGL::MG_Remote::Client {
}
void EmitPauseTransformFeedback() {
ClientSession& session = RequireSession("PauseTransformFeedback");
VerbChannel session = ChannelFor("PauseTransformFeedback");
BeforeReadOnlyVerb();
// Reserved IS zero and the contract says so (MGPStreamOutputControl{Reserved = 0}).
@@ -1611,7 +1728,7 @@ namespace MobileGL::MG_Remote::Client {
}
void EmitResumeTransformFeedback() {
ClientSession& session = RequireSession("ResumeTransformFeedback");
VerbChannel session = ChannelFor("ResumeTransformFeedback");
BeforeReadOnlyVerb();
MG_Pipe::MGPStreamOutputControl record{};
@@ -1621,7 +1738,7 @@ namespace MobileGL::MG_Remote::Client {
}
void EmitBindTransformFeedback(GLuint name) {
ClientSession& session = RequireSession("BindTransformFeedback");
VerbChannel session = ChannelFor("BindTransformFeedback");
BeforeReadOnlyVerb();
MG_Pipe::MGPStreamOutputBind record{};
@@ -1640,7 +1757,7 @@ namespace MobileGL::MG_Remote::Client {
}
void EmitPatchParameteri(GLenum pname, GLint value) {
ClientSession& session = RequireSession("PatchParameteri");
VerbChannel session = ChannelFor("PatchParameteri");
BeforeReadOnlyVerb();
MG_Pipe::MGPPatchParameter record{};
@@ -2230,6 +2347,39 @@ namespace MobileGL::MG_Remote::Client {
table.GL.MultiDrawElementsIndirectCount = &EmitMultiDrawElementsIndirectCount;
table.GL.DispatchCompute = &EmitDispatchCompute;
table.GL.DispatchComputeIndirect = &EmitDispatchComputeIndirect;
// P13 W4b: the texture family - its record arm resolves the texture from the verb's own
// handle (VerbMipRes, VerbCopyTexDst, the copy's two endpoints).
table.GL.GenerateMipmap = &EmitGenerateMipmap;
table.GL.CopyTexImage2D = &EmitCopyTexImage2D;
table.GL.CopyTexSubImage2D = &EmitCopyTexSubImage2D;
table.GL.CopyImageSubData = &EmitCopyImageSubData;
table.GL.ReadPixels = &EmitReadPixels;
table.GL.GetTexImage = &EmitGetTexImage;
table.GL.GetTextureImage = &EmitGetTextureImage;
// P13 W4c: framebuffer verbs name their framebuffers by handle (MGPClear::Fbo, the blit's
// pair), image binds and storage-block bindings carry their objects' handles.
table.GL.Clear = &EmitClear;
table.GL.ClearBufferfi = &EmitClearBufferfi;
table.GL.ClearBufferfv = &EmitClearBufferfv;
table.GL.ClearBufferiv = &EmitClearBufferiv;
table.GL.ClearBufferuiv = &EmitClearBufferuiv;
table.GL.ClearNamedFramebufferfi = &EmitClearNamedFramebufferfi;
table.GL.ClearNamedFramebufferfv = &EmitClearNamedFramebufferfv;
table.GL.ClearNamedFramebufferiv = &EmitClearNamedFramebufferiv;
table.GL.ClearNamedFramebufferuiv = &EmitClearNamedFramebufferuiv;
table.GL.BlitFramebuffer = &EmitBlitFramebuffer;
table.GL.BlitNamedFramebuffer = &EmitBlitNamedFramebuffer;
table.GL.BindImageTexture = &EmitBindImageTexture;
table.GL.ShaderStorageBlockBinding = &EmitShaderStorageBlockBinding;
// The XFB family: the span's capture program and targets ride Begin; the object is named
// by lifetime id. It follows the program family because Begin names the capture program
// by its shader CSO, whose archive monolith records carry from W4c.
table.GL.BeginTransformFeedback = &EmitBeginTransformFeedback;
table.GL.EndTransformFeedback = &EmitEndTransformFeedback;
table.GL.PauseTransformFeedback = &EmitPauseTransformFeedback;
table.GL.ResumeTransformFeedback = &EmitResumeTransformFeedback;
table.GL.BindTransformFeedback = &EmitBindTransformFeedback;
table.GL.DeleteTransformFeedback = &EmitDeleteTransformFeedback;
g_monolithVerbPort = true;
}
+1 -1
View File
@@ -128,7 +128,7 @@
}
void EmitDeleteTransformFeedback(GLuint name) {
auto& session = RequireSession("DeleteTransformFeedback");
VerbChannel session = ChannelFor("DeleteTransformFeedback");
BeforeReadOnlyVerb();
const auto& context = *MG_State::pGLContext;
const MG_Pipe::MGPStreamOutputBind object{name, 0, context.GetTransformFeedbackLifetimeId(name)};
@@ -94,7 +94,7 @@
return;
}
const Uint64 tight = layout.Tight;
ClientSession& session = RequireSession("GetTextureImage");
VerbChannel session = ChannelFor("GetTextureImage");
BeforeReadOnlyVerb();
// A by-name texture need not occur in any currently bound sampler.
MG_Pipe::MGPipeTextureEmitterInstance().DrainTextureSubData(*MG_State::pGLContext);
@@ -703,6 +703,7 @@ namespace MobileGL::MG_Remote::Server {
++m_bufferReadbackWrites;
// The acceptance is the applier's (a dead handle is a counted no-op, a range
// past the store a trip wire), so a refused write ends the landing here.
const MG_Pipe::MGPipeServerOriginatedWriteScope serverWrite;
if (!MG_Pipe::MGPipeApplyResourceSubData(write, bytes, nullptr)) return false;
}
}
+75 -23
View File
@@ -298,6 +298,18 @@ namespace MobileGL::MG_Remote::Server {
explicit StagedTextureStore(Bool copies) : m_copies(copies) {}
Bool CopiesIntoServerStorage() const { return m_copies; }
// P13 W4b: THE ALIASING MODE - monolith's record arm. The store keeps every piece of
// bookkeeping it keeps under split (definitions, extents, the exact covered runs, the
// driver-written and GPU-dirty marks), but a level's bytes are the FRONTEND's level shadow
// in place: a run records the address it arrived at (minus its offset in the level) and
// nothing is copied. The readers then ask exactly what they ask under split - is the
// level covered, what are its bytes - and get the same answers from memory that is
// already there. The two copy-only operations (FollowCopy, AdoptClientBoxes) stay
// copy-only: this store never writes frontend memory, so a copy it cannot follow marks
// the destination driver-written, which sends its readers to the driver.
Bool Aliases() const { return !m_copies && MG_Config::RecordArmAliasesFrontend(); }
// Does this store answer at all: a copying store, or an aliasing one.
Bool Holds() const { return m_copies || Aliases(); }
void SetDeviceLimits(Int maxTextureSize, Int max3DTextureSize, Int maxCubeMapTextureSize,
Int maxArrayTextureLayers, Int maxTextureBufferSize) {
const std::lock_guard<std::mutex> lock(m_mutex);
@@ -330,9 +342,13 @@ namespace MobileGL::MG_Remote::Server {
// reallocates it, which is StagedShadowStore::Adopt's note one level down. Nothing needs
// the base while a level is still being assembled - the readers run at the barrier, after
// the last piece - so callers here ignore it.
// `frontendShadow`: the bytes ARE the frontend's level shadow (a client record's run) - the
// one source an aliasing store may keep by address. Every other adoption (the server's own
// readbacks, regenerations and promotions) is server memory that dies with the call, and is
// copied even by an aliasing store.
const Uint8* AdoptRun(Uint64 key, Uint16 uploadTarget, Uint16 level, const IntVec3& extent,
Uint64 imageOffset, const void* bytes, SizeT byteSize) {
if (!m_copies) return nullptr;
Uint64 imageOffset, const void* bytes, SizeT byteSize, Bool frontendShadow = false) {
if (!Holds()) return nullptr;
const ShadowKey bucketed = Bucketed(key);
const std::lock_guard<std::mutex> lock(m_mutex);
LevelShadow& shadow = m_shadows[bucketed].Levels[PackLevel(uploadTarget, level)];
@@ -348,6 +364,7 @@ namespace MobileGL::MG_Remote::Server {
// belt that keeps B4's rule if that refusal is ever relaxed.
if (!shadow.Defined || shadow.Extent != extent) {
shadow.Bytes.clear();
shadow.Alias = nullptr;
shadow.Covered.clear();
}
#endif
@@ -358,7 +375,7 @@ namespace MobileGL::MG_Remote::Server {
m_driverWritten[bucketed] = true;
}
shadow.GpuDirty = false;
return CopyRunInto(shadow, key, uploadTarget, level, imageOffset, bytes, byteSize);
return CopyRunInto(shadow, key, uploadTarget, level, imageOffset, bytes, byteSize, frontendShadow);
}
// THE WHOLE-LEVEL SPELLING: the run is the level's COMPLETE current content, beginning at
@@ -368,8 +385,8 @@ namespace MobileGL::MG_Remote::Server {
// into; under monolith both do nothing and return nullptr, and the caller
// (Ops_H_TextureSubData) has already returned before reaching either.
const Uint8* Adopt(Uint64 key, Uint16 uploadTarget, Uint16 level, const IntVec3& extent,
const void* bytes, SizeT byteSize) {
if (!m_copies) return nullptr;
const void* bytes, SizeT byteSize, Bool frontendShadow = false) {
if (!Holds()) return nullptr;
const ShadowKey bucketed = Bucketed(key);
const std::lock_guard<std::mutex> lock(m_mutex);
LevelShadow& shadow = m_shadows[bucketed].Levels[PackLevel(uploadTarget, level)];
@@ -378,8 +395,9 @@ namespace MobileGL::MG_Remote::Server {
// P8-E: the caller states these bytes ARE the level, which a followed copy is part of.
shadow.HoldsFollowedCopy = false;
shadow.Bytes.clear();
shadow.Alias = nullptr;
shadow.Covered.clear();
return CopyRunInto(shadow, key, uploadTarget, level, 0, bytes, byteSize);
return CopyRunInto(shadow, key, uploadTarget, level, 0, bytes, byteSize, frontendShadow);
}
// A storage-defining respecify named this level: it EXISTS from here on, at the exact
@@ -408,7 +426,7 @@ namespace MobileGL::MG_Remote::Server {
void NoteLevelDefined(Uint64 key, Uint16 uploadTarget, Uint16 level, const IntVec3& extent,
Uint8 resourceTarget = static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::Tex2D),
Uint32 internalFormat = static_cast<Uint32>(TextureInternalFormat::RGBA8)) {
if (!m_copies) return;
if (!Holds()) return;
const ShadowKey bucketed = Bucketed(key);
const std::lock_guard<std::mutex> lock(m_mutex);
Uint64 byteBound = 0;
@@ -431,6 +449,8 @@ namespace MobileGL::MG_Remote::Server {
if (!shadow.Defined || shadow.Extent != extent || shadow.InternalFormat != internalFormat ||
shadow.ResourceTarget != resourceTarget || shadow.DeclaredByteBound != byteBound) {
shadow.Bytes.clear();
// An aliased level's base is the frontend allocation this redefinition replaced.
shadow.Alias = nullptr;
// The covered set is a statement about THIS coordinate system, so it goes with
// the bytes it described.
shadow.Covered.clear();
@@ -460,7 +480,7 @@ namespace MobileGL::MG_Remote::Server {
// never crossed - and the mark is what answers "dirty region, level has no pending
// upload" without asking the client (§2.2's last row).
void MarkLevelGpuDirty(Uint64 key, Uint16 uploadTarget, Uint16 level, Bool dirty) {
if (!m_copies) return;
if (!Holds()) return;
const ShadowKey bucketed = Bucketed(key);
const std::lock_guard<std::mutex> lock(m_mutex);
m_shadows[bucketed].Levels[PackLevel(uploadTarget, level)].GpuDirty = dirty;
@@ -510,7 +530,7 @@ namespace MobileGL::MG_Remote::Server {
// next run for it is the CLIENT's shadow, which never saw the copy: AdoptClientBoxes
// lands only the texels that run says the client wrote.
void MarkDriverWritten(Uint64 key) {
if (!m_copies) return;
if (!Holds()) return;
const std::lock_guard<std::mutex> lock(m_mutex);
m_driverWritten[Bucketed(key)] = true;
m_any.store(true, std::memory_order_release);
@@ -645,15 +665,10 @@ namespace MobileGL::MG_Remote::Server {
// reads the level image WHOLE (LevelByteSize / the sync's texel base), so a level with
// one piece missing is as unreadable as one with none.
const Uint8* RequireLevelBytes(Uint64 key, Uint16 uploadTarget, Uint16 level, const char* site) const {
if (!m_copies) return nullptr;
if (!Holds()) return nullptr;
const std::lock_guard<std::mutex> lock(m_mutex);
const LevelShadow* shadow = FindLevel(key, uploadTarget, level);
if (shadow != nullptr && shadow->Defined && shadow->DeclaredByteBound != 0 &&
shadow->Bytes.size() == shadow->DeclaredByteBound &&
StagedShadowStore::CoverageHas(shadow->Covered, 0,
static_cast<SizeT>(shadow->DeclaredByteBound))) {
return shadow->Bytes.data();
}
if (shadow != nullptr && LevelComplete(*shadow)) return LevelData(*shadow);
// Verbatim what the MGLOG_F said, through the funnel that publishes it (P7 wave 0).
SessionFail(MGFatalFamily::StageSnapshotTooNarrow,
"MGPipe: Fatal{StageSnapshotTooNarrow, \"%s\"} - the texture sync wants the "
@@ -675,7 +690,7 @@ namespace MobileGL::MG_Remote::Server {
// covered level answers, and nothing is fatal: an uncovered box is the caller's to skip.
const Uint8* CoveredBoxBytes(Uint64 key, Uint16 uploadTarget, Uint16 level, Int x, Int y, Uint32 w, Uint32 h,
SizeT texel, Int* rowTexels) const {
if (!m_copies) return nullptr;
if (!Holds()) return nullptr;
const std::lock_guard<std::mutex> lock(m_mutex);
const LevelShadow* shadow = FindLevel(key, uploadTarget, level);
if (shadow == nullptr || !shadow->Defined || x < 0 || y < 0 || w == 0 || h == 0 ||
@@ -684,11 +699,11 @@ namespace MobileGL::MG_Remote::Server {
for (Uint32 row = 0; row < h; ++row) {
const SizeT begin = TexelOffset(shadow->Extent, x, y + static_cast<Int>(row), 0, texel);
const SizeT end = begin + static_cast<SizeT>(w) * texel;
if (end > shadow->Bytes.size() || !StagedShadowStore::CoverageHas(shadow->Covered, begin, end))
if (end > LevelBytesHeld(*shadow) || !StagedShadowStore::CoverageHas(shadow->Covered, begin, end))
return nullptr;
}
*rowTexels = shadow->Extent.x();
return shadow->Bytes.data();
return LevelData(*shadow);
}
// Diagnostics the sync path and the unit cases read, so that a check can assert WHAT
@@ -697,7 +712,7 @@ namespace MobileGL::MG_Remote::Server {
const std::lock_guard<std::mutex> lock(m_mutex);
const LevelShadow* shadow = FindLevel(key, uploadTarget, level);
if (shadow == nullptr || !shadow->Defined || shadow->DeclaredByteBound == 0 ||
shadow->Bytes.size() != shadow->DeclaredByteBound) return false;
LevelBytesHeld(*shadow) != shadow->DeclaredByteBound) return false;
// THE WHOLE LEVEL, not merely the currently assembled prefix: all runs must cover
// the server-derived bound before a reader can treat the level as complete.
return StagedShadowStore::CoverageHas(
@@ -737,7 +752,7 @@ namespace MobileGL::MG_Remote::Server {
const std::lock_guard<std::mutex> lock(m_mutex);
const LevelShadow* shadow = FindLevel(key, uploadTarget, level);
if (shadow == nullptr || !shadow->Defined) return 0;
return shadow->Bytes.size();
return LevelBytesHeld(*shadow);
}
Uint64 LevelDeclaredByteBound(Uint64 key, Uint16 uploadTarget, Uint16 level) const {
if (!m_any.load(std::memory_order_acquire)) return 0;
@@ -820,7 +835,20 @@ namespace MobileGL::MG_Remote::Server {
Bool GpuDirty = false;
// P8-E: FollowCopy wrote server-side texels into Bytes that the client's shadow lacks.
Bool HoldsFollowedCopy = false;
// P13 W4b, the aliasing mode: the level's first byte in the FRONTEND's level shadow,
// learned from the first run that arrived (its address minus its offset in the level).
// Null until then, and nulled with the coordinate system it belongs to. Bytes stays
// empty in this mode; LevelData / LevelBytesHeld are the two readers' spellings.
const Uint8* Alias = nullptr;
};
static const Uint8* LevelData(const LevelShadow& shadow) {
return shadow.Alias != nullptr ? shadow.Alias : shadow.Bytes.data();
}
// How many bytes of the level image this store can hand out: an aliased level is the
// frontend's whole level allocation, which is exactly its declared bound.
static SizeT LevelBytesHeld(const LevelShadow& shadow) {
return shadow.Alias != nullptr ? static_cast<SizeT>(shadow.DeclaredByteBound) : shadow.Bytes.size();
}
struct TextureShadow {
ska::flat_hash_map<Uint32, LevelShadow> Levels;
};
@@ -872,7 +900,7 @@ namespace MobileGL::MG_Remote::Server {
// Check the run's exclusive end against the extent recorded by the server's prior
// resource_respecify before any cast, coverage edit, or vector growth.
const Uint8* CopyRunInto(LevelShadow& shadow, Uint64 key, Uint16 uploadTarget, Uint16 level,
Uint64 imageOffset, const void* bytes, SizeT byteSize) {
Uint64 imageOffset, const void* bytes, SizeT byteSize, Bool frontendShadow) {
if (bytes != nullptr && byteSize != 0) {
constexpr Uint64 max = std::numeric_limits<Uint64>::max();
const Uint64 runBytes = static_cast<Uint64>(byteSize);
@@ -901,11 +929,35 @@ namespace MobileGL::MG_Remote::Server {
const auto* raw = static_cast<const Uint8*>(bytes);
const SizeT begin = static_cast<SizeT>(imageOffset);
const SizeT needed = static_cast<SizeT>(end);
if (!m_copies && frontendShadow) {
// The aliasing mode: the run IS the frontend level shadow at `begin`, so the
// level's base is one subtraction away and nothing is copied. Every run of one
// level comes from one allocation; a run from another is a level redefined
// without a respecify (or one the server had been holding a copy of), which
// would leave earlier coverage describing other memory - so it starts the
// coverage over.
const Uint8* base = raw - begin;
if (shadow.Alias != base) {
shadow.Covered.clear();
shadow.Bytes.clear();
shadow.Alias = base;
}
StagedShadowStore::CoverageAdd(shadow.Covered, begin, needed);
m_any.store(true, std::memory_order_release);
return shadow.Alias;
}
if (shadow.Alias != nullptr) {
// A server copy landing on an aliased level: the level becomes server-owned, so
// the bytes it already stands for are taken into it first.
shadow.Bytes.assign(shadow.Alias, shadow.Alias + static_cast<SizeT>(shadow.DeclaredByteBound));
shadow.Alias = nullptr;
}
if (shadow.Bytes.size() < needed) shadow.Bytes.resize(needed, 0);
std::memcpy(shadow.Bytes.data() + begin, raw, byteSize);
StagedShadowStore::CoverageAdd(shadow.Covered, begin, needed);
}
m_any.store(true, std::memory_order_release);
if (shadow.Alias != nullptr) return shadow.Alias;
return shadow.Bytes.empty() ? nullptr : shadow.Bytes.data();
}
@@ -927,7 +979,7 @@ namespace MobileGL::MG_Remote::Server {
// that does not divide evenly is a level this cannot address texel by texel.
static Bool LevelComplete(const LevelShadow& shadow) {
return shadow.Defined && shadow.DeclaredByteBound != 0 &&
shadow.Bytes.size() == shadow.DeclaredByteBound &&
LevelBytesHeld(shadow) == shadow.DeclaredByteBound &&
StagedShadowStore::CoverageHas(shadow.Covered, 0, static_cast<SizeT>(shadow.DeclaredByteBound));
}
static SizeT LevelTexelBytes(const LevelShadow& shadow) {
+2 -24
View File
@@ -20,33 +20,11 @@
#include <MG_Impl/GLImpl/Buffer/GL_Buffer.h>
#include <MG_Impl/GetProcAddress.h>
#include <MG_Impl/GLImpl/Getter/GL_Getter.h>
#include <MG_Test/ScopedMonolithFrontendArm.h>
using namespace MobileGL;
namespace {
// P13 W4a: THE MONOLITH FRONTEND ARM, PINNED. The cases that use this assert how the frontend
// object pushes a map's bytes when the backend reads the object itself - the arm W4 retires
// family by family and W6 deletes. On the record arm (every wire, and monolith's from W4a) the
// client's persistent-map tracker owns that push instead, which the split lanes cover. When
// W6 deletes the frontend arm, these cases go with it.
class ScopedMonolithFrontendArm {
public:
ScopedMonolithFrontendArm() {
#if MOBILEGL_BUILD_DISAGGREGATED
m_saved = MG_Config::MonolithTakesRecordArm;
MG_Config::MonolithTakesRecordArm = false;
#endif
}
~ScopedMonolithFrontendArm() {
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Config::MonolithTakesRecordArm = m_saved;
#endif
}
private:
Bool m_saved = false;
};
} // namespace
using MobileGL::MG_Test::ScopedMonolithFrontendArm;
class BufferTest : public ::testing::Test {
protected:
@@ -0,0 +1,49 @@
// MobileGL - MobileGL/MG_Test/ScopedMonolithFrontendArm.h
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
#pragma once
#include <Config.h>
#include <MG_Backend/BackendObjects.h>
namespace MobileGL::MG_Test {
// P13 W4: THE MONOLITH FRONTEND ARM, PINNED FOR ONE CASE. A case that uses this asserts how
// the frontend answers when the backend reads the frontend's objects itself - pushing a map's
// bytes through the object, reading a texture back from the frontend's own level shadow - which
// is the arm W4 retires family by family and W6 deletes. On the record arm (every wire, and
// monolith's from W4) the client tracker and the backend's record readers own those answers,
// and the split and integration lanes cover them. When W6 deletes the frontend arm, the cases
// that use this go with it.
//
// Both halves of the arm: the predicate, and the backend table (the verb port's slots go back
// to the backend's own entry points for the scope).
class ScopedMonolithFrontendArm {
public:
ScopedMonolithFrontendArm() : m_savedTable(MG_Backend::gBackendFunctionsTable) {
#if MOBILEGL_BUILD_DISAGGREGATED
m_saved = MG_Config::MonolithTakesRecordArm;
MG_Config::MonolithTakesRecordArm = false;
if (MG_Config::Transport == MG_Config::TransportMode::Monolith && MG_Backend::pActiveBackendObject) {
MG_Backend::gBackendFunctionsTable = MG_Backend::pActiveBackendObject->GetBackendFunctions();
}
#endif
}
~ScopedMonolithFrontendArm() {
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Config::MonolithTakesRecordArm = m_saved;
#endif
MG_Backend::gBackendFunctionsTable = m_savedTable;
}
ScopedMonolithFrontendArm(const ScopedMonolithFrontendArm&) = delete;
ScopedMonolithFrontendArm& operator=(const ScopedMonolithFrontendArm&) = delete;
private:
MG_Backend::GlobalBackendFunctionsTable m_savedTable;
Bool m_saved = false;
};
} // namespace MobileGL::MG_Test
+4
View File
@@ -13,6 +13,7 @@
#include "Includes.h"
#include "Init.h"
#include <MG_Test/ScopedMonolithFrontendArm.h>
#include <Config.h>
#include <MG_Backend/BackendObjects.h>
#include <MG_Backend/DirectGLES/Managers.h>
@@ -1065,6 +1066,7 @@ TEST_F(TextureTest, EverySamplerScalarPnameConvertsToTheQueriedType) {
// caller's buffer: on glGetTexImage, which has no bufSize argument, that is a heap overflow of
// (shadowTexelSize - clientTexelSize) * texelCount bytes. It must refuse instead.
TEST_F(TextureTest, ShadowReadbackRefusesALayoutItCannotProduceInsteadOfOverrunningTheBuffer) {
const MobileGL::MG_Test::ScopedMonolithFrontendArm frontendArm;
GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture);
ASSERT_NE(texture, 0u);
@@ -1106,6 +1108,7 @@ TEST_F(TextureTest, ShadowReadbackRefusesALayoutItCannotProduceInsteadOfOverrunn
// readback path DOES write it. Refusing keeps the two paths from answering the same call with two
// different destination layouts.
TEST_F(TextureTest, ShadowReadbackRefusesAPackStateItCannotHonour) {
const MobileGL::MG_Test::ScopedMonolithFrontendArm frontendArm;
GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture);
ASSERT_NE(texture, 0u);
@@ -2144,6 +2147,7 @@ TEST_F(TextureTest, GetTextureSubImageSelectsTheCubeFaceZOffsetNames) {
// precisely. glGetTexImage passes no bufSize, which skips the destination-size branch but NOT the
// PBO one, so this is the only spelling where the six-face sizing was reachable at all.
TEST_F(TextureTest, GetTexImageOfOneCubeFacePacksIntoAOneFacePixelPackBuffer) {
const MobileGL::MG_Test::ScopedMonolithFrontendArm frontendArm;
constexpr GLsizei kEdge = 2;
constexpr SizeT kFaceBytes = static_cast<SizeT>(kEdge) * kEdge * 4;
@@ -23,6 +23,7 @@
#include "Includes.h"
#include "Init.h"
#include <MG_Test/ScopedMonolithFrontendArm.h>
#include <MG_Backend/BackendObjects.h>
#include <MG_Impl/GLImpl/Getter/GL_Getter.h>
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
@@ -521,6 +522,7 @@ namespace {
// resolves the face into a Vulkan baseArrayLayer and was always right, which is what made this
// a disagreement between the two backends rather than a uniform wrong answer.)
TEST_F(TextureViewTest, CubeMapViewOfAnArrayReadsTheFaceEachTokenNames) {
const MobileGL::MG_Test::ScopedMonolithFrontendArm frontendArm;
constexpr GLint kLayers = 8;
constexpr GLint kViewMinLayer = 2;