mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 12:48:32 +09:00
[Fix, Test] (MG_Impl, Pipe): gate the four P4a families on D-K2's dependency bits as well - Espryt refuses bit 10 without bit 11 or bit 7, but the client had already emitted and the acceptance-cleared level flags left its legacy arm nothing to upload
This commit is contained in:
@@ -922,17 +922,150 @@ namespace MobileGL::MG_Pipe {
|
||||
MGPipeGetResourceOps() != nullptr;
|
||||
}
|
||||
|
||||
// THE SAME TRIPLE `wants()` APPLIES TO EVERY EMISSION at the validate point, and it is
|
||||
// ================================================================================
|
||||
// AND THE FOURTH HALF: D-K2's DEPENDENCY TABLE, ON THE CLIENT (S-3, ID-41)
|
||||
// ================================================================================
|
||||
//
|
||||
// THE DEFECT THIS CLOSES. Espryt's four `Resolve<Family>SubsystemArm()` functions
|
||||
// (Managers.cpp ~:3595-3745) REFUSE a family whose D-K2 dependency bit is clear and run
|
||||
// the legacy arm instead - the shape ResolveVertexInputSubsystemArm's bit-8-requires-
|
||||
// bit-7 refusal set as the precedent. That refusal is a BACKSTOP and it cannot restore a
|
||||
// correct picture on its own, because the client's emission was gated on the operator's
|
||||
// mask ALONE: at 0x7ff (bit 10 set, bit 11 clear) the client emitted the whole texture
|
||||
// family, the applier accepted it, the emitter cleared each level's per-level dirty flag
|
||||
// on that acceptance (D-D5 as amended by ID-18 M3) - and then the server refused bit 10
|
||||
// and ran the legacy path, which found nothing left to upload. 438/491 on the DirectGLES
|
||||
// integration lane, the same 47 texture-upload failures ID-39 saw on Magma for the
|
||||
// consumer-less version of exactly this mistake.
|
||||
//
|
||||
// So the rule is the SAME "nothing at all, not less" rule as the consumer conjunct
|
||||
// above: with a dependency unmet the client emits NOTHING for that family and the legacy
|
||||
// pull path runs untouched, on both sides of the boundary.
|
||||
//
|
||||
// THE TABLE IS WRITTEN ONCE, HERE, and every one of its rows is the client mirror of the
|
||||
// refusal Espryt already implements, bit for bit and non-transitively - the two must say
|
||||
// the SAME thing, because a client that withheld more than the server refuses would
|
||||
// leave the server's handle arm live with no records to read, and a client that withheld
|
||||
// less is the defect above.
|
||||
struct P4aFamilyDependencyRow {
|
||||
Uint64 Family; // exactly one bit, and it is one of kMGPipeP4aFamilySubsystems
|
||||
Uint64 Requires; // the bits MOBILEGL_PIPE_PUSH must ALSO carry for it to be live
|
||||
};
|
||||
|
||||
inline constexpr P4aFamilyDependencyRow kMGPipeP4aFamilyDependencies[] = {
|
||||
// BIT 9 REQUIRES BIT 10. Every MGPSurface::Res in a set_framebuffer_state record
|
||||
// names a Texture or a Renderbuffer handle, and only bit 10 populates those two slot
|
||||
// tables (Managers.cpp ResolveFramebufferSubsystemArm).
|
||||
{kMGPipeSubsystemFramebuffer, kMGPipeSubsystemTextureResources},
|
||||
|
||||
// BIT 10 REQUIRES BIT 7 - a buffer texture's MGPResourceDesc::BufferForTexBuffer
|
||||
// names a Buffer handle and only bit 7 puts twins in the resource slot table (D-D1,
|
||||
// ResolveTextureResourceSubsystemArm's first row) - AND BIT 11, which is D-K2's
|
||||
// FOURTH row (ID-14/ID-15): MGPTextureParams::BuiltinSampler is a SamplerCso HANDLE,
|
||||
// only bit 11 mints sampler CSOs (c0b's four unconditional mints deliberately
|
||||
// exclude it), and the applier's verdict for a null one is Fatal{ProtocolCorruption}
|
||||
// rather than a decline. The brief's original "bit 10 without 11 is fine" is
|
||||
// WITHDRAWN for P4a as built.
|
||||
{kMGPipeSubsystemTextureResources,
|
||||
kMGPipeSubsystemResources | kMGPipeSubsystemSamplers},
|
||||
|
||||
// BIT 11 REQUIRES BIT 10. Every MGPBoundView::Texture and every MGPImageView::Res
|
||||
// names a Texture handle and only bit 10 populates that slot table; without it every
|
||||
// per-unit lookup would miss and the walk would `continue` WITHOUT unbinding
|
||||
// (ResolveSamplerSubsystemArm). With the row above this is SYMMETRIC: bits 10 and 11
|
||||
// are one arm with two switches, and the only two masks that reach either handle arm
|
||||
// are "both set" and "neither set".
|
||||
{kMGPipeSubsystemSamplers, kMGPipeSubsystemTextureResources},
|
||||
|
||||
// BIT 12 DEPENDS ON NOTHING, and that is a ROW rather than an absence so the table
|
||||
// covers the four families exhaustively (the static_assert below): a ShaderCso handle
|
||||
// names no texture and no buffer, the archive rides beside the record as a companion
|
||||
// pointer, and the extra inputs the server specialises on are read from state the
|
||||
// backend already holds (ResolveProgramSubsystemArm).
|
||||
{kMGPipeSubsystemPrograms, 0},
|
||||
};
|
||||
|
||||
// THE MIRROR PAIRS THAT STAY FINE, said out loud rather than left as an absence, because
|
||||
// an unreachable branch that says something different is how the reachable one drifts
|
||||
// (Managers.cpp's own words at :2377-2381) - and because the table is only trustworthy if
|
||||
// what it does NOT contain was decided rather than forgotten:
|
||||
// - bit 10 set, bit 9 clear: FINE. The legacy FBO sync reaches the texture twin through
|
||||
// SyncTextureObjectToBackend, which dispatches to the handle arm by itself.
|
||||
// - bit 11 set, bit 9 clear: FINE, for the same reason - a sampler view names a texture,
|
||||
// never a framebuffer.
|
||||
// - bit 7 set, bit 10 clear: FINE, and it is P3a's shipped configuration.
|
||||
// - bit 12 set with any or none of 9/10/11: FINE, per the last row.
|
||||
// - bit 10 set, bit 11 clear (and its mirror) is NOT fine and is the row above; this is
|
||||
// the one sentence in the brief that P4a as built withdrew.
|
||||
constexpr Uint64 P4aFamilyDependencyBits(Uint64 subsystem) {
|
||||
Uint64 required = 0;
|
||||
for (const P4aFamilyDependencyRow& row : kMGPipeP4aFamilyDependencies) {
|
||||
if ((subsystem & row.Family) != 0) required |= row.Requires;
|
||||
}
|
||||
return required;
|
||||
}
|
||||
|
||||
// The table covers the four families this phase migrates and nothing else, so a fifth
|
||||
// family added to kMGPipeP4aFamilySubsystems without a row here does not silently inherit
|
||||
// "depends on nothing".
|
||||
constexpr Uint64 P4aFamilyDependencyTableCoverage() {
|
||||
Uint64 covered = 0;
|
||||
for (const P4aFamilyDependencyRow& row : kMGPipeP4aFamilyDependencies) covered |= row.Family;
|
||||
return covered;
|
||||
}
|
||||
static_assert(P4aFamilyDependencyTableCoverage() == kMGPipeP4aFamilySubsystems,
|
||||
"every P4a family needs a D-K2 dependency row, even an empty one");
|
||||
static_assert(P4aFamilyDependencyBits(kMGPipeSubsystemFramebuffer) ==
|
||||
kMGPipeSubsystemTextureResources,
|
||||
"bit 9 requires bit 10");
|
||||
static_assert(P4aFamilyDependencyBits(kMGPipeSubsystemTextureResources) ==
|
||||
(kMGPipeSubsystemResources | kMGPipeSubsystemSamplers),
|
||||
"bit 10 requires bit 7 and bit 11");
|
||||
static_assert(P4aFamilyDependencyBits(kMGPipeSubsystemSamplers) ==
|
||||
kMGPipeSubsystemTextureResources,
|
||||
"bit 11 requires bit 10");
|
||||
static_assert(P4aFamilyDependencyBits(kMGPipeSubsystemPrograms) == 0, "bit 12 depends on nothing");
|
||||
// No family may depend on itself: a row that did would be unfalsifiable (its own bit is
|
||||
// set by the time the conjunct is evaluated) and would read as a dependency nobody has.
|
||||
static_assert((P4aFamilyDependencyBits(kMGPipeSubsystemFramebuffer) &
|
||||
kMGPipeSubsystemFramebuffer) == 0 &&
|
||||
(P4aFamilyDependencyBits(kMGPipeSubsystemTextureResources) &
|
||||
kMGPipeSubsystemTextureResources) == 0 &&
|
||||
(P4aFamilyDependencyBits(kMGPipeSubsystemSamplers) &
|
||||
kMGPipeSubsystemSamplers) == 0,
|
||||
"a D-K2 row must not name its own family");
|
||||
// The default mask carries every dependency, so the shipped arm is unchanged by all of
|
||||
// this - the table only ever narrows a HAND-PICKED A/B mask.
|
||||
static_assert((kMGPipeSubsystemsMigratedAtP4a &
|
||||
P4aFamilyDependencyBits(kMGPipeP4aFamilySubsystems)) ==
|
||||
P4aFamilyDependencyBits(kMGPipeP4aFamilySubsystems),
|
||||
"the P4a phase mask must satisfy every dependency it declares");
|
||||
|
||||
// IT IS THE RUNTIME BIT THAT IS TESTED, NOT THE OTHER FAMILY'S LIVENESS, and that is
|
||||
// deliberate: Espryt's resolvers classify their arms from MOBILEGL_PIPE_PUSH alone, so
|
||||
// testing anything else here would make the two sides disagree at some mask - which is
|
||||
// the failure this whole commit is about, one level up. The mask is passed in rather than
|
||||
// read, so the walk's single read of MG_Config::Features.PipePush stays the one read a
|
||||
// whole validate point resolves against.
|
||||
Bool P4aFamilyDependenciesAreSet(Uint64 subsystem, Uint64 pushMask) {
|
||||
const Uint64 required = P4aFamilyDependencyBits(subsystem);
|
||||
return (pushMask & required) == required;
|
||||
}
|
||||
|
||||
// THE SAME QUADRUPLE `wants()` APPLIES TO EVERY EMISSION at the validate point, and it is
|
||||
// deliberately the same predicate rather than a second copy of it: the operator's
|
||||
// per-subsystem A/B bit in MOBILEGL_PIPE_PUSH, this build having WIRED the family
|
||||
// at all, AND - for a P4a family - a backend having registered the consumer. The second
|
||||
// half is the family's own kMGPipeWired*Subsystem constant, which lives in the family's
|
||||
// emit header and is 0 until the commit that gives the emitter its body - so a client
|
||||
// path that lands before its emitter does is inert by construction rather than by
|
||||
// everyone remembering to check; the third is P4aFamilyHasItsConsumer above.
|
||||
// at all, AND - for a P4a family - a backend having registered the consumer and every
|
||||
// D-K2 dependency bit of the family being set. The second half is the family's own
|
||||
// kMGPipeWired*Subsystem constant, which lives in the family's emit header and is 0 until
|
||||
// the commit that gives the emitter its body - so a client path that lands before its
|
||||
// emitter does is inert by construction rather than by everyone remembering to check; the
|
||||
// third is P4aFamilyHasItsConsumer above and the fourth is P4aFamilyDependenciesAreSet.
|
||||
Bool FamilyIsLive(Uint64 subsystem, Uint64 wired) {
|
||||
return (MG_Config::Features.PipePush & subsystem) != 0 && (wired & subsystem) != 0 &&
|
||||
P4aFamilyHasItsConsumer(subsystem);
|
||||
const Uint64 pushMask = MG_Config::Features.PipePush;
|
||||
return (pushMask & subsystem) != 0 && (wired & subsystem) != 0 &&
|
||||
P4aFamilyHasItsConsumer(subsystem) &&
|
||||
P4aFamilyDependenciesAreSet(subsystem, pushMask);
|
||||
}
|
||||
|
||||
// ---- THE FAMILY SEAM ----
|
||||
@@ -1088,12 +1221,14 @@ namespace MobileGL::MG_Pipe {
|
||||
PublicationLatch().NoteUnpublished(kind, handle);
|
||||
}
|
||||
|
||||
// THE GATE ITSELF, AS AN OBSERVABLE (ID-39). Every P4a birth hook below and every `wants()`
|
||||
// row in the walk resolve through FamilyIsLive / P4aFamilyHasItsConsumer, and neither is
|
||||
// reachable from a test - so this is the one door a unit case has onto the answer, and it
|
||||
// is the SAME expression rather than a second copy of it. A subsystem outside
|
||||
// kMGPipeP4aFamilySubsystems answers the pair the P2/P3a families have always answered,
|
||||
// which is what makes "nothing that emits today changes" checkable instead of asserted.
|
||||
// THE GATE ITSELF, AS AN OBSERVABLE (ID-39, widened by S-3 / ID-41). Every P4a birth hook
|
||||
// below and every `wants()` row in the walk resolve through FamilyIsLive /
|
||||
// P4aFamilyHasItsConsumer / P4aFamilyDependenciesAreSet, and none of the three is reachable
|
||||
// from a test - so this is the one door a unit case has onto the answer, and it is the SAME
|
||||
// expression rather than a second copy of it. A subsystem outside kMGPipeP4aFamilySubsystems
|
||||
// answers the pair the P2/P3a families have always answered (its consumer conjunct is
|
||||
// vacuous and its dependency set is empty), which is what makes "nothing that emits today
|
||||
// changes" checkable instead of asserted.
|
||||
Bool MGPipeP4aFamilyEmits(Uint64 subsystem, Uint64 wired) {
|
||||
return FamilyIsLive(subsystem, wired);
|
||||
}
|
||||
@@ -2249,12 +2384,19 @@ namespace MobileGL::MG_Pipe {
|
||||
// a property of the RUNNING BACKEND, and collapsing the two would make a bisect that
|
||||
// lands between them unreadable. The P2/P3a bits are outside kMGPipeP4aFamilySubsystems,
|
||||
// so the conjunct is true for every one of them and nothing that emits today changes.
|
||||
//
|
||||
// AND THE SIXTH IS P4aFamilyDependenciesAreSet (S-3 / ID-41), the client half of D-K2:
|
||||
// a family one of whose dependency bits the operator left clear emits NOTHING here for
|
||||
// the same reason - the server REFUSES that family and runs its legacy arm, and an
|
||||
// emission the server refuses is an emission whose acceptance already cleared a frontend
|
||||
// dirty flag the legacy arm still owed. Same table, same four families, one place.
|
||||
const Uint64 pushMask = MG_Config::Features.PipePush;
|
||||
const auto wants = [&](MGPipeDirty bit) {
|
||||
const Uint64 subsystem = MGPipeSubsystemForDirty(bit);
|
||||
return subsystem != 0 && (pushMask & subsystem) != 0 &&
|
||||
(kMGPipeWiredSubsystems & subsystem) != 0 &&
|
||||
P4aFamilyHasItsConsumer(subsystem) &&
|
||||
P4aFamilyDependenciesAreSet(subsystem, pushMask) &&
|
||||
(dirty & MGPipeDirtyBit(bit)) != 0;
|
||||
};
|
||||
Uint64 payloadBytes = 0;
|
||||
@@ -2319,14 +2461,17 @@ namespace MobileGL::MG_Pipe {
|
||||
payloadBytes += EmitShaderState(*ctx);
|
||||
}
|
||||
// The texture drain has no dirty bit over it (see its definition); it is gated on the
|
||||
// subsystem bit, on this build having wired the family at all and on a backend having
|
||||
// registered the consumer, which is the same triple `wants()` applies to every other
|
||||
// emission. The third one is the whole of ID-39 on the path where it mattered most:
|
||||
// the drain is what clears a level's dirty flags on acceptance, so a drain that ran
|
||||
// against an applier no backend reads is exactly how Magma lost its texel uploads.
|
||||
// subsystem bit, on this build having wired the family at all, on a backend having
|
||||
// registered the consumer and on D-K2's dependency bits for the family being set, which
|
||||
// is the same quadruple `wants()` applies to every other emission. The last two are the
|
||||
// whole of ID-39 and of S-3 on the path where they mattered most: the drain is what
|
||||
// clears a level's dirty flags on acceptance, so a drain that ran against an applier no
|
||||
// backend reads is exactly how Magma lost its texel uploads, and a drain that ran at a
|
||||
// mask whose bit 11 or bit 7 is clear is how Espryt lost them at 0x7ff and 0x5ff.
|
||||
if ((pushMask & kMGPipeSubsystemTextureResources) != 0 &&
|
||||
(kMGPipeWiredSubsystems & kMGPipeSubsystemTextureResources) != 0 &&
|
||||
P4aFamilyHasItsConsumer(kMGPipeSubsystemTextureResources)) {
|
||||
P4aFamilyHasItsConsumer(kMGPipeSubsystemTextureResources) &&
|
||||
P4aFamilyDependenciesAreSet(kMGPipeSubsystemTextureResources, pushMask)) {
|
||||
payloadBytes += DrainTextureSubData(*ctx);
|
||||
}
|
||||
if (wants(MGPipeDirty::NewSamplerViews)) {
|
||||
@@ -2399,13 +2544,15 @@ namespace MobileGL::MG_Pipe {
|
||||
// the very fields the migration just took over.
|
||||
const MGPipeFieldEmitter emitter = kMGPipeFieldEmittedBy[i];
|
||||
const Uint64 subsystem = SubsystemForEmitter(emitter);
|
||||
// P4aFamilyHasItsConsumer is in this conjunction for the reason it is in `wants()`:
|
||||
// "supplied" means A CALL WENT OUT CARRYING THIS FIELD, and on a backend with no
|
||||
// consumer no P4a call went out at all - so withholding the pull here would leave
|
||||
// the field unfilled at the very verb that reads it.
|
||||
// P4aFamilyHasItsConsumer and P4aFamilyDependenciesAreSet are in this conjunction for
|
||||
// the reason they are in `wants()`: "supplied" means A CALL WENT OUT CARRYING THIS
|
||||
// FIELD, and on a backend with no consumer - or at a mask that leaves one of the
|
||||
// family's D-K2 dependency bits clear - no P4a call went out at all, so withholding
|
||||
// the pull here would leave the field unfilled at the very verb that reads it.
|
||||
const Bool supplied = subsystem != 0 && (subsystem & kMGPipeWiredSubsystems) != 0 &&
|
||||
(pushMask & subsystem) != 0 &&
|
||||
P4aFamilyHasItsConsumer(subsystem) &&
|
||||
P4aFamilyDependenciesAreSet(subsystem, pushMask) &&
|
||||
EmittedCallSuppliesTheWholeField(field) &&
|
||||
(applierDerives || AppliedWithoutDerivation(field));
|
||||
if (!supplied) MGPipeFillAccess::CopyField(inputs, *ctx, field);
|
||||
|
||||
@@ -43,19 +43,27 @@ namespace MobileGL::MG_Pipe {
|
||||
// stop where it says it stops (MG_Test/ScopedPipeVerb.h).
|
||||
void MGPipeLeaveVerb();
|
||||
|
||||
// PipeFill.cpp. DOES THIS BUILD, ON THIS BACKEND, EMIT FOR THIS P4a FAMILY? (ID-39.) The
|
||||
// three conjuncts are the operator's per-subsystem bit in MOBILEGL_PIPE_PUSH, the family's
|
||||
// own kMGPipeWired*Subsystem constant (`wired`, which the caller passes because it lives in
|
||||
// the family's emit header and this header may not include one), and - for the four
|
||||
// families P4a migrates - a backend having registered MGPipeResourceOps, which is the same
|
||||
// per-backend signal `MGPipeResourceSubsystemEnabled()` has applied to P3a's buffers since
|
||||
// the phase began.
|
||||
// PipeFill.cpp. DOES THIS BUILD, ON THIS BACKEND, AT THIS MASK, EMIT FOR THIS P4a FAMILY?
|
||||
// (ID-39, widened by S-3 / ID-41.) The four conjuncts are the operator's per-subsystem bit
|
||||
// in MOBILEGL_PIPE_PUSH, the family's own kMGPipeWired*Subsystem constant (`wired`, which
|
||||
// the caller passes because it lives in the family's emit header and this header may not
|
||||
// include one), and - for the four families P4a migrates - a backend having registered
|
||||
// MGPipeResourceOps (the same per-backend signal `MGPipeResourceSubsystemEnabled()` has
|
||||
// applied to P3a's buffers since the phase began) and every D-K2 dependency bit of the
|
||||
// family being set in the same mask.
|
||||
//
|
||||
// THE THIRD CONJUNCT IS THE ONE THIS DECLARATION EXISTS FOR. Magma (DirectVulkan) registers
|
||||
// no table and has no P4a twins; before it, the client emitted, the applier accepted, the
|
||||
// emitters cleared their per-level dirty flags on that acceptance, and Magma's legacy
|
||||
// upload path found nothing to upload. With it the four families emit NOTHING there and the
|
||||
// legacy pull path runs exactly as it does on a pull build.
|
||||
// THE LAST TWO CONJUNCTS ARE THE ONES THIS DECLARATION EXISTS FOR, and they are the same
|
||||
// defect twice. Magma (DirectVulkan) registers no table and has no P4a twins; at a mask like
|
||||
// 0x7ff Espryt REFUSES the texture family server-side because D-K2's fourth row says bit 10
|
||||
// requires bit 11. In both cases the client emitted anyway, the applier accepted, the
|
||||
// emitters cleared their per-level dirty flags on that acceptance, and the legacy upload
|
||||
// path that still owed those texels found nothing to upload (66 DirectVulkan cases at ID-39,
|
||||
// 47 DirectGLES cases at ID-41). With them the four families emit NOTHING in that state and
|
||||
// the legacy pull path runs exactly as it does on a pull build.
|
||||
//
|
||||
// D-K2's TABLE IS IN PipeFill.cpp, ONCE: bit 9 requires bit 10, bit 10 requires bits 7 and
|
||||
// 11, bit 11 requires bit 10, bit 12 depends on nothing - the client mirror, bit for bit, of
|
||||
// the four `Resolve<Family>SubsystemArm()` refusals in DirectGLES/Managers.cpp.
|
||||
//
|
||||
// It is exported for the unit gate and for no other caller: the gate itself is
|
||||
// FamilyIsLive() inside PipeFill.cpp, every birth hook and every `wants()` row resolves
|
||||
|
||||
@@ -256,11 +256,17 @@ namespace {
|
||||
struct FramebufferScope {
|
||||
FramebufferScope() {
|
||||
m_previousPush = MG_Config::Features.PipePush;
|
||||
// D-K2, both rows: bit 9 requires bit 10 (MGPSurface::Res names a texture or
|
||||
// renderbuffer handle) and bit 10 requires bit 11 (MGPTextureParams::BuiltinSampler
|
||||
// is a SamplerCso out of the sampler family's content-addressed cache).
|
||||
MG_Config::Features.PipePush |=
|
||||
kMGPipeSubsystemFramebuffer | kMGPipeSubsystemTextureResources | kMGPipeSubsystemSamplers;
|
||||
// D-K2, ALL THREE ROWS THAT REACH THIS SUITE, because the client enforces them since
|
||||
// S-3 / ID-41 and not only Espryt's Resolve*SubsystemArm: bit 9 requires bit 10
|
||||
// (MGPSurface::Res names a texture or renderbuffer handle), bit 10 requires bit 11
|
||||
// (MGPTextureParams::BuiltinSampler is a SamplerCso out of the sampler family's
|
||||
// content-addressed cache) and bit 10 requires bit 7 (a buffer texture's
|
||||
// BufferForTexBuffer names a Buffer handle, D-D1). Bit 7 changes nothing else here:
|
||||
// this file constructs no BufferObject.
|
||||
MG_Config::Features.PipePush |= kMGPipeSubsystemResources |
|
||||
kMGPipeSubsystemFramebuffer |
|
||||
kMGPipeSubsystemTextureResources |
|
||||
kMGPipeSubsystemSamplers;
|
||||
m_previousContext = Move(MG_State::pGLContext);
|
||||
MG_State::pGLContext = MakeUnique<GLContext>();
|
||||
MGPipeFramebufferEmitterInstance().ResetForTest();
|
||||
|
||||
@@ -212,7 +212,11 @@ TEST(TextureEmit, TheEmitterIsOneNeverDestroyedProcessSingleton) {
|
||||
X(TextureEmit, ATexturesBuiltinSamplerHoldsOneCacheReferenceAndSwapsItWithTheContent) \
|
||||
X(TextureEmit, ARecycledTextureSlotDoesNotInheritItsPredecessorsBindMask) \
|
||||
X(TextureEmit, ALevelMarkedCleanIsCollectedAtTheNextDrain) \
|
||||
X(TextureEmit, WithNoBackendConsumerTheFamilyGateIsFalseAndNothingReachesTheApplier)
|
||||
X(TextureEmit, WithNoBackendConsumerTheFamilyGateIsFalseAndNothingReachesTheApplier) \
|
||||
X(TextureEmit, WithTheSamplerBitClearTheTextureFamilyGateIsFalseAndNothingReachesTheApplier) \
|
||||
X(TextureEmit, \
|
||||
WithTheBufferResourceBitClearTheTextureFamilyGateIsFalseAndNothingReachesTheApplier) \
|
||||
X(TextureEmit, EveryDKTwoDependencyRowGatesItsOwnFamilyAndTheMirrorPairsStayLive)
|
||||
|
||||
#define MGL_DECLARE_PULL_SKIP(Suite, Name) \
|
||||
TEST(Suite, Name) { GTEST_SKIP() << "compiled only under MOBILEGL_PIPE_PUSH"; }
|
||||
@@ -240,10 +244,20 @@ namespace {
|
||||
struct TextureScope {
|
||||
TextureScope() {
|
||||
m_previousPush = MG_Config::Features.PipePush;
|
||||
// D-K2's fourth row, in the fixture: BIT 10 REQUIRES BIT 11.
|
||||
// MGPTextureParams::BuiltinSampler is a SamplerCso handle out of the sampler
|
||||
// family's content-addressed cache, and a null there is Fatal{ProtocolCorruption}.
|
||||
MG_Config::Features.PipePush |= kMGPipeSubsystemTextureResources | kMGPipeSubsystemSamplers;
|
||||
// D-K2's WHOLE ROW FOR BIT 10, in the fixture, because the client now enforces it
|
||||
// (S-3 / ID-41) and not only Espryt's ResolveTextureResourceSubsystemArm:
|
||||
// - BIT 10 REQUIRES BIT 11 - MGPTextureParams::BuiltinSampler is a SamplerCso
|
||||
// handle out of the sampler family's content-addressed cache and a null there is
|
||||
// Fatal{ProtocolCorruption};
|
||||
// - BIT 10 REQUIRES BIT 7 - a buffer texture's BufferForTexBuffer names a Buffer
|
||||
// handle and only bit 7 puts twins in the resource slot table (D-D1). Arming it
|
||||
// changes nothing else here: this file constructs no BufferObject, so no P3a
|
||||
// resource hook has anything to fire on.
|
||||
// Without all three the family gate is FALSE and every case below would be green for
|
||||
// the wrong reason - which is what the dependency cases at the end of this file pin.
|
||||
MG_Config::Features.PipePush |= kMGPipeSubsystemResources |
|
||||
kMGPipeSubsystemTextureResources |
|
||||
kMGPipeSubsystemSamplers;
|
||||
m_previousContext = Move(MG_State::pGLContext);
|
||||
MG_State::pGLContext = MakeUnique<GLContext>();
|
||||
MGPipeTextureEmitterInstance().ResetForTest();
|
||||
@@ -910,6 +924,177 @@ TEST(TextureEmit, WithNoBackendConsumerTheFamilyGateIsFalseAndNothingReachesTheA
|
||||
EXPECT_FALSE(consumed->IsStorageDirty(TextureUploadTarget::Texture2D, 0));
|
||||
}
|
||||
|
||||
// ==================== S-3 (ID-41): D-K2's DEPENDENCY TABLE, ON THE CLIENT ====================
|
||||
//
|
||||
// THE SAME DEFECT AS THE CASE ABOVE, ONE SIGNAL OVER. Espryt's four
|
||||
// `Resolve<Family>SubsystemArm()` functions REFUSE a family whose D-K2 dependency bit is clear
|
||||
// and run the legacy arm instead - the backstop. But the client's emission used to be gated on
|
||||
// MOBILEGL_PIPE_PUSH's own bit alone, so at 0x7ff (bit 10 set, bit 11 clear) it emitted the whole
|
||||
// texture family anyway, the applier accepted it, the emitter cleared each level's dirty flag on
|
||||
// that acceptance - and the server then refused bit 10 and ran a legacy path with nothing left to
|
||||
// upload. 438/491 on the DirectGLES integration lane, the same 47 texture-upload failures ID-39
|
||||
// saw on Magma for the consumer-less version of the identical mistake.
|
||||
//
|
||||
// WHAT THESE THREE CASES PIN IS "NOTHING AT ALL", NOT "LESS", exactly as the consumer case above
|
||||
// does: no create, no respecify, no params, no entry on the drain list - and the FRONTEND's own
|
||||
// dirty flag still set, which is the state the legacy pull path reads and the one whose loss no
|
||||
// pixel comparison on this side can see.
|
||||
TEST(TextureEmit, WithTheSamplerBitClearTheTextureFamilyGateIsFalseAndNothingReachesTheApplier) {
|
||||
TextureScope scope;
|
||||
// THE 0x7ff SHAPE EXACTLY: bit 10 set, bit 11 clear. D-K2's FOURTH row (ID-14/ID-15) -
|
||||
// MGPTextureParams::BuiltinSampler is a SamplerCso handle, only bit 11 mints sampler CSOs,
|
||||
// and the applier's verdict for a null one is Fatal{ProtocolCorruption}.
|
||||
MG_Config::Features.PipePush &= ~kMGPipeSubsystemSamplers;
|
||||
|
||||
EXPECT_FALSE(MGPipeP4aFamilyEmits(kMGPipeSubsystemTextureResources, kMGPipeWiredTextureSubsystem))
|
||||
<< "bit 10 is set and bit 11 is clear; Espryt refuses this family, so the client must not "
|
||||
"emit into it";
|
||||
|
||||
const auto texture = MakeTexture2D(43, 32);
|
||||
const MGPipeHandle handle = Textures().FindTexture(*texture);
|
||||
// The mint is unconditional and stays that way: what a dependency withholds is the EMISSION,
|
||||
// never the identity, exactly as for the consumer conjunct.
|
||||
ASSERT_FALSE(MGPipeHandleIsNull(handle));
|
||||
EXPECT_FALSE(MGPipeHandleIsPublished(MGPipeKind::Texture, handle))
|
||||
<< "a create was published for a family the server refuses at this mask";
|
||||
EXPECT_EQ(Textures().CreateCount(), 0u);
|
||||
EXPECT_EQ(Textures().RespecifyCount(), 0u);
|
||||
EXPECT_TRUE(MGPipeApplier().TextureResources.empty());
|
||||
|
||||
texture->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{0, 0, 0},
|
||||
IntVec3{8, 8, 1});
|
||||
EXPECT_EQ(Textures().DrainListSize(), 0u);
|
||||
Textures().DrainTextureSubData(Ctx());
|
||||
EXPECT_EQ(Textures().SubDataCount(), 0u);
|
||||
// THE ONE THAT MATTERED, and it is the whole of S-3: the level's flag is what Espryt's LEGACY
|
||||
// texture arm uploads from once it has refused bit 10.
|
||||
EXPECT_TRUE(texture->IsStorageDirty(TextureUploadTarget::Texture2D, 0))
|
||||
<< "the level's dirty flag was cleared at a mask whose server-side arm is the legacy one, "
|
||||
"so those texels exist nowhere";
|
||||
EXPECT_EQ(MGPipeApplier().RefusedNoConsumer, 0u)
|
||||
<< "the belt is about the consumer; the DEPENDENCY is the gate's job and the gate is what "
|
||||
"must have stopped this";
|
||||
|
||||
// ---- and with bit 11 back, the same sequence lands ----
|
||||
MG_Config::Features.PipePush |= kMGPipeSubsystemSamplers;
|
||||
EXPECT_TRUE(MGPipeP4aFamilyEmits(kMGPipeSubsystemTextureResources, kMGPipeWiredTextureSubsystem));
|
||||
const auto live = MakeTexture2D(44, 32);
|
||||
const MGPipeHandle liveHandle = Textures().FindTexture(*live);
|
||||
ASSERT_FALSE(MGPipeHandleIsNull(liveHandle));
|
||||
EXPECT_TRUE(MGPipeHandleIsPublished(MGPipeKind::Texture, liveHandle));
|
||||
live->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{0, 0, 0},
|
||||
IntVec3{8, 8, 1});
|
||||
EXPECT_EQ(Textures().DrainListSize(), 1u);
|
||||
Textures().DrainTextureSubData(Ctx());
|
||||
EXPECT_EQ(Textures().SubDataCount(), 1u);
|
||||
EXPECT_FALSE(live->IsStorageDirty(TextureUploadTarget::Texture2D, 0));
|
||||
}
|
||||
|
||||
// THE OTHER HALF OF THE SAME ROW: bit 10 requires bit 7 as well (D-D1). A buffer texture's
|
||||
// MGPResourceDesc::BufferForTexBuffer names a Buffer handle and only bit 7 puts twins in the
|
||||
// resource slot table, so ResolveTextureResourceSubsystemArm refuses bit 10 without it - and a
|
||||
// refused family must not have had its flags cleared by an emission that already went out. This
|
||||
// is the arm the 0x5ff-shaped masks reach from the other side.
|
||||
TEST(TextureEmit, WithTheBufferResourceBitClearTheTextureFamilyGateIsFalseAndNothingReachesTheApplier) {
|
||||
TextureScope scope;
|
||||
MG_Config::Features.PipePush &= ~kMGPipeSubsystemResources;
|
||||
|
||||
EXPECT_FALSE(MGPipeP4aFamilyEmits(kMGPipeSubsystemTextureResources, kMGPipeWiredTextureSubsystem))
|
||||
<< "bit 10 is set and bit 7 is clear; Espryt refuses this family, so the client must not "
|
||||
"emit into it";
|
||||
|
||||
const auto texture = MakeTexture2D(45, 32);
|
||||
const MGPipeHandle handle = Textures().FindTexture(*texture);
|
||||
ASSERT_FALSE(MGPipeHandleIsNull(handle));
|
||||
EXPECT_FALSE(MGPipeHandleIsPublished(MGPipeKind::Texture, handle));
|
||||
EXPECT_EQ(Textures().CreateCount(), 0u);
|
||||
EXPECT_EQ(Textures().RespecifyCount(), 0u);
|
||||
EXPECT_TRUE(MGPipeApplier().TextureResources.empty());
|
||||
|
||||
texture->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{0, 0, 0},
|
||||
IntVec3{8, 8, 1});
|
||||
EXPECT_EQ(Textures().DrainListSize(), 0u);
|
||||
Textures().DrainTextureSubData(Ctx());
|
||||
EXPECT_EQ(Textures().SubDataCount(), 0u);
|
||||
EXPECT_TRUE(texture->IsStorageDirty(TextureUploadTarget::Texture2D, 0));
|
||||
EXPECT_EQ(MGPipeApplier().RefusedNoConsumer, 0u);
|
||||
}
|
||||
|
||||
// THE TABLE ITSELF, ONE ROW PER FAMILY, AND THE MIRROR PAIRS THAT STAY LIVE. The two cases above
|
||||
// drive the texture family end to end because its emitter is the one this suite owns; the
|
||||
// framebuffer, sampler and program families are asserted through the gate itself, which is the
|
||||
// single predicate every one of their birth hooks and every `wants()` row in the walk resolves
|
||||
// through (MGPipeP4aFamilyEmits returns FamilyIsLive, not a second copy of it). `wired` is passed
|
||||
// as the family's own bit, which is what that constant is once the family has landed its emitter.
|
||||
//
|
||||
// THE ROWS ARE NON-TRANSITIVE ON PURPOSE. Espryt's resolvers classify their arms from
|
||||
// MOBILEGL_PIPE_PUSH alone, so this table asks the same question they ask - "is the dependency
|
||||
// BIT set" - and not "is the other family live". At a mask like 0x7ff that means the framebuffer
|
||||
// family stays LIVE on both sides while the texture family is dead on both sides, which is the
|
||||
// state the two must agree on; a client that withheld more than the server refuses would leave
|
||||
// the server's handle arm live with no records to read.
|
||||
TEST(TextureEmit, EveryDKTwoDependencyRowGatesItsOwnFamilyAndTheMirrorPairsStayLive) {
|
||||
TextureScope scope;
|
||||
const auto emits = [](Uint64 family) { return MGPipeP4aFamilyEmits(family, family); };
|
||||
const Uint64 kAll = kMGPipeSubsystemResources | kMGPipeSubsystemFramebuffer |
|
||||
kMGPipeSubsystemTextureResources | kMGPipeSubsystemSamplers |
|
||||
kMGPipeSubsystemPrograms;
|
||||
|
||||
// ---- every dependency satisfied: all four families live ----
|
||||
MG_Config::Features.PipePush = kAll;
|
||||
EXPECT_TRUE(emits(kMGPipeSubsystemFramebuffer));
|
||||
EXPECT_TRUE(emits(kMGPipeSubsystemTextureResources));
|
||||
EXPECT_TRUE(emits(kMGPipeSubsystemSamplers));
|
||||
EXPECT_TRUE(emits(kMGPipeSubsystemPrograms));
|
||||
|
||||
// ---- ROW 1: bit 9 requires bit 10 (MGPSurface::Res names a texture or renderbuffer) ----
|
||||
MG_Config::Features.PipePush = kAll & ~kMGPipeSubsystemTextureResources;
|
||||
EXPECT_FALSE(emits(kMGPipeSubsystemFramebuffer)) << "bit 9 set, bit 10 clear";
|
||||
// ROW 3 falls out of the same mask: bit 11 requires bit 10.
|
||||
EXPECT_FALSE(emits(kMGPipeSubsystemSamplers)) << "bit 11 set, bit 10 clear";
|
||||
EXPECT_TRUE(emits(kMGPipeSubsystemPrograms)) << "bit 12 depends on nothing";
|
||||
|
||||
// ---- ROW 2a: bit 10 requires bit 11 - the 0x7ff shape ----
|
||||
MG_Config::Features.PipePush = kAll & ~kMGPipeSubsystemSamplers;
|
||||
EXPECT_FALSE(emits(kMGPipeSubsystemTextureResources)) << "bit 10 set, bit 11 clear";
|
||||
EXPECT_TRUE(emits(kMGPipeSubsystemFramebuffer))
|
||||
<< "bit 9's row names bit 10 and bit 10 IS set at this mask - the rows are non-transitive "
|
||||
"because Espryt's ResolveFramebufferSubsystemArm is";
|
||||
EXPECT_TRUE(emits(kMGPipeSubsystemPrograms));
|
||||
|
||||
// ---- ROW 2b: bit 10 requires bit 7 - the D-D1 half ----
|
||||
MG_Config::Features.PipePush = kAll & ~kMGPipeSubsystemResources;
|
||||
EXPECT_FALSE(emits(kMGPipeSubsystemTextureResources)) << "bit 10 set, bit 7 clear";
|
||||
EXPECT_TRUE(emits(kMGPipeSubsystemSamplers)) << "bit 11's only row is bit 10, which is set";
|
||||
EXPECT_TRUE(emits(kMGPipeSubsystemPrograms));
|
||||
|
||||
// ---- ROW 4: bit 12 depends on NOTHING, so it is live entirely on its own ----
|
||||
MG_Config::Features.PipePush = kMGPipeSubsystemPrograms;
|
||||
EXPECT_TRUE(emits(kMGPipeSubsystemPrograms));
|
||||
|
||||
// ---- THE MIRROR PAIRS, said out loud: an unreachable branch that says something different
|
||||
// is how the reachable one drifts (Managers.cpp:2377-2381's own words) ----
|
||||
// bits 10 + 11 without bit 9: FINE, and it is the 0xdff arm.
|
||||
MG_Config::Features.PipePush = kAll & ~kMGPipeSubsystemFramebuffer;
|
||||
EXPECT_TRUE(emits(kMGPipeSubsystemTextureResources));
|
||||
EXPECT_TRUE(emits(kMGPipeSubsystemSamplers));
|
||||
EXPECT_TRUE(emits(kMGPipeSubsystemPrograms));
|
||||
// bit 7 without bit 10: FINE, and it is P3a's shipped configuration - no P4a family is live
|
||||
// because none of their own bits is set, and that is the ONLY reason.
|
||||
MG_Config::Features.PipePush = kMGPipeSubsystemResources;
|
||||
EXPECT_FALSE(emits(kMGPipeSubsystemFramebuffer));
|
||||
EXPECT_FALSE(emits(kMGPipeSubsystemTextureResources));
|
||||
EXPECT_FALSE(emits(kMGPipeSubsystemSamplers));
|
||||
EXPECT_FALSE(emits(kMGPipeSubsystemPrograms));
|
||||
|
||||
// ---- AND THE P2/P3a FAMILIES ARE NOT NARROWED BY ANY OF IT: their bits are outside
|
||||
// kMGPipeP4aFamilySubsystems, so they have no dependency row and no consumer conjunct.
|
||||
// This is what makes "nothing that emits today changes" checkable rather than asserted.
|
||||
MG_Config::Features.PipePush = kMGPipeSubsystemVertexInput;
|
||||
EXPECT_TRUE(emits(kMGPipeSubsystemVertexInput))
|
||||
<< "bit 8 alone, with bit 7 clear: P3a's own rule, which this table must not touch";
|
||||
}
|
||||
|
||||
// ============================ M4 ============================
|
||||
//
|
||||
// THE CANONICAL ORDER FOR THE TEXTURES THE HINT WAS WRITTEN FOR: glTexStorage2D, then
|
||||
|
||||
Reference in New Issue
Block a user