mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-10 21:28:32 +09:00
[Feat] (Pipe): key the framebuffer record by the framebuffer handle and resolve the two bindings through it, so a framebuffer that is named but bound to neither can still be described
This commit is contained in:
+124
-31
@@ -1110,11 +1110,18 @@ namespace MobileGL::MG_Pipe {
|
|||||||
|
|
||||||
// ---- P4a's working state, cleared for the same reason and with the same serial rule
|
// ---- 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,
|
// (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
|
// sampler views, shader CSOs and, since ID-19(b), the FRAMEBUFFER RECORDS - are
|
||||||
// group exactly as a buffer does, and its record is where the extent, the parameters
|
// deliberately NOT here: a texture lives in a share group exactly as a buffer does, and
|
||||||
// and the pending-upload set the backend reads now live.
|
// its record is where the extent, the parameters and the pending-upload set the backend
|
||||||
g_applier.DrawFramebuffer = MGPFramebufferState{};
|
// reads now live.
|
||||||
g_applier.ReadFramebuffer = MGPFramebufferState{};
|
//
|
||||||
|
// WHAT A MAKE-CURRENT TAKES OF THE FRAMEBUFFER FAMILY IS THE TWO BOUND HANDLES AND
|
||||||
|
// NOTHING ELSE, and that is the same observable it always took: DrawFramebuffer() /
|
||||||
|
// ReadFramebuffer() answer null after this, exactly as the zeroed records used to answer
|
||||||
|
// a null Fbo. FramebufferRecords stays because a framebuffer that is only ever addressed
|
||||||
|
// BY NAME - the DSA blit and clear entry points - has no re-emission trigger at all, so
|
||||||
|
// dropping its record here would lose it for the life of the process.
|
||||||
|
g_applier.BoundFramebuffer = {};
|
||||||
g_applier.BoundSamplerViews = {};
|
g_applier.BoundSamplerViews = {};
|
||||||
g_applier.SamplerViewStart = 0;
|
g_applier.SamplerViewStart = 0;
|
||||||
g_applier.SamplerViewCount = 0;
|
g_applier.SamplerViewCount = 0;
|
||||||
@@ -1154,21 +1161,27 @@ namespace MobileGL::MG_Pipe {
|
|||||||
g_applier.SamplerViewCsos.clear();
|
g_applier.SamplerViewCsos.clear();
|
||||||
g_applier.ShaderCsos.clear();
|
g_applier.ShaderCsos.clear();
|
||||||
g_applier.CompositeShaderCsos.clear();
|
g_applier.CompositeShaderCsos.clear();
|
||||||
|
// AND THE FRAMEBUFFER RECORDS, which MGPipeApplierReset above deliberately keeps. Here
|
||||||
|
// there is nothing for them to outlive: the served context is going away and the
|
||||||
|
// framebuffers it owned are container objects that go with it. A table left standing
|
||||||
|
// would answer a lookup from the NEXT context on a slot the allocator has re-minted -
|
||||||
|
// with the same generation, if the next context happens to reach the same one - and
|
||||||
|
// that is the aliasing every other table is cleared to prevent.
|
||||||
|
g_applier.FramebufferRecords.clear();
|
||||||
g_applier.DrawProgram = kMGPipeNullHandle;
|
g_applier.DrawProgram = kMGPipeNullHandle;
|
||||||
g_applier.DispatchProgram = kMGPipeNullHandle;
|
g_applier.DispatchProgram = kMGPipeNullHandle;
|
||||||
g_applier.BoundShaderCso = kMGPipeNullHandle;
|
g_applier.BoundShaderCso = kMGPipeNullHandle;
|
||||||
// AND "THE WORKING HANDLES THEY COULD NAME" IS ALL OF THEM, NOT JUST THE THREE ABOVE.
|
// AND "THE WORKING HANDLES THEY COULD NAME" IS ALL OF THEM, NOT JUST THE THREE ABOVE.
|
||||||
// The two framebuffer records hold eleven MGPSurface::Res naming texture and
|
// Every framebuffer record holds eleven MGPSurface::Res naming texture and renderbuffer
|
||||||
// renderbuffer records this function has just dropped, and the three unit windows hold
|
// records this function has just dropped - which is why the table itself goes above -
|
||||||
// entries naming sampler-view, sampler-CSO and texture records it has just dropped. A
|
// and the three unit windows hold entries naming sampler-view, sampler-CSO and texture
|
||||||
// window left populated here is a set of handles into empty tables: the next resolve
|
// records it has just dropped. A window left populated here is a set of handles into
|
||||||
// either refuses and counts (a teardown-time refusal storm nothing asked for) or, on a
|
// empty tables: the next resolve either refuses and counts (a teardown-time refusal
|
||||||
// slot the next context re-mints, resolves to somebody else's record. So this takes the
|
// storm nothing asked for) or, on a slot the next context re-mints, resolves to somebody
|
||||||
// same working state MGPipeApplierReset does - which is the honest reading of the rule
|
// else's record. So this takes the same working state MGPipeApplierReset does - which is
|
||||||
// as well: Reset keeps the object records BECAUSE they outlive a make-current, and here
|
// the honest reading of the rule as well: Reset keeps the object records BECAUSE they
|
||||||
// they do not outlive anything.
|
// outlive a make-current, and here they do not outlive anything.
|
||||||
g_applier.DrawFramebuffer = MGPFramebufferState{};
|
g_applier.BoundFramebuffer = {};
|
||||||
g_applier.ReadFramebuffer = MGPFramebufferState{};
|
|
||||||
g_applier.BoundSamplerViews = {};
|
g_applier.BoundSamplerViews = {};
|
||||||
g_applier.SamplerViewStart = 0;
|
g_applier.SamplerViewStart = 0;
|
||||||
g_applier.SamplerViewCount = 0;
|
g_applier.SamplerViewCount = 0;
|
||||||
@@ -2037,15 +2050,32 @@ namespace MobileGL::MG_Pipe {
|
|||||||
// ================================================================================
|
// ================================================================================
|
||||||
|
|
||||||
void MGPipeApplySetFramebufferState(const MGPFramebufferState& state) {
|
void MGPipeApplySetFramebufferState(const MGPFramebufferState& state) {
|
||||||
// GL HAS TWO INDEPENDENT FRAMEBUFFER BINDINGS AND THIS RECORD DESCRIBES ONE, so Target
|
// GL HAS TWO INDEPENDENT FRAMEBUFFER BINDINGS AND THIS RECORD DESCRIBES ONE FRAMEBUFFER,
|
||||||
// is what says which - and Both is one object bound to both, which writes both records
|
// so Target says which binding - if any - the description also BINDS it to. Both is one
|
||||||
// from one call. A value outside the three is not a binding this server has, and
|
// object bound to both and moves both handles from one call; Named
|
||||||
// guessing one would put a draw's attachments into the read record or the other way
|
// (kMGPipeFramebufferTargetNamed) moves neither, which is how the DSA entry points
|
||||||
// round, which is the defect class the per-target emission exists to close.
|
// describe a framebuffer they are about to blit into or clear without claiming it is
|
||||||
|
// bound (ID-19). A value above Named is not a target this server has, and guessing one
|
||||||
|
// would put a draw's attachments into the read binding or the other way round.
|
||||||
const char* fault = nullptr;
|
const char* fault = nullptr;
|
||||||
if (state.Target >= static_cast<Uint8>(MGPipeFramebufferTarget::Count)) {
|
if (state.Target > kMGPipeFramebufferTargetNamed) {
|
||||||
fault = "the record names no framebuffer binding target";
|
fault = "the record names no framebuffer binding target";
|
||||||
}
|
}
|
||||||
|
// THE RECORD IS ADDRESSED BY THE FRAMEBUFFER HANDLE NOW, so the handle has to be one.
|
||||||
|
// The null handle is refused rather than stored: {0,0} is what "nothing is bound to this
|
||||||
|
// binding" reads as on the other side of BoundFramebuffer, and a record installed there
|
||||||
|
// would be answered to every caller asking about an EMPTY binding. Every emitter has a
|
||||||
|
// handle for every framebuffer it describes - kMGPipeDefaultFramebuffer {0,1} for the
|
||||||
|
// default one, a minted {slot, gen} otherwise - so a null here is a malformed record.
|
||||||
|
else if (MGPipeHandleIsNull(state.Fbo)) {
|
||||||
|
fault = "the record names the null framebuffer handle";
|
||||||
|
}
|
||||||
|
// And the same bound gate the five object tables carry, for the same reason: Slot is a
|
||||||
|
// client-supplied Uint32 arriving in a payload and this is the one number in the call
|
||||||
|
// that reaches an allocator.
|
||||||
|
else if (state.Fbo.Slot >= kMGPipeMaxFramebufferSlots) {
|
||||||
|
fault = "the framebuffer slot is outside the record table's bound";
|
||||||
|
}
|
||||||
// THE DRAW-BUFFER ARRAY IS AN INDEX INTO THIS RECORD'S OWN Color[], and -1 is NONE. An
|
// THE DRAW-BUFFER ARRAY IS AN INDEX INTO THIS RECORD'S OWN Color[], and -1 is NONE. An
|
||||||
// entry outside that range would have the server read a colour attachment the record
|
// entry outside that range would have the server read a colour attachment the record
|
||||||
// does not carry: the wire array is 8 wide, the driver's MaxColorAttachments is not
|
// does not carry: the wire array is 8 wide, the driver's MaxColorAttachments is not
|
||||||
@@ -2069,23 +2099,86 @@ namespace MobileGL::MG_Pipe {
|
|||||||
// NO HANDLE IS RESOLVED HERE AND NONE MAY BE. A framebuffer has a handle but no wire
|
// NO HANDLE IS RESOLVED HERE AND NONE MAY BE. A framebuffer has a handle but no wire
|
||||||
// lifetime - the catalogue has no framebuffer create and no framebuffer destroy,
|
// lifetime - the catalogue has no framebuffer create and no framebuffer destroy,
|
||||||
// because a framebuffer is state and this call is the only one that names one - so
|
// because a framebuffer is state and this call is the only one that names one - so
|
||||||
// there is no record to refuse against and this entry point never counts a refusal.
|
// there is nothing to look up, this entry point never counts a refusal, and the record
|
||||||
|
// it writes OVERWRITES whatever the slot held. That last part is the point rather than
|
||||||
|
// a compromise: the slot's previous occupant is dead by construction (the client frees
|
||||||
|
// a framebuffer's slot when the object dies and the allocator bumps the generation on
|
||||||
|
// reuse), and the record arriving now describes whatever holds the slot today.
|
||||||
|
//
|
||||||
// The surfaces' Res handles are not resolved either: an attachment PINS its texture,
|
// The surfaces' Res handles are not resolved either: an attachment PINS its texture,
|
||||||
// and in monolith the frontend's own SharedPtr is that keep-alive, so a refusal here
|
// and in monolith the frontend's own SharedPtr is that keep-alive, so a refusal here
|
||||||
// would be enforcing a lifetime rule monolith cannot need and split has not defined.
|
// would be enforcing a lifetime rule monolith cannot need and split has not defined.
|
||||||
if (state.Target != static_cast<Uint8>(MGPipeFramebufferTarget::Read)) {
|
MGPipeFramebufferRecord* record =
|
||||||
g_applier.DrawFramebuffer = state;
|
RecordAt(g_applier.FramebufferRecords, state.Fbo.Slot, kMGPipeMaxFramebufferSlots);
|
||||||
|
// Unreachable: the bound was checked above, before anything moved. The null check is
|
||||||
|
// what makes that an argument rather than an assumption.
|
||||||
|
if (record == nullptr) return;
|
||||||
|
record->Gen = state.Fbo.Gen;
|
||||||
|
record->Live = true;
|
||||||
|
record->State = state;
|
||||||
|
|
||||||
|
// AND ONLY THEN THE BINDINGS. Named moves neither, which is the whole of the fourth
|
||||||
|
// target: "this record describes the framebuffer it names; no binding changes".
|
||||||
|
if (state.Target == static_cast<Uint8>(MGPipeFramebufferTarget::Draw) ||
|
||||||
|
state.Target == static_cast<Uint8>(MGPipeFramebufferTarget::Both)) {
|
||||||
|
g_applier.BoundFramebuffer[static_cast<SizeT>(MGPipeFramebufferTarget::Draw)] = state.Fbo;
|
||||||
}
|
}
|
||||||
if (state.Target != static_cast<Uint8>(MGPipeFramebufferTarget::Draw)) {
|
if (state.Target == static_cast<Uint8>(MGPipeFramebufferTarget::Read) ||
|
||||||
g_applier.ReadFramebuffer = state;
|
state.Target == static_cast<Uint8>(MGPipeFramebufferTarget::Both)) {
|
||||||
|
g_applier.BoundFramebuffer[static_cast<SizeT>(MGPipeFramebufferTarget::Read)] = state.Fbo;
|
||||||
}
|
}
|
||||||
// ONE SERIAL FOR THE PAIR, and it moves once per applied record - a Both record is one
|
// ONE SERIAL FOR THE FAMILY, and it moves once per applied record - a Both record is one
|
||||||
// record. It is what retires the four g_fboSynced* arrays and the twin's own
|
// record and a Named record is one record. It is what retires the four g_fboSynced*
|
||||||
// {slot version, object version, backend id generation} quadruple: a compare that used
|
// arrays and the twin's own {slot version, object version, backend id generation}
|
||||||
// to ask "is my memo still the frontend's" asks "is my serial still the server's".
|
// quadruple: a compare that used to ask "is my memo still the frontend's" asks "is my
|
||||||
|
// serial still the server's". A Named record moves it for the same reason a bound one
|
||||||
|
// does - a twin that memoised a framebuffer's attachments has to hear that they moved,
|
||||||
|
// and whether the framebuffer happens to be bound is not that question.
|
||||||
++g_applier.FramebufferSerial;
|
++g_applier.FramebufferSerial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// THE READ SIDE OF THE TABLE (ID-19(b)/(d)). Package D asks it by handle at every DSA entry
|
||||||
|
// point; the two accessors under it ask it through a binding, which is the question package
|
||||||
|
// E's SyncCurrentFBOByRecord has always asked.
|
||||||
|
const MGPFramebufferState* MGPipeApplierState::FramebufferRecordFor(MGPipeHandle fbo) const {
|
||||||
|
// "Nothing is bound" and "no record" are BOTH null and only the second of them is worth
|
||||||
|
// a word: the null handle is what a make-current leaves in BoundFramebuffer and what an
|
||||||
|
// unbound target reads as, it arrives here on every draw of a context that has not
|
||||||
|
// described its framebuffers yet, and logging it would be a storm about the ordinary
|
||||||
|
// transitional state. It is separated first so the generation test below can be loud.
|
||||||
|
if (MGPipeHandleIsNull(fbo)) return nullptr;
|
||||||
|
if (fbo.Slot >= FramebufferRecords.size()) return nullptr;
|
||||||
|
const MGPipeFramebufferRecord& record = FramebufferRecords[fbo.Slot];
|
||||||
|
// Nothing has been written at this slot. Also not an error, and also silent: it is what
|
||||||
|
// every framebuffer looks like before its first set_framebuffer_state, and the caller's
|
||||||
|
// answer to it is to run its pre-handle arm.
|
||||||
|
if (!record.Live) return nullptr;
|
||||||
|
if (record.Gen != fbo.Gen) {
|
||||||
|
// AND THIS ONE IS. A record exists at the slot and it describes a DIFFERENT object:
|
||||||
|
// either the caller is holding a handle whose framebuffer died and whose slot has
|
||||||
|
// been recycled, or an emitter minted the successor without ever describing it. On
|
||||||
|
// the handle arm the visible consequence is a DSA blit or clear into a driver
|
||||||
|
// framebuffer that never got its attachments, which is precisely the failure
|
||||||
|
// ID-19 exists to close - so this is the loud refusal P3a's lesson demands and
|
||||||
|
// never a quiet fall-back.
|
||||||
|
++StaleFramebufferRecordLookups;
|
||||||
|
MGLOG_E_ONCE("MGPipe: the framebuffer record at slot %u is generation %u and the "
|
||||||
|
"lookup named generation %u; refusing it rather than answering with "
|
||||||
|
"another framebuffer's attachments",
|
||||||
|
fbo.Slot, record.Gen, fbo.Gen);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return &record.State;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MGPFramebufferState* MGPipeApplierState::DrawFramebuffer() const {
|
||||||
|
return FramebufferRecordFor(BoundFramebuffer[static_cast<SizeT>(MGPipeFramebufferTarget::Draw)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const MGPFramebufferState* MGPipeApplierState::ReadFramebuffer() const {
|
||||||
|
return FramebufferRecordFor(BoundFramebuffer[static_cast<SizeT>(MGPipeFramebufferTarget::Read)]);
|
||||||
|
}
|
||||||
|
|
||||||
// ================================================================================
|
// ================================================================================
|
||||||
// w2: sampler CSOs, sampler views and the three unit sets
|
// w2: sampler CSOs, sampler views and the three unit sets
|
||||||
// ================================================================================
|
// ================================================================================
|
||||||
|
|||||||
+156
-16
@@ -147,6 +147,42 @@ namespace MobileGL::MG_Pipe {
|
|||||||
static_assert(kMGPipeMaxShaderCsoSlots > kMGPipeShaderCsoCompositeSlotBase,
|
static_assert(kMGPipeMaxShaderCsoSlots > kMGPipeShaderCsoCompositeSlotBase,
|
||||||
"the ShaderCso bound must contain the composite band, or a composite handle "
|
"the ShaderCso bound must contain the composite band, or a composite handle "
|
||||||
"is refused as out of range on arrival");
|
"is refused as out of range on arrival");
|
||||||
|
// ID-19(b): the framebuffer record is now PER OBJECT and its table is slot-indexed like the
|
||||||
|
// five above, so it takes a bound on the same terms. A framebuffer record is 304 bytes and
|
||||||
|
// an FBO is a CONTAINER object - not shared between contexts, minted a few dozen at a time
|
||||||
|
// by a renderer and a few hundred by a shader pack - so 1<<16 is orders of magnitude above
|
||||||
|
// any live population and still turns a corrupt Uint32 into a refusal rather than a
|
||||||
|
// 4-billion-entry resize.
|
||||||
|
inline constexpr Uint32 kMGPipeMaxFramebufferSlots = 1u << 16;
|
||||||
|
|
||||||
|
// THE FOURTH set_framebuffer_state TARGET, AND IT IS THE CONTRACT'S TO MINT (c0e:
|
||||||
|
// MGPipeFramebufferTarget::Named = 3). It is declared here as a plain constant because wire
|
||||||
|
// v3 and c0e run in parallel: the applier must ADMIT the value now, and this package may not
|
||||||
|
// write MGPipeTypes.h. When c0e lands, this constant is deleted and every use below becomes
|
||||||
|
// static_cast<Uint8>(MGPipeFramebufferTarget::Named) - wire's verification round retires it,
|
||||||
|
// and the static_assert underneath is what makes forgetting impossible: the day the
|
||||||
|
// enumerator exists, MGPipeFramebufferTarget::Count becomes 4 and this fires.
|
||||||
|
//
|
||||||
|
// ITS MEANING: "this record describes the framebuffer it names; no binding changes." Draw /
|
||||||
|
// Read / Both write the record AND set the bound handle(s); Named writes the record only.
|
||||||
|
// That is what lets the DSA entry points - BlitNamedFramebuffer and the four
|
||||||
|
// ClearNamedFramebuffer* - be handed a record for a framebuffer that is bound to neither
|
||||||
|
// binding, which is the hole esprytobj's C-1 found: the applier used to hold the two BOUND
|
||||||
|
// records only, so a named blit or clear reached a driver FBO that never got its
|
||||||
|
// attachments.
|
||||||
|
inline constexpr Uint8 kMGPipeFramebufferTargetNamed = 3;
|
||||||
|
static_assert(static_cast<Uint8>(MGPipeFramebufferTarget::Count) == kMGPipeFramebufferTargetNamed,
|
||||||
|
"c0e has landed MGPipeFramebufferTarget::Named: delete kMGPipeFramebufferTargetNamed "
|
||||||
|
"and spell the enumerator (wire's verification round, ID-21)");
|
||||||
|
|
||||||
|
// The two framebuffer BINDINGS, and there are two rather than three: Both and Named are
|
||||||
|
// things a RECORD says, not bindings a server has. MGPipeApplierState::BoundFramebuffer is
|
||||||
|
// indexed by MGPipeFramebufferTarget::Draw / ::Read, which is what makes package D's
|
||||||
|
// "is this framebuffer the one bound to target t" one array compare (ID-19(d)).
|
||||||
|
inline constexpr Uint32 kMGPipeFramebufferBindingCount = 2;
|
||||||
|
static_assert(static_cast<Uint8>(MGPipeFramebufferTarget::Draw) == 0 &&
|
||||||
|
static_cast<Uint8>(MGPipeFramebufferTarget::Read) == 1,
|
||||||
|
"BoundFramebuffer is indexed by the target byte; Draw and Read must be 0 and 1");
|
||||||
|
|
||||||
// ---- P4a's SHAPE bounds, and they are the same argument the slot bounds above make, one
|
// ---- P4a's SHAPE bounds, and they are the same argument the slot bounds above make, one
|
||||||
// level down: every number below arrives inside a payload, every one of them decides how
|
// level down: every number below arrives inside a payload, every one of them decides how
|
||||||
@@ -343,6 +379,27 @@ namespace MobileGL::MG_Pipe {
|
|||||||
Uint64 ContentSerial = 0;
|
Uint64 ContentSerial = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// set_framebuffer_state's record, HELD PER FRAMEBUFFER OBJECT and indexed by the handle's
|
||||||
|
// slot (ID-19(b)). It is the one record kind in this file whose object has NO WIRE LIFETIME:
|
||||||
|
// the catalogue has no framebuffer create and no framebuffer destroy, because a framebuffer
|
||||||
|
// is state and set_framebuffer_state is the only call that names one (D-I2). So there is
|
||||||
|
// nothing to mark dead and nothing to refuse against, and a slot is simply OVERWRITTEN by
|
||||||
|
// its successor's record - which is correct rather than merely tolerable, since the record
|
||||||
|
// that reaches this table is the description of whatever object holds the slot NOW.
|
||||||
|
//
|
||||||
|
// `Live` is therefore NOT a lifetime. It means "a record has been written at this slot",
|
||||||
|
// which is the only question a reader can ask: it separates a table entry that exists
|
||||||
|
// because the vector grew past it from one an emission actually wrote. The GENERATION is
|
||||||
|
// still checked on every lookup (P3a contract-review M2), and a mismatch is a LOUD refusal -
|
||||||
|
// it means an emitter handed a stale handle, or minted a successor without describing it,
|
||||||
|
// which is exactly the seam defect the DSA arm would otherwise turn into a blit into a
|
||||||
|
// driver framebuffer with no attachments.
|
||||||
|
struct MGPipeFramebufferRecord {
|
||||||
|
Uint32 Gen = 0;
|
||||||
|
Bool Live = false;
|
||||||
|
MGPFramebufferState State{};
|
||||||
|
};
|
||||||
|
|
||||||
struct MGPipeApplierState {
|
struct MGPipeApplierState {
|
||||||
// Indexed by slot; slot 0 is the reserved null handle and is never live
|
// Indexed by slot; slot 0 is the reserved null handle and is never live
|
||||||
// (MGPipeHandles.h kMGPipeFirstAllocatableSlot).
|
// (MGPipeHandles.h kMGPipeFirstAllocatableSlot).
|
||||||
@@ -432,6 +489,19 @@ namespace MobileGL::MG_Pipe {
|
|||||||
// handle is an ordinary ShaderCso handle, and create/bind/delete_shader_state name it
|
// handle is an ordinary ShaderCso handle, and create/bind/delete_shader_state name it
|
||||||
// exactly as they name any other program.
|
// exactly as they name any other program.
|
||||||
Vector<MGPipeShaderCsoRecord> CompositeShaderCsos;
|
Vector<MGPipeShaderCsoRecord> CompositeShaderCsos;
|
||||||
|
// AND THE SIXTH, WHICH IS THE ONE ID-19 ADDED. Keyed by the FRAMEBUFFER HANDLE's slot,
|
||||||
|
// for the reason MGPipeFramebufferRecord states: the two bound-target records the phase
|
||||||
|
// started with could not describe a framebuffer that is bound to neither binding, and
|
||||||
|
// the five DSA entry points (BlitNamedFramebuffer, the four ClearNamedFramebuffer*) hand
|
||||||
|
// Espryt exactly that.
|
||||||
|
//
|
||||||
|
// IT IS AN OBJECT TABLE AND IT LIVES WHERE THE OTHER OBJECT TABLES LIVE, which is also
|
||||||
|
// its make-current rule: MGPipeApplierReset does NOT clear it. An FBO is not shared
|
||||||
|
// between contexts, but its record is addressed by a slot out of one global allocator,
|
||||||
|
// so nothing aliases across a switch - and dropping the table would leave a
|
||||||
|
// DSA-only framebuffer with no record and no event that would ever re-emit one (the
|
||||||
|
// client's suppressor invalidation re-emits the two BOUND records and nothing else).
|
||||||
|
Vector<MGPipeFramebufferRecord> FramebufferRecords;
|
||||||
|
|
||||||
// Every call this applier REFUSED because it named a record this applier does not
|
// 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
|
// have: an unknown slot, a slot that is not live, or a generation that has moved on
|
||||||
@@ -456,7 +526,10 @@ namespace MobileGL::MG_Pipe {
|
|||||||
// MGPSurface::Res is likewise left unresolved on purpose (D-I3: the keep-alives are the
|
// MGPSurface::Res is likewise left unresolved on purpose (D-I3: the keep-alives are the
|
||||||
// frontend's SharedPtrs and enforcing them is a later phase's). Its only verdict is
|
// frontend's SharedPtrs and enforcing them is a later phase's). Its only verdict is
|
||||||
// Fatal{ProtocolCorruption} on a malformed record, and this counter must stay at 0
|
// Fatal{ProtocolCorruption} on a malformed record, and this counter must stay at 0
|
||||||
// across every framebuffer call in every build.
|
// across every framebuffer call in every build. ID-19(b) does not change that: the
|
||||||
|
// per-object table is WRITTEN by that call and never looked up by it, and the refusal
|
||||||
|
// that the table CAN produce - a lookup whose generation has moved on - happens on the
|
||||||
|
// server's own read path and is counted apart, in StaleFramebufferRecordLookups.
|
||||||
//
|
//
|
||||||
// THE OTHER CLASS IS NOT COUNTED HERE AND MUST NOT BE: a var-tail window outside its
|
// 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
|
// bound, or a set_texture_params whose BuiltinSampler is the null handle, would make
|
||||||
@@ -513,13 +586,32 @@ namespace MobileGL::MG_Pipe {
|
|||||||
// restarts walks back through values already stamped into a twin that outlived the
|
// 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.
|
// 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
|
// WHICH FRAMEBUFFER IS BOUND TO EACH BINDING, and that is ALL this pair is since
|
||||||
// record is fully resolved: ReadSurface comes from the READ framebuffer's own read
|
// ID-19(b): the record itself lives in FramebufferRecords above, keyed by the handle.
|
||||||
// buffer, so the shared-FBO case cannot lose it, and DrawBuffers[] is applied only for
|
// Indexed by MGPipeFramebufferTarget::Draw / ::Read. kMGPipeNullHandle means "nothing
|
||||||
// a record whose Target is not Read.
|
// described this binding yet", which is what a make-current leaves behind.
|
||||||
MGPFramebufferState DrawFramebuffer{};
|
//
|
||||||
MGPFramebufferState ReadFramebuffer{};
|
// set_framebuffer_state Draw / Read / Both writes the RECORD at state.Fbo's slot AND
|
||||||
|
// sets the handle(s) here; Named (kMGPipeFramebufferTargetNamed) writes the record and
|
||||||
|
// touches nothing here at all - that is the whole of the fourth target's meaning.
|
||||||
|
Array<MGPipeHandle, kMGPipeFramebufferBindingCount> BoundFramebuffer{};
|
||||||
|
// ONE SERIAL FOR THE FAMILY, and it moves on EVERY write - a Named record's included,
|
||||||
|
// because a twin memoising "the framebuffer state I have seen" has to hear about a
|
||||||
|
// named framebuffer's attachments exactly as it hears about a bound one's. It is the
|
||||||
|
// number that retires the four g_fboSynced* arrays and the twin's {slot version, object
|
||||||
|
// version, backend id generation} triple.
|
||||||
Uint64 FramebufferSerial = 0;
|
Uint64 FramebufferSerial = 0;
|
||||||
|
// Every FramebufferRecordFor() that found a record at the slot whose GENERATION had
|
||||||
|
// moved on. It is NOT RefusedObjectCalls: this is a READ by the server's own sync path
|
||||||
|
// and not a call this applier refused, and set_framebuffer_state's counter contract
|
||||||
|
// (below) is that no framebuffer call ever moves that one. A non-zero value here is a
|
||||||
|
// seam defect - an emitter minted a successor for a recycled slot and never described
|
||||||
|
// it, or handed out a handle it had already retired - so it is counted AND logged, and
|
||||||
|
// a unit case reads it in every build for the reason the other counters exist.
|
||||||
|
//
|
||||||
|
// `mutable` because the three accessors below are const: package E holds the applier
|
||||||
|
// through a `const auto&` and must keep doing so.
|
||||||
|
mutable Uint64 StaleFramebufferRecordLookups = 0;
|
||||||
|
|
||||||
// The three kVarTail unit sets, as received. NO STAGE DIMENSION: MobileGL's
|
// 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
|
// texture-unit space is one merged array of 192, the same unit may be sampled from two
|
||||||
@@ -551,6 +643,26 @@ namespace MobileGL::MG_Pipe {
|
|||||||
MGPipeHandle DispatchProgram = kMGPipeNullHandle;
|
MGPipeHandle DispatchProgram = kMGPipeNullHandle;
|
||||||
MGPipeHandle BoundShaderCso = kMGPipeNullHandle;
|
MGPipeHandle BoundShaderCso = kMGPipeNullHandle;
|
||||||
Uint64 ProgramBindingSerial = 0;
|
Uint64 ProgramBindingSerial = 0;
|
||||||
|
|
||||||
|
// ---- THE THREE FRAMEBUFFER ACCESSORS (ID-19(b)/(d)). They are functions rather than
|
||||||
|
// members because the storage moved under them and their callers must not have to know
|
||||||
|
// it did: `DrawFramebuffer()` / `ReadFramebuffer()` answer the question the two members
|
||||||
|
// used to answer - "which record describes the framebuffer bound to this binding" - by
|
||||||
|
// resolving BoundFramebuffer[t] through FramebufferRecords.
|
||||||
|
//
|
||||||
|
// NULL IS A REAL ANSWER AND HAS EXACTLY THREE CAUSES: nothing is bound to that binding
|
||||||
|
// (the null handle, which is what a make-current leaves and is NOT an error), no record
|
||||||
|
// has been written at that slot, or the slot's generation has moved on under the handle
|
||||||
|
// (which IS an error and is counted and logged - see StaleFramebufferRecordLookups). A
|
||||||
|
// caller that used to test `MGPipeHandleIsNull(st.DrawFramebuffer.Fbo)` tests the
|
||||||
|
// pointer instead; the two are the same question.
|
||||||
|
//
|
||||||
|
// Defined in PipeApply.cpp rather than inline HERE so this header keeps its include
|
||||||
|
// closure: the stale-generation path logs, and MG_Util/Debug/Log.h is not in this
|
||||||
|
// header's closure and may not become part of it.
|
||||||
|
const MGPFramebufferState* FramebufferRecordFor(MGPipeHandle fbo) const;
|
||||||
|
const MGPFramebufferState* DrawFramebuffer() const;
|
||||||
|
const MGPFramebufferState* ReadFramebuffer() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
// The monolith's single applier. Under split there is one per served context.
|
// The monolith's single applier. Under split there is one per served context.
|
||||||
@@ -570,11 +682,21 @@ namespace MobileGL::MG_Pipe {
|
|||||||
// destroy, and dropping them is a dropped write on the far side of it.
|
// 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
|
// 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
|
// BINDINGS, the three unit sets, DrawProgram / DispatchProgram / BoundShaderCso - all of it
|
||||||
// per-context working state - with their serials ADVANCED and never zeroed. Not cleared:
|
// 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 and renderbuffer resources, sampler CSOs, sampler views, shader CSOs, the
|
||||||
// texture params and pending uploads that ride on a resource record, because a texture
|
// framebuffer RECORDS, and the texture params and pending uploads that ride on a resource
|
||||||
// lives in a share group exactly as a buffer does.
|
// record, because a texture lives in a share group exactly as a buffer does.
|
||||||
|
//
|
||||||
|
// ID-19(b) MOVED THE FRAMEBUFFER RECORD ACROSS THAT LINE and the reason is worth stating.
|
||||||
|
// Before it, the whole framebuffer state was working state and a make-current took it. Now
|
||||||
|
// the RECORD is an object record and only the two BOUND HANDLES are working state, so a
|
||||||
|
// switch clears the bindings - after which DrawFramebuffer() / ReadFramebuffer() answer
|
||||||
|
// null, exactly as the cleared records used to answer a null Fbo - and leaves the table
|
||||||
|
// standing. Dropping the table instead would silently lose the record of every framebuffer
|
||||||
|
// that is described by NAME and never bound, because the client's re-emission on a fresh
|
||||||
|
// context is driven by MGPipeSetHashSuppressor::InvalidateAll, which re-sends the two bound
|
||||||
|
// records and nothing else.
|
||||||
//
|
//
|
||||||
// AND THEREFORE NO P4a TRACKER NEEDS A RE-PUBLICATION PATH ON FreshlyPrimed, AND NONE MAY
|
// 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
|
// HAVE ONE: re-emitting create_sampler_state for a record the applier still holds would
|
||||||
@@ -781,11 +903,29 @@ namespace MobileGL::MG_Pipe {
|
|||||||
// follow this one on the same branch.
|
// follow this one on the same branch.
|
||||||
|
|
||||||
// set_framebuffer_state. Fully resolved - nothing in the record requires a lookup on the
|
// 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
|
// far side. ContentHash covers every field including Fbo and DrawBuffers[8], which is what
|
||||||
// applier keeps the two records apart; Both writes both. ContentHash covers every field
|
// makes a suppressed record provably mean "the draw-buffer array did not move" and
|
||||||
// including Fbo and DrawBuffers[8], which is what makes a suppressed record provably mean
|
// therefore "the fragColor broadcast count did not move".
|
||||||
// "the draw-buffer array did not move" and therefore "the fragColor broadcast count did
|
//
|
||||||
// not move".
|
// `state.Target` NOW SAYS TWO THINGS AT ONCE (ID-19(b)), and the record always does the
|
||||||
|
// first of them:
|
||||||
|
//
|
||||||
|
// - THE RECORD IS ALWAYS WRITTEN, at FramebufferRecords[state.Fbo.Slot], whatever the
|
||||||
|
// target is. The table is keyed by the framebuffer HANDLE, so one framebuffer's record
|
||||||
|
// can never displace another's, and a slot whose object has been recycled is simply
|
||||||
|
// overwritten by its successor's record (D-I2: no wire lifetime, so nothing to retire).
|
||||||
|
// - Draw / Read / Both ADDITIONALLY set BoundFramebuffer[Draw] / [Read] / both.
|
||||||
|
// kMGPipeFramebufferTargetNamed sets NEITHER: it is how a DSA entry point hands Espryt
|
||||||
|
// a framebuffer it is about to blit into or clear WITHOUT claiming it is bound.
|
||||||
|
//
|
||||||
|
// FramebufferSerial advances on every applied record, Named included.
|
||||||
|
//
|
||||||
|
// Two refusals, both Fatal{ProtocolCorruption} and neither counted (see RefusedObjectCalls:
|
||||||
|
// this entry point resolves nothing and can only ever fault): a target above Named, a
|
||||||
|
// draw-buffer entry outside the record's own Color[], a slot at or above
|
||||||
|
// kMGPipeMaxFramebufferSlots, and the NULL HANDLE - a record that named {0,0} would install
|
||||||
|
// itself where "nothing is bound" is read, and every emitter has a handle for every
|
||||||
|
// framebuffer it describes (kMGPipeDefaultFramebuffer {0,1} for the default one).
|
||||||
void MGPipeApplySetFramebufferState(const MGPFramebufferState& state);
|
void MGPipeApplySetFramebufferState(const MGPFramebufferState& state);
|
||||||
|
|
||||||
// create_sampler_state. `parameters` is the client's canonical SamplerParameters copy,
|
// create_sampler_state. `parameters` is the client's canonical SamplerParameters copy,
|
||||||
|
|||||||
@@ -221,32 +221,42 @@ TEST(FramebufferEmit, ADrawRecordAndAReadRecordAreKeptApartAndBothWritesBoth) {
|
|||||||
const Uint64 serialAtStart = MGPipeApplier().FramebufferSerial;
|
const Uint64 serialAtStart = MGPipeApplier().FramebufferSerial;
|
||||||
|
|
||||||
MGPipeApplySetFramebufferState(FramebufferRecord(MGPipeHandle{4, 1}, MGPipeFramebufferTarget::Draw, 100));
|
MGPipeApplySetFramebufferState(FramebufferRecord(MGPipeHandle{4, 1}, MGPipeFramebufferTarget::Draw, 100));
|
||||||
EXPECT_EQ(MGPipeApplier().DrawFramebuffer.Fbo, (MGPipeHandle{4, 1}));
|
ASSERT_NE(MGPipeApplier().DrawFramebuffer(), nullptr);
|
||||||
EXPECT_EQ(MGPipeApplier().DrawFramebuffer.Width, 100u);
|
EXPECT_EQ(MGPipeApplier().DrawFramebuffer()->Fbo, (MGPipeHandle{4, 1}));
|
||||||
EXPECT_EQ(MGPipeApplier().DrawFramebuffer.Color[0].InternalFormat, 0x8058u);
|
EXPECT_EQ(MGPipeApplier().DrawFramebuffer()->Width, 100u);
|
||||||
EXPECT_EQ(MGPipeApplier().DrawFramebuffer.DrawBuffers[0], 0);
|
EXPECT_EQ(MGPipeApplier().DrawFramebuffer()->Color[0].InternalFormat, 0x8058u);
|
||||||
EXPECT_EQ(MGPipeApplier().ReadFramebuffer.Fbo, kMGPipeNullHandle)
|
EXPECT_EQ(MGPipeApplier().DrawFramebuffer()->DrawBuffers[0], 0);
|
||||||
|
EXPECT_EQ(MGPipeApplier().ReadFramebuffer(), nullptr)
|
||||||
<< "a Draw record landed in the read binding as well";
|
<< "a Draw record landed in the read binding as well";
|
||||||
const Uint64 afterDraw = MGPipeApplier().FramebufferSerial;
|
const Uint64 afterDraw = MGPipeApplier().FramebufferSerial;
|
||||||
EXPECT_GT(afterDraw, serialAtStart) << "an applied record must move the serial the twin memoises";
|
EXPECT_GT(afterDraw, serialAtStart) << "an applied record must move the serial the twin memoises";
|
||||||
|
|
||||||
MGPipeApplySetFramebufferState(FramebufferRecord(MGPipeHandle{5, 2}, MGPipeFramebufferTarget::Read, 200));
|
MGPipeApplySetFramebufferState(FramebufferRecord(MGPipeHandle{5, 2}, MGPipeFramebufferTarget::Read, 200));
|
||||||
EXPECT_EQ(MGPipeApplier().ReadFramebuffer.Fbo, (MGPipeHandle{5, 2}));
|
ASSERT_NE(MGPipeApplier().ReadFramebuffer(), nullptr);
|
||||||
EXPECT_EQ(MGPipeApplier().ReadFramebuffer.Width, 200u);
|
EXPECT_EQ(MGPipeApplier().ReadFramebuffer()->Fbo, (MGPipeHandle{5, 2}));
|
||||||
EXPECT_EQ(MGPipeApplier().DrawFramebuffer.Fbo, (MGPipeHandle{4, 1}))
|
EXPECT_EQ(MGPipeApplier().ReadFramebuffer()->Width, 200u);
|
||||||
<< "a Read record overwrote the draw binding";
|
ASSERT_NE(MGPipeApplier().DrawFramebuffer(), nullptr) << "a Read record overwrote the draw binding";
|
||||||
EXPECT_EQ(MGPipeApplier().DrawFramebuffer.Width, 100u);
|
EXPECT_EQ(MGPipeApplier().DrawFramebuffer()->Fbo, (MGPipeHandle{4, 1}));
|
||||||
|
EXPECT_EQ(MGPipeApplier().DrawFramebuffer()->Width, 100u);
|
||||||
EXPECT_GT(MGPipeApplier().FramebufferSerial, afterDraw);
|
EXPECT_GT(MGPipeApplier().FramebufferSerial, afterDraw);
|
||||||
|
|
||||||
// Both: one record, one serial bump, two destinations.
|
// Both: one record, one serial bump, two destinations.
|
||||||
const Uint64 beforeBoth = MGPipeApplier().FramebufferSerial;
|
const Uint64 beforeBoth = MGPipeApplier().FramebufferSerial;
|
||||||
MGPipeApplySetFramebufferState(FramebufferRecord(MGPipeHandle{6, 3}, MGPipeFramebufferTarget::Both, 300));
|
MGPipeApplySetFramebufferState(FramebufferRecord(MGPipeHandle{6, 3}, MGPipeFramebufferTarget::Both, 300));
|
||||||
EXPECT_EQ(MGPipeApplier().DrawFramebuffer.Fbo, (MGPipeHandle{6, 3}));
|
ASSERT_NE(MGPipeApplier().DrawFramebuffer(), nullptr);
|
||||||
EXPECT_EQ(MGPipeApplier().ReadFramebuffer.Fbo, (MGPipeHandle{6, 3}));
|
ASSERT_NE(MGPipeApplier().ReadFramebuffer(), nullptr);
|
||||||
EXPECT_EQ(MGPipeApplier().DrawFramebuffer.Width, 300u);
|
EXPECT_EQ(MGPipeApplier().DrawFramebuffer()->Fbo, (MGPipeHandle{6, 3}));
|
||||||
EXPECT_EQ(MGPipeApplier().ReadFramebuffer.Width, 300u);
|
EXPECT_EQ(MGPipeApplier().ReadFramebuffer()->Fbo, (MGPipeHandle{6, 3}));
|
||||||
|
EXPECT_EQ(MGPipeApplier().DrawFramebuffer()->Width, 300u);
|
||||||
|
EXPECT_EQ(MGPipeApplier().ReadFramebuffer()->Width, 300u);
|
||||||
EXPECT_EQ(MGPipeApplier().FramebufferSerial, beforeBoth + 1)
|
EXPECT_EQ(MGPipeApplier().FramebufferSerial, beforeBoth + 1)
|
||||||
<< "a Both record is ONE record and moves the serial once";
|
<< "a Both record is ONE record and moves the serial once";
|
||||||
|
// The two earlier framebuffers keep their own records - the table is keyed by the handle,
|
||||||
|
// so binding a third displaced neither (ID-19(b)).
|
||||||
|
ASSERT_NE(MGPipeApplier().FramebufferRecordFor(MGPipeHandle{4, 1}), nullptr);
|
||||||
|
EXPECT_EQ(MGPipeApplier().FramebufferRecordFor(MGPipeHandle{4, 1})->Width, 100u);
|
||||||
|
ASSERT_NE(MGPipeApplier().FramebufferRecordFor(MGPipeHandle{5, 2}), nullptr);
|
||||||
|
EXPECT_EQ(MGPipeApplier().FramebufferRecordFor(MGPipeHandle{5, 2})->Width, 200u);
|
||||||
|
|
||||||
// A framebuffer has a handle but NO wire lifetime, so there is no record to refuse against
|
// A framebuffer has a handle but NO wire lifetime, so there is no record to refuse against
|
||||||
// and this entry point never counts an object refusal.
|
// and this entry point never counts an object refusal.
|
||||||
@@ -254,22 +264,25 @@ TEST(FramebufferEmit, ADrawRecordAndAReadRecordAreKeptApartAndBothWritesBoth) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// A target outside the three is not a binding this server has, and guessing one would put a
|
// A target outside the FOUR is not a target this server has, and guessing one would put a
|
||||||
// draw's attachments into the read record or the other way round.
|
// draw's attachments into the read binding or the other way round. Named (3) is legal since
|
||||||
|
// ID-19(b) and has its own case below; the first refused value is the one above it.
|
||||||
TEST(FramebufferEmit, ATargetOutsideTheThreeBindingsIsRefusedNamingTheRecord) {
|
TEST(FramebufferEmit, ATargetOutsideTheThreeBindingsIsRefusedNamingTheRecord) {
|
||||||
#if !MOBILEGL_PIPE_PUSH
|
#if !MOBILEGL_PIPE_PUSH
|
||||||
GTEST_SKIP() << "MOBILEGL_PIPE_PUSH is off: there is no applier in this build";
|
GTEST_SKIP() << "MOBILEGL_PIPE_PUSH is off: there is no applier in this build";
|
||||||
#else
|
#else
|
||||||
ApplierGuard guard;
|
ApplierGuard guard;
|
||||||
MGPFramebufferState bad = FramebufferRecord(MGPipeHandle{7, 4}, MGPipeFramebufferTarget::Draw, 100);
|
MGPFramebufferState bad = FramebufferRecord(MGPipeHandle{7, 4}, MGPipeFramebufferTarget::Draw, 100);
|
||||||
bad.Target = static_cast<Uint8>(MGPipeFramebufferTarget::Count);
|
bad.Target = static_cast<Uint8>(kMGPipeFramebufferTargetNamed + 1);
|
||||||
const Uint64 serialBefore = MGPipeApplier().FramebufferSerial;
|
const Uint64 serialBefore = MGPipeApplier().FramebufferSerial;
|
||||||
|
|
||||||
ExpectRefusedNaming("set_framebuffer_state {slot=7, gen=4, target=3}: the record names no framebuffer "
|
ExpectRefusedNaming("set_framebuffer_state {slot=7, gen=4, target=4}: the record names no framebuffer "
|
||||||
"binding target",
|
"binding target",
|
||||||
[&bad]() { MGPipeApplySetFramebufferState(bad); });
|
[&bad]() { MGPipeApplySetFramebufferState(bad); });
|
||||||
EXPECT_EQ(MGPipeApplier().DrawFramebuffer.Fbo, kMGPipeNullHandle);
|
EXPECT_EQ(MGPipeApplier().DrawFramebuffer(), nullptr);
|
||||||
EXPECT_EQ(MGPipeApplier().ReadFramebuffer.Fbo, kMGPipeNullHandle);
|
EXPECT_EQ(MGPipeApplier().ReadFramebuffer(), nullptr);
|
||||||
|
EXPECT_EQ(MGPipeApplier().FramebufferRecordFor(MGPipeHandle{7, 4}), nullptr)
|
||||||
|
<< "a refused record was written into the per-object table anyway";
|
||||||
EXPECT_EQ(MGPipeApplier().FramebufferSerial, serialBefore)
|
EXPECT_EQ(MGPipeApplier().FramebufferSerial, serialBefore)
|
||||||
<< "a refused record must not move the serial";
|
<< "a refused record must not move the serial";
|
||||||
#endif
|
#endif
|
||||||
@@ -289,7 +302,8 @@ TEST(FramebufferEmit, ADrawBufferEntryOutsideTheRecordsOwnArrayIsRefusedRatherTh
|
|||||||
MGPFramebufferState legal = FramebufferRecord(MGPipeHandle{8, 1}, MGPipeFramebufferTarget::Draw, 100);
|
MGPFramebufferState legal = FramebufferRecord(MGPipeHandle{8, 1}, MGPipeFramebufferTarget::Draw, 100);
|
||||||
legal.DrawBuffers[7] = static_cast<Int8>(kMGPipeMaxColorAttachments - 1);
|
legal.DrawBuffers[7] = static_cast<Int8>(kMGPipeMaxColorAttachments - 1);
|
||||||
MGPipeApplySetFramebufferState(legal);
|
MGPipeApplySetFramebufferState(legal);
|
||||||
ASSERT_EQ(MGPipeApplier().DrawFramebuffer.Fbo, (MGPipeHandle{8, 1}));
|
ASSERT_NE(MGPipeApplier().DrawFramebuffer(), nullptr);
|
||||||
|
ASSERT_EQ(MGPipeApplier().DrawFramebuffer()->Fbo, (MGPipeHandle{8, 1}));
|
||||||
const Uint64 serialBefore = MGPipeApplier().FramebufferSerial;
|
const Uint64 serialBefore = MGPipeApplier().FramebufferSerial;
|
||||||
|
|
||||||
MGPFramebufferState past = FramebufferRecord(MGPipeHandle{8, 1}, MGPipeFramebufferTarget::Draw, 111);
|
MGPFramebufferState past = FramebufferRecord(MGPipeHandle{8, 1}, MGPipeFramebufferTarget::Draw, 111);
|
||||||
@@ -304,15 +318,19 @@ TEST(FramebufferEmit, ADrawBufferEntryOutsideTheRecordsOwnArrayIsRefusedRatherTh
|
|||||||
"colour attachment outside the record's own array",
|
"colour attachment outside the record's own array",
|
||||||
[&negative]() { MGPipeApplySetFramebufferState(negative); });
|
[&negative]() { MGPipeApplySetFramebufferState(negative); });
|
||||||
|
|
||||||
EXPECT_EQ(MGPipeApplier().DrawFramebuffer.Width, 100u) << "a refused record was stored anyway";
|
ASSERT_NE(MGPipeApplier().DrawFramebuffer(), nullptr);
|
||||||
|
EXPECT_EQ(MGPipeApplier().DrawFramebuffer()->Width, 100u) << "a refused record was stored anyway";
|
||||||
EXPECT_EQ(MGPipeApplier().FramebufferSerial, serialBefore);
|
EXPECT_EQ(MGPipeApplier().FramebufferSerial, serialBefore);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// D-J4. The two framebuffer records are per-context WORKING state and a make-current takes
|
// D-J4, as ID-19(b) leaves it. The two framebuffer BINDINGS are per-context working state and a
|
||||||
// them - but their serial ADVANCES rather than restarting, because a counter that walks back
|
// make-current takes them - so both accessors answer null afterwards, exactly as the zeroed
|
||||||
// through values it has already stamped into a twin that outlived the switch is not a
|
// records used to answer a null Fbo - while the per-object RECORD survives, because a
|
||||||
// generation at all. Restoring `= 0` anywhere in the reset leaves this red.
|
// framebuffer that is only ever addressed BY NAME has no re-emission trigger at all. The serial
|
||||||
|
// ADVANCES rather than restarting, because a counter that walks back through values it has
|
||||||
|
// already stamped into a twin that outlived the switch is not a generation at all. Restoring
|
||||||
|
// `= 0` anywhere in the reset, or clearing the table there, leaves this red.
|
||||||
TEST(FramebufferEmit, AMakeCurrentClearsBothRecordsAndAdvancesTheSerialRatherThanZeroingIt) {
|
TEST(FramebufferEmit, AMakeCurrentClearsBothRecordsAndAdvancesTheSerialRatherThanZeroingIt) {
|
||||||
#if !MOBILEGL_PIPE_PUSH
|
#if !MOBILEGL_PIPE_PUSH
|
||||||
GTEST_SKIP() << "MOBILEGL_PIPE_PUSH is off: there is no applier in this build";
|
GTEST_SKIP() << "MOBILEGL_PIPE_PUSH is off: there is no applier in this build";
|
||||||
@@ -320,13 +338,18 @@ TEST(FramebufferEmit, AMakeCurrentClearsBothRecordsAndAdvancesTheSerialRatherTha
|
|||||||
ApplierGuard guard;
|
ApplierGuard guard;
|
||||||
MGPipeApplySetFramebufferState(FramebufferRecord(MGPipeHandle{4, 1}, MGPipeFramebufferTarget::Both, 100));
|
MGPipeApplySetFramebufferState(FramebufferRecord(MGPipeHandle{4, 1}, MGPipeFramebufferTarget::Both, 100));
|
||||||
const Uint64 serialBefore = MGPipeApplier().FramebufferSerial;
|
const Uint64 serialBefore = MGPipeApplier().FramebufferSerial;
|
||||||
ASSERT_EQ(MGPipeApplier().DrawFramebuffer.Width, 100u);
|
ASSERT_NE(MGPipeApplier().DrawFramebuffer(), nullptr);
|
||||||
|
ASSERT_EQ(MGPipeApplier().DrawFramebuffer()->Width, 100u);
|
||||||
|
|
||||||
MGPipeApplierReset(); // the make-current
|
MGPipeApplierReset(); // the make-current
|
||||||
|
|
||||||
EXPECT_EQ(MGPipeApplier().DrawFramebuffer.Fbo, kMGPipeNullHandle);
|
EXPECT_EQ(MGPipeApplier().DrawFramebuffer(), nullptr);
|
||||||
EXPECT_EQ(MGPipeApplier().ReadFramebuffer.Fbo, kMGPipeNullHandle);
|
EXPECT_EQ(MGPipeApplier().ReadFramebuffer(), nullptr);
|
||||||
EXPECT_EQ(MGPipeApplier().DrawFramebuffer.Width, 0u);
|
EXPECT_EQ(MGPipeApplier().BoundFramebuffer[0], kMGPipeNullHandle);
|
||||||
|
EXPECT_EQ(MGPipeApplier().BoundFramebuffer[1], kMGPipeNullHandle);
|
||||||
|
ASSERT_NE(MGPipeApplier().FramebufferRecordFor(MGPipeHandle{4, 1}), nullptr)
|
||||||
|
<< "the per-object record is not working state and a make-current may not take it";
|
||||||
|
EXPECT_EQ(MGPipeApplier().FramebufferRecordFor(MGPipeHandle{4, 1})->Width, 100u);
|
||||||
EXPECT_GT(MGPipeApplier().FramebufferSerial, serialBefore)
|
EXPECT_GT(MGPipeApplier().FramebufferSerial, serialBefore)
|
||||||
<< "the serial was carried over or restarted; the cleared window is itself a change the "
|
<< "the serial was carried over or restarted; the cleared window is itself a change the "
|
||||||
"twin has to hear about, and no stamped value may ever recur";
|
"twin has to hear about, and no stamped value may ever recur";
|
||||||
@@ -369,7 +392,8 @@ TEST(FramebufferEmit, AReleaseOfTheObjectRecordsAlsoClearsTheWorkingHandlesThatC
|
|||||||
image.InternalFormat = 0x8058u; // GL_RGBA8
|
image.InternalFormat = 0x8058u; // GL_RGBA8
|
||||||
MGPipeApplySetShaderImages(MGPShaderImages{2, 1, 0xCCCCu}, &image);
|
MGPipeApplySetShaderImages(MGPShaderImages{2, 1, 0xCCCCu}, &image);
|
||||||
|
|
||||||
ASSERT_EQ(MGPipeApplier().DrawFramebuffer.Color[0].Res, (MGPipeHandle{9, 1}));
|
ASSERT_NE(MGPipeApplier().DrawFramebuffer(), nullptr);
|
||||||
|
ASSERT_EQ(MGPipeApplier().DrawFramebuffer()->Color[0].Res, (MGPipeHandle{9, 1}));
|
||||||
ASSERT_EQ(MGPipeApplier().SamplerViewCount, 1u);
|
ASSERT_EQ(MGPipeApplier().SamplerViewCount, 1u);
|
||||||
ASSERT_EQ(MGPipeApplier().BoundSamplerViews[2].View, (MGPipeHandle{3, 1}));
|
ASSERT_EQ(MGPipeApplier().BoundSamplerViews[2].View, (MGPipeHandle{3, 1}));
|
||||||
ASSERT_EQ(MGPipeApplier().SamplerStateCount, 1u);
|
ASSERT_EQ(MGPipeApplier().SamplerStateCount, 1u);
|
||||||
@@ -379,10 +403,12 @@ TEST(FramebufferEmit, AReleaseOfTheObjectRecordsAlsoClearsTheWorkingHandlesThatC
|
|||||||
|
|
||||||
MGPipeApplierReleaseObjectRecords();
|
MGPipeApplierReleaseObjectRecords();
|
||||||
|
|
||||||
EXPECT_EQ(MGPipeApplier().DrawFramebuffer.Fbo, kMGPipeNullHandle);
|
EXPECT_EQ(MGPipeApplier().DrawFramebuffer(), nullptr);
|
||||||
EXPECT_EQ(MGPipeApplier().ReadFramebuffer.Fbo, kMGPipeNullHandle);
|
EXPECT_EQ(MGPipeApplier().ReadFramebuffer(), nullptr);
|
||||||
EXPECT_EQ(MGPipeApplier().DrawFramebuffer.Color[0].Res, kMGPipeNullHandle)
|
EXPECT_EQ(MGPipeApplier().FramebufferRecordFor(MGPipeHandle{4, 1}), nullptr)
|
||||||
<< "a surface handle into an emptied texture table survived the teardown";
|
<< "a framebuffer record holding eleven MGPSurface::Res into the emptied texture and "
|
||||||
|
"renderbuffer tables survived the teardown";
|
||||||
|
EXPECT_TRUE(MGPipeApplier().FramebufferRecords.empty());
|
||||||
EXPECT_EQ(MGPipeApplier().SamplerViewStart, 0u);
|
EXPECT_EQ(MGPipeApplier().SamplerViewStart, 0u);
|
||||||
EXPECT_EQ(MGPipeApplier().SamplerViewCount, 0u);
|
EXPECT_EQ(MGPipeApplier().SamplerViewCount, 0u);
|
||||||
EXPECT_EQ(MGPipeApplier().BoundSamplerViews[2].View, kMGPipeNullHandle);
|
EXPECT_EQ(MGPipeApplier().BoundSamplerViews[2].View, kMGPipeNullHandle);
|
||||||
|
|||||||
Reference in New Issue
Block a user