From 9c7339b214e31bebd19297620e500dddede15661 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 5 Sep 2026 13:46:49 -0400 Subject: [PATCH] [Feat] (State): give RenderbufferObject the never-reused lifetime id every other cache-keyable state object already has MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RenderbufferObject was the last state object a backend twin registry keys on that could only be identified by its heap address or its GL name - both of which recycle. BufferObject, VertexArrayObject and ProgramObject all carry a process-wide, never-reused id for exactly this; the renderbuffer's absence is named in plan B §10.4-5 as one of the two latent problems P0 closes. - Mirrors BufferObject.h:202-208 / BufferObject.cpp:19-24 verbatim in shape: a private static AllocateLifetimeId() over a namespace-scope std::atomic starting at 1 (so a zero-initialised memo slot can never carry a live object's id), a const member initialised from it at construction, and an inline const getter. The doc comment is the buffer one restated for the renderbuffer's own recycling sources. - Deliberately NO GetVersion(): plan B §11 P0 says the id only. A mutation counter would be a second, independent invalidation surface to keep correct, and nothing needs one yet - the renderbuffer's mutable content already reaches the backends through AllocateStorage / SetInternalFormat / SetSamples. - No caller yet, by design: the id exists so §4.7.3's D1 rekey (and the DirectGLES renderbuffer twin registry at Managers.h:1858) has something to key on. It is a pure addition - no existing field, signature or answer changes. - ObjectLifetimeIdTest gains the two cases the other two object types already have, so the renderbuffer is covered by the same allocator-reuse probe: an object rebuilt at a freed address must not answer to the dead one's id, and two live ones must differ. - Tested: cmake --build build-linux -j 24 (clean); ctest -R ObjectLifetimeId -> 6/6 passed (4 pre-existing + 2 new). --- .../RenderbufferState/RenderbufferObject.cpp | 12 ++++++++++++ .../RenderbufferState/RenderbufferObject.h | 11 +++++++++++ .../MG_Test/State/ObjectLifetimeIdTest.cpp | 18 ++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/MobileGL/MG_State/GLState/RenderbufferState/RenderbufferObject.cpp b/MobileGL/MG_State/GLState/RenderbufferState/RenderbufferObject.cpp index 90159adf..91bfca50 100644 --- a/MobileGL/MG_State/GLState/RenderbufferState/RenderbufferObject.cpp +++ b/MobileGL/MG_State/GLState/RenderbufferState/RenderbufferObject.cpp @@ -9,9 +9,21 @@ #include "RenderbufferObject.h" #include +#include + namespace MobileGL { namespace MG_State { namespace GLState { + namespace { + // Starts at 1 so a zero-initialized cache slot can never carry a live + // renderbuffer's id. + std::atomic g_nextRenderbufferLifetimeId{1}; + } + + Uint64 RenderbufferObject::AllocateLifetimeId() { + return g_nextRenderbufferLifetimeId.fetch_add(1, std::memory_order_relaxed); + } + RenderbufferObject::RenderbufferObject(Uint externalIndex) : m_externalIndex(externalIndex) {} Uint RenderbufferObject::GetExternalIndex() const { diff --git a/MobileGL/MG_State/GLState/RenderbufferState/RenderbufferObject.h b/MobileGL/MG_State/GLState/RenderbufferState/RenderbufferObject.h index 11153772..1e1f304b 100644 --- a/MobileGL/MG_State/GLState/RenderbufferState/RenderbufferObject.h +++ b/MobileGL/MG_State/GLState/RenderbufferState/RenderbufferObject.h @@ -42,9 +42,20 @@ namespace MobileGL { Int GetDepthSize() const; Int GetStencilSize() const; Int GetSamples() const; + // Globally-unique, never-reused id for THIS object's lifetime - same contract + // and same motivation as BufferObject::GetLifetimeId(), + // ProgramObject::GetLifetimeId() and VertexArrayObject::GetLifetimeId(). A + // backend that folds a renderbuffer's IDENTITY into a cache key must use this, + // never the GL name (LIFO-recycled by glGenRenderbuffers) and never the heap + // address (recycled by the allocator): both let a deleted-and-recreated + // renderbuffer answer to a dead one's cache entry. + Uint64 GetLifetimeId() const { return m_lifetimeId; } private: + static Uint64 AllocateLifetimeId(); + Uint m_externalIndex = 0; + const Uint64 m_lifetimeId = AllocateLifetimeId(); TextureInternalFormat m_internalFormat = TextureInternalFormat::RGBA; Int m_width = 0; Int m_height = 0; diff --git a/MobileGL/MG_Test/State/ObjectLifetimeIdTest.cpp b/MobileGL/MG_Test/State/ObjectLifetimeIdTest.cpp index 3290be28..0f4a3f02 100644 --- a/MobileGL/MG_Test/State/ObjectLifetimeIdTest.cpp +++ b/MobileGL/MG_Test/State/ObjectLifetimeIdTest.cpp @@ -32,6 +32,7 @@ #include "Includes.h" #include +#include #include using namespace MobileGL; @@ -138,3 +139,20 @@ TEST(ObjectLifetimeIdTest, LiveVertexArrayObjectsHaveDistinctLifetimeIds) { TEST(ObjectLifetimeIdTest, LiveBufferObjectsHaveDistinctLifetimeIds) { ExpectDistinctIdsWhileBothAlive("BufferObject"); } + +// The renderbuffer had no lifetime id at all until plan B §11 P0 gave it one: it is +// the one FBO attachment source whose identity a backend twin registry can only have +// keyed on the heap address or the GL name, both of which recycle. +TEST(ObjectLifetimeIdTest, RenderbufferObjectAtARecycledAddressCarriesAFreshLifetimeId) { + using MG_State::GLState::RenderbufferObject; + const int reuseCount = ProbeLifetimeIdAcrossAddressReuse("RenderbufferObject"); + if (reuseCount == 0) { + GTEST_SKIP() << "inconclusive, not proven: this allocator never handed the same address back across 64 " + "construct/destroy rounds, so the recycled-address case was never exercised"; + } + RecordProperty("address_reuses_observed", reuseCount); +} + +TEST(ObjectLifetimeIdTest, LiveRenderbufferObjectsHaveDistinctLifetimeIds) { + ExpectDistinctIdsWhileBothAlive("RenderbufferObject"); +}