mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Feat] (P13 W4b/W4c): Espryt monolith takes the record arm for textures, readbacks, framebuffers, units, images, programs and XFB
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 ----
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user