mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-10 05:08:31 +09:00
[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:
@@ -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) {
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user