[MG_Backend, MG_Impl] complete GLES XFB lifetimes, lazy readback and sampler windows

This commit is contained in:
2026-09-20 12:43:07 -04:00
parent c3a5d2f58e
commit e2d6e33db4
6 changed files with 124 additions and 11 deletions
+14 -3
View File
@@ -2394,7 +2394,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
void Ops_H_Readback(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPReadback& record) {
#if MOBILEGL_BUILD_DISAGGREGATED
// 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::Transport != MG_Config::TransportMode::Monolith
? EnsureBufferResourceForHandle(nullptr, res) : FindBufferResourceForHandle(res);
#else
auto* resource = FindBufferResourceForHandle(res);
#endif
if (!resource || resource->id == 0 || !resource->storageInitialized) return;
if (!CanTouchGLNow() || resource->contextGeneration != g_bufferContextGeneration) return;
if (resource->persistentMapped) {
@@ -2715,9 +2723,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (bytes == nullptr || record.Blob.Size == 0) return;
const auto* stored = PipeTextureRecordForHandle(res);
if (stored == nullptr) return;
const IntVec3 extent = MG_Remote::Server::StagedTextureMipExtent(
stored->Desc.Target, stored->Desc.Width, stored->Desc.Height, stored->Desc.Depth,
static_cast<Uint32>(record.Level));
const IntVec3 extent = record.LevelWidth && record.LevelHeight && record.LevelDepth
? IntVec3{static_cast<Int>(record.LevelWidth), static_cast<Int>(record.LevelHeight),
static_cast<Int>(record.LevelDepth)}
: MG_Remote::Server::StagedTextureMipExtent(
stored->Desc.Target, stored->Desc.Width, stored->Desc.Height, stored->Desc.Depth,
static_cast<Uint32>(record.Level));
store.Adopt(MG_Remote::Server::StagedTextureStore::KeyForHandle(res),
MG_Pipe::MGPipeSubDataUploadTargetOf(record.Target), record.Level, extent,
bytes, static_cast<SizeT>(record.Blob.Size));
+11
View File
@@ -737,6 +737,15 @@ namespace MobileGL::MG_Pipe {
}
m_lastVaoLifetime = vaoLifetime;
m_lastVaoConfig = vaoConfig;
// A redundant bind of the default texture can grow the unit window
// without changing either binding or sampling generations. Both sets
// must still publish that new prefix, including its null samplers.
const Int maxTextureUnit = ctx.GetMaxTouchedTextureUnit();
if (!m_primed || maxTextureUnit != m_lastMaxTextureUnit) {
dirty |= MGPipeDirtyBit(MGPipeDirty::NewSamplerViews) |
MGPipeDirtyBit(MGPipeDirty::NewSamplers);
}
m_lastMaxTextureUnit = maxTextureUnit;
// ---- bit 2: the PACK half of the pixel store, BitwiseEqual ----
const PixelStoreParameters pack = ctx.GetPixelStoreParameters(false);
@@ -791,6 +800,7 @@ namespace MobileGL::MG_Pipe {
std::memset(m_lastPushed, 0, sizeof(m_lastPushed));
m_lastVaoLifetime = 0;
m_lastVaoConfig = 0;
m_lastMaxTextureUnit = -1;
m_renderStateVersion.Reset();
m_pipelineStateVersion.Reset();
m_framebufferBind.Reset();
@@ -881,6 +891,7 @@ namespace MobileGL::MG_Pipe {
Uint64 m_lastPushed[kMGPipeDirtyCount]{};
Uint64 m_lastVaoLifetime = 0;
Uint32 m_lastVaoConfig = 0;
Int m_lastMaxTextureUnit = -1;
MGPipeWidenedCounter m_renderStateVersion;
MGPipeWidenedCounter m_pipelineStateVersion;
// The draw framebuffer BINDING slot version, widened for the same reason: a Uint16
@@ -17,6 +17,12 @@
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
#include <MG_Backend/DirectGLES/Managers.h>
#include <MG_Backend/DirectGLES/DirectGLES.h>
#if MOBILEGL_BUILD_DISAGGREGATED
#include <Config.h>
#include <MG_Impl/Pipe/SlotAllocator.h>
#include <MG_Remote/Client/ClientSession.h>
#include <MG_Remote/Server/ServerLoop.h>
#endif
#define MGITEST_PIPE_APPLY_PEEK_LIVE 1
#endif
#endif
@@ -74,8 +80,70 @@ namespace MGITest {
}
} // namespace
static bool ReadAppliedTextureParams(MGB::TextureImpl::BackendTextureObject* twin,
unsigned glTarget, EsprytAppliedTextureParamsPeek* out);
static void CopyTextureParamsRecord(const MGP::MGPipeResourceRecord& record, unsigned slot,
PipeTextureParamsRecordPeek& out) {
out.Slot = slot;
out.Gen = static_cast<unsigned>(record.Gen);
out.ParamsSerial = static_cast<unsigned long long>(record.ParamsSerial);
for (int channel = 0; channel < 4; ++channel) out.Swizzle[channel] = SwizzleToGLEnum(record.Params.Swizzle[channel]);
out.DepthStencilMode = DepthStencilModeToGLEnum(record.Params.DepthStencilMode);
}
#if MOBILEGL_BUILD_DISAGGREGATED
enum class ServerPeekKind { Record, Applied, View };
static bool PeekOnApplyThread(unsigned glTextureName, unsigned target, ServerPeekKind kind, void* output) {
auto* object = FrontendTexture(glTextureName);
auto* session = MobileGL::MG_Remote::Client::ClientSession::Active();
if (!object || !session || !output) return false;
struct Request {
MGP::MGPipeHandle Texture, View;
unsigned Target;
ServerPeekKind Kind;
void* Output;
bool Result = false;
} request{MGP::MGPipeSlots().FindByLifetimeId(MGP::MGPipeKind::Texture, object->GetLifetimeId()),
MGP::MGPipeSlots().FindByLifetimeId(MGP::MGPipeKind::SamplerViewCso, object->GetLifetimeId()),
target, kind, output};
if (MGP::MGPipeHandleIsNull(request.Texture)) return false;
if (session->WaitForApplied(session->LastPublishedSeq(), 30000) !=
MobileGL::MG_Remote::Transport::SessionWait::Reached) return false;
const auto result = MobileGL::MG_Remote::Server::ServerLoopInstance().RunProbeOnApplyThreadForTesting(
+[](void* opaque) -> MobileGLResult {
auto& r = *static_cast<Request*>(opaque);
const auto& records = MGP::MGPipeApplier().TextureResources;
if (r.Texture.Slot >= records.size()) return MOBILEGL_OK;
const auto& record = records[r.Texture.Slot];
if (!record.Live || record.Gen != r.Texture.Gen) return MOBILEGL_OK;
if (r.Kind == ServerPeekKind::Record) {
CopyTextureParamsRecord(record, r.Texture.Slot, *static_cast<PipeTextureParamsRecordPeek*>(r.Output));
r.Result = true;
return MOBILEGL_OK;
}
auto* found = MGB::TextureImpl::g_backendTextureObjects.FindByHandle(r.Texture);
if (!found || !*found) return MOBILEGL_OK;
if (r.Kind == ServerPeekKind::Applied)
r.Result = ReadAppliedTextureParams(found->get(), r.Target,
static_cast<EsprytAppliedTextureParamsPeek*>(r.Output));
else {
*static_cast<bool*>(r.Output) = !MGP::MGPipeHandleIsNull(r.View) &&
MGB::SamplerViewImpl::FindSamplerViewForHandle(r.View) != nullptr;
r.Result = true;
}
return MOBILEGL_OK;
}, &request);
return result == MOBILEGL_OK && request.Result;
}
#endif
bool PeekPipeTextureParamsRecord(unsigned glTextureName, PipeTextureParamsRecordPeek* out) {
if (out == nullptr) return false;
#if MOBILEGL_BUILD_DISAGGREGATED
if (MobileGL::MG_Config::Transport != MobileGL::MG_Config::TransportMode::Monolith)
return PeekOnApplyThread(glTextureName, 0, ServerPeekKind::Record, out);
#endif
const MGP::MGPipeApplierState& applier = MGP::MGPipeApplier();
// Slot 0 is the reserved null handle and is never live (MGPipeHandles.h), so the scan
// starts at 1 and a match at 0 is impossible rather than merely unlikely.
@@ -83,13 +151,7 @@ namespace MGITest {
const MGP::MGPipeResourceRecord& record = applier.TextureResources[slot];
if (!record.Live) continue;
if (record.Desc.GlNameForDiag != static_cast<MobileGL::Uint32>(glTextureName)) continue;
out->Slot = static_cast<unsigned>(slot);
out->Gen = static_cast<unsigned>(record.Gen);
out->ParamsSerial = static_cast<unsigned long long>(record.ParamsSerial);
for (int channel = 0; channel < 4; ++channel) {
out->Swizzle[channel] = SwizzleToGLEnum(record.Params.Swizzle[channel]);
}
out->DepthStencilMode = DepthStencilModeToGLEnum(record.Params.DepthStencilMode);
CopyTextureParamsRecord(record, static_cast<unsigned>(slot), *out);
return true;
}
return false;
@@ -97,10 +159,18 @@ namespace MGITest {
bool PeekEsprytAppliedTextureParams(unsigned glTextureName, unsigned glTarget,
EsprytAppliedTextureParamsPeek* out) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MobileGL::MG_Config::Transport != MobileGL::MG_Config::TransportMode::Monolith)
return PeekOnApplyThread(glTextureName, glTarget, ServerPeekKind::Applied, out);
#endif
return ReadAppliedTextureParams(EsprytTwin(glTextureName), glTarget, out);
}
static bool ReadAppliedTextureParams(MGB::TextureImpl::BackendTextureObject* twin,
unsigned glTarget, EsprytAppliedTextureParamsPeek* out) {
if (out == nullptr) return false;
const int bindingQuery = BindingQueryFor(glTarget);
if (bindingQuery == 0) return false;
MGB::TextureImpl::BackendTextureObject* const twin = EsprytTwin(glTextureName);
if (twin == nullptr) return false;
const MobileGL::Uint backendId = twin->GetBackendTextureId();
if (backendId == 0) return false;
@@ -151,6 +221,10 @@ namespace MGITest {
}
bool PeekEsprytHasSamplerViewForTexture(unsigned glTextureName, bool* outExists) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MobileGL::MG_Config::Transport != MobileGL::MG_Config::TransportMode::Monolith)
return PeekOnApplyThread(glTextureName, 0, ServerPeekKind::View, outExists);
#endif
if (outExists == nullptr) return false;
MobileGL::MG_State::GLState::ITextureObject* const object = FrontendTexture(glTextureName);
if (object == nullptr) return false;
+2
View File
@@ -848,6 +848,7 @@ namespace MobileGL::MG_Pipe {
MGPipeHandle VerbCopyTexDst = kMGPipeNullHandle;
MGPipeHandle VerbStorageBlockProgram = kMGPipeNullHandle;
Uint64 BoundStreamOutputLifetimeId = 0;
Uint64 VerbDeleteStreamOutputLifetimeId = 0;
UnorderedMap<Uint64, MGPStreamOutputBegin> StreamOutputSpans;
// generate_mipmap's texture (MGPMipPlan::Res).
MGPipeHandle VerbMipRes = kMGPipeNullHandle;
@@ -876,6 +877,7 @@ namespace MobileGL::MG_Pipe {
VerbBlitNamedConsumed = false;
VerbCopyTexDst = kMGPipeNullHandle;
VerbStorageBlockProgram = kMGPipeNullHandle;
VerbDeleteStreamOutputLifetimeId = 0;
VerbMipRes = kMGPipeNullHandle;
VerbMipBaseLevel = VerbMipLevelCount = 0;
VerbIndirectBuffer = kMGPipeNullHandle;
@@ -1069,7 +1069,9 @@ namespace MobileGL::MG_Remote::Server {
state.StreamOutputSpans.erase(object.LifetimeId);
if (state.BoundStreamOutputLifetimeId == object.LifetimeId) state.BoundStreamOutputLifetimeId = 0;
}
state.VerbDeleteStreamOutputLifetimeId = object.LifetimeId;
if (table->GL.DeleteTransformFeedback) table->GL.DeleteTransformFeedback(object.GlName);
state.VerbDeleteStreamOutputLifetimeId = 0;
return true;
}
+13
View File
@@ -80,6 +80,7 @@ namespace {
X(TrackerWalk, TheIndexBufferBitFiresWhenTheSlotVersionWrapsOntoADifferentBuffer) \
X(TrackerWalk, ABaseInstanceSurvivesTheFirstWalkOnAFreshContext) \
X(TrackerWalk, ASamplerBindAloneFiresTheSamplerStateBit) \
X(TrackerWalk, ARedundantDefaultBindStillPublishesAGrowingSamplerWindow) \
X(TrackerWalk, ARestagedProgramPipelineFiresTheProgramBits) \
X(TrackerWalk, ARelinkOfAStageProgramFiresTheProgramBits) \
X(TrackerWalk, UseProgramZeroLeavesTheBoundPipelineDrivingTheProgramBits) \
@@ -784,6 +785,18 @@ namespace {
EXPECT_EQ(Walk(), 0u) << "the widened shutter fires forever";
}
TEST_F(TrackerWalk, ARedundantDefaultBindStillPublishesAGrowingSamplerWindow) {
Walk();
ASSERT_EQ(Walk(), 0u);
const Uint64 generation = Ctx().GetTextureBindGeneration();
Ctx().NoteTextureUnitTouched(7, false);
ASSERT_EQ(Ctx().GetTextureBindGeneration(), generation);
const Uint32 dirty = Walk();
EXPECT_NE(dirty & MGPipeDirtyBit(MGPipeDirty::NewSamplerViews), 0u);
EXPECT_NE(dirty & MGPipeDirtyBit(MGPipeDirty::NewSamplers), 0u);
EXPECT_EQ(Walk(), 0u);
}
// BITS 6/7/8 UNDER A SEPARABLE PROGRAM PIPELINE. GetCurrentProgram() is null for the whole
// life of a bound pipeline, so all three shutters used to latch 0 and never move again
// after the first walk: glUseProgramStages would rebuild the composite and EmitShaderState