diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index e1ba43f0..f327fe59 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -193,7 +193,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::DataArmIsRecord() && FramebufferSubsystemEnabled(); + return FramebufferSubsystemEnabled(); } [[noreturn]] static void RefuseFramebufferBindingSlotRead() { MG_Pipe::MGPipeInputPoisonFatalForVerb(MG_Pipe::MGPipeInputField::GetFramebufferBindingSlot, @@ -232,23 +232,21 @@ namespace MobileGL::MG_Backend::DirectGLES { // grep and a comment naming it is a hit) and for an MG_State type inside // MGPipeResourceOps, and this is neither. SamplerImpl::BackendSamplerObject* GetRawDepthFetchSampler() { - if (MG_Config::DataArmIsRecord()) { - static UniquePtr native; - static Uint generation = 0; - if (generation != g_backendContextGeneration) { - native.reset(); // old generation's destructor never deletes a successor id - generation = g_backendContextGeneration; - } - if (!native) { - native = MakeUnique(); - const GLuint id = native->GetBackendSamplerId(); - g_GLESFuncs.glSamplerParameteri(id, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - g_GLESFuncs.glSamplerParameteri(id, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - g_GLESFuncs.glSamplerParameteri(id, GL_TEXTURE_COMPARE_MODE, GL_NONE); - g_GLESFuncs.glSamplerParameteri(id, GL_TEXTURE_COMPARE_FUNC, GL_ALWAYS); - } - return native.get(); + static UniquePtr native; + static Uint generation = 0; + if (generation != g_backendContextGeneration) { + native.reset(); // old generation's destructor never deletes a successor id + generation = g_backendContextGeneration; } + if (!native) { + native = MakeUnique(); + const GLuint id = native->GetBackendSamplerId(); + g_GLESFuncs.glSamplerParameteri(id, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + g_GLESFuncs.glSamplerParameteri(id, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + g_GLESFuncs.glSamplerParameteri(id, GL_TEXTURE_COMPARE_MODE, GL_NONE); + g_GLESFuncs.glSamplerParameteri(id, GL_TEXTURE_COMPARE_FUNC, GL_ALWAYS); + } + return native.get(); if (!g_rawDepthFetchSamplerState) { g_rawDepthFetchSamplerState = MakeShared(0); g_rawDepthFetchSamplerState->SetMinFilter(SamplerFilterMode::Nearest); @@ -359,42 +357,40 @@ namespace MobileGL::MG_Backend::DirectGLES { // object's MappedData()/GetSize() (B3) and the client slot allocator (T2) are never // read. Coverage is asserted: commands read from bytes no record staged would be // zero-filled, which is a missing record, not a valid command stream. - if (MG_Config::DataArmIsRecord()) { - const MG_Pipe::MGPipeHandle handle = MG_Pipe::MGPipeApplier().VerbIndirectBuffer; - if (!MG_Pipe::MGPipeHandleIsNull(handle)) { - auto* resource = BufferImpl::FindBufferResourceForHandle(handle); - const Uint8* base = resource ? BufferImpl::SplitHostBytes(*resource) : nullptr; - // P8-C: a store nothing ever staged but a shader wrote (the GPU-culling shape) - // takes its first copy from this server's own GL buffer. A store that HAS a copy - // is not refreshed here: most indirect draws execute natively and read no field - // on the CPU, so ExecuteIndexed/ArraysIndirectCommands refresh the fields only - // when they consume them. - if (base == nullptr && resource != nullptr) { - base = BufferImpl::SplitHostBytesForCpuRead(*resource, handle, "indirect_command_bytes"); - } - const SizeT commandOffset = reinterpret_cast(indirect); - if (base == nullptr) { - MGLOG_E_ONCE("%s skipped: the verb's indirect buffer {%u, %u} has no staged " - "shadow on this side and no GPU write to take one from", - label, handle.Slot, handle.Gen); - return nullptr; - } - if (commandOffset + requiredBytes > BufferImpl::ResourceWidthForHandle(handle)) { - MGLOG_E_ONCE("%s skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range", label); - return nullptr; - } - BufferImpl::RequireStagedCoverage(*resource, base, commandOffset, - commandOffset + requiredBytes, "indirect_command_bytes"); - return base + commandOffset; } - // No buffer was bound: the frontend already raised the GL error; keep the old - // fall-through shape (a null indirect pointer is the same skip it was). - if (!indirect) { - MGLOG_E_ONCE("%s skipped: indirect pointer is null", label); + const MG_Pipe::MGPipeHandle handle = MG_Pipe::MGPipeApplier().VerbIndirectBuffer; + if (!MG_Pipe::MGPipeHandleIsNull(handle)) { + auto* resource = BufferImpl::FindBufferResourceForHandle(handle); + const Uint8* base = resource ? BufferImpl::SplitHostBytes(*resource) : nullptr; + // P8-C: a store nothing ever staged but a shader wrote (the GPU-culling shape) + // takes its first copy from this server's own GL buffer. A store that HAS a copy + // is not refreshed here: most indirect draws execute natively and read no field + // on the CPU, so ExecuteIndexed/ArraysIndirectCommands refresh the fields only + // when they consume them. + if (base == nullptr && resource != nullptr) { + base = BufferImpl::SplitHostBytesForCpuRead(*resource, handle, "indirect_command_bytes"); + } + const SizeT commandOffset = reinterpret_cast(indirect); + if (base == nullptr) { + MGLOG_E_ONCE("%s skipped: the verb's indirect buffer {%u, %u} has no staged " + "shadow on this side and no GPU write to take one from", + label, handle.Slot, handle.Gen); return nullptr; } - MGLOG_E_ONCE("%s skipped: the verb carried no indirect buffer handle", label); + if (commandOffset + requiredBytes > BufferImpl::ResourceWidthForHandle(handle)) { + MGLOG_E_ONCE("%s skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range", label); + return nullptr; + } + BufferImpl::RequireStagedCoverage(*resource, base, commandOffset, + commandOffset + requiredBytes, "indirect_command_bytes"); + return base + commandOffset; } + // No buffer was bound: the frontend already raised the GL error; keep the old + // fall-through shape (a null indirect pointer is the same skip it was). + if (!indirect) { + MGLOG_E_ONCE("%s skipped: indirect pointer is null", label); return nullptr; } + MGLOG_E_ONCE("%s skipped: the verb carried no indirect buffer handle", label); + return nullptr; auto drawBuffer = MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); if (drawBuffer) { drawBuffer->SyncPersistentMappedRange(); @@ -582,7 +578,6 @@ namespace MobileGL::MG_Backend::DirectGLES { // the .def's own "WHO READS IT" section names this function, and now it does. The reason // is the table's and is printed from it. Bool ResolveBufferBindingSubsystemArm() { - if (!MG_Config::DataArmIsRecord()) return false; const Uint64 mask = MG_Config::Features.PipePush; const Bool bitSet = (mask & MG_Pipe::kMGPipeSubsystemBufferBindings) != 0; if (bitSet && @@ -806,7 +801,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // // The applier's ShaderBufferWritableMask survives as the server's record of WHICH // points a shader may write through, which is what P9's narrowing channel will name. - if (MG_Config::DataArmIsRecord()) return; + return; const SizeT bindingPointCnt = MG_Pipe::gPipeInputs.GetTouchedBufferBindingPointCount(BufferTarget::ShaderStorage); for (SizeT i = 0; i < bindingPointCnt; ++i) { @@ -1355,20 +1350,11 @@ namespace MobileGL::MG_Backend::DirectGLES { // P5c (hd, CONTRACT-P5C §3.5): under an active transport the buffer is the verb // record's handle; the frontend binding slot and the client allocator are // never read (T2). The twin's ensure is what SyncBoundBuffer's did. - if (MG_Config::DataArmIsRecord()) { - const MG_Pipe::MGPipeHandle handle = MG_Pipe::MGPipeApplier().VerbIndirectBuffer; - if (!MG_Pipe::MGPipeHandleIsNull(handle)) { - auto* resource = EnsureBufferResourceForHandle(nullptr, handle); - if (resource && resource->id != 0) { - BindBufferId(GL_DRAW_INDIRECT_BUFFER, resource->id); - } - } - } else - { - auto& possibleIndirectBuffer = - MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); - if (possibleIndirectBuffer) { - SyncBoundBuffer(BufferTarget::DrawIndirect, GL_DRAW_INDIRECT_BUFFER); + const MG_Pipe::MGPipeHandle handle = MG_Pipe::MGPipeApplier().VerbIndirectBuffer; + if (!MG_Pipe::MGPipeHandleIsNull(handle)) { + auto* resource = EnsureBufferResourceForHandle(nullptr, handle); + if (resource && resource->id != 0) { + BindBufferId(GL_DRAW_INDIRECT_BUFFER, resource->id); } } } @@ -1421,16 +1407,13 @@ namespace MobileGL::MG_Backend::DirectGLES { if (includeDispatchIndirectBuffer) { // P5c (hd, CONTRACT-P5C §3.5): see the draw-indirect twin above - the verb // record's handle, never the frontend binding slot or the client allocator. - if (MG_Config::DataArmIsRecord()) { - const MG_Pipe::MGPipeHandle handle = MG_Pipe::MGPipeApplier().VerbDispatchIndirectBuffer; - if (!MG_Pipe::MGPipeHandleIsNull(handle)) { - auto* resource = EnsureBufferResourceForHandle(nullptr, handle); - if (resource && resource->id != 0) { - BindBufferId(GL_DISPATCH_INDIRECT_BUFFER, resource->id); - } + const MG_Pipe::MGPipeHandle handle = MG_Pipe::MGPipeApplier().VerbDispatchIndirectBuffer; + if (!MG_Pipe::MGPipeHandleIsNull(handle)) { + auto* resource = EnsureBufferResourceForHandle(nullptr, handle); + if (resource && resource->id != 0) { + BindBufferId(GL_DISPATCH_INDIRECT_BUFFER, resource->id); } - } else - SyncBoundBuffer(BufferTarget::DispatchIndirect, GL_DISPATCH_INDIRECT_BUFFER); + } } } } // namespace BufferImpl @@ -1544,32 +1527,23 @@ namespace MobileGL::MG_Backend::DirectGLES { SizeT ScatterSize = 0; Bool NeedsBind = true; }; - Array g_roleXfbState; - Int g_activeXfbRole = -1; + // P13 W6: one role - the record arm's (the frontend arm's GL-name keyed state went). + Array g_roleXfbState; RoleXfbState& ActiveXfbState() { - const Int role = MG_Config::DataArmIsRecord(); - auto& state = g_roleXfbState[role]; + auto& state = g_roleXfbState[0]; if (state.NativeGeneration != g_backendContextGeneration) { state = {}; state.NativeGeneration = g_backendContextGeneration; } - if (g_activeXfbRole != role) { - state.NeedsBind = true; - g_activeXfbRole = role; - } return state; } XfbObjectState& CurrentXfb() { auto& state = ActiveXfbState(); - 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 - } else if (state.NeedsBind) { - BindTransformFeedback(static_cast(state.Current)); - } + 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 auto& g_currentXfbState = state.Cached; auto& g_xfbObjects = state.Objects; const auto g_currentXfbName = state.Current; @@ -1641,73 +1615,69 @@ namespace MobileGL::MG_Backend::DirectGLES { } if (g_GLESFuncs.glMapBufferRange != nullptr && g_GLESFuncs.glUnmapBuffer != nullptr) { for (const auto& target : targets) { - if (!MG_Config::DataArmIsRecord()) - if (!target.buffer) continue; // P5c: under an active transport the frontend object is client // 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::DataArmIsRecord()) { - const MG_Pipe::MGPipeHandle res = target.handle; - auto* resource = BufferImpl::FindBufferResourceForHandle(res); - if (resource == nullptr) continue; - if (resource->persistentMapped) { - // P8-C: nothing to copy - the capture reaches a coherent map - - // but this side's CPU readers must finish before reading it. - resource->serverGpuWritten = true; - continue; - } - const SizeT size = target.end - target.start; - BufferImpl::BindBufferId(BufferImpl::TempBufferTarget, target.backendId); - void* mapped = g_GLESFuncs.glMapBufferRange(BufferImpl::TempBufferTarget, - static_cast(target.start), - static_cast(size), GL_MAP_READ_BIT); - if (mapped == nullptr) { - MGLOG_E_ONCE("EndTransformFeedback: failed to map backend buffer %u [%zu, %zu) for " - "capture readback (ES error %s); the captured data will NOT be visible to " - "the application", - target.backendId, target.start, target.end, - MG_Util::ConvertGLEnumToString(TakeXfbDriverError()).c_str()); - continue; - } - if (MG_Pipe::gMGPipeCallbacks.OnBufferWriteback != nullptr) { - // ONE EVENT PER SLICE, NOT ONE PER CAPTURE. The bytes travel - // INLINE in a SEG_EVENT record and the ring refuses any record - // above half its capacity outright, so a whole-range post of a - // capture wider than ~128 KiB was Fatal{EventRingOverflow} - the - // SERVER dying on a large buffer. The slicing shape is - // BufferObject.cpp's one ring over: in-order delivery makes the - // last slice's landing imply every earlier one, so the client - // needs nothing new to reassemble them. - // - // INSIDE THE MAPPING, deliberately: the map is the expensive part - // and the slices are reads of one live pointer, so this unmaps - // once below however many events it posts. - const SizeT step = XfbWritebackSliceStep(size); - for (SizeT off = 0; off < size; off += step) { - const SizeT bytes = std::min(step, size - off); - MG_Pipe::gMGPipeCallbacks.OnBufferWriteback( - res, target.start + off, - MG_Pipe::MGPBlobRef{ - reinterpret_cast(static_cast(mapped) + off), - static_cast(bytes), MG_Pipe::kMGHostSpanSegNone, 0}); - } - } else { - MGLOG_E_ONCE("EndTransformFeedback: no reverse channel is installed, so the " - "captured bytes of buffer {%u,%u} cannot reach the client shadow", - res.Slot, res.Gen); - } - // P8-C: the server's own staged copy takes the same bytes, from the - // same mapping, so this side's CPU readers see the capture too. - BufferImpl::StageServerWrittenRange(*resource, res, mapped, target.start, size); - g_GLESFuncs.glUnmapBuffer(BufferImpl::TempBufferTarget); - // Ops_H_Readback's order: writeback, unmap, THEN the serial - // stamp, so the next draw does not re-upload the server's stale - // staged bytes over the capture that just landed. - resource->syncedChangeSerial = BufferImpl::ResourceSerialForHandle(res); - BufferImpl::BumpBufferMutationEpoch(); + const MG_Pipe::MGPipeHandle res = target.handle; + auto* resource = BufferImpl::FindBufferResourceForHandle(res); + if (resource == nullptr) continue; + if (resource->persistentMapped) { + // P8-C: nothing to copy - the capture reaches a coherent map - + // but this side's CPU readers must finish before reading it. + resource->serverGpuWritten = true; continue; } + const SizeT size = target.end - target.start; + BufferImpl::BindBufferId(BufferImpl::TempBufferTarget, target.backendId); + void* mapped = g_GLESFuncs.glMapBufferRange(BufferImpl::TempBufferTarget, + static_cast(target.start), + static_cast(size), GL_MAP_READ_BIT); + if (mapped == nullptr) { + MGLOG_E_ONCE("EndTransformFeedback: failed to map backend buffer %u [%zu, %zu) for " + "capture readback (ES error %s); the captured data will NOT be visible to " + "the application", + target.backendId, target.start, target.end, + MG_Util::ConvertGLEnumToString(TakeXfbDriverError()).c_str()); + continue; + } + if (MG_Pipe::gMGPipeCallbacks.OnBufferWriteback != nullptr) { + // ONE EVENT PER SLICE, NOT ONE PER CAPTURE. The bytes travel + // INLINE in a SEG_EVENT record and the ring refuses any record + // above half its capacity outright, so a whole-range post of a + // capture wider than ~128 KiB was Fatal{EventRingOverflow} - the + // SERVER dying on a large buffer. The slicing shape is + // BufferObject.cpp's one ring over: in-order delivery makes the + // last slice's landing imply every earlier one, so the client + // needs nothing new to reassemble them. + // + // INSIDE THE MAPPING, deliberately: the map is the expensive part + // and the slices are reads of one live pointer, so this unmaps + // once below however many events it posts. + const SizeT step = XfbWritebackSliceStep(size); + for (SizeT off = 0; off < size; off += step) { + const SizeT bytes = std::min(step, size - off); + MG_Pipe::gMGPipeCallbacks.OnBufferWriteback( + res, target.start + off, + MG_Pipe::MGPBlobRef{ + reinterpret_cast(static_cast(mapped) + off), + static_cast(bytes), MG_Pipe::kMGHostSpanSegNone, 0}); + } + } else { + MGLOG_E_ONCE("EndTransformFeedback: no reverse channel is installed, so the " + "captured bytes of buffer {%u,%u} cannot reach the client shadow", + res.Slot, res.Gen); + } + // P8-C: the server's own staged copy takes the same bytes, from the + // same mapping, so this side's CPU readers see the capture too. + BufferImpl::StageServerWrittenRange(*resource, res, mapped, target.start, size); + g_GLESFuncs.glUnmapBuffer(BufferImpl::TempBufferTarget); + // Ops_H_Readback's order: writeback, unmap, THEN the serial + // stamp, so the next draw does not re-upload the server's stale + // staged bytes over the capture that just landed. + resource->syncedChangeSerial = BufferImpl::ResourceSerialForHandle(res); + BufferImpl::BumpBufferMutationEpoch(); + continue; if (target.buffer->IsBackendPersistentMapped()) continue; const SizeT size = target.end - target.start; BufferImpl::BindBufferId(BufferImpl::TempBufferTarget, target.backendId); @@ -1741,9 +1711,8 @@ namespace MobileGL::MG_Backend::DirectGLES { // scratch storage cannot be provided, in which case the caller falls back to the // direct binding (which produces a wrong layout, but is what happened before). Bool BindScatterCaptureBuffer(SizeT packedStride, SizeT capacityVertices) { - const Bool server = MG_Config::DataArmIsRecord(); - auto& g_scatterBufferId = server ? CurrentXfb().serverScatterBuffer : ActiveXfbState().ScatterBuffer; - auto& g_scatterBufferSize = server ? CurrentXfb().serverScatterSize : ActiveXfbState().ScatterSize; + auto& g_scatterBufferId = CurrentXfb().serverScatterBuffer; + auto& g_scatterBufferSize = CurrentXfb().serverScatterSize; if (packedStride == 0 || capacityVertices == 0) return false; if (g_GLESFuncs.glGenBuffers == nullptr || g_GLESFuncs.glBufferData == nullptr) return false; const SizeT required = packedStride * capacityVertices; @@ -1780,8 +1749,7 @@ 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) { - auto& g_scatterBufferId = MG_Config::DataArmIsRecord() - ? xfb.serverScatterBuffer : ActiveXfbState().ScatterBuffer; + auto& g_scatterBufferId = xfb.serverScatterBuffer; const XfbProgramSource program{xfb.scatterProgram, xfb.scatterArchive}; if (!program || xfb.targets.empty()) return; if (g_GLESFuncs.glMapBufferRange == nullptr || g_GLESFuncs.glUnmapBuffer == nullptr) return; @@ -1819,8 +1787,6 @@ 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 (!MG_Config::DataArmIsRecord()) - 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)); if (stride == 0) continue; @@ -1833,50 +1799,47 @@ namespace MobileGL::MG_Backend::DirectGLES { // poke into client memory. MG_Pipe::MGPipeHandle splitRes = MG_Pipe::kMGPipeNullHandle; BufferImpl::GLESBufferResource* splitResource = nullptr; - if (MG_Config::DataArmIsRecord()) { - splitRes = target.handle; - splitResource = BufferImpl::FindBufferResourceForHandle(splitRes); - if (splitResource == nullptr) { - // THE ONLY DISCARD LEFT, and it is the impossible one: this target - // was recorded by BindBufferRange/Base against a handle the applier - // had already minted a twin for, so no twin here means the slot - // table and the capture list disagree about one resource. There is - // no backendId to upload into either, so there is nothing to do but - // name it. - MGLOG_E_ONCE("EndTransformFeedback: a scattered capture target (handle {%u,%u}) has " - "no server twin at all; its capture is discarded", - splitRes.Slot, splitRes.Gen); - continue; + splitRes = target.handle; + splitResource = BufferImpl::FindBufferResourceForHandle(splitRes); + if (splitResource == nullptr) { + // THE ONLY DISCARD LEFT, and it is the impossible one: this target + // was recorded by BindBufferRange/Base against a handle the applier + // had already minted a twin for, so no twin here means the slot + // table and the capture list disagree about one resource. There is + // no backendId to upload into either, so there is nothing to do but + // name it. + MGLOG_E_ONCE("EndTransformFeedback: a scattered capture target (handle {%u,%u}) has " + "no server twin at all; its capture is discarded", + splitRes.Slot, splitRes.Gen); + continue; + } + // P8-C: made current first - a store a shader wrote here is refreshed. + const Uint8* hostBytes = BufferImpl::SplitHostBytesForCpuRead(*splitResource, splitRes, + "xfb_scatter_pre_capture"); + // A NULL SHADOW IS NOT A LOST CAPTURE. It is the ORPHANED store + // (glBufferData(size, NULL) with no resource_subdata since), which + // M-3 declares legal: every byte the application has not staged is + // UNDEFINED by its own declaration, and `staged` is already + // value-initialised to zero - which is byte for byte what the monolith + // arm scatters over when it reads a fresh MappedData(). Skipping the + // Memcpy is therefore the WHOLE of the difference; skipping the TARGET + // threw the varyings away and left the application's buffer holding its + // pre-draw bytes under GL_NO_ERROR, the silent-loss class this file's + // preamble exists to close. The streaming idiom that hits it - orphan, + // partial glBufferSubData, capture into the rest - is ordinary. + if (hostBytes != nullptr) { + // Managers.cpp's pool-reuse ladder guards its whole-store + // MGL_SERVER_STAGED_REQUIRE with exactly this predicate and for + // exactly this reason: for a store whose content the application + // SUPPLIED a coverage gap is a missing record and zero-filling past + // it is silent data loss (Fatal by name); for an orphaned one the + // gap is its own undefined content and the read is legal. + if (BufferImpl::ResourceContentIsDeclared(splitRes)) { + BufferImpl::RequireStagedCoverage(*splitResource, hostBytes, target.start, + target.end, "xfb_scatter_pre_capture"); } - // P8-C: made current first - a store a shader wrote here is refreshed. - const Uint8* hostBytes = BufferImpl::SplitHostBytesForCpuRead(*splitResource, splitRes, - "xfb_scatter_pre_capture"); - // A NULL SHADOW IS NOT A LOST CAPTURE. It is the ORPHANED store - // (glBufferData(size, NULL) with no resource_subdata since), which - // M-3 declares legal: every byte the application has not staged is - // UNDEFINED by its own declaration, and `staged` is already - // value-initialised to zero - which is byte for byte what the monolith - // arm scatters over when it reads a fresh MappedData(). Skipping the - // Memcpy is therefore the WHOLE of the difference; skipping the TARGET - // threw the varyings away and left the application's buffer holding its - // pre-draw bytes under GL_NO_ERROR, the silent-loss class this file's - // preamble exists to close. The streaming idiom that hits it - orphan, - // partial glBufferSubData, capture into the rest - is ordinary. - if (hostBytes != nullptr) { - // Managers.cpp's pool-reuse ladder guards its whole-store - // MGL_SERVER_STAGED_REQUIRE with exactly this predicate and for - // exactly this reason: for a store whose content the application - // SUPPLIED a coverage gap is a missing record and zero-filling past - // it is silent data loss (Fatal by name); for an orphaned one the - // gap is its own undefined content and the read is legal. - if (BufferImpl::ResourceContentIsDeclared(splitRes)) { - BufferImpl::RequireStagedCoverage(*splitResource, hostBytes, target.start, - target.end, "xfb_scatter_pre_capture"); - } - Memcpy(staged.data(), hostBytes + target.start, rangeBytes); - } - } else - Memcpy(staged.data(), target.buffer->MappedData() + target.start, rangeBytes); + Memcpy(staged.data(), hostBytes + target.start, rangeBytes); + } for (const auto& varying : program->GetTransformFeedbackVaryings()) { if (varying.bufferIndex != target.bufferIndex) continue; @@ -1889,39 +1852,36 @@ namespace MobileGL::MG_Backend::DirectGLES { } } - 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 - // than half its capacity outright, so one whole-range post of a - // capture over ~128 KiB was Fatal{EventRingOverflow} and the server - // died. Only the EVENT is cut; the glBufferSubData below stays one - // call over the whole `staged` vector, because the ES store has no - // ring and cutting it would change how many backend calls one - // application call makes. - const SizeT step = XfbWritebackSliceStep(rangeBytes); - for (SizeT off = 0; off < rangeBytes; off += step) { - const SizeT bytes = std::min(step, rangeBytes - off); - MG_Pipe::gMGPipeCallbacks.OnBufferWriteback( - splitRes, target.start + off, - MG_Pipe::MGPBlobRef{reinterpret_cast(staged.data() + off), - static_cast(bytes), - MG_Pipe::kMGHostSpanSegNone, 0}); - } - } else { - MGLOG_E_ONCE("EndTransformFeedback: no reverse channel is installed, so the " - "scattered capture of buffer {%u,%u} cannot reach the client shadow", - splitRes.Slot, splitRes.Gen); + 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 + // than half its capacity outright, so one whole-range post of a + // capture over ~128 KiB was Fatal{EventRingOverflow} and the server + // died. Only the EVENT is cut; the glBufferSubData below stays one + // call over the whole `staged` vector, because the ES store has no + // ring and cutting it would change how many backend calls one + // application call makes. + const SizeT step = XfbWritebackSliceStep(rangeBytes); + for (SizeT off = 0; off < rangeBytes; off += step) { + const SizeT bytes = std::min(step, rangeBytes - off); + MG_Pipe::gMGPipeCallbacks.OnBufferWriteback( + splitRes, target.start + off, + MG_Pipe::MGPBlobRef{reinterpret_cast(staged.data() + off), + static_cast(bytes), + MG_Pipe::kMGHostSpanSegNone, 0}); } - // P8-C: the staged copy takes the composed range too, so this side's CPU - // readers see the capture (before P8 it kept the pre-capture bytes). - BufferImpl::StageServerWrittenRange(*splitResource, splitRes, staged.data(), target.start, - rangeBytes); - // Ops_H_Readback's stamp, for its reason: the GL store now holds the - // capture, and the next draw must not re-upload an older range over it. - splitResource->syncedChangeSerial = BufferImpl::ResourceSerialForHandle(splitRes); - } else - target.buffer->WritebackFromBackend({staged.data(), rangeBytes}, target.start); + } else { + MGLOG_E_ONCE("EndTransformFeedback: no reverse channel is installed, so the " + "scattered capture of buffer {%u,%u} cannot reach the client shadow", + splitRes.Slot, splitRes.Gen); + } + // P8-C: the staged copy takes the composed range too, so this side's CPU + // readers see the capture (before P8 it kept the pre-capture bytes). + BufferImpl::StageServerWrittenRange(*splitResource, splitRes, staged.data(), target.start, + rangeBytes); + // Ops_H_Readback's stamp, for its reason: the GL store now holds the + // capture, and the next draw must not re-upload an older range over it. + splitResource->syncedChangeSerial = BufferImpl::ResourceSerialForHandle(splitRes); // Serial bumped with no backend op (see ReadbackCapturedRanges). BufferImpl::BumpBufferMutationEpoch(); if (g_GLESFuncs.glBufferSubData != nullptr) { @@ -1968,16 +1928,12 @@ namespace MobileGL::MG_Backend::DirectGLES { if (!xfb.pending || xfb.paused) return; XfbProgramSource program; const MG_Pipe::MGPStreamOutputBegin* span = nullptr; - if (MG_Config::DataArmIsRecord()) { - 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 = MG_Pipe::gPipeInputs.GetTransformFeedbackProgram(); + 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; } if (!program) { // The pending flag is deliberately NOT consumed here. It used to be cleared @@ -1998,18 +1954,16 @@ 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 (MG_Config::DataArmIsRecord()) { - 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; - } + 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; auto& point = MG_Pipe::gPipeInputs.GetBufferBindingPoint(BufferTarget::TransformFeedback, static_cast(i)); const auto& bufferObject = point.GetBoundObject(); @@ -2023,8 +1977,6 @@ namespace MobileGL::MG_Backend::DirectGLES { xfb.targets.push_back({bufferObject, backendResource->id, start, end, i}); } - if (!MG_Config::DataArmIsRecord()) - 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. @@ -2159,8 +2111,8 @@ 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::DataArmIsRecord(); - const Uint64 key = server ? MG_Pipe::MGPipeApplier().BoundStreamOutputLifetimeId : name; + const Uint64 key = MG_Pipe::MGPipeApplier().BoundStreamOutputLifetimeId; + (void)name; roleState.NeedsBind = false; g_currentXfbState = nullptr; // name changes; operator[] below may also rehash // The capture buffer bindings are the OBJECT's, not the context's: the bind below @@ -2189,10 +2141,10 @@ 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::DataArmIsRecord(); - const Uint64 key = server ? MG_Pipe::MGPipeApplier().VerbDeleteStreamOutputLifetimeId : name; + const Uint64 key = MG_Pipe::MGPipeApplier().VerbDeleteStreamOutputLifetimeId; + (void)name; // A generated but never-bound name has no native object or lifetime. - if (server && key == 0) return; + if (key == 0) return; const auto it = g_xfbObjects.find(key); if (it == g_xfbObjects.end()) return; if (it->second.esId != 0 && g_GLESFuncs.glDeleteTransformFeedbacks != nullptr) { @@ -2401,7 +2353,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // from the GL thread (MG_Impl/Pipe/OwnedDrawInputs.h), so there is nothing for the // server to stage - and reading gPipeInputs there is the role violation this family's // other monolith glue is guarded against. - if (MG_Config::DataArmIsRecord()) return true; + return true; const auto& currentVAO = MG_Pipe::gPipeInputs.GetBoundVertexArray(); if (!currentVAO) return true; @@ -2455,7 +2407,7 @@ namespace MobileGL::MG_Backend::DirectGLES { static Bool SyncClientSideVertexArraysForIndirectFetch( const Uint8* commandBytes, SizeT commandOffset, GLsizei stride, GLsizei index, Uint8 indexSize, const SharedPtr& commandBuffer) { - if (MG_Config::DataArmIsRecord()) return true; + return true; // P13 W4a (ID-P8-14a): ASK FIRST WHETHER THERE IS ANYTHING TO SNAPSHOT. The command // words below are read only to size a client-memory fetch; a VAO whose attributes all // come from buffers has none, and reading them anyway cost a whole-buffer readback of a @@ -2496,7 +2448,7 @@ namespace MobileGL::MG_Backend::DirectGLES { if (count <= 0 || instanceCount <= 0) return true; // MONOLITH ONLY - see SyncClientSideVertexArraysForFetch; this wrapper's own read of // the bound VAO is the frontend read that must not happen on an apply thread. - if (MG_Config::DataArmIsRecord()) return true; + return true; const auto& currentVAO = MG_Pipe::gPipeInputs.GetBoundVertexArray(); if (!currentVAO) return true; const Uint8 indexSize = static_cast(MG_Util::GetGLTypeSize(type)); @@ -3093,7 +3045,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::DataArmIsRecord() && maxTouchedUnit >= 0) { + if (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 @@ -3231,7 +3183,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::DataArmIsRecord() && SamplerSubsystemEnabled() && + return SamplerSubsystemEnabled() && TextureResourceSubsystemEnabled(); } @@ -4123,26 +4075,24 @@ namespace MobileGL::MG_Backend::DirectGLES { // there is one statement of it, and the unit-tracking bookkeeping below stays out of // reach of an apply thread entirely. void MarkWritableImageBufferTexturesGpuWritten() { - 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, - // and the texture descriptor's backing-store handle. - if (g_writableImageBufferUnitCount == 0) return; - const auto& st = MG_Pipe::MGPipeApplier(); - for (Uint unit = 0; unit < g_writableImageBufferUnits.size() && unit < st.BoundShaderImages.size(); - ++unit) { - if (!g_writableImageBufferUnits[unit]) continue; - const MG_Pipe::MGPImageView& view = st.BoundShaderImages[unit]; - if (!IsWritableImageBufferView(view)) continue; - const auto* record = PipeTextureRecordForHandle(view.Res); - if (record == nullptr) continue; - if (auto* buffer = BufferImpl::FindBufferResourceForHandle(record->Desc.BufferForTexBuffer)) { - buffer->serverGpuWritten = true; - } + // 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, + // and the texture descriptor's backing-store handle. + if (g_writableImageBufferUnitCount == 0) return; + const auto& st = MG_Pipe::MGPipeApplier(); + for (Uint unit = 0; unit < g_writableImageBufferUnits.size() && unit < st.BoundShaderImages.size(); + ++unit) { + if (!g_writableImageBufferUnits[unit]) continue; + const MG_Pipe::MGPImageView& view = st.BoundShaderImages[unit]; + if (!IsWritableImageBufferView(view)) continue; + const auto* record = PipeTextureRecordForHandle(view.Res); + if (record == nullptr) continue; + if (auto* buffer = BufferImpl::FindBufferResourceForHandle(record->Desc.BufferForTexBuffer)) { + buffer->serverGpuWritten = true; } - return; } + return; if (g_writableImageBufferUnitCount == 0) return; for (Uint unit = 0; unit < g_writableImageBufferUnits.size(); ++unit) { if (!g_writableImageBufferUnits[unit]) continue; @@ -4157,7 +4107,7 @@ namespace MobileGL::MG_Backend::DirectGLES { BufferImpl::MarkBufferGpuWritten(bufferObject); // P13 W4a: image units stay on this arm until W4c, but monolith's buffer readers // are already the record arm's (SplitHostBytesForCpuRead), and they ask the TWIN. - if (MG_Config::DataArmIsRecord() && bufferObject) { + if (bufferObject) { if (auto* twin = BufferImpl::FindBufferResourceForHandle( BufferImpl::HandleOfBuffer(bufferObject.get()))) { twin->serverGpuWritten = true; @@ -4234,20 +4184,18 @@ namespace MobileGL::MG_Backend::DirectGLES { std::max(st.ShaderImageStart + st.ShaderImageCount, static_cast(g_imageUnitHighWaterMark)), static_cast(unitCount)); - 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 - // record has described yet. Its zero-initialized Unit - // is not a binding identity: treating it as one unbinds - // real unit 0 after binding the current program's images. - // Array position identifies both retained and empty slots. - auto view = st.BoundShaderImages[unit]; - view.Unit = unit; - SyncImageTextureBinding(view); - } - return; + for (Uint32 unit = 0; unit < end; ++unit) { + if (unit >= st.BoundShaderImages.size()) break; + // The union can include a slot no set_shader_images + // record has described yet. Its zero-initialized Unit + // is not a binding identity: treating it as one unbinds + // real unit 0 after binding the current program's images. + // Array position identifies both retained and empty slots. + auto view = st.BoundShaderImages[unit]; + view.Unit = unit; + SyncImageTextureBinding(view); } + return; for (Uint32 unit = 0; unit < end; ++unit) { SyncImageTextureBinding(unit); } @@ -4259,7 +4207,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::DataArmIsRecord()) return; + return; for (Uint unit = 0; unit < unitCount; ++unit) { SyncImageTextureBinding(unit); } @@ -4326,22 +4274,20 @@ namespace MobileGL::MG_Backend::DirectGLES { // ES context is rebuilt, which no sweep does. void SyncImageTextureBindingsForDraw(const DrawTextureSyncKeys& keys) { if (g_imageUnitHighWaterMark == 0) return; - if (MG_Config::DataArmIsRecord()) { - const auto& st = MG_Pipe::MGPipeApplier(); - if (g_imageSweepValid && g_imageSweepShaderImagesSerial == st.ShaderImagesSerial && - g_imageSweepTextureShutterSerial == st.TextureShutterSerial && - g_imageSweepContextSerial == st.ContextSerial && - g_imageSweepBackendContextGeneration == g_backendContextGeneration) { - return; - } - SyncImageTextureBindings(); - g_imageSweepShaderImagesSerial = st.ShaderImagesSerial; - g_imageSweepTextureShutterSerial = st.TextureShutterSerial; - g_imageSweepContextSerial = st.ContextSerial; - g_imageSweepBackendContextGeneration = g_backendContextGeneration; - g_imageSweepValid = true; + const auto& st = MG_Pipe::MGPipeApplier(); + if (g_imageSweepValid && g_imageSweepShaderImagesSerial == st.ShaderImagesSerial && + g_imageSweepTextureShutterSerial == st.TextureShutterSerial && + g_imageSweepContextSerial == st.ContextSerial && + g_imageSweepBackendContextGeneration == g_backendContextGeneration) { return; } + SyncImageTextureBindings(); + g_imageSweepShaderImagesSerial = st.ShaderImagesSerial; + g_imageSweepTextureShutterSerial = st.TextureShutterSerial; + g_imageSweepContextSerial = st.ContextSerial; + g_imageSweepBackendContextGeneration = g_backendContextGeneration; + g_imageSweepValid = true; + return; if (g_imageSweepValid && g_imageSweepContextId == keys.contextId && g_imageSweepSamplingGeneration == keys.samplingGeneration && g_imageSweepBackendContextGeneration == g_backendContextGeneration) { @@ -4475,7 +4421,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::DataArmIsRecord()) return true; + return true; return record.Fbo == g_backendFramebufferObjects.HandleOf(bound.get()); } @@ -6758,15 +6704,13 @@ 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::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 " - "apply thread; CONTRACT-P5E.md §4.2 deletes the identity sampler family under a " - "transport and §5.3 makes the missing entry unrepresentable", - static_cast(unit)); - std::abort(); - } + 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 " + "apply thread; CONTRACT-P5E.md §4.2 deletes the identity sampler family under a " + "transport and §5.3 makes the missing entry unrepresentable", + static_cast(unit)); + std::abort(); // P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named debt inside the // scope - the refusal above is what retires it under a transport. const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry; @@ -6866,15 +6810,13 @@ 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::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 " - "reads client memory and is refused under an active transport " - "(CONTRACT-P5E.md §5.3)", - static_cast(maxTouchedUnit)); - std::abort(); - } + 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 " + "reads client memory and is refused under an active transport " + "(CONTRACT-P5E.md §5.3)", + static_cast(maxTouchedUnit)); + std::abort(); MGLOG_E_ONCE("A sampler record does not describe the binding it names: a draw touches " "units 0..%d and no bind_sampler_states has ever been applied while the " "sampler subsystem bit is set; running the pre-handle sampler walk.", @@ -8107,14 +8049,9 @@ namespace MobileGL::MG_Backend::DirectGLES { // resource resolves from it by handle - the frontend binding slot and the client slot // allocator are never read (T2). const MG_Pipe::MGPipeHandle verbBufferHandle = - MG_Config::DataArmIsRecord() - ? MG_Pipe::MGPipeApplier().VerbIndirectBuffer - : MG_Pipe::kMGPipeNullHandle; + MG_Pipe::MGPipeApplier().VerbIndirectBuffer; const Bool useNative = - MG_Config::DataArmIsRecord() - ? !MG_Pipe::MGPipeHandleIsNull(verbBufferHandle) && SupportsNativeIndirectDraws() - : - drawIndirectBuffer != nullptr && SupportsNativeIndirectDraws(); + !MG_Pipe::MGPipeHandleIsNull(verbBufferHandle) && SupportsNativeIndirectDraws(); commandBytes = CurrentIndirectCommandBytes(verbBufferHandle, commandBytes, commandOffset, useNative, /*indexed=*/true); if (useNative) { @@ -8124,10 +8061,7 @@ namespace MobileGL::MG_Backend::DirectGLES { const Int paramsBinding = backendProgram ? backendProgram->GetIndirectParamsBinding() : -1; if (paramsBinding >= 0) { auto* resource = - MG_Config::DataArmIsRecord() - ? BufferImpl::EnsureBufferResourceForHandle(nullptr, verbBufferHandle) - : - BufferImpl::EnsureBufferResource(drawIndirectBuffer); + BufferImpl::EnsureBufferResourceForHandle(nullptr, verbBufferHandle); if (resource && resource->id != 0) { BufferImpl::BindBufferBaseCached(GL_SHADER_STORAGE_BUFFER, static_cast(paramsBinding), resource->id); @@ -8201,14 +8135,9 @@ namespace MobileGL::MG_Backend::DirectGLES { // P5c (hd, CONTRACT-P5C §3.5): see ExecuteIndexedIndirectCommands - the verb's handle, // never the frontend binding slot or the client allocator. const MG_Pipe::MGPipeHandle verbBufferHandle = - MG_Config::DataArmIsRecord() - ? MG_Pipe::MGPipeApplier().VerbIndirectBuffer - : MG_Pipe::kMGPipeNullHandle; + MG_Pipe::MGPipeApplier().VerbIndirectBuffer; const Bool useNative = - MG_Config::DataArmIsRecord() - ? !MG_Pipe::MGPipeHandleIsNull(verbBufferHandle) && SupportsNativeIndirectDraws() - : - drawIndirectBuffer != nullptr && SupportsNativeIndirectDraws(); + !MG_Pipe::MGPipeHandleIsNull(verbBufferHandle) && SupportsNativeIndirectDraws(); commandBytes = CurrentIndirectCommandBytes(verbBufferHandle, commandBytes, commandOffset, useNative, /*indexed=*/false); if (useNative) { @@ -8216,10 +8145,7 @@ namespace MobileGL::MG_Backend::DirectGLES { const Int paramsBinding = backendProgram ? backendProgram->GetIndirectParamsBinding() : -1; if (paramsBinding >= 0) { auto* resource = - MG_Config::DataArmIsRecord() - ? BufferImpl::EnsureBufferResourceForHandle(nullptr, verbBufferHandle) - : - BufferImpl::EnsureBufferResource(drawIndirectBuffer); + BufferImpl::EnsureBufferResourceForHandle(nullptr, verbBufferHandle); if (resource && resource->id != 0) { BufferImpl::BindBufferBaseCached(GL_SHADER_STORAGE_BUFFER, static_cast(paramsBinding), resource->id); @@ -8623,7 +8549,6 @@ namespace MobileGL::MG_Backend::DirectGLES { // from the apply thread aborts by name instead of pulling GetBoundVertexArray behind // the family's back. void RefuseElementArrayBufferFromTheFrontend(const char* entry) { - if (!MG_Config::DataArmIsRecord()) return; if (!BufferImpl::VertexInputReadsRecords()) return; MGLOG_F("MGPipe: Fatal{RoleViolation, \"MGPipeSlots\"} - %s read the frontend VAO's " "element-array slot with a live transport. The index buffer of this family is " @@ -8720,101 +8645,63 @@ namespace MobileGL::MG_Backend::DirectGLES { // carried client indices (no index buffer bound) takes the client-pointer arm below, // `indices` already resolved through the server's segment resolver. Monolith takes // the original body, verbatim. - if (MG_Config::DataArmIsRecord()) { - const auto& applierState = MG_Pipe::MGPipeApplier(); - const MG_Pipe::MGPipeHandle elements = applierState.IndexBuffer.Res; - serverElementBinding = 0; - if (!MG_Pipe::MGPipeHandleIsNull(elements)) { - auto* elementResource = BufferImpl::FindBufferResourceForHandle(elements); - // The WHOLE buffer is rewritten, not just this draw's range, so that every - // index keeps its position: an indirect draw's firstIndex lives in GPU memory - // and cannot be adjusted from here. It is an ELEMENT index, so it survives - // widening unchanged. - const SizeT sizeBytes = BufferImpl::ResourceWidthForHandle(elements); - if (sizeBytes < sourceIndexSize) { - return; // Nothing to restart on; let the driver see the draw unchanged. - } - if (sizeBytes > kMaxRestartRewriteBytes) { - MGLOG_E_ONCE("Draw skipped: GL_PRIMITIVE_RESTART with restart index %u needs the %zu-byte element " - "array buffer rewritten every draw, which is past the %zu-byte ceiling. Use " - "GL_PRIMITIVE_RESTART_FIXED_INDEX, or set glPrimitiveRestartIndex to the all-ones " - "value of the index type.", - applicationRestartIndex, sizeBytes, kMaxRestartRewriteBytes); - m_valid = false; - return; - } - // The staged shadow is the source of truth for CPU reads on this side: a - // persistent map's blocks were consumed by the applier before this verb ran, and - // (P8-C) a store a shader wrote on this side is read back from this server's own - // GL buffer first - no writeback ever reaches the server's copy. - const Uint8* hostBytes = - elementResource - ? BufferImpl::SplitHostBytesForCpuRead(*elementResource, elements, - "primitive_restart_substitution") - : nullptr; - if (hostBytes == nullptr) { - MGLOG_E_ONCE("Draw skipped: GL_PRIMITIVE_RESTART with restart index %u needs a CPU-readable copy of " - "the bound element array buffer and none is available.", - applicationRestartIndex); - m_valid = false; - return; - } - // The M-3 rule for a whole-store reader (Managers.cpp:3238): for a store whose - // content the application SUPPLIED a coverage gap is a missing record and is - // Fatal by name; for one it ORPHANED the gap is its own undefined content and - // reading the shadow's zero-fill is exactly what the monolith arm's - // MappedData() answers. - const MG_Pipe::MGPipeResourceRecord* elementRecord = nullptr; - if (elements.Slot < applierState.Resources.size()) { - const auto& candidate = applierState.Resources[elements.Slot]; - if (candidate.Live && candidate.Gen == elements.Gen) elementRecord = &candidate; - } - if (elementRecord != nullptr && elementRecord->Desc.HasDefinedContent != 0) { - BufferImpl::RequireStagedCoverage(*elementResource, hostBytes, 0, sizeBytes, - "primitive_restart_substitution"); - } - hasElementBuffer = true; - serverElementBinding = static_cast(elementResource->id); - source = hostBytes; - indexCount = sizeBytes / sourceIndexSize; - sourceByteOffset = reinterpret_cast(indices); + const auto& applierState = MG_Pipe::MGPipeApplier(); + const MG_Pipe::MGPipeHandle elements = applierState.IndexBuffer.Res; + serverElementBinding = 0; + if (!MG_Pipe::MGPipeHandleIsNull(elements)) { + auto* elementResource = BufferImpl::FindBufferResourceForHandle(elements); + // The WHOLE buffer is rewritten, not just this draw's range, so that every + // index keeps its position: an indirect draw's firstIndex lives in GPU memory + // and cannot be adjusted from here. It is an ELEMENT index, so it survives + // widening unchanged. + const SizeT sizeBytes = BufferImpl::ResourceWidthForHandle(elements); + if (sizeBytes < sourceIndexSize) { + return; // Nothing to restart on; let the driver see the draw unchanged. } - } else - { - const auto& indexBuffer = BoundElementArrayBuffer(); - if (indexBuffer) { - hasElementBuffer = true; - // The WHOLE buffer is rewritten, not just this draw's range, so that every index - // keeps its position: an indirect draw's firstIndex lives in GPU memory and cannot be - // adjusted from here. It is an ELEMENT index, so it survives widening unchanged. - const SizeT sizeBytes = indexBuffer->GetSize(); - if (sizeBytes < sourceIndexSize) { - return; // Nothing to restart on; let the driver see the draw unchanged. - } - if (sizeBytes > kMaxRestartRewriteBytes) { - MGLOG_E_ONCE("Draw skipped: GL_PRIMITIVE_RESTART with restart index %u needs the %zu-byte element " - "array buffer rewritten every draw, which is past the %zu-byte ceiling. Use " - "GL_PRIMITIVE_RESTART_FIXED_INDEX, or set glPrimitiveRestartIndex to the all-ones " - "value of the index type.", - applicationRestartIndex, sizeBytes, kMaxRestartRewriteBytes); - m_valid = false; - return; - } - // The shadow is the source of truth for CPU reads, but a persistent map or a - // shader write may have moved past it since the last sync. - indexBuffer->SyncPersistentMappedRange(); - indexBuffer->SyncGpuWrites(); - source = indexBuffer->MappedData(); - if (source == nullptr) { - MGLOG_E_ONCE("Draw skipped: GL_PRIMITIVE_RESTART with restart index %u needs a CPU-readable copy of " - "the bound element array buffer and none is available.", - applicationRestartIndex); - m_valid = false; - return; - } - indexCount = sizeBytes / sourceIndexSize; - sourceByteOffset = reinterpret_cast(indices); + if (sizeBytes > kMaxRestartRewriteBytes) { + MGLOG_E_ONCE("Draw skipped: GL_PRIMITIVE_RESTART with restart index %u needs the %zu-byte element " + "array buffer rewritten every draw, which is past the %zu-byte ceiling. Use " + "GL_PRIMITIVE_RESTART_FIXED_INDEX, or set glPrimitiveRestartIndex to the all-ones " + "value of the index type.", + applicationRestartIndex, sizeBytes, kMaxRestartRewriteBytes); + m_valid = false; + return; } + // The staged shadow is the source of truth for CPU reads on this side: a + // persistent map's blocks were consumed by the applier before this verb ran, and + // (P8-C) a store a shader wrote on this side is read back from this server's own + // GL buffer first - no writeback ever reaches the server's copy. + const Uint8* hostBytes = + elementResource + ? BufferImpl::SplitHostBytesForCpuRead(*elementResource, elements, + "primitive_restart_substitution") + : nullptr; + if (hostBytes == nullptr) { + MGLOG_E_ONCE("Draw skipped: GL_PRIMITIVE_RESTART with restart index %u needs a CPU-readable copy of " + "the bound element array buffer and none is available.", + applicationRestartIndex); + m_valid = false; + return; + } + // The M-3 rule for a whole-store reader (Managers.cpp:3238): for a store whose + // content the application SUPPLIED a coverage gap is a missing record and is + // Fatal by name; for one it ORPHANED the gap is its own undefined content and + // reading the shadow's zero-fill is exactly what the monolith arm's + // MappedData() answers. + const MG_Pipe::MGPipeResourceRecord* elementRecord = nullptr; + if (elements.Slot < applierState.Resources.size()) { + const auto& candidate = applierState.Resources[elements.Slot]; + if (candidate.Live && candidate.Gen == elements.Gen) elementRecord = &candidate; + } + if (elementRecord != nullptr && elementRecord->Desc.HasDefinedContent != 0) { + BufferImpl::RequireStagedCoverage(*elementResource, hostBytes, 0, sizeBytes, + "primitive_restart_substitution"); + } + hasElementBuffer = true; + serverElementBinding = static_cast(elementResource->id); + source = hostBytes; + indexCount = sizeBytes / sourceIndexSize; + sourceByteOffset = reinterpret_cast(indices); } if (!hasElementBuffer) { // No element array buffer: `indices` is a client pointer, so only the draw's own @@ -9015,10 +8902,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // P5c (hd, CONTRACT-P5C §3.5): with an active transport the frontend binding slot // is never read - the Execute* helpers resolve the buffer from the verb record's // handle (T2). Monolith reads the slot as it always did. - MG_Config::DataArmIsRecord() - ? SharedPtr(nullptr) - : - MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); + SharedPtr(nullptr); ExecuteIndexedIndirectCommands(mode, type, indexSize, commandBytes, reinterpret_cast(indirect), drawIndirectBuffer, drawcount, stride, "MultiDrawElementsIndirect"); } @@ -9054,57 +8938,55 @@ namespace MobileGL::MG_Backend::DirectGLES { // reads from the SERVER's staged shadow - the frontend binding slots, the frontend // accessors (B3) and the client allocator (T2) are never read. Monolith takes the // original body below, verbatim. - if (MG_Config::DataArmIsRecord()) { - const auto& applierState = MG_Pipe::MGPipeApplier(); - const SizeT commandOffset = reinterpret_cast(indirect); - const SizeT commandBytes = commandOffset + static_cast(stride) * static_cast(maxdrawcount - 1) + - sizeof(DrawElementsIndirectCommand); - auto* drawResource = BufferImpl::FindBufferResourceForHandle(applierState.VerbIndirectBuffer); - auto* paramResource = - BufferImpl::FindBufferResourceForHandle(applierState.VerbIndirectParameterBuffer); - // P8-C: the count is CONSUMED here, so the parameter buffer is made current first - // (a store a shader wrote on this side is read back); the command buffer only gets a - // first copy when it has none - its fields are made current by the executor, and - // only when it consumes them (CurrentIndirectCommandBytes). - const Uint8* const parameterBytes = - paramResource ? BufferImpl::SplitHostBytesForCpuRead(*paramResource, - applierState.VerbIndirectParameterBuffer, - "indirect_count_parameter") - : nullptr; - const Uint8* drawBytes = drawResource ? BufferImpl::SplitHostBytes(*drawResource) : nullptr; - if (drawBytes == nullptr && drawResource != nullptr) { - drawBytes = BufferImpl::SplitHostBytesForCpuRead(*drawResource, applierState.VerbIndirectBuffer, - "indirect_count_commands"); - } - if (commandBytes > BufferImpl::ResourceWidthForHandle(applierState.VerbIndirectBuffer)) { - MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range"); - return; - } - if (drawcount < 0 || static_cast(drawcount) + sizeof(Uint32) > - BufferImpl::ResourceWidthForHandle(applierState.VerbIndirectParameterBuffer)) { - MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: invalid GL_PARAMETER_BUFFER binding or range"); - return; - } - // No staged shadow means no count to read, not a wrong one - the monolith body's - // own rule for a buffer with no CPU shadow. - if (parameterBytes == nullptr || drawBytes == nullptr) { - MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: CPU fallback cannot read the parameter or " - "draw-indirect buffer"); - return; - } - BufferImpl::RequireStagedCoverage(*drawResource, drawBytes, commandOffset, commandBytes, - "multidraw_elements_indirect_count_commands"); - BufferImpl::RequireStagedCoverage(*paramResource, parameterBytes, static_cast(drawcount), - static_cast(drawcount) + sizeof(Uint32), - "multidraw_elements_indirect_count_parameter"); - Uint32 actualDrawCount = 0; - std::memcpy(&actualDrawCount, parameterBytes + drawcount, sizeof(actualDrawCount)); - actualDrawCount = std::min(actualDrawCount, static_cast(maxdrawcount)); - ExecuteIndexedIndirectCommands(mode, type, indexSize, drawBytes + commandOffset, commandOffset, - nullptr, static_cast(actualDrawCount), stride, - "MultiDrawElementsIndirectCount"); + const auto& applierState = MG_Pipe::MGPipeApplier(); + const SizeT commandOffset = reinterpret_cast(indirect); + const SizeT commandBytes = commandOffset + static_cast(stride) * static_cast(maxdrawcount - 1) + + sizeof(DrawElementsIndirectCommand); + auto* drawResource = BufferImpl::FindBufferResourceForHandle(applierState.VerbIndirectBuffer); + auto* paramResource = + BufferImpl::FindBufferResourceForHandle(applierState.VerbIndirectParameterBuffer); + // P8-C: the count is CONSUMED here, so the parameter buffer is made current first + // (a store a shader wrote on this side is read back); the command buffer only gets a + // first copy when it has none - its fields are made current by the executor, and + // only when it consumes them (CurrentIndirectCommandBytes). + const Uint8* const parameterBytes = + paramResource ? BufferImpl::SplitHostBytesForCpuRead(*paramResource, + applierState.VerbIndirectParameterBuffer, + "indirect_count_parameter") + : nullptr; + const Uint8* drawBytes = drawResource ? BufferImpl::SplitHostBytes(*drawResource) : nullptr; + if (drawBytes == nullptr && drawResource != nullptr) { + drawBytes = BufferImpl::SplitHostBytesForCpuRead(*drawResource, applierState.VerbIndirectBuffer, + "indirect_count_commands"); + } + if (commandBytes > BufferImpl::ResourceWidthForHandle(applierState.VerbIndirectBuffer)) { + MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range"); return; } + if (drawcount < 0 || static_cast(drawcount) + sizeof(Uint32) > + BufferImpl::ResourceWidthForHandle(applierState.VerbIndirectParameterBuffer)) { + MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: invalid GL_PARAMETER_BUFFER binding or range"); + return; + } + // No staged shadow means no count to read, not a wrong one - the monolith body's + // own rule for a buffer with no CPU shadow. + if (parameterBytes == nullptr || drawBytes == nullptr) { + MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: CPU fallback cannot read the parameter or " + "draw-indirect buffer"); + return; + } + BufferImpl::RequireStagedCoverage(*drawResource, drawBytes, commandOffset, commandBytes, + "multidraw_elements_indirect_count_commands"); + BufferImpl::RequireStagedCoverage(*paramResource, parameterBytes, static_cast(drawcount), + static_cast(drawcount) + sizeof(Uint32), + "multidraw_elements_indirect_count_parameter"); + Uint32 actualDrawCount = 0; + std::memcpy(&actualDrawCount, parameterBytes + drawcount, sizeof(actualDrawCount)); + actualDrawCount = std::min(actualDrawCount, static_cast(maxdrawcount)); + ExecuteIndexedIndirectCommands(mode, type, indexSize, drawBytes + commandOffset, commandOffset, + nullptr, static_cast(actualDrawCount), stride, + "MultiDrawElementsIndirectCount"); + return; auto drawBuffer = MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); auto parameterBuffer = MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::Parameter).GetBoundObject(); @@ -9180,10 +9062,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // P5c (hd, CONTRACT-P5C §3.5): with an active transport the frontend binding slot // is never read - the Execute* helpers resolve the buffer from the verb record's // handle (T2). Monolith reads the slot as it always did. - MG_Config::DataArmIsRecord() - ? SharedPtr(nullptr) - : - MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); + SharedPtr(nullptr); ExecuteArraysIndirectCommands(mode, commandBytes, reinterpret_cast(indirect), drawIndirectBuffer, drawcount, stride, "MultiDrawArraysIndirect"); } @@ -9216,55 +9095,53 @@ namespace MobileGL::MG_Backend::DirectGLES { // P5c (hd, CONTRACT-P5C §3.5): see the indexed twin - the verb record's handles and // the SERVER's staged shadow, never the frontend bindings or the client allocator. - if (MG_Config::DataArmIsRecord()) { - const auto& applierState = MG_Pipe::MGPipeApplier(); - const SizeT commandOffset = reinterpret_cast(indirect); - const SizeT commandBytes = commandOffset + static_cast(stride) * static_cast(maxdrawcount - 1) + - sizeof(DrawArraysIndirectCommand); - auto* drawResource = BufferImpl::FindBufferResourceForHandle(applierState.VerbIndirectBuffer); - auto* paramResource = - BufferImpl::FindBufferResourceForHandle(applierState.VerbIndirectParameterBuffer); - // P8-C: the count is CONSUMED here, so the parameter buffer is made current first - // (a store a shader wrote on this side is read back); the command buffer only gets a - // first copy when it has none - its fields are made current by the executor, and - // only when it consumes them (CurrentIndirectCommandBytes). - const Uint8* const parameterBytes = - paramResource ? BufferImpl::SplitHostBytesForCpuRead(*paramResource, - applierState.VerbIndirectParameterBuffer, - "indirect_count_parameter") - : nullptr; - const Uint8* drawBytes = drawResource ? BufferImpl::SplitHostBytes(*drawResource) : nullptr; - if (drawBytes == nullptr && drawResource != nullptr) { - drawBytes = BufferImpl::SplitHostBytesForCpuRead(*drawResource, applierState.VerbIndirectBuffer, - "indirect_count_commands"); - } - if (commandBytes > BufferImpl::ResourceWidthForHandle(applierState.VerbIndirectBuffer)) { - MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range"); - return; - } - if (drawcount < 0 || static_cast(drawcount) + sizeof(Uint32) > - BufferImpl::ResourceWidthForHandle(applierState.VerbIndirectParameterBuffer)) { - MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: invalid GL_PARAMETER_BUFFER binding or range"); - return; - } - if (parameterBytes == nullptr || drawBytes == nullptr) { - MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: CPU fallback cannot read the parameter or " - "draw-indirect buffer"); - return; - } - BufferImpl::RequireStagedCoverage(*drawResource, drawBytes, commandOffset, commandBytes, - "multidraw_arrays_indirect_count_commands"); - BufferImpl::RequireStagedCoverage(*paramResource, parameterBytes, static_cast(drawcount), - static_cast(drawcount) + sizeof(Uint32), - "multidraw_arrays_indirect_count_parameter"); - Uint32 actualDrawCount = 0; - std::memcpy(&actualDrawCount, parameterBytes + drawcount, sizeof(actualDrawCount)); - actualDrawCount = std::min(actualDrawCount, static_cast(maxdrawcount)); - ExecuteArraysIndirectCommands(mode, drawBytes + commandOffset, commandOffset, nullptr, - static_cast(actualDrawCount), stride, - "MultiDrawArraysIndirectCount"); + const auto& applierState = MG_Pipe::MGPipeApplier(); + const SizeT commandOffset = reinterpret_cast(indirect); + const SizeT commandBytes = commandOffset + static_cast(stride) * static_cast(maxdrawcount - 1) + + sizeof(DrawArraysIndirectCommand); + auto* drawResource = BufferImpl::FindBufferResourceForHandle(applierState.VerbIndirectBuffer); + auto* paramResource = + BufferImpl::FindBufferResourceForHandle(applierState.VerbIndirectParameterBuffer); + // P8-C: the count is CONSUMED here, so the parameter buffer is made current first + // (a store a shader wrote on this side is read back); the command buffer only gets a + // first copy when it has none - its fields are made current by the executor, and + // only when it consumes them (CurrentIndirectCommandBytes). + const Uint8* const parameterBytes = + paramResource ? BufferImpl::SplitHostBytesForCpuRead(*paramResource, + applierState.VerbIndirectParameterBuffer, + "indirect_count_parameter") + : nullptr; + const Uint8* drawBytes = drawResource ? BufferImpl::SplitHostBytes(*drawResource) : nullptr; + if (drawBytes == nullptr && drawResource != nullptr) { + drawBytes = BufferImpl::SplitHostBytesForCpuRead(*drawResource, applierState.VerbIndirectBuffer, + "indirect_count_commands"); + } + if (commandBytes > BufferImpl::ResourceWidthForHandle(applierState.VerbIndirectBuffer)) { + MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range"); return; } + if (drawcount < 0 || static_cast(drawcount) + sizeof(Uint32) > + BufferImpl::ResourceWidthForHandle(applierState.VerbIndirectParameterBuffer)) { + MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: invalid GL_PARAMETER_BUFFER binding or range"); + return; + } + if (parameterBytes == nullptr || drawBytes == nullptr) { + MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: CPU fallback cannot read the parameter or " + "draw-indirect buffer"); + return; + } + BufferImpl::RequireStagedCoverage(*drawResource, drawBytes, commandOffset, commandBytes, + "multidraw_arrays_indirect_count_commands"); + BufferImpl::RequireStagedCoverage(*paramResource, parameterBytes, static_cast(drawcount), + static_cast(drawcount) + sizeof(Uint32), + "multidraw_arrays_indirect_count_parameter"); + Uint32 actualDrawCount = 0; + std::memcpy(&actualDrawCount, parameterBytes + drawcount, sizeof(actualDrawCount)); + actualDrawCount = std::min(actualDrawCount, static_cast(maxdrawcount)); + ExecuteArraysIndirectCommands(mode, drawBytes + commandOffset, commandOffset, nullptr, + static_cast(actualDrawCount), stride, + "MultiDrawArraysIndirectCount"); + return; auto drawBuffer = MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); auto parameterBuffer = MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::Parameter).GetBoundObject(); @@ -9447,10 +9324,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // P5c (hd, CONTRACT-P5C §3.5): with an active transport the frontend binding slot // is never read - the Execute* helpers resolve the buffer from the verb record's // handle (T2). Monolith reads the slot as it always did. - MG_Config::DataArmIsRecord() - ? SharedPtr(nullptr) - : - MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); + SharedPtr(nullptr); ExecuteIndexedIndirectCommands(mode, type, indexSize, commandBytes, reinterpret_cast(indirect), drawIndirectBuffer, 1, sizeof(DrawElementsIndirectCommand), "DrawElementsIndirect"); @@ -9494,10 +9368,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // P5c (hd, CONTRACT-P5C §3.5): with an active transport the frontend binding slot // is never read - the Execute* helpers resolve the buffer from the verb record's // handle (T2). Monolith reads the slot as it always did. - MG_Config::DataArmIsRecord() - ? SharedPtr(nullptr) - : - MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); + SharedPtr(nullptr); ExecuteArraysIndirectCommands(mode, commandBytes, reinterpret_cast(indirect), drawIndirectBuffer, 1, sizeof(DrawArraysIndirectCommand), "DrawArraysIndirect"); } @@ -10718,48 +10589,46 @@ 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::DataArmIsRecord()) { - auto& applierState = MG_Pipe::MGPipeApplier(); - const MG_Pipe::MGPipeHandle readFbo = applierState.VerbBlitReadFbo; - const MG_Pipe::MGPipeHandle drawFbo = applierState.VerbBlitDrawFbo; - if (!MG_Pipe::MGPipeHandleIsNull(readFbo) || !MG_Pipe::MGPipeHandleIsNull(drawFbo)) { - applierState.VerbBlitNamedConsumed = true; - TextureImpl::SyncNeccessaryTextures(); - RenderStateImpl::SyncRenderState(); + auto& applierState = MG_Pipe::MGPipeApplier(); + const MG_Pipe::MGPipeHandle readFbo = applierState.VerbBlitReadFbo; + const MG_Pipe::MGPipeHandle drawFbo = applierState.VerbBlitDrawFbo; + if (!MG_Pipe::MGPipeHandleIsNull(readFbo) || !MG_Pipe::MGPipeHandleIsNull(drawFbo)) { + applierState.VerbBlitNamedConsumed = true; + TextureImpl::SyncNeccessaryTextures(); + RenderStateImpl::SyncRenderState(); - SyncAndBindFramebufferByHandle(readFbo, FramebufferTarget::Read, /*forceSync=*/true); - SyncAndBindFramebufferByHandle(drawFbo, FramebufferTarget::Draw, /*forceSync=*/true); + SyncAndBindFramebufferByHandle(readFbo, FramebufferTarget::Read, /*forceSync=*/true); + SyncAndBindFramebufferByHandle(drawFbo, FramebufferTarget::Draw, /*forceSync=*/true); - MGLOG_D("ES BlitNamedFramebuffer({%u,%u} -> {%u,%u}, %d, %d, %d, %d, %d, %d, %d, %d, 0x%x, %s)", - readFbo.Slot, readFbo.Gen, drawFbo.Slot, drawFbo.Gen, srcX0, srcY0, srcX1, - srcY1, dstX0, dstY0, dstX1, dstY1, mask, - MG_Util::ConvertGLEnumToString(filter).c_str()); - // P5e (fb, §5.4): only the probed defect makes this do anything, and it now - // asks the two RECORDS. The state-note detour this used to take - the twin - // table handing back the frontend object it was synced from, plus - // pDefaultFramebufferInfo->defaultFBO for a default endpoint - is gone with the - // note itself; a named framebuffer always has a record (a Named record precedes - // every DSA site, ID-19), so the "missing object skips the workaround" case is - // now a missing record and means the same thing. - const auto* readRecord = BlitEndpointRecord(readFbo, FramebufferTarget::Read); - const auto* drawRecord = BlitEndpointRecord(drawFbo, FramebufferTarget::Draw); - if (readRecord != nullptr && drawRecord != nullptr) { - mask &= ~BlitLayeredDestinationAspects(*readRecord, *drawRecord, srcX0, srcY0, srcX1, - srcY1, dstX0, dstY0, dstX1, dstY1, mask); - } - if (mask != 0) { - IssueBlitWithResolveFallback(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, - mask, filter); - } - // The driver's bindings are now the two NAMED framebuffers; put the bound ones - // back exactly as the monolith named entry point does. - ForceBindCurrentFBO(FramebufferTarget::Read); - ForceBindCurrentFBO(FramebufferTarget::Draw); - DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) { - MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); - }); - return; + MGLOG_D("ES BlitNamedFramebuffer({%u,%u} -> {%u,%u}, %d, %d, %d, %d, %d, %d, %d, %d, 0x%x, %s)", + readFbo.Slot, readFbo.Gen, drawFbo.Slot, drawFbo.Gen, srcX0, srcY0, srcX1, + srcY1, dstX0, dstY0, dstX1, dstY1, mask, + MG_Util::ConvertGLEnumToString(filter).c_str()); + // P5e (fb, §5.4): only the probed defect makes this do anything, and it now + // asks the two RECORDS. The state-note detour this used to take - the twin + // table handing back the frontend object it was synced from, plus + // pDefaultFramebufferInfo->defaultFBO for a default endpoint - is gone with the + // note itself; a named framebuffer always has a record (a Named record precedes + // every DSA site, ID-19), so the "missing object skips the workaround" case is + // now a missing record and means the same thing. + const auto* readRecord = BlitEndpointRecord(readFbo, FramebufferTarget::Read); + const auto* drawRecord = BlitEndpointRecord(drawFbo, FramebufferTarget::Draw); + if (readRecord != nullptr && drawRecord != nullptr) { + mask &= ~BlitLayeredDestinationAspects(*readRecord, *drawRecord, srcX0, srcY0, srcX1, + srcY1, dstX0, dstY0, dstX1, dstY1, mask); } + if (mask != 0) { + IssueBlitWithResolveFallback(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, + mask, filter); + } + // The driver's bindings are now the two NAMED framebuffers; put the bound ones + // back exactly as the monolith named entry point does. + ForceBindCurrentFBO(FramebufferTarget::Read); + ForceBindCurrentFBO(FramebufferTarget::Draw); + DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) { + MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); + }); + return; } TextureImpl::SyncNeccessaryTextures(); @@ -10876,23 +10745,21 @@ 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::DataArmIsRecord()) { - const MG_Pipe::MGPipeHandle dstHandle = MG_Pipe::MGPipeApplier().VerbCopyTexDst; - auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.GetOrCreateByHandle(dstHandle); - if (backendTextureSlot == nullptr) { - MGLOG_E_ONCE("%s: the verb's destination texture handle {%u, %u} is refused by " - "the twin slot table (live generation %u)", - __func__, dstHandle.Slot, dstHandle.Gen, - TextureImpl::g_backendTextureObjects.LiveGenAt(dstHandle.Slot)); - return false; - } - auto& backendObj = *backendTextureSlot; - if (!backendObj) { - backendObj = MakeShared(); - } - backendObj->Bind(TextureImpl::ConvertTextureTargetToBackendGLEnum(textureTarget), unit); - return true; + const MG_Pipe::MGPipeHandle dstHandle = MG_Pipe::MGPipeApplier().VerbCopyTexDst; + auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.GetOrCreateByHandle(dstHandle); + if (backendTextureSlot == nullptr) { + MGLOG_E_ONCE("%s: the verb's destination texture handle {%u, %u} is refused by " + "the twin slot table (live generation %u)", + __func__, dstHandle.Slot, dstHandle.Gen, + TextureImpl::g_backendTextureObjects.LiveGenAt(dstHandle.Slot)); + return false; } + auto& backendObj = *backendTextureSlot; + if (!backendObj) { + backendObj = MakeShared(); + } + backendObj->Bind(TextureImpl::ConvertTextureTargetToBackendGLEnum(textureTarget), unit); + return true; auto& textureUnit = MG_Pipe::gPipeInputs.GetTextureUnitObject(unit); const auto& bindingSlot = textureUnit.GetBindingSlot(textureTarget); @@ -12052,40 +11919,24 @@ 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::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 - // by the draw program, so the per-draw unit list - which is the sampler-view window - // now - never brings it across; before P5e the frontend walk over every slot of every - // touched unit happened to sync it as a side effect. The named narrowing (P5e-5) says - // the union that still covers every texture includes "the waited texture ops", and - // this is one of them: it syncs its own endpoint by handle. - TextureImpl::NoteDriverSideTextureWriteByHandle(dstHandle); - auto& backendTexture = TextureImpl::SyncTextureToBackendByHandle(dstHandle); - if (!backendTexture || dstRecord == nullptr) { - MGLOG_E_ONCE("CopyTexImage2D: the verb's destination texture {%u, %u} has no twin " - "or no applier record on this side", - dstHandle.Slot, dstHandle.Gen); - return; - } - dstBackendTexture = backendTexture.get(); - mgInternalFormat = static_cast(dstRecord->Desc.InternalFormat); - } else - { - const auto& textureObject = MG_Pipe::gPipeInputs.GetTextureUnitObject((Int)activeTextureUnit) - .GetBindingSlot(MG_Util::ConvertGLEnumToTextureTarget(target)) - .GetBoundObject(); - if (textureObject) TextureImpl::NoteDriverSideTextureWrite(textureObject); - auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get()); - if (!backendTextureSlot || !*backendTextureSlot) { - MGLOG_E_ONCE("CopyTexSubImage2D: No backend texture found for texture %u.", - textureObject ? textureObject->GetExternalIndex() : 0); - return; - } - dstBackendTexture = backendTextureSlot->get(); - mgInternalFormat = textureObject->GetFormat(); + 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 + // by the draw program, so the per-draw unit list - which is the sampler-view window + // now - never brings it across; before P5e the frontend walk over every slot of every + // touched unit happened to sync it as a side effect. The named narrowing (P5e-5) says + // the union that still covers every texture includes "the waited texture ops", and + // this is one of them: it syncs its own endpoint by handle. + TextureImpl::NoteDriverSideTextureWriteByHandle(dstHandle); + auto& backendTexture = TextureImpl::SyncTextureToBackendByHandle(dstHandle); + if (!backendTexture || dstRecord == nullptr) { + MGLOG_E_ONCE("CopyTexImage2D: the verb's destination texture {%u, %u} has no twin " + "or no applier record on this side", + dstHandle.Slot, dstHandle.Gen); + return; } + dstBackendTexture = backendTexture.get(); + mgInternalFormat = static_cast(dstRecord->Desc.InternalFormat); dstBackendTexture->Bind(target, activeTextureUnit); GLenum format = GL_DEPTH_COMPONENT; @@ -12174,33 +12025,18 @@ namespace MobileGL::MG_Backend::DirectGLES { TextureImpl::BackendTextureObject* dstBackendTexture = nullptr; // 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::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. - TextureImpl::NoteDriverSideTextureWriteByHandle(dstHandle); - auto& backendTexture = TextureImpl::SyncTextureToBackendByHandle(dstHandle); - if (!backendTexture) { - MGLOG_E_ONCE("CopyTexSubImage2D: the verb's destination texture {%u, %u} has no " - "twin on this side", - dstHandle.Slot, dstHandle.Gen); - return; - } - dstBackendTexture = backendTexture.get(); - } else - { - const auto& textureObject = MG_Pipe::gPipeInputs.GetTextureUnitObject(activeTextureUnit) - .GetBindingSlot(MG_Util::ConvertGLEnumToTextureTarget(target)) - .GetBoundObject(); - if (textureObject) TextureImpl::NoteDriverSideTextureWrite(textureObject); - auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get()); - if (!backendTextureSlot || !*backendTextureSlot) { - MGLOG_E_ONCE("CopyTexSubImage2D: No backend texture found for texture %u.", - textureObject ? textureObject->GetExternalIndex() : 0); - return; - } - dstBackendTexture = backendTextureSlot->get(); + 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. + TextureImpl::NoteDriverSideTextureWriteByHandle(dstHandle); + auto& backendTexture = TextureImpl::SyncTextureToBackendByHandle(dstHandle); + if (!backendTexture) { + MGLOG_E_ONCE("CopyTexSubImage2D: the verb's destination texture {%u, %u} has no " + "twin on this side", + dstHandle.Slot, dstHandle.Gen); + return; } + dstBackendTexture = backendTexture.get(); dstBackendTexture->Bind(target, activeTextureUnit); DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) { @@ -12610,27 +12446,25 @@ 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::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. - const Bool driverWrittenBefore = MG_Record::ServerStagedTexture().IsDriverWritten( - MG_Record::StagedTextureStore::KeyForHandle(mipRes)); - // Every arm GenerateMipmapByRecord takes writes the levels above the base on the - // driver only (P8-B2's CPU arm uploads its levels there too), so the note is - // unconditional. - TextureImpl::NoteDriverSideTextureWriteByHandle(mipRes); - auto& backendTexture = TextureImpl::SyncTextureToBackendByHandle(mipRes); - const auto* record = PipeTextureRecordForHandle(mipRes); - if (!backendTexture || record == nullptr) { - MGLOG_E_ONCE("MGPipe: generate_mipmap names texture {%u, %u}, which has no applier " - "record or no driver texture; the call is dropped", - mipRes.Slot, mipRes.Gen); - return; - } - GenerateMipmapByRecord(target, mipRes, *record, backendTexture, driverWrittenBefore); + 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. + const Bool driverWrittenBefore = MG_Record::ServerStagedTexture().IsDriverWritten( + MG_Record::StagedTextureStore::KeyForHandle(mipRes)); + // Every arm GenerateMipmapByRecord takes writes the levels above the base on the + // driver only (P8-B2's CPU arm uploads its levels there too), so the note is + // unconditional. + TextureImpl::NoteDriverSideTextureWriteByHandle(mipRes); + auto& backendTexture = TextureImpl::SyncTextureToBackendByHandle(mipRes); + const auto* record = PipeTextureRecordForHandle(mipRes); + if (!backendTexture || record == nullptr) { + MGLOG_E_ONCE("MGPipe: generate_mipmap names texture {%u, %u}, which has no applier " + "record or no driver texture; the call is dropped", + mipRes.Slot, mipRes.Gen); return; } + GenerateMipmapByRecord(target, mipRes, *record, backendTexture, driverWrittenBefore); + return; auto unitIndex = MG_Pipe::gPipeInputs.GetActiveTextureUnit(); auto& unit = MG_Pipe::gPipeInputs.GetTextureUnitObject(unitIndex); auto& slot = unit.GetBindingSlot(MG_Util::ConvertGLEnumToTextureTarget(target)); @@ -12786,15 +12620,12 @@ namespace MobileGL::MG_Backend::DirectGLES { static Bool MakeGLESCopyImageEndpoint(const CopyImageEndpoint& endpoint, GLenum appTarget, GLint x, GLint y, GLint z, GLESCopyImageEndpoint& out) { if (endpoint.IsRenderbuffer()) { - if (MG_Config::DataArmIsRecord()) { - if (!PipeRenderbufferRecordForHandle(endpoint.RenderbufferHandle)) return false; - auto* slot = RenderbufferImpl::g_backendRenderbufferObjects.GetOrCreateByHandle(endpoint.RenderbufferHandle); - if (!slot) return false; - if (!*slot) *slot = MakeShared(); - out.renderbuffer = *slot; - out.renderbuffer->SyncToBackendByHandle(endpoint.RenderbufferHandle); - } else - out.renderbuffer = SyncRenderbufferObjectToBackend(endpoint.Renderbuffer); + if (!PipeRenderbufferRecordForHandle(endpoint.RenderbufferHandle)) return false; + auto* slot = RenderbufferImpl::g_backendRenderbufferObjects.GetOrCreateByHandle(endpoint.RenderbufferHandle); + if (!slot) return false; + if (!*slot) *slot = MakeShared(); + out.renderbuffer = *slot; + out.renderbuffer->SyncToBackendByHandle(endpoint.RenderbufferHandle); if (!out.renderbuffer) return false; out.target = GL_RENDERBUFFER; out.x = x; @@ -12814,13 +12645,7 @@ 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 (MG_Config::DataArmIsRecord()) { - out.texture = TextureImpl::SyncTextureToBackendByHandle(endpoint.TextureHandle); - } else - { - if (!endpoint.Texture) return false; - out.texture = TextureImpl::SyncTextureObjectToBackend(endpoint.Texture); - } + out.texture = TextureImpl::SyncTextureToBackendByHandle(endpoint.TextureHandle); if (!out.texture) return false; const TextureTarget stateTarget = MG_Util::ConvertGLEnumToTextureTarget(appTarget); out.target = TextureImpl::ConvertTextureTargetToBackendGLEnum(stateTarget); @@ -12838,13 +12663,11 @@ namespace MobileGL::MG_Backend::DirectGLES { } static TextureInternalFormat GetCopyImageEndpointFormat(const CopyImageEndpoint& endpoint) { - if (MG_Config::DataArmIsRecord()) { - const auto* record = endpoint.IsRenderbuffer() - ? PipeRenderbufferRecordForHandle(endpoint.RenderbufferHandle) - : PipeTextureRecordForHandle(endpoint.TextureHandle); - return record ? static_cast(record->Desc.InternalFormat) - : TextureInternalFormat::Unknown; - } + const auto* record = endpoint.IsRenderbuffer() + ? PipeRenderbufferRecordForHandle(endpoint.RenderbufferHandle) + : PipeTextureRecordForHandle(endpoint.TextureHandle); + return record ? static_cast(record->Desc.InternalFormat) + : TextureInternalFormat::Unknown; if (endpoint.IsRenderbuffer()) return endpoint.Renderbuffer->GetInternalFormat(); return endpoint.Texture ? endpoint.Texture->GetFormat() : TextureInternalFormat::Unknown; } @@ -12895,7 +12718,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::DataArmIsRecord()) return; + return; // 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 // monolith arm the call was a no-op (PipeCatalogueTest's list and the lint @@ -12990,7 +12813,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::DataArmIsRecord() && handleArmDestination) { + if (handleArmDestination) { const Bool followed = !srcEndpoint.IsRenderbuffer() && !MG_Pipe::MGPipeHandleIsNull(srcEndpoint.TextureHandle) && TextureImpl::FollowCopyImageInStagedStore( @@ -13073,7 +12896,7 @@ namespace MobileGL::MG_Backend::DirectGLES { MG_Util::ConvertGLEnumToString(dstTarget).c_str()); MOBILEGL_ASSERT(false, "glCopyImageSubData failed after frontend validation accepted the request."); // P8-E: the store already made the move the driver just refused. - if (MG_Config::DataArmIsRecord() && handleArmDestination) { + if (handleArmDestination) { TextureImpl::NoteDriverSideTextureWriteByHandle(dstEndpoint.TextureHandle); } return; @@ -13099,10 +12922,8 @@ 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::DataArmIsRecord()) { - TextureImpl::NoteImageUnitBoundWithoutReadingTheFrontend(unit, texture != 0); - return; - } + TextureImpl::NoteImageUnitBoundWithoutReadingTheFrontend(unit, texture != 0); + return; (void)texture; TextureImpl::SyncImageTextureBinding(unit); } @@ -13144,15 +12965,13 @@ 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 (MG_Config::DataArmIsRecord()) { - 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; + 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; if (!MG_Pipe::gPipeInputs.ValidateProgramName(program)) return; auto& programObject = MG_Pipe::gPipeInputs.GetProgramObject(program); if (!programObject) return; @@ -13361,9 +13180,7 @@ 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 = - MG_Config::DataArmIsRecord() - ? SplitReadbackPackBuffer() : - MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); + SplitReadbackPackBuffer(); const SizeT pboOffset = reinterpret_cast(pixels); if (pixelPackBufferObject && pboOffset + packedSize > pixelPackBufferObject->GetSize()) { MGLOG_E_ONCE("ReadPixels: %s readback PBO is too small", what); @@ -14380,9 +14197,7 @@ namespace MobileGL::MG_Backend::DirectGLES { return true; } const auto& pixelPackBufferObject = - MG_Config::DataArmIsRecord() - ? SplitReadbackPackBuffer() : - MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); + SplitReadbackPackBuffer(); if (!pixelPackBufferObject && pixels == nullptr) { return true; } @@ -14631,25 +14446,20 @@ 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::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 " - "DECLINED and the destination keeps its bytes", - static_cast(uploadTarget), level); - MG_Pipe::gPipeInputs.RecordError( - ErrorCode::InvalidOperation, - MakeUnique( - "DirectGLES", "GetTexImage", - "the level has no shadow on the server and there is no client shadow to convert")); - return false; - } - } else - MG_Pipe::MGPipeUnmigratedEmulation("get-tex-image-shadow"); + 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 " + "DECLINED and the destination keeps its bytes", + static_cast(uploadTarget), level); + MG_Pipe::gPipeInputs.RecordError( + ErrorCode::InvalidOperation, + MakeUnique( + "DirectGLES", "GetTexImage", + "the level has no shadow on the server and there is no client shadow to convert")); + return false; + } const auto& pixelPackBufferObject = - MG_Config::DataArmIsRecord() - ? SplitReadbackPackBuffer() : - MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); + SplitReadbackPackBuffer(); if (!pixelPackBufferObject && pixels == nullptr) { return true; } @@ -14968,9 +14778,7 @@ 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 = - MG_Config::DataArmIsRecord() - ? SplitReadbackPackBuffer() : - MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); + SplitReadbackPackBuffer(); Bool usePBO = false; GLuint packBufferId = 0; if (pixelPackBufferObject) { @@ -15411,9 +15219,7 @@ 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 = - MG_Config::DataArmIsRecord() - ? SplitReadbackPackBuffer() : - MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); + SplitReadbackPackBuffer(); Bool usePBO = false; GLuint packBufferId = 0; if (pixelPackBufferObject) { diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index dbc7984d..33106e50 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -2201,8 +2201,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // before this). So under split the subdata MINTS the twin it needs to stage into. // Monolith is untouched: the twin stays lazy there because the frontend object's // MappedData is the source and nothing is lost by deferring the allocation (D-A2). - if (resource == nullptr && bytes != nullptr && - MG_Config::DataArmIsRecord()) { + if (resource == nullptr && bytes != nullptr) { resource = GetOrCreateBufferResourceForHandle(res); } if (!resource) return; @@ -2406,8 +2405,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // An empty capture may conservatively request a writeback before // any draw materialized this buffer. Resolve its staged resource // exactly as a GPU consumer would, then return the real bytes. - auto* resource = MG_Config::DataArmIsRecord() - ? EnsureBufferResourceForHandle(nullptr, res) : FindBufferResourceForHandle(res); + auto* resource = EnsureBufferResourceForHandle(nullptr, res); if (!resource || resource->id == 0 || !resource->storageInitialized) return; if (!CanTouchGLNow() || resource->contextGeneration != g_bufferContextGeneration) return; if (resource->persistentMapped) { @@ -3893,9 +3891,6 @@ namespace MobileGL::MG_Backend::DirectGLES { // // A null object is the "no frontend to ask" case and is treated as "not mapped", // which is what the record already says. - const Bool askTheObjectWhetherItIsMapped = - !MG_Config::DataArmIsRecord(); - if (askTheObjectWhetherItIsMapped && frontend != nullptr && frontend->IsMapped()) return false; if (resource->pendingRespecify || !resource->storageInitialized) return false; if (!resource->pendingRanges.empty()) return false; if (resource->storageSize != static_cast(record->Desc.Width)) return false; @@ -3960,9 +3955,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // so a corrupt staged upload rendered the frontend's correct bytes and the R-2.5 // 0xDD audit could not reach a draw. R-2 / table 3: honest inproc is inproc that // does not read the client object's memory. Under monolith nothing changes. - if (MG_Config::DataArmIsRecord()) { - return resource->hostBytes; - } + return resource->hostBytes; if (bufferObject) return bufferObject->MappedData(); return resource->hostBytes; }; @@ -4099,9 +4092,6 @@ namespace MobileGL::MG_Backend::DirectGLES { // layer-1 surface ("buffer-legacy-arm") and the client's own pre-verb // persistent-map push is the only producer; the call is skipped outright. Under // monolith D-N's placement stands. - if (bufferObject && !MG_Config::DataArmIsRecord()) { - bufferObject->SyncPersistentMappedRange(); - } const auto* record = ResourceRecordOf(res); const SizeT size = record != nullptr ? static_cast(record->Desc.Width) : 0; @@ -4144,12 +4134,9 @@ namespace MobileGL::MG_Backend::DirectGLES { // the last orphan". With no coverage and a clear bit the store is undefined by the // application's own declaration and the upload stays a pure NULL reallocation. const Bool shadowHasContent = - MG_Config::DataArmIsRecord() - ? (record->Desc.HasDefinedContent != 0 || + (record->Desc.HasDefinedContent != 0 || (ServerStaged().CopiesIntoServerStorage() ? ServerStaged().CoveredRunCount(resource) != 0 - : resource->contentSinceRespecify)) - : - (bufferObject ? bufferObject->HasDefinedContent() : (record->Desc.HasDefinedContent != 0)); + : resource->contentSinceRespecify)); const void* initialData = shadowHasContent ? hostBase : nullptr; // M-3 / codex 4: a RespecifyStorageWith(..., initialData != nullptr) is a WHOLE-STORE // [0, size) upload from the base, so it owes the same coverage the pending-range drain @@ -5018,8 +5005,7 @@ namespace MobileGL::MG_Backend::DirectGLES { "MOBILEGL_PIPE_PUSH leaves kMGPipeSubsystemFramebuffer (bit 9) clear (or refuses " "it) and MOBILEGL_PIPE_LEGACY_MEMOS=0 disables the pre-handle g_fboSynced* arm"); } - if (MG_Config::DataArmIsRecord() && - verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) { + if (verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) { StopOnTransportWithoutRecordArm( "kMGPipeSubsystemFramebuffer (bit 9)", "the pre-handle g_fboSynced* / g_fboTextureSyncList arm reads the framebuffer " @@ -5061,8 +5047,7 @@ namespace MobileGL::MG_Backend::DirectGLES { "refuses it) and MOBILEGL_PIPE_LEGACY_MEMOS=0 disables the pre-handle texture " "cheap-gate trio"); } - if (MG_Config::DataArmIsRecord() && - verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) { + if (verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) { StopOnTransportWithoutRecordArm( "kMGPipeSubsystemTextureResources (bit 10)", "the pre-handle texture cheap-gate trio resolves through the frontend texture " @@ -5095,8 +5080,7 @@ namespace MobileGL::MG_Backend::DirectGLES { "it) and MOBILEGL_PIPE_LEGACY_MEMOS=0 disables UnitSamplerLookupMemo's WeakPtr " "arm and SamplerPassMemo's raw-pointer rows"); } - if (MG_Config::DataArmIsRecord() && - verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) { + if (verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) { StopOnTransportWithoutRecordArm( "kMGPipeSubsystemSamplers (bit 11)", "UnitSamplerLookupMemo's WeakPtr arm and SamplerPassMemo's raw-pointer rows are " @@ -5122,8 +5106,7 @@ namespace MobileGL::MG_Backend::DirectGLES { "MOBILEGL_PIPE_PUSH leaves kMGPipeSubsystemPrograms (bit 12) clear and " "MOBILEGL_PIPE_LEGACY_MEMOS=0 disables g_programTwinLookupMemo"); } - if (MG_Config::DataArmIsRecord() && - verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) { + if (verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) { StopOnTransportWithoutRecordArm( "kMGPipeSubsystemPrograms (bit 12)", "g_programTwinLookupMemo is keyed on the frontend ProgramObject and the legacy arm " @@ -6279,7 +6262,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // at adoption for the same reason, so the fallback below is a null and a refusal // rather than a stale read. const Bool sourceGpuWritten = - MG_Config::DataArmIsRecord() && resource->serverGpuWritten; + resource->serverGpuWritten; if (sourceGpuWritten) { BufferImpl::SplitHostBytesForCpuRead(*resource, binding.Res, "fp64_vertex_narrowing"); } @@ -6463,153 +6446,151 @@ namespace MobileGL::MG_Backend::DirectGLES { } // 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(record.Desc.InternalFormat); - // ES image binding needs immutable storage, but an already - // immutable core-format allocation needs no replacement. Keep - // its GPU contents and native name; normal serial/shape checks - // below still consume pending uploads and real redefinitions. - if (m_isInitialized && m_backendStorageImmutable && !GetImageBindableStorageWidening(format)) { - m_imageBindableStorageRequired = true; - m_forceTextureParamsResync = true; - return; - } - // P9 W2 (notes/p9/W2-REMINT.md): EVERY BYTE THIS RE-MINT REPLAYS IS ALREADY ON THE - // SERVER. The GPU image holds what the GPU wrote (render targets, clears, copies, - // generated mips, image stores) and the staged store holds what the client uploaded; - // the client is never asked, so no pull protocol exists. Reading only the store would - // be wrong: a GPU-written level's store run is stale or absent - both host corpus - // re-mints (iris-photon, iris-derivative) are render targets with no staged bytes. - const Bool hadBackendStorage = m_isInitialized; - // The R-16 control for W2-b's fallback below: a readback the driver refuses cannot be - // produced on the host (llvmpipe renders every image format), so the test lane forces - // one. Read by the SERVER, like the MGITEST_MAGMA_FORCE_* knobs. - static const Bool s_forceReadbackFailure = - std::getenv("MGITEST_ESPRYT_FORCE_REMINT_READBACK_FAILURE") != nullptr; - auto& store = MG_Record::ServerStagedTexture(); - const Uint64 key = MG_Record::StagedTextureStore::KeyForHandle(res); - const auto target = BufferImpl::StagedTextureTargetForPipeTarget(record.Desc.Target); - GLenum canonicalFormat = GL_NONE, canonicalType = GL_NONE; - MG_Util::TextureFormatProcessor::NormalizePixelFormat( - MG_Util::ConvertTextureInternalFormatToGLEnum(format), {}, nullptr, - &canonicalFormat, &canonicalType); - for (const auto& uploadTarget : - BufferImpl::StagedUploadTargetsForPipeTarget(record.Desc.Target)) { - for (Uint32 level = 0; level < record.Desc.Levels; ++level) { - const auto targetCode = static_cast(uploadTarget); - const auto levelCode = static_cast(level); - const IntVec3 extent = store.LevelExtentOrUndefined(key, targetCode, levelCode); - if (extent.x() <= 0 || extent.y() <= 0 || extent.z() <= 0) continue; - const auto* pending = FindPipeTextureUpload(record, targetCode, levelCode); - const Bool covered = store.IsCovered(key, targetCode, levelCode); - const MG_Pipe::MGPBox whole{0, 0, 0, static_cast(extent.x()), - static_cast(extent.y()), static_cast(extent.z())}; - // A genuinely full pending write owns every byte. A - // union box covering the level is not sufficient when - // its explicit rectangles leave GPU-written gaps. - const auto coversWhole = [&](const auto& box) { - return box.X == 0 && box.Y == 0 && box.Z == 0 && - box.W == whole.W && box.H == whole.H && box.D == whole.D; - }; - const Bool pendingWhole = pending && (pending->Regions.empty() - ? coversWhole(pending->UnionBox) - : std::any_of(pending->Regions.begin(), pending->Regions.end(), coversWhole)); - // W2-a: ONLY A LEVEL THE DRIVER ACTUALLY HOLDS IS READ BACK. The store's - // Defined-ness follows the client's respecifies, the driver's follows this - // twin's last sync, and the two differ for a level defined (null data, or null - // data plus a partial upload) after that sync: the driver has no such level, so - // the GPU cannot have written it. A covered level then replays its staged run - // (the client's whole-level shadow, pending boxes included) and an uncovered one - // is undefined content with nothing to preserve. Reading it anyway was - // Fatal{ResourceUnavailable, "image-promotion-readback"} on legal GL. - const Bool nativeLevel = - m_isInitialized && - NativeTextureLevelHasExtent(m_backendTextureId, target, uploadTarget, - static_cast(level), extent); - if (nativeLevel && !(covered && pendingWhole)) { - Vector snapshot; - const Bool readBack = - !s_forceReadbackFailure && - ReadTextureLevelTight(m_backendTextureId, target, uploadTarget, format, - static_cast(level), extent, - /*sourceUsesImageCarrier=*/false, canonicalFormat, - canonicalType, snapshot); - if (!readBack && !covered) { - // The GPU holds this level, the driver will not hand it back, and no - // CPU byte of it exists anywhere: the one case with no honest answer. - MGLOG_F("MGPipe: Fatal{ResourceUnavailable, \"image-promotion-readback\"} {%u,%u} level=%u", - res.Slot, res.Gen, level); - std::abort(); - } - if (!readBack) { - // W2-b: the driver refused the readback (a format it cannot attach - // to a framebuffer - SNORM without EXT_render_snorm, 16-bit norm - // without EXT_texture_norm16, float without EXT_color_buffer_float) - // but the store covers the level. Its staged run is exactly what the - // monolith re-mint replays; texels the GPU wrote since that upload - // are not preserved, which is monolith's answer too. - MGLOG_W_ONCE("MGPipe: image promotion of texture {%u,%u} level %u: the driver " - "refused the readback (internal format %u); replaying the level's " - "staged bytes instead (remint-readback-fallback)", - res.Slot, res.Gen, level, record.Desc.InternalFormat); - } - if (readBack && pending) { - // Native contents include prior draws/image - // writes; the pending stage owns ONLY its boxes. - // Merge those boxes before adopting a whole-level - // snapshot, never overwrite untouched GPU texels - // with the rest of a stale client staging run. - const SizeT pixels = static_cast(extent.x()) * extent.y() * extent.z(); - const SizeT stagedSize = store.LevelByteSize(key, targetCode, levelCode); - if (!covered || !pixels || snapshot.size() != stagedSize || stagedSize % pixels) { - MGLOG_F("MGPipe: Fatal{StageSnapshotTooNarrow, \"image-promotion-pending\"}"); - std::abort(); - } - const SizeT bpp = stagedSize / pixels; - const Uint8* staged = store.RequireLevelBytes(key, targetCode, levelCode, "image-promotion"); - const auto merge = [&](Int32 x, Int32 y, Int32 z, Uint32 w, Uint32 h, Uint32 d) { - if (x < 0 || y < 0 || z < 0 || Uint64(x) + w > whole.W || - Uint64(y) + h > whole.H || Uint64(z) + d > whole.D) { - MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"image-promotion-region\"}"); - std::abort(); - } - for (Uint32 slice = 0; slice < d; ++slice) { - for (Uint32 row = 0; row < h; ++row) { - const SizeT offset = ((static_cast(z + slice) * whole.H + y + row) * whole.W + x) * bpp; - Memcpy(snapshot.data() + offset, staged + offset, static_cast(w) * bpp); - } - } - }; - if (pending->Regions.empty()) { - const auto& box = pending->UnionBox; - merge(box.X, box.Y, box.Z, box.W, box.H, box.D); - } else { - for (const auto& box : pending->Regions) - merge(box.X, box.Y, box.Z, box.W, box.H, box.D); - } - } - if (readBack) { - store.Adopt(key, targetCode, levelCode, extent, snapshot.data(), snapshot.size()); - } - } - // Undefined contents have no byte run to preserve. A - // first allocation can still be image-bindable without - // inventing an upload or consulting the frontend. - if (!store.IsCovered(key, targetCode, levelCode)) continue; - if (!RearmPipeTextureLevelUpload(res, - MG_Pipe::MGPipePackSubDataTarget(record.Desc.Target, static_cast(uploadTarget)), - levelCode, whole)) { - MGLOG_F("MGPipe: Fatal{ResourceUnavailable, \"image-promotion-pending-capacity\"}"); + const auto format = static_cast(record.Desc.InternalFormat); + // ES image binding needs immutable storage, but an already + // immutable core-format allocation needs no replacement. Keep + // its GPU contents and native name; normal serial/shape checks + // below still consume pending uploads and real redefinitions. + if (m_isInitialized && m_backendStorageImmutable && !GetImageBindableStorageWidening(format)) { + m_imageBindableStorageRequired = true; + m_forceTextureParamsResync = true; + return; + } + // P9 W2 (notes/p9/W2-REMINT.md): EVERY BYTE THIS RE-MINT REPLAYS IS ALREADY ON THE + // SERVER. The GPU image holds what the GPU wrote (render targets, clears, copies, + // generated mips, image stores) and the staged store holds what the client uploaded; + // the client is never asked, so no pull protocol exists. Reading only the store would + // be wrong: a GPU-written level's store run is stale or absent - both host corpus + // re-mints (iris-photon, iris-derivative) are render targets with no staged bytes. + const Bool hadBackendStorage = m_isInitialized; + // The R-16 control for W2-b's fallback below: a readback the driver refuses cannot be + // produced on the host (llvmpipe renders every image format), so the test lane forces + // one. Read by the SERVER, like the MGITEST_MAGMA_FORCE_* knobs. + static const Bool s_forceReadbackFailure = + std::getenv("MGITEST_ESPRYT_FORCE_REMINT_READBACK_FAILURE") != nullptr; + auto& store = MG_Record::ServerStagedTexture(); + const Uint64 key = MG_Record::StagedTextureStore::KeyForHandle(res); + const auto target = BufferImpl::StagedTextureTargetForPipeTarget(record.Desc.Target); + GLenum canonicalFormat = GL_NONE, canonicalType = GL_NONE; + MG_Util::TextureFormatProcessor::NormalizePixelFormat( + MG_Util::ConvertTextureInternalFormatToGLEnum(format), {}, nullptr, + &canonicalFormat, &canonicalType); + for (const auto& uploadTarget : + BufferImpl::StagedUploadTargetsForPipeTarget(record.Desc.Target)) { + for (Uint32 level = 0; level < record.Desc.Levels; ++level) { + const auto targetCode = static_cast(uploadTarget); + const auto levelCode = static_cast(level); + const IntVec3 extent = store.LevelExtentOrUndefined(key, targetCode, levelCode); + if (extent.x() <= 0 || extent.y() <= 0 || extent.z() <= 0) continue; + const auto* pending = FindPipeTextureUpload(record, targetCode, levelCode); + const Bool covered = store.IsCovered(key, targetCode, levelCode); + const MG_Pipe::MGPBox whole{0, 0, 0, static_cast(extent.x()), + static_cast(extent.y()), static_cast(extent.z())}; + // A genuinely full pending write owns every byte. A + // union box covering the level is not sufficient when + // its explicit rectangles leave GPU-written gaps. + const auto coversWhole = [&](const auto& box) { + return box.X == 0 && box.Y == 0 && box.Z == 0 && + box.W == whole.W && box.H == whole.H && box.D == whole.D; + }; + const Bool pendingWhole = pending && (pending->Regions.empty() + ? coversWhole(pending->UnionBox) + : std::any_of(pending->Regions.begin(), pending->Regions.end(), coversWhole)); + // W2-a: ONLY A LEVEL THE DRIVER ACTUALLY HOLDS IS READ BACK. The store's + // Defined-ness follows the client's respecifies, the driver's follows this + // twin's last sync, and the two differ for a level defined (null data, or null + // data plus a partial upload) after that sync: the driver has no such level, so + // the GPU cannot have written it. A covered level then replays its staged run + // (the client's whole-level shadow, pending boxes included) and an uncovered one + // is undefined content with nothing to preserve. Reading it anyway was + // Fatal{ResourceUnavailable, "image-promotion-readback"} on legal GL. + const Bool nativeLevel = + m_isInitialized && + NativeTextureLevelHasExtent(m_backendTextureId, target, uploadTarget, + static_cast(level), extent); + if (nativeLevel && !(covered && pendingWhole)) { + Vector snapshot; + const Bool readBack = + !s_forceReadbackFailure && + ReadTextureLevelTight(m_backendTextureId, target, uploadTarget, format, + static_cast(level), extent, + /*sourceUsesImageCarrier=*/false, canonicalFormat, + canonicalType, snapshot); + if (!readBack && !covered) { + // The GPU holds this level, the driver will not hand it back, and no + // CPU byte of it exists anywhere: the one case with no honest answer. + MGLOG_F("MGPipe: Fatal{ResourceUnavailable, \"image-promotion-readback\"} {%u,%u} level=%u", + res.Slot, res.Gen, level); std::abort(); } + if (!readBack) { + // W2-b: the driver refused the readback (a format it cannot attach + // to a framebuffer - SNORM without EXT_render_snorm, 16-bit norm + // without EXT_texture_norm16, float without EXT_color_buffer_float) + // but the store covers the level. Its staged run is exactly what the + // monolith re-mint replays; texels the GPU wrote since that upload + // are not preserved, which is monolith's answer too. + MGLOG_W_ONCE("MGPipe: image promotion of texture {%u,%u} level %u: the driver " + "refused the readback (internal format %u); replaying the level's " + "staged bytes instead (remint-readback-fallback)", + res.Slot, res.Gen, level, record.Desc.InternalFormat); + } + if (readBack && pending) { + // Native contents include prior draws/image + // writes; the pending stage owns ONLY its boxes. + // Merge those boxes before adopting a whole-level + // snapshot, never overwrite untouched GPU texels + // with the rest of a stale client staging run. + const SizeT pixels = static_cast(extent.x()) * extent.y() * extent.z(); + const SizeT stagedSize = store.LevelByteSize(key, targetCode, levelCode); + if (!covered || !pixels || snapshot.size() != stagedSize || stagedSize % pixels) { + MGLOG_F("MGPipe: Fatal{StageSnapshotTooNarrow, \"image-promotion-pending\"}"); + std::abort(); + } + const SizeT bpp = stagedSize / pixels; + const Uint8* staged = store.RequireLevelBytes(key, targetCode, levelCode, "image-promotion"); + const auto merge = [&](Int32 x, Int32 y, Int32 z, Uint32 w, Uint32 h, Uint32 d) { + if (x < 0 || y < 0 || z < 0 || Uint64(x) + w > whole.W || + Uint64(y) + h > whole.H || Uint64(z) + d > whole.D) { + MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"image-promotion-region\"}"); + std::abort(); + } + for (Uint32 slice = 0; slice < d; ++slice) { + for (Uint32 row = 0; row < h; ++row) { + const SizeT offset = ((static_cast(z + slice) * whole.H + y + row) * whole.W + x) * bpp; + Memcpy(snapshot.data() + offset, staged + offset, static_cast(w) * bpp); + } + } + }; + if (pending->Regions.empty()) { + const auto& box = pending->UnionBox; + merge(box.X, box.Y, box.Z, box.W, box.H, box.D); + } else { + for (const auto& box : pending->Regions) + merge(box.X, box.Y, box.Z, box.W, box.H, box.D); + } + } + if (readBack) { + store.Adopt(key, targetCode, levelCode, extent, snapshot.data(), snapshot.size()); + } + } + // Undefined contents have no byte run to preserve. A + // first allocation can still be image-bindable without + // inventing an upload or consulting the frontend. + if (!store.IsCovered(key, targetCode, levelCode)) continue; + if (!RearmPipeTextureLevelUpload(res, + MG_Pipe::MGPipePackSubDataTarget(record.Desc.Target, static_cast(uploadTarget)), + levelCode, whole)) { + MGLOG_F("MGPipe: Fatal{ResourceUnavailable, \"image-promotion-pending-capacity\"}"); + std::abort(); } } - // W2-d: `trp=` counts a re-mint of storage the backend ALREADY held, on this arm as - // on the frontend one (which counts in RequireImageBindableStorage). The keep path - // above is not a re-mint and returns before this; a first allocation holds nothing. - if (hadBackendStorage && MG_Util::PipeStats::Enabled()) { - MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::TextureRemintPulls, 1); - } + } + // W2-d: `trp=` counts a re-mint of storage the backend ALREADY held, on this arm as + // on the frontend one (which counts in RequireImageBindableStorage). The keep path + // above is not a re-mint and returns before this; a first allocation holds nothing. + if (hadBackendStorage && MG_Util::PipeStats::Enabled()) { + MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::TextureRemintPulls, 1); } // Every defined byte now belongs to the server and every required // replay is in its pending set. The existing upload path builds and @@ -7759,7 +7740,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // to be transport-gated now say so themselves, so a future by-handle noter cannot quietly // re-open ID-107's hole somewhere else. #define MGB_TEXTURE_RECORD_ARM_SELECTED() \ - (MG_Config::DataArmIsRecord() && TextureResourceSubsystemEnabled()) + (TextureResourceSubsystemEnabled()) // Returns true when the caller must decline (no handle noted: the caller's bug it always // was, named by the caller's own log line), false when the record arm can serve the call, @@ -8373,12 +8354,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::DataArmIsRecord() && pushedStorage != nullptr && + if (pushedStorage != nullptr && !MG_Pipe::MGPipeHandleIsNull(pushedStorage->Desc.ViewOf)) { SyncTextureViewToBackendByRecord(pushedRes, *pushedStorage, stateTextureObject); return; } - if (MG_Config::DataArmIsRecord() && pushedStorage != nullptr) { + if (pushedStorage != nullptr) { // Not a view: fall through to the storage body with the frontend test skipped. } else // A texture created by glTextureView owns no storage: the levels, the format and @@ -8431,10 +8412,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::DataArmIsRecord()) { - RequireImageBindableStorageByHandle(pushedRes, *pushedStorage); - } else - RequireImageBindableStorage(stateTextureObject); + RequireImageBindableStorageByHandle(pushedRes, *pushedStorage); } // ONE compare replaces the whole cheap-gate trio AND the content version. The @@ -11136,30 +11114,10 @@ namespace MobileGL::MG_Backend::DirectGLES { // push-monolith arm keeps the frontend sync it had at f6cfcbd3, token for token, // because tx2's record arm inside SyncMipmapsToBackend is itself selected by // `Transport != Monolith` and cannot answer here. - 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). - TextureImpl::NoteDriverSideTextureWriteByHandle(surface.Res); - } else - { - const SharedPtr monolithTexture = - (monolithAttachment != nullptr && monolithAttachment->IsTexture()) - ? monolithAttachment->GetTexture() - : nullptr; - if (!monolithTexture) { - MGLOG_E_ONCE("MGPipe: attachment record names texture {%u, %u} on the " - "push-monolith arm, where the storage sync needs the frontend " - "texture and this caller supplied none - refusing to attach a " - "texture whose levels were never pushed", - surface.Res.Slot, surface.Res.Gen); - return false; - } - backendTextureObject->SyncMipmapsToBackend(monolithTexture); - // From here on every draw, clear and blit into this framebuffer can write the - // texture behind the shadow's back. - TextureImpl::NoteDriverSideTextureWrite(monolithTexture); - } + 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). + TextureImpl::NoteDriverSideTextureWriteByHandle(surface.Res); const auto uploadTarget = static_cast(surface.UploadTarget); if (surface.Layered != 0) { g_GLESFuncs.glFramebufferTexture(glFBOTarget, glBackendAttachment, @@ -11223,24 +11181,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // GLContext (RecordError) and the by-handle form deliberately does not, because // 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 (MG_Config::DataArmIsRecord()) { - backendRenderbufferObject->SyncToBackendByHandle(surface.Res); - } else - { - const SharedPtr monolithRenderbuffer = - (monolithAttachment != nullptr && monolithAttachment->IsRenderbuffer()) - ? monolithAttachment->GetRenderbuffer() - : nullptr; - if (!monolithRenderbuffer) { - MGLOG_E_ONCE("MGPipe: attachment record names renderbuffer {%u, %u} on the " - "push-monolith arm, where the storage allocation needs the " - "frontend renderbuffer and this caller supplied none - refusing " - "to attach a renderbuffer with no storage", - surface.Res.Slot, surface.Res.Gen); - return false; - } - backendRenderbufferObject->SyncToBackend(monolithRenderbuffer); - } + backendRenderbufferObject->SyncToBackendByHandle(surface.Res); backendRenderbufferObject->Bind(); g_GLESFuncs.glFramebufferRenderbuffer(glFBOTarget, glBackendAttachment, GL_RENDERBUFFER, backendRenderbufferObject->GetBackendRenderbufferId()); @@ -11273,10 +11214,8 @@ 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 (MG_Config::DataArmIsRecord()) { - const auto* record = MG_Pipe::MGPipeApplier().ReadFramebuffer(); - return record && IsAlphaWidenedColorSurface(record->ReadSurface); - } + const auto* record = MG_Pipe::MGPipeApplier().ReadFramebuffer(); + return record && IsAlphaWidenedColorSurface(record->ReadSurface); const auto* attachmentObject = GetReadColorAttachment(); if (attachmentObject == nullptr) { return false; @@ -11285,11 +11224,9 @@ namespace MobileGL::MG_Backend::DirectGLES { } Bool IsFixedPointFallbackReadAttachment() { - if (MG_Config::DataArmIsRecord()) { - const auto* record = MG_Pipe::MGPipeApplier().ReadFramebuffer(); - return record && record->ReadSurface.Kind != MG_Pipe::kMGPipeSurfaceKindNone && - IsSnormFormat(static_cast(record->ReadSurface.InternalFormat)); - } + const auto* record = MG_Pipe::MGPipeApplier().ReadFramebuffer(); + return record && record->ReadSurface.Kind != MG_Pipe::kMGPipeSurfaceKindNone && + IsSnormFormat(static_cast(record->ReadSurface.InternalFormat)); const auto& readFBO = MG_Pipe::gPipeInputs.GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); if (!readFBO) { @@ -11456,10 +11393,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // P5c (hd): with an active transport the handle is the caller's // (m_pushedSyncHandle), never the client allocator's - see SyncToBackend. const MG_Pipe::MGPipeHandle fbo = - MG_Config::DataArmIsRecord() - ? m_pushedSyncHandle - : - g_backendFramebufferObjects.HandleOf(stateFBOObject.get()); + m_pushedSyncHandle; // ID-19: the OBJECT's record, not "the record of whatever is bound to READ". A // framebuffer whose read buffer is being pushed need not be the read binding at // all - glNamedFramebufferReadBuffer and the DSA clears reach here by name - and @@ -11632,10 +11566,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // is never probed (T2). A null there means a caller reached this arm without a // record, which the null-record refusal below names. const MG_Pipe::MGPipeHandle fbo = - MG_Config::DataArmIsRecord() - ? m_pushedSyncHandle - : - g_backendFramebufferObjects.HandleOf(stateFBOObject.get()); + m_pushedSyncHandle; pushedRecord = PushedFramebufferRecord(fbo); if (pushedRecord == nullptr) { MGLOG_E_ONCE("MGPipe: framebuffer %u has no applier record on the handle arm, so it " @@ -12937,11 +12868,9 @@ 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::DataArmIsRecord()) { - const auto& st = MG_Pipe::MGPipeApplier(); - if (static_cast(unit) >= st.BoundShaderImages.size()) return 0; - return static_cast(st.BoundShaderImages[static_cast(unit)].InternalFormat); - } + const auto& st = MG_Pipe::MGPipeApplier(); + if (static_cast(unit) >= st.BoundShaderImages.size()) return 0; + return static_cast(st.BoundShaderImages[static_cast(unit)].InternalFormat); return static_cast(MG_Pipe::gPipeInputs.GetImageTextureBinding(unit).Format); } diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index bcb65002..6e7dc704 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -788,7 +788,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::DataArmIsRecord() && ProgramSubsystemEnabled(); + return ProgramSubsystemEnabled(); } // ---- P4a: what the twins read INSTEAD of the frontend object ---- @@ -1312,8 +1312,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // conjunction in MultiDraw.cpp would satisfy in letter while giving the family two // selectors that can drift apart. One definition, spelled at every site that reads it. inline Bool VertexInputReadsRecords() { - return MG_Config::DataArmIsRecord() && - VertexInputSubsystemEnabled(); + return VertexInputSubsystemEnabled(); } // Registered as the frontend's BufferBackendOps at backend init and on diff --git a/MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp b/MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp index 5d7cd985..174fe0df 100644 --- a/MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp +++ b/MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp @@ -289,15 +289,13 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl { // an empty slot, and not the arm test (ID-110: the arm was decided by the transport // above, never inferred from a null). Uint BoundDrawIndirectBufferId() { - if (MG_Config::DataArmIsRecord()) { - const MG_Pipe::MGPipeHandle res = MG_Pipe::MGPipeApplier().VerbIndirectBuffer; - if (MG_Pipe::MGPipeHandleIsNull(res)) return 0; - auto* resource = BufferImpl::EnsureBufferResourceForHandle(nullptr, res); - if (resource == nullptr) { - RefuseMissingIndirectBufferRecord("BoundDrawIndirectBufferId", res); - } - return resource->id; + const MG_Pipe::MGPipeHandle res = MG_Pipe::MGPipeApplier().VerbIndirectBuffer; + if (MG_Pipe::MGPipeHandleIsNull(res)) return 0; + auto* resource = BufferImpl::EnsureBufferResourceForHandle(nullptr, res); + if (resource == nullptr) { + RefuseMissingIndirectBufferRecord("BoundDrawIndirectBufferId", res); } + return resource->id; // MONOLITH GLUE from here down, token for token what this function did before. const auto& indirect = MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); diff --git a/MobileGL/MG_Backend/DirectGLES/Utils.cpp b/MobileGL/MG_Backend/DirectGLES/Utils.cpp index c8cc59f8..4dcba653 100644 --- a/MobileGL/MG_Backend/DirectGLES/Utils.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Utils.cpp @@ -2334,9 +2334,7 @@ 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 = - MG_Config::DataArmIsRecord() - ? SplitReadbackPackBuffer() : - MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); + SplitReadbackPackBuffer(); // 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_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index 047dc230..8f1327b1 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -39,10 +39,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { namespace { void RejectWireLegacyBuffer() { - if (MG_Config::DataArmIsRecord()) { - MGLOG_F("MGPipe: Fatal{RoleViolation, \"buffer-legacy-arm\"} (Magma P7 buffer consumer)"); - std::abort(); - } + MGLOG_F("MGPipe: Fatal{RoleViolation, \"buffer-legacy-arm\"} (Magma P7 buffer consumer)"); + std::abort(); } // Generation of the live VulkanRenderer instance, mirroring // DirectGLES's g_syncContextGeneration. BackendObject_DirectVulkan @@ -395,19 +393,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { } void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) { - if (MG_Config::DataArmIsRecord()) { - DrawWireIndirect(mode, type, indirect, drawcount, stride, true); - return; - } + DrawWireIndirect(mode, type, indirect, drawcount, stride, true); + return; MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawElementsIndirect called with null VulkanRenderer"); MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::MultiDrawElementsIndirect called with null GL context"); pVulkanRenderer->MultiDrawElementsIndirect(mode, type, indirect, drawcount, stride); } void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) { - if (MG_Config::DataArmIsRecord()) { - DrawWireIndirect(mode, 0, indirect, drawcount, stride, false); - return; - } + DrawWireIndirect(mode, 0, indirect, drawcount, stride, false); + return; MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawArraysIndirect called with null VulkanRenderer"); MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::MultiDrawArraysIndirect called with null GL context"); @@ -459,20 +453,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { } void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride) { - if (MG_Config::DataArmIsRecord()) { - DrawWireIndirect(mode, type, indirect, maxdrawcount, stride, true, true, drawcount); - return; - } + DrawWireIndirect(mode, type, indirect, maxdrawcount, stride, true, true, drawcount); + return; MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawElementsIndirectCount called with null VulkanRenderer"); MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::MultiDrawElementsIndirectCount called with null GL context"); pVulkanRenderer->MultiDrawElementsIndirectCount(mode, type, indirect, drawcount, maxdrawcount, stride); } void MultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride) { - if (MG_Config::DataArmIsRecord()) { - DrawWireIndirect(mode, 0, indirect, maxdrawcount, stride, false, true, drawcount); - return; - } + DrawWireIndirect(mode, 0, indirect, maxdrawcount, stride, false, true, drawcount); + return; MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawArraysIndirectCount called with null VulkanRenderer"); MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::MultiDrawArraysIndirectCount called with null GL context"); @@ -545,10 +535,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { DrawElementsInstancedBaseVertexBaseInstance(mode, count, type, indices, instancecount, 0, 0); } void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect) { - if (MG_Config::DataArmIsRecord()) { - DrawWireIndirect(mode, type, indirect, 1, 0, true); - return; - } + DrawWireIndirect(mode, type, indirect, 1, 0, true); + return; MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawElementsIndirect called with null VulkanRenderer"); MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::DrawElementsIndirect called with null GL context"); @@ -608,10 +596,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { DrawArraysInstancedBaseInstance(mode, first, count, instancecount, 0); } void DrawArraysIndirect(GLenum mode, const void* indirect) { - if (MG_Config::DataArmIsRecord()) { - DrawWireIndirect(mode, 0, indirect, 1, 0, false); - return; - } + DrawWireIndirect(mode, 0, indirect, 1, 0, false); + return; MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawArraysIndirect called with null VulkanRenderer"); MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::DrawArraysIndirect called with null GL context"); @@ -684,10 +670,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // DispatchComputeIndirect takes its transport branch into DispatchWireComputeIndirect // (WireDraw.inc), which issues vkCmdDispatchIndirect from the verb's store. The CPU read // that was here (ReadWireBuffer, a whole-GPU wait once a shader had written the store) is gone. - if (MG_Config::DataArmIsRecord()) { - pVulkanRenderer->DispatchComputeIndirect(indirect); - return; - } + pVulkanRenderer->DispatchComputeIndirect(indirect); + return; MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DispatchComputeIndirect called with null VulkanRenderer"); MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::DispatchComputeIndirect called with null GL context"); pVulkanRenderer->DispatchComputeIndirect(indirect); @@ -749,23 +733,21 @@ namespace MobileGL::MG_Backend::DirectVulkan { } void ShaderStorageBlockBinding(GLuint program, const GLchar* storageBlockName, GLuint storageBlockBinding) { - if (MG_Config::DataArmIsRecord()) { - auto& state = MG_Pipe::MGPipeApplier(); - const auto handle = state.VerbStorageBlockProgram; - if (handle.Slot >= state.ShaderCsos.size() || !state.ShaderCsos[handle.Slot].Live || - state.ShaderCsos[handle.Slot].Gen != handle.Gen || !storageBlockName) { - MGLOG_F("MGPipe: Fatal{UnmigratedVerb, \"Magma:storage-block-program-record\"}"); - std::abort(); - } - auto& record = state.ShaderCsos[handle.Slot]; - auto found = std::find_if(record.StorageOverrides.begin(), record.StorageOverrides.end(), - [&](const auto& entry) { return entry.Name == storageBlockName; }); - if (found == record.StorageOverrides.end()) - record.StorageOverrides.push_back({storageBlockName, static_cast(storageBlockBinding)}); - else found->Binding = static_cast(storageBlockBinding); - ++record.BindingsSerial; - return; + auto& state = MG_Pipe::MGPipeApplier(); + const auto handle = state.VerbStorageBlockProgram; + if (handle.Slot >= state.ShaderCsos.size() || !state.ShaderCsos[handle.Slot].Live || + state.ShaderCsos[handle.Slot].Gen != handle.Gen || !storageBlockName) { + MGLOG_F("MGPipe: Fatal{UnmigratedVerb, \"Magma:storage-block-program-record\"}"); + std::abort(); } + auto& record = state.ShaderCsos[handle.Slot]; + auto found = std::find_if(record.StorageOverrides.begin(), record.StorageOverrides.end(), + [&](const auto& entry) { return entry.Name == storageBlockName; }); + if (found == record.StorageOverrides.end()) + record.StorageOverrides.push_back({storageBlockName, static_cast(storageBlockBinding)}); + else found->Binding = static_cast(storageBlockBinding); + ++record.BindingsSerial; + return; auto* programObject = TryGetDirectVulkanProgram(program); if (!programObject || storageBlockName == nullptr) return; const Int maxBindings = @@ -852,28 +834,26 @@ namespace MobileGL::MG_Backend::DirectVulkan { // memory) into uint32 values with the loop-closing first index appended. static Bool BuildClosedLineLoopIndices(GLsizei count, GLenum type, const void* indices, Vector& outIndices) { - if (MG_Config::DataArmIsRecord()) { - const SizeT width = MG_Util::GetGLTypeSize(type); - if ((width != 1 && width != 2 && width != 4) || count < 2) return false; - const auto& bound = MG_Pipe::MGPipeApplier().IndexBuffer; - Vector owned; - const Uint8* bytes = static_cast(indices); - if (!MG_Pipe::MGPipeHandleIsNull(bound.Res)) { - owned.resize(static_cast(count) * width); - const Uint64 offset = bound.Offset + reinterpret_cast(indices); - if (offset < bound.Offset || !pVulkanRenderer->GetWireBufferManager().ReadWireBuffer( - bound.Res, offset, owned.size(), owned.data())) return false; - bytes = owned.data(); - } - if (!bytes) return false; - outIndices.resize(static_cast(count) + 1); - for (GLsizei i = 0; i < count; ++i) { - outIndices[i] = 0; - Memcpy(&outIndices[i], bytes + static_cast(i) * width, width); - } - outIndices[count] = outIndices[0]; - return true; + const SizeT width = MG_Util::GetGLTypeSize(type); + if ((width != 1 && width != 2 && width != 4) || count < 2) return false; + const auto& bound = MG_Pipe::MGPipeApplier().IndexBuffer; + Vector owned; + const Uint8* bytes = static_cast(indices); + if (!MG_Pipe::MGPipeHandleIsNull(bound.Res)) { + owned.resize(static_cast(count) * width); + const Uint64 offset = bound.Offset + reinterpret_cast(indices); + if (offset < bound.Offset || !pVulkanRenderer->GetWireBufferManager().ReadWireBuffer( + bound.Res, offset, owned.size(), owned.data())) return false; + bytes = owned.data(); } + if (!bytes) return false; + outIndices.resize(static_cast(count) + 1); + for (GLsizei i = 0; i < count; ++i) { + outIndices[i] = 0; + Memcpy(&outIndices[i], bytes + static_cast(i) * width, width); + } + outIndices[count] = outIndices[0]; + return true; const SizeT indexSize = MG_Util::GetGLTypeSize(type); if (indexSize == 0 || count < 2) { return false; @@ -959,23 +939,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { // batch that command buffer belongs to - the handle SetupDraw is holding goes stale, and // the next vkCmd* records into a command buffer that is no longer the frame's. Reconciling // HERE, before SetupDraw starts recording, keeps the wait out of the recording. - if (!MG_Config::DataArmIsRecord()) { - const auto& currentVAO = MG_Pipe::gPipeInputs.GetBoundVertexArray(); - Bool clientArray = false; - if (currentVAO) { - for (const auto& attribute : currentVAO->GetAllAttributes()) { - if (attribute.Enabled && !attribute.Buffer) { - clientArray = true; - break; - } - } - } - if (clientArray) { - if (const auto& elementBuffer = currentVAO->GetIndexBufferBindingSlot().GetBoundObject()) { - elementBuffer->SyncGpuWrites(); - } - } - } pVulkanRenderer->DrawElements(payload); } @@ -1028,9 +991,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // MultiDrawIndexedCmd left the client-memory shape addressing a view whose byte // offset is a hardcoded 0, so UploadAndBindIndexBuffer saw a null client pointer, // declined the whole batch and painted nothing.) - const Bool noIndexBuffer = MG_Config::DataArmIsRecord() - ? MG_Pipe::MGPipeHandleIsNull(MG_Pipe::MGPipeApplier().IndexBuffer.Res) - : MG_Pipe::gPipeInputs.GetBoundVertexArray()->GetIndexBufferBindingSlot().GetBoundObject() == nullptr; + const Bool noIndexBuffer = MG_Pipe::MGPipeHandleIsNull(MG_Pipe::MGPipeApplier().IndexBuffer.Res); if (noIndexBuffer) { for (GLsizei i = 0; i < drawcount; ++i) { if (count[i] <= 0) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp index d9d15f58..f5736264 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp @@ -129,7 +129,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Split staging may also be the destination of a GPU UBO range copy. // Persistent host writes must be coherent: Upload deliberately writes // the mapped pointer without a per-range VMA flush. - if (MG_Config::DataArmIsRecord() && m_desc.persistentlyMapped) { + if (m_desc.persistentlyMapped) { bufferDesc.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; } if (!buffer.Create(bufferDesc)) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp index 6820e496..2bc171d1 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp @@ -225,8 +225,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // A layout transition writes the image too. Order it after the wire // render pass's storeOp, clears and copies instead of relying on the old // per-draw queue-idle path. This applies even when contents are discarded. - if (MG_Config::DataArmIsRecord()) - presentBarrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT; + presentBarrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT; presentBarrier.dstAccessMask = 0; presentBarrier.oldLayout = oldLayout; presentBarrier.newLayout = presentLayout; @@ -239,8 +238,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { presentBarrier.subresourceRange.baseArrayLayer = 0; presentBarrier.subresourceRange.layerCount = 1; vkCmdPipelineBarrier(commandBuffer, - MG_Config::DataArmIsRecord() - ? VK_PIPELINE_STAGE_ALL_COMMANDS_BIT : VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &presentBarrier); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp index d4c76e15..f1650e49 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp @@ -586,15 +586,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_bufferManager = bufferManager; m_programFactory = programFactory; m_minDynamicOffsetAlignment = std::max(1, minUniformBufferOffsetAlignment); - if (MG_Config::DataArmIsRecord()) { - VkPhysicalDeviceProperties properties{}; - vkGetPhysicalDeviceProperties(m_physicalDevice, &properties); - m_wireStorageOffsetAlignment = std::max(1, properties.limits.minStorageBufferOffsetAlignment); - m_wireTexelOffsetAlignment = std::max(1, properties.limits.minTexelBufferOffsetAlignment); - m_wireMaxUniformRange = properties.limits.maxUniformBufferRange; - m_wireMaxStorageRange = properties.limits.maxStorageBufferRange; - m_wireMaxTexelElements = properties.limits.maxTexelBufferElements; - } + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(m_physicalDevice, &properties); + m_wireStorageOffsetAlignment = std::max(1, properties.limits.minStorageBufferOffsetAlignment); + m_wireTexelOffsetAlignment = std::max(1, properties.limits.minTexelBufferOffsetAlignment); + m_wireMaxUniformRange = properties.limits.maxUniformBufferRange; + m_wireMaxStorageRange = properties.limits.maxStorageBufferRange; + m_wireMaxTexelElements = properties.limits.maxTexelBufferElements; m_frameCount = frameCount; m_maxBindings = maxBindings; m_samplerResolveMemo.assign(m_maxBindings, SamplerResolveMemo{}); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp index bac5bf21..41408c64 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp @@ -800,15 +800,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (m_physicalDevice == VK_NULL_HANDLE || format == VK_FORMAT_UNDEFINED) { return false; } - if (MG_Config::DataArmIsRecord()) { - const auto found = m_wireVertexFormatSupport.find(format); - if (found != m_wireVertexFormatSupport.end()) return found->second; - VkFormatProperties properties{}; - vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &properties); - const Bool supported = (properties.bufferFeatures & VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT) != 0; - m_wireVertexFormatSupport.emplace(format, supported); - return supported; - } + const auto found = m_wireVertexFormatSupport.find(format); + if (found != m_wireVertexFormatSupport.end()) return found->second; + VkFormatProperties properties{}; + vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &properties); + const Bool supported = (properties.bufferFeatures & VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT) != 0; + m_wireVertexFormatSupport.emplace(format, supported); + return supported; VkFormatProperties properties{}; vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &properties); return (properties.bufferFeatures & VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT) != 0; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp index 7b49956a..41d8c720 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp @@ -235,9 +235,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } // namespace void VkBufferManager::RegisterWireResourceOps() { - if (MG_Config::DataArmIsRecord()) { - MG_Pipe::MGPipeSetResourceOps(&g_vulkanWireResourceOps); - } + MG_Pipe::MGPipeSetResourceOps(&g_vulkanWireResourceOps); } namespace { @@ -1383,7 +1381,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { .frameCount = m_initInfo.frameCount, .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | - (MG_Config::DataArmIsRecord() ? VkBufferUsageFlags{VK_BUFFER_USAGE_TRANSFER_DST_BIT} : 0u) | + (VkBufferUsageFlags{VK_BUFFER_USAGE_TRANSFER_DST_BIT}) | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, .memoryUsage = m_initInfo.transientMemoryUsage, .allocationFlags = m_initInfo.transientAllocationFlags, diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index da9c3505..f6083eef 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -3127,12 +3127,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { inline ProgramFactory::CompileOptionFlags GetShaderTransformFlags(VkSurfaceTransformFlagBitsKHR preTransform) { ProgramFactory::CompileOptionFlags flags = ProgramFactory::CompileOptionBit::PositionZRemap; - const Bool wire = MG_Config::DataArmIsRecord(); - const auto* wireFbo = wire ? MG_Pipe::MGPipeApplier().DrawFramebuffer() : nullptr; - const auto currentDrawFBO = wire ? SharedPtr{} : - MG_Pipe::gPipeInputs.GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); - const Bool isDefault = wire ? (wireFbo && wireFbo->IsDefault) : - (currentDrawFBO && currentDrawFBO->IsDefaultFramebuffer()); + const auto* wireFbo = MG_Pipe::MGPipeApplier().DrawFramebuffer(); + const Bool isDefault = wireFbo && wireFbo->IsDefault; if (isDefault) { flags |= ProgramFactory::CompileOptionBit::PositionYFlip; // gl_FragCoord follows the same rule the default-framebuffer RECTANGLES follow @@ -5010,28 +5006,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { // (stencil) test always passes and nothing is written - even when the bound // image is a packed depth-stencil texture attached through only one half. { - if (MG_Config::DataArmIsRecord()) { - const auto* fbo = MG_Pipe::MGPipeApplier().DrawFramebuffer(); - if (fbo && !fbo->IsDefault) { - if (fbo->Depth.Kind == MG_Pipe::kMGPipeSurfaceKindNone) depthTestEnabled = false; - if (fbo->Stencil.Kind == MG_Pipe::kMGPipeSurfaceKindNone) stencilTestEnabled = false; - } - } else - { - const auto& gatingFbo = - MG_Pipe::gPipeInputs.GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); - if (gatingFbo != nullptr && !gatingFbo->IsDefaultFramebuffer()) { - const auto& depthAtt = gatingFbo->GetAttachment(MobileGL::FramebufferAttachmentType::Depth); - const auto& stencilAtt = gatingFbo->GetAttachment(MobileGL::FramebufferAttachmentType::Stencil); - if (!depthAtt.IsValid() || depthAtt.IsEmpty()) { - depthTestEnabled = false; - } - if (!stencilAtt.IsValid() || stencilAtt.IsEmpty()) { - stencilTestEnabled = false; - } + const auto* fbo = MG_Pipe::MGPipeApplier().DrawFramebuffer(); + if (fbo && !fbo->IsDefault) { + if (fbo->Depth.Kind == MG_Pipe::kMGPipeSurfaceKindNone) depthTestEnabled = false; + if (fbo->Stencil.Kind == MG_Pipe::kMGPipeSurfaceKindNone) stencilTestEnabled = false; } } - } const StencilFaceState& frontStencil = MG_Pipe::gPipeInputs.GetStencilState(StencilFace::Front); const StencilFaceState& backStencil = MG_Pipe::gPipeInputs.GetStencilState(StencilFace::Back); const VkPolygonMode requestedPolygonMode = @@ -5240,8 +5220,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(payload.colorAttachmentCount <= PipelineFactory::PipelineCreatePayload::kMaxColorAttachments, "GetOrCreatePipeline: colorAttachmentCount=%u exceeds payload capacity", payload.colorAttachmentCount); - const auto* wireFbo = MG_Config::DataArmIsRecord() ? - MG_Pipe::MGPipeApplier().DrawFramebuffer() : nullptr; + const auto* wireFbo = MG_Pipe::MGPipeApplier().DrawFramebuffer(); const auto drawFboBinding = wireFbo ? SharedPtr{} : MG_Pipe::gPipeInputs.GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); MOBILEGL_ASSERT(wireFbo != nullptr || drawFboBinding != nullptr, "GetOrCreatePipeline: draw framebuffer is null"); @@ -5903,10 +5882,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // the SAME draw-framebuffer binding the draw uses - see the assert below. MOBILEGL_ASSERT( [&] { - if (MG_Config::DataArmIsRecord()) { - const auto* fbo = MG_Pipe::MGPipeApplier().DrawFramebuffer(); - return isDefaultFbo == (fbo != nullptr && fbo->IsDefault); - } + const auto* fbo = MG_Pipe::MGPipeApplier().DrawFramebuffer(); + return isDefaultFbo == (fbo != nullptr && fbo->IsDefault); const auto& fbo = MG_Pipe::gPipeInputs.GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); return isDefaultFbo == (fbo != nullptr && fbo->IsDefaultFramebuffer()); @@ -6332,14 +6309,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { // split server records every draw its client sends into the same frame command buffer, so // the same loading frame grows it the same way there. SplitOversizedRecording(); - if (MG_Config::DataArmIsRecord()) { - if (!RewindWireDescriptorSetsIfDue()) return false; - // The wire route returns before the monolith branch's draw-gated - // sweep. Dead wire texture/renderbuffer records otherwise live - // until the next frame boundary, which a long trace may not reach. - m_textureManager->CollectGarbage(); - return SetupWireDraw(frame, mode, aspects, drawParams, pIndexBufferView); - } + if (!RewindWireDescriptorSetsIfDue()) return false; + // The wire route returns before the monolith branch's draw-gated + // sweep. Dead wire texture/renderbuffer records otherwise live + // until the next frame boundary, which a long trace may not reach. + m_textureManager->CollectGarbage(); + return SetupWireDraw(frame, mode, aspects, drawParams, pIndexBufferView); // Sync each sampled texture at most once across this whole draw: the layout // probe loop, the post-transition loop, and ResolveSamplerDescriptor would // otherwise each re-run the full SyncTexture path on the same textures. @@ -6965,12 +6940,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { void VulkanRenderer::DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ) { // Before the wire branch, for SetupDraw's reason. SplitOversizedRecording(); - if (MG_Config::DataArmIsRecord()) { - if (!RewindWireDescriptorSetsIfDue()) return; - m_textureManager->CollectGarbage(); - DispatchWireCompute(numGroupsX, numGroupsY, numGroupsZ); - return; - } + if (!RewindWireDescriptorSetsIfDue()) return; + m_textureManager->CollectGarbage(); + DispatchWireCompute(numGroupsX, numGroupsY, numGroupsZ); + return; m_textureManager->CollectGarbage(); auto& frame = m_frameContext.GetCurrent(); // The DISPATCH accessor: with a pipeline bound this is its compute stage program @@ -7029,12 +7002,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { void VulkanRenderer::DispatchComputeIndirect(GLintptr indirect) { SplitOversizedRecording(); // P8-SV: the wire arm's own indirect dispatch, DispatchCompute's shape one call over. - if (MG_Config::DataArmIsRecord()) { - if (!RewindWireDescriptorSetsIfDue()) return; - m_textureManager->CollectGarbage(); - DispatchWireComputeIndirect(indirect); - return; - } + if (!RewindWireDescriptorSetsIfDue()) return; + m_textureManager->CollectGarbage(); + DispatchWireComputeIndirect(indirect); + return; m_textureManager->CollectGarbage(); auto& frame = m_frameContext.GetCurrent(); // See DispatchCompute: the dispatch accessor, not the draw one. @@ -7139,9 +7110,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { vkCmdPipelineBarrier(frame.commandBuffer, // ALL_COMMANDS does not include HOST. The barrier's // HOST_WRITE access must have a matching source stage. - MG_Config::DataArmIsRecord() - ? VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT - : VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr); } @@ -7301,17 +7270,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { #include "WireYuvImage.inc" void VulkanRenderer::Clear(GLbitfield mask) { - if (MG_Config::DataArmIsRecord()) { - const auto* fbo = MG_Pipe::MGPipeApplier().DrawFramebuffer(); - if (!fbo) MagmaWireFatal("clear-framebuffer-record"); - ClearAttachmentPayload payload{}; - payload.mask = mask; - payload.color = MG_Pipe::gPipeInputs.GetClearColor(); - payload.depth = MG_Pipe::gPipeInputs.GetClearDepth(); - payload.stencil = MG_Pipe::gPipeInputs.GetClearStencil(); - ClearWireFramebuffer(*fbo, payload); - return; - } + const auto* fbo = MG_Pipe::MGPipeApplier().DrawFramebuffer(); + if (!fbo) MagmaWireFatal("clear-framebuffer-record"); + ClearAttachmentPayload payload{}; + payload.mask = mask; + payload.color = MG_Pipe::gPipeInputs.GetClearColor(); + payload.depth = MG_Pipe::gPipeInputs.GetClearDepth(); + payload.stencil = MG_Pipe::gPipeInputs.GetClearStencil(); + ClearWireFramebuffer(*fbo, payload); + return; m_clearManager->CollectGarbage(); if ((mask & (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) == 0) { return; @@ -7703,12 +7670,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { void VulkanRenderer::QueueClearBufferPayload(GLenum buffer, GLint drawbuffer, const ClearAttachmentPayload& clearPayload) { - if (MG_Config::DataArmIsRecord()) { - const auto* fbo = MG_Pipe::MGPipeApplier().DrawFramebuffer(); - if (!fbo) MagmaWireFatal("clear-buffer-framebuffer-record"); - ClearWireFramebuffer(*fbo,clearPayload,buffer == GL_COLOR ? drawbuffer : -1); - return; - } + const auto* fbo = MG_Pipe::MGPipeApplier().DrawFramebuffer(); + if (!fbo) MagmaWireFatal("clear-buffer-framebuffer-record"); + ClearWireFramebuffer(*fbo,clearPayload,buffer == GL_COLOR ? drawbuffer : -1); + return; auto* fbo = MG_Pipe::gPipeInputs.GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject().get(); if (!fbo) { return; @@ -8727,10 +8692,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { void VulkanRenderer::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) { - if (MG_Config::DataArmIsRecord()) { - BlitWireFramebuffers(srcX0,srcY0,srcX1,srcY1,dstX0,dstY0,dstX1,dstY1,mask,filter); - return; - } + BlitWireFramebuffers(srcX0,srcY0,srcX1,srcY1,dstX0,dstY0,dstX1,dstY1,mask,filter); + return; auto readFbo = MG_Pipe::gPipeInputs.GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); auto drawFbo = MG_Pipe::gPipeInputs.GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); BlitNamedFramebuffer(readFbo, drawFbo, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); @@ -9367,10 +9330,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { void VulkanRenderer::CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) { - if (MG_Config::DataArmIsRecord()) { - CopyWireFramebufferToTexture(target,level,xoffset,yoffset,x,y,width,height); - return; - } + CopyWireFramebufferToTexture(target,level,xoffset,yoffset,x,y,width,height); + return; if (width <= 0 || height <= 0) { return; } @@ -9684,8 +9645,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } Uint CopyImageEndpointName(const CopyImageEndpoint& endpoint) { - if (MG_Config::DataArmIsRecord()) - return endpoint.IsRenderbuffer() ? endpoint.RenderbufferHandle.Slot : endpoint.TextureHandle.Slot; + return endpoint.IsRenderbuffer() ? endpoint.RenderbufferHandle.Slot : endpoint.TextureHandle.Slot; if (endpoint.IsRenderbuffer()) return endpoint.Renderbuffer->GetExternalIndex(); return endpoint.Texture ? endpoint.Texture->GetExternalIndex() : 0u; } @@ -9696,9 +9656,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { const CopyImageEndpoint& dstEndpoint, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) { - const Bool wire = MG_Config::DataArmIsRecord(); + const Bool wire = true; // P13 W6: the record arm is the only arm MG_Pipe::MGPipeHandle dstStorageHandle = dstEndpoint.TextureHandle; - if (wire && HasPendingRecordedWork() && !FlushPendingCommands()) { + if (HasPendingRecordedWork() && !FlushPendingCommands()) { if (LatchWireDeviceLoss("copy-image-flush")) return; MagmaWireFatal("copy-image-flush"); } @@ -9740,24 +9700,22 @@ namespace MobileGL::MG_Backend::DirectVulkan { // SyncTextureAndGetDescriptor the copy always used; the renderbuffer arm goes through the // render-pass manager, which is where a renderbuffer's VkImage lives. const auto resolveImage = [this](const CopyImageEndpoint& endpoint, CopyImageVkImage& out) { - if (MG_Config::DataArmIsRecord()) { - const Bool renderbuffer = endpoint.IsRenderbuffer(); - // The same handle-keyed allocation used by wire FBO clear/draw/read. - // The legacy render-pass manager owns a different, frontend-keyed map. - auto* resource = m_textureManager->SyncTextureResourceByHandle( - renderbuffer ? endpoint.RenderbufferHandle : endpoint.TextureHandle, renderbuffer); - if (!resource) MagmaWireFatal("copy-image-resource"); - out.isRenderbuffer = renderbuffer; - out.image = resource->image; - out.trackedLayout = &resource->layout; - out.aspect = resource->aspect; - out.mipLevels = resource->mipLevels; - out.extent = resource->extent; - out.depth = resource->depth; - out.arrayLayers = resource->arrayLayers; - out.format = resource->format; - return out.image != VK_NULL_HANDLE; - } + const Bool renderbuffer = endpoint.IsRenderbuffer(); + // The same handle-keyed allocation used by wire FBO clear/draw/read. + // The legacy render-pass manager owns a different, frontend-keyed map. + auto* resource = m_textureManager->SyncTextureResourceByHandle( + renderbuffer ? endpoint.RenderbufferHandle : endpoint.TextureHandle, renderbuffer); + if (!resource) MagmaWireFatal("copy-image-resource"); + out.isRenderbuffer = renderbuffer; + out.image = resource->image; + out.trackedLayout = &resource->layout; + out.aspect = resource->aspect; + out.mipLevels = resource->mipLevels; + out.extent = resource->extent; + out.depth = resource->depth; + out.arrayLayers = resource->arrayLayers; + out.format = resource->format; + return out.image != VK_NULL_HANDLE; if (endpoint.IsRenderbuffer()) { auto* resource = m_renderPassManager->GetOrCreateRenderbufferResource(endpoint.Renderbuffer); if (resource == nullptr) return false; @@ -9988,7 +9946,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } const auto materializeClear = [this, &frame](const CopyImageEndpoint& endpoint) { - if (MG_Config::DataArmIsRecord()) return true; + return true; if (endpoint.IsRenderbuffer()) { return MaterializePendingClearForRenderbuffer(frame.commandBuffer, endpoint.Renderbuffer); } @@ -10260,10 +10218,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { void VulkanRenderer::ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) { - if (MG_Config::DataArmIsRecord()) { - ReadWirePixels(x,y,width,height,format,type,pixels); - return; - } + ReadWirePixels(x,y,width,height,format,type,pixels); + return; if (width <= 0 || height <= 0) { return; } @@ -10958,8 +10914,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Store honoring the client pack state (single slice). const SharedPtr wireReplyHasNoPackBuffer; const auto& pixelPackBufferObject = - MG_Config::DataArmIsRecord() ? wireReplyHasNoPackBuffer : - MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); + wireReplyHasNoPackBuffer; const auto packParams = MG_Pipe::gPipeInputs.GetPixelStoreParameters(false); const SizeT rowPixels = static_cast(packParams.RowLength > 0 ? packParams.RowLength : width); const SizeT packAlignment = packParams.Alignment > 0 ? static_cast(packParams.Alignment) : 1; @@ -11234,10 +11189,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { } void VulkanRenderer::GenerateMipmap(GLenum target) { - if (MG_Config::DataArmIsRecord()) { - GenerateWireMipmap(); - return; - } + GenerateWireMipmap(); + return; const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); // Whatever is left here is a coverage gap in this backend, not a broken invariant, so it // declines (leaving the mip chain unwritten) rather than asserting the process down. What @@ -11341,9 +11294,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // server's staged shadow (keyed by the synced TextureResource above) and the client's // level storage is never written; in monolith the client-object path runs unchanged. const Bool allocatedMipmapStorage = - MG_Config::DataArmIsRecord() - ? EnsureGenerateMipmapShadowAllocated(*resource, baseMipLevel, uploadTargets) - : EnsureGenerateMipmapStorageAllocated(*mipmapTexture, baseMipLevel, endMipLevel); + EnsureGenerateMipmapShadowAllocated(*resource, baseMipLevel, uploadTargets); MOBILEGL_ASSERT(allocatedMipmapStorage, "GenerateMipmap could not allocate a full mip chain for this texture."); resource = m_textureManager->SyncTextureAndGetDescriptor(*texture); @@ -11632,8 +11583,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { "undefined behaviour rather than a capture"); return false; } - if (MG_Config::DataArmIsRecord()) - return BeginWireXfbCaptureForDraw(frame); + return BeginWireXfbCaptureForDraw(frame); const auto& program = MG_Pipe::gPipeInputs.GetTransformFeedbackProgram(); if (!program || program->GetTransformFeedbackVaryingCount() == 0) { return false; @@ -12653,42 +12603,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { vertexRange.instanceCount = 1; // MONOLITH ONLY: with a transport the frontend VAO is not this side's to read at all // (the client owns those bytes and stages them itself), and this runs on the apply thread. - if (!MG_Config::DataArmIsRecord()) { - const auto& currentVAO = MG_Pipe::gPipeInputs.GetBoundVertexArray(); - Bool clientArray = false; - if (currentVAO) { - for (const auto& attribute : currentVAO->GetAllAttributes()) { - if (attribute.Enabled && !attribute.Buffer) { - clientArray = true; - break; - } - } - } - if (clientArray) { - // The commands may be shader-written, and a stale shadow would size the upload - // off words no shader ever wrote. - drawBuffer->SyncGpuWrites(); - const Uint8* commandHostBytes = drawBuffer->MappedData(); - if (commandHostBytes == nullptr) { - MGLOG_E_ONCE("MultiDrawArraysIndirect skipped: a client-memory vertex array needs " - "the commands on the CPU and the indirect buffer has no readable shadow"); - return; - } - Uint64 lastElement = 0; - for (GLsizei idraw = 0; idraw < drawcount; ++idraw) { - VkDrawIndirectCommand command{}; - Memcpy(&command, - commandHostBytes + commandOffset + static_cast(idraw) * static_cast(stride), - sizeof(command)); - lastElement = std::max(lastElement, - static_cast(command.firstVertex) + command.vertexCount); - vertexRange.firstInstance = std::max(vertexRange.firstInstance, command.firstInstance); - vertexRange.instanceCount = std::max(vertexRange.instanceCount, command.instanceCount); - } - vertexRange.firstVertex = 0; - vertexRange.vertexCount = static_cast(lastElement); - } - } if (!SetupDraw(frame, mode, DrawSetupAspect::IndirectDrawBuffer, vertexRange)) { return; @@ -12997,16 +12911,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { return false; } - if (MG_Config::DataArmIsRecord()) { - // Texture uploads submit on this queue with their own fences. The - // renderer watermark alone cannot prove images/views are idle. - if (m_textureManager && !m_textureManager->WireUploadsAreIdle()) return false; - // No preparation, recording or GPU work remains. A minimized Present - // may have abandoned a recording tagged for a submission that will - // never occur, so reclaim those future-tagged objects as well. - CollectWireObjects(m_completedSubmitCounter, true); - ClearAllWireDrawPassCaches(); - } + // Texture uploads submit on this queue with their own fences. The + // renderer watermark alone cannot prove images/views are idle. + if (m_textureManager && !m_textureManager->WireUploadsAreIdle()) return false; + // No preparation, recording or GPU work remains. A minimized Present + // may have abandoned a recording tagged for a submission that will + // never occur, so reclaim those future-tagged objects as well. + CollectWireObjects(m_completedSubmitCounter, true); + ClearAllWireDrawPassCaches(); // Every submission is complete and nothing recorded references the // per-frame transients. Pure-reclaim work runs on every drain: it only @@ -14420,11 +14332,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkPhysicalDeviceDescriptorIndexingProperties descriptorIndexingProperties{}; descriptorIndexingProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_PROPERTIES; const Bool descriptorIndexingCore = -#if MOBILEGL_BUILD_DISAGGREGATED && !defined(VK_USE_PLATFORM_WIN32_KHR) +#if !defined(VK_USE_PLATFORM_WIN32_KHR) // CreateInstance requests Vulkan 1.1 on these platforms. A 1.2+ GPU // does not promote descriptor indexing into that application's core - // API; the wire path must enable VK_EXT_descriptor_indexing instead. - (!MG_Config::DataArmIsRecord()) && + // API, so the record arm enables VK_EXT_descriptor_indexing instead (P13 W6: + // every library, not only the transport build - the old monolith-only `true` + // here treated the 1.2 device as core under a 1.1 instance). + false && #endif m_physicalDevice.properties.apiVersion >= VK_API_VERSION_1_2; const Bool descriptorIndexingExtension = @@ -14446,8 +14360,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_physicalDevice.properties.apiVersion >= VK_API_VERSION_1_1 && IsExtensionSupported(availableExtensions, VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME) && IsExtensionSupported(availableExtensions, VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME); - const Bool wireDepthResolveEnabled = MG_Config::DataArmIsRecord() && - getPhysicalDeviceProperties2 && (wireDepthResolveCore || wireDepthResolveExtensions); + const Bool wireDepthResolveEnabled = getPhysicalDeviceProperties2 && (wireDepthResolveCore || wireDepthResolveExtensions); m_wireCreateRenderPass2 = nullptr; m_wireDepthResolveModes = m_wireStencilResolveModes = 0; if (wireDepthResolveEnabled) { @@ -14470,8 +14383,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // write the stencil aspect without it. Enabled on the same terms as the resolve // extensions above - wire arms only - and the arm declines stencil where it is absent // rather than producing an undefined aspect. - m_wireShaderStencilExport = MG_Config::DataArmIsRecord() && - IsExtensionSupported(availableExtensions, VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME); + m_wireShaderStencilExport = IsExtensionSupported(availableExtensions, VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME); if (m_wireShaderStencilExport) EnableOptionalDeviceExtension(availableExtensions, enabledDeviceExtensions, VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME); @@ -14487,8 +14399,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_wireGetAhbProperties = nullptr; m_sharedImageSyncFd = false; #if defined(__ANDROID__) - if (MG_Config::DataArmIsRecord() && - m_physicalDevice.properties.apiVersion >= VK_API_VERSION_1_1 && + if (m_physicalDevice.properties.apiVersion >= VK_API_VERSION_1_1 && IsExtensionSupported(availableExtensions, VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME) && IsExtensionSupported(availableExtensions, VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME)) { m_wireAhbImport = true; @@ -15038,7 +14949,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { IsExtensionSupported(availableExtensions, VK_EXT_ROBUSTNESS_2_EXTENSION_NAME) ? VK_EXT_ROBUSTNESS_2_EXTENSION_NAME : IsExtensionSupported(availableExtensions, VK_KHR_ROBUSTNESS_2_EXTENSION_NAME) ? VK_KHR_ROBUSTNESS_2_EXTENSION_NAME : nullptr; - if (MG_Config::DataArmIsRecord() && robustness2Name != nullptr && + if (robustness2Name != nullptr && getPhysicalDeviceFeatures2 != nullptr) { VkPhysicalDeviceFeatures2 featureQuery{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2}; featureQuery.pNext = &robustness2Features; @@ -15052,10 +14963,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_wireNullDescriptor = true; } } - if (MG_Config::DataArmIsRecord()) { - MGLOG_I("Magma wire: robustness2 nullDescriptor %s (%s)", m_wireNullDescriptor ? "enabled" : "unavailable", - robustness2Name != nullptr ? robustness2Name : "no robustness2 extension"); - } + MGLOG_I("Magma wire: robustness2 nullDescriptor %s (%s)", m_wireNullDescriptor ? "enabled" : "unavailable", + robustness2Name != nullptr ? robustness2Name : "no robustness2 extension"); deviceCreateInfo.enabledExtensionCount = static_cast(enabledDeviceExtensions.size()); deviceCreateInfo.ppEnabledExtensionNames = enabledDeviceExtensions.data(); @@ -15285,7 +15194,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { // red when the real recording, readback or tally stops detecting it too. void VulkanRenderer::ArmWireDepthResolveOrder() { m_wirePreferShaderDepthResolve = false; - if (!MG_Config::DataArmIsRecord()) return; // ResolveWireDepthStencil's own test for the render-pass arm, less its per-format half // (the probe asks that per format): without it the shader pass is the only arm. const Bool renderPassArmAvailable = !MagmaWireForcedShaderDepthResolve() && m_wireCreateRenderPass2 && diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp index ed164c3a..1852fbe1 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp @@ -1341,7 +1341,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::DataArmIsRecord()) buffer->SyncGpuWrites(); + buffer->SyncGpuWrites(); const Range1D range = bindingPoint.GetRange(); const Uint8* mapped = buffer->MappedData(); if (mapped == nullptr) continue; diff --git a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp index 6f40ed94..9c502324 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp @@ -49,7 +49,7 @@ // SyncGpuWrites, exactly as glMapBufferRange and glGetBufferSubData already do. Monolith keeps its // 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). -#define MGL_SYNC_PIXEL_UNPACK_SOURCE(buffer) do { if (MG_Config::DataArmIsRecord()) (buffer)->SyncGpuWrites(); } while (0) +#define MGL_SYNC_PIXEL_UNPACK_SOURCE(buffer) do { (buffer)->SyncGpuWrites(); } while (0) namespace MobileGL::MG_Impl::GLImpl { static SharedPtr nullTextureObject; @@ -6117,8 +6117,7 @@ namespace MobileGL::MG_Impl::GLImpl { viewObject->SetInternalFormat(viewInternalFormat); viewObject->SetSamples(storageOwner->GetSamples()); viewObject->SetFixedSampleLocations(storageOwner->HasFixedSampleLocations()); - if (MG_Config::DataArmIsRecord()) - MG_Pipe::MGPipeEmitSamplerViewCreate(*viewObject); + MG_Pipe::MGPipeEmitSamplerViewCreate(*viewObject); } void TexStorage1D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width) { @@ -6580,12 +6579,10 @@ namespace MobileGL::MG_Impl::GLImpl { static void GetTextureImageForUploadTarget(const SharedPtr& textureObject, TextureUploadTarget uploadTarget, GLint level, GLenum format, GLenum type, GLsizei bufSize, void* pixels, const char* caller) { - if (MG_Config::DataArmIsRecord()) { - MGP_FILL(GetTextureImage); - MG_Backend::gBackendFunctionsTable.GL.GetTextureImage(textureObject, uploadTarget, level, format, type, - bufSize, pixels); - return; - } + MGP_FILL(GetTextureImage); + MG_Backend::gBackendFunctionsTable.GL.GetTextureImage(textureObject, uploadTarget, level, format, type, + bufSize, pixels); + return; if (MG_Backend::pActiveBackendObject != nullptr && MG_Backend::pActiveBackendObject->GetBackendType() == BackendType::DirectVulkan && MGL_BACKEND_SLOT_LOCAL(GetTextureImage)) { @@ -6607,8 +6604,7 @@ namespace MobileGL::MG_Impl::GLImpl { } auto uploadTarget = GetPrimaryUploadTarget(textureObject); // Unlike glGetTexImage's face target, this DSA call reads all six faces. - if (MG_Config::DataArmIsRecord() && - textureObject->GetTarget() == TextureTarget::TextureCubeMap) + if (textureObject->GetTarget() == TextureTarget::TextureCubeMap) uploadTarget = TextureUploadTarget::Unknown; GetTextureImageForUploadTarget(textureObject, uploadTarget, level, format, type, bufSize, pixels, __func__); @@ -6870,10 +6866,8 @@ 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 (MG_Config::DataArmIsRecord()) { - GetTexImage_Backend(target, level, format, type, pixels); - return; - } + GetTexImage_Backend(target, level, format, type, pixels); + return; if (MGL_BACKEND_SLOT_LOCAL(GetTexImage)) { GetTexImage_Backend(target, level, format, type, pixels); return; diff --git a/MobileGL/MG_Impl/Pipe/PipeFill.cpp b/MobileGL/MG_Impl/Pipe/PipeFill.cpp index 62b2968f..39712b87 100644 --- a/MobileGL/MG_Impl/Pipe/PipeFill.cpp +++ b/MobileGL/MG_Impl/Pipe/PipeFill.cpp @@ -2262,9 +2262,7 @@ namespace MobileGL::MG_Pipe { } Bool PipeInputs::HasOpenTransformFeedbackSpan(Uint64 lifetimeId) const { - if (MG_Config::DataArmIsRecord()) { - return lifetimeId != 0 && MGPipeApplier().StreamOutputSpans.count(lifetimeId) != 0; - } + return lifetimeId != 0 && MGPipeApplier().StreamOutputSpans.count(lifetimeId) != 0; MGP_STICKY_FORWARD_PULL(HasOpenTransformFeedbackSpan); const auto* ctx = LiveContext(); diff --git a/MobileGL/MG_Impl/Pipe/VertexInputEmit.h b/MobileGL/MG_Impl/Pipe/VertexInputEmit.h index 122f9aa7..022e0665 100644 --- a/MobileGL/MG_Impl/Pipe/VertexInputEmit.h +++ b/MobileGL/MG_Impl/Pipe/VertexInputEmit.h @@ -384,13 +384,11 @@ namespace MobileGL::MG_Pipe { vao.GetAttributeBindingIndex(static_cast(i))); // Client addresses never cross the transport. Draw emission snapshots // their referenced elements into owned buffers with a zero byte origin. - if (MG_Config::DataArmIsRecord() && - !vao.GetAttribute(static_cast(i)).Buffer) m_attributes[i].Offset = 0; + if (!vao.GetAttribute(static_cast(i)).Buffer) m_attributes[i].Offset = 0; } for (SizeT i = 0; i < kBindings; ++i) { m_bindingPoints[i] = MGPipeBuildVertexBindingPointWire(vao.GetBindingPoint(static_cast(i))); - if (MG_Config::DataArmIsRecord() && - !vao.GetBindingPoint(static_cast(i)).Buffer) m_bindingPoints[i].Offset = 0; + if (!vao.GetBindingPoint(static_cast(i)).Buffer) m_bindingPoints[i].Offset = 0; } // Attributes first, then binding points, both ascending and contiguous. constexpr SizeT kAttribBytes = kAttribs * sizeof(MGPVertexAttribWire); diff --git a/MobileGL/MG_IntegrationTest/CMakeLists.txt b/MobileGL/MG_IntegrationTest/CMakeLists.txt index fb822d62..9616df3b 100644 --- a/MobileGL/MG_IntegrationTest/CMakeLists.txt +++ b/MobileGL/MG_IntegrationTest/CMakeLists.txt @@ -73,6 +73,7 @@ add_executable(MobileGLIntegrationTest Scenarios/AsyncCompileScenario.cpp Scenarios/ArbShaderObjectsScenario.cpp Scenarios/CubeFaceReadbackScenario.cpp + Scenarios/TextureUnitSamplingScenario.cpp Scenarios/XfbAfterClipDistanceScenario.cpp Scenarios/UnwrittenPositionOutputScenario.cpp Scenarios/SampleMaskScopeScenario.cpp @@ -2456,6 +2457,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED) mgl_itest_register_split_arms("ArbShaderObjectsScenario.*" "") # P13 W6: the cube-face readback rules the deleted frontend-arm unit cases pinned. mgl_itest_register_split_arms("CubeFaceReadbackScenario.*" "") + mgl_itest_register_split_arms("TextureUnitSamplingScenario.*" "") mgl_itest_register_split_arms("TriangleScenario.*" "") mgl_itest_register_split_arms("PersistentCoherentMapScenario.*" "") # P11 A2 (MG_Remote/CONTRACT-P11.md §2): the >= 16 MiB arena on all three arms. Its tier case diff --git a/MobileGL/MG_IntegrationTest/Scenarios/TextureUnitSamplingScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/TextureUnitSamplingScenario.cpp new file mode 100644 index 00000000..8da54c76 --- /dev/null +++ b/MobileGL/MG_IntegrationTest/Scenarios/TextureUnitSamplingScenario.cpp @@ -0,0 +1,211 @@ +// MobileGL - MobileGL/MG_IntegrationTest/Scenarios/TextureUnitSamplingScenario.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header +// +// P13 W6: WHAT A DRAW SAMPLES FROM A TEXTURE UNIT, ON A REAL CONTEXT. Two GL 4.6 rules that were +// pinned by DirectGLESSanity unit cases driving the monolith frontend arm's unit walk +// (BindCurrentTextures over the frontend's binding slots). That arm is gone - every library walks +// the units by handle now - so the rules are asserted here, through the whole pipe, on both +// backends and every arm the scenarios run on: +// +// * A multisample texture is fetched, never filtered, so the mip-chain completeness rules do +// not apply to it (GL 4.6 8.17) even though its MIN_FILTER keeps the initial +// NEAREST_MIPMAP_LINEAR. Calling it incomplete leaves the unit unbound, and every texelFetch +// reads zero from a texture that was written correctly (KHR-GL43.compute_shader.resource- +// texture's shape: clear through an FBO, then read through sampler2DMS). +// * Binding texture 0 to a unit samples the unit's default texture, which has no image and is +// therefore incomplete: (0, 0, 0, 1) (GL 4.6 8.17.1). The texture bound there before must not +// keep answering. + +#include +#include +#include + +#include "../Harness/HeadlessGL.h" +#include "../Harness/ScenarioFixture.h" + +#ifdef GLAPI +#undef GLAPI +#endif +#define GL_GLEXT_PROTOTYPES +#include +#include +#undef GL_GLEXT_PROTOTYPES + +namespace MGITest { + namespace { + + constexpr GLsizei kEdge = 4; + + constexpr const char* kVertexSource = R"(#version 330 core +void main() { + vec2 corner = vec2((gl_VertexID << 1) & 2, gl_VertexID & 2); + gl_Position = vec4(corner * 2.0 - 1.0, 0.0, 1.0); +} +)"; + + constexpr const char* kFetchMultisampleSource = R"(#version 330 core +uniform sampler2DMS u_texture; +out vec4 o_color; +void main() { o_color = texelFetch(u_texture, ivec2(gl_FragCoord.xy), 0); } +)"; + + constexpr const char* kSample2DSource = R"(#version 330 core +uniform sampler2D u_texture; +out vec4 o_color; +void main() { o_color = texture(u_texture, vec2(0.5, 0.5)); } +)"; + + class TextureUnitSamplingScenario : public ScenarioTest { + protected: + void SetUp() override { + ScenarioTest::SetUp(); + if (!Ready()) return; + DrainErrors(); + glGenVertexArrays(1, &m_vao); + glBindVertexArray(m_vao); + glGenTextures(1, &m_target); + glBindTexture(GL_TEXTURE_2D, m_target); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kEdge, kEdge); + glBindTexture(GL_TEXTURE_2D, 0); + glGenFramebuffers(1, &m_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_target, 0); + glViewport(0, 0, kEdge, kEdge); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + glPixelStorei(GL_PACK_ALIGNMENT, 4); + } + + void TearDown() override { + if (!Ready()) return; + glUseProgram(0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + if (m_fbo != 0) glDeleteFramebuffers(1, &m_fbo); + if (m_msFbo != 0) glDeleteFramebuffers(1, &m_msFbo); + if (m_target != 0) glDeleteTextures(1, &m_target); + if (m_source != 0) glDeleteTextures(1, &m_source); + if (m_program != 0) glDeleteProgram(m_program); + if (m_vao != 0) glDeleteVertexArrays(1, &m_vao); + DrainErrors(); + ScenarioTest::TearDown(); + } + + static void DrainErrors() { + for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) { + } + } + + static GLuint Compile(GLenum stage, const char* source) { + const GLuint shader = glCreateShader(stage); + glShaderSource(shader, 1, &source, nullptr); + glCompileShader(shader); + GLint ok = GL_FALSE; + glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); + if (ok != GL_TRUE) { + char log[1024] = {}; + glGetShaderInfoLog(shader, sizeof(log), nullptr, log); + ADD_FAILURE() << "shader compile failed: " << log; + } + return shader; + } + + void UseProgram(const char* fragmentSource) { + const GLuint vs = Compile(GL_VERTEX_SHADER, kVertexSource); + const GLuint fs = Compile(GL_FRAGMENT_SHADER, fragmentSource); + m_program = glCreateProgram(); + glAttachShader(m_program, vs); + glAttachShader(m_program, fs); + glLinkProgram(m_program); + glDeleteShader(vs); + glDeleteShader(fs); + GLint ok = GL_FALSE; + glGetProgramiv(m_program, GL_LINK_STATUS, &ok); + ASSERT_EQ(ok, GL_TRUE) << "program link failed"; + glUseProgram(m_program); + glUniform1i(glGetUniformLocation(m_program, "u_texture"), 0); + } + + std::array DrawAndReadCentre() { + glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); + glClearColor(0.5f, 0.5f, 0.5f, 0.5f); + glClear(GL_COLOR_BUFFER_BIT); + glDrawArrays(GL_TRIANGLES, 0, 3); + std::array texel{}; + glReadPixels(kEdge / 2, kEdge / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, texel.data()); + return texel; + } + + GLuint m_vao = 0; + GLuint m_fbo = 0; + GLuint m_msFbo = 0; + GLuint m_target = 0; + GLuint m_source = 0; + GLuint m_program = 0; + }; + + TEST_F(TextureUnitSamplingScenario, AMultisampleTextureIsFetchedDespiteItsDefaultMipmapFilter) { + if (!Ready()) return; + glGenTextures(1, &m_source); + glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, m_source); + glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA8, kEdge, kEdge, GL_FALSE); + GLint minFilter = 0; + glGetTexParameteriv(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_MIN_FILTER, &minFilter); + ASSERT_EQ(minFilter, GL_NEAREST_MIPMAP_LINEAR) + << "the fixture is stale: the initial MIN_FILTER no longer asks for mipmaps"; + + glGenFramebuffers(1, &m_msFbo); + glBindFramebuffer(GL_FRAMEBUFFER, m_msFbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, m_source, 0); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), static_cast(GL_FRAMEBUFFER_COMPLETE)); + glClearColor(1.0f, 0.0f, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + ASSERT_EQ(glGetError(), static_cast(GL_NO_ERROR)) << "clearing the multisample texture failed"; + + UseProgram(kFetchMultisampleSource); + if (HasFatalFailure()) return; + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, m_source); + const auto texel = DrawAndReadCentre(); + EXPECT_EQ(glGetError(), static_cast(GL_NO_ERROR)); + EXPECT_EQ(static_cast(texel[0]), 255) << "red: the multisample texture was not bound"; + EXPECT_EQ(static_cast(texel[1]), 0); + EXPECT_EQ(static_cast(texel[2]), 255) << "blue: the multisample texture was not bound"; + EXPECT_EQ(static_cast(texel[3]), 255); + Gl().EndFrame(); + } + + TEST_F(TextureUnitSamplingScenario, BindingZeroSamplesTheIncompleteDefaultTexture) { + if (!Ready()) return; + glGenTextures(1, &m_source); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_source); + const GLubyte red[4] = {255, 0, 0, 255}; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, red); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + UseProgram(kSample2DSource); + if (HasFatalFailure()) return; + + const auto bound = DrawAndReadCentre(); + ASSERT_EQ(static_cast(bound[0]), 255) << "the control: the bound texture is sampled"; + ASSERT_EQ(static_cast(bound[1]), 0); + + glBindTexture(GL_TEXTURE_2D, 0); + const auto unbound = DrawAndReadCentre(); + EXPECT_EQ(glGetError(), static_cast(GL_NO_ERROR)); + EXPECT_EQ(static_cast(unbound[0]), 0) + << "texture 0 is bound, yet the unit still answers with the texture bound before it"; + EXPECT_EQ(static_cast(unbound[1]), 0); + EXPECT_EQ(static_cast(unbound[2]), 0); + EXPECT_EQ(static_cast(unbound[3]), 255) << "an incomplete texture samples as (0, 0, 0, 1)"; + Gl().EndFrame(); + } + + } // namespace +} // namespace MGITest diff --git a/MobileGL/MG_Pipe/PipeApply.cpp b/MobileGL/MG_Pipe/PipeApply.cpp index ca1dc0da..787e0112 100644 --- a/MobileGL/MG_Pipe/PipeApply.cpp +++ b/MobileGL/MG_Pipe/PipeApply.cpp @@ -1500,7 +1500,7 @@ namespace MobileGL::MG_Pipe { // existing backend hooks authoritative when present, and use this fallback otherwise. void AdoptTextureWithoutBackendHook(const MGPipeResourceRecord& stored, const MGPSubData& upload, const void* bytes, const MGPSubRegion* regions) { - if (!MG_Config::DataArmIsRecord() || !bytes || upload.Blob.Size == 0) return; + if (!bytes || upload.Blob.Size == 0) return; auto& store = MG_Record::ServerStagedTexture(); const auto& desc = stored.Desc; const Uint64 key = MG_Record::StagedTextureStore::KeyForHandle(upload.Res); @@ -1523,7 +1523,6 @@ namespace MobileGL::MG_Pipe { } void DefineTextureWithoutBackendHook(const MGPResourceDesc& desc, const MGPRespecifiedLevel* level) { - if (!MG_Config::DataArmIsRecord()) return; auto& store = MG_Record::ServerStagedTexture(); const Uint64 key = MG_Record::StagedTextureStore::KeyForHandle(desc.Resource); const auto define = [&](Uint16 target, Uint16 mip, const IntVec3& extent) { @@ -1971,8 +1970,7 @@ namespace MobileGL::MG_Pipe { } void MGPipeApplierReleaseObjectRecords() { - if (MG_Config::DataArmIsRecord() && - (g_resourceOps == nullptr || g_resourceOps->TextureDestroy == nullptr)) { + if ((g_resourceOps == nullptr || g_resourceOps->TextureDestroy == nullptr)) { MG_Record::ServerStagedTexture().DropAll(); } // The served context is going away and this applier with it. Under split that is one @@ -2733,9 +2731,8 @@ namespace MobileGL::MG_Pipe { if (static_cast(handle.Kind) == MGPipeKind::Texture) { if (g_resourceOps != nullptr && g_resourceOps->TextureDestroy != nullptr) g_resourceOps->TextureDestroy(handle.Handle); - else if (MG_Config::DataArmIsRecord()) - MG_Record::ServerStagedTexture().Drop( - MG_Record::StagedTextureStore::KeyForHandle(handle.Handle)); + else { MG_Record::ServerStagedTexture().Drop( + MG_Record::StagedTextureStore::KeyForHandle(handle.Handle)); } } if (static_cast(handle.Kind) != MGPipeKind::Buffer) return; if (g_resourceOps != nullptr && g_resourceOps->Destroy != nullptr) { diff --git a/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp b/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp index c67d794b..924f962d 100644 --- a/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp +++ b/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp @@ -535,7 +535,7 @@ namespace MobileGL::MG_State::GLState { // P13 W4a: monolith's record arm refreshes an aliased store through this function too // (Managers.cpp RefreshAliasedStoreFromGpu), and a whole-store landing there is exactly as // current as one under split. - if (MG_Config::DataArmIsRecord() && atOffset == 0 && data.size >= m_size) { + if (atOffset == 0 && data.size >= m_size) { m_gpuWritePending = false; } } diff --git a/MobileGL/MG_Test/Backend/DirectGLES/ContextEpochTest.cpp b/MobileGL/MG_Test/Backend/DirectGLES/ContextEpochTest.cpp index 1343654d..5c155131 100644 --- a/MobileGL/MG_Test/Backend/DirectGLES/ContextEpochTest.cpp +++ b/MobileGL/MG_Test/Backend/DirectGLES/ContextEpochTest.cpp @@ -102,59 +102,6 @@ TEST(ContextEpochTest, RawDepthSamplerOwnsANativeIdForEachContextGeneration) { #endif } -// P3b/P4b wave 2-D package D2, STEP 1 of the raw-depth-fetch sampler monolith cleanup: the case -// above is parameterised over the OTHER arm, so that the claim DirectGLES.cpp:334-336 makes in -// prose - "P5f fs's transport arm below uses only a native sampler with fixed values. The legacy -// pair is not constructed or consulted by the server." - is a test rather than a comment. -// -// STEP 2 OF THAT CLEANUP - deleting what the transport arm makes unreachable - IS NOT DONE HERE -// AND IS RECORDED AS P13. Every candidate (the two file-static SharedPtrs at DirectGLES.cpp:82-83, -// the monolith arm at :361-371, NeedsRawDepthFetchSampler at :393-399 and the pre-handle sampler -// pass at :8015-8024) is UNCONDITIONAL code: none of it sits behind -// `#if !MOBILEGL_BUILD_DISAGGREGATED`, the pull build (DISAGGREGATED=OFF, PIPE_PUSH=OFF) compiles -// and links all of it, and the monolith arm is the ENTIRE body GetRawDepthFetchSampler has there. -// Deleting any of it moves pull `.text` and fails G1's 0/0/0/0 - which is the rule -// ARCHITECTURE.md:340 states for exactly this class of retired-but-compiled code. The package's -// instruction was "only if it keeps the pull build's .text identical"; it does not, so the -// deletion stops here and is filed rather than attempted. -// -// What this case CAN assert without touching a line of library code is that the two arms are -// genuinely two objects, which is the observable form of "not consulted by the server": a server -// that had fallen through to the legacy pair would hand back the same pointer under both -// transports. -TEST(ContextEpochTest, RawDepthSamplerTakesADifferentObjectOnEachArmAndTheServerNeverTakesTheLegacyOne) { -#if MOBILEGL_BUILD_DISAGGREGATED - DriverScope scope; // pins InProcess and bumps the generation, so both arms start cold - samplerParameters = 0; - - // --- the transport-native arm --- - SamplerImpl::BackendSamplerObject* const native = GetRawDepthFetchSampler(); - ASSERT_NE(native, nullptr); - const Uint nativeId = native->GetBackendSamplerId(); - EXPECT_NE(nativeId, 0u); - EXPECT_EQ(samplerParameters, 4u) << "the native arm programs its four fixed values directly"; - - // --- the monolith arm, same entry point, same process --- - MG_Config::Transport = MG_Config::TransportMode::Monolith; - SamplerImpl::BackendSamplerObject* const legacy = GetRawDepthFetchSampler(); - ASSERT_NE(legacy, nullptr); - EXPECT_NE(legacy, native) - << "both transports handed back the SAME BackendSamplerObject, so the server did not take " - "the transport-native arm at all - it fell through to g_rawDepthFetchSamplerBackend, " - "which DirectGLES.cpp:334-336 says it must not touch"; - EXPECT_NE(legacy->GetBackendSamplerId(), nativeId) - << "the two arms share a native sampler id"; - - // --- and back, to prove the selection is live rather than a first-call latch --- - MG_Config::Transport = MG_Config::TransportMode::InProcess; - EXPECT_EQ(GetRawDepthFetchSampler(), native) - << "the transport arm's cached native sampler did not survive a round trip through the " - "monolith arm, so one of the two arms is rebuilding state the other owns"; -#else - GTEST_SKIP() << "both arms exist only in the disaggregated build; the pull build compiles the " - "monolith arm alone and it is the whole function there"; -#endif -} TEST(ContextEpochTest, SameXfbNameInTwoServedContextsDoesNotAliasThePausedSpan) { #if MOBILEGL_BUILD_DISAGGREGATED diff --git a/MobileGL/MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp b/MobileGL/MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp index 80a425c8..162845b3 100644 --- a/MobileGL/MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp +++ b/MobileGL/MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp @@ -173,87 +173,5 @@ TEST(SubsystemDepsTest, TheClientNeverEmitsAFamilyTheTableRefuses) { RecordProperty("client_emitting_combinations", emitted); } -// --------------------------------------------------------------------------------------------- -// 3. THE SERVER CONSUMER GATE agrees with the table at every interesting mask. -// -// The resolvers answer "did this family's handle arm come up", which is the dependency rule AND -// the family's own bit AND, for the framebuffer one, D-C3's separate wire-caps refusal. So the -// comparison is one-directional where it has to be: a resolver may say NO for a reason of its -// own, but it may never say YES at a mask the table refuses - that is the direction in which a -// disagreement is a half-run subsystem rather than an extra refusal. -// --------------------------------------------------------------------------------------------- -TEST(SubsystemDepsTest, TheServerConsumerGateNeverArmsAFamilyTheTableRefuses) { - using namespace MobileGL::MG_Backend::DirectGLES; - // THE RESOLVERS SURVIVE A SWEEP because a refused family takes the (unreachable since P13 W3b) - // legacy verdict and returns false instead of stopping; ConfigLoader refuses any such mask at - // startup, so this sweep exercises the resolvers' own logic, not a reachable configuration. - struct Resolver { - const char* Name; - Uint64 Family; - Bool (*Resolve)(); - }; - const Resolver resolvers[] = { - {"ResolveFramebufferSubsystemArm", kMGPipeSubsystemFramebuffer, - &ResolveFramebufferSubsystemArm}, - {"ResolveTextureResourceSubsystemArm", kMGPipeSubsystemTextureResources, - &ResolveTextureResourceSubsystemArm}, - {"ResolveSamplerSubsystemArm", kMGPipeSubsystemSamplers, &ResolveSamplerSubsystemArm}, - {"ResolveProgramSubsystemArm", kMGPipeSubsystemPrograms, &ResolveProgramSubsystemArm}, - }; - for (const Uint64 mask : InterestingMasks()) { - PushMaskScope scope(mask); - for (const Resolver& resolver : resolvers) { - const Bool armed = resolver.Resolve(); - if (!armed) continue; - EXPECT_TRUE(MGPipeSubsystemDependenciesAreSet(resolver.Family, mask)) - << "at MOBILEGL_PIPE_PUSH=" << Describe(mask) << " the SERVER's " << resolver.Name - << " ARMED the handle arm for family " << Describe(resolver.Family) - << " while MG_Pipe/SubsystemDeps.def says its dependency bits are not all set. " - "The client withholds the family at this mask, so the server is running a " - "handle arm nobody is feeding - which is the half-run subsystem D-K2 exists " - "to make impossible."; - EXPECT_NE(mask & resolver.Family, 0ull) - << resolver.Name << " armed with its own bit clear at " << Describe(mask); - } - } -} -// --------------------------------------------------------------------------------------------- -// 4. ...and the refusal really happens at the two documented lanes, so that test 3 is not -// vacuously green because every resolver said NO for an unrelated reason. -// --------------------------------------------------------------------------------------------- -TEST(SubsystemDepsTest, TheTwoDocumentedRefusalLanesStillRefuseAndTheFullMaskStillArms) { - using namespace MobileGL::MG_Backend::DirectGLES; - { - PushMaskScope scope(kMGPipeSubsystemsMigratedAtP5e); - EXPECT_TRUE(ResolveTextureResourceSubsystemArm()) - << "the texture family's handle arm does not come up at its own phase mask, so every " - "refusal assertion in this file is vacuous"; - EXPECT_TRUE(ResolveSamplerSubsystemArm()); - EXPECT_TRUE(ResolveProgramSubsystemArm()); - // THE FRAMEBUFFER RESOLVER IS RECORDED, NOT ASSERTED, and the asymmetry is the point: - // ResolveFramebufferSubsystemArm folds D-C3's wire-caps refusal in beside the dependency - // rule (Managers.cpp:4323-4325), and that half asks the SERVER's published caps - which a - // bare unit binary with no session and no registered backend cannot answer in the - // affirmative. So a false here says nothing about D-K2 and asserting true would pin - // whatever a test binary happens to have linked. The sweep above still covers it in the - // direction that matters (it may never ARM where the table refuses). - RecordProperty("framebuffer_resolver_at_p5e", ResolveFramebufferSubsystemArm() ? 1 : 0); - } - { - // 0x7ff: bits 0..10, i.e. the texture family WITHOUT the sampler family - D-K2's fourth - // row, and the mask CMakeLists.txt's refusal lane pins. - PushMaskScope scope(0x7ffull); - EXPECT_FALSE(ResolveTextureResourceSubsystemArm()) - << "0x7ff is the mask whose texture family Espryt refuses by name; if it now arms, " - "D-K2's fourth row has been dropped on the server side while the client and the " - ".def still carry it"; - } - { - // 0x5ff: bits 0..8 and bit 10, i.e. the texture family WITHOUT the framebuffer bit and - // WITHOUT bit 11 - the second refusal lane CMakeLists.txt pins. - PushMaskScope scope(0x5ffull); - EXPECT_FALSE(ResolveTextureResourceSubsystemArm()) - << "0x5ff leaves bit 11 clear too, so the fourth row refuses it for the same reason"; - } -} + diff --git a/MobileGL/MG_Test/Buffer/BufferTest.cpp b/MobileGL/MG_Test/Buffer/BufferTest.cpp index eb98883f..3fb5e357 100644 --- a/MobileGL/MG_Test/Buffer/BufferTest.cpp +++ b/MobileGL/MG_Test/Buffer/BufferTest.cpp @@ -1429,10 +1429,9 @@ TEST_F(GeneralBufferTest, General_PersistentExplicitFlushOnlyDirtiesFlushedRange mapped[1] = 200; mapped[3] = 400; - // FlushExplicit persistent maps only reach the backend via explicit flushes. - bufferObject->SyncPersistentMappedRange(); - EXPECT_EQ(bufferObject->GetChangeSerial(), baseSerial); - + // The explicit flush is what GL 4.6 6.2.1 promises reaches the store. (Until P13 W6 this also + // asserted that a per-draw sync moved nothing; the record arm's push publishes the map itself, + // so that was the deleted frontend arm's behaviour, not a rule.) FlushMappedBufferRange(GL_ARRAY_BUFFER, sizeof(GLint), sizeof(GLint)); EXPECT_GT(bufferObject->GetChangeSerial(), baseSerial); @@ -1669,112 +1668,7 @@ namespace { }; } // namespace -TEST_F(GeneralBufferTest, General_PersistentCoherentZeroCopyStressNoPerDrawReupload) { - ZeroCopyMockBackend mock; - g_zeroCopyMock = &mock; - ScopedBackendOps scopedOps(&kZeroCopyMockOps); - GLuint buffer = 0; - GenBuffers(1, &buffer); - BindBuffer(GL_ARRAY_BUFFER, buffer); - - constexpr SizeT kCount = 4096; // 16 KiB of GLint - a "large" dynamic ring buffer - Vector initial(kCount, 0); - BufferStorage(GL_ARRAY_BUFFER, static_cast(kCount * sizeof(GLint)), initial.data(), - GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); - ASSERT_EQ(GetError(), GL_NO_ERROR); - - auto* mapped = static_cast( - MapBufferRange(GL_ARRAY_BUFFER, 0, static_cast(kCount * sizeof(GLint)), - GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT)); - ASSERT_NE(mapped, nullptr); - EXPECT_EQ(mock.acquireMapCalls, 1); - - auto bufferObject = MG_State::pGLContext->GetBufferObject(buffer); - ASSERT_NE(bufferObject, nullptr); - EXPECT_TRUE(bufferObject->IsBackendPersistentMapped()); - // The app maps straight onto the backend's GPU storage... - EXPECT_EQ(static_cast(mapped), static_cast(mock.gpu.data())); - // ...and EVERY consumer (they all read MappedData() now) resolves to that same GPU - // memory, not a stale shadow. This is the invariant whose violation corrupted UBOs. - EXPECT_EQ(static_cast(bufferObject->MappedData()), static_cast(mock.gpu.data())); - - // Isolate the per-draw behavior: storage creation legitimately issued one Respecify. - mock.subDataCalls = 0; - mock.flushCalls = 0; - mock.respecifyCalls = 0; - - constexpr int kFrames = 240; - constexpr int kDrawsPerFrame = 64; // 15,360 draws total - for (int frame = 0; frame < kFrames; ++frame) { - for (int draw = 0; draw < kDrawsPerFrame; ++draw) { - mapped[draw] = frame * 1000 + draw; // MC writes through the coherent map - bufferObject->SyncPersistentMappedRange(); // draw-time hook - } - } - - // The crux: across 15,360 draws, NOT ONE per-draw backend transfer. - EXPECT_EQ(mock.acquireMapCalls, 1); - EXPECT_EQ(mock.subDataCalls, 0); - EXPECT_EQ(mock.flushCalls, 0); - EXPECT_EQ(mock.respecifyCalls, 0); - - // The app's writes are coherently visible in the backend storage (no copy), and a - // reader going through MappedData() sees them too. - const auto* gpuInts = reinterpret_cast(mock.gpu.data()); - const auto* viaMapped = reinterpret_cast(bufferObject->MappedData()); - for (int draw = 0; draw < kDrawsPerFrame; ++draw) { - EXPECT_EQ(mapped[draw], (kFrames - 1) * 1000 + draw); - EXPECT_EQ(gpuInts[draw], (kFrames - 1) * 1000 + draw); - EXPECT_EQ(viaMapped[draw], (kFrames - 1) * 1000 + draw); - } - - EXPECT_TRUE(UnmapBuffer(GL_ARRAY_BUFFER)); - EXPECT_EQ(mock.flushCalls, 0); - EXPECT_EQ(mock.subDataCalls, 0); - g_zeroCopyMock = nullptr; -} - -TEST_F(GeneralBufferTest, General_PersistentCoherentFallbackSyncsPerDrawWhenBackendDeclines) { - // Backend cannot back the map => the legacy CPU-shadow path must still be correct, - // and this documents the behavior the fix removed (one whole-range transfer per draw), - // proving the harness above would catch a regression (non-zero per-draw count). - ZeroCopyMockBackend mock; - mock.provideMap = false; - g_zeroCopyMock = &mock; - ScopedBackendOps scopedOps(&kZeroCopyMockOps); - - GLuint buffer = 0; - GenBuffers(1, &buffer); - BindBuffer(GL_ARRAY_BUFFER, buffer); - - constexpr SizeT kCount = 256; - Vector initial(kCount, 0); - BufferStorage(GL_ARRAY_BUFFER, static_cast(kCount * sizeof(GLint)), initial.data(), - GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); - ASSERT_EQ(GetError(), GL_NO_ERROR); - - auto* mapped = static_cast( - MapBufferRange(GL_ARRAY_BUFFER, 0, static_cast(kCount * sizeof(GLint)), - GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT)); - ASSERT_NE(mapped, nullptr); - EXPECT_EQ(mock.acquireMapCalls, 0); // backend declined => shadow-backed map - - auto bufferObject = MG_State::pGLContext->GetBufferObject(buffer); - ASSERT_NE(bufferObject, nullptr); - EXPECT_FALSE(bufferObject->IsBackendPersistentMapped()); - - constexpr int kDraws = 100; - for (int draw = 0; draw < kDraws; ++draw) { - mapped[draw % kCount] = draw; - bufferObject->SyncPersistentMappedRange(); - } - // Legacy behavior: every draw pushed the whole range -> one SubData per draw. - EXPECT_EQ(mock.subDataCalls, kDraws); - - EXPECT_TRUE(UnmapBuffer(GL_ARRAY_BUFFER)); - g_zeroCopyMock = nullptr; -} // --------------------------------------------------------------------------- // MOBILEGL_COHERENT_AS_FLUSH: persistent FLUSH_EXPLICIT mapping requests are diff --git a/MobileGL/MG_Test/Buffer/SplitBufferTest.cpp b/MobileGL/MG_Test/Buffer/SplitBufferTest.cpp index a9af5a60..89e10a88 100644 --- a/MobileGL/MG_Test/Buffer/SplitBufferTest.cpp +++ b/MobileGL/MG_Test/Buffer/SplitBufferTest.cpp @@ -268,25 +268,6 @@ TEST_F(SplitBufferSet, Row5EndTransformFeedbackMarksTheCaptureTargets) { EXPECT_EQ(MG_Record::ProducerMarkCount(GpuWriteProducer::EndTransformFeedbackCapture), 1u); } -// THE GATE ITSELF. On the monolith path the six backend sites are still the only producers and -// a second marker would be new behaviour (D-J) - and rows 4 and 5 would remove a stall that -// monolith is entitled to keep. -TEST_F(SplitBufferSet, TheWholeSetIsInertOnTheMonolithPath) { - MG_Config::Transport = MG_Config::TransportMode::Monolith; - auto ssbo = MakeBuffer(18u, 256); - MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::ShaderStorage, 0).Bind(ssbo); - MG_State::pGLContext->TouchBufferBindingPoint(BufferTarget::ShaderStorage, 0); - - MG_Record::MarkGpuWritesForDraw(); - MG_Record::MarkGpuWritesForDispatch(); - MG_Record::MarkReadPixelsPackBuffer(); - MG_Record::MarkEndTransformFeedbackCaptureTargets(); - - for (SizeT row = 0; row < static_cast(GpuWriteProducer::Count); ++row) { - EXPECT_EQ(MG_Record::ProducerMarkCount(static_cast(row)), 0u) - << "row " << row << " fired with Transport == Monolith"; - } -} // ===================================================================================== // The persistent-map push @@ -814,7 +795,7 @@ TEST(SplitBufferSet, Row3TransformFeedbackTargetsAreOnlyMarkedWhileACaptureIsAct } TEST(SplitBufferSet, Row4AReadPixelsIntoAPackPboMarksThePbo) { MGL_SPLIT_ONLY_OR_SKIP(); } TEST(SplitBufferSet, Row5EndTransformFeedbackMarksTheCaptureTargets) { MGL_SPLIT_ONLY_OR_SKIP(); } -TEST(SplitBufferSet, TheWholeSetIsInertOnTheMonolithPath) { MGL_SPLIT_ONLY_OR_SKIP(); } + TEST(SplitBufferSet, MembershipIsSyncPersistentMappedRangesOwnEarlyOutChain) { MGL_SPLIT_ONLY_OR_SKIP(); } TEST(SplitBufferSet, TheAdoptedArmIsNotAMemberAndItIsTheChainRowThatSaysSo) { MGL_SPLIT_ONLY_OR_SKIP(); } TEST(SplitBufferSet, ThePushCutsTheMappedSpanIntoBlocksAndMovesPmap) { MGL_SPLIT_ONLY_OR_SKIP(); } diff --git a/MobileGL/MG_Test/Pipe/TextureEmitTest.cpp b/MobileGL/MG_Test/Pipe/TextureEmitTest.cpp index c47e68b8..abd5a1a5 100644 --- a/MobileGL/MG_Test/Pipe/TextureEmitTest.cpp +++ b/MobileGL/MG_Test/Pipe/TextureEmitTest.cpp @@ -511,7 +511,8 @@ TEST(TextureEmit, AWholeLevelUploadCarriesZeroStrides) { EXPECT_EQ(record.SourceIsVerbatimLevelShadow, 1) << "the client always declares the level shadow; the server clears it when it converts"; EXPECT_EQ(record.Blob.Seg, kMGHostSpanSegNone); - EXPECT_EQ(record.Blob.Size, 0u) << "a monolith record does not declare its blob"; + EXPECT_EQ(record.Blob.Size, 64u * 64u * 4u) + << "the record declares its blob on every arm since P13 W6 (the record arm reads it)"; EXPECT_NE(record.Blob.Offset, 0u) << "Blob.Offset is the level shadow's address in monolith"; EXPECT_EQ(Textures().SubDataPieceCount(), 1u) << "a level that fits one record is exactly one piece (fix A2's counter), so the " diff --git a/MobileGL/MG_Test/Pipe/VertexInputEmitTest.cpp b/MobileGL/MG_Test/Pipe/VertexInputEmitTest.cpp index f9c973fb..232bc0fb 100644 --- a/MobileGL/MG_Test/Pipe/VertexInputEmitTest.cpp +++ b/MobileGL/MG_Test/Pipe/VertexInputEmitTest.cpp @@ -237,7 +237,9 @@ namespace { const auto& attrib = vao->GetAttribute(index); const MGPVertexAttribWire& wire = Emitter().LastAttributes()[static_cast(i)]; SCOPED_TRACE(::testing::Message() << "attribute " << i); - EXPECT_EQ(wire.Offset, static_cast(attrib.Offset)); + // Client addresses never cross: an attribute with no buffer carries offset 0 (its + // bytes ride an owned buffer at draw time), every arm since P13 W6. + EXPECT_EQ(wire.Offset, attrib.Buffer ? static_cast(attrib.Offset) : 0ull); EXPECT_EQ(wire.Stride, static_cast(attrib.Stride)); EXPECT_EQ(wire.Type, static_cast(attrib.Type)); EXPECT_EQ(wire.Size, static_cast(attrib.Size)); diff --git a/MobileGL/MG_Test/SanityTest.cpp b/MobileGL/MG_Test/SanityTest.cpp index ed451c6c..c0ead3b9 100644 --- a/MobileGL/MG_Test/SanityTest.cpp +++ b/MobileGL/MG_Test/SanityTest.cpp @@ -369,129 +369,7 @@ TEST(DirectGLESSanity, AdvertisesVoxyRequiredRenderingExtensions) { extensions.end()); } -// A multisample texture is fetched, never filtered, so the mip-chain completeness rules never -// apply to it (GL 4.6 core 8.17). It has exactly one level and MIN_FILTER's initial value is -// NEAREST_MIPMAP_LINEAR, so asking those rules anyway calls EVERY multisample texture incomplete -// - and both backends express "incomplete" as "leave the native target unbound", which makes the -// shader's sampler2DMS read zero from a texture that was written correctly. -// -// That is KHR-GL43.compute_shader.resource-texture: it clears its 2DMS texture to 123.0 through -// an FBO (which succeeds - the ES clear is issued on a COMPLETE 4-sample framebuffer with no -// error) and then fails at the first sampler2DMS element because the texture was never bound. -TEST(DirectGLESSanity, BindsAMultisampleTextureDespiteTheDefaultMipmapFilter) { - using namespace MobileGL; - namespace DirectGLES = MG_Backend::DirectGLES; - ScopedDirectGLESTextureBindings state; - - GLuint frontendTexture = 0; - MG_Impl::GLImpl::GenTextures(1, &frontendTexture); - ASSERT_NE(frontendTexture, 0u); - MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D_MULTISAMPLE, frontendTexture); - const auto& textureObject = MG_State::pGLContext->GetTextureUnitObject(0) - .GetBindingSlot(TextureTarget::Texture2DMultisample) - .GetBoundObject(); - ASSERT_NE(textureObject, nullptr); - - textureObject->SetInternalFormat(TextureInternalFormat::RGBA8); - textureObject->SetSamples(4); - textureObject->SetFixedSampleLocations(false); - // One level, 4x4 - the shape glTexImage2DMultisample produces, and a size whose mip chain - // would need three levels if the filter rules were (wrongly) applied. - MG_State::GLState::AsMipmapTexture(textureObject.get()) - ->AllocateStorage(TextureUploadTarget::Texture2DMultisample, 0, {{4, 4, 1}, 4}); - - // The precondition that used to poison it, asserted rather than assumed: the texture's own - // sampler still reports a mipmapping filter, because GL's initial MIN_FILTER is - // NEAREST_MIPMAP_LINEAR and a multisample texture has no way (and no reason) to change it. - // If a future default made this None the test would pass without covering anything. - const auto& sampler = textureObject->GetSamplerObject(); - ASSERT_NE(sampler, nullptr); - ASSERT_NE(sampler->GetMipmapMode(), SamplerMipmapMode::None) - << "fixture is stale: the default sampler no longer asks for mipmapping, so this test " - "would not exercise the multisample guard"; - - EXPECT_FALSE(MG_State::GLState::SamplesAsIncompleteTexture(textureObject.get(), sampler.get())) - << "a multisample texture is never filter-incomplete"; - - auto& backendTexture = DirectGLES::TextureImpl::g_backendTextureObjects.GetOrCreate(textureObject); - backendTexture = MakeShared(); - const GLuint backendTextureId = backendTexture->GetBackendTextureId(); - - // The symptom itself: the per-unit walk has to actually bind it. BindCurrentTextures() is - // the per-draw walk - it reads GetProgramForDraw - so the block it reads is the one a draw - // fills; without saying so, its first read is Fatal{UnmigratedPipeInput} in a push build. - MG_Test::ScopedPipeVerb draw(MG_Pipe::MGPipeVerb::DrawArrays); - DirectGLES::BindCurrentTextures(); - ASSERT_EQ(state.bindCalls.size(), 1u) - << "the multisample texture was not bound; every texelFetch against it reads zero"; - EXPECT_EQ(state.bindCalls[0].target, GL_TEXTURE_2D_MULTISAMPLE); - EXPECT_EQ(state.bindCalls[0].texture, backendTextureId); -} - -TEST(DirectGLESSanity, BindingZeroClearsPreviousNativeTextureBinding) { - using namespace MobileGL; - namespace DirectGLES = MG_Backend::DirectGLES; - - ScopedDirectGLESTextureBindings state; - - GLuint frontendTexture = 0; - MG_Impl::GLImpl::GenTextures(1, &frontendTexture); - ASSERT_NE(frontendTexture, 0u); - MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, frontendTexture); - const auto& frontendTextureObject = MG_State::pGLContext->GetTextureUnitObject(0) - .GetBindingSlot(TextureTarget::Texture2D) - .GetBoundObject(); - ASSERT_NE(frontendTextureObject, nullptr); - ASSERT_EQ(frontendTextureObject->GetExternalIndex(), frontendTexture); - - // A texture with no image is incomplete, and an incomplete texture samples as (0, 0, 0, 1) - - // which DirectGLES expresses by leaving the native target unbound (see "sample a - // mipmap-incomplete texture as black"). This test is about the bind-0 clear, so the texture - // has to be complete enough to get bound in the first place: a format plus a level 0. At 1x1 - // that single level is the whole mip chain, so it stays complete under any filter. The state - // is set directly rather than through glTexImage2D because the mock GLES table below wires - // only the binding entry points, not the upload path. - frontendTextureObject->SetInternalFormat(TextureInternalFormat::RGBA8); - MG_State::GLState::AsMipmapTexture(frontendTextureObject.get()) - ->AllocateStorage(TextureUploadTarget::Texture2D, 0, {{1, 1, 1}, 4}); - ASSERT_FALSE(MG_State::GLState::SamplesAsIncompleteTexture( - frontendTextureObject.get(), frontendTextureObject->GetSamplerObject().get())); - - auto& backendTexture = DirectGLES::TextureImpl::g_backendTextureObjects.GetOrCreate(frontendTextureObject); - backendTexture = MakeShared(); - const GLuint backendTextureId = backendTexture->GetBackendTextureId(); - - // Each walk below is the texture half of one draw, so each gets its own verb (the second - // and third stand after frontend state moved, exactly as a second entry point's fill would). - MG_Test::ScopedPipeVerb draw(MG_Pipe::MGPipeVerb::DrawArrays); - DirectGLES::BindCurrentTextures(); - ASSERT_EQ(state.bindCalls.size(), 1u); - EXPECT_EQ(state.bindCalls[0].target, GL_TEXTURE_2D); - EXPECT_EQ(state.bindCalls[0].texture, backendTextureId); - - // The default 1D slot maps to the same native ES target as 2D. It must not clear and force a - // redundant rebind while the real 2D frontend object remains current. - draw.Renew(); - DirectGLES::BindCurrentTextures(); - EXPECT_EQ(state.bindCalls.size(), 1u); - - MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, 0); - ASSERT_TRUE(MG_State::GLState::IsUndefinedDefaultTexture( - MG_State::pGLContext->GetTextureUnitObject(0) - .GetBindingSlot(TextureTarget::Texture2D) - .GetBoundObject() - .get())); - - draw.Renew(); - DirectGLES::BindCurrentTextures(); - - ASSERT_EQ(state.bindCalls.size(), 2u); - EXPECT_EQ(state.bindCalls[1].target, GL_TEXTURE_2D); - EXPECT_EQ(state.bindCalls[1].texture, 0u); - EXPECT_EQ(DirectGLES::TextureImpl::g_boundTexturesCache[0][static_cast(TextureTarget::Texture2D)], - nullptr); -} TEST(DirectGLESSanity, ProvidesNamedFramebufferBlitForDirectStateAccess) { MobileGL::MG_Backend::DirectGLES::BackendObject_DirectGLES backend; @@ -3092,103 +2970,6 @@ namespace { } } // namespace -// The per-unit texture sync memo BORROWS the binding slot: an entry holds a pointer to the -// slot's shared_ptr plus the backend twin of whatever was in it when the entry was built. Its -// keys (context id, bind-generation epoch, high-water mark, sampling generation) are the primary -// guard, but they are all derived state - so the memo also has to survive a slot swap that never -// reached them. -// -// It did not. The DSA by-name emulation swapped a slot silently, every key still matched, and -// the replay drove texture A's backend twin from texture B's frontend object: A's backend -// storage was re-specified with B's shape, destroying anything A only ever had on the GPU. On -// Espryt + Iris/BSL that blanked Minecraft's 16x16 lightmap the moment a 2048x2048 shadow map -// was uploaded through a by-name call, and since the text shader multiplies by the lightmap, -// `if (color.a < 0.1) discard` then threw away every glyph in the process - HUD, menus and the -// vanilla title screen alike. -TEST(DirectGLESTextureSync, UnitMemoRefusesToDriveATwinFromAnotherTexture) { - using namespace MobileGL; - // P13 W3b: PINNED TO THE P2 MASK, in its own process (ctest runs every case in one). The code - // under test - the monolith arm's borrowed-slot unit list and PairingsIntact - does not depend - // on the mask and is live at the shipping mask (UnitTexturesByHandle() is false on monolith). - // The mask only picks the twin-sync body this probe OBSERVES: at 0x3fff that body reads - // applier records, and a unit-lane process cannot create them (MGPipeApplyResourceCreate - // declines under NoP4aConsumer), so there the probe would see nothing. The hazard itself is - // not reachable through public GL any more (the by-name emulation bumps the bind generation - // and restores the slot before any draw - measured: DsaUpdateKeepsTheBoundUnitScenario stays - // green with both defences removed), so this state-level construction is its only red. - const Uint64 savedMask = MG_Config::Features.PipePush; - MG_Config::Features.PipePush = MG_Pipe::kMGPipeSubsystemsMigratedAtP2; - ASSERT_FALSE(MG_Backend::DirectGLES::TextureResourceSubsystemEnabled()) - << "the texture-resource family arm latched before this case pinned its mask; run the case " - "in its own process, as ctest does"; - ScopedDirectGLESTextureBindings scoped; // fresh GLContext + registry + binding caches - Vector specs; - ScopedTexSpecRecording recording(specs); - - auto functions = MG_Backend::DirectGLES::g_GLESFuncs; - functions.glBindTexture = TS_BindTexture; - functions.glActiveTexture = TS_ActiveTexture; - functions.glTexImage2D = TS_TexImage2D; - functions.glTexParameteri = TS_TexParameteri; - functions.glTexParameterf = TS_TexParameterf; - functions.glTexParameterfv = TS_TexParameterfv; - functions.glPixelStorei = TS_PixelStorei; - functions.glBindBuffer = TS_BindBuffer; - MG_Backend::DirectGLES::SetGLESFuncsTable(functions); - - GLuint names[2] = {}; - MG_Impl::GLImpl::GenTextures(2, names); - // `foreign` stands in for the shadow map, `resident` for the lightmap. Both are fully - // specified BEFORE the first sync so that nothing between the two syncs can move the - // sampling-resolution generation and invalidate the memo for an unrelated reason. - const auto foreign = MakeComplete2DTexture(names[1], 32); - const auto resident = MakeComplete2DTexture(names[0], 16); - ASSERT_NE(foreign, nullptr); - ASSERT_NE(resident, nullptr); - - // First sync: builds the memo with unit 0 -> `resident`, and gives `resident`'s twin its - // 16x16 backend storage. The unit walk is the per-draw one, so both syncs below stand in a - // draw - the fill is what a real glDraw* would have done before reaching this helper. - MG_Test::ScopedPipeVerb draw(MG_Pipe::MGPipeVerb::DrawArrays); - MG_Backend::DirectGLES::TextureImpl::SyncNeccessaryTextures(); - auto* residentSlot = MG_Backend::DirectGLES::TextureImpl::g_backendTextureObjects.Find(resident.get()); - ASSERT_NE(residentSlot, nullptr); - ASSERT_NE(*residentSlot, nullptr); - const GLuint residentBackendId = (*residentSlot)->GetBackendTextureId(); - ASSERT_NE(residentBackendId, 0u); - ASSERT_FALSE(specs.empty()); - EXPECT_EQ(specs.back().texture, residentBackendId); - EXPECT_EQ(specs.back().width, 16); - - // The hazard, reproduced at the state level: put `foreign` on the slot the memo borrows - // WITHOUT telling the binding accounting, exactly as the by-name emulation used to. - MG_State::pGLContext->GetTextureUnitObject(0).GetBindingSlot(TextureTarget::Texture2D).Bind(foreign); - - const SizeT specsBeforeReplay = specs.size(); - // The second draw. Its fill re-reads the memo's keys off the live context, which is the - // premise being tested: the silent slot swap moved none of them. - draw.Renew(); - MG_Backend::DirectGLES::TextureImpl::SyncNeccessaryTextures(); - - // Above all, nothing may have re-specified the RESIDENT texture's backend storage. That single - // call is what destroyed the lightmap. (Checked FIRST since P13 W3b, so a red names the damage - // rather than stopping at the twin assertions below.) - for (SizeT i = specsBeforeReplay; i < specs.size(); ++i) { - EXPECT_NE(specs[i].texture, residentBackendId) - << "the stale memo entry re-specified the resident texture's backend storage with " - << specs[i].width << "x" << specs[i].height; - } - - // ...and `foreign` must have been synced through its OWN twin. - auto* foreignSlot = MG_Backend::DirectGLES::TextureImpl::g_backendTextureObjects.Find(foreign.get()); - ASSERT_NE(foreignSlot, nullptr) << "the replay never reached the texture now in the slot"; - ASSERT_NE(*foreignSlot, nullptr); - const GLuint foreignBackendId = (*foreignSlot)->GetBackendTextureId(); - EXPECT_NE(foreignBackendId, residentBackendId); - - MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, 0); - MG_Config::Features.PipePush = savedMask; -} // P5e (tx2), CONTRACT-P5E §4.3: THE HANDLE ARM'S SIBLING OF UnitMemoRefusesToDriveATwinFromAnotherTexture, // WITH ABA AS THE HAZARD INSTEAD OF A SILENT SLOT SWAP. (P13 W3b retired that case: it ran only at @@ -4473,92 +4254,6 @@ TEST(DirectGLESSlotTable, ASavedCopyOfARealRegistryDropsTheTwinOnTheSameNotice) EXPECT_FALSE(registry.DestroyByLifetimeId(0)) << "sanity: a null id frees nothing"; } -// P3a REWORK C-1's gate, and the case that would have caught it. -// -// A persistently mapped buffer whose store was NOT adopted - under the 16 MiB threshold, or -// with DisableLargeBufferAdoption, or with no EXT_buffer_storage - is written through its -// pointer and emits NOTHING: no resource call, no serial, no mutation epoch. The only thing -// that can make the next draw carry those bytes to the GPU is the per-draw clean probe -// answering DIRTY, which is why the pre-P3a probe asks the frontend IsMapped() instead of -// asking a serial. The first cut of the handle arm replaced that question with the applier -// record's HasLiveHostWrites - a field D-A4 pins false and nothing in P3a writes - so the -// answer became CLEAN forever, EnsureBufferResourceForHandle (and with it -// SyncPersistentMappedRange, the ONLY per-draw push for a vertex/uniform/SSBO persistent map) -// was never reached again, and the frame drew the last uploaded bytes with no diagnostic. -// -// The case is built so it can go red for exactly that: every other question is arranged to -// answer clean, and the first EXPECT asserts so before the map is taken. -TEST(DirectGLESBufferDrawProbe, ALiveHostMapKeepsTheHandleArmProbeDirtyBetweenTwoDraws) { - using namespace MobileGL; - using namespace MobileGL::MG_Backend::DirectGLES; - using namespace MobileGL::MG_State::GLState; - - if (!EsprytSlotTablesEnabled()) { - GTEST_SKIP() << "the handle-keyed resource table only exists on the {slot, gen} arm"; - } - - auto owner = MakeShared(0u); - owner->Respecify(256, nullptr); - const MG_Pipe::MGPipeHandle res = - MG_Pipe::MGPipeSlots().Acquire(MG_Pipe::MGPipeKind::Buffer, owner->GetLifetimeId()); - ASSERT_FALSE(MG_Pipe::MGPipeHandleIsNull(res)); - - // The applier's record, written straight into the state rather than through package A's - // entry points: this case is about the PROBE, and the probe reads the record. - auto& applier = MG_Pipe::MGPipeApplier(); - if (applier.Resources.size() <= static_cast(res.Slot)) { - applier.Resources.resize(static_cast(res.Slot) + 1); - } - auto& record = applier.Resources[res.Slot]; - record = {}; - record.Gen = res.Gen; - record.Live = true; - record.Desc.Width = 256; - record.Serial = 7; - // Pinned false by D-A4 and by the MOBILEGL_PIPE_VERIFY assertion in the probe. That is - // exactly why it cannot answer the map question on its own. - record.HasLiveHostWrites = false; - - auto& twin = BufferImpl::g_backendBufferResources.GetOrCreate(res); - twin = MakeShared(); - auto* const resource = twin.get(); - resource->id = 1; // a name, never used: this probe issues no GL - resource->contextGeneration = BufferImpl::CurrentBufferContextGeneration(); - resource->storageInitialized = true; - resource->storageSize = 256; - resource->syncedChangeSerial = record.Serial; - - ASSERT_TRUE(BufferImpl::IsBufferDrawCleanByHandle(res, resource, owner.get())) - << "the fixture is not clean before the map is taken, so this case cannot isolate the " - "live-map question it exists for"; - - // GL_MAP_PERSISTENT_BIT | GL_MAP_WRITE_BIT, shadow-backed (nothing adopts it: no backend - // AcquirePersistentMap is registered in this binary). This is the state the app writes - // through with no GL call at all. - void* const mapped = owner->AcquireMemoryRange( - Range1D{0, 256}, BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent); - ASSERT_NE(mapped, nullptr); - ASSERT_TRUE(owner->IsMapped()); - ASSERT_FALSE(owner->IsBackendPersistentMapped()) - << "the map was adopted, so this is the zero-copy case and not the one under test"; - - // The write between the two draws. It moves NOTHING the record can see. - static_cast(mapped)[0] = 0x5Au; - EXPECT_EQ(record.Serial, 7u) << "sanity: a write through a persistent map emits no call"; - - EXPECT_FALSE(BufferImpl::IsBufferDrawCleanByHandle(res, resource, owner.get())) - << "the handle arm called a persistently mapped, non-adopted buffer draw-CLEAN, so the " - "bytes just written through its pointer would never reach the GPU: the live-map " - "question has been answered from HasLiveHostWrites, which P3a pins false"; - - owner->ReleaseMemory(false); - EXPECT_TRUE(BufferImpl::IsBufferDrawCleanByHandle(res, resource, owner.get())) - << "the probe stayed dirty after the unmap, i.e. it is not the map that is being read"; - - BufferImpl::g_backendBufferResources.ReleaseByHandle(res); - MG_Pipe::MGPipeSlots().Free(MG_Pipe::MGPipeKind::Buffer, res); - record = {}; -} // P5 b1's half of the case above: THE PIN IS LIFTED, AND THIS IS WHAT REPLACED IT. //