mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 20:28:32 +09:00
[Fix] (MG_Impl, MG_Test): publish the create a respecify's handle never got - a buffer born while the resource op table was unregistered had every later respecify refused and drew through id 0 with no diagnostic
This commit is contained in:
@@ -634,6 +634,33 @@ namespace MobileGL::MG_Pipe {
|
||||
const MGPipeHandle handle = tracker.Acquire(buffer);
|
||||
Uint16 bindMask = tracker.BindMask(handle);
|
||||
if (auto* ctx = LiveContext()) bindMask = tracker.RefreshBindMask(*ctx, buffer, handle);
|
||||
// M-1: THE CREATE FIRST, IF THIS HANDLE NEVER PUBLISHED ONE - which makes the
|
||||
// create/destroy latch self-healing in both directions instead of only one.
|
||||
//
|
||||
// The constructor's create is gated on MGPipeResourceSubsystemEnabled(), which is bit 7
|
||||
// AND "a backend registered MGPipeResourceOps"; the CONSUMER's gate is bit 7 alone. The
|
||||
// two disagree across a register/unregister boundary, and there is a real window:
|
||||
// UnregisterBufferBackendOps nulls the table from OnBackendContextDestroyed
|
||||
// (DestroyEGLContext) and the re-register happens at the next MakeCurrent, while D-A2
|
||||
// deliberately keeps NotifySubData reachable off the render thread. A buffer born in
|
||||
// that window latched Published = false, so the applier had no record for it and every
|
||||
// later respecify was REFUSED - after which EnsureBufferResourceForHandle read
|
||||
// ResourceRecordOf == nullptr, took size 0, returned a twin with no store and drew
|
||||
// through id 0, with no diagnostic anywhere. The legacy arm recovers from the same
|
||||
// window by twinning lazily off the frontend object and full-uploading from the shadow;
|
||||
// this is the handle arm's equivalent, and it costs one bool compare per respecify.
|
||||
//
|
||||
// A create rather than a respecify because that is what the record's absence means: the
|
||||
// applier starts the record over on a create (it does not edit one), so this cannot
|
||||
// resurrect a field from a recycled slot, and the respecify below then defines the
|
||||
// storage exactly as it would have.
|
||||
if (!tracker.WasPublished(handle)) {
|
||||
const MGPResourceDesc createDesc =
|
||||
MGPipeBuildResourceDesc(buffer, handle, bindMask, /*storageDefined=*/false);
|
||||
tracker.NoteDesc(createDesc, true);
|
||||
tracker.NotePublished(handle);
|
||||
MGPipeApplyResourceCreate(createDesc);
|
||||
}
|
||||
const MGPResourceDesc desc = MGPipeBuildResourceDesc(buffer, handle, bindMask, true);
|
||||
tracker.NoteDesc(desc, false);
|
||||
// initialBytes is the client's own shadow base - zero copy, and null is a real answer
|
||||
|
||||
@@ -1791,6 +1791,63 @@ namespace {
|
||||
EXPECT_FALSE(MGPipeSlots().IsLive(MGPipeKind::Buffer, handle));
|
||||
EXPECT_EQ(MGPipeApplier().RefusedResourceCalls, 0u);
|
||||
}
|
||||
|
||||
// M-1: ...AND THE LATCH HEALS IN THE OTHER DIRECTION TOO. The case above covers a buffer
|
||||
// that was published and then lost its backend; this is the mirror - a buffer BORN while no
|
||||
// resource op table was registered, which is a real window and not a theoretical one:
|
||||
// UnregisterBufferBackendOps nulls the table from OnBackendContextDestroyed and the
|
||||
// re-register happens at the next MakeCurrent, while D-A2 keeps the content path reachable
|
||||
// off the render thread.
|
||||
//
|
||||
// Before the repair the buffer latched Published = false, so no applier record existed;
|
||||
// every later respecify was REFUSED and the backend's ensure path then read a null record,
|
||||
// took size 0 and drew through id 0, silently, for the object's whole life. The legacy arm
|
||||
// recovers from the same window by twinning lazily and full-uploading from the shadow.
|
||||
TEST(ResourceEmit, ARespecifyPublishesTheCreateAHandleNeverGot) {
|
||||
PushArm arm;
|
||||
MGPipeResourceTracker& tracker = MGPipeResourceTrackerInstance();
|
||||
|
||||
// The window: the table is gone, so the constructor mints the handle (unconditional)
|
||||
// and emits nothing.
|
||||
MGPipeSetResourceOps(nullptr);
|
||||
ASSERT_FALSE(MGPipeResourceSubsystemEnabled());
|
||||
const SharedPtr<BufferObject> buffer = MakeBuffer(1);
|
||||
const MGPipeHandle handle = tracker.Find(*buffer);
|
||||
ASSERT_FALSE(MGPipeHandleIsNull(handle)) << "the constructor did not mint a handle";
|
||||
ASSERT_FALSE(tracker.WasPublished(handle));
|
||||
// Not through RecordOf: the applier's vector may not even reach this slot yet, which is
|
||||
// the whole point, and RecordOf would index past its end to find out.
|
||||
ASSERT_TRUE(MGPipeApplier().Resources.size() <= static_cast<SizeT>(handle.Slot) ||
|
||||
!MGPipeApplier().Resources[handle.Slot].Live)
|
||||
<< "a create went out with no table registered";
|
||||
|
||||
// The window closes - MakeCurrent re-registers - and the application defines the store.
|
||||
MGPipeSetResourceOps(&arm.m_ops);
|
||||
ASSERT_TRUE(MGPipeResourceSubsystemEnabled());
|
||||
const Uint64 createsBefore = tracker.CreateCount();
|
||||
const Uint64 refusalsBefore = MGPipeApplier().RefusedResourceCalls;
|
||||
buffer->Respecify(256, nullptr);
|
||||
|
||||
EXPECT_EQ(tracker.CreateCount(), createsBefore + 1)
|
||||
<< "the respecify did not publish the create this handle never got, so the applier "
|
||||
"still has no record to respecify into";
|
||||
EXPECT_EQ(MGPipeApplier().RefusedResourceCalls, refusalsBefore)
|
||||
<< "the respecify was refused: the record the create should have opened is missing";
|
||||
EXPECT_TRUE(tracker.WasPublished(handle)) << "the repair did not latch";
|
||||
ASSERT_TRUE(RecordOf(handle.Slot).Live);
|
||||
EXPECT_EQ(RecordOf(handle.Slot).Gen, handle.Gen);
|
||||
EXPECT_EQ(RecordOf(handle.Slot).Desc.Width, 256u)
|
||||
<< "the storage the repair's create deliberately does not carry was not defined by "
|
||||
"the respecify that follows it";
|
||||
|
||||
// ...and the repair is once, not per respecify.
|
||||
const Uint64 createsAfterRepair = tracker.CreateCount();
|
||||
buffer->Respecify(512, nullptr);
|
||||
EXPECT_EQ(tracker.CreateCount(), createsAfterRepair)
|
||||
<< "every respecify re-published a create; the latch is not being read";
|
||||
EXPECT_EQ(RecordOf(handle.Slot).Desc.Width, 512u);
|
||||
EXPECT_EQ(MGPipeApplier().RefusedResourceCalls, refusalsBefore);
|
||||
}
|
||||
#endif // MOBILEGL_PIPE_PUSH
|
||||
} // namespace
|
||||
|
||||
|
||||
Reference in New Issue
Block a user