[Refactor] (P13 W6d): Espryt's family arm predicates fold to the record arm, checked once at capability bring-up, and the frontend-arm walks they selected go.

This commit is contained in:
2026-10-06 18:08:33 -04:00
parent dc1e95b4c3
commit 8abae647d0
7 changed files with 698 additions and 2708 deletions
@@ -981,6 +981,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// built).
UpdateAdvertisedCapabilityExtensions(m_GLESCapabilities);
UpdateDynamicBackendParameters();
// The colour-attachment cap the framebuffer family checks is known from here on.
DirectGLES::ResolveRecordArmFamilies();
PopulateFormatCapabilities(m_GLESFunctions, m_GLESCapabilities, MutableFormatCapabilities());
PrintFormatCapabilities(GetFormatCapabilities());
return true;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+6 -86
View File
@@ -755,6 +755,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bool ResolveTextureResourceSubsystemArm();
Bool ResolveSamplerSubsystemArm();
Bool ResolveProgramSubsystemArm();
// P13 W6: every family runs the record arm - the frontend arms these resolvers used to choose
// between are gone, so no site asks them any more. What they still decide is whether this
// configuration and this driver CAN run it (a mask missing a family's dependency, a
// colour-attachment cap above the wire's width), and that is asked once, at capability
// bring-up, and stops by name when the answer is no.
void ResolveRecordArmFamilies();
inline Bool FramebufferSubsystemEnabled() {
static const Bool enabled = ResolveFramebufferSubsystemArm();
@@ -1288,33 +1294,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// that question otherwise. Push-only, like the rest of this block.
Uint CurrentBufferContextGeneration();
// P5e (vi), CONTRACT-P5E §5.1 + §5.8 (ruling 1 / ID-81): THE ARM SELECTOR for this
// family, and it is a conjunction on purpose.
//
// Transport != Monolith the record arm exists because there is no frontend VAO on
// this side of a real split. Under Transport=monolith the
// push build keeps its frontend arms token for token - that
// is what the verify comparator compares against, and what
// makes MOBILEGL_IPC_RUN_AHEAD=0 a pure wait-rule A/B on
// identical server code rather than an arm swap.
// the family bit `0x0ff` (bit 7 on, bit 8 off) is a supported A/B and must
// keep running the legacy vertex-input walk; the bit is
// already the gate the rest of this family reads.
//
// It is NOT gated on run-ahead. The records carry the whole family either way, so a
// lockstep split session reads them too and the wait rule changes nothing here - which
// is the only reason ra can flip one constant at the end of the phase and change no
// backend code at all.
//
// P5e (mv): it lives in the HEADER rather than in DirectGLES.cpp because the multi-draw
// path is a second translation unit that has to select the SAME arm - and the addendum's
// rule is that a site states the transport test it relies on, which a copy of the
// conjunction in MultiDraw.cpp would satisfy in letter while giving the family two
// selectors that can drift apart. One definition, spelled at every site that reads it.
inline Bool VertexInputReadsRecords() {
return VertexInputSubsystemEnabled();
}
// Registered as the frontend's BufferBackendOps at backend init and on
// every MakeCurrent (the ES context can be destroyed and recreated, e.g.
// by the trace replayer's probe context).
@@ -1380,15 +1359,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// its retained store) forever. Compared and stamped by
// BackendVertexArrayObject::SyncToBackend.
extern Uint64 g_bufferBackendIdGeneration;
// Redundant-bind cache for INDEXED buffer bindings (glBindBufferBase/Range on
// GL_UNIFORM_BUFFER / GL_SHADER_STORAGE_BUFFER / GL_TRANSFORM_FEEDBACK_BUFFER):
// skips the GL call when the (id, range) already at that index matches, like the
// array-buffer/texture/sampler caches already do. Invalidated on MakeCurrent
// (context may reset).
// Binds the transform feedback capture points [0, bufferCount) from the frontend
// state, and touches nothing else - in particular it never binds a zero the
// application did not ask for. See the definition for why that matters on Mali.
void SyncTransformFeedbackBindingPoints(SizeT bufferCount);
void BindBufferBaseCached(GLenum glTarget, Uint index, Uint id);
void BindBufferRangeCached(GLenum glTarget, Uint index, Uint id, GLintptr offset, GLsizeiptr size);
void InvalidateIndexedBufferBindingCache();
@@ -1503,7 +1473,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
public:
BackendVertexArrayObject();
~BackendVertexArrayObject();
void SyncToBackend(const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject);
// PUBLIC AS OF P5e (vi), and the move is the point rather than a convenience: with
// a live transport there is no frontend VAO on this side to hand to the overload
// above, so VertexArrayImpl::SyncCurrentVAOFromRecords calls this ENTRY directly
@@ -1515,26 +1484,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
void SyncClientSideAttributesForDrawArrays(
const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject, GLint first, GLsizei count,
Uint32 fetchBaseInstance = 0);
// THE SAME UPLOAD FOR A DRAW WHOSE FETCHED ELEMENTS THE (first, count) RANGE DOES NOT
// DESCRIBE: an indexed draw reads the elements its indices name, an instanced one one
// element per instance, and the indirect forms a range that lives in GPU memory.
// `plan` is MGPipeClientFetchPlan's answer for this very draw, so this arm and the
// wire arm's owned snapshot fetch the same elements by construction.
//
// False when a client array could not be snapshotted (an index or command block this
// side cannot read, a fetch range that cannot be represented). The caller must then
// SKIP the draw: issuing it would have the shader read whatever the ES context last
// held for that attribute, which is a wrong picture rather than an error.
//
// UNGUARDED WITH ITS CALLERS. Its only call site is
// VertexArrayImpl::SyncClientSideVertexArraysForFetch in DirectGLES.cpp, whose
// body returns early unless the transport is Monolith - and the monolith arm is
// what the pull, verify and push builds run, not a split-arm fallback. Those
// flavors therefore compile it, so its declaration and definition (Managers.cpp)
// have to be visible there too.
Bool SyncClientSideAttributesForDraw(
const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject,
const MG_Pipe::MGPipeClientFetchPlan& plan, Uint32 fetchBaseInstance);
Uint GetBackendVertexArrayId() const { return m_backendVAOId; }
void Bind() const;
@@ -1940,11 +1889,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// attachment sync and the image sweep both call it, which is why it is declared
// once here rather than twice in two packages' worktrees.
void SyncMipmapsToBackendByHandle(MG_Pipe::MGPipeHandle texture);
// The storage half of the sync for a texture created by glTextureView. Instead of
// allocating storage and replaying uploads, it makes this object's ES name BE a view
// of the storage texture's ES name (EXT/OES_texture_view), which is what gives the
// two names one image and independent per-texture parameters at the same time. The
// parameter and sampler halves are unchanged and run on this name as on any other.
void SyncTextureViewToBackend(const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject);
void StampViewSyncKeys(const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject);
// P5e (tx2). THE VIEW ARM UNDER A TRANSPORT, and what it can and cannot answer.
@@ -2532,13 +2476,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// resolving it here keeps the monolith-glue lookup at one site per sync.
const MG_Pipe::MGPFramebufferState* PushedFramebufferRecord(MG_Pipe::MGPipeHandle fbo);
// THE BINDING QUESTION, WHICH IS NOW A DIFFERENT QUESTION FROM THE DESCRIPTION (ID-19(d)):
// "is the framebuffer this handle names the one bound to `target`". One array compare
// against MGPipeApplierState::BoundFramebuffer, never a record lookup - a Named record
// describes an object without claiming any binding for it, so asking the record would
// give the wrong answer by construction.
Bool PushedFramebufferIsBoundTo(FramebufferTarget target, MG_Pipe::MGPipeHandle fbo);
// ---- P5e (fb, CONTRACT-P5E.md §5.4): the reverse index, texture -> framebuffers ------
//
// "Which framebuffers currently have this texture attached." The detach walk
@@ -2554,8 +2491,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// why nothing prunes a dead row.
void NoteFramebufferTextureAttachments(MG_Pipe::MGPipeHandle fbo,
const MG_Pipe::MGPFramebufferState& record);
Vector<MG_Pipe::MGPipeHandle> FramebuffersAttachingTexture(MG_Pipe::MGPipeHandle texture);
// The record's surface for an attachment POINT, or null when this record does not
// describe that point at all (Color8..Color31, and the default framebuffer's FRONT/BACK
// tokens). MGPFramebufferState carries Color[8] + Depth + Stencil, which is every point
@@ -2754,8 +2689,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
void EnsureDepthAttachment2D(ScratchFramebuffer& fb, GLenum fbTarget, Uint tex, GLenum texTarget, GLint level,
Bool withStencil, Bool stencilOnly = false);
void EnsureDepthAttachmentLayer(ScratchFramebuffer& fb, GLenum fbTarget, Uint tex, GLint level, GLint layer);
void EnsureNoColorAttachment(ScratchFramebuffer& fb, GLenum fbTarget);
void EnsureNoDepthAttachment(ScratchFramebuffer& fb, GLenum fbTarget);
void EnsureReadBuffer(ScratchFramebuffer& fb, GLenum readBuffer);
void EnsureDrawBuffer(ScratchFramebuffer& fb, GLenum drawBuffer);
// A 1x1 RGBA8-renderbuffer-complete FBO (GenerateMipmap needs a complete
@@ -3007,9 +2940,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
Uint GetUniformBlockBinding(Uint index) const;
Int GetUniformSamplerOrImageUnitIndex(Uint location) const;
// The monolith-glue constructor: the archive IS the frontend's live tables, so
// there is no overlay and the three mutable fields answer from them directly.
static ProgramArchiveSource FromFrontend(const MG_State::GLState::ProgramObject& program);
// The handle arm: the record's own archive, with its three tails overlaid.
static ProgramArchiveSource FromRecord(MG_Pipe::MGPipeHandle cso,
const MG_Pipe::MGPipeShaderCsoRecord& record);
@@ -3079,7 +3009,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
BackendProgramObjectImpl();
~BackendProgramObjectImpl();
void SyncToBackend(const SharedPtr<MG_State::GLState::ProgramObject>& stateProgramObject);
// P5e (pg), CONTRACT-P5E.md §5.5: the same sync keyed on the ShaderCso HANDLE and
// answered from the record - the archive the create carries and the three binding
// tails set_program_bindings carries. It is an OVERLOAD beside the frontend one,
@@ -3361,12 +3290,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// has to rebuild it. Computed from the values, so re-setting a block to the binding it
// already has costs nothing. 0 when nothing was ever rebound.
Uint64 ComputeShaderStorageBlockBindingSignature(const ProgramBuildSource& src);
// P5e (pg): the computation itself, for the monolith arm - the source constructor seeds
// itself with it and the monolith draw path asks it per draw. Push-only: in a pull build
// the overload above IS this body, so no name is added there.
Uint64 ComputeShaderStorageBlockBindingSignatureOf(
const MG_State::GLState::ProgramObject& program);
// Everything the image-format bake needs from one walk of a program's uniform
// reflection. GLSL ES requires a format layout qualifier on every image uniform;
// desktop GLSL lets a writeonly (or readonly) declaration omit one, and the only
@@ -3581,9 +3504,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
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
+30 -63
View File
@@ -177,75 +177,42 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
BoundIndexBufferView ResolveBoundIndexBuffer(IndexBufferQuestion question, const char* entry) {
BoundIndexBufferView view;
if (BufferImpl::VertexInputReadsRecords()) {
const auto& st = MG_Pipe::MGPipeApplier();
const MG_Pipe::MGPipeHandle res = BufferImpl::ResolveDrawIndexBufferFromRecord(st).Res;
// A null Res is "no element array buffer bound", exactly as a null frontend slot
// is on the arm below - the batch's indices are a client array, and every caller
// already has an arm for that. It is NOT the arm test; the arm was decided above.
if (MG_Pipe::MGPipeHandleIsNull(res)) return view;
view.Present = true;
if (question == IndexBufferQuestion::Presence) return view;
const auto& st = MG_Pipe::MGPipeApplier();
const MG_Pipe::MGPipeHandle res = BufferImpl::ResolveDrawIndexBufferFromRecord(st).Res;
// A null Res is "no element array buffer bound", exactly as a null frontend slot
// is on the arm below - the batch's indices are a client array, and every caller
// already has an arm for that. It is NOT the arm test; the arm was decided above.
if (MG_Pipe::MGPipeHandleIsNull(res)) return view;
view.Present = true;
if (question == IndexBufferQuestion::Presence) return view;
if (question == IndexBufferQuestion::HostBytes) {
// The applier has already consumed the persistent-map blocks for this
// resource before the draw verb replayed; the one Sync* this arm owes is the
// monolith's SyncGpuWrites, and (P8-C) SplitHostBytesForCpuRead is it: a store
// a shader wrote on this side is read back from this server's own GL buffer,
// because no writeback ever reaches the server's staged copy. That is also why
// this arm cannot be expressed as "find the object and run the monolith body".
auto* resource = BufferImpl::FindBufferResourceForHandle(res);
if (resource == nullptr) RefuseMissingIndexBufferRecord(entry, res, "backend resource");
view.Size = BufferImpl::ResourceWidthForHandle(res);
view.HostBytes = BufferImpl::SplitHostBytesForCpuRead(*resource, res, "multidraw_index_rebase");
if (view.HostBytes != nullptr && IndexBufferRecordHasDefinedContent(st, res)) {
BufferImpl::RequireStagedCoverage(*resource, view.HostBytes, 0, view.Size,
"multidraw_index_rebase");
}
// A null HostBytes is not a refusal: MappedData() on the monolith arm is
// equally allowed to be null (an adopted coherent map keeps its bytes
// elsewhere), and the one caller that asks declines the tier for it.
return view;
}
auto* resource = BufferImpl::EnsureBufferResourceForHandle(nullptr, res);
if (question == IndexBufferQuestion::HostBytes) {
// The applier has already consumed the persistent-map blocks for this
// resource before the draw verb replayed; the one Sync* this arm owes is the
// monolith's SyncGpuWrites, and (P8-C) SplitHostBytesForCpuRead is it: a store
// a shader wrote on this side is read back from this server's own GL buffer,
// because no writeback ever reaches the server's staged copy. That is also why
// this arm cannot be expressed as "find the object and run the monolith body".
auto* resource = BufferImpl::FindBufferResourceForHandle(res);
if (resource == nullptr) RefuseMissingIndexBufferRecord(entry, res, "backend resource");
view.Id = resource->id;
if (question == IndexBufferQuestion::DriverNameAndSize) {
view.Size = BufferImpl::ResourceWidthForHandle(res);
view.Size = BufferImpl::ResourceWidthForHandle(res);
view.HostBytes = BufferImpl::SplitHostBytesForCpuRead(*resource, res, "multidraw_index_rebase");
if (view.HostBytes != nullptr && IndexBufferRecordHasDefinedContent(st, res)) {
BufferImpl::RequireStagedCoverage(*resource, view.HostBytes, 0, view.Size,
"multidraw_index_rebase");
}
// A null HostBytes is not a refusal: MappedData() on the monolith arm is
// equally allowed to be null (an adopted coherent map keeps its bytes
// elsewhere), and the one caller that asks declines the tier for it.
return view;
}
// MONOLITH GLUE from here down, token for token what each call site did before.
const auto& vao = MG_Pipe::gPipeInputs.GetBoundVertexArray();
if (!vao) return view;
const auto& ibo = vao->GetIndexBufferBindingSlot().GetBoundObject();
if (!ibo) return view;
view.Present = true;
switch (question) {
case IndexBufferQuestion::Presence:
break;
case IndexBufferQuestion::DriverName: {
const auto* resource = BufferImpl::EnsureBufferResource(ibo);
view.Id = resource ? resource->id : 0;
break;
auto* resource = BufferImpl::EnsureBufferResourceForHandle(nullptr, res);
if (resource == nullptr) RefuseMissingIndexBufferRecord(entry, res, "backend resource");
view.Id = resource->id;
if (question == IndexBufferQuestion::DriverNameAndSize) {
view.Size = BufferImpl::ResourceWidthForHandle(res);
}
case IndexBufferQuestion::DriverNameAndSize: {
const auto* resource = BufferImpl::EnsureBufferResource(ibo);
view.Id = resource ? resource->id : 0;
view.Size = ibo->GetSize();
break;
}
case IndexBufferQuestion::HostBytes:
// The shadow is the source of truth for CPU reads, but a persistent map or
// a shader write may have moved past it since the last sync.
ibo->SyncPersistentMappedRange();
ibo->SyncGpuWrites();
view.HostBytes = ibo->MappedData();
view.Size = ibo->GetSize();
break;
}
(void)entry;
return view;
}
+3 -3
View File
@@ -119,9 +119,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
EsprytSlotArmVerdict ClassifyEsprytSlotArm(Bool subsystemBitSet, Bool legacyMemosEnabled);
// This process's verdict, read off MG_Config::Features. Latches nothing and stops nothing.
EsprytSlotArmVerdict CurrentEsprytSlotArmVerdict();
// Says, at backend bring-up, that the knobs leave no arm - and does NOT stop.
//
// The stop cannot live here, and that is the whole point of the split. Backend context
@@ -137,6 +134,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// DirectGLESSlotTable.EglBringUpUnderTheArmlessKnobPairReturnsInsteadOfStopping, which runs
// the real bring-up entry point under the pair in a forked child: edit that site back to
// ResolveEsprytSlotTablesArm() and the case fails naming both knobs.
// This process's verdict, read off MG_Config::Features. Latches nothing and stops nothing.
EsprytSlotArmVerdict CurrentEsprytSlotArmVerdict();
void DiagnoseEsprytSlotArm();
// Reads the config, logs, installs the death-notice consumer, and STOPS when the operator
-19
View File
@@ -1814,25 +1814,6 @@ TEST(DirectVulkanSanity, SpirvStorageImageFormatsMapToVulkanFormats) {
}
}
TEST(DirectVulkanSanity, MutableStorageImageViewsUseVulkanCompatibilityClasses) {
using MobileGL::MG_Backend::DirectVulkan::VkTextureManager;
EXPECT_TRUE(VkTextureManager::AreStorageImageViewFormatsCompatible(
VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32_UINT));
EXPECT_TRUE(VkTextureManager::AreStorageImageViewFormatsCompatible(
VK_FORMAT_R32_UINT, VK_FORMAT_R32_SINT));
EXPECT_TRUE(VkTextureManager::AreStorageImageViewFormatsCompatible(
VK_FORMAT_R16G16B16A16_UNORM, VK_FORMAT_R16G16B16A16_SFLOAT));
EXPECT_TRUE(VkTextureManager::AreStorageImageViewFormatsCompatible(
VK_FORMAT_R32_SFLOAT, VK_FORMAT_R8G8B8A8_UINT));
EXPECT_TRUE(VkTextureManager::AreStorageImageViewFormatsCompatible(
VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32_SFLOAT));
EXPECT_FALSE(VkTextureManager::AreStorageImageViewFormatsCompatible(
VK_FORMAT_R32_SFLOAT, VK_FORMAT_R16G16B16A16_SFLOAT));
EXPECT_FALSE(VkTextureManager::AreStorageImageViewFormatsCompatible(
VK_FORMAT_R32_SFLOAT, VK_FORMAT_D32_SFLOAT));
}
TEST(DirectVulkanSanity, StorageImageViewFormatUsesBindingOnlyForFormatlessFloatPolicy) {
using MobileGL::MG_Backend::DirectVulkan::UniformManager;