[Refactor] (Espryt): key the texture, renderbuffer, framebuffer, sampler and program twins on the client-minted handle instead of the frontend object

This commit is contained in:
2026-09-08 16:57:36 -04:00
committed by rereview
parent 568d587ae7
commit 33f5e878e7
2 changed files with 427 additions and 0 deletions
+253
View File
@@ -214,6 +214,26 @@ namespace MobileGL::MG_Backend::DirectGLES {
case MG_Pipe::MGPipeKind::ShaderCso:
PrgramImpl::g_backendProgramObjects.DestroyByLifetimeId(lifetimeId);
break;
#if MOBILEGL_PIPE_PUSH
case MG_Pipe::MGPipeKind::SamplerViewCso:
// P4a (D-I1, D5): the sixth kind, and the only one whose notice names a
// lifetime id that belongs to ANOTHER object - a sampler view is minted off its
// texture's id, one per ITextureObject, so ~TextureObjectBase raises this arm
// and the texture arm above from the same id. That is legal precisely because
// the two kinds have separate slot spaces, and it is why the two arms are
// written out separately rather than folded: the allocator resolves
// (kind, lifetimeId), so each finds its own slot or nothing.
//
// IDEMPOTENT, like every arm here. This is the REDUNDANT SECOND PATH: the
// client's MGPipeEmitSamplerViewCsoDestroyAndFree emits delete_sampler_view,
// raises this notice and frees the slot, in that order. Whichever of the two
// frees first wins; the other resolves nothing, because the allocator erases
// its lifetimeId -> slot mapping on Free and OnFrontendObjectDestroyed answers
// false for a handle it cannot resolve. This twin owns no driver id at all, so
// even a double release is a pointer reset.
SamplerViewImpl::BackendSamplerViewTable::OnFrontendObjectDestroyed(lifetimeId);
break;
#endif
case MG_Pipe::MGPipeKind::VertexElementsCso:
// P3a C-1: this is now the SECOND path, not the only one. The client speaks the
// whole death itself (MGPipeEmitVertexElementsDestroyAndFree: delete the
@@ -3484,6 +3504,239 @@ namespace MobileGL::MG_Backend::DirectGLES {
void UploadRingOnPresent() { RingOnPresent(g_uploadRing); }
} // namespace BufferImpl
#if MOBILEGL_PIPE_PUSH
// ---- P4a (D-K3): the four family arm resolvers, beside BufferImpl's two ----
//
// They reuse BufferImpl's ClassifyPipeSubsystemArm / StopOnArmlessPipeSubsystem unchanged:
// one voice for "the operator left no arm at all", so the six families cannot disagree
// about what an armless verdict means or about whether it stops.
//
// Every one of the four answers legacyArmSurvivesLegacyMemos = FALSE, because every one of
// the four pre-handle arms is compiled under MOBILEGL_PIPE_LEGACY_MEMOS (Managers.h names
// them per family). So all four can reach NoArm and all four stop with the named
// Fatal{PipeLegacyMemosDisabled, ...} rather than running the very arm the operator asked
// to have taken away and handing back a result measured on it.
namespace {
// Shared by the three dependent families, so the refusal reads the same way three times
// and a future fourth cannot invent a different wording. Returns true when the
// dependency is missing, having said so once.
Bool PipeSubsystemDependencyMissing(Uint64 mask, Uint64 dependencyBit, const char* what) {
if ((mask & dependencyBit) != 0) return false;
MGLOG_E("MGPipe: %s - REFUSING the dependent bit and running the legacy arm. "
"Set both bits, or clear both",
what);
return true;
}
// The release-build VOICE for StateBackendObjectRegistry::GetOrCreateByHandle's two
// silent refusals, shared by the five kinds P4a re-keys so the wording cannot drift
// between them. It is the shape GetOrCreateBufferResourceForHandle gives P3a's buffer
// family, lifted into one template because five copies of it is five chances to write
// one of them differently.
//
// WHY A VOICE AT ALL: the table asserts, and MOBILEGL_ASSERT compiles out at INFO -
// which all three gate builds and every shipped build are - so an object that silently
// stops being twinned is exactly the failure mode the refusal exists to replace.
//
// Returns null on the legacy arm too, so a caller that reaches this without checking
// its family's arm gets nothing rather than a twin the legacy arm would never see.
template <typename Registry>
typename Registry::BackendPtr* AdoptTwinByHandle(Registry& registry, MG_Pipe::MGPipeHandle handle,
const char* kindName) {
if (MG_Pipe::MGPipeHandleIsNull(handle)) return nullptr;
if (handle.Slot >= Registry::SlotTable::kMaxHandleSlot) {
MGLOG_E_ONCE("MGPipe: %s handle slot %u is past the backend table's %u bound - "
"refusing to twin it",
kindName, handle.Slot, Registry::SlotTable::kMaxHandleSlot);
return nullptr;
}
const Uint32 liveGen = registry.LiveGenAt(handle.Slot);
if (liveGen != 0 && liveGen > handle.Gen) {
// SlotTables.h:301-321: forward is a recycle and resets the twin, BACKWARD is
// refused, because adopting it would release the incumbent LIVE twin's driver
// id and then stamp the slot back to the dead object's generation, after which
// the incumbent's own FindByHandle refuses it and it is silently handed a
// fresh, empty twin - a leak AND an object that loses its storage with no
// diagnostic. That is the shape commit d7655247 fixed on the buffer side.
MGLOG_E_ONCE("MGPipe: %s handle {%u, %u} names a generation BEHIND the live "
"twin's %u - refusing rather than dropping the incumbent's driver "
"object",
kindName, handle.Slot, handle.Gen, liveGen);
return nullptr;
}
return registry.GetOrCreateByHandle(handle);
}
} // namespace
Bool ResolveFramebufferSubsystemArm() {
const Uint64 mask = MG_Config::Features.PipePush;
const Bool bitSet = (mask & MG_Pipe::kMGPipeSubsystemFramebuffer) != 0;
Bool refused = false;
if (bitSet) {
// BIT 9 REQUIRES BIT 10. Every MGPSurface::Res in the record names a Texture or a
// Renderbuffer handle, and only bit 10 puts twins in those two slot tables; without
// it every attachment lookup would miss and the walk would leave the driver
// framebuffer holding whatever the last owner attached. The mirror pair (bit 10 set,
// bit 9 clear) is FINE: the legacy FBO sync reaches the texture twin through
// SyncTextureObjectToBackend, which dispatches to the handle arm by itself.
refused = PipeSubsystemDependencyMissing(
mask, MG_Pipe::kMGPipeSubsystemTextureResources,
"kMGPipeSubsystemFramebuffer (bit 9) is set but kMGPipeSubsystemTextureResources "
"(bit 10) is clear; MGPSurface::Res names a Texture or Renderbuffer handle and "
"only bit 10 populates those slot tables");
// D-C3: the wire array is Color[8] and this is the driver's RAW ES cap, which is
// NOT clamped to 8 on the GLES path (ValidateColorAttachmentInRange rejects at or
// above it, and BackendObject_DirectGLES publishes it verbatim). Every campaign
// device reports 8 and ES 3.2's minimum is 8 - but a driver reporting more would
// make the record silently truncate, and truncating silently is the bug class this
// phase is closing, while widening the payload is a wire change nobody has evidence
// for. So: refuse the bit, name the cap, run the legacy arm.
if (!refused &&
static_cast<Uint32>(std::max<Int>(g_GLESCapabilities.MaxColorAttachments, 0)) >
MG_Pipe::kMGPipeMaxColorAttachments) {
MGLOG_E("MGPipe: this driver reports GL_MAX_COLOR_ATTACHMENTS = %d, above "
"MGPFramebufferState::Color[%u]'s wire width - REFUSING "
"kMGPipeSubsystemFramebuffer (bit 9) rather than truncating the record, "
"and running the legacy framebuffer arm",
g_GLESCapabilities.MaxColorAttachments, MG_Pipe::kMGPipeMaxColorAttachments);
refused = true;
}
}
const BufferImpl::PipeSubsystemArmVerdict verdict = BufferImpl::ClassifyPipeSubsystemArm(
bitSet && !refused, MG_Config::Features.PipeLegacyMemos,
/*legacyArmSurvivesLegacyMemos=*/false);
if (verdict == BufferImpl::PipeSubsystemArmVerdict::NoArm) {
BufferImpl::StopOnArmlessPipeSubsystem(
"MOBILEGL_PIPE_PUSH leaves kMGPipeSubsystemFramebuffer (bit 9) clear (or refuses "
"it) and MOBILEGL_PIPE_LEGACY_MEMOS=0 disables the pre-handle g_fboSynced* arm");
}
const Bool enabled = verdict == BufferImpl::PipeSubsystemArmVerdict::Handles;
MGLOG_D("MGPipe: Espryt framebuffer family runs the %s arm", enabled ? "handle" : "legacy");
return enabled;
}
Bool ResolveTextureResourceSubsystemArm() {
const Uint64 mask = MG_Config::Features.PipePush;
const Bool bitSet = (mask & MG_Pipe::kMGPipeSubsystemTextureResources) != 0;
Bool refused = false;
if (bitSet) {
// BIT 10 REQUIRES BIT 7. A buffer texture's MGPResourceDesc::BufferForTexBuffer
// names a Buffer handle and only bit 7 puts twins in the resource slot table, so
// with bit 7 clear every glTexBuffer would resolve to no storage at all. The mirror
// pair (bit 7 set, bit 10 clear) is FINE and is P3a's shipped configuration.
refused = PipeSubsystemDependencyMissing(
mask, MG_Pipe::kMGPipeSubsystemResources,
"kMGPipeSubsystemTextureResources (bit 10) is set but kMGPipeSubsystemResources "
"(bit 7) is clear; a buffer texture's BufferForTexBuffer names a Buffer handle "
"and only bit 7 populates the resource slot table");
}
const BufferImpl::PipeSubsystemArmVerdict verdict = BufferImpl::ClassifyPipeSubsystemArm(
bitSet && !refused, MG_Config::Features.PipeLegacyMemos,
/*legacyArmSurvivesLegacyMemos=*/false);
if (verdict == BufferImpl::PipeSubsystemArmVerdict::NoArm) {
BufferImpl::StopOnArmlessPipeSubsystem(
"MOBILEGL_PIPE_PUSH leaves kMGPipeSubsystemTextureResources (bit 10) clear (or "
"refuses it) and MOBILEGL_PIPE_LEGACY_MEMOS=0 disables the pre-handle texture "
"cheap-gate trio");
}
const Bool enabled = verdict == BufferImpl::PipeSubsystemArmVerdict::Handles;
MGLOG_D("MGPipe: Espryt texture-resource family runs the %s arm", enabled ? "handle" : "legacy");
return enabled;
}
Bool ResolveSamplerSubsystemArm() {
const Uint64 mask = MG_Config::Features.PipePush;
const Bool bitSet = (mask & MG_Pipe::kMGPipeSubsystemSamplers) != 0;
Bool refused = false;
if (bitSet) {
// BIT 11 REQUIRES BIT 10, and this is the pair G12 drives at 0x9ff. Every
// MGPBoundView::Texture and every MGPImageView::Res names a Texture handle, and
// only bit 10 puts one in the slot table; without it every per-unit lookup would
// miss and the walk would `continue` WITHOUT unbinding - i.e. every draw would
// sample through whatever the unit last held, which is exactly the shape the
// bit-8-without-bit-7 refusal exists to prevent one family over. The mirror pair
// (bit 10 set, bit 11 clear) is FINE.
refused = PipeSubsystemDependencyMissing(
mask, MG_Pipe::kMGPipeSubsystemTextureResources,
"kMGPipeSubsystemSamplers (bit 11) is set but kMGPipeSubsystemTextureResources "
"(bit 10) is clear; every MGPBoundView::Texture and MGPImageView::Res names a "
"Texture handle and only bit 10 populates that slot table");
}
const BufferImpl::PipeSubsystemArmVerdict verdict = BufferImpl::ClassifyPipeSubsystemArm(
bitSet && !refused, MG_Config::Features.PipeLegacyMemos,
/*legacyArmSurvivesLegacyMemos=*/false);
if (verdict == BufferImpl::PipeSubsystemArmVerdict::NoArm) {
BufferImpl::StopOnArmlessPipeSubsystem(
"MOBILEGL_PIPE_PUSH leaves kMGPipeSubsystemSamplers (bit 11) clear (or refuses "
"it) and MOBILEGL_PIPE_LEGACY_MEMOS=0 disables UnitSamplerLookupMemo's WeakPtr "
"arm and SamplerPassMemo's raw-pointer rows");
}
const Bool enabled = verdict == BufferImpl::PipeSubsystemArmVerdict::Handles;
MGLOG_D("MGPipe: Espryt sampler family runs the %s arm", enabled ? "handle" : "legacy");
return enabled;
}
Bool ResolveProgramSubsystemArm() {
// Bit 12 depends on NOTHING, and that is said out loud rather than left as an absence:
// a ShaderCso handle names no texture and no buffer, the archive rides beside the
// record as a companion pointer, and the eight extra inputs the server still
// specialises on (D-H5) are read from state this backend already holds.
const Bool bitSet = (MG_Config::Features.PipePush & MG_Pipe::kMGPipeSubsystemPrograms) != 0;
const BufferImpl::PipeSubsystemArmVerdict verdict = BufferImpl::ClassifyPipeSubsystemArm(
bitSet, MG_Config::Features.PipeLegacyMemos, /*legacyArmSurvivesLegacyMemos=*/false);
if (verdict == BufferImpl::PipeSubsystemArmVerdict::NoArm) {
BufferImpl::StopOnArmlessPipeSubsystem(
"MOBILEGL_PIPE_PUSH leaves kMGPipeSubsystemPrograms (bit 12) clear and "
"MOBILEGL_PIPE_LEGACY_MEMOS=0 disables g_programTwinLookupMemo");
}
const Bool enabled = verdict == BufferImpl::PipeSubsystemArmVerdict::Handles;
MGLOG_D("MGPipe: Espryt program family runs the %s arm", enabled ? "handle" : "legacy");
return enabled;
}
namespace SamplerViewImpl {
BackendSamplerViewTable g_backendSamplerViews;
BackendSamplerViewObject* GetOrCreateSamplerViewForHandle(MG_Pipe::MGPipeHandle view) {
if (MG_Pipe::MGPipeHandleIsNull(view)) return nullptr;
// The table refuses both of these itself; this is the release-build VOICE for the
// refusal, because MOBILEGL_ASSERT compiles out at INFO and a view that silently
// stops being twinned is the failure mode the refusal exists to replace. Same shape
// as GetOrCreateBufferResourceForHandle's.
if (view.Slot >= BackendSamplerViewTable::kMaxHandleSlot) {
MGLOG_E_ONCE("MGPipe: sampler-view handle slot %u is past the backend table's %u "
"bound - refusing to twin it",
view.Slot, BackendSamplerViewTable::kMaxHandleSlot);
return nullptr;
}
const Uint32 liveGen = g_backendSamplerViews.LiveGenAt(view.Slot);
if (liveGen != 0 && liveGen > view.Gen) {
MGLOG_E_ONCE("MGPipe: sampler-view handle {%u, %u} names a generation BEHIND the "
"live twin's %u - refusing rather than dropping the incumbent",
view.Slot, view.Gen, liveGen);
return nullptr;
}
auto& twin = g_backendSamplerViews.GetOrCreate(view);
if (!twin) twin = MakeShared<BackendSamplerViewObject>();
return twin.get();
}
BackendSamplerViewObject* FindSamplerViewForHandle(MG_Pipe::MGPipeHandle view) {
auto* twin = g_backendSamplerViews.FindByHandle(view);
return twin ? twin->get() : nullptr;
}
MG_Pipe::MGPipeHandle HandleOfSamplerViewForTexture(
const MG_State::GLState::ITextureObject* textureObject) {
// Through the table's own single-entry front memo rather than straight into
// MGPipeSlots().FindByLifetimeId, which is a hash lookup. HandleOf answers
// identically - the same allocator probe on a miss - and remembers the answer; a
// null answer is deliberately never memoised (SlotTables.h, at HandleOf).
return g_backendSamplerViews.HandleOf(textureObject);
}
} // namespace SamplerViewImpl
#endif
namespace VertexArrayImpl {
namespace {
SizeT GetDataTypeSize(DataType type) {
+174
View File
@@ -428,6 +428,51 @@ namespace MobileGL::MG_Backend::DirectGLES {
return nullptr;
}
// P4a (D-B1): resolve-or-create BY THE HANDLE THE CALL CARRIED. This is the shape P3a
// already runs for the buffer family through BackendBufferResourceTable, lifted onto
// the five registries that still mint their own handles off a frontend lifetime id -
// the debt SlotTables.h records against itself at the top of that file.
//
// POINTER, not the reference GetOrCreate(StatePtr) returns, and that is deliberate:
// this call has THREE ways to decline and every one of them has to be visible to the
// caller rather than answered with a parked twin.
// * the legacy arm is running, so there is no slot table to index;
// * the slot is past the table's sanity bound (a corrupt 32-bit slot must not decide
// a vector resize);
// * the generation is BEHIND the live entry's. SlotTables.h:301-321 is the whole
// argument: forward is a recycle and resets the twin, BACKWARD is refused, because
// adopting it would destroy the incumbent LIVE twin's driver ids and then stamp the
// slot back to the dead object's generation - the shape commit d7655247 fixed.
// The refusal is SILENT here and gets its release-build voice at the per-kind resolver
// in Managers.cpp, exactly as GetOrCreateBufferResourceForHandle gives P3a's.
BackendPtr* GetOrCreateByHandle(MG_Pipe::MGPipeHandle handle) {
if (!EsprytSlotTablesEnabled()) return nullptr;
if (MG_Pipe::MGPipeHandleIsNull(handle)) return nullptr;
if (handle.Slot >= SlotTable::kMaxHandleSlot) return nullptr;
const Uint32 liveGen = m_slotTable.LiveGenAt(handle.Slot);
if (liveGen != 0 && liveGen > handle.Gen) return nullptr;
return &m_slotTable.GetOrCreate(handle);
}
// The generation of the LIVE entry at this slot, or 0. It exists so a caller can
// DIAGNOSE, in a release build where MOBILEGL_ASSERT is inert, the refusal above
// performs silently.
Uint32 LiveGenAt(Uint32 slot) const {
if (!EsprytSlotTablesEnabled()) return 0;
return m_slotTable.LiveGenAt(slot);
}
// The death half of GetOrCreateByHandle, for a kind whose announcement is its own
// destroy CALL rather than the shared death notice. Hands the twin OUT rather than
// destroying it in place, so the caller reaches whatever the driver id owes - a
// delete, a pool enrolment, a deferred release - with the entry already retired and a
// re-entrant GetOrCreate from the twin's destructor cannot resurrect it. The SLOT is
// not freed: for a handle-keyed kind the CLIENT frees it after the destroy returns.
BackendPtr ReleaseByHandle(MG_Pipe::MGPipeHandle handle) {
if (!EsprytSlotTablesEnabled()) return BackendPtr{};
return m_slotTable.ReleaseByHandle(handle);
}
// P2 step e2. STATIC, because a death notice is about an object and not about a
// registry instance: it is answered by EVERY table of this kind that exists - this
// registry's own, and any by-value copy of it a fixture or a context reset is holding
@@ -552,6 +597,68 @@ namespace MobileGL::MG_Backend::DirectGLES {
using TwinRegistry = StateBackendObjectRegistry<StateObject, BackendObject>;
#endif
#if MOBILEGL_PIPE_PUSH
// ---- P4a (D-K3): one arm resolver per family, beside BufferImpl's two ----
//
// Four bits and therefore four resolvers, for P3a's reason one level out: a framebuffer
// path that regressed, a texture path that regressed, a sampler path that regressed and a
// program path that regressed are four different findings, and clearing one must not
// disarm the other three.
//
// THE RESOLUTION IS LAZY, at the first use, and never at bring-up. Backend context creation
// runs inside eglMakeCurrent and the integration harness pre-flights exactly that sequence
// in a FORKED CHILD; a child that dies on a signal is reported as "no usable GPU" and every
// scenario in the lane is SKIPPED - the lane goes green having run nothing, on the very
// pair of env vars the A/B is driven with, which is what ROADMAP.md:7 forbids. So a stop
// has to land in a test body, i.e. at the first lookup. That is what the inline latches
// below give: a guard-variable load and a perfectly-predicted branch per consult, and the
// arm dispatch folds into the caller (SlotTables.h's EsprytSlotTablesEnabled argument
// verbatim - every one of these is consulted on the per-draw path).
//
// ALL FOUR CAN REACH NoArm and all four STOP there rather than skipping green, because
// every one of the four legacy arms is compiled under MOBILEGL_PIPE_LEGACY_MEMOS:
// framebuffer - the four g_fboSynced* arrays and StampSyncedFBO;
// texture - the twin's m_prevTextureInfo / m_syncedContentVersion cheap-gate trio;
// samplers - UnitSamplerLookupMemo's WeakPtr arm and SamplerPassMemo's raw
// BackendSamplerObject* rows;
// programs - g_programTwinLookupMemo.
//
// THREE OF THEM CARRY A DEPENDENCY (MGPipe.h, D-K2) and it is diagnosed and REFUSED here
// rather than half-run, the bit-8-requires-bit-7 shape ResolveVertexInputSubsystemArm
// already ships: bit 11 requires bit 10, bit 9 requires bit 10, bit 10 requires bit 7. The
// mirror pairs (10 without 11, 10 without 9, 7 without 10) are all FINE and are said so out
// loud, because an unreachable branch that says something different is how the reachable
// one drifts. Bit 12 depends on nothing: a ShaderCso handle names no texture and no buffer.
//
// ResolveFramebufferSubsystemArm additionally carries D-C3's bring-up refusal: the wire
// array is MGPFramebufferState::Color[8] and GetDynamicParameters().MaxColorAttachments is
// the driver's RAW ES cap, which is not clamped to 8 on this path. A driver reporting more
// would silently truncate the record, so the bit is refused with one MGLOG_E naming the cap
// and the legacy arm runs. Widening the payload is a wire change nobody has evidence for;
// truncating silently is the bug class this phase is closing.
Bool ResolveFramebufferSubsystemArm();
Bool ResolveTextureResourceSubsystemArm();
Bool ResolveSamplerSubsystemArm();
Bool ResolveProgramSubsystemArm();
inline Bool FramebufferSubsystemEnabled() {
static const Bool enabled = ResolveFramebufferSubsystemArm();
return enabled;
}
inline Bool TextureResourceSubsystemEnabled() {
static const Bool enabled = ResolveTextureResourceSubsystemArm();
return enabled;
}
inline Bool SamplerSubsystemEnabled() {
static const Bool enabled = ResolveSamplerSubsystemArm();
return enabled;
}
inline Bool ProgramSubsystemEnabled() {
static const Bool enabled = ResolveProgramSubsystemArm();
return enabled;
}
#endif
namespace BufferImpl {
const GLenum TempBufferTarget = GL_ARRAY_BUFFER;
@@ -2231,6 +2338,73 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_backendSamplerObjects;
} // namespace SamplerImpl
#if MOBILEGL_PIPE_PUSH
namespace SamplerViewImpl {
// P4a (D-F2/D-F3): the SIXTH Espryt twin table, and the only one of the six whose kind
// has no frontend object at all. MobileGL has no sampler-view class: GL binds a texture
// to a unit and the sampler uniform's type, the mipmap-completeness predicates and
// IsUndefinedDefaultTexture decide what the shader sees. Gallium's one-view-per-slot IS
// that resolved form, the resolution moves to the CLIENT (ARCHITECTURE.md:206), and
// create_sampler_view carries the restrictions the resolution had to read.
//
// So this twin owns NO DRIVER ID. There is nothing in ES to create for a view; the id
// the unit binds is the texture's, and it lives on BackendTextureObject. What this twin
// is, is the server's MEMO of one resolved view: the record it was built from, keyed on
// that record's serial, plus the two BACKEND-SPECIFIC POST-PROCESSINGS
// ARCHITECTURE.md:206 keeps on the server and which act on the already-resolved set.
// Espryt's is the raw-depth-fetch sampler substitution; Magma's feedback-loop detection
// is its own and is not here.
//
// A twin with no driver id still earns a table: it is what turns "re-derive the
// substitution decision for every sampled unit of every draw" into one serial compare,
// and it is the slot space the client's per-texture SamplerViewCso handle indexes.
// There is deliberately no destructor: nothing here owns a GPU object, so the teardown
// sentinel's whole reason (a twin destructor must not call into an unloaded driver)
// does not apply and the default one is correct in every teardown order.
struct BackendSamplerViewObject {
// The view record as last synced, verbatim. Reading it here rather than re-asking
// the applier is what lets a caller hold the twin across another applier call.
MG_Pipe::MGPSamplerView View{};
// The applier record's Serial this memo was built from. 0 = never synced, and 0 is
// never a real serial (the applier's counters start at 1), so a zeroed memo is a
// guaranteed miss.
Uint64 SyncedSerial = 0;
// Espryt's post-processing, decided from the RESOLVED set: the view's
// InternalFormat answers IsDepthFormatInternalFormat and the sampler CSO record's
// SamplerParameters answer compareMode / minFilter / mipmapMode / magFilter. The
// decision is re-derived when either serial moves; the sampler serial is kept
// beside it so a sampler mutation alone re-derives it.
Uint64 SyncedSamplerSerial = 0;
Bool NeedsRawDepthFetchSampler = false;
};
// Handle-keyed ONLY, exactly like P3a's BackendBufferResourceTable: the StateObject
// parameter names ITextureObject because the template names one and because the view is
// minted off the TEXTURE's lifetime id (D-F2: one view per ITextureObject), which is
// what makes HandleOf below resolve at all. Not one member that would dereference it is
// instantiated - no Find(StateObject*), no ForEachLive - and the handle overloads never
// look at it.
using BackendSamplerViewTable = BackendSlotTable<MG_State::GLState::ITextureObject,
BackendSamplerViewObject,
MG_Pipe::MGPipeKind::SamplerViewCso>;
extern BackendSamplerViewTable g_backendSamplerViews;
// Resolve-or-create / resolve-only by the handle the call carried. Neither touches
// MGPipeSlots(): the handle ARRIVED, already minted by the side that owns minting.
BackendSamplerViewObject* GetOrCreateSamplerViewForHandle(MG_Pipe::MGPipeHandle view);
BackendSamplerViewObject* FindSamplerViewForHandle(MG_Pipe::MGPipeHandle view);
// MONOLITH GLUE, and named as such, the HandleOfBuffer shape: the SamplerViewCso handle
// of a texture this backend is looking at through a frontend object. Legal only because
// the view is minted off the texture's own lifetime id; under a real split neither the
// object nor its lifetime id exists on this side and the handle has to arrive in the
// payload (which, for every path P4a switches over, it does - this is for the paths
// P3b/P4b still owns).
MG_Pipe::MGPipeHandle HandleOfSamplerViewForTexture(
const MG_State::GLState::ITextureObject* textureObject);
} // namespace SamplerViewImpl
#endif
namespace RenderbufferImpl {
class BackendRenderbufferObject {
public: