[MG_Backend] (Disaggregated): P5e id - ResolveVaoTwin / ResolveTextureTwin / ResolveProgramTwin by handle

- The three by-handle resolvers the per-family packages call, in the shape
  SamplerImpl::ResolveSamplerCsoTwin already runs (CONTRACT-P5E §4.1/§4.2):
  handle in, THE RECORD FIRST, then AdoptTwinByHandle, then a twin if the slot is
  empty. A handle with no applier record is a seam defect and adopting a slot for
  it would leave a twin nothing can sync, so it is a loud null rather than an
  empty twin; a generation behind the live entry's is refused by
  PipeTwinHandleIsAdoptable rather than dropping the incumbent's driver object.
- WHAT THEY DO NOT DO is the deliverable: no frontend object is touched and the
  client's slot allocator is never probed. They also do not SYNC - which serials
  gate a VAO / texture / program sync, and what the sync reads, belongs to vi,
  tx2 and pg, and a resolver that synced would have to know.
- ResolveProgramTwin resolves through the band-aware PipeShaderCsoRecordForHandle
  and adopts into SlotTables.h's m_band, which is why the band landed in the same
  package: after the rekey a program-pipeline composite's bind is the ordinary
  path through this function.
- Declared beside each family's registry global rather than in the registry class
  region BRIEF-P5E names, so that the three read exactly like the sampler
  resolver they copy. Reported to the integrator as a range widening.
This commit is contained in:
2026-09-18 06:35:24 -04:00
parent 69a278549b
commit 0d54de62e2
@@ -4885,6 +4885,27 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (!record.Live || record.Gen != cso.Gen) return nullptr;
return &record;
}
// P5e (id), CONTRACT-P5E §4.1. See Managers.h for the contract; the body is the one
// SamplerImpl::ResolveSamplerCsoTwin already runs, with the sync left to vi.
BackendVertexArrayObject* ResolveVaoTwin(MG_Pipe::MGPipeHandle elements) {
if (MG_Pipe::MGPipeHandleIsNull(elements)) return nullptr;
// THE RECORD FIRST, before the table is touched: a handle with no record means the
// client named a vertex-elements CSO it never described (or one it deleted while a
// binding still names it), and adopting a slot for it would leave a twin nothing
// can ever sync. The applier's own Gen check is what turns a stale handle into this
// null rather than into its successor's record.
if (PipeRecordAt(MG_Pipe::MGPipeApplier().VertexElementsCsos, elements) == nullptr) {
MGLOG_E_ONCE("MGPipe: vertex-elements CSO {%u, %u} has no applier record on the handle "
"arm, so no driver VAO can be built for it and the draw keeps what is bound",
elements.Slot, elements.Gen);
return nullptr;
}
auto* slot = AdoptTwinByHandle(g_backendVertexArrayObjects, elements, "vertex-elements CSO");
if (slot == nullptr) return nullptr;
if (!*slot) *slot = MakeShared<BackendVertexArrayObject>();
return slot->get();
}
#endif // MOBILEGL_PIPE_PUSH
void BackendVertexArrayObject::SyncToBackend(
@@ -9105,6 +9126,28 @@ namespace MobileGL::MG_Backend::DirectGLES {
MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS>
g_boundTexturesCache;
TwinRegistry<MG_State::GLState::ITextureObject, BackendTextureObject, MG_Pipe::MGPipeKind::Texture> g_backendTextureObjects;
#if MOBILEGL_PIPE_PUSH
// P5e (id), CONTRACT-P5E §4.1. See Managers.h for the contract.
BackendTextureObject* ResolveTextureTwin(MG_Pipe::MGPipeHandle res) {
if (MG_Pipe::MGPipeHandleIsNull(res)) return nullptr;
// THE RECORD FIRST: a texture handle with no resource record is a seam defect - the
// client named a texture it never created, or one whose resource_destroy has
// already applied while a binding still names it - and a twin adopted for it would
// own a driver texture nothing ever uploads to.
if (PipeTextureRecordForHandle(res) == nullptr) {
MGLOG_E_ONCE("MGPipe: texture {%u, %u} has no applier resource record on the handle "
"arm, so no driver texture can be built for it and the unit keeps what "
"it holds",
res.Slot, res.Gen);
return nullptr;
}
auto* slot = AdoptTwinByHandle(g_backendTextureObjects, res, "texture");
if (slot == nullptr) return nullptr;
if (!*slot) *slot = MakeShared<BackendTextureObject>();
return slot->get();
}
#endif
} // namespace TextureImpl
namespace FramebufferImpl {
@@ -10765,6 +10808,27 @@ namespace MobileGL::MG_Backend::DirectGLES {
constexpr Int kMaxDrainedProgramErrors = 32;
TwinRegistry<MG_State::GLState::ProgramObject, BackendProgramObjectImpl, MG_Pipe::MGPipeKind::ShaderCso> g_backendProgramObjects;
#if MOBILEGL_PIPE_PUSH
// P5e (id), CONTRACT-P5E §4.1. See Managers.h for the contract - in particular why the
// composite band had to land in the same package as this function.
BackendProgramObjectImpl* ResolveProgramTwin(MG_Pipe::MGPipeHandle cso) {
if (MG_Pipe::MGPipeHandleIsNull(cso)) return nullptr;
// THE RECORD FIRST, and through the BAND-AWARE reader: a composite pipeline's CSO
// lives in the applier's CompositeShaderCsos table, and asking the ordinary one for
// it would answer null for a program that is perfectly live.
if (PipeShaderCsoRecordForHandle(cso) == nullptr) {
MGLOG_E_ONCE("MGPipe: shader CSO {%u, %u} has no applier record on the handle arm, so "
"no driver program can be built for it and the draw keeps what is bound",
cso.Slot, cso.Gen);
return nullptr;
}
auto* slot = AdoptTwinByHandle(g_backendProgramObjects, cso, "shader CSO");
if (slot == nullptr) return nullptr;
if (!*slot) *slot = MakeShared<BackendProgramObjectImpl>();
return slot->get();
}
#endif
BackendProgramObjectImpl::BackendProgramObjectImpl() {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);