diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index 70cd3c78..13a6d626 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -9,6 +9,9 @@ #pragma once #include #include "MG_State/GLState/TextureState/TextureEnum.h" +#if MOBILEGL_BUILD_DISAGGREGATED +#include +#endif namespace MobileGL { namespace MG_State::GLState { @@ -33,6 +36,9 @@ namespace MobileGL { struct CopyImageEndpoint { SharedPtr Texture; SharedPtr Renderbuffer; +#if MOBILEGL_BUILD_DISAGGREGATED + MG_Pipe::MGPipeHandle TextureHandle = MG_Pipe::kMGPipeNullHandle; +#endif Bool IsRenderbuffer() const { return Renderbuffer != nullptr; } Bool Exists() const { return Texture != nullptr || Renderbuffer != nullptr; } diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index f1629990..9d113eb0 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -64,6 +64,14 @@ #endif namespace MobileGL::MG_Backend::DirectGLES { +#if MOBILEGL_BUILD_DISAGGREGATED + // read_pixels writes the server reply scratch; client-side scatter owns any PBO. + static const SharedPtr& SplitReadbackPackBuffer() { + static const SharedPtr none; + return none; + } +#endif + MG_External::EGLFunctionsTable g_EGLFuncs; MG_External::GLESFunctionsTable g_GLESFuncs; MG_External::GLESCapabilities g_GLESCapabilities; @@ -1520,8 +1528,39 @@ namespace MobileGL::MG_Backend::DirectGLES { // gl_NextBuffer is the shape that makes them differ: buffer 0 has stride 0 // and nothing bound, so target 0 describes buffer 1. SizeT bufferIndex = 0; +#if MOBILEGL_BUILD_DISAGGREGATED + MG_Pipe::MGPipeHandle handle = MG_Pipe::kMGPipeNullHandle; +#endif }; + +#if MOBILEGL_BUILD_DISAGGREGATED + struct XfbProgramSource { + SharedPtr Frontend; + SharedPtr Archive; + explicit operator Bool() const { return Frontend || Archive; } + const XfbProgramSource* operator->() const { return this; } + SizeT GetTransformFeedbackBufferCount() const { + return Archive ? Archive->Link.xfbStrides.size() : Frontend->GetTransformFeedbackBufferCount(); + } + Uint32 GetTransformFeedbackStride(Uint32 index) const { + return Archive ? (index < Archive->Link.xfbStrides.size() ? Archive->Link.xfbStrides[index] : 0) + : Frontend->GetTransformFeedbackStride(index); + } + Bool NeedsScatteredTransformFeedbackCapture() const { + return Archive ? Archive->Link.xfbNeedsScatteredCapture : Frontend->NeedsScatteredTransformFeedbackCapture(); + } + Uint32 GetTransformFeedbackPackedStride() const { + return Archive ? Archive->Link.xfbPackedStride : Frontend->GetTransformFeedbackPackedStride(); + } + GLenum GetTransformFeedbackBufferMode() const { + return Archive ? Archive->Link.xfbBufferMode : Frontend->GetTransformFeedbackBufferMode(); + } + const auto& GetTransformFeedbackVaryings() const { + return Archive ? Archive->Link.xfbVaryings : Frontend->GetTransformFeedbackVaryings(); + } + }; +#endif // Per frontend transform feedback object. The default object (name 0) maps to // the driver's default object (id 0) and is always present. struct XfbObjectState { @@ -1536,6 +1575,9 @@ namespace MobileGL::MG_Backend::DirectGLES { // End scatters them into `targets`. Bool scattered = false; SharedPtr scatterProgram; +#if MOBILEGL_BUILD_DISAGGREGATED + SharedPtr scatterArchive; +#endif SizeT scatterCapacityVertices = 0; }; @@ -1612,6 +1654,9 @@ 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) +#endif if (!target.buffer) continue; #if MOBILEGL_BUILD_DISAGGREGATED // P5c: under an active transport the frontend object is client @@ -1619,7 +1664,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // 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) { - const MG_Pipe::MGPipeHandle res = BufferImpl::HandleOfBuffer(target.buffer.get()); + const MG_Pipe::MGPipeHandle res = target.handle; auto* resource = BufferImpl::FindBufferResourceForHandle(res); if (resource == nullptr || resource->persistentMapped) continue; const SizeT size = target.end - target.start; @@ -1724,7 +1769,11 @@ namespace MobileGL::MG_Backend::DirectGLES { // occupies are written, so the holes gl_SkipComponents asks for keep whatever the // application had put there - which is the whole point of the feature. void ScatterCapturedRecords(XfbObjectState& xfb) { +#if MOBILEGL_BUILD_DISAGGREGATED + const XfbProgramSource program{xfb.scatterProgram, xfb.scatterArchive}; +#else const auto& program = xfb.scatterProgram; +#endif if (!program || xfb.targets.empty()) return; if (g_GLESFuncs.glMapBufferRange == nullptr || g_GLESFuncs.glUnmapBuffer == nullptr) return; @@ -1761,6 +1810,9 @@ namespace MobileGL::MG_Backend::DirectGLES { // whole range down once. 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) +#endif if (!target.buffer) continue; // By BUFFER index, not by position in the compacted list - see XfbCaptureTarget. const SizeT stride = program->GetTransformFeedbackStride(static_cast(target.bufferIndex)); @@ -1776,7 +1828,7 @@ namespace MobileGL::MG_Backend::DirectGLES { MG_Pipe::MGPipeHandle splitRes = MG_Pipe::kMGPipeNullHandle; BufferImpl::GLESBufferResource* splitResource = nullptr; if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { - splitRes = BufferImpl::HandleOfBuffer(target.buffer.get()); + splitRes = target.handle; splitResource = BufferImpl::FindBufferResourceForHandle(splitRes); const Uint8* hostBytes = splitResource != nullptr ? splitResource->hostBytes : nullptr; if (splitResource == nullptr || hostBytes == nullptr) { @@ -1868,7 +1920,23 @@ namespace MobileGL::MG_Backend::DirectGLES { // not captured, and opening the span would also subject it to the capture // primitive-mode rule the paused draw is exempt from. if (!xfb.pending || xfb.paused) return; +#if MOBILEGL_BUILD_DISAGGREGATED + XfbProgramSource program; + const MG_Pipe::MGPStreamOutputBegin* span = nullptr; + if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + const auto& state = MG_Pipe::MGPipeApplier(); + const auto found = state.StreamOutputSpans.find(state.BoundStreamOutputLifetimeId); + if (found != state.StreamOutputSpans.end()) { + span = &found->second; + const auto* record = PipeShaderCsoRecordForHandle(span->CaptureProgram); + if (record) program.Archive = record->Archive; + } + } else { + program.Frontend = MGB_CTX->GetTransformFeedbackProgram(); + } +#else const auto& program = MGB_CTX->GetTransformFeedbackProgram(); +#endif if (!program) { // The pending flag is deliberately NOT consumed here. It used to be cleared // before this check, so a single draw that could not see the capture program @@ -1888,6 +1956,20 @@ namespace MobileGL::MG_Backend::DirectGLES { // recording it here keeps End independent of the frontend capture state. 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 (!span || i >= 4) continue; + const auto& range = span->Targets[i]; + if (MG_Pipe::MGPipeHandleIsNull(range.Res) || range.Size == 0) continue; + auto* resource = BufferImpl::EnsureBufferResourceForHandle(nullptr, range.Res); + if (!resource || resource->id == 0) continue; + xfb.targets.push_back({nullptr, resource->id, static_cast(range.Offset), + static_cast(range.Offset + range.Size), i, range.Res}); + BufferImpl::BindBufferRangeCached(GL_TRANSFORM_FEEDBACK_BUFFER, static_cast(i), + resource->id, static_cast(range.Offset), static_cast(range.Size)); + continue; + } +#endif auto& point = MGB_CTX->GetBufferBindingPoint(BufferTarget::TransformFeedback, static_cast(i)); const auto& bufferObject = point.GetBoundObject(); @@ -1901,12 +1983,18 @@ namespace MobileGL::MG_Backend::DirectGLES { xfb.targets.push_back({bufferObject, backendResource->id, start, end, i}); } +#if MOBILEGL_BUILD_DISAGGREGATED + if (MG_Config::Transport == MG_Config::TransportMode::Monolith) +#endif BufferImpl::SyncTransformFeedbackBindingPoints(bufferCount); // A layout with holes or several interleaved buffers is not expressible on ES: // capture gap-free into scratch storage and place the records at End instead. xfb.scattered = false; xfb.scatterProgram.reset(); +#if MOBILEGL_BUILD_DISAGGREGATED + xfb.scatterArchive.reset(); +#endif xfb.scatterCapacityVertices = 0; if (program->NeedsScatteredTransformFeedbackCapture()) { SizeT capacityVertices = ~SizeT(0); @@ -1922,7 +2010,12 @@ namespace MobileGL::MG_Backend::DirectGLES { if (capacityVertices == ~SizeT(0)) capacityVertices = 0; if (BindScatterCaptureBuffer(program->GetTransformFeedbackPackedStride(), capacityVertices)) { xfb.scattered = true; +#if MOBILEGL_BUILD_DISAGGREGATED + xfb.scatterProgram = program.Frontend; + xfb.scatterArchive = program.Archive; +#else xfb.scatterProgram = program; +#endif xfb.scatterCapacityVertices = capacityVertices; } else { // NO SPAN RATHER THAN A SPAN THAT WRITES SOMEWHERE ELSE. The ES program for a @@ -2007,6 +2100,9 @@ namespace MobileGL::MG_Backend::DirectGLES { ScatterCapturedRecords(xfb); xfb.scattered = false; xfb.scatterProgram.reset(); +#if MOBILEGL_BUILD_DISAGGREGATED + xfb.scatterArchive.reset(); +#endif } else { ReadbackCapturedRanges(xfb.targets); } @@ -10757,7 +10853,9 @@ namespace MobileGL::MG_Backend::DirectGLES { ZoneScopedNC(__func__, TRACY_ZONECOLOR_BACKEND); #endif auto unit = MGB_CTX->GetActiveTextureUnit(); +#if !MOBILEGL_BUILD_DISAGGREGATED auto& textureUnit = MGB_CTX->GetTextureUnitObject(unit); +#endif auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); if (!TextureImpl::IsSupportedTextureTarget(textureTarget)) { @@ -10789,6 +10887,9 @@ namespace MobileGL::MG_Backend::DirectGLES { } #endif +#if MOBILEGL_BUILD_DISAGGREGATED + auto& textureUnit = MGB_CTX->GetTextureUnitObject(unit); +#endif const auto& bindingSlot = textureUnit.GetBindingSlot(textureTarget); { const auto& textureObject = bindingSlot.GetBoundObject(); @@ -12297,8 +12398,15 @@ namespace MobileGL::MG_Backend::DirectGLES { // An endpoint that named nothing is the frontend validator's INVALID_VALUE and never // 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) { + out.texture = TextureImpl::SyncTextureToBackendByHandle(endpoint.TextureHandle); + } else +#endif + { if (!endpoint.Texture) return false; out.texture = TextureImpl::SyncTextureObjectToBackend(endpoint.Texture); + } if (!out.texture) return false; const TextureTarget stateTarget = MG_Util::ConvertGLEnumToTextureTarget(appTarget); out.target = TextureImpl::ConvertTextureTargetToBackendGLEnum(stateTarget); @@ -12316,6 +12424,13 @@ namespace MobileGL::MG_Backend::DirectGLES { } static TextureInternalFormat GetCopyImageEndpointFormat(const CopyImageEndpoint& endpoint) { +#if MOBILEGL_BUILD_DISAGGREGATED + if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + const auto* record = PipeTextureRecordForHandle(endpoint.TextureHandle); + return record ? static_cast(record->Desc.InternalFormat) + : TextureInternalFormat::Unknown; + } +#endif if (endpoint.IsRenderbuffer()) return endpoint.Renderbuffer->GetInternalFormat(); return endpoint.Texture ? endpoint.Texture->GetFormat() : TextureInternalFormat::Unknown; } @@ -12596,6 +12711,17 @@ namespace MobileGL::MG_Backend::DirectGLES { // effect by the block's next use. void ShaderStorageBlockBinding(GLuint program, const GLchar* storageBlockName, GLuint storageBlockBinding) { if (!storageBlockName) return; +#if MOBILEGL_BUILD_DISAGGREGATED + if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + const auto handle = MG_Pipe::MGPipeApplier().VerbStorageBlockProgram; + auto* slot = PrgramImpl::g_backendProgramObjects.FindByHandle(handle); + if (slot && *slot && (*slot)->GetBackendProgramId()) { + PrgramImpl::ApplyShaderStorageBlockBinding((*slot)->GetBackendProgramId(), + storageBlockName, storageBlockBinding); + } + return; + } +#endif if (!MGB_CTX->ValidateProgramName(program)) return; auto& programObject = MGB_CTX->GetProgramObject(program); if (!programObject) return; @@ -12806,6 +12932,11 @@ namespace MobileGL::MG_Backend::DirectGLES { const SizeT rowBytes = static_cast(width) * dstPixelBytes; const SizeT packedSize = dstOffset + static_cast(height - 1) * dstRowStride + rowBytes; const auto& pixelPackBufferObject = + +#if MOBILEGL_BUILD_DISAGGREGATED + MG_Config::Transport != MG_Config::TransportMode::Monolith + ? SplitReadbackPackBuffer() : +#endif MGB_CTX->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); const SizeT pboOffset = reinterpret_cast(pixels); if (pixelPackBufferObject && pboOffset + packedSize > pixelPackBufferObject->GetSize()) { @@ -13788,6 +13919,11 @@ namespace MobileGL::MG_Backend::DirectGLES { return true; } const auto& pixelPackBufferObject = + +#if MOBILEGL_BUILD_DISAGGREGATED + MG_Config::Transport != MG_Config::TransportMode::Monolith + ? SplitReadbackPackBuffer() : +#endif MGB_CTX->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); if (!pixelPackBufferObject && pixels == nullptr) { return true; @@ -14025,6 +14161,11 @@ namespace MobileGL::MG_Backend::DirectGLES { MG_Pipe::MGPipeUnmigratedEmulation("get-tex-image-shadow"); #endif const auto& pixelPackBufferObject = + +#if MOBILEGL_BUILD_DISAGGREGATED + MG_Config::Transport != MG_Config::TransportMode::Monolith + ? SplitReadbackPackBuffer() : +#endif MGB_CTX->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); if (!pixelPackBufferObject && pixels == nullptr) { return true; @@ -14335,6 +14476,11 @@ namespace MobileGL::MG_Backend::DirectGLES { // (the driver-level binding used to stay on the user PBO after this call, // capturing subsequent client-memory readbacks into it). auto& pixelPackBufferObject = + +#if MOBILEGL_BUILD_DISAGGREGATED + MG_Config::Transport != MG_Config::TransportMode::Monolith + ? SplitReadbackPackBuffer() : +#endif MGB_CTX->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); Bool usePBO = false; GLuint packBufferId = 0; @@ -14766,6 +14912,11 @@ namespace MobileGL::MG_Backend::DirectGLES { // Handle PBO. The pack binding is scoped: it returns to the resting 0 state // 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 + ? SplitReadbackPackBuffer() : +#endif MGB_CTX->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); Bool usePBO = false; GLuint packBufferId = 0; diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index e6bb6114..0c79d997 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -10287,6 +10287,12 @@ namespace MobileGL::MG_Backend::DirectGLES { // (the three-channel colour-renderable widening). GL answers such a read with 1.0, but // 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) { + const auto* record = MG_Pipe::MGPipeApplier().ReadFramebuffer(); + return record && IsAlphaWidenedColorSurface(record->ReadSurface); + } +#endif const auto* attachmentObject = GetReadColorAttachment(); if (attachmentObject == nullptr) { return false; @@ -10295,6 +10301,13 @@ namespace MobileGL::MG_Backend::DirectGLES { } Bool IsFixedPointFallbackReadAttachment() { +#if MOBILEGL_BUILD_DISAGGREGATED + if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + const auto* record = MG_Pipe::MGPipeApplier().ReadFramebuffer(); + return record && record->ReadSurface.Kind != MG_Pipe::kMGPipeSurfaceKindNone && + IsSnormFormat(static_cast(record->ReadSurface.InternalFormat)); + } +#endif const auto& readFBO = MGB_CTX->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); if (!readFBO) { diff --git a/MobileGL/MG_Backend/DirectGLES/Utils.cpp b/MobileGL/MG_Backend/DirectGLES/Utils.cpp index 5ad96c6c..e2106c7d 100644 --- a/MobileGL/MG_Backend/DirectGLES/Utils.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Utils.cpp @@ -37,6 +37,14 @@ #include namespace MobileGL::MG_Backend::DirectGLES { +#if MOBILEGL_BUILD_DISAGGREGATED + // read_pixels writes the server reply scratch; client-side scatter owns any PBO. + static const SharedPtr& SplitReadbackPackBuffer() { + static const SharedPtr none; + return none; + } +#endif + #if MOBILEGL_BUILD_DISAGGREGATED const FormatCapabilityCache* ActiveBackendFormatCaps() { // Under a live split session the SERVER's private backend is what owns the context on the @@ -2350,7 +2358,12 @@ namespace MobileGL::MG_Backend::DirectGLES { static Bool StoreClientRows(SizeT dstPixelBytes, SizeT swapGroupSize, GLsizei width, GLsizei sliceHeight, GLsizei sliceCount, void* pixels, Bool applyPackImageParams, FillRow&& fillRow) { const auto& pixelPackBufferObject = - MGB_CTX->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); + +#if MOBILEGL_BUILD_DISAGGREGATED + MG_Config::Transport != MG_Config::TransportMode::Monolith + ? SplitReadbackPackBuffer() : +#endif + MGB_CTX->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); // Destination layout is computed from the client-side PACK parameters; only the actual pixel // rows are written so skip regions of the destination stay untouched. diff --git a/MobileGL/MG_Impl/Pipe/PipeFill.cpp b/MobileGL/MG_Impl/Pipe/PipeFill.cpp index 3769e5f6..e9f7931a 100644 --- a/MobileGL/MG_Impl/Pipe/PipeFill.cpp +++ b/MobileGL/MG_Impl/Pipe/PipeFill.cpp @@ -2117,6 +2117,18 @@ namespace MobileGL::MG_Pipe { #endif SizeT PipeInputs::GetBufferBindingPointCount(BufferTarget target) const { +#if MOBILEGL_BUILD_DISAGGREGATED + if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + switch (target) { + case BufferTarget::Uniform: + case BufferTarget::ShaderStorage: + case BufferTarget::AtomicCounter: + case BufferTarget::TransformFeedback: + return kMGPipeMaxBufferBindingPoints; + default: return 0; + } + } +#endif MGP_STICKY_FORWARD_PULL(GetBufferBindingPointCount); const auto* ctx = LiveContext(); return ctx != nullptr ? ctx->GetBufferBindingPointCount(target) : 0; @@ -2135,6 +2147,12 @@ namespace MobileGL::MG_Pipe { } Bool PipeInputs::HasOpenTransformFeedbackSpan(Uint64 lifetimeId) const { +#if MOBILEGL_BUILD_DISAGGREGATED + if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + return lifetimeId != 0 && MGPipeApplier().StreamOutputSpans.count(lifetimeId) != 0; + } +#endif + MGP_STICKY_FORWARD_PULL(HasOpenTransformFeedbackSpan); const auto* ctx = LiveContext(); return ctx != nullptr && ctx->HasOpenTransformFeedbackSpan(lifetimeId); diff --git a/MobileGL/MG_IntegrationTest/Harness/dualblock-expected-fatals.txt b/MobileGL/MG_IntegrationTest/Harness/dualblock-expected-fatals.txt index ad2e54fb..8790eb08 100644 --- a/MobileGL/MG_IntegrationTest/Harness/dualblock-expected-fatals.txt +++ b/MobileGL/MG_IntegrationTest/Harness/dualblock-expected-fatals.txt @@ -34,23 +34,10 @@ # a row advances those entries to their next one. See docs/Disaggregated/notes/p5f/f1-report.md. # --------------------------------------------------------------------------------------------- # -# Espryt's readback path reads the framebuffer binding slot out of the block (the field's -# Espryt half retired at P5e for the UNBARRIERED path only; the barriered readback pull is the -# residual). The largest block of the census by far. -# 132 entries -GetFramebufferBindingSlot@ReadPixels -# The XFB program check at draw setup (FieldOwnership.def: "P3b/P4b (Espryt), P7 (Magma)"). -# 11 entries (the TessellationXfbCapture / XfbCaptureBufferReuse family) -GetTransformFeedbackProgram@DrawArrays -# CopyImageSubData resolves both textures through the sticky forward (def: "P7"). -# 6 entries -GetTextureObject@CopyImageSubData -# Magma's Clear path (the two DirectVulkan.Split.NamedBlit entries) - fm's first rows. -# 2 entries +# P5f fe measured at 881e050e: 181 entries, 175 green, 4 skipped, 2 red. +# Espryt retired all five previous first-blocker pairs, and their downstream reads: +# READ FBO surface classification + reply-only PixelPack; copy destination arm ordering; +# copy-image endpoint handles; capture archive/ranges; storage-block program handle. +# The complete lane reached no new first-blocker pair (see fe-report.md). +# Magma's Clear path remains fm's independently owned pair (two NamedBlit entries). GetFramebufferBindingSlot@Clear -# CopyTexImage2D's texture-unit read (def: "P5e (Espryt unbarriered), P7 (Magma)"). -# 2 entries -GetTextureUnitObject@CopyTexImage2D -# The program-name validation sticky forward at ShaderStorageBlockBinding (def: "P9"). -# 2 entries -ValidateProgramName@ShaderStorageBlockBinding diff --git a/MobileGL/MG_IntegrationTest/Harness/strict-expected-markers.txt b/MobileGL/MG_IntegrationTest/Harness/strict-expected-markers.txt index 09db9ad0..5fb4b401 100644 --- a/MobileGL/MG_IntegrationTest/Harness/strict-expected-markers.txt +++ b/MobileGL/MG_IntegrationTest/Harness/strict-expected-markers.txt @@ -19,34 +19,10 @@ # <(python3 scripts/gen_pipe_field_ownership.py --print-admitted) this-file # and put the census it prints here. # -# --------------------------------------------------------------------------------------------- -# RE-MEASURED ON THE WAVE-3 INTEGRATION TREE (pa + mv + gl merged), 181 lane entries, 181 green, -# ZERO Fatal pairs. The note this block used to carry - that it was measured at gl's base and the -# merge would retire three rows - was discharged exactly as written: the ratchet named -# GetProgramForDraw@DrawArrays as an expected pair that no longer appears, and named -# GetBufferBindingSlot@DrawArrays as one the lane had not seen before. Both are reconciled below. -# GetProgramForDispatch@DispatchCompute never reached this file (pa retired it before it was -# written) and the ordinary GetBoundVertexArray@DrawArrays is gone; the two that remain under that -# name are the client-vertex-array entries, which are escalation-barriered and P8's. -# --------------------------------------------------------------------------------------------- -# -# Admitted by the generated table (disjuncts 1 and 2 of ID-116 / ID-125): -GetBufferBindingSlot@ReadPixels -# NEW IN WAVE 3, and it was named before it could fire: mv's report called -# BoundDrawIndirectBufferId's GetBufferBindingSlot(DrawIndirect) a latent row that "would surface -# as GetBufferBindingSlot@DrawArrays if an indirect tier were ever forced under strict", and then -# gated the five multi-draw tiers, which forced it. Retires in P8 (FieldOwnership.def:78). -GetBufferBindingSlot@DrawArrays -GetFramebufferBindingSlot@ReadPixels -GetProgramObject@ShaderStorageBlockBinding -GetTextureObject@CopyImageSubData -GetTextureUnitObject@CopyTexImage2D -GetTransformFeedbackProgram@DrawArrays -ValidateProgramName@ShaderStorageBlockBinding -# -# Admitted by ESCALATION only (disjunct 3, ID-128): the record was barriered because its payload -# said so - an open transform-feedback span, or a draw carrying client vertex arrays - which no -# table indexed by (field, verb) can see. All three are pulls on the draw verb by entries that -# are NOT the ordinary draw path. -GetBoundVertexArray@DrawArrays -GetBufferBindingPoint@DrawArrays +# P5f fe census: 179 integration-split entries pass, with zero Fatal, ordinary Admitted, +# or ESCALATED pairs. The expected set is therefore empty; either new marker kind is red. +# Eight live pairs retired through server framebuffer/texture/program records, reply scratch, +# and the Begin XFB snapshot. The two already-stale rows GetBoundVertexArray@DrawArrays and +# GetBufferBindingSlot@DrawArrays were independently identified at the f1 baseline (f1-report +# section 8.1). fe-report records every removed pair and its source. P8 client-array tests +# retain their separate lane; an empty census here does not assert that P8 is implemented. diff --git a/MobileGL/MG_Pipe/FieldOwnership.def b/MobileGL/MG_Pipe/FieldOwnership.def index 4c3bc75f..a0b3ce72 100644 --- a/MobileGL/MG_Pipe/FieldOwnership.def +++ b/MobileGL/MG_Pipe/FieldOwnership.def @@ -83,11 +83,11 @@ /* (:2995) is SELF-DECLARED monolith glue (DirectGLES.cpp:2961-2965) while BindCurrentFBO */ \ /* (:4303-4353) is already split-clean - the bind is migrated, the sync is not. */ \ X(GetFramebufferBindingSlot, BARRIER_PULLED, "P5e (Espryt unbarriered), P7 (Magma)", \ - "frontend BindingSlot pointer") \ + "P5f fe: Espryt reads framebuffer records; Magma retains frontend pointer debt") \ X(GetImageTextureBinding, BARRIER_PULLED, "P5e (Espryt unbarriered), P7 (Magma)", \ "frontend ImageTextureBinding base pointer") \ X(GetTextureUnitObject, BARRIER_PULLED, "P5e (Espryt unbarriered), P7 (Magma)", \ - "frontend TextureUnit base pointer; 13 Espryt + 8 Magma sites") \ + "P5f fe: Espryt transport readers retired; Magma retains texture-unit debt") \ /* DirectGLES.cpp:4497, PrepareForDraw, the second unconditional pointer read of every draw. */ \ X(GetProgramForDraw, BARRIER_PULLED, "P5e (Espryt unbarriered), P7 (Magma)", \ "frontend SharedPtr; the record carries a handle") \ @@ -104,7 +104,7 @@ /* case a field census taken from "what the scenario does" rather than from the mask would */ \ /* miss. The family's five VALUE rows rode set_context_values out of this list at P5c rv. */ \ X(GetTransformFeedbackProgram, BARRIER_PULLED, "P3b/P4b (Espryt), P7 (Magma)", \ - "frontend SharedPtr") \ + "P5f fe: Espryt capture uses the Begin snapshot; Magma retains program debt") \ \ /* ---- APPLIER_DERIVED ---- */ \ /* THE FIRST ROW, and it is the one the contract asked P5 to split into two field ids. It is */ \ @@ -144,14 +144,14 @@ /* pair that disagrees. Their poison exemption (generated/PipeFilled.inc:422's sticky arm) */ \ /* is cancelled under split by the server stamp, which zeroes their FilledGen - and 0 loses */ \ /* to nothing, because :419's "never filled" test runs first. */ \ - X(GetBufferBindingPointCount, BARRIER_PULLED, "P7/P13", \ - "an argument-keyed lookup into the frontend's binding-point table") \ + X(GetBufferBindingPointCount, APPLIER_DERIVED, "-", \ + "P5f fe: server constant binding-point capacity, no frontend lookup") \ X(GetProgramObject, BARRIER_PULLED, "P9", \ "hands the backend a frontend SharedPtr keyed by GL name") \ X(GetTextureObject, BARRIER_PULLED, "P7", \ "hands the backend a frontend SharedPtr keyed by GL name") \ - X(HasOpenTransformFeedbackSpan, BARRIER_PULLED, "P7/P9", \ - "a lookup into frontend XFB span state keyed by lifetime id") \ + X(HasOpenTransformFeedbackSpan, APPLIER_DERIVED, "-", \ + "P5f fe: begin/end_stream_output maintain server spans by lifetime id") \ X(ValidateProgramName, BARRIER_PULLED, "P9", \ "a frontend name-table probe; Coverage.def calls it kClientResolved") \ /* P5c ev DELETED the forward under an active transport (CONTRACT-P5C section 4.2): */ \ @@ -176,13 +176,13 @@ // Mechanism is what replaces the forward, which is NOT the same question as which phase // retires the pull. #define MGP_FIELD_OWNERSHIP_FORWARD_LIST(X) \ - X(GetBufferBindingPointCount, BARRIER_PULLED, "P7/P13", \ + X(GetBufferBindingPointCount, APPLIER_DERIVED, "-", \ "a server-side binding-point table") \ X(GetProgramObject, BARRIER_PULLED, "P9", \ "a client-resolved program handle table (ARCHITECTURE 3.2 explicitly-not-ported)") \ X(GetTextureObject, BARRIER_PULLED, "P7", \ "a server-side texture handle table") \ - X(HasOpenTransformFeedbackSpan, BARRIER_PULLED, "P7/P9", \ + X(HasOpenTransformFeedbackSpan, APPLIER_DERIVED, "-", \ "server-side XFB span state") \ X(ValidateProgramName, BARRIER_PULLED, "P9", \ "a client-resolved program name probe") \ diff --git a/MobileGL/MG_Pipe/MGPipeTypes.h b/MobileGL/MG_Pipe/MGPipeTypes.h index bfc8fd15..7f015d37 100644 --- a/MobileGL/MG_Pipe/MGPipeTypes.h +++ b/MobileGL/MG_Pipe/MGPipeTypes.h @@ -1669,8 +1669,12 @@ namespace MobileGL::MG_Pipe { struct MGPStreamOutputBegin { Uint32 PrimitiveMode; Uint32 Pad0; + // P5f: immutable span snapshot; no frontend pointers survive Begin. + MGPipeHandle CaptureProgram; + Uint64 LifetimeId; + MGPBufferRange Targets[4]; }; - MGP_ASSERT_POD(MGPStreamOutputBegin, 8); + MGP_ASSERT_POD(MGPStreamOutputBegin, 120); // end_stream_output carries the accounting the client owns; the scatter itself is a // read-modify-write of the client's shadow and lives there (section 7.2.1). diff --git a/MobileGL/MG_Pipe/PipeApply.cpp b/MobileGL/MG_Pipe/PipeApply.cpp index c10c8153..5190a0b0 100644 --- a/MobileGL/MG_Pipe/PipeApply.cpp +++ b/MobileGL/MG_Pipe/PipeApply.cpp @@ -1379,6 +1379,12 @@ namespace MobileGL::MG_Pipe { g_applier.DrawProgram = kMGPipeNullHandle; g_applier.DispatchProgram = kMGPipeNullHandle; g_applier.BoundShaderCso = kMGPipeNullHandle; +#if MOBILEGL_BUILD_DISAGGREGATED + g_applier.BoundStreamOutputLifetimeId = 0; + // Begin is emitted once per span, not on make-current. Its immutable object + // snapshot survives with the resource/program records until End or release; + // set_context_values restores the returning context's bound lifetime id. +#endif // P5e (sb, CONTRACT-P5E.md §5.6): the three binding-point windows are per-context // WORKING state and go with the rest of it - a returning context has its own // glBindBufferBase history and may not inherit the one this applier was left holding. @@ -1431,6 +1437,10 @@ namespace MobileGL::MG_Pipe { g_applier.DrawProgram = kMGPipeNullHandle; g_applier.DispatchProgram = kMGPipeNullHandle; g_applier.BoundShaderCso = kMGPipeNullHandle; +#if MOBILEGL_BUILD_DISAGGREGATED + g_applier.BoundStreamOutputLifetimeId = 0; + g_applier.StreamOutputSpans.clear(); +#endif // AND "THE WORKING HANDLES THEY COULD NAME" IS ALL OF THEM, NOT JUST THE THREE ABOVE. // Every framebuffer record holds eleven MGPSurface::Res naming texture and renderbuffer // records this function has just dropped - which is why the table itself goes above - @@ -1578,6 +1588,9 @@ namespace MobileGL::MG_Pipe { // memory the rule exists to stop it reading. One store, inert until the caps bit is // published, and it is what makes the two roles' third clause the same clause. g_applier.IsTransformFeedbackActive = values.IsTransformFeedbackActive != 0; +#if MOBILEGL_BUILD_DISAGGREGATED + g_applier.BoundStreamOutputLifetimeId = values.BoundTransformFeedbackLifetimeId; +#endif } void MGPipeApplySetPatchState(const MGPPatchState& patch) { diff --git a/MobileGL/MG_Pipe/PipeApply.h b/MobileGL/MG_Pipe/PipeApply.h index 7d41d405..8e5ce01b 100644 --- a/MobileGL/MG_Pipe/PipeApply.h +++ b/MobileGL/MG_Pipe/PipeApply.h @@ -846,6 +846,9 @@ namespace MobileGL::MG_Pipe { Bool VerbBlitNamedConsumed = false; // copy_framebuffer_to_texture's destination texture (MGPCopyFromFramebuffer::Dst). MGPipeHandle VerbCopyTexDst = kMGPipeNullHandle; + MGPipeHandle VerbStorageBlockProgram = kMGPipeNullHandle; + Uint64 BoundStreamOutputLifetimeId = 0; + UnorderedMap StreamOutputSpans; // generate_mipmap's texture (MGPMipPlan::Res). MGPipeHandle VerbMipRes = kMGPipeNullHandle; // The current indirect draw's command buffer and (for the *Count forms) parameter @@ -870,6 +873,7 @@ namespace MobileGL::MG_Pipe { VerbBlitDrawFbo = kMGPipeNullHandle; VerbBlitNamedConsumed = false; VerbCopyTexDst = kMGPipeNullHandle; + VerbStorageBlockProgram = kMGPipeNullHandle; VerbMipRes = kMGPipeNullHandle; VerbIndirectBuffer = kMGPipeNullHandle; VerbIndirectParameterBuffer = kMGPipeNullHandle; diff --git a/MobileGL/MG_Pipe/PipeFields.def b/MobileGL/MG_Pipe/PipeFields.def index ca5d77c3..d4dd7302 100644 --- a/MobileGL/MG_Pipe/PipeFields.def +++ b/MobileGL/MG_Pipe/PipeFields.def @@ -236,7 +236,7 @@ F(Bits) F(ByRegion) #define MGP_FIELDS_MGPStreamOutputBegin(F) \ - F(PrimitiveMode) + F(PrimitiveMode) F(CaptureProgram) F(LifetimeId) F(Targets) #define MGP_FIELDS_MGPXfbAccounting(F) \ F(CapturedVertices) F(PrimitivesWritten) F(PrimitiveMode) diff --git a/MobileGL/MG_Pipe/generated/PipeFieldOwnership.inc b/MobileGL/MG_Pipe/generated/PipeFieldOwnership.inc index 7d922734..af763391 100644 --- a/MobileGL/MG_Pipe/generated/PipeFieldOwnership.inc +++ b/MobileGL/MG_Pipe/generated/PipeFieldOwnership.inc @@ -42,7 +42,7 @@ inline constexpr MGPipeFieldOwnership kMGPipeFieldOwnership[kMGPipeInputFieldCou MGPipeFieldOwnership::kBarrierPulled, // GetBoundVertexArray MGPipeFieldOwnership::kBarrierPulled, // GetBufferBindingSlot MGPipeFieldOwnership::kBarrierPulled, // GetBufferBindingPoint - MGPipeFieldOwnership::kBarrierPulled, // GetBufferBindingPointCount + MGPipeFieldOwnership::kApplierDerived, // GetBufferBindingPointCount MGPipeFieldOwnership::kRecordSupplied, // GetTouchedBufferBindingPointCount MGPipeFieldOwnership::kRecordSupplied, // GetClampReadColor MGPipeFieldOwnership::kRecordSupplied, // GetClearColor @@ -96,7 +96,7 @@ inline constexpr MGPipeFieldOwnership kMGPipeFieldOwnership[kMGPipeInputFieldCou MGPipeFieldOwnership::kBarrierPulled, // ValidateProgramName MGPipeFieldOwnership::kBarrierPulled, // RecordError MGPipeFieldOwnership::kRecordSupplied, // GetBoundTransformFeedbackLifetimeId - MGPipeFieldOwnership::kBarrierPulled, // HasOpenTransformFeedbackSpan + MGPipeFieldOwnership::kApplierDerived, // HasOpenTransformFeedbackSpan }; // The ROADMAP phase whose row retires the pull. "-" for every class but BARRIER-PULLED. @@ -109,7 +109,7 @@ inline constexpr const char* kMGPipeFieldRetiringPhase[kMGPipeInputFieldCount] = "P5e (Espryt unbarriered), P7 (Magma)", // GetBoundVertexArray "P8 (indirect), P9 (readback), P13 (transfer)", // GetBufferBindingSlot "P5e (Espryt unbarriered), P7 (Magma)", // GetBufferBindingPoint - "P7/P13", // GetBufferBindingPointCount + "-", // GetBufferBindingPointCount "-", // GetTouchedBufferBindingPointCount "-", // GetClampReadColor "-", // GetClearColor @@ -163,7 +163,7 @@ inline constexpr const char* kMGPipeFieldRetiringPhase[kMGPipeInputFieldCount] = "P9", // ValidateProgramName "P9", // RecordError "-", // GetBoundTransformFeedbackLifetimeId - "P7/P9", // HasOpenTransformFeedbackSpan + "-", // HasOpenTransformFeedbackSpan }; // The seven sticky forwards, which are among the 63 above and need a row of their own: @@ -182,19 +182,19 @@ inline constexpr MGPipeInputField kMGPipeFieldOwnershipForwardField[kMGPipeField MGPipeInputField::RecordError, }; inline constexpr MGPipeFieldOwnership kMGPipeFieldOwnershipForward[kMGPipeFieldOwnershipForwardCount] = { - MGPipeFieldOwnership::kBarrierPulled, // GetBufferBindingPointCount + MGPipeFieldOwnership::kApplierDerived, // GetBufferBindingPointCount MGPipeFieldOwnership::kBarrierPulled, // GetProgramObject MGPipeFieldOwnership::kBarrierPulled, // GetTextureObject - MGPipeFieldOwnership::kBarrierPulled, // HasOpenTransformFeedbackSpan + MGPipeFieldOwnership::kApplierDerived, // HasOpenTransformFeedbackSpan MGPipeFieldOwnership::kBarrierPulled, // ValidateProgramName MGPipeFieldOwnership::kFatal, // InvalidateCompileEnv MGPipeFieldOwnership::kBarrierPulled, // RecordError }; inline constexpr const char* kMGPipeFieldOwnershipForwardMechanism[kMGPipeFieldOwnershipForwardCount] = { - "a server-side binding-point table", // GetBufferBindingPointCount, retires in P7/P13 + "a server-side binding-point table", // GetBufferBindingPointCount, retires in - "a client-resolved program handle table (ARCHITECTURE 3.2 explicitly-not-ported)", // GetProgramObject, retires in P9 "a server-side texture handle table", // GetTextureObject, retires in P7 - "server-side XFB span state", // HasOpenTransformFeedbackSpan, retires in P7/P9 + "server-side XFB span state", // HasOpenTransformFeedbackSpan, retires in - "a client-resolved program name probe", // ValidateProgramName, retires in P9 "DELETED under split by P5c ev; the re-arriving caps snapshot IS the invalidation (R-12)", // InvalidateCompileEnv, retires in - "OnGlError, the ordered reverse-channel error post (R-12); its ordering is P9's", // RecordError, retires in P9 @@ -323,7 +323,7 @@ inline constexpr SizeT kMGPipeVerbBoundaryExemptCount = 3; // wait. A verb with no stamp row admits nothing - the server stamps only MGP_VERB_OP_LIST's // ops, so no other verb can be the one a residual pull is reported against. inline constexpr MGPipeFieldMask kMGPipeAdmittedPullMask[kMGPipeVerbCount] = { - {{0x5808402000000140ull, 0x0000000000000000ull}}, // DrawArrays + {{0x1808402000000040ull, 0x0000000000000000ull}}, // DrawArrays {{0x0000000000000000ull, 0x0000000000000000ull}}, // DrawElements {{0x0000000000000000ull, 0x0000000000000000ull}}, // DrawElementsBaseVertex {{0x0000000000000000ull, 0x0000000000000000ull}}, // MultiDrawArrays @@ -343,7 +343,7 @@ inline constexpr MGPipeFieldMask kMGPipeAdmittedPullMask[kMGPipeVerbCount] = { {{0x0000000000000000ull, 0x0000000000000000ull}}, // DrawArraysInstanced {{0x0000000000000000ull, 0x0000000000000000ull}}, // DrawElementsIndirect {{0x0000000000000000ull, 0x0000000000000000ull}}, // DrawArraysIndirect - {{0x5800402000000100ull, 0x0000000000000000ull}}, // Clear + {{0x1800402000000000ull, 0x0000000000000000ull}}, // Clear {{0x0000000000000000ull, 0x0000000000000000ull}}, // ClearBufferfi {{0x0000000000000000ull, 0x0000000000000000ull}}, // ClearBufferfv {{0x0000000000000000ull, 0x0000000000000000ull}}, // ClearBufferuiv @@ -352,27 +352,27 @@ inline constexpr MGPipeFieldMask kMGPipeAdmittedPullMask[kMGPipeVerbCount] = { {{0x0000000000000000ull, 0x0000000000000000ull}}, // ClearNamedFramebufferfi {{0x0000000000000000ull, 0x0000000000000000ull}}, // ClearNamedFramebufferiv {{0x0000000000000000ull, 0x0000000000000000ull}}, // ClearNamedFramebufferuiv - {{0x5800402000000100ull, 0x0000000000000000ull}}, // BlitFramebuffer + {{0x1800402000000000ull, 0x0000000000000000ull}}, // BlitFramebuffer {{0x0000000000000000ull, 0x0000000000000000ull}}, // BlitNamedFramebuffer - {{0x5800c02000300180ull, 0x0000000000000000ull}}, // CopyTexImage2D + {{0x1800c02000300080ull, 0x0000000000000000ull}}, // CopyTexImage2D {{0x0000000000000000ull, 0x0000000000000000ull}}, // CopyTexSubImage2D - {{0x5800c02000300180ull, 0x0000000000000000ull}}, // CopyImageSubData - {{0x5800c02000300180ull, 0x0000000000000000ull}}, // GenerateMipmap - {{0x5800c02000300140ull, 0x0000000000000000ull}}, // ReadPixels + {{0x1800c02000300080ull, 0x0000000000000000ull}}, // CopyImageSubData + {{0x1800c02000300080ull, 0x0000000000000000ull}}, // GenerateMipmap + {{0x1800c02000300040ull, 0x0000000000000000ull}}, // ReadPixels {{0x0000000000000000ull, 0x0000000000000000ull}}, // GetTexImage - {{0x5800c02000300140ull, 0x0000000000000000ull}}, // GetTextureImage - {{0x5800402000000140ull, 0x0000000000000000ull}}, // DispatchCompute + {{0x1800c02000300040ull, 0x0000000000000000ull}}, // GetTextureImage + {{0x1800402000000040ull, 0x0000000000000000ull}}, // DispatchCompute {{0x0000000000000000ull, 0x0000000000000000ull}}, // DispatchComputeIndirect - {{0x5800402000000100ull, 0x0000000000000000ull}}, // MemoryBarrier + {{0x1800402000000000ull, 0x0000000000000000ull}}, // MemoryBarrier {{0x0000000000000000ull, 0x0000000000000000ull}}, // MemoryBarrierByRegion - {{0x5800402000000100ull, 0x0000000000000000ull}}, // BindImageTexture + {{0x1800402000000000ull, 0x0000000000000000ull}}, // BindImageTexture {{0x0000000000000000ull, 0x0000000000000000ull}}, // GetIntegeri_v - {{0x5800c02000300100ull, 0x0000000000000000ull}}, // ShaderStorageBlockBinding - {{0x5800402000000100ull, 0x0000000000000000ull}}, // FenceSync - {{0x5800402000000100ull, 0x0000000000000000ull}}, // ClientWaitSync - {{0x5800402000000100ull, 0x0000000000000000ull}}, // WaitSync - {{0x5800402000000100ull, 0x0000000000000000ull}}, // DeleteSync - {{0x5800402000000100ull, 0x0000000000000000ull}}, // GetSyncStatus + {{0x1800c02000300000ull, 0x0000000000000000ull}}, // ShaderStorageBlockBinding + {{0x1800402000000000ull, 0x0000000000000000ull}}, // FenceSync + {{0x1800402000000000ull, 0x0000000000000000ull}}, // ClientWaitSync + {{0x1800402000000000ull, 0x0000000000000000ull}}, // WaitSync + {{0x1800402000000000ull, 0x0000000000000000ull}}, // DeleteSync + {{0x1800402000000000ull, 0x0000000000000000ull}}, // GetSyncStatus {{0x0000000000000000ull, 0x0000000000000000ull}}, // IsTimerQuerySupported {{0x0000000000000000ull, 0x0000000000000000ull}}, // BeginTimeElapsedQuery {{0x0000000000000000ull, 0x0000000000000000ull}}, // EndTimeElapsedQuery @@ -384,17 +384,17 @@ inline constexpr MGPipeFieldMask kMGPipeAdmittedPullMask[kMGPipeVerbCount] = { {{0x0000000000000000ull, 0x0000000000000000ull}}, // EndOcclusionQuery {{0x0000000000000000ull, 0x0000000000000000ull}}, // BeginXfbPrimitivesQuery {{0x0000000000000000ull, 0x0000000000000000ull}}, // EndXfbPrimitivesQuery - {{0x5800402000000100ull, 0x0000000000000000ull}}, // PatchParameteri - {{0x5808402000000180ull, 0x0000000000000000ull}}, // BeginTransformFeedback - {{0x5808402000000180ull, 0x0000000000000000ull}}, // EndTransformFeedback - {{0x5808402000000180ull, 0x0000000000000000ull}}, // PauseTransformFeedback - {{0x5808402000000180ull, 0x0000000000000000ull}}, // ResumeTransformFeedback - {{0x5808402000000180ull, 0x0000000000000000ull}}, // BindTransformFeedback + {{0x1800402000000000ull, 0x0000000000000000ull}}, // PatchParameteri + {{0x1808402000000080ull, 0x0000000000000000ull}}, // BeginTransformFeedback + {{0x1808402000000080ull, 0x0000000000000000ull}}, // EndTransformFeedback + {{0x1808402000000080ull, 0x0000000000000000ull}}, // PauseTransformFeedback + {{0x1808402000000080ull, 0x0000000000000000ull}}, // ResumeTransformFeedback + {{0x1808402000000080ull, 0x0000000000000000ull}}, // BindTransformFeedback {{0x0000000000000000ull, 0x0000000000000000ull}}, // DeleteTransformFeedback {{0x0000000000000000ull, 0x0000000000000000ull}}, // GetGpuTimestampNs }; -inline constexpr SizeT kMGPipeAdmittedPullPairCount = 174; +inline constexpr SizeT kMGPipeAdmittedPullPairCount = 128; // THE PREDICATE THE STRICT KNOB ASKS (ID-117). A 69 x 128-bit table, so this costs a shift and // a test on the apply thread - the same shape MGPipeFieldMaskHas already has. @@ -428,7 +428,7 @@ static_assert(MGPipeEveryAdmittedPullIsADebtOfItsVerbsClass(), // The class sizes, as constants a test can pin without recounting the table. inline constexpr SizeT kMGPipeRecordSuppliedFieldCount = 41; -inline constexpr SizeT kMGPipeApplierDerivedFieldCount = 4; -inline constexpr SizeT kMGPipeBarrierPulledFieldCount = 15; +inline constexpr SizeT kMGPipeApplierDerivedFieldCount = 6; +inline constexpr SizeT kMGPipeBarrierPulledFieldCount = 13; inline constexpr SizeT kMGPipeFatalFieldCount = 3; static_assert(kMGPipeRecordSuppliedFieldCount + kMGPipeApplierDerivedFieldCount + kMGPipeBarrierPulledFieldCount + kMGPipeFatalFieldCount == kMGPipeInputFieldCount, "the four class sizes do not partition the field set"); diff --git a/MobileGL/MG_Remote/Client/EmitTables.cpp b/MobileGL/MG_Remote/Client/EmitTables.cpp index f422cee8..5a9ab870 100644 --- a/MobileGL/MG_Remote/Client/EmitTables.cpp +++ b/MobileGL/MG_Remote/Client/EmitTables.cpp @@ -62,6 +62,7 @@ #include #include #include +#include #include namespace MobileGL::MG_Remote::Client { @@ -1412,6 +1413,24 @@ namespace MobileGL::MG_Remote::Client { // The GL token verbatim (contract table 0's "GL enums on the wire"): the sink hands // it to the backend slot that takes it, and nothing between here and there reads it. record.PrimitiveMode = static_cast(primitiveMode); + auto& context = *MG_State::pGLContext; + record.LifetimeId = context.GetBoundTransformFeedbackLifetimeId(); + const auto& program = context.GetTransformFeedbackProgram(); + if (program) { + Uint64 bytes = 0; + record.CaptureProgram = MG_Pipe::MGPipeProgramEmitterInstance().AcquireShaderCso(*program, bytes); + } + static_assert(MG_State::GLState::GLContext::MAX_TRANSFORM_FEEDBACK_BUFFERS == 4); + for (Uint i = 0; i < 4; ++i) { + const auto& point = context.GetBufferBindingPoint(BufferTarget::TransformFeedback, i); + const auto& buffer = point.GetBoundObject(); + if (!buffer) continue; + const auto range = point.GetRange(); + const SizeT start = std::min(range.start, buffer->GetSize()); + const SizeT end = std::min(range.end, buffer->GetSize()); + record.Targets[i] = {MG_Pipe::MGPipeResourceTrackerInstance().Find(*buffer), start, + end > start ? end - start : 0}; + } session.EmitAndWait(MG_Pipe::MGPWireOp::BeginStreamOutput, &record, sizeof(record), nullptr, 0, nullptr, 0, nullptr); } diff --git a/MobileGL/MG_Remote/Server/PipeApplier.cpp b/MobileGL/MG_Remote/Server/PipeApplier.cpp index 3c9f557f..28099e0f 100644 --- a/MobileGL/MG_Remote/Server/PipeApplier.cpp +++ b/MobileGL/MG_Remote/Server/PipeApplier.cpp @@ -855,9 +855,9 @@ namespace MobileGL::MG_Remote::Server { // retires them by making the backend take the handles that travel beside the names. MG_Backend::CopyImageEndpoint src{}; MG_Backend::CopyImageEndpoint dst{}; - src.Texture = MG_Pipe::gPipeInputs.GetTextureObject(static_cast(copy.SrcGlName)); - dst.Texture = MG_Pipe::gPipeInputs.GetTextureObject(static_cast(copy.DstGlName)); - if (!src.Exists() || !dst.Exists()) { + src.TextureHandle = copy.Src; + dst.TextureHandle = copy.Dst; + if (MG_Pipe::MGPipeHandleIsNull(copy.Src) || MG_Pipe::MGPipeHandleIsNull(copy.Dst)) { // The monolith's own answer to this, in its own words (DirectGLES.cpp:9067 // "source or destination image failed to sync; declining the copy"): the frontend // validator is what keeps it unreachable and what reports the INVALID_VALUE the @@ -921,6 +921,8 @@ namespace MobileGL::MG_Remote::Server { // Both backends resolve the PROGRAM through the barrier-pulled GetProgramObject(GlName) // / TryGetDirectVulkanProgram - `rsp` again, retired by P9. ShaderCso travels beside the // name for the phase that dispatches on it. + MG_Pipe::MGPipeApplier().ClearVerbHandles(); + MG_Pipe::MGPipeApplier().VerbStorageBlockProgram = binding.ShaderCso; table->GL.ShaderStorageBlockBinding(static_cast(binding.GlName), name, static_cast(binding.Binding)); ++m_storageBlockBindings; @@ -944,6 +946,9 @@ namespace MobileGL::MG_Remote::Server { // crash or as a silent success. Contract §2 t2 says so for PatchParameteri by name. Bool ServerVerbSink::OnBeginStreamOutput(const MG_Pipe::MGPStreamOutputBegin& begin) { + auto& state = MG_Pipe::MGPipeApplier(); + state.BoundStreamOutputLifetimeId = begin.LifetimeId; + state.StreamOutputSpans[begin.LifetimeId] = begin; const MG_Backend::GlobalBackendFunctionsTable* table = Table("begin_stream_output"); if (table == nullptr) return false; if (table->GL.BeginTransformFeedback == nullptr) return false; @@ -961,7 +966,11 @@ namespace MobileGL::MG_Remote::Server { Bool ServerVerbSink::OnEndStreamOutput(const MG_Pipe::MGPXfbAccounting& accounting) { const MG_Backend::GlobalBackendFunctionsTable* table = Table("end_stream_output"); if (table == nullptr) return false; - if (table->GL.EndTransformFeedback == nullptr) return false; + if (table->GL.EndTransformFeedback == nullptr) { + auto& state = MG_Pipe::MGPipeApplier(); + state.StreamOutputSpans.erase(state.BoundStreamOutputLifetimeId); + return false; + } // THE THREE ACCOUNTING FIELDS ARE NOT READ, AND THAT IS THE RULING RATHER THAN AN // OMISSION. glEndTransformFeedback takes no arguments; the numbers are the CLIENT's own // per-span accounting (contract §2 t2's companions row) and the client is where they are @@ -972,6 +981,8 @@ namespace MobileGL::MG_Remote::Server { // server-side scatter is what will need them. (void)accounting; table->GL.EndTransformFeedback(); + auto& state = MG_Pipe::MGPipeApplier(); + state.StreamOutputSpans.erase(state.BoundStreamOutputLifetimeId); ++m_streamOutputSpans; return true; } @@ -1008,6 +1019,7 @@ namespace MobileGL::MG_Remote::Server { // lifetime of its own - it has no reader on this side today, and pretending otherwise // by folding it into the key is exactly the "a GL name is never an identity" confusion // the contract's GlName row is written against. + MG_Pipe::MGPipeApplier().BoundStreamOutputLifetimeId = bind.LifetimeId; table->GL.BindTransformFeedback(static_cast(bind.GlName)); ++m_streamOutputBinds; return true; diff --git a/MobileGL/MG_Test/Pipe/FieldOwnershipTest.cpp b/MobileGL/MG_Test/Pipe/FieldOwnershipTest.cpp index aabd719d..e44e643f 100644 --- a/MobileGL/MG_Test/Pipe/FieldOwnershipTest.cpp +++ b/MobileGL/MG_Test/Pipe/FieldOwnershipTest.cpp @@ -263,10 +263,8 @@ TEST_F(FieldOwnershipTest, TheReducedPathsUnmigratedFieldsAreAllAccountedFor) { // (CONTRACT-P5C.md §7 table 2) - and the only other BARRIER-PULLED rows are six of the // seven sticky forwards (the seventh, InvalidateCompileEnv, is FATAL since P5c ev). const MGPipeInputField pulledSticky[] = { - MGPipeInputField::GetBufferBindingPointCount, MGPipeInputField::GetProgramObject, MGPipeInputField::GetTextureObject, - MGPipeInputField::HasOpenTransformFeedbackSpan, MGPipeInputField::ValidateProgramName, MGPipeInputField::RecordError, }; @@ -281,7 +279,7 @@ TEST_F(FieldOwnershipTest, TheReducedPathsUnmigratedFieldsAreAllAccountedFor) { EXPECT_TRUE(named) << kMGPipeInputFieldNames[i] << " is BARRIER-PULLED and not in the pinned object-class list"; } - EXPECT_EQ(pulledCount, 15u) << "9 non-sticky object rows + 6 sticky forwards"; + EXPECT_EQ(pulledCount, 13u) << "9 non-sticky object rows + 4 sticky forwards"; EXPECT_EQ(pulledCount, kMGPipeBarrierPulledFieldCount); EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetPixelStoreParameters), MGPipeFieldOwnership::kApplierDerived); @@ -705,8 +703,11 @@ TEST_F(FieldOwnershipTest, TheStickyExemptionIsCancelledByTheServerStamp) { MGPipeServerStampVerbBoundary(MGPipeVerb::Clear); for (SizeT i = 0; i < kMGPipeFieldOwnershipForwardCount; ++i) { const MGPipeInputField field = kMGPipeFieldOwnershipForwardField[i]; - EXPECT_FALSE(MGPipeInputFieldIsFresh(gPipeInputs.FilledState(), field)) - << kMGPipeInputFieldNames[Index(field)] << " is still exempt under split"; + // P5f fe retired the scalar forwards to server-owned answers. Their freshness + // comes from that ownership; the remaining client forwards lose the exemption. + const Bool serverOwned = MGPipeFieldOwnershipOf(field) == MGPipeFieldOwnership::kApplierDerived; + EXPECT_EQ(MGPipeInputFieldIsFresh(gPipeInputs.FilledState(), field), serverOwned) + << kMGPipeInputFieldNames[Index(field)] << " has the wrong server-stamp freshness"; } } @@ -899,6 +900,75 @@ namespace { }; } // namespace +TEST_F(FieldOwnershipTest, SplitBindingPointCapacityNeverConsultsTheFrontend) { + RoleSplitArm guard; + guard.Arm(true); + MGPipeServerStampVerbBoundary(MGPipeVerb::DrawArrays); + EXPECT_EQ(gPipeInputs.GetBufferBindingPointCount(BufferTarget::Uniform), 84u); + EXPECT_EQ(gPipeInputs.GetBufferBindingPointCount(BufferTarget::TransformFeedback), 84u); + EXPECT_EQ(gPipeInputs.GetBufferBindingPointCount(BufferTarget::PixelPack), 0u); + EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetBufferBindingPointCount), + MGPipeFieldOwnership::kApplierDerived); + MGPipeServerClearVerbBoundary(); +} + +TEST_F(FieldOwnershipTest, SplitOpenSpansAreOwnedByTheApplierAndSurviveOtherBindings) { + RoleSplitArm guard; + guard.Arm(true); + auto& state = MGPipeApplier(); + state.StreamOutputSpans.clear(); + MGPipeServerStampVerbBoundary(MGPipeVerb::DrawArrays); + EXPECT_FALSE(gPipeInputs.HasOpenTransformFeedbackSpan(0)); + EXPECT_FALSE(gPipeInputs.HasOpenTransformFeedbackSpan(11)); + state.StreamOutputSpans[11] = {}; + state.StreamOutputSpans[12] = {}; + state.BoundStreamOutputLifetimeId = 12; + EXPECT_TRUE(gPipeInputs.HasOpenTransformFeedbackSpan(11)); + EXPECT_TRUE(gPipeInputs.HasOpenTransformFeedbackSpan(12)); + state.StreamOutputSpans.erase(12); + EXPECT_TRUE(gPipeInputs.HasOpenTransformFeedbackSpan(11)); + EXPECT_FALSE(gPipeInputs.HasOpenTransformFeedbackSpan(12)); + EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::HasOpenTransformFeedbackSpan), + MGPipeFieldOwnership::kApplierDerived); + state.StreamOutputSpans.clear(); + state.BoundStreamOutputLifetimeId = 0; + MGPipeServerClearVerbBoundary(); +} + +TEST_F(FieldOwnershipTest, SplitCaptureSnapshotSurvivesMakeCurrentUntilObjectRelease) { + RoleSplitArm guard; + guard.Arm(true); + auto& state = MGPipeApplier(); + state.StreamOutputSpans.clear(); + MGPStreamOutputBegin begin{}; + begin.LifetimeId = 0x100000002ull; + begin.CaptureProgram = {7, 2}; + begin.Targets[3] = {{9, 3}, 16, 64}; + state.StreamOutputSpans[begin.LifetimeId] = begin; + state.BoundStreamOutputLifetimeId = begin.LifetimeId; + + // Make-current resets binding state, not a live object's open capture. The + // returning context emits context values again, but never repeats Begin. + MGPipeApplierReset(); + MGPContextValues returning{}; + returning.BoundTransformFeedbackLifetimeId = begin.LifetimeId; + returning.IsTransformFeedbackActive = 1; + MGPipeApplySetContextValues(returning); + EXPECT_EQ(state.BoundStreamOutputLifetimeId, begin.LifetimeId); + EXPECT_TRUE(gPipeInputs.HasOpenTransformFeedbackSpan(begin.LifetimeId)); + const auto found = state.StreamOutputSpans.find(begin.LifetimeId); + EXPECT_NE(found, state.StreamOutputSpans.end()); + if (found != state.StreamOutputSpans.end()) { + EXPECT_EQ(found->second.CaptureProgram, begin.CaptureProgram); + EXPECT_EQ(found->second.Targets[3].Res, begin.Targets[3].Res); + EXPECT_EQ(found->second.Targets[3].Offset, 16u); + EXPECT_EQ(found->second.Targets[3].Size, 64u); + } + MGPipeApplierReleaseObjectRecords(); + EXPECT_FALSE(gPipeInputs.HasOpenTransformFeedbackSpan(begin.LifetimeId)); + EXPECT_EQ(state.BoundStreamOutputLifetimeId, 0u); +} + TEST_F(FieldOwnershipTest, RoleSplitOffFoldsTheFillSideOntoTheSharedBlock) { EXPECT_FALSE(MGPipeRoleSplitActive()); EXPECT_EQ(&MGPipeClientInputs(), &gPipeInputs); diff --git a/MobileGL/MG_Test/Wire/PipeWireCodecTest.cpp b/MobileGL/MG_Test/Wire/PipeWireCodecTest.cpp index c6d3e92f..d098c440 100644 --- a/MobileGL/MG_Test/Wire/PipeWireCodecTest.cpp +++ b/MobileGL/MG_Test/Wire/PipeWireCodecTest.cpp @@ -1194,6 +1194,9 @@ TEST_F(PipeWireCodecTest, TheFourStreamOutputSpanRowsReachTheSink) { Wire2 wire; MGPStreamOutputBegin begin{}; begin.PrimitiveMode = 4; + begin.CaptureProgram = MakeHandle(95); + begin.LifetimeId = 0x100000002ull; + begin.Targets[3] = {MakeHandle(96), 0x100000010ull, 64}; MGPStreamOutputControl control{}; MGPXfbAccounting end{}; end.CapturedVertices = 300; @@ -1210,6 +1213,11 @@ TEST_F(PipeWireCodecTest, TheFourStreamOutputSpanRowsReachTheSink) { } ASSERT_EQ(wire.Sink().Begins.size(), 1u); EXPECT_EQ(wire.Sink().Begins[0].PrimitiveMode, 4u); + EXPECT_EQ(wire.Sink().Begins[0].CaptureProgram, MakeHandle(95)); + EXPECT_EQ(wire.Sink().Begins[0].LifetimeId, 0x100000002ull); + EXPECT_EQ(wire.Sink().Begins[0].Targets[3].Res, MakeHandle(96)); + EXPECT_EQ(wire.Sink().Begins[0].Targets[3].Offset, 0x100000010ull); + EXPECT_EQ(wire.Sink().Begins[0].Targets[3].Size, 64u); EXPECT_EQ(wire.Sink().Pauses, 1u); EXPECT_EQ(wire.Sink().Resumes, 1u); ASSERT_EQ(wire.Sink().Ends.size(), 1u);