mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 14:18:31 +09:00
[Feat] (Pipe): land the P4a contract - the resource target enum, the framebuffer target byte, the texture params' builtin sampler, the sampler-parameter field table, four subsystem bits, seven dirty arms and the program archive codec
This commit is contained in:
@@ -195,6 +195,46 @@
|
||||
// resolution reads the applier's BoundVertexElements instead of the object - at which point
|
||||
// PipeFill.cpp's EmittedCallSuppliesTheWholeField arm is where that is decided, deliberately
|
||||
// rather than silently by this row's presence.
|
||||
//
|
||||
// P4a ADDS SIX ROWS, and the same note applies to every one of them: each is SHAPE-ONLY, each
|
||||
// lands in PipeFill.cpp's EmittedCallSuppliesTheWholeField FALSE arm, and the decision is
|
||||
// taken THERE rather than inherited from a row's presence here. The rows and their calls:
|
||||
//
|
||||
// GetFramebufferBindingSlot -> SetFramebufferState GetProgramForDraw -> SetDrawProgram
|
||||
// GetImageTextureBinding -> SetShaderImages GetProgramForDispatch -> SetDispatchProgram
|
||||
// GetTextureUnitObject -> SetSamplerViews GetMaxTouchedTextureUnit -> SetSamplerViews
|
||||
//
|
||||
// Five of the six are the pointer-storage case GetBoundVertexArray already documents: the
|
||||
// field is a BindingSlot<FramebufferObject>, an ImageTextureBinding, a TextureUnit or a
|
||||
// SharedPtr<ProgramObject> - frontend heap references - and the calls carry eight-byte
|
||||
// {slot, gen} handles and resolved descriptors. The applier has no way to produce a pointer
|
||||
// and P4a deliberately does not give it one; skipping the pull would leave every one of those
|
||||
// mirrors null on every draw of every push build. What retires those pulls is not a better
|
||||
// applier, it is the phase where the backend stops reading a frontend object at all.
|
||||
//
|
||||
// THE SIXTH IS A DIFFERENT ARGUMENT AND IT IS WORTH WRITING DOWN, because it looks like the
|
||||
// easy one. GetMaxTouchedTextureUnit is a plain Int, and set_sampler_views' Count IS that
|
||||
// value plus one (the second merge rule: a high-water mark is directly the count argument).
|
||||
// But the set is SUPPRESSED on an unchanged content hash and is emitted only when bit 12 fires,
|
||||
// and bit 12's shutter is Mix(textureContent, GetTextureBindGeneration()) - which does NOT
|
||||
// move on a redundant re-bind of the object a unit already holds, while the high-water mark
|
||||
// DOES (see NoteUnitTouched in DirtySurface.def). So the applier's Count can lag the frontend's
|
||||
// high-water mark by exactly the case the suppressor exists to swallow, and the field keeps
|
||||
// being pulled. Narrowing that is P3b/P4b's, with the backend debounce it takes over.
|
||||
//
|
||||
// FOUR ACCESSORS THAT MAP TO A P4a CALL ARE DELIBERATELY NOT HERE, for GetPixelStoreParameters'
|
||||
// reason - a row here says "this field is supplied", and for these it would be a half-truth:
|
||||
// GetActiveTextureUnit - glActiveTexture's selector. set_sampler_views carries the RESOLVED
|
||||
// per-unit set and no active-unit selector at all; nothing on the wire carries it.
|
||||
// GetTextureContextId - a context identity the backend keys its own tables on. No call
|
||||
// carries it and none should: it is the server's question about the client, not state.
|
||||
// GetTextureBindGeneration / GetSamplingResolutionGeneration - frontend SHUTTERS. What
|
||||
// replaces them server-side is the applier's own Serial, which is a different value with a
|
||||
// different owner; claiming the sets supply the generations would make the fill loop skip
|
||||
// two counters no record carries.
|
||||
// And GetTextureObject / GetProgramObject are STICKY (see MGP_COVERAGE_STICKY_LIST): they are
|
||||
// keyed by GL name, they are object lookups rather than verb state, and a forwarded field has
|
||||
// no storage for an emitted call to supply.
|
||||
#define MGP_COVERAGE_EMITTED_LIST(X) \
|
||||
X(GetBlendColor, SetDynamicState) \
|
||||
X(GetBlendEquationIndexed, CreateRenderState) \
|
||||
@@ -210,8 +250,11 @@
|
||||
X(GetDepthFunc, CreateRenderState) \
|
||||
X(GetDepthMask, CreateRenderState) \
|
||||
X(GetDepthRangeIndexed, SetDynamicState) \
|
||||
X(GetFramebufferBindingSlot, SetFramebufferState) \
|
||||
X(GetImageTextureBinding, SetShaderImages) \
|
||||
X(GetLineWidth, SetDynamicState) \
|
||||
X(GetLogicOp, CreateRenderState) \
|
||||
X(GetMaxTouchedTextureUnit, SetSamplerViews) \
|
||||
X(GetMinSampleShadingValue, CreateRenderState) \
|
||||
X(GetPatchDefaultInnerLevel, SetPatchState) \
|
||||
X(GetPatchDefaultOuterLevel, SetPatchState) \
|
||||
@@ -221,11 +264,14 @@
|
||||
X(GetPolygonOffsetFactor, SetDynamicState) \
|
||||
X(GetPolygonOffsetUnits, SetDynamicState) \
|
||||
X(GetPrimitiveRestartIndex, SetDynamicState) \
|
||||
X(GetProgramForDispatch, SetDispatchProgram) \
|
||||
X(GetProgramForDraw, SetDrawProgram) \
|
||||
X(GetProvokingVertexMode, CreateRenderState) \
|
||||
X(GetRenderStateParameters, CreateRenderState) \
|
||||
X(GetRenderStateParametersVersion, BindRenderState) \
|
||||
X(GetScissorBox, SetDynamicState) \
|
||||
X(GetStencilState, CreateRenderState) \
|
||||
X(GetTextureUnitObject, SetSamplerViews) \
|
||||
X(GetViewport, SetDynamicState) \
|
||||
X(GetViewportIndexed, SetDynamicState) \
|
||||
X(IsCapabilityEnabled, CreateRenderState) \
|
||||
|
||||
@@ -154,6 +154,21 @@
|
||||
// The gate is therefore a COMPLETENESS gate over what the scanner does see. The semantic
|
||||
// proof stays the MOBILEGL_PIPE_VERIFY lane, which is blind to none of them.
|
||||
//
|
||||
// THE MUTATOR PREFIX SET WIDENS AT P4a, and what it does NOT gain is the more interesting
|
||||
// half. `pGLContext->` + Add|Set|Mark|Bump|Allocate|Truncate|Record|Notify|Begin|End could
|
||||
// not see `UseProgram`, `BindVertexArray`, `BindProgramPipelineObject` or
|
||||
// `BindTransformFeedbackObject` - four mutators that each move a field P3a or P4a pushes -
|
||||
// because none of them starts with one of those words. `Use` and `Bind` are added, and the
|
||||
// complete set the widening surfaces was enumerated by grep at the phase's base ref so it
|
||||
// cannot surprise anybody: exactly those four names, on seven call sites.
|
||||
//
|
||||
// `Create*` and `Pop*` are DELIBERATELY NOT ADDED. They create or destroy objects rather than
|
||||
// move a pushed field, and each object class's creation and destruction is already answered
|
||||
// twice over - by its own Mark*ForDeletion row below and by the constructor-time
|
||||
// resource_create - so adding them would produce rows that restate an answer this file already
|
||||
// gives, and every one of them would have to be maintained against a mechanism that is not
|
||||
// theirs. A gate whose rows do not each carry their own question is a gate nobody reads.
|
||||
//
|
||||
// clang-format off
|
||||
|
||||
// X(Mutator, Answer)
|
||||
@@ -279,6 +294,34 @@
|
||||
/* so the honest answer is the pull. Narrowing it is P3b's, when it takes the subsystem */ \
|
||||
/* over and the binding points get a generation of their own. */ \
|
||||
X(SetNamedTransformFeedbackBinding, kPulledEveryVerb) \
|
||||
/* ---- P4a, THE FOUR THE WIDENED PREFIX SET SURFACES. Every one of them moves a field */ \
|
||||
/* P3a or P4a pushes and none of them was visible to the scan before, because none */ \
|
||||
/* begins with one of the ten words the pattern matched. */ \
|
||||
/* UseProgram is bit 6's whole subject: the shutter is */ \
|
||||
/* Mix(GetCurrentProgram()->GetLifetimeId(), GetLinkVersion()) and glUseProgram is */ \
|
||||
/* what moves the object it reads through. Two call sites. */ \
|
||||
/* BindVertexArray is bit 5's, for the same reason one level down: the shutter mixes */ \
|
||||
/* the bound VAO's identity with its configuration version, and this is the bind. */ \
|
||||
/* Three call sites. */ \
|
||||
X(UseProgram, NEW_SHADER) \
|
||||
X(BindVertexArray, NEW_VERTEX_ELEMENTS) \
|
||||
/* NOT NEW_SHADER, and the derivation refutes it outright rather than leaving it a */ \
|
||||
/* judgement: this mutator writes m_boundProgramPipeline (plus the pipeline name table) */ \
|
||||
/* and bit 6's shutter reads m_currentProgram's lifetime id and link version - disjoint */ \
|
||||
/* sets, on every path. That is not an oversight in the shutter either: it reads */ \
|
||||
/* GetCurrentProgram() and DELIBERATELY NOT GetProgramForDraw(), because the tracker */ \
|
||||
/* must not force a compile just to answer "did the shader move", and flattening a */ \
|
||||
/* pipeline into its composite is exactly the compile it would force. What a bind moves */ \
|
||||
/* is which program the validate point will flatten, and that field - */ \
|
||||
/* GetProgramForDraw - is in the may-read mask of every class that draws and is copied */ \
|
||||
/* by the residual fill at every verb of those classes, EMITTED-AND-STILL-PULLED like */ \
|
||||
/* GetBoundVertexArray. So the pull is what holds on every path, and it is the answer. */ \
|
||||
X(BindProgramPipelineObject, kPulledEveryVerb) \
|
||||
/* No shutter at all, and none is needed: the transform-feedback binding reaches the */ \
|
||||
/* backend through GetBoundTransformFeedbackLifetimeId and its siblings, which are in */ \
|
||||
/* the kDraw and kXfbSpan may-read masks, so the residual fill copies them at every */ \
|
||||
/* verb of those classes. Narrowing it is P4b's, with set_stream_output_targets. */ \
|
||||
X(BindTransformFeedbackObject, kPulledEveryVerb) \
|
||||
/* ---- an object's death: no generation, because there is no longer an object */ \
|
||||
/* to carry one. Espryt 0b's delete_* / resource_destroy publishes the kinds */ \
|
||||
/* that have a handle on the wire; programs, program pipelines and shaders have */ \
|
||||
@@ -296,9 +339,24 @@
|
||||
/* Destroyed consumer, package espryt), not the client's: the client mints */ \
|
||||
/* the CSO handle and emits create/bind, and the free rides with that */ \
|
||||
/* consumer. Until it lands the row states the design, not the tree. */ \
|
||||
/* P4a CLOSES ONE OF THE THREE HOLES ABOVE AND STATES WHY THE OTHER TWO ARE NOT HOLES. */ \
|
||||
/* MarkProgramForDeletion -> kExplicitDestroy. delete_shader_state exists now and */ \
|
||||
/* ~ProgramObject emits it through the client-side death helper, in the fixed */ \
|
||||
/* order: the wire delete first, the backend notice second, the slot free last. A */ \
|
||||
/* program pipeline COMPOSITE takes the same call on the same helper - the server */ \
|
||||
/* never learns it is a composite. */ \
|
||||
/* MarkProgramPipelineForDeletion stays kUnpublishedDestroy, and it is NOT waiting */ \
|
||||
/* for a later phase: a ProgramPipelineObject has no lifetime id and no wire object */ \
|
||||
/* at all (its only identity is m_everBound). It never gets a handle, so there is */ \
|
||||
/* nothing for a delete to name. What its cache's eviction DOES publish is the */ \
|
||||
/* composite's delete_shader_state, which is the row above. */ \
|
||||
/* MarkShaderForDeletion stays kUnpublishedDestroy for the same kind of reason: a */ \
|
||||
/* ShaderObject has no lifetime id and never crosses the boundary - the payload is */ \
|
||||
/* per-stage SPIR-V plus the reflection archive, not source, and glslang lives */ \
|
||||
/* entirely on the client. */ \
|
||||
X(MarkBufferObjectForDeletion, kExplicitDestroy) \
|
||||
X(MarkFramebufferObjectForDeletion, kExplicitDestroy) \
|
||||
X(MarkProgramForDeletion, kUnpublishedDestroy) \
|
||||
X(MarkProgramForDeletion, kExplicitDestroy) \
|
||||
X(MarkProgramPipelineForDeletion, kUnpublishedDestroy) \
|
||||
X(MarkRenderbufferObjectForDeletion, kExplicitDestroy) \
|
||||
X(MarkSamplerObjectForDeletion, kExplicitDestroy) \
|
||||
@@ -326,9 +384,32 @@
|
||||
// UNDECIDED, each with the reason --check prints for it. Every bit answer NOT listed here
|
||||
// is marked derived: --check fails when the derivation cannot decide it, and fails again
|
||||
// when a mark here names a pair the derivation now decides, so this list can neither hide a
|
||||
// row nor outlive its reason. Empty today: every bit answer above is supported at field
|
||||
// level. The ten mutators that reach a tainted body (--check prints the count) all carry a
|
||||
// prose answer, which no derivation checks.
|
||||
#define MGP_DIRTY_SURFACE_UNDECIDED_LIST(X)
|
||||
// row nor outlive its reason.
|
||||
//
|
||||
// IT WAS EMPTY UNTIL P4a, and it stops being empty for a reason that is a property of the
|
||||
// SCANNER rather than of the two rows. Both entries below are bit answers that are plainly
|
||||
// true - glUseProgram is what moves the object bit 6's shutter reads through, and
|
||||
// glBindVertexArray is what moves the object bit 5's shutter reads through - and the write
|
||||
// analysis cannot say so, because each of them reaches, BY NAME, a body that writes a member
|
||||
// with no m_ prefix:
|
||||
//
|
||||
// UseProgram -> DestroyProgramSlot() writes `attachedShaders`
|
||||
// BindVertexArray -> a call spelled `Bind(` resolves to every body of that name, one of
|
||||
// which (ImageTextureBinding::Bind) writes `Access`
|
||||
//
|
||||
// A call resolved by name to every body of that name is one of the three over-approximations
|
||||
// this analysis documents about itself, and an unplaceable write TAINTS the body it is in -
|
||||
// which is the right default, because "it does not write anything the shutter reads" must
|
||||
// never be claimed about code the script could not read. Widening the taint rule to ignore
|
||||
// non-m_ writes would weaken the one mechanism that catches a genuine under-fire, so the rows
|
||||
// are MARKED, with the tool's own reason, rather than the tool being made more permissive.
|
||||
// Control 9c is what proves a marked row still needs the mark, and control 18 is what fails
|
||||
// the moment either of these becomes decidable and the mark outlives its reason.
|
||||
//
|
||||
// The ten mutators that reach a tainted body (--check prints the count) all carry a prose
|
||||
// answer, which no derivation checks; these two are the first that carry a bit answer.
|
||||
#define MGP_DIRTY_SURFACE_UNDECIDED_LIST(X) \
|
||||
X(UseProgram, NEW_SHADER) \
|
||||
X(BindVertexArray, NEW_VERTEX_ELEMENTS)
|
||||
|
||||
// clang-format on
|
||||
|
||||
@@ -83,7 +83,30 @@ namespace MobileGL::MG_Pipe {
|
||||
// the other.
|
||||
inline constexpr Uint64 kMGPipeSubsystemResources = 1ull << 7;
|
||||
inline constexpr Uint64 kMGPipeSubsystemVertexInput = 1ull << 8;
|
||||
// bits 9..62 reserved for the later phases, allocated in ROADMAP order.
|
||||
// P4a's four. FOUR AND NOT ONE, 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.
|
||||
//
|
||||
// THREE OF THEM HAVE A DEPENDENCY and it is diagnosed at the first use, never half-run -
|
||||
// one Resolve<Family>SubsystemArm per family beside the backend's existing
|
||||
// ResolveResourceSubsystemArm, modelled on the bit-8-requires-bit-7 refusal it already
|
||||
// ships, and lazy rather than at bring-up because a pre-flight child dying on a signal
|
||||
// makes a whole lane SKIP green: bit 11 requires bit 10 because
|
||||
// every MGPBoundView::Texture and MGPImageView::Res names a Texture handle and only bit 10
|
||||
// puts one in the slot table; bit 9 requires bit 10 because MGPSurface::Res does; and bit
|
||||
// 10 requires bit 7 because a buffer texture's BufferForTexBuffer names a Buffer handle.
|
||||
// The mirror pairs (10 without 11, 10 without 9, 7 without 10) are all fine, and are
|
||||
// stated as such because an unreachable branch that says something different is how the
|
||||
// reachable one drifts. Bit 12 depends on nothing.
|
||||
inline constexpr Uint64 kMGPipeSubsystemFramebuffer = 1ull << 9; // set_framebuffer_state
|
||||
inline constexpr Uint64 kMGPipeSubsystemTextureResources = 1ull << 10; // texture + renderbuffer
|
||||
// resource_*, set_texture_params
|
||||
inline constexpr Uint64 kMGPipeSubsystemSamplers = 1ull << 11; // sampler CSO, sampler view,
|
||||
// the three unit sets
|
||||
inline constexpr Uint64 kMGPipeSubsystemPrograms = 1ull << 12; // shader CSO, draw/dispatch
|
||||
// program, global constants
|
||||
// bits 13..62 reserved for the later phases, allocated in ROADMAP order.
|
||||
// NOT a subsystem, a BEHAVIOUR: turn OFF client-side content addressing of CSOs, so
|
||||
// every pipeline-version change mints a fresh CSO and the map is never probed. This is
|
||||
// the negative control the whole CSO design is measured against (ROADMAP.md P2).
|
||||
@@ -93,6 +116,14 @@ namespace MobileGL::MG_Pipe {
|
||||
// "everything P2 had and nothing of mine" arm is spelled MOBILEGL_PIPE_PUSH=0x7f.
|
||||
inline constexpr Uint64 kMGPipeSubsystemsMigratedAtP2 = 0x7full; // bits 0..6
|
||||
inline constexpr Uint64 kMGPipeSubsystemsMigratedAtP3a = 0x1ffull; // bits 0..8
|
||||
// P4a's, and the two above are NOT edited: 0x1ff is P4a's T2 arm and its "everything P3a
|
||||
// had and nothing of mine" control, exactly as 0x7f was P3a's.
|
||||
inline constexpr Uint64 kMGPipeSubsystemsMigratedAtP4a = 0x1fffull; // bits 0..12
|
||||
static_assert(kMGPipeSubsystemsMigratedAtP4a ==
|
||||
(kMGPipeSubsystemsMigratedAtP3a | kMGPipeSubsystemFramebuffer |
|
||||
kMGPipeSubsystemTextureResources | kMGPipeSubsystemSamplers |
|
||||
kMGPipeSubsystemPrograms),
|
||||
"the P4a phase constant and P4a's four subsystem bits have drifted");
|
||||
|
||||
// The catalogue itself. Only macros, so it is safe to expand inside the namespace, and
|
||||
// consumers (the unit test, later the transport) get MGP_CALL_LIST from this header.
|
||||
|
||||
@@ -85,6 +85,17 @@ namespace MobileGL::MG_Pipe {
|
||||
// a pipeline object, and the server never learns it is a composite - it is just another
|
||||
// ShaderCso. Reserving a band rather than a flag keeps the composite resolver's
|
||||
// lifetime bookkeeping out of the ordinary program slot allocator.
|
||||
//
|
||||
// THE ONE ENTRY POINT INTO THE BAND is MGPipeSlotAllocator::AllocateComposite(lifetimeId)
|
||||
// (MG_Impl/Pipe/SlotAllocator.h, P4a D-H7). MGPipeSlotAllocator::Allocate REFUSES the band
|
||||
// for kind ShaderCso, which is what makes "an ordinary program can never be handed a
|
||||
// composite slot" a property of the allocator rather than of its callers; the band carries
|
||||
// its own exhaustion assert, so exhausting it is a named Fatal rather than silent slot
|
||||
// theft from ordinary programs. A composite's slot has TWO independent release paths - the
|
||||
// pipeline cache's LRU eviction and the composite ProgramObject's own destructor - and
|
||||
// both go through one client-side death helper (MG_Pipe/PipeMutation.h's
|
||||
// MGPipeEmitShaderCsoDestroyAndFree), whose second call is a proven no-op because Free
|
||||
// refuses a slot that is not live at that generation.
|
||||
inline constexpr Uint32 kMGPipeShaderCsoSlotLimit = 1u << 20;
|
||||
inline constexpr Uint32 kMGPipeShaderCsoCompositeSlotBase =
|
||||
kMGPipeShaderCsoSlotLimit - (kMGPipeShaderCsoSlotLimit >> 4);
|
||||
|
||||
+231
-16
@@ -145,15 +145,152 @@ namespace MobileGL::MG_Pipe {
|
||||
static_assert(sizeof(MGPCaps) == sizeof(DynamicBackendParameters) + 8 + 24 + 24,
|
||||
"MGPCaps gained padding or a member; update the wire format");
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// MGPResourceDesc's two discriminators (P4a, D-A3 / D-A4)
|
||||
// ---------------------------------------------------------------------------------
|
||||
|
||||
// MGPResourceDesc::Target. P3a minted no enum for this list because it had exactly one
|
||||
// producer and used the leading member's value (0) for it; P4a's texture family is the
|
||||
// second producer, so the list is written out here, beside the field, in the order the
|
||||
// field's own comment already wrote it.
|
||||
//
|
||||
// TexRect IS A THIRTEENTH ENUMERATOR AND THE BRIEF'S LIST HAS TWELVE. MobileGL's
|
||||
// TextureTarget has TextureRectangle (MG_State/GLState/TextureState/TextureEnum.h), the
|
||||
// table below may not have a `default:` arm, and folding rectangle onto Tex2D would erase
|
||||
// a distinction the frontend keeps and both backends switch on (Espryt's
|
||||
// MapToBackendTextureTarget lowers Tex1D the same way and Tex1D still has its own
|
||||
// enumerator here). It is appended AFTER TexBuffer so every value the design document
|
||||
// names keeps the number it was given.
|
||||
enum class MGPipeResourceTarget : Uint8 {
|
||||
Buffer = 0,
|
||||
Tex1D,
|
||||
Tex2D,
|
||||
Tex3D,
|
||||
Tex1DArray,
|
||||
Tex2DArray,
|
||||
TexCube,
|
||||
TexCubeArray,
|
||||
Tex2DMS,
|
||||
Tex2DMSArray,
|
||||
Renderbuffer,
|
||||
TexBuffer,
|
||||
TexRect,
|
||||
Count,
|
||||
};
|
||||
|
||||
// P3a's constant, moved here from MG_Impl/Pipe/ResourceTracker.h with the enum: the ack
|
||||
// predicate at the bottom of this header now names the buffer target explicitly (D-A2) and
|
||||
// may not reach into MG_Impl to do it. The static_assert is what keeps the two spellings
|
||||
// from drifting; nothing may open-code either.
|
||||
inline constexpr Uint8 kMGPipeResourceTargetBuffer =
|
||||
static_cast<Uint8>(MGPipeResourceTarget::Buffer);
|
||||
static_assert(kMGPipeResourceTargetBuffer == static_cast<Uint8>(MGPipeResourceTarget::Buffer),
|
||||
"P3a's kMGPipeResourceTargetBuffer and MGPipeResourceTarget::Buffer have drifted");
|
||||
|
||||
// A sentinel the table below returns for a TextureTarget enumerator it does not name. It
|
||||
// is NOT a legal Target value - it does not fit the field's Uint8 - so an unmapped
|
||||
// enumerator is a build break at the static_assert rather than a descriptor that quietly
|
||||
// describes the wrong kind of storage. Exactly kMGPipeBindUnmapped's shape.
|
||||
inline constexpr Uint32 kMGPipeResourceTargetUnmapped = 0x100u;
|
||||
|
||||
// The one table. No `default:` arm on purpose - that is what makes the static_assert
|
||||
// below able to see an unnamed enumerator, and it is the shape
|
||||
// MGPipeBindMaskForBufferTarget already uses for BufferTarget.
|
||||
constexpr Uint32 MGPipeResourceTargetForTextureTarget(MobileGL::TextureTarget target) {
|
||||
switch (target) {
|
||||
case MobileGL::TextureTarget::Texture1D:
|
||||
return static_cast<Uint32>(MGPipeResourceTarget::Tex1D);
|
||||
case MobileGL::TextureTarget::Texture2D:
|
||||
return static_cast<Uint32>(MGPipeResourceTarget::Tex2D);
|
||||
case MobileGL::TextureTarget::Texture3D:
|
||||
return static_cast<Uint32>(MGPipeResourceTarget::Tex3D);
|
||||
case MobileGL::TextureTarget::TextureCubeMap:
|
||||
return static_cast<Uint32>(MGPipeResourceTarget::TexCube);
|
||||
// Its own enumerator rather than Tex2D: see the enum's comment.
|
||||
case MobileGL::TextureTarget::TextureRectangle:
|
||||
return static_cast<Uint32>(MGPipeResourceTarget::TexRect);
|
||||
case MobileGL::TextureTarget::Texture2DMultisample:
|
||||
return static_cast<Uint32>(MGPipeResourceTarget::Tex2DMS);
|
||||
case MobileGL::TextureTarget::TextureBuffer:
|
||||
return static_cast<Uint32>(MGPipeResourceTarget::TexBuffer);
|
||||
case MobileGL::TextureTarget::Texture1DArray:
|
||||
return static_cast<Uint32>(MGPipeResourceTarget::Tex1DArray);
|
||||
case MobileGL::TextureTarget::Texture2DArray:
|
||||
return static_cast<Uint32>(MGPipeResourceTarget::Tex2DArray);
|
||||
case MobileGL::TextureTarget::TextureCubeMapArray:
|
||||
return static_cast<Uint32>(MGPipeResourceTarget::TexCubeArray);
|
||||
case MobileGL::TextureTarget::Texture2DMultisampleArray:
|
||||
return static_cast<Uint32>(MGPipeResourceTarget::Tex2DMSArray);
|
||||
// NOT TEXTURE TARGETS. Listed rather than defaulted so the completeness assert still
|
||||
// sees them, and mapped to the sentinel because no descriptor may carry either: the
|
||||
// count is the enum's bound and Unknown is what an unresolved GL enum becomes.
|
||||
case MobileGL::TextureTarget::TextureTargetCount:
|
||||
case MobileGL::TextureTarget::Unknown:
|
||||
return kMGPipeResourceTargetUnmapped;
|
||||
}
|
||||
return kMGPipeResourceTargetUnmapped;
|
||||
}
|
||||
|
||||
constexpr Bool MGPipeEveryTextureTargetIsMapped() {
|
||||
for (SizeT i = 0; i < static_cast<SizeT>(MobileGL::TextureTarget::TextureTargetCount); ++i) {
|
||||
if (MGPipeResourceTargetForTextureTarget(static_cast<MobileGL::TextureTarget>(i)) ==
|
||||
kMGPipeResourceTargetUnmapped) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static_assert(MGPipeEveryTextureTargetIsMapped(),
|
||||
"a TextureTarget enumerator has no MGPResourceDesc::Target row: add it to "
|
||||
"MGPipeResourceTargetForTextureTarget, and give it an enumerator of its own "
|
||||
"rather than folding it onto a neighbour (D-A3)");
|
||||
static_assert(MGPipeResourceTargetForTextureTarget(MobileGL::TextureTarget::Texture2D) !=
|
||||
MGPipeResourceTargetForTextureTarget(MobileGL::TextureTarget::TextureRectangle),
|
||||
"a rectangle texture is not a 2D texture on the wire; both backends switch on "
|
||||
"the difference");
|
||||
|
||||
// MGPResourceDesc::BindMask's twelve bits, in the order the field's comment names them.
|
||||
//
|
||||
// THEY LIVED IN MG_Impl/Pipe/ResourceTracker.h THROUGH P3a, with that file's own note
|
||||
// saying "the integrator moves them beside the field when a second producer appears
|
||||
// (P4a's texture family)". P4a is that producer: a texture sets kMGPipeBindSampler,
|
||||
// kMGPipeBindShaderImage, kMGPipeBindRenderTarget and kMGPipeBindDepthStencil, which are
|
||||
// the four bits nothing set before. The mask is STICKY - ORed, never cleared - and is
|
||||
// emitted on both resource_create and every resource_respecify.
|
||||
enum MGPipeBindBit : Uint16 {
|
||||
kMGPipeBindNone = 0,
|
||||
kMGPipeBindVertex = 1u << 0,
|
||||
kMGPipeBindIndex = 1u << 1,
|
||||
kMGPipeBindConstant = 1u << 2,
|
||||
kMGPipeBindShaderBuffer = 1u << 3,
|
||||
kMGPipeBindIndirect = 1u << 4,
|
||||
kMGPipeBindSampler = 1u << 5,
|
||||
kMGPipeBindShaderImage = 1u << 6,
|
||||
kMGPipeBindRenderTarget = 1u << 7,
|
||||
kMGPipeBindDepthStencil = 1u << 8,
|
||||
kMGPipeBindStreamOutput = 1u << 9,
|
||||
kMGPipeBindAtomic = 1u << 10,
|
||||
// THE D-B7 SWITCH. With kCapNeedsHostIndexBytes set the server mirrors this
|
||||
// resource's bytes so it can rewrite restart indices and flatten multi-draws
|
||||
// (ARCHITECTURE.md 10.3). Getting it wrong is invisible in monolith and silently
|
||||
// disables both under split, which is why it is set from a table rather than from a
|
||||
// special case at the emission site.
|
||||
kMGPipeBindElementArray = 1u << 11,
|
||||
};
|
||||
|
||||
// Discriminated resource descriptor: buffers, every texture target and renderbuffers
|
||||
// share one create/respecify shape (section 4.5.1).
|
||||
struct MGPResourceDesc {
|
||||
MGPipeHandle Resource;
|
||||
Uint8 Target; // Buffer | Tex1D..TexCubeArray | Tex2DMS.. | Renderbuffer | TexBuffer
|
||||
// MGPipeResourceTarget: Buffer | Tex1D..TexCubeArray | Tex2DMS.. | Renderbuffer |
|
||||
// TexBuffer | TexRect. Never open-coded; the texture half comes from
|
||||
// MGPipeResourceTargetForTextureTarget above.
|
||||
Uint8 Target;
|
||||
Uint8 StorageKind; // == TextureStorageType (Mipmap | Buffer)
|
||||
// VERTEX|INDEX|CONSTANT|SHADER_BUFFER|INDIRECT|SAMPLER|SHADER_IMAGE|RENDER_TARGET|
|
||||
// DEPTH_STENCIL|STREAM_OUTPUT|ATOMIC|ELEMENT_ARRAY. The ELEMENT_ARRAY bit is the
|
||||
// D-B7 switch: with kCapNeedsHostIndexBytes set the server mirrors this resource.
|
||||
// MGPipeBindBit, above: VERTEX|INDEX|CONSTANT|SHADER_BUFFER|INDIRECT|SAMPLER|
|
||||
// SHADER_IMAGE|RENDER_TARGET|DEPTH_STENCIL|STREAM_OUTPUT|ATOMIC|ELEMENT_ARRAY. The
|
||||
// ELEMENT_ARRAY bit is the D-B7 switch: with kCapNeedsHostIndexBytes set the server
|
||||
// mirrors this resource.
|
||||
Uint16 BindMask;
|
||||
Uint32 InternalFormat; // already resolved to an uncompressed fallback by the client
|
||||
Uint32 Width, Height, Depth;
|
||||
@@ -304,18 +441,35 @@ namespace MobileGL::MG_Pipe {
|
||||
MGP_ASSERT_POD(MGPSamplerView, 36);
|
||||
|
||||
// Per texture OBJECT, independent of any view.
|
||||
//
|
||||
// P4a, D-E1: 32 -> 40 bytes. BuiltinSampler is the SamplerCso carrying the
|
||||
// SamplerParameters of the SamplerObject every ITextureObject owns
|
||||
// (TextureState/TextureObject.h's m_sampler, constructed by TextureObjectBase's
|
||||
// constructor). GL 4.6 core table 23.18 makes filter/wrap/compare/border SAMPLER state,
|
||||
// and Espryt pushes it with glTexParameter* onto the TEXTURE rather than with
|
||||
// glBindSampler onto the unit - behaviour P4a preserves exactly. Naming the CSO rather
|
||||
// than widening this payload with a filter/wrap/border block is what keeps ONE authority
|
||||
// for one value: SyncTextureParamsToBackend reads this record, SyncBuiltinSamplerToBackend
|
||||
// reads that CSO's SamplerParameters, and the two pushes stay two pushes.
|
||||
struct MGPTextureParams {
|
||||
MGPipeHandle Res;
|
||||
Uint16 BaseLevel, MaxLevel;
|
||||
Uint8 Swizzle[4];
|
||||
Uint8 DepthStencilMode;
|
||||
MGPipeHandle Res; // 0
|
||||
// Kind SamplerCso. kMGPipeNullHandle is ILLEGAL - every texture object owns a sampler
|
||||
// object, so a null here is Fatal{ProtocolCorruption} rather than "no sampler".
|
||||
MGPipeHandle BuiltinSampler; // 8
|
||||
Uint16 BaseLevel, MaxLevel; // 16
|
||||
Uint8 Swizzle[4]; // 20
|
||||
Uint8 DepthStencilMode; // 24
|
||||
// Mirrors m_forceTextureParamsResync: the widened-channel carrier needs a swizzle
|
||||
// override that the frontend params version does not move for.
|
||||
Uint8 ForceResync;
|
||||
Uint8 Pad0[2];
|
||||
Float MinLod, MaxLod, LodBias;
|
||||
Uint8 ForceResync; // 25
|
||||
// Mirrors m_forceSamplerResync, which had no wire spelling at all before P4a. What it
|
||||
// guards is not mis-filtering but an INCOMPLETE texture sampling (0,0,0,1) after a
|
||||
// driver re-mint, which is why it is a second bit and not folded into ForceResync.
|
||||
Uint8 SamplerResync; // 26
|
||||
Uint8 Pad0; // 27
|
||||
Float MinLod, MaxLod, LodBias; // 28
|
||||
};
|
||||
MGP_ASSERT_POD(MGPTextureParams, 32);
|
||||
MGP_ASSERT_POD(MGPTextureParams, 40);
|
||||
|
||||
// create_shader_state. The reflection blob is the whole LinkArtifacts + SpirvArtifacts
|
||||
// archive; P0.5 extracts those types out of ProgramObject.h so a server can
|
||||
@@ -352,6 +506,35 @@ namespace MobileGL::MG_Pipe {
|
||||
};
|
||||
MGP_ASSERT_POD(MGPSurface, 24);
|
||||
|
||||
// P4a, D-C2/D-C3: the record is emitted PER BOUND TARGET.
|
||||
//
|
||||
// GL has two independent framebuffer bindings and this record carries one Fbo and one
|
||||
// ReadSurface, so Target says which binding it describes: 0 = Draw, 1 = Read, 2 = Both
|
||||
// (one object bound to both targets). The draw-buffer array is applied only for a record
|
||||
// whose Target is not Read - Espryt's own comment records the Minecraft 26.x OIT bug
|
||||
// where a READ-only sync landed glDrawBuffers on the wrong framebuffer - and ReadSurface
|
||||
// is resolved from the READ framebuffer's own read buffer, which is what makes the
|
||||
// read-buffer-shared-FBO defect class unrepresentable rather than merely fixed.
|
||||
enum class MGPipeFramebufferTarget : Uint8 {
|
||||
Draw = 0,
|
||||
Read = 1,
|
||||
Both = 2,
|
||||
Count,
|
||||
};
|
||||
|
||||
// MGPFramebufferState::Color[] and DrawBuffers[] are ONE array width, and it is the wire's
|
||||
// bound rather than the driver's: GetDynamicParameters().MaxColorAttachments is the raw ES
|
||||
// cap and is not clamped to 8 on the GLES path, so a driver reporting more would silently
|
||||
// truncate this record. The framebuffer subsystem bit is REFUSED at its first lookup in
|
||||
// that case, with one ERROR naming the cap, and the legacy arm runs - the same shape the
|
||||
// backend's existing bit-8-requires-bit-7 refusal already ships
|
||||
// (ResolveFramebufferSubsystemArm, beside ResolveResourceSubsystemArm). Widening the
|
||||
// payload is a wire change nobody has evidence for, and truncating silently is the bug
|
||||
// class this phase is closing.
|
||||
inline constexpr Uint32 kMGPipeMaxColorAttachments = 8;
|
||||
static_assert(kMGPipeMaxColorAttachments == MobileGL::kMGMaxDrawBuffers,
|
||||
"MGPFramebufferState::Color[] and DrawBuffers[] are one array width");
|
||||
|
||||
struct MGPFramebufferState {
|
||||
MGPipeHandle Fbo; // kMGPipeDefaultFramebuffer for the default framebuffer
|
||||
MGPSurface Color[8];
|
||||
@@ -361,7 +544,16 @@ namespace MobileGL::MG_Pipe {
|
||||
MGPSurface ReadSurface;
|
||||
Int8 DrawBuffers[8]; // attachment index, -1 = NONE
|
||||
Uint16 Width, Height, Layers, Samples;
|
||||
Uint8 FixedSampleLocations, IsDefault, Complete, Pad0;
|
||||
// Complete is FramebufferObject::CheckCompleteness(), the frontend-only answer - NOT
|
||||
// glCheckFramebufferStatus's. CheckFramebufferStatus_State additionally consults
|
||||
// ActiveBackendRejectsDistinctDepthStencil() and HasNonRenderableColorAttachment,
|
||||
// which read the backend's probed format-capability cache; a client emitting that
|
||||
// answer would be reading the backend from the client side, which is the exact
|
||||
// coupling this boundary exists to remove. A later phase must not assume the stronger
|
||||
// answer, and glCheckFramebufferStatus keeps answering from the frontend as it does
|
||||
// today.
|
||||
Uint8 FixedSampleLocations, IsDefault, Complete;
|
||||
Uint8 Target; // MGPipeFramebufferTarget, above (P4a, D-C2; was Pad0)
|
||||
Uint32 Pad1;
|
||||
// Two jobs (section 4.5.6): the server's render-pass memo key, and the CLIENT's
|
||||
// emission suppressor - an unchanged hash means this record is not sent at all.
|
||||
@@ -378,6 +570,20 @@ namespace MobileGL::MG_Pipe {
|
||||
// static_assert, because this header may not include a frontend one.
|
||||
inline constexpr Uint32 kMGPipeMaxVertexAttribs = 32;
|
||||
|
||||
// P4a, D-G2. MobileGL's texture-unit space is ONE MERGED array of
|
||||
// TextureState::MAX_TEXTURE_IMAGE_UNITS = 192 - there is no stage dimension on
|
||||
// set_sampler_views / bind_sampler_states / set_shader_images, because the same unit may
|
||||
// be sampled from two stages and per-stage 32 is an advertised number rather than a
|
||||
// storage shape. These two bound the three var-tail sets' Start + Count, and a record
|
||||
// that names a window outside them is Fatal{ProtocolCorruption} - the var-tail window IS
|
||||
// the bound and entries outside it are not cleared.
|
||||
//
|
||||
// Pinned against the frontend constant in MG_Impl/Pipe/PipeFill.cpp, the one translation
|
||||
// unit that sees both, exactly as kMGPipeMaxVertexAttribs is: this header may not include
|
||||
// a frontend one.
|
||||
inline constexpr Uint32 kMGPipeMaxTextureUnits = 192;
|
||||
inline constexpr Uint32 kMGPipeMaxImageUnits = 192;
|
||||
|
||||
struct MGPVertexBuffer {
|
||||
MGPipeHandle Res;
|
||||
Uint64 Offset;
|
||||
@@ -675,10 +881,19 @@ namespace MobileGL::MG_Pipe {
|
||||
// is ((void)0) - the applier is one function call away - and the transport wires the
|
||||
// doorbell to this predicate when it lands.
|
||||
//
|
||||
// Immutable is exactly the right discriminator: it is set iff the store came from a
|
||||
// glBufferStorage* entry point, which is the definition of the allowed case.
|
||||
// P4a, D-A2: THE PREDICATE IS NARROWED TO NAME THE BUFFER TARGET, and that is a
|
||||
// requirement rather than a tidy-up. glTexStorage* also sets Immutable - it is a real
|
||||
// descriptor fact the backend reads, and the client must set it - but texture allocation
|
||||
// is already deferred to sync time in monolith (glTexImage*/glTexStorage* only
|
||||
// MarkStorageDirty; even glRenderbufferStorage* allocates lazily inside SyncToBackend), so
|
||||
// splitting changes no observable behaviour and this batch must NOT ack. glBufferStorage
|
||||
// stays the only entry point allowed a synchronous acknowledgement.
|
||||
//
|
||||
// PipeCatalogueTest.ResourceRespecifyAcksOnlyImmutableStorage drives glTexStorage2D and
|
||||
// glRenderbufferStorage idioms through it, and is the negative control for a future
|
||||
// widening.
|
||||
inline Bool MGPipeResourceRespecifyNeedsAck(const MGPResourceDesc& desc) {
|
||||
return desc.Immutable != 0;
|
||||
return desc.Immutable != 0 && desc.Target == kMGPipeResourceTargetBuffer;
|
||||
}
|
||||
|
||||
// The forward terminator for a server-initiated texture pull (section 7.1). May carry
|
||||
|
||||
@@ -626,6 +626,7 @@ namespace MobileGL::MG_Pipe {
|
||||
// the backend's own bring-up and teardown, not by a state reset.
|
||||
g_applier.RefusedResourceCalls = 0;
|
||||
g_applier.RefusedVertexInputCalls = 0;
|
||||
g_applier.RefusedObjectCalls = 0;
|
||||
g_applier.BoundVertexElements = kMGPipeNullHandle;
|
||||
g_applier.VertexBuffers = {};
|
||||
g_applier.VertexBufferStart = 0;
|
||||
@@ -662,6 +663,31 @@ namespace MobileGL::MG_Pipe {
|
||||
// the first compare after the switch is a mismatch, which is the safe direction.
|
||||
++g_applier.VertexBuffersSerial;
|
||||
++g_applier.IndexBufferSerial;
|
||||
|
||||
// ---- P4a's working state, cleared for the same reason and with the same serial rule
|
||||
// (D-J4). The OBJECT records - texture and renderbuffer resources, sampler CSOs,
|
||||
// sampler views, shader CSOs - are deliberately NOT here: a texture lives in a share
|
||||
// group exactly as a buffer does, and its record is where the extent, the parameters
|
||||
// and the pending-upload set the backend reads now live.
|
||||
g_applier.DrawFramebuffer = MGPFramebufferState{};
|
||||
g_applier.ReadFramebuffer = MGPFramebufferState{};
|
||||
g_applier.BoundSamplerViews = {};
|
||||
g_applier.SamplerViewStart = 0;
|
||||
g_applier.SamplerViewCount = 0;
|
||||
g_applier.BoundSamplerStates = {};
|
||||
g_applier.SamplerStateStart = 0;
|
||||
g_applier.SamplerStateCount = 0;
|
||||
g_applier.BoundShaderImages = {};
|
||||
g_applier.ShaderImageStart = 0;
|
||||
g_applier.ShaderImageCount = 0;
|
||||
g_applier.DrawProgram = kMGPipeNullHandle;
|
||||
g_applier.DispatchProgram = kMGPipeNullHandle;
|
||||
g_applier.BoundShaderCso = kMGPipeNullHandle;
|
||||
++g_applier.FramebufferSerial;
|
||||
++g_applier.SamplerViewsSerial;
|
||||
++g_applier.SamplerStatesSerial;
|
||||
++g_applier.ShaderImagesSerial;
|
||||
++g_applier.ProgramBindingSerial;
|
||||
}
|
||||
|
||||
void MGPipeApplierReleaseObjectRecords() {
|
||||
@@ -675,6 +701,23 @@ namespace MobileGL::MG_Pipe {
|
||||
g_applier.BoundVertexElements = kMGPipeNullHandle;
|
||||
++g_applier.VertexBuffersSerial;
|
||||
++g_applier.IndexBufferSerial;
|
||||
// P4a's five object tables go with them, and the working handles they could name go
|
||||
// too - a bound shader CSO whose record has just been dropped must not survive as a
|
||||
// handle the next call resolves against.
|
||||
g_applier.TextureResources.clear();
|
||||
g_applier.RenderbufferResources.clear();
|
||||
g_applier.SamplerCsos.clear();
|
||||
g_applier.SamplerViewCsos.clear();
|
||||
g_applier.ShaderCsos.clear();
|
||||
g_applier.CompositeShaderCsos.clear();
|
||||
g_applier.DrawProgram = kMGPipeNullHandle;
|
||||
g_applier.DispatchProgram = kMGPipeNullHandle;
|
||||
g_applier.BoundShaderCso = kMGPipeNullHandle;
|
||||
++g_applier.FramebufferSerial;
|
||||
++g_applier.SamplerViewsSerial;
|
||||
++g_applier.SamplerStatesSerial;
|
||||
++g_applier.ShaderImagesSerial;
|
||||
++g_applier.ProgramBindingSerial;
|
||||
}
|
||||
|
||||
void MGPipeApplyCreateRenderState(const MGPRenderStateDesc& desc, const void* chunkBytes) {
|
||||
@@ -1371,4 +1414,84 @@ namespace MobileGL::MG_Pipe {
|
||||
void MGPipeDeriveRenderStateFieldsForChunks(PipeInputs& inputs, Uint32 globalChunkBits) {
|
||||
MGPipeApplyAccess::DeriveRenderStateFields(inputs, globalChunkBits);
|
||||
}
|
||||
|
||||
// ================================================================================
|
||||
// P4a: the fifteen object and working-state entry points - STUBS (contract commit)
|
||||
// ================================================================================
|
||||
//
|
||||
// Every body below is deliberately empty at the contract commit, exactly as P3a's nine
|
||||
// resource entry points were at theirs. What this commit fixes is the SIGNATURE and the
|
||||
// storage it will write into: packages B and C compile and link against these, package D
|
||||
// and E read the records above, and the gates see the whole shape - so nothing after this
|
||||
// commit has to change a declaration, and no two packages ever edit one file.
|
||||
//
|
||||
// The bodies land in w1 (framebuffer + texture resources), w2 (samplers, views and the
|
||||
// three unit sets) and w3 (programs and the default uniform block), on this same branch
|
||||
// and before any client package runs against them: a stub applier under a real client is
|
||||
// how P3a's first Espryt round produced 202 red cases that had to be argued rather than
|
||||
// measured, and the order exists to make that structurally impossible.
|
||||
//
|
||||
// (void) casts rather than unnamed parameters, so the parameter NAMES stay in the
|
||||
// definition and the bodies that replace these start from the vocabulary the header uses.
|
||||
|
||||
void MGPipeApplySetFramebufferState(const MGPFramebufferState& state) { (void)state; }
|
||||
|
||||
void MGPipeApplyCreateSamplerState(const MGPSamplerDesc& desc, const SamplerParameters* parameters) {
|
||||
(void)desc;
|
||||
(void)parameters;
|
||||
}
|
||||
|
||||
void MGPipeApplyDeleteSamplerState(const MGPHandleOnly& handle) { (void)handle; }
|
||||
|
||||
void MGPipeApplyCreateSamplerView(const MGPSamplerView& view) { (void)view; }
|
||||
|
||||
void MGPipeApplyDeleteSamplerView(const MGPHandleOnly& handle) { (void)handle; }
|
||||
|
||||
void MGPipeApplySetTextureParams(const MGPTextureParams& params) { (void)params; }
|
||||
|
||||
void MGPipeApplySetSamplerViews(const MGPSamplerViews& hdr, const MGPBoundView* tail) {
|
||||
(void)hdr;
|
||||
(void)tail;
|
||||
}
|
||||
|
||||
void MGPipeApplyBindSamplerStates(const MGPSamplerStates& hdr, const MGPipeHandle* tail) {
|
||||
(void)hdr;
|
||||
(void)tail;
|
||||
}
|
||||
|
||||
void MGPipeApplySetShaderImages(const MGPShaderImages& hdr, const MGPImageView* tail) {
|
||||
(void)hdr;
|
||||
(void)tail;
|
||||
}
|
||||
|
||||
void MGPipeApplyCreateShaderState(const MGPProgramDesc& desc,
|
||||
const MG_State::GLState::LinkArtifacts* link,
|
||||
const MG_State::GLState::SpirvArtifacts* spirv) {
|
||||
(void)desc;
|
||||
(void)link;
|
||||
(void)spirv;
|
||||
}
|
||||
|
||||
void MGPipeApplyBindShaderState(const MGPHandleOnly& handle) { (void)handle; }
|
||||
|
||||
void MGPipeApplyDeleteShaderState(const MGPHandleOnly& handle) { (void)handle; }
|
||||
|
||||
void MGPipeApplySetDrawProgram(const MGPHandleOnly& handle) { (void)handle; }
|
||||
|
||||
void MGPipeApplySetDispatchProgram(const MGPHandleOnly& handle) { (void)handle; }
|
||||
|
||||
void MGPipeApplySetGlobalConstants(const MGPGlobalConstants& record, const void* bytes) {
|
||||
(void)record;
|
||||
(void)bytes;
|
||||
}
|
||||
|
||||
// THE MONOLITH BODY IS A NO-OP AND THAT IS THE WHOLE OF IT: the emulation this names still
|
||||
// runs, exactly as it does today, on the same code path. What the call site buys is that
|
||||
// the set of emulations a split server cannot serve is NAMED, GREPPABLE and PINNED, so P5
|
||||
// and P8 give it teeth by editing one function instead of rediscovering five call sites.
|
||||
//
|
||||
// It takes a literal and does nothing with it. Not a log line, not a counter: it sits on
|
||||
// paths a frame can reach many times, and ROADMAP.md forbids committing hot-path
|
||||
// instrumentation.
|
||||
void MGPipeUnmigratedEmulation(const char* name) { (void)name; }
|
||||
} // namespace MobileGL::MG_Pipe
|
||||
|
||||
@@ -28,6 +28,17 @@
|
||||
// (MG_Impl/Pipe) already have it, and MG_Pipe sits below MG_Backend.
|
||||
//
|
||||
// Compiled only under MOBILEGL_PIPE_PUSH (CMakeLists.txt), so the pull build gains no symbol.
|
||||
// P4a: create_shader_state carries the reflection ARCHIVE, and in monolith the archive does
|
||||
// not travel - the two structs ride beside the record through the entry point's companion
|
||||
// pointers, exactly as P3a's `const void* initialBytes` does (D-H3, the one Blob rule). So
|
||||
// this header needs their NAMES and never their definitions; the forward declaration is the
|
||||
// whole coupling and the closure gate is what keeps it one. The verify build is the only
|
||||
// place the codec runs, and it runs from PipeApply.cpp.
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
struct LinkArtifacts;
|
||||
struct SpirvArtifacts;
|
||||
} // namespace MobileGL::MG_State::GLState
|
||||
|
||||
namespace MobileGL::MG_Pipe {
|
||||
struct PipeInputs;
|
||||
|
||||
@@ -109,8 +120,33 @@ namespace MobileGL::MG_Pipe {
|
||||
// own dense high-water mark and no further, so the bound costs nothing until a record is
|
||||
// already corrupt. Package C bounds handle.Slot the same way before
|
||||
// BackendSlotTable::EntryAt, which resizes on a client-supplied index too.
|
||||
// P4a: THE BOUND IS PER KIND, not per table, and that is what keeps one number honest
|
||||
// while the number of tables grows. The slot spaces of kinds Buffer, Texture and
|
||||
// Renderbuffer are INDEPENDENT (MGPipeSlotAllocator allocates per kind), so three
|
||||
// different objects can hold slot 7; the applier therefore keeps one Vector per resource
|
||||
// KIND and indexes it by slot, rather than one Vector indexed by slot alone. Each is
|
||||
// bounded by kMGPipeMaxResourceSlots and each grows only to its own dense high-water mark.
|
||||
inline constexpr Uint32 kMGPipeMaxResourceSlots = 1u << 20;
|
||||
inline constexpr Uint32 kMGPipeMaxVertexElementsSlots = 1u << 16;
|
||||
// P4a's three, and the argument is written out for each because the records differ in
|
||||
// size. None is ever allocated by being named: the tables grow to the client's own dense
|
||||
// high-water mark and no further, so the bound costs nothing until a record is corrupt.
|
||||
//
|
||||
// A sampler CSO record is a 100-byte value plus a handle, and sampler CSOs are
|
||||
// CONTENT-ADDRESSED at capacity 256 on the client, so the live population is bounded by
|
||||
// that cache and not by the application. 1<<16 is far above anything a GL program can hold
|
||||
// and small enough that a corrupt slot is refused rather than allocated.
|
||||
inline constexpr Uint32 kMGPipeMaxSamplerCsoSlots = 1u << 16;
|
||||
// A sampler VIEW is identity-addressed one per ITextureObject (P4a D-F2), so its
|
||||
// population tracks the texture population exactly and it takes the texture bound.
|
||||
inline constexpr Uint32 kMGPipeMaxSamplerViewSlots = 1u << 20;
|
||||
// The shader-CSO bound is the SLOT LIMIT ITSELF, because the composite band lives inside
|
||||
// that space (MGPipeHandles.h): a bound below it would refuse the very slots
|
||||
// AllocateComposite is allowed to hand out.
|
||||
inline constexpr Uint32 kMGPipeMaxShaderCsoSlots = kMGPipeShaderCsoSlotLimit;
|
||||
static_assert(kMGPipeMaxShaderCsoSlots > kMGPipeShaderCsoCompositeSlotBase,
|
||||
"the ShaderCso bound must contain the composite band, or a composite handle "
|
||||
"is refused as out of range on arrival");
|
||||
|
||||
// One record per live resource, indexed by MGPipeHandle::Slot, kind Buffer; slot 0 is the
|
||||
// reserved null handle and is never live.
|
||||
@@ -129,6 +165,102 @@ namespace MobileGL::MG_Pipe {
|
||||
// persistent-mapped host writes can set it with zero new record kinds; a verify build
|
||||
// pins that it is false, so that phase cannot land a silent semantic change under it.
|
||||
Bool HasLiveHostWrites = false;
|
||||
|
||||
// ---- P4a. Only a record of kind Texture ever carries these; a buffer's stay at
|
||||
// their defaults, which is what keeps ONE record type for the discriminated
|
||||
// descriptor rather than a second one that would have to be kept in step with it.
|
||||
|
||||
// set_texture_params, per texture OBJECT and independent of any binding - which is
|
||||
// the whole point of addressing it by resource: a texture that is only an FBO
|
||||
// attachment, only an image-unit binding or only a glCopyImageSubData endpoint has no
|
||||
// sampler view to hang its parameters on, and today the READ-attachment case reaches
|
||||
// no parameter push at all. ParamsSerial replaces the twin's
|
||||
// m_syncedTextureParamsVersion + m_forceTextureParamsResync pair.
|
||||
MGPTextureParams Params{};
|
||||
Uint64 ParamsSerial = 0;
|
||||
// The SamplerViewCso minted for this texture (P4a D-F2: one per ITextureObject,
|
||||
// re-issued on the same handle whenever the restrictions move).
|
||||
MGPipeHandle ViewCso = kMGPipeNullHandle;
|
||||
// THE PENDING-UPLOAD SET, and it is server-side state on purpose (D-D5). The client
|
||||
// clears its own dirty flags at EMISSION, for the levels whose record the applier
|
||||
// accepted; Espryt's upload loop has bail arms - an incomplete texture returns early,
|
||||
// a multisample target refreshes and skips - that today leave the frontend flag set,
|
||||
// so a naive move of the clear to the client would lose those texels. The applier
|
||||
// accumulates the emitted shape here instead, it survives any number of bails, and
|
||||
// Espryt consumes and clears an entry only where it actually uploads.
|
||||
//
|
||||
// The verify lane's RETAIN MODE is what gates the shape: a consume-and-clear set
|
||||
// cannot be recomputed after emission, so the tracker retains the pre-clear set and
|
||||
// the comparator compares the emitted (UnionBox, RegionCount, Regions[]) against it
|
||||
// field by field.
|
||||
struct PendingUpload {
|
||||
Uint16 UploadTarget = 0;
|
||||
Uint16 Level = 0;
|
||||
MGPBox UnionBox{};
|
||||
Vector<MGPSubRegion> Regions;
|
||||
};
|
||||
Vector<PendingUpload> PendingUploads;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// P4a: the three new object-record kinds (D-J1)
|
||||
// ---------------------------------------------------------------------------------
|
||||
//
|
||||
// All three follow MGPipeResourceRecord's shape exactly - Gen, Live, a payload and a
|
||||
// server-owned monotone Serial - because the body-level idioms are the same ones:
|
||||
// a create starts the record OVER rather than editing it (a recycled slot's record must
|
||||
// not contribute one field, and Serial stays 0 because a create is not a mutation, so a
|
||||
// fresh backend twin starting at 0 agrees without either side publishing anything); the
|
||||
// serial moves BEFORE the backend is told; a destroy drops the record whole and keeps the
|
||||
// generation, and the CLIENT frees the slot afterwards.
|
||||
|
||||
// create_sampler_state / delete_sampler_state. The parameters cross byte for byte
|
||||
// INCLUDING borderColorForm - all three border representations are always numerically
|
||||
// populated, so the value alone cannot say which driver entry point to use - and
|
||||
// MOBILEGL_PIPE_VERIFY compares them FIELD BY FIELD (PipeFields.def's
|
||||
// MGP_FIELDS_SamplerParameters), because the struct has three bytes of trailing padding
|
||||
// and a byte comparison of it is a coin flip rather than a gate.
|
||||
struct MGPipeSamplerCsoRecord {
|
||||
Uint32 Gen = 0;
|
||||
Bool Live = false;
|
||||
SamplerParameters Params{};
|
||||
Uint64 Serial = 0;
|
||||
};
|
||||
|
||||
// create_sampler_view / delete_sampler_view: ONLY the view restrictions. Everything a
|
||||
// glTexParameter writes lives on set_texture_params instead. Re-issuing on the same
|
||||
// handle is how a restriction change travels (Gen moves only on slot reuse); it bumps
|
||||
// Serial and does not rebind anything.
|
||||
struct MGPipeSamplerViewRecord {
|
||||
Uint32 Gen = 0;
|
||||
Bool Live = false;
|
||||
MGPSamplerView View{};
|
||||
Uint64 Serial = 0;
|
||||
};
|
||||
|
||||
// create/bind/delete_shader_state, plus set_global_constants' per-program half.
|
||||
//
|
||||
// THE ARTEFACTS ARE NOT HELD HERE IN MONOLITH: MGPProgramDesc's seven MGPBlobRefs are all
|
||||
// declared with Size 0 ("this record does not declare its blob") and the LinkArtifacts /
|
||||
// SpirvArtifacts ride beside the record through the entry point's companion pointers, so
|
||||
// the applier stores the DESCRIPTOR and the identity and the server reads the frontend's
|
||||
// own archive. That is what keeps the codec off the monolith hot path entirely; the verify
|
||||
// build is where it is exercised, by serialising, deserialising and field-comparing before
|
||||
// storing.
|
||||
//
|
||||
// GlobalConstants is the one allocation P4a adds per program, it is bounded by
|
||||
// Desc.GlobalUboSize, and it is NOT on the hot path: set_global_constants is
|
||||
// (ShaderCso, Version) keyed and fires at most once per program per frame.
|
||||
struct MGPipeShaderCsoRecord {
|
||||
Uint32 Gen = 0;
|
||||
Bool Live = false;
|
||||
MGPProgramDesc Desc{};
|
||||
// GetUBOContentVersion() as last received. ~0u is the backends' "never uploaded"
|
||||
// sentinel and the client must never emit it, so it is also what this starts at.
|
||||
Uint32 GlobalConstantsVersion = ~Uint32{0};
|
||||
Vector<Uint8> GlobalConstants;
|
||||
Uint64 GlobalConstantsSerial = 0;
|
||||
Uint64 Serial = 0;
|
||||
};
|
||||
|
||||
// The vertex-elements CSO as the applier holds it: the unpacked blob, both views, plus
|
||||
@@ -217,6 +349,30 @@ namespace MobileGL::MG_Pipe {
|
||||
Vector<MGPipeResourceRecord> Resources;
|
||||
Vector<MGPipeVertexElementsRecord> VertexElementsCsos;
|
||||
|
||||
// ---- P4a's object records. FIVE MORE TABLES, and the two resource ones are separate
|
||||
// Vectors rather than more rows of `Resources` above because the slot space is PER
|
||||
// KIND: a Buffer, a Texture and a Renderbuffer can all hold slot 7 at once, so a
|
||||
// single slot-indexed table would alias three different objects onto one record. The
|
||||
// record TYPE is shared - one discriminated descriptor for buffers, every texture
|
||||
// target and renderbuffers - and the bound is shared; only the table is per kind.
|
||||
//
|
||||
// Like the two above they are share-group state: MGPipeApplierReset does not touch
|
||||
// them, and only the object's own death signal and MGPipeApplierReleaseObjectRecords
|
||||
// clear them.
|
||||
Vector<MGPipeResourceRecord> TextureResources;
|
||||
Vector<MGPipeResourceRecord> RenderbufferResources;
|
||||
Vector<MGPipeSamplerCsoRecord> SamplerCsos;
|
||||
Vector<MGPipeSamplerViewRecord> SamplerViewCsos;
|
||||
Vector<MGPipeShaderCsoRecord> ShaderCsos;
|
||||
// The ShaderCso COMPOSITE band's records, indexed by (slot - the band's base), for the
|
||||
// same reason MGPipeSlotAllocator keeps the band in a table of its own: the band
|
||||
// starts at 983040, so one program-pipeline composite in the slot-indexed vector above
|
||||
// would grow it to ~983k records of ~240 bytes each. THE SERVER STILL NEVER LEARNS IT
|
||||
// IS A COMPOSITE - the split is an indexing detail on this side of the wire, the
|
||||
// handle is an ordinary ShaderCso handle, and create/bind/delete_shader_state name it
|
||||
// exactly as they name any other program.
|
||||
Vector<MGPipeShaderCsoRecord> CompositeShaderCsos;
|
||||
|
||||
// Every call this applier REFUSED because it named a record this applier does not
|
||||
// have: an unknown slot, a slot that is not live, or a generation that has moved on
|
||||
// under it. The refusal is a defined no-op - nothing stored, nothing dispatched, no
|
||||
@@ -227,6 +383,19 @@ namespace MobileGL::MG_Pipe {
|
||||
// build. Per context, like the four render-state wire counters above.
|
||||
Uint64 RefusedResourceCalls = 0;
|
||||
Uint64 RefusedVertexInputCalls = 0;
|
||||
// P4a's, in the same shape and for the same reason: every framebuffer, sampler,
|
||||
// sampler-view, program and texture-params call this applier refused because it named
|
||||
// a record this applier does not have. One counter rather than five, because the five
|
||||
// families share one legal refusal sequence (teardown ->
|
||||
// MGPipeApplierReleaseObjectRecords -> ~Object -> death notices naming records already
|
||||
// dropped) and an operator reading a log wants to know that ANY object call was
|
||||
// dropped; the log line names the call and the handle.
|
||||
//
|
||||
// THE OTHER CLASS IS NOT COUNTED HERE AND MUST NOT BE: a var-tail window outside its
|
||||
// bound, or a set_texture_params whose BuiltinSampler is the null handle, would make
|
||||
// the backend act outside its own storage or sample an object that does not exist -
|
||||
// that is Fatal{ProtocolCorruption}, not a dropped call.
|
||||
Uint64 RefusedObjectCalls = 0;
|
||||
|
||||
// ---- working state: what the next draw fetches with. All of it is per context and
|
||||
// all of it is cleared by MGPipeApplierReset, EXCEPT the two serials, which only ever
|
||||
@@ -271,6 +440,50 @@ namespace MobileGL::MG_Pipe {
|
||||
// operator greps is PipeStats' map-persistent-roundtrips (mpr); this member is the
|
||||
// applier-side observable a unit case reads without a stats window.
|
||||
Uint64 MapPersistentRoundtrips = 0;
|
||||
|
||||
// ---- P4a's WORKING state. All of it is per context and all of it is cleared by
|
||||
// MGPipeApplierReset, EXCEPT the serials, which only ever advance - a counter that
|
||||
// restarts walks back through values already stamped into a twin that outlived the
|
||||
// switch, and P4a deletes the identity patches that used to close that hole.
|
||||
|
||||
// set_framebuffer_state, per bound target (D-C2). Target = Both writes both. The
|
||||
// record is fully resolved: ReadSurface comes from the READ framebuffer's own read
|
||||
// buffer, so the shared-FBO case cannot lose it, and DrawBuffers[] is applied only for
|
||||
// a record whose Target is not Read.
|
||||
MGPFramebufferState DrawFramebuffer{};
|
||||
MGPFramebufferState ReadFramebuffer{};
|
||||
Uint64 FramebufferSerial = 0;
|
||||
|
||||
// The three kVarTail unit sets, as received. NO STAGE DIMENSION: MobileGL's
|
||||
// texture-unit space is one merged array of 192, the same unit may be sampled from two
|
||||
// stages, and stage is derived server-side from the reflection archive only where the
|
||||
// target API needs it.
|
||||
//
|
||||
// THE VAR-TAIL WINDOW IS THE BOUND AND ENTRIES OUTSIDE IT ARE NOT CLEARED - the
|
||||
// record is "the last set as received", exactly as set_vertex_buffers is, and
|
||||
// Start + Count above the bound is Fatal{ProtocolCorruption}.
|
||||
Array<MGPBoundView, kMGPipeMaxTextureUnits> BoundSamplerViews{};
|
||||
Uint32 SamplerViewStart = 0;
|
||||
Uint32 SamplerViewCount = 0;
|
||||
Uint64 SamplerViewsSerial = 0;
|
||||
|
||||
Array<MGPipeHandle, kMGPipeMaxTextureUnits> BoundSamplerStates{};
|
||||
Uint32 SamplerStateStart = 0;
|
||||
Uint32 SamplerStateCount = 0;
|
||||
Uint64 SamplerStatesSerial = 0;
|
||||
|
||||
Array<MGPImageView, kMGPipeMaxImageUnits> BoundShaderImages{};
|
||||
Uint32 ShaderImageStart = 0;
|
||||
Uint32 ShaderImageCount = 0;
|
||||
Uint64 ShaderImagesSerial = 0;
|
||||
|
||||
// set_draw_program / set_dispatch_program are two calls because the frontend has two
|
||||
// joins and two PipeInputs slots; bind_shader_state is the third, and a null handle is
|
||||
// legal in all three and means "nothing bound".
|
||||
MGPipeHandle DrawProgram = kMGPipeNullHandle;
|
||||
MGPipeHandle DispatchProgram = kMGPipeNullHandle;
|
||||
MGPipeHandle BoundShaderCso = kMGPipeNullHandle;
|
||||
Uint64 ProgramBindingSerial = 0;
|
||||
};
|
||||
|
||||
// The monolith's single applier. Under split there is one per served context.
|
||||
@@ -288,6 +501,20 @@ namespace MobileGL::MG_Pipe {
|
||||
// vertex-input serials rather than zeroing them. It does NOT drop the resource or
|
||||
// vertex-elements records: those describe share-group objects that the switch does not
|
||||
// destroy, and dropping them is a dropped write on the far side of it.
|
||||
//
|
||||
// P4a EXTENDS BOTH HALVES AND THE RULE IS UNCHANGED (D-J4). Cleared: the two framebuffer
|
||||
// records, the three unit sets, DrawProgram / DispatchProgram / BoundShaderCso - all of it
|
||||
// per-context working state - with their serials ADVANCED and never zeroed. Not cleared:
|
||||
// texture and renderbuffer resources, sampler CSOs, sampler views, shader CSOs, and the
|
||||
// texture params and pending uploads that ride on a resource record, because a texture
|
||||
// lives in a share group exactly as a buffer does.
|
||||
//
|
||||
// AND THEREFORE NO P4a TRACKER NEEDS A RE-PUBLICATION PATH ON FreshlyPrimed, AND NONE MAY
|
||||
// HAVE ONE: re-emitting create_sampler_state for a record the applier still holds would
|
||||
// move its Serial for nothing. What DOES reset on a fresh context is each emitter's
|
||||
// BOUND-HANDLE latch - the framebuffer and unit-set hashes through
|
||||
// MGPipeSetHashSuppressor::InvalidateAll, and the program emitter's BoundShaderCso mirror -
|
||||
// because those mirror working state this function just cleared.
|
||||
void MGPipeApplierReset();
|
||||
|
||||
// THE OTHER SCOPE: the served context is going away and its applier with it, so the object
|
||||
@@ -403,6 +630,105 @@ namespace MobileGL::MG_Pipe {
|
||||
// configuration. Bumps IndexBufferSerial.
|
||||
void MGPipeApplySetIndexBuffer(const MGPIndexBuffer& record);
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// P4a: the fifteen object and working-state entry points (D-A1, D-B1, D-J1)
|
||||
// ---------------------------------------------------------------------------------
|
||||
//
|
||||
// NOT ONE OF THEM DISPATCHES TO A BACKEND FUNCTION POINTER, and that is the single most
|
||||
// important structural decision in P4a rather than an omission. Nothing in these families
|
||||
// reaches the backend at GL-call time today - texture storage only marks a level dirty and
|
||||
// Espryt allocates lazily at sync, texture params run from SyncTextureObjectToBackend at
|
||||
// draw sync, renderbuffer storage is allocated inside SyncToBackend on a four-field cache,
|
||||
// a sampler twin is created lazily from the program pass, and the framebuffer, unit sets
|
||||
// and program are all resolved at PrepareForDraw. So every call below is either an OBJECT
|
||||
// RECORD the applier stores or WORKING STATE the applier stores, and Espryt reads the
|
||||
// applier at the sync points it already has, keyed on a server-owned Serial instead of a
|
||||
// frontend version. MGPipeResourceOps is therefore UNCHANGED - nine members, same
|
||||
// signatures - and P4a adds no backend op table and no op-table member at all.
|
||||
//
|
||||
// The consequence for the four resource entry points above: they BRANCH on
|
||||
// record.Desc.Target. A buffer target dispatches into MGPipeResourceOps exactly as P3a
|
||||
// wrote it; every other target stores and returns. The branch is one comparison against
|
||||
// kMGPipeResourceTargetBuffer and it is where a mis-typed descriptor becomes visible.
|
||||
//
|
||||
// AT THE CONTRACT COMMIT EVERY BODY BELOW IS A STUB, exactly as P3a's nine were: the
|
||||
// signatures are what the client, the backend and the gates compile against and the
|
||||
// records above are what they write into; the bodies land in the three commits that
|
||||
// follow this one on the same branch.
|
||||
|
||||
// set_framebuffer_state. Fully resolved - nothing in the record requires a lookup on the
|
||||
// far side. `state.Target` says which binding it describes (Draw / Read / Both) and the
|
||||
// applier keeps the two records apart; Both writes both. ContentHash covers every field
|
||||
// including Fbo and DrawBuffers[8], which is what makes a suppressed record provably mean
|
||||
// "the draw-buffer array did not move" and therefore "the fragColor broadcast count did
|
||||
// not move".
|
||||
void MGPipeApplySetFramebufferState(const MGPFramebufferState& state);
|
||||
|
||||
// create_sampler_state. `parameters` is the client's canonical SamplerParameters copy,
|
||||
// beside the record for the one Blob rule's reason; the applier stores it by value.
|
||||
void MGPipeApplyCreateSamplerState(const MGPSamplerDesc& desc, const SamplerParameters* parameters);
|
||||
// delete_sampler_state: emitted by the CSO cache's LRU eviction and by the frontend
|
||||
// sampler object's death helper. Clears Live and drops the record; the client frees the
|
||||
// slot afterwards.
|
||||
void MGPipeApplyDeleteSamplerState(const MGPHandleOnly& handle);
|
||||
|
||||
// create_sampler_view. Re-issued on the SAME handle whenever the view restrictions move,
|
||||
// which is legal because Gen increments only on slot reuse and never on a respecify.
|
||||
void MGPipeApplyCreateSamplerView(const MGPSamplerView& view);
|
||||
void MGPipeApplyDeleteSamplerView(const MGPHandleOnly& handle);
|
||||
|
||||
// set_texture_params: addressed by RESOURCE and independent of any binding, which is what
|
||||
// lets a texture that is only an attachment, only an image-unit binding or only a
|
||||
// glCopyImageSubData endpoint carry its parameters at all. params.BuiltinSampler may never
|
||||
// be the null handle - every ITextureObject owns a sampler object - so a null is
|
||||
// Fatal{ProtocolCorruption} rather than "no sampler".
|
||||
void MGPipeApplySetTextureParams(const MGPTextureParams& params);
|
||||
|
||||
// set_sampler_views / bind_sampler_states / set_shader_images: `tail` is hdr.Count entries
|
||||
// starting at hdr.Start, and hdr.Start + hdr.Count above the unit bound is
|
||||
// Fatal{ProtocolCorruption}. Entries outside the declared window are NOT cleared.
|
||||
void MGPipeApplySetSamplerViews(const MGPSamplerViews& hdr, const MGPBoundView* tail);
|
||||
void MGPipeApplyBindSamplerStates(const MGPSamplerStates& hdr, const MGPipeHandle* tail);
|
||||
void MGPipeApplySetShaderImages(const MGPShaderImages& hdr, const MGPImageView* tail);
|
||||
|
||||
// create_shader_state. THE ARTEFACTS TRAVEL BESIDE THE RECORD, by pointer: all seven of
|
||||
// desc.Spirv[] and desc.Reflection are declared with Size 0 ("this record does not declare
|
||||
// its blob"), which is what a monolith emission is, and the codec is NOT called - zero
|
||||
// serialisation cost on the monolith path. A verify build serialises, deserialises and
|
||||
// field-compares before storing, and a mismatch is Fatal{PipeVerifyDiffer, "program-archive"}.
|
||||
// Splitting this record for a transport whose ring caps one record at half its capacity is
|
||||
// P5's problem, not this entry point's.
|
||||
void MGPipeApplyCreateShaderState(const MGPProgramDesc& desc,
|
||||
const MG_State::GLState::LinkArtifacts* link,
|
||||
const MG_State::GLState::SpirvArtifacts* spirv);
|
||||
void MGPipeApplyBindShaderState(const MGPHandleOnly& handle);
|
||||
void MGPipeApplyDeleteShaderState(const MGPHandleOnly& handle);
|
||||
void MGPipeApplySetDrawProgram(const MGPHandleOnly& handle);
|
||||
void MGPipeApplySetDispatchProgram(const MGPHandleOnly& handle);
|
||||
|
||||
// set_global_constants: the DEFAULT UNIFORM BLOCK only. Keyed (ShaderCso, Version) and
|
||||
// emitted at most once per program per frame; `bytes` is MapUBO()'s image, GetUBOSize()
|
||||
// long, handed over as a companion pointer with Blob.Size 0. record.Version is
|
||||
// GetUBOContentVersion() and may never be ~0u, which is the backends' "never uploaded"
|
||||
// sentinel.
|
||||
void MGPipeApplySetGlobalConstants(const MGPGlobalConstants& record, const void* bytes);
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// P4a: the named, greppable unmigrated emulations (D-M)
|
||||
// ---------------------------------------------------------------------------------
|
||||
//
|
||||
// ROADMAP.md's P4a row ends "emulation 在 split 下显式 Fatal 直到 P8". In monolith the
|
||||
// code paths keep running exactly as today - the Fatal is a SPLIT-only arm - so this costs
|
||||
// P4a a named call site per unmigrated emulation and nothing else. P5/P8 give it teeth: a
|
||||
// split server that reaches one of these has no client address space to read and must
|
||||
// abort loudly rather than degrade silently.
|
||||
//
|
||||
// Monolith body: (void)name;. The list of names is pinned by
|
||||
// PipeCatalogueTest.EveryUnmigratedEmulationIsNamedOnce and the call count is grepped by
|
||||
// the purity gate, so a site that quietly disappears is a red gate rather than a surprise
|
||||
// at P8.
|
||||
void MGPipeUnmigratedEmulation(const char* name);
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// The derivation step (ARCHITECTURE.md 5.3, P2 brief D5)
|
||||
// ---------------------------------------------------------------------------------
|
||||
|
||||
@@ -78,9 +78,12 @@
|
||||
F(Cso) F(Texture) F(InternalFormat) F(Target) F(MinLevel) F(NumLevels) F(MinLayer) F(NumLayers) \
|
||||
F(Samples) F(FixedSampleLocations)
|
||||
|
||||
// P4a, D-E1: BuiltinSampler and SamplerResync. Pad0 stays unlisted - gen_pipe.py's
|
||||
// PADDING_MEMBER_RE (^Pad\d*$) excludes it, and a member that stops being called Pad<n> MUST
|
||||
// gain a row here or pipe-gates goes red.
|
||||
#define MGP_FIELDS_MGPTextureParams(F) \
|
||||
F(Res) F(BaseLevel) F(MaxLevel) F(Swizzle) F(DepthStencilMode) F(ForceResync) F(MinLod) F(MaxLod) \
|
||||
F(LodBias)
|
||||
F(Res) F(BuiltinSampler) F(BaseLevel) F(MaxLevel) F(Swizzle) F(DepthStencilMode) F(ForceResync) \
|
||||
F(SamplerResync) F(MinLod) F(MaxLod) F(LodBias)
|
||||
|
||||
#define MGP_FIELDS_MGPProgramDesc(F) \
|
||||
F(Cso) F(StageMask) F(GlobalUboSize) F(ReservedNumSamplesOffset) F(SpirvStatus) F(NativeFloat64) \
|
||||
@@ -89,9 +92,12 @@
|
||||
#define MGP_FIELDS_MGPSurface(F) \
|
||||
F(Res) F(InternalFormat) F(Kind) F(Layered) F(Level) F(Layer) F(UploadTarget)
|
||||
|
||||
// P4a, D-C2: Pad0 became Uint8 Target, and gen_pipe.py's PADDING_MEMBER_RE only excludes a
|
||||
// member still NAMED Pad<n> - so the rename without this row is a pipe-gates failure, which
|
||||
// is exactly the trip wire that makes the byte impossible to add silently.
|
||||
#define MGP_FIELDS_MGPFramebufferState(F) \
|
||||
F(Fbo) F(Color) F(Depth) F(Stencil) F(ReadSurface) F(DrawBuffers) F(Width) F(Height) F(Layers) \
|
||||
F(Samples) F(FixedSampleLocations) F(IsDefault) F(Complete) F(ContentHash)
|
||||
F(Samples) F(FixedSampleLocations) F(IsDefault) F(Complete) F(Target) F(ContentHash)
|
||||
|
||||
#define MGP_FIELDS_MGPVertexBuffer(F) \
|
||||
F(Res) F(Offset) F(Stride) F(Divisor) F(BindingIndex)
|
||||
@@ -254,6 +260,18 @@
|
||||
F(SwapBytes) F(LSBFirst) F(RowLength) F(ImageHeight) F(SkipPixels) F(SkipRows) F(SkipImages) \
|
||||
F(Alignment)
|
||||
|
||||
// P4a, D-F1: THE PADDING TRAP. SamplerParameters is sizeof == 100 with THREE BYTES OF
|
||||
// TRAILING PADDING (96 bytes of members plus the 1-byte borderColorForm) and had no field
|
||||
// table and no MGP_VERIFY_PAYLOAD_LIST row at all, so MGPSamplerDesc's blob was compared as
|
||||
// BYTES and MOBILEGL_PIPE_VERIFY could false-differ on uninitialised padding - a coin flip
|
||||
// rather than a gate. With this list the comparator sees the sixteen members and the three
|
||||
// bytes can never enter the answer. The client-side CSO cache hashes and memcmp-confirms over
|
||||
// a ZERO-INITIALISED canonical copy for the same reason, which is the other half of D-F1.
|
||||
#define MGP_FIELDS_SamplerParameters(F) \
|
||||
F(wrapS) F(wrapT) F(wrapR) F(minFilter) F(magFilter) F(mipmapMode) F(minLod) F(maxLod) \
|
||||
F(lodBias) F(maxAnisotropy) F(compareFunc) F(compareMode) F(borderColor) F(borderColorI) \
|
||||
F(borderColorUI) F(borderColorForm)
|
||||
|
||||
#define MGP_FIELDS_PerBufferBlendState(F) \
|
||||
F(Enabled) F(SrcFactorRGB) F(DstFactorRGB) F(SrcFactorAlpha) F(DstFactorAlpha) F(ColorEquation) \
|
||||
F(AlphaEquation)
|
||||
@@ -326,7 +344,8 @@
|
||||
P(MGPDrawRange) P(MGPDrawIndirect) P(MGPGridInfo) P(MGPMemoryBarrier) P(MGPStreamOutputBegin) \
|
||||
P(MGPXfbAccounting) P(MGPStreamOutputControl) P(MGPFlush) P(MGPPresent) P(MGPSwapInterval) \
|
||||
P(MGPSurfaceInfo) \
|
||||
P(RenderStateParameters) P(PixelStoreParameters) P(PerBufferBlendState) P(StencilFaceState) \
|
||||
P(RenderStateParameters) P(PixelStoreParameters) P(SamplerParameters) P(PerBufferBlendState) \
|
||||
P(StencilFaceState) \
|
||||
P(DynamicBackendParameters) P(MGHostSpan) \
|
||||
P(MGPVertexAttribWire) P(MGPVertexBindingPointWire)
|
||||
|
||||
|
||||
@@ -140,6 +140,66 @@ namespace MobileGL::MG_Pipe {
|
||||
// is asked rather than assumed.
|
||||
Bool MGPipeEmitVertexElementsDestroyAndFree(Uint64 lifetimeId);
|
||||
|
||||
// ---- P4a: ONE CLIENT-SIDE DEATH HELPER PER KIND P4a MINTS (brief D-I1) ----
|
||||
//
|
||||
// BACKEND-NEUTRAL FROM DAY ONE, and this is the P3a final-review lesson taken forward
|
||||
// rather than repeated. Before it, the only thing that ever returned a VertexElementsCso
|
||||
// slot was DirectGLES' StateObjectDeathOps table; under a backend that installs none -
|
||||
// DirectVulkan/Magma, which keeps its own age-reclaimed identity table on purpose - every
|
||||
// VAO ever created held its slot and its applier record for the life of the process, and
|
||||
// past 65536 slots every create became a permanent Fatal{ProtocolCorruption}. P4a mints
|
||||
// SIX kinds, so the rule is stated once and obeyed six times: whatever mints a handle owns
|
||||
// the death of that handle, the client mints all six, and a backend death notice is a
|
||||
// redundant SECOND path that must be idempotent - which it is, because it resolves through
|
||||
// the same lifetimeId -> slot map these free, and MGPipeSlotAllocator::Free refuses a slot
|
||||
// that is not live at that generation.
|
||||
//
|
||||
// THE ORDER INSIDE EACH IS FIXED AND IS NOT A PACKAGE'S CHOICE:
|
||||
// 1. emit the wire delete FIRST - it drops the applier's record while the record still
|
||||
// exists, so a recycled slot cannot inherit a field;
|
||||
// 2. raise NotifyStateObjectDestroyed SECOND - it resolves the handle through the
|
||||
// allocator, and a backend told after the Free could no longer find its twin, which
|
||||
// moves the leak from the client to the driver object;
|
||||
// 3. free the slot LAST, and a double free on a stale generation is a proven no-op
|
||||
// because Free bumps no generation (the bump rides the next handout).
|
||||
//
|
||||
// ALL SIX TAKE THE LIFETIME ID rather than the object, for MGPipeEmitVertexElementsDestroy
|
||||
// AndFree's reason: they run from a destructor, where the last SharedPtr has already
|
||||
// dropped, and the lifetime id is what the slot allocator resolves the handle from. It is
|
||||
// also what keeps this header a declaration-only coupling - no frontend class needs
|
||||
// forward-declaring for any of them.
|
||||
//
|
||||
// Each returns whether its wire delete actually went out, which is the LATCH taken at the
|
||||
// object's create and not a second reading of the subsystem predicate: an object born
|
||||
// while a subsystem bit was clear and destroyed after it was set would otherwise free its
|
||||
// slot with the applier's record still Live, on a slot about to be handed out again. The
|
||||
// legacy path runs only when the answer is false.
|
||||
|
||||
// ResourceDestroy, and then the SamplerViewCso minted off this same lifetime id (P4a
|
||||
// D-F2: one sampler view per ITextureObject). Called from TextureObjectBase's VIRTUAL
|
||||
// destructor, so 2D / 3D / cube / buffer / view all announce exactly once.
|
||||
Bool MGPipeEmitTextureDestroyAndFree(Uint64 lifetimeId);
|
||||
// ResourceDestroy.
|
||||
Bool MGPipeEmitRenderbufferDestroyAndFree(Uint64 lifetimeId);
|
||||
// NO WIRE CALL AT ALL (D-I2). PipeCalls.def has no framebuffer delete, because a
|
||||
// framebuffer is not a resource and is not a CSO - it is STATE, and set_framebuffer_state
|
||||
// is the only call that names one - and the catalogue is closed, so P4a does not invent a
|
||||
// row. The handle is minted and freed entirely client-side and this helper does steps 2
|
||||
// and 3 only. A recycled framebuffer handle is distinguished by Gen, which is inside the
|
||||
// record's ContentHash, so it can never be suppressed against its predecessor's record.
|
||||
Bool MGPipeEmitFramebufferDestroyAndFree(Uint64 lifetimeId);
|
||||
// DeleteSamplerState. Also the path the content-addressed CSO cache's LRU eviction takes,
|
||||
// which is why it is addressed by lifetime id and not by "the object that owns it".
|
||||
Bool MGPipeEmitSamplerCsoDestroyAndFree(Uint64 lifetimeId);
|
||||
// DeleteSamplerView. Called by the texture helper above; a sampler view has no frontend
|
||||
// object of its own, so this is the only path there is.
|
||||
Bool MGPipeEmitSamplerViewCsoDestroyAndFree(Uint64 lifetimeId);
|
||||
// DeleteShaderState, for an ordinary program AND for a program-pipeline COMPOSITE, whose
|
||||
// slot has two independent release paths - the pipeline cache's LRU eviction and the
|
||||
// composite ProgramObject's own destructor. One helper for both, and the second call is a
|
||||
// proven no-op.
|
||||
Bool MGPipeEmitShaderCsoDestroyAndFree(Uint64 lifetimeId);
|
||||
|
||||
void MGPipeEmitResourceCreate(MG_State::GLState::BufferObject& buffer);
|
||||
void MGPipeEmitResourceRespecify(MG_State::GLState::BufferObject& buffer);
|
||||
void MGPipeEmitResourceSubData(MG_State::GLState::BufferObject& buffer, SizeT offset, SizeT size);
|
||||
|
||||
@@ -308,8 +308,13 @@ enum class MGPipeFieldEmitter : Uint8 {
|
||||
BindRenderState,
|
||||
BindVertexElements,
|
||||
CreateRenderState,
|
||||
SetDispatchProgram,
|
||||
SetDrawProgram,
|
||||
SetDynamicState,
|
||||
SetFramebufferState,
|
||||
SetPatchState,
|
||||
SetSamplerViews,
|
||||
SetShaderImages,
|
||||
SetVertexAttribDefaults,
|
||||
};
|
||||
|
||||
@@ -318,8 +323,13 @@ inline constexpr const char* kMGPipeFieldEmitterNames[] = {
|
||||
"BindRenderState",
|
||||
"BindVertexElements",
|
||||
"CreateRenderState",
|
||||
"SetDispatchProgram",
|
||||
"SetDrawProgram",
|
||||
"SetDynamicState",
|
||||
"SetFramebufferState",
|
||||
"SetPatchState",
|
||||
"SetSamplerViews",
|
||||
"SetShaderImages",
|
||||
"SetVertexAttribDefaults",
|
||||
};
|
||||
|
||||
@@ -344,11 +354,11 @@ inline constexpr MGPipeFieldEmitter kMGPipeFieldEmittedBy[kMGPipeInputFieldCount
|
||||
MGPipeFieldEmitter::CreateRenderState, // GetDepthFunc
|
||||
MGPipeFieldEmitter::CreateRenderState, // GetDepthMask
|
||||
MGPipeFieldEmitter::SetDynamicState, // GetDepthRangeIndexed
|
||||
MGPipeFieldEmitter::kNone, // GetFramebufferBindingSlot
|
||||
MGPipeFieldEmitter::kNone, // GetImageTextureBinding
|
||||
MGPipeFieldEmitter::SetFramebufferState, // GetFramebufferBindingSlot
|
||||
MGPipeFieldEmitter::SetShaderImages, // GetImageTextureBinding
|
||||
MGPipeFieldEmitter::SetDynamicState, // GetLineWidth
|
||||
MGPipeFieldEmitter::CreateRenderState, // GetLogicOp
|
||||
MGPipeFieldEmitter::kNone, // GetMaxTouchedTextureUnit
|
||||
MGPipeFieldEmitter::SetSamplerViews, // GetMaxTouchedTextureUnit
|
||||
MGPipeFieldEmitter::CreateRenderState, // GetMinSampleShadingValue
|
||||
MGPipeFieldEmitter::SetPatchState, // GetPatchDefaultInnerLevel
|
||||
MGPipeFieldEmitter::SetPatchState, // GetPatchDefaultOuterLevel
|
||||
@@ -359,8 +369,8 @@ inline constexpr MGPipeFieldEmitter kMGPipeFieldEmittedBy[kMGPipeInputFieldCount
|
||||
MGPipeFieldEmitter::SetDynamicState, // GetPolygonOffsetFactor
|
||||
MGPipeFieldEmitter::SetDynamicState, // GetPolygonOffsetUnits
|
||||
MGPipeFieldEmitter::SetDynamicState, // GetPrimitiveRestartIndex
|
||||
MGPipeFieldEmitter::kNone, // GetProgramForDispatch
|
||||
MGPipeFieldEmitter::kNone, // GetProgramForDraw
|
||||
MGPipeFieldEmitter::SetDispatchProgram, // GetProgramForDispatch
|
||||
MGPipeFieldEmitter::SetDrawProgram, // GetProgramForDraw
|
||||
MGPipeFieldEmitter::kNone, // GetProgramObject
|
||||
MGPipeFieldEmitter::CreateRenderState, // GetProvokingVertexMode
|
||||
MGPipeFieldEmitter::CreateRenderState, // GetRenderStateParameters
|
||||
@@ -371,7 +381,7 @@ inline constexpr MGPipeFieldEmitter kMGPipeFieldEmittedBy[kMGPipeInputFieldCount
|
||||
MGPipeFieldEmitter::kNone, // GetTextureBindGeneration
|
||||
MGPipeFieldEmitter::kNone, // GetTextureContextId
|
||||
MGPipeFieldEmitter::kNone, // GetTextureObject
|
||||
MGPipeFieldEmitter::kNone, // GetTextureUnitObject
|
||||
MGPipeFieldEmitter::SetSamplerViews, // GetTextureUnitObject
|
||||
MGPipeFieldEmitter::kNone, // GetTransformFeedbackCapturedVertices
|
||||
MGPipeFieldEmitter::kNone, // GetTransformFeedbackGeneration
|
||||
MGPipeFieldEmitter::kNone, // GetTransformFeedbackPausedPrimitiveCounter
|
||||
@@ -388,7 +398,7 @@ inline constexpr MGPipeFieldEmitter kMGPipeFieldEmittedBy[kMGPipeInputFieldCount
|
||||
MGPipeFieldEmitter::kNone, // GetBoundTransformFeedbackLifetimeId
|
||||
MGPipeFieldEmitter::kNone, // HasOpenTransformFeedbackSpan
|
||||
};
|
||||
inline constexpr SizeT kMGPipeEmittedFieldCount = 34;
|
||||
inline constexpr SizeT kMGPipeEmittedFieldCount = 40;
|
||||
|
||||
struct MGPipeFilledState {
|
||||
Uint64 CurrentVerbSerial;
|
||||
|
||||
@@ -109,6 +109,7 @@ inline Bool MGPipeVerify(const MGPSwapInterval& a, const MGPSwapInterval& b, con
|
||||
inline Bool MGPipeVerify(const MGPSurfaceInfo& a, const MGPSurfaceInfo& b, const char** outField);
|
||||
inline Bool MGPipeVerify(const RenderStateParameters& a, const RenderStateParameters& b, const char** outField);
|
||||
inline Bool MGPipeVerify(const PixelStoreParameters& a, const PixelStoreParameters& b, const char** outField);
|
||||
inline Bool MGPipeVerify(const SamplerParameters& a, const SamplerParameters& b, const char** outField);
|
||||
inline Bool MGPipeVerify(const PerBufferBlendState& a, const PerBufferBlendState& b, const char** outField);
|
||||
inline Bool MGPipeVerify(const StencilFaceState& a, const StencilFaceState& b, const char** outField);
|
||||
inline Bool MGPipeVerify(const DynamicBackendParameters& a, const DynamicBackendParameters& b, const char** outField);
|
||||
@@ -247,6 +248,8 @@ struct MGPipeHasFieldVerifier<RenderStateParameters> : std::true_type {};
|
||||
template <>
|
||||
struct MGPipeHasFieldVerifier<PixelStoreParameters> : std::true_type {};
|
||||
template <>
|
||||
struct MGPipeHasFieldVerifier<SamplerParameters> : std::true_type {};
|
||||
template <>
|
||||
struct MGPipeHasFieldVerifier<PerBufferBlendState> : std::true_type {};
|
||||
template <>
|
||||
struct MGPipeHasFieldVerifier<StencilFaceState> : std::true_type {};
|
||||
@@ -629,6 +632,11 @@ inline Bool MGPipeVerify(const PixelStoreParameters& a, const PixelStoreParamete
|
||||
return true;
|
||||
}
|
||||
|
||||
inline Bool MGPipeVerify(const SamplerParameters& a, const SamplerParameters& b, const char** outField) {
|
||||
MGP_FIELDS_SamplerParameters(MGP_VERIFY_FIELD)
|
||||
return true;
|
||||
}
|
||||
|
||||
inline Bool MGPipeVerify(const PerBufferBlendState& a, const PerBufferBlendState& b, const char** outField) {
|
||||
MGP_FIELDS_PerBufferBlendState(MGP_VERIFY_FIELD)
|
||||
return true;
|
||||
@@ -661,4 +669,4 @@ inline Bool MGPipeVerify(const MGPVertexBindingPointWire& a, const MGPVertexBind
|
||||
|
||||
#undef MGP_VERIFY_FIELD
|
||||
|
||||
inline constexpr SizeT kMGPipeVerifiedPayloadCount = 71;
|
||||
inline constexpr SizeT kMGPipeVerifiedPayloadCount = 72;
|
||||
|
||||
Reference in New Issue
Block a user