[Fix, Test] (MG_Pipe, clientfb, clientsp): retire every emitter's entry at the object's death - the six death helpers freed the slot and told no emitter, so a dead-but-unrecycled texture handle still resolved to the freed ITextureObject* (the allocator's generation moves only at the next hand-out) and the drain list kept the level: glTexImage2D; glDeleteTextures; <any verb> called a virtual on freed memory from the next validate point (final review C-2); the helpers now forward to the texture, renderbuffer, framebuffer, sampler-view and shader-CSO emitters between the wire delete and the free, ResolveTexture refuses a dead slot loudly on IsLive, the sticky-mask producers stamp the generation they write under, and the delete-then-use sequence is pinned for every kind, for a recycled slot, and under MALLOC_PERTURB_ on both backends

This commit is contained in:
2026-09-08 23:59:48 -04:00
parent 4bb5e082d3
commit a7d56a2fe5
10 changed files with 484 additions and 28 deletions
+23 -1
View File
@@ -234,7 +234,8 @@ TEST(FramebufferEmit, TheEmitterIsOneNeverDestroyedProcessSingleton) {
X(FramebufferEmit, ANamedRecordIsSuppressedPerObjectAndNeverAgainstABoundRecord) \
X(FramebufferEmit, ADrawBufferTokenAboveTheWireWidthIsRefusedNotTruncated) \
X(FramebufferEmit, ALayeredCubeAttachmentDoesNotAssertAFaceItCannotKnow) \
X(FramebufferEmit, EveryNonTexturePointCarriesTheUnknownSentinelsRatherThanZero)
X(FramebufferEmit, EveryNonTexturePointCarriesTheUnknownSentinelsRatherThanZero) \
X(FramebufferEmit, ADeadFramebuffersNamedRecordLatchIsRetired)
#define MGL_DECLARE_PULL_SKIP(Suite, Name) \
TEST(Suite, Name) { GTEST_SKIP() << "compiled only under MOBILEGL_PIPE_PUSH"; }
@@ -772,6 +773,27 @@ TEST(FramebufferEmit, EveryNonTexturePointCarriesTheUnknownSentinelsRatherThanZe
"only moved field is the attachment's texture target would be suppressed";
(void)probe;
}
// ============================ final review C-2 ============================
//
// A framebuffer has no wire lifetime (D-I2), so the only client state under its handle is this
// emitter's per-object Named latch - and the death helper retires it before the slot is freed,
// the shape every P4a kind takes (ID-8). A recycled handle's Gen already refused the stale
// latch, so this pins the hygiene rather than a picture.
TEST(FramebufferEmit, ADeadFramebuffersNamedRecordLatchIsRetired) {
FramebufferScope scope;
MGPipeHandle handle{};
{
const auto fbo = MakeShared<FramebufferObject>(31);
const auto color = MakeColorTexture(32, 8);
fbo->AttachTexture(FramebufferAttachmentType::Color0, color, TextureUploadTarget::Texture2D);
handle = MGPipeFramebufferEmitter::HandleFor(*fbo);
ASSERT_GT(Framebuffers().EmitFramebufferByName(*fbo), 0u) << "the Named record did not go out";
ASSERT_TRUE(Framebuffers().NamedRecordIsLatched(handle));
}
EXPECT_FALSE(MGPipeSlots().IsLive(MGPipeKind::Framebuffer, handle));
EXPECT_FALSE(Framebuffers().NamedRecordIsLatched(handle))
<< "the dead framebuffer's Named latch survived its death";
}
#endif // MOBILEGL_PIPE_PUSH
// =========================================================================================