mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 12:48:32 +09:00
[Fix] (Pipe, State): take the texture and framebuffer families through the contract own birth hooks, wire the texture subsystem bit, hand the applier the region tail it was promised and clear a level dirty flag only where the record was accepted
This commit is contained in:
@@ -64,22 +64,23 @@ namespace MobileGL::MG_Pipe {
|
||||
// D-C1: the MGPSurface builder, one pure function, one statement per field
|
||||
// ---------------------------------------------------------------------------------
|
||||
|
||||
// MGPSurface::Kind. MGPipeKind is REUSED rather than a second three-value enum minted
|
||||
// beside it: it already spells Texture and Renderbuffer, its None is 0, and a
|
||||
// zero-initialised MGPSurface therefore already IS the empty attachment point the contract
|
||||
// describes ({Res = kMGPipeNullHandle, Kind = None} and every other field zero). If the
|
||||
// contract package later wants a dedicated enumeration beside MGPSurface it is a rename,
|
||||
// not a re-encoding.
|
||||
inline constexpr Uint8 kMGPipeSurfaceKindNone = static_cast<Uint8>(MGPipeKind::None);
|
||||
inline constexpr Uint8 kMGPipeSurfaceKindTexture = static_cast<Uint8>(MGPipeKind::Texture);
|
||||
inline constexpr Uint8 kMGPipeSurfaceKindRenderbuffer = static_cast<Uint8>(MGPipeKind::Renderbuffer);
|
||||
static_assert(kMGPipeSurfaceKindNone == 0,
|
||||
"a zero-initialised MGPSurface must already be the empty attachment point");
|
||||
// MGPSurface::Kind's three constants ARE THE CONTRACT'S (ID-12 DV-4, c0c):
|
||||
// kMGPipeSurfaceKindNone / ...Texture / ...Renderbuffer live in MG_Pipe/MGPipeTypes.h under
|
||||
// exactly these names with the same MGPipeKind derivation and the same static_assert. This
|
||||
// package's copies were a redefinition in the same namespace and are deleted.
|
||||
|
||||
// The upload target an attachment names, RESOLVED: an attachment made through an entry
|
||||
// point that carries no face token stores TextureUploadTarget::Unknown, and the record goes
|
||||
// out fully resolved - nothing in it may require a lookup on the far side. This is the same
|
||||
// fallback FramebufferAttachmentObject::GetSize already applies to answer its own question.
|
||||
// out fully resolved - nothing in it may require a lookup on the far side.
|
||||
//
|
||||
// THE FALLBACK IS ONLY LEGAL FOR A SINGLE-TARGET TEXTURE (m1), and v1's was not. The
|
||||
// precedent it copied - FramebufferAttachmentObject::GetSize - needs an EXTENT, which is
|
||||
// identical across a cube map's six faces; face IDENTITY is not, so
|
||||
// `glFramebufferTexture(GL_COLOR_ATTACHMENT0, cube, 0)` resolved to targets[0] and the
|
||||
// record ASSERTED CubeMapPositiveX for a layered attachment that names all six. A texture
|
||||
// with exactly one upload target has a [0] that IS the truth; anything else keeps Unknown,
|
||||
// which is the value the field already carries for "this attachment names no single face"
|
||||
// and which Layered = 1 tells the reader to ignore.
|
||||
inline MobileGL::TextureUploadTarget MGPipeResolveAttachmentUploadTarget(
|
||||
const MG_State::GLState::FramebufferAttachmentObject& attachment) {
|
||||
MobileGL::TextureUploadTarget resolved = attachment.GetTextureUploadTarget();
|
||||
@@ -87,7 +88,7 @@ namespace MobileGL::MG_Pipe {
|
||||
const auto& texture = attachment.GetTexture();
|
||||
if (!texture) return MobileGL::TextureUploadTarget::Unknown;
|
||||
const auto& targets = texture->GetUploadTargets();
|
||||
return targets.empty() ? MobileGL::TextureUploadTarget::Unknown : targets[0];
|
||||
return targets.size() == 1 ? targets[0] : MobileGL::TextureUploadTarget::Unknown;
|
||||
}
|
||||
|
||||
// ONE PURE FUNCTION, ONE STATEMENT PER FIELD, and that shape is a gate requirement rather
|
||||
@@ -98,9 +99,22 @@ namespace MobileGL::MG_Pipe {
|
||||
// `res` is handed in because resolving it needs the slot allocator and this function stays
|
||||
// pure; `internalFormat` is INLINE in the record on purpose, so the four cross-object masks
|
||||
// fall out at push time with no lookup on the far side.
|
||||
// THE EMPTY POINT IS THE ZERO-INITIALISED RECORD EXCEPT FOR ITS TWO TARGET FIELDS. Both
|
||||
// are Uint16 enumerations whose zero is a REAL value - TextureTarget::Texture1D and
|
||||
// TextureUploadTarget::Texture1D - so a reader that forgot to gate on Kind would read a
|
||||
// plausible wrong answer rather than a nonsense one. Unknown (0xFFFF) is what the contract
|
||||
// spells for TextureTarget (kMGPipeSurfaceNoTextureTarget) and m6 applies the same rule to
|
||||
// UploadTarget, which shares the collision ID-12 DV-3 ruled on for MGPSubData::Target.
|
||||
inline MGPSurface MGPipeEmptySurface() {
|
||||
MGPSurface surface{};
|
||||
surface.UploadTarget = static_cast<Uint16>(MobileGL::TextureUploadTarget::Unknown);
|
||||
surface.TextureTarget = kMGPipeSurfaceNoTextureTarget;
|
||||
return surface;
|
||||
}
|
||||
|
||||
inline MGPSurface MGPipeBuildSurface(const MG_State::GLState::FramebufferAttachmentObject& attachment,
|
||||
MGPipeHandle res) {
|
||||
MGPSurface surface{};
|
||||
MGPSurface surface = MGPipeEmptySurface();
|
||||
if (attachment.IsEmpty()) return surface;
|
||||
surface.Res = res;
|
||||
if (attachment.IsTexture()) {
|
||||
@@ -111,6 +125,13 @@ namespace MobileGL::MG_Pipe {
|
||||
surface.Level = static_cast<Uint16>(std::max<Int>(attachment.GetTextureLevel(), 0));
|
||||
surface.Layer = static_cast<Uint32>(std::max<Int>(attachment.GetTextureLayer(), 0));
|
||||
surface.UploadTarget = static_cast<Uint16>(MGPipeResolveAttachmentUploadTarget(attachment));
|
||||
// ID-12 DV-5: the field that WAS Pad0, and the size did not move. The four
|
||||
// cross-object masks all reduce to (format, TEXTURE TARGET) -
|
||||
// ShouldUseCaveatTextureFormat / BackendTextureFormatAddsAlpha - and no
|
||||
// TextureUploadTarget -> TextureTarget inverse exists anywhere in the tree, so
|
||||
// without this the inline InternalFormat cannot make them fall out at push time and
|
||||
// the backend keeps reading the frontend attachment objects.
|
||||
surface.TextureTarget = static_cast<Uint16>(texture->GetTarget());
|
||||
return surface;
|
||||
}
|
||||
const auto& renderbuffer = attachment.GetRenderbuffer();
|
||||
@@ -119,7 +140,6 @@ namespace MobileGL::MG_Pipe {
|
||||
surface.Layered = 0;
|
||||
surface.Level = 0;
|
||||
surface.Layer = 0;
|
||||
surface.UploadTarget = 0;
|
||||
return surface;
|
||||
}
|
||||
|
||||
@@ -142,6 +162,22 @@ namespace MobileGL::MG_Pipe {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// m2: A DRAW-BUFFER TOKEN CAN NAME A COLOUR POINT THE RECORD CANNOT CARRY, and D-C3's
|
||||
// refusal loop only ever scanned ATTACHMENTS. `glDrawBuffers(1, {GL_COLOR_ATTACHMENT10})`
|
||||
// with nothing attached at 10 is legal state - draw-incomplete, but legal - and the index
|
||||
// above would have written 10 into a record whose Color[] is 8 wide, so the server would
|
||||
// index out of its own storage or invent a bound the record does not carry. Truncating
|
||||
// silently is the bug class this phase is closing, so the record is refused exactly as an
|
||||
// over-wide attachment is.
|
||||
inline Bool MGPipeDrawBufferIsInsideTheWireWidth(MobileGL::FramebufferAttachmentType buffer) {
|
||||
using MobileGL::FramebufferAttachmentType;
|
||||
if (buffer < FramebufferAttachmentType::Color0 || buffer > FramebufferAttachmentType::ColorMax) {
|
||||
return true; // None and the four default-framebuffer tokens; neither indexes Color[]
|
||||
}
|
||||
return static_cast<Int>(buffer) - static_cast<Int>(FramebufferAttachmentType::Color0) <
|
||||
static_cast<Int>(kMGPipeMaxColorAttachments);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// D-C4: ContentHash, and the one input it must not swallow
|
||||
// ---------------------------------------------------------------------------------
|
||||
@@ -169,6 +205,11 @@ namespace MobileGL::MG_Pipe {
|
||||
dst.Level = src.Level;
|
||||
dst.Layer = src.Layer;
|
||||
dst.UploadTarget = src.UploadTarget;
|
||||
// MANDATORY, not optional: TextureTarget is a PipeFields.def row now, so a
|
||||
// field-wise copy that skipped it would suppress a record whose only moved field is
|
||||
// the attachment's texture target - and that field decides three of the four
|
||||
// cross-object masks.
|
||||
dst.TextureTarget = src.TextureTarget;
|
||||
}
|
||||
|
||||
inline Uint64 MGPipeFramebufferStateContentHash(const MGPFramebufferState& state) {
|
||||
@@ -234,14 +275,13 @@ namespace MobileGL::MG_Pipe {
|
||||
Bool drawOk = false;
|
||||
Bool readOk = false;
|
||||
if (shared) {
|
||||
drawOk = BuildFramebufferState(*drawFbo, *drawFbo, MGPipeFramebufferTarget::Both, drawState);
|
||||
drawOk = BuildFramebufferState(*drawFbo, MGPipeFramebufferTarget::Both, drawState);
|
||||
} else {
|
||||
if (drawFbo) {
|
||||
drawOk = BuildFramebufferState(*drawFbo, readFbo ? *readFbo : *drawFbo,
|
||||
MGPipeFramebufferTarget::Draw, drawState);
|
||||
drawOk = BuildFramebufferState(*drawFbo, MGPipeFramebufferTarget::Draw, drawState);
|
||||
}
|
||||
if (readFbo) {
|
||||
readOk = BuildFramebufferState(*readFbo, *readFbo, MGPipeFramebufferTarget::Read, readState);
|
||||
readOk = BuildFramebufferState(*readFbo, MGPipeFramebufferTarget::Read, readState);
|
||||
}
|
||||
}
|
||||
if (!drawOk && !readOk) return 0;
|
||||
@@ -279,10 +319,79 @@ namespace MobileGL::MG_Pipe {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// ID-19(c): EVERY DSA ENTRY POINT THAT HANDS A FRAMEBUFFER TO THE SERVER BY NAME IS
|
||||
// PRECEDED BY A RECORD FOR IT, and that is the phase's main correction rather than a
|
||||
// nicety. With only the two BOUND-target records, glClearNamedFramebufferfv(fbo) on an
|
||||
// unbound fbo made the backend mint a fresh driver framebuffer with NO ATTACHMENTS,
|
||||
// find no record for it, decline, and issue the clear against it anyway -
|
||||
// GL_INVALID_FRAMEBUFFER_OPERATION and nothing cleared, where the legacy arm cleared
|
||||
// correctly (esprytobj C-1).
|
||||
//
|
||||
// THE TARGET IS Named ONLY WHEN THE OBJECT IS BOUND TO NEITHER BINDING. A record always
|
||||
// writes FramebufferRecords[Fbo.Slot]; Draw/Read/Both ADDITIONALLY set the bound
|
||||
// handle(s). So handing a currently-bound framebuffer a Named record would overwrite
|
||||
// the bound record's Target with one that says "no binding" while BoundFramebuffer
|
||||
// still names it, and the server would read a record whose Target contradicts the
|
||||
// binding it is resolved through. Re-asserting the binding the object already has is
|
||||
// free (the content hash suppresses it) and keeps the two consistent.
|
||||
//
|
||||
// Returns the bytes that went on the wire.
|
||||
Uint64 EmitFramebufferByName(const FramebufferObject& fbo) {
|
||||
if (!MGPipeFramebufferSubsystemEnabled()) return 0;
|
||||
MGPipeFramebufferTarget target = MGPipeFramebufferTarget::Named;
|
||||
const Bool boundToDraw = IsBoundTo(fbo, MobileGL::FramebufferTarget::Draw);
|
||||
const Bool boundToRead = IsBoundTo(fbo, MobileGL::FramebufferTarget::Read);
|
||||
if (boundToDraw && boundToRead) {
|
||||
target = MGPipeFramebufferTarget::Both;
|
||||
} else if (boundToDraw) {
|
||||
target = MGPipeFramebufferTarget::Draw;
|
||||
} else if (boundToRead) {
|
||||
target = MGPipeFramebufferTarget::Read;
|
||||
}
|
||||
|
||||
MGPFramebufferState state{};
|
||||
if (!BuildFramebufferState(fbo, target, state)) return 0;
|
||||
|
||||
// THE SUPPRESSOR IS KEYED BY THE FRAMEBUFFER THE RECORD NAMES, never by one global
|
||||
// slot (MGPipeTypes.h states the rule): two different objects' Named records in a
|
||||
// row must both go out, and a Named record must never be suppressed against the
|
||||
// same object's bound record or the reverse. Target is a ContentHash input, so the
|
||||
// second half holds by construction; the per-object table is what buys the first.
|
||||
// The two BOUND latches stay what they are - "does the server's draw/read binding
|
||||
// already hold this record" - and a bound-target emission from here consults them,
|
||||
// because a rebind of an unchanged object must still move the binding.
|
||||
if (target == MGPipeFramebufferTarget::Named) {
|
||||
NamedEntry& entry = NamedEntryFor(state.Fbo);
|
||||
if (entry.Has && entry.Gen == state.Fbo.Gen && entry.LastHash == state.ContentHash) {
|
||||
return 0;
|
||||
}
|
||||
const Uint64 bytes = Emit(state);
|
||||
entry.Has = true;
|
||||
entry.Gen = state.Fbo.Gen;
|
||||
entry.LastHash = state.ContentHash;
|
||||
return bytes;
|
||||
}
|
||||
if (target == MGPipeFramebufferTarget::Both) {
|
||||
if (state.ContentHash == m_lastEmitted[kDraw] && state.ContentHash == m_lastEmitted[kRead]) {
|
||||
return 0;
|
||||
}
|
||||
const Uint64 bytes = Emit(state);
|
||||
m_lastEmitted[kDraw] = state.ContentHash;
|
||||
m_lastEmitted[kRead] = state.ContentHash;
|
||||
return bytes;
|
||||
}
|
||||
const SizeT slot = target == MGPipeFramebufferTarget::Read ? kRead : kDraw;
|
||||
if (state.ContentHash == m_lastEmitted[slot]) return 0;
|
||||
const Uint64 bytes = Emit(state);
|
||||
m_lastEmitted[slot] = state.ContentHash;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// ---- what a unit case reads. The emitter builds INTO these and hands the applier the
|
||||
// same objects, so "what was emitted" costs no copy. ----
|
||||
const MGPFramebufferState& LastDraw() const { return m_lastDraw; }
|
||||
const MGPFramebufferState& LastRead() const { return m_lastRead; }
|
||||
const MGPFramebufferState& LastNamed() const { return m_lastNamed; }
|
||||
Uint64 EmissionCount() const { return m_emissions; }
|
||||
Uint64 RefusedCount() const { return m_refusals; }
|
||||
|
||||
@@ -295,6 +404,13 @@ namespace MobileGL::MG_Pipe {
|
||||
void Reset() {
|
||||
m_lastEmitted[kDraw] = 0;
|
||||
m_lastEmitted[kRead] = 0;
|
||||
// The per-object latch goes too, and the safe direction is why: MGPipeApplierReset
|
||||
// keeps FramebufferRecords standing (they are object state, ID-19(b)) but
|
||||
// ReleaseObjectRecords clears the whole table, and this emitter cannot tell the two
|
||||
// scopes apart from here. Keeping a latch across a table that may have been dropped
|
||||
// would suppress the one record that had to go out; dropping it costs one extra
|
||||
// 304-byte record per named framebuffer after a context switch.
|
||||
m_named.clear();
|
||||
}
|
||||
|
||||
void ResetCounters() { m_emissions = m_refusals = 0; }
|
||||
@@ -304,6 +420,7 @@ namespace MobileGL::MG_Pipe {
|
||||
ResetCounters();
|
||||
m_lastDraw = MGPFramebufferState{};
|
||||
m_lastRead = MGPFramebufferState{};
|
||||
m_lastNamed = MGPFramebufferState{};
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -311,7 +428,9 @@ namespace MobileGL::MG_Pipe {
|
||||
static constexpr SizeT kRead = 1;
|
||||
|
||||
Uint64 Emit(const MGPFramebufferState& state) {
|
||||
if (state.Target == static_cast<Uint8>(MGPipeFramebufferTarget::Read)) {
|
||||
if (state.Target == static_cast<Uint8>(MGPipeFramebufferTarget::Named)) {
|
||||
m_lastNamed = state;
|
||||
} else if (state.Target == static_cast<Uint8>(MGPipeFramebufferTarget::Read)) {
|
||||
m_lastRead = state;
|
||||
} else {
|
||||
m_lastDraw = state;
|
||||
@@ -325,16 +444,20 @@ namespace MobileGL::MG_Pipe {
|
||||
return sizeof(MGPFramebufferState);
|
||||
}
|
||||
|
||||
// `fbo` is the object this record describes; `readFbo` is the object whose OWN read
|
||||
// buffer resolves ReadSurface, and for a Draw record that is the read-bound object
|
||||
// rather than this one.
|
||||
// ONE RECORD DESCRIBES ONE FRAMEBUFFER OBJECT - the one named by `fbo` - and every
|
||||
// field in it is a property of THAT object. Target is the only binding-specific one.
|
||||
//
|
||||
// THE RESOLVED READ SURFACE IS WHAT STRUCTURALLY CLOSES THE read-buffer-shared-FBO
|
||||
// DEFECT CLASS: the record carries the surface, not an index, and it is resolved from
|
||||
// the READ framebuffer's own read buffer, so the "same FBO as draw" skip that used to
|
||||
// lose it cannot be expressed.
|
||||
Bool BuildFramebufferState(const FramebufferObject& fbo, const FramebufferObject& readFbo,
|
||||
MGPipeFramebufferTarget target, MGPFramebufferState& out) {
|
||||
// ReadSurface IS RESOLVED FROM THIS FRAMEBUFFER'S OWN READ BUFFER UNDER EVERY TARGET,
|
||||
// Named included (c0e / MGPipeTypes.h). v1 resolved a Draw record's ReadSurface from
|
||||
// the READ-bound object, which was D-C2's letter and muddled in substance: the record
|
||||
// then described a surface that is not part of the framebuffer its own Fbo names, and a
|
||||
// glReadBuffer on the read FBO moved the DRAW record's ContentHash and forced a
|
||||
// redundant draw emission. Resolving it per object is what makes the
|
||||
// read-buffer-shared-FBO defect class unrepresentable rather than merely fixed - the
|
||||
// record carries a surface, not an index, and no field of it refers to "whatever is
|
||||
// bound".
|
||||
Bool BuildFramebufferState(const FramebufferObject& fbo, MGPipeFramebufferTarget target,
|
||||
MGPFramebufferState& out) {
|
||||
// D-C3, THE CLIENT HALF OF THE BRING-UP REFUSAL. The wire array is 8 wide and
|
||||
// GetDynamicParameters().MaxColorAttachments is the driver's raw ES cap, not
|
||||
// clamped to 8 on the GLES path. An attachment point at or above the wire width
|
||||
@@ -356,6 +479,25 @@ namespace MobileGL::MG_Pipe {
|
||||
return false;
|
||||
}
|
||||
|
||||
// m2, THE SAME REFUSAL ONE FIELD OVER. A draw-buffer token may name a colour point
|
||||
// at or above the wire width with nothing attached there, which the loop above
|
||||
// cannot see; MGPipeDrawBufferIndex would then write 8..31 into an 8-wide array.
|
||||
{
|
||||
const auto& tokens = fbo.GetDrawBuffers();
|
||||
for (SizeT i = 0; i < kMGPipeMaxColorAttachments; ++i) {
|
||||
if (MGPipeDrawBufferIsInsideTheWireWidth(tokens[i])) continue;
|
||||
MGLOG_E_ONCE("MGPipe: framebuffer %u names colour point %d in draw buffer %u, which is "
|
||||
"at or above the wire width of %u - set_framebuffer_state is refused "
|
||||
"rather than truncated and the legacy arm runs",
|
||||
fbo.GetExternalIndex(),
|
||||
static_cast<Int>(tokens[i]) -
|
||||
static_cast<Int>(FramebufferAttachmentType::Color0),
|
||||
static_cast<Uint>(i), static_cast<Uint>(kMGPipeMaxColorAttachments));
|
||||
++m_refusals;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
out = MGPFramebufferState{};
|
||||
out.Fbo = HandleFor(fbo);
|
||||
out.Target = static_cast<Uint8>(target);
|
||||
@@ -376,7 +518,7 @@ namespace MobileGL::MG_Pipe {
|
||||
}
|
||||
out.Depth = SurfaceOf(fbo, FramebufferAttachmentType::Depth);
|
||||
out.Stencil = SurfaceOf(fbo, FramebufferAttachmentType::Stencil);
|
||||
out.ReadSurface = SurfaceOf(readFbo, readFbo.GetReadBuffer());
|
||||
out.ReadSurface = SurfaceOf(fbo, fbo.GetReadBuffer());
|
||||
|
||||
const auto& drawBuffers = fbo.GetDrawBuffers();
|
||||
for (SizeT i = 0; i < kMGPipeMaxColorAttachments; ++i) {
|
||||
@@ -395,12 +537,30 @@ namespace MobileGL::MG_Pipe {
|
||||
return true;
|
||||
}
|
||||
|
||||
static Bool IsBoundTo(const FramebufferObject& fbo, MobileGL::FramebufferTarget target) {
|
||||
if (MG_State::pGLContext == nullptr) return false;
|
||||
const auto& bound = MG_State::pGLContext->GetFramebufferBindingSlot(target).GetBoundObject();
|
||||
return bound && bound.get() == &fbo;
|
||||
}
|
||||
|
||||
struct NamedEntry {
|
||||
Uint32 Gen = 0;
|
||||
Uint64 LastHash = 0;
|
||||
Bool Has = false;
|
||||
};
|
||||
|
||||
NamedEntry& NamedEntryFor(MGPipeHandle fbo) {
|
||||
const SizeT slot = fbo.Slot;
|
||||
if (slot >= m_named.size()) m_named.resize(slot + 1);
|
||||
return m_named[slot];
|
||||
}
|
||||
|
||||
MGPSurface SurfaceOf(const FramebufferObject& fbo, FramebufferAttachmentType type) {
|
||||
if (type == FramebufferAttachmentType::None || type == FramebufferAttachmentType::Unknown) {
|
||||
return MGPSurface{};
|
||||
return MGPipeEmptySurface();
|
||||
}
|
||||
const auto& attachment = fbo.GetAttachment(type);
|
||||
if (attachment.IsEmpty()) return MGPSurface{};
|
||||
if (attachment.IsEmpty()) return MGPipeEmptySurface();
|
||||
MGPipeTextureEmitter& textures = MGPipeTextureEmitterInstance();
|
||||
// D-A4's two producers: an attachment point is what sets RENDER_TARGET and
|
||||
// DEPTH_STENCIL, the two sticky bind bits nothing set before P4a. Sticky and ORed,
|
||||
@@ -457,8 +617,13 @@ namespace MobileGL::MG_Pipe {
|
||||
}
|
||||
|
||||
Array<Uint64, 2> m_lastEmitted{};
|
||||
// The per-FRAMEBUFFER suppressor for Named records, slot-indexed with the generation
|
||||
// checked, exactly as the applier's own table is. A framebuffer has no wire lifetime
|
||||
// (D-I2), so a successor simply overwrites its predecessor's entry.
|
||||
Vector<NamedEntry> m_named;
|
||||
MGPFramebufferState m_lastDraw{};
|
||||
MGPFramebufferState m_lastRead{};
|
||||
MGPFramebufferState m_lastNamed{};
|
||||
Uint64 m_emissions = 0;
|
||||
Uint64 m_refusals = 0;
|
||||
};
|
||||
|
||||
+373
-299
@@ -27,28 +27,33 @@
|
||||
// HEADER-ONLY, for the ownership reason Tracker.h and ResourceTracker.h both state.
|
||||
//
|
||||
// ---------------------------------------------------------------------------------------
|
||||
// HOW MG_State REACHES THIS FILE, and it is a DEVIATION worth reading before the code.
|
||||
// HOW MG_State REACHES THIS FILE: IT DOES NOT, AND THAT IS THE POINT (c0b, ID-13).
|
||||
//
|
||||
// P3a's buffer family declares its emission points in MG_Pipe/PipeMutation.h and defines them
|
||||
// in MG_Impl/Pipe/PipeFill.cpp, so BufferObject.cpp sees a declaration and never the client's
|
||||
// tracker. P4a cannot copy that shape: BOTH of those files belong to the contract package for
|
||||
// the whole phase (no file is touched twice by two packages), and they carry no texture
|
||||
// emission declaration. The next-best arrangement, and the one used here, keeps the SAME
|
||||
// property one level in:
|
||||
// v1 of this package shipped a deviation - six free functions here, called from
|
||||
// TextureObject.cpp and RenderbufferObject.cpp - because at the contract TAG
|
||||
// MG_Pipe/PipeMutation.h declared only the six DEATH helpers and this package may not edit
|
||||
// A's files. c0b landed the BIRTH half, so the deviation is retired rather than carried:
|
||||
// MG_State now calls MGPipeMintTextureHandle / MGPipeEmitTextureResourceCreate /
|
||||
// ...ResourceRespecify / MGPipeEmitTextureParams / MGPipeNoteTextureLevelDirty and the two
|
||||
// renderbuffer twins, all DECLARED in MG_Pipe/PipeMutation.h and DEFINED in
|
||||
// MG_Impl/Pipe/PipeFill.cpp, which forwards to the entry points below through
|
||||
// ForwardWhenWired<kMGPipeWiredTextureSubsystem>. No MG_State translation unit includes this
|
||||
// header any more, which is the property check_include_closure.py's mutation-header probe
|
||||
// exists to keep.
|
||||
//
|
||||
// * every texture emission point is a protected member of TextureObjectBase
|
||||
// (TextureState/TextureObject.h, guarded by MOBILEGL_PIPE_PUSH, non-virtual, so the pull
|
||||
// build's object layout and vtable are untouched) DECLARED there and DEFINED in
|
||||
// TextureState/TextureObject.cpp - the ONE MG_State translation unit that includes this
|
||||
// header. Every other texture .cpp - the cube's, the view's, the buffer texture's - calls
|
||||
// the inherited helper and still sees only a declaration.
|
||||
// * the renderbuffer half has no base class to hang helpers on and exactly one .cpp, so
|
||||
// RenderbufferState/RenderbufferObject.cpp is the second and last such translation unit.
|
||||
// WHAT THIS FILE OWES THAT SEAM, and a mismatch is a compile error in this package's own
|
||||
// commit rather than a surprise at the merge (that is what the wired constant buys):
|
||||
//
|
||||
// So the client's tracker reaches exactly two MG_State translation units instead of zero. The
|
||||
// integrator can fold these six free functions into PipeMutation.h and PipeFill.cpp in one
|
||||
// mechanical commit once the phase's file ownership relaxes; nothing else has to move.
|
||||
// EmitResourceCreate(ITextureObject&) EmitResourceRespecify(ITextureObject&)
|
||||
// EmitTextureParams(ITextureObject&) NoteLevelDirty(ITextureObject&, Uint32, Uint32)
|
||||
// EmitRenderbufferCreate(RenderbufferObject&)
|
||||
// EmitRenderbufferRespecify(RenderbufferObject&)
|
||||
//
|
||||
// and the PUBLICATION LATCH is A's too: MGPipeNoteHandlePublished is called where a create
|
||||
// actually goes out and MGPipeHandleIsPublished is what the death helpers read, so this file
|
||||
// keeps no Published flag of its own.
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
#include <MG_Impl/Pipe/SamplerEmit.h>
|
||||
#include <MG_Impl/Pipe/SlotAllocator.h>
|
||||
#include <MG_Pipe/MGPipe.h>
|
||||
#include <MG_Pipe/PipeApply.h>
|
||||
@@ -72,38 +77,37 @@ namespace MobileGL::MG_Pipe {
|
||||
// emitters their bodies, with no file touched twice - and a Coverage.def row can never
|
||||
// silently drop a field on the floor before the call that carries it exists.
|
||||
//
|
||||
// IT IS STILL 0, AND THAT IS A BLOCKED FLIP RATHER THAN AN UNFINISHED ONE. Unlike the other
|
||||
// three P4a families, this one does not get four fresh apply entry points: the catalogue is
|
||||
// closed and a texture rides P3a's OWN resource_create / resource_respecify /
|
||||
// resource_subdata / resource_destroy rows. On a base without the wire package's `w1` those
|
||||
// four have P3a's BUFFER bodies, and two of their properties make a texture record actively
|
||||
// harmful rather than merely ignored:
|
||||
// IT WAS 0 IN v1, AND THE THING THAT BLOCKED IT IS NOW IN THE TREE. Unlike the other three
|
||||
// P4a families this one gets no fresh apply entry points - the catalogue is closed and a
|
||||
// texture rides P3a's OWN resource_create / resource_respecify / resource_subdata /
|
||||
// resource_destroy rows - so on a base without the wire package's per-kind record vectors
|
||||
// and its texture branch, two properties made a texture record actively harmful rather than
|
||||
// merely ignored: MGPipeApplierState::Resources was ONE slot-indexed vector, so a texture
|
||||
// create at slot 12 overwrote the BUFFER record at slot 12; and SubDataBoxFault validated
|
||||
// every record as the buffer half of MGPSubData, so a texture sub-data record was
|
||||
// Fatal{ProtocolCorruption} on `record.Level != 0` alone. Both are closed on this base
|
||||
// (three per-kind vectors, ApplyTextureUpload, SubDataTextureFault, and C1's level-scoped
|
||||
// PendingUploads clear), so the constant is its own bit and the family is live.
|
||||
//
|
||||
// * MGPipeApplierState::Resources is ONE vector indexed by SLOT (D-B2 makes it three, one
|
||||
// per resource kind). Buffer, Texture and Renderbuffer slot spaces are independent, so
|
||||
// a texture create at slot 12 OVERWRITES the buffer record at slot 12, and the next
|
||||
// write to that buffer is refused against the texture's extent - a dropped content
|
||||
// write with no diagnostic beyond the refusal counter;
|
||||
// * SubDataBoxFault validates every record as the buffer half of MGPSubData, so a texture
|
||||
// sub-data record is Fatal{ProtocolCorruption} on `record.Level != 0` alone (D-D4's
|
||||
// drain cases and TextureTest.GetTexImageReadsALevelWhoseLowerLevelsWereNeverDefined
|
||||
// abort in a verify build, which is how this was found rather than argued).
|
||||
//
|
||||
// So the flip is `w1`'s to unblock and the integrator's to make, in the rebase of this
|
||||
// branch onto the wire branch: change the 0 below to kMGPipeSubsystemTextureResources, and
|
||||
// nothing else. The whole conversion is already gated by TextureEmit's cases, which arm the
|
||||
// emitter directly (ArmForTest) and assert on the EMITTED records rather than on applier
|
||||
// state - so the flip cannot land untested, and until it lands nothing this file builds
|
||||
// reaches an applier that cannot hold it.
|
||||
inline constexpr Uint64 kMGPipeWiredTextureSubsystem = 0;
|
||||
// THE FLIP IS THE WHOLE SWITCH AND NOTHING ELSE MOVES: PipeFill.cpp ORs this constant into
|
||||
// kMGPipeWiredSubsystems, static_asserts it is 0-or-its-own-bit, gates every birth hook on
|
||||
// FamilyIsLive(kMGPipeSubsystemTextureResources, this), and gates DrainTextureSubData on
|
||||
// the same OR. Bit 9 (framebuffer) REQUIRES bit 10 (D-K2), because every MGPSurface::Res
|
||||
// names a texture or renderbuffer handle the applier must hold a record for - so this
|
||||
// package may never be integrated with only one of the two constants set.
|
||||
inline constexpr Uint64 kMGPipeWiredTextureSubsystem = kMGPipeSubsystemTextureResources;
|
||||
static_assert(kMGPipeWiredTextureSubsystem == 0 ||
|
||||
kMGPipeWiredTextureSubsystem == kMGPipeSubsystemTextureResources,
|
||||
"a family's wired constant is 0 or its own bit and nothing else");
|
||||
|
||||
// BOTH HALVES MATTER, exactly as MGPipeResourceSubsystemEnabled()'s two do. The bit is the
|
||||
// operator's per-subsystem A/B; the emitter's arm is "has this build's texture family been
|
||||
// switched on at all", and it is initialised from the constant above. There is no third
|
||||
// half - no MGPipeResourceOps member and no backend op table (D-B1: every P4a call is an
|
||||
// object record or working state the applier stores, and none of them dispatches to a
|
||||
// backend function pointer) - which is what makes the A/B a pure configuration question
|
||||
// rather than a bring-up-order one.
|
||||
// BOTH HALVES MATTER, exactly as MGPipeResourceSubsystemEnabled()'s two do, and they are
|
||||
// the SAME PAIR PipeFill.cpp's FamilyIsLive applies - the operator's per-subsystem A/B bit
|
||||
// in MOBILEGL_PIPE_PUSH, and this build having wired the family at all. There is no third
|
||||
// half: v1 carried a runtime `m_armed` latch so a unit case could drive the conversion on a
|
||||
// base whose applier could not hold the record, and with the constant flipped that latch
|
||||
// would only be able to LIE (PipeFill.cpp's gate does not consult it, so an unarmed emitter
|
||||
// would still be driven by every frontend mutation). It is deleted; a case that wants the
|
||||
// family off clears MG_Config::Features.PipePush, which is the switch the shipped build has.
|
||||
inline Bool MGPipeTextureSubsystemEnabled();
|
||||
|
||||
// DO THE RECORDS THIS EMITTER BUILDS REACH THE APPLIER ON THIS BASE? It is the wired
|
||||
@@ -144,43 +148,17 @@ namespace MobileGL::MG_Pipe {
|
||||
: MobileGL::TextureStorageType::Mipmap);
|
||||
}
|
||||
|
||||
// MGPSubData::Target, for a TEXTURE, and the packing is stated here because the payload has
|
||||
// exactly one Uint16 for two facts the server needs: WHICH KIND of storage the destination
|
||||
// is (the applier branches on it - a buffer target dispatches into MGPipeResourceOps, every
|
||||
// other target stores and returns) and WHICH CUBE FACE / upload target the level belongs
|
||||
// to, which is not derivable from the resource target at all.
|
||||
// MGPSubData::Target's PACKING AND THE TWO DEPTH-STENCIL NUMBERS ARE THE CONTRACT'S NOW
|
||||
// (ID-12 DV-2/DV-3, c0c): MGPipePackSubDataTarget / MGPipeSubDataResourceTargetOf /
|
||||
// MGPipeSubDataUploadTargetOf and kMGPipeDepthStencilModeDepth/Stencil live in
|
||||
// MG_Pipe/MGPipeTypes.h under exactly these names, with Uint32 arguments so the header
|
||||
// stays backend-neutral. This package's copies were the same spelling in the same
|
||||
// namespace - a redefinition - and are deleted; the packing argument they carried is
|
||||
// stated where the definitions now are.
|
||||
//
|
||||
// low byte = MGPipeResourceTarget (0 == Buffer, and P3a's buffer records are
|
||||
// unchanged: their upload byte is 0 too, so a
|
||||
// buffer record still compares == 0 whole)
|
||||
// high byte = MobileGL::TextureUploadTarget (26 enumerators; a byte is ample)
|
||||
//
|
||||
// The buffer half of the encoding is what keeps this backward-compatible with
|
||||
// MGPipeBuildSubDataRecord's `out.Target = kMGPipeResourceTargetBuffer`, so the applier's
|
||||
// buffer branch can keep testing the whole field or only the low byte and be right either
|
||||
// way.
|
||||
inline constexpr Uint16 MGPipePackSubDataTarget(Uint32 resourceTarget,
|
||||
MobileGL::TextureUploadTarget uploadTarget) {
|
||||
return static_cast<Uint16>((resourceTarget & 0xFFu) |
|
||||
((static_cast<Uint32>(uploadTarget) & 0xFFu) << 8));
|
||||
}
|
||||
inline constexpr Uint8 MGPipeSubDataResourceTargetOf(Uint16 packed) {
|
||||
return static_cast<Uint8>(packed & 0xFFu);
|
||||
}
|
||||
inline constexpr Uint8 MGPipeSubDataUploadTargetOf(Uint16 packed) {
|
||||
return static_cast<Uint8>((packed >> 8) & 0xFFu);
|
||||
}
|
||||
static_assert(MGPipePackSubDataTarget(kMGPipeResourceTargetBuffer,
|
||||
static_cast<MobileGL::TextureUploadTarget>(0)) ==
|
||||
kMGPipeResourceTargetBuffer,
|
||||
"a buffer sub-data record's Target must stay exactly kMGPipeResourceTargetBuffer");
|
||||
|
||||
// MGPTextureParams::DepthStencilMode. The frontend keeps a GLenum (GL_DEPTH_COMPONENT /
|
||||
// GL_STENCIL_INDEX, 0x1902 / 0x1901) and the payload byte cannot hold one, so the two
|
||||
// legal values are numbered - 0 is DEPTH_COMPONENT, which is also the GL initial value and
|
||||
// therefore what a zero-initialised record already says.
|
||||
inline constexpr Uint8 kMGPipeDepthStencilModeDepth = 0;
|
||||
inline constexpr Uint8 kMGPipeDepthStencilModeStencil = 1;
|
||||
// WHAT STAYS HERE is the GLenum -> byte translation, which is frontend knowledge: the
|
||||
// frontend keeps GL_DEPTH_COMPONENT / GL_STENCIL_INDEX (0x1902 / 0x1901) and the payload
|
||||
// byte cannot hold one.
|
||||
inline Uint8 MGPipeDepthStencilModeByte(GLenum mode) {
|
||||
return mode == GL_STENCIL_INDEX ? kMGPipeDepthStencilModeStencil : kMGPipeDepthStencilModeDepth;
|
||||
}
|
||||
@@ -442,6 +420,7 @@ namespace MobileGL::MG_Pipe {
|
||||
MGPipeHandle AcquireTexture(Uint64 lifetimeId, ITextureObject* object) {
|
||||
const MGPipeHandle handle = MGPipeSlots().Acquire(MGPipeKind::Texture, lifetimeId);
|
||||
Entry& entry = EntryFor(m_textures, handle);
|
||||
RetireIfRecycled(entry, handle);
|
||||
entry.Texture = object;
|
||||
entry.Gen = handle.Gen;
|
||||
return handle;
|
||||
@@ -452,6 +431,7 @@ namespace MobileGL::MG_Pipe {
|
||||
MGPipeHandle AcquireRenderbuffer(Uint64 lifetimeId) {
|
||||
const MGPipeHandle handle = MGPipeSlots().Acquire(MGPipeKind::Renderbuffer, lifetimeId);
|
||||
Entry& entry = EntryFor(m_renderbuffers, handle);
|
||||
RetireIfRecycled(entry, handle);
|
||||
entry.Gen = handle.Gen;
|
||||
return handle;
|
||||
}
|
||||
@@ -479,77 +459,68 @@ namespace MobileGL::MG_Pipe {
|
||||
// P4a get their producers here and in the framebuffer emitter: RENDER_TARGET and
|
||||
// DEPTH_STENCIL from an attachment point, SAMPLER from a resolved sampler view and
|
||||
// SHADER_IMAGE from a resolved image unit (the sampler package's two).
|
||||
// A MASK CHANGE AFTER THE ALLOCATION IS A METADATA RESPECIFY (ID-18 M4), and without it
|
||||
// the sticky half of D-A4 is a no-op for exactly the textures it was written for. The
|
||||
// mask rides resource_create and every resource_respecify - and an IMMUTABLE texture has
|
||||
// no further respecify, that being what immutable means - so for the canonical order
|
||||
// `glTexStorage2D(...); glBindImageTexture(...)` the applier's record kept
|
||||
// ImageBindableHint = 0 for ever and the PREVENTION half of the texture-remint stall
|
||||
// class never fired. So a mask that actually MOVES re-emits the stored descriptor with
|
||||
// the new mask: every storage-defining field is byte-identical to what the applier
|
||||
// holds, which is exactly the shape wire applies as a METADATA UPDATE - the descriptor
|
||||
// is replaced, no reallocation is acked, and NO pending upload is dropped, so a mask
|
||||
// change arriving between a glTexSubImage2D and the sync that consumes it cannot eat
|
||||
// the texels.
|
||||
void NoteTextureBoundAs(MGPipeHandle handle, Uint16 bit) {
|
||||
if (MGPipeHandleIsNull(handle)) return;
|
||||
Entry& entry = EntryFor(m_textures, handle);
|
||||
const Uint16 before = entry.BindMask;
|
||||
entry.BindMask = static_cast<Uint16>(before | bit);
|
||||
const Uint16 now = static_cast<Uint16>(before | bit);
|
||||
if (now == before) return;
|
||||
entry.BindMask = now;
|
||||
// AN ImageBindableHint TRANSITION IS THE ONE THING THE CLIENT ASKS A RESYNC FOR
|
||||
// (D-E2): the widened-channel carrier needs a swizzle override that the frontend's
|
||||
// own params version does not move for, so the transition arms ForceResync on the
|
||||
// next set_texture_params rather than being silently folded into the descriptor.
|
||||
// SamplerResync stays the SERVER's byte and is never set from here.
|
||||
if ((before & kMGPipeBindShaderImage) == 0 && (bit & kMGPipeBindShaderImage) != 0) {
|
||||
entry.ForceParamsResync = true;
|
||||
}
|
||||
RepublishMask(MGPipeKind::Texture, handle, entry);
|
||||
}
|
||||
void NoteRenderbufferBoundAs(MGPipeHandle handle, Uint16 bit) {
|
||||
if (MGPipeHandleIsNull(handle)) return;
|
||||
Entry& entry = EntryFor(m_renderbuffers, handle);
|
||||
entry.BindMask = static_cast<Uint16>(entry.BindMask | bit);
|
||||
const Uint16 before = entry.BindMask;
|
||||
const Uint16 now = static_cast<Uint16>(before | bit);
|
||||
if (now == before) return;
|
||||
entry.BindMask = now;
|
||||
RepublishMask(MGPipeKind::Renderbuffer, handle, entry);
|
||||
}
|
||||
Uint16 TextureBindMask(MGPipeHandle handle) const { return MaskOf(m_textures, handle); }
|
||||
Uint16 RenderbufferBindMask(MGPipeHandle handle) const { return MaskOf(m_renderbuffers, handle); }
|
||||
|
||||
// ---- the publication latch (D-I1) ----
|
||||
//
|
||||
// The create is gated at its call site and the destroy inside the death helper, so the
|
||||
// two ask the SAME question at two different moments. An object born while the
|
||||
// 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. So the
|
||||
// answer is LATCHED at the create and the destroy uses the latched one.
|
||||
Bool TextureRecordIsPublished(MGPipeHandle handle) const {
|
||||
return PublishedIn(m_textures, handle);
|
||||
}
|
||||
Bool RenderbufferRecordIsPublished(MGPipeHandle handle) const {
|
||||
return PublishedIn(m_renderbuffers, handle);
|
||||
}
|
||||
void NoteTextureRecordDestroyed(MGPipeHandle handle) { Retire(m_textures, handle); }
|
||||
void NoteRenderbufferRecordDestroyed(MGPipeHandle handle) { Retire(m_renderbuffers, handle); }
|
||||
// ---- the three object calls (the entry points PipeFill.cpp forwards to) ----
|
||||
|
||||
// ---- the three object calls ----
|
||||
|
||||
void EmitTextureCreate(ITextureObject& texture) {
|
||||
// resource_create, from TextureObjectBase's CONSTRUCTOR - so the DERIVED object does
|
||||
// not exist yet and only members TextureObjectBase itself implements may be read.
|
||||
// GetTarget(), GetExternalIndex() and GetLifetimeId() are all overridden ON THE BASE,
|
||||
// so they dispatch to the base's own bodies here and read members the mem-init list has
|
||||
// already written; GetStorageType() and GetUploadTargets() are NOT, so calling either
|
||||
// would be undefined behaviour and the storage kind is derived from the target instead
|
||||
// (exact, and re-checked at the first respecify where the object IS complete).
|
||||
void EmitResourceCreate(ITextureObject& texture) {
|
||||
const MGPipeHandle handle = AcquireTexture(texture.GetLifetimeId(), &texture);
|
||||
Entry& entry = EntryFor(m_textures, handle);
|
||||
const MGPResourceDesc desc = MGPipeBuildTextureResourceDesc(
|
||||
texture, handle, entry.BindMask, /*storageDefined=*/false, kMGPipeNullHandle,
|
||||
kMGPipeNullHandle, 0, 0);
|
||||
entry.Published = true;
|
||||
entry.LastDesc = desc;
|
||||
entry.HasLastDesc = true;
|
||||
NoteDesc(desc, /*isCreate=*/true);
|
||||
if constexpr (MGPipeTextureRecordsReachTheApplier()) MGPipeApplyResourceCreate(desc);
|
||||
}
|
||||
|
||||
// resource_create for a texture whose DERIVED object does not exist yet - the base
|
||||
// constructor. Nothing virtual is touched: the target and the GL name are the two
|
||||
// facts a create carries and both are plain members by then. See
|
||||
// MGPipeTextureStorageKindForTarget for why the storage kind may not be asked for here.
|
||||
void EmitTextureCreateFromBase(Uint64 lifetimeId, ITextureObject* object,
|
||||
MobileGL::TextureTarget target, Uint externalIndex) {
|
||||
const MGPipeHandle handle = AcquireTexture(lifetimeId, object);
|
||||
Entry& entry = EntryFor(m_textures, handle);
|
||||
MGPResourceDesc desc{};
|
||||
desc.Resource = handle;
|
||||
desc.Target = static_cast<Uint8>(MGPipeResourceTargetForTextureTarget(target));
|
||||
desc.StorageKind = MGPipeTextureStorageKindForTarget(target);
|
||||
desc.Target = static_cast<Uint8>(MGPipeResourceTargetForTextureTarget(texture.GetTarget()));
|
||||
desc.StorageKind = MGPipeTextureStorageKindForTarget(texture.GetTarget());
|
||||
desc.BindMask = entry.BindMask;
|
||||
desc.GlNameForDiag = static_cast<Uint32>(externalIndex);
|
||||
entry.Published = true;
|
||||
entry.LastDesc = desc;
|
||||
entry.HasLastDesc = true;
|
||||
desc.ImageBindableHint = (entry.BindMask & kMGPipeBindShaderImage) != 0 ? 1 : 0;
|
||||
desc.GlNameForDiag = static_cast<Uint32>(texture.GetExternalIndex());
|
||||
NoteDesc(desc, /*isCreate=*/true);
|
||||
if constexpr (MGPipeTextureRecordsReachTheApplier()) MGPipeApplyResourceCreate(desc);
|
||||
PublishCreate(MGPipeKind::Texture, handle, entry, desc);
|
||||
}
|
||||
|
||||
// resource_respecify, from every storage-defining entry point. DEDUPED ON THE
|
||||
@@ -559,9 +530,12 @@ namespace MobileGL::MG_Pipe {
|
||||
// this record carries). A byte compare of an 88-byte POD is cheaper than the emission
|
||||
// it avoids, and it is the same "version-first skip before anything expensive" shape
|
||||
// every other P4a emission takes.
|
||||
void EmitTextureRespecify(ITextureObject& texture) {
|
||||
void EmitResourceRespecify(ITextureObject& texture) {
|
||||
const MGPipeHandle handle = AcquireTexture(texture.GetLifetimeId(), &texture);
|
||||
Entry& entry = EntryFor(m_textures, handle);
|
||||
// THE VIEW'S OWNER IS ACQUIRED FIRST, and no Entry& is held across it (m3): the
|
||||
// owner's slot can be higher than this table's size, so AcquireTexture would
|
||||
// resize() the vector out from under a reference taken before it. Every Entry&
|
||||
// below is taken after the last call that can grow the table.
|
||||
MGPipeHandle viewOf = kMGPipeNullHandle;
|
||||
if (const auto& owner = texture.GetViewStorageOwner()) {
|
||||
// ONE HOP ALWAYS REACHES STORAGE: glTextureView composes a view-of-a-view onto
|
||||
@@ -590,6 +564,7 @@ namespace MobileGL::MG_Pipe {
|
||||
: static_cast<Uint64>(bufferTexture.GetBufferRangeSizeInBytes());
|
||||
}
|
||||
}
|
||||
Entry& entry = EntryFor(m_textures, handle);
|
||||
const MGPResourceDesc desc = MGPipeBuildTextureResourceDesc(
|
||||
texture, handle, entry.BindMask, /*storageDefined=*/true, viewOf, bufferHandle, bufOffset,
|
||||
bufSize);
|
||||
@@ -598,21 +573,43 @@ namespace MobileGL::MG_Pipe {
|
||||
// subsystem bit was clear has no applier record, and every later respecify would be
|
||||
// REFUSED. A create rather than a respecify, because that is what the record's
|
||||
// absence means and because the applier starts a record over on a create.
|
||||
if (!entry.Published) {
|
||||
if (!MGPipeHandleIsPublished(MGPipeKind::Texture, handle)) {
|
||||
const MGPResourceDesc createDesc = MGPipeBuildTextureResourceDesc(
|
||||
texture, handle, entry.BindMask, /*storageDefined=*/false, viewOf, bufferHandle,
|
||||
bufOffset, bufSize);
|
||||
entry.Published = true;
|
||||
NoteDesc(createDesc, /*isCreate=*/true);
|
||||
if constexpr (MGPipeTextureRecordsReachTheApplier()) MGPipeApplyResourceCreate(createDesc);
|
||||
PublishCreate(MGPipeKind::Texture, handle, entry, createDesc);
|
||||
}
|
||||
entry.LastDesc = desc;
|
||||
entry.HasLastDesc = true;
|
||||
NoteDesc(desc, /*isCreate=*/false);
|
||||
// NO initial bytes: a texture's texels travel as resource_subdata out of the drain
|
||||
// list, never inside its storage definition. This is what keeps glTexImage2D's
|
||||
// "define the level and upload it" one allocation and one upload rather than two.
|
||||
if constexpr (MGPipeTextureRecordsReachTheApplier()) MGPipeApplyResourceRespecify(desc, nullptr);
|
||||
//
|
||||
// AND THE MIRROR ONLY ADVANCES ON ACCEPTANCE (ID-18 M3): the dedupe above is a claim
|
||||
// about what the APPLIER holds, so a refused respecify must leave LastDesc naming
|
||||
// the descriptor that actually landed, or the next identical call is suppressed
|
||||
// against a record that was never stored.
|
||||
Bool accepted = ApplyRespecify(desc);
|
||||
if constexpr (MGPipeTextureRecordsReachTheApplier()) {
|
||||
if (!accepted) {
|
||||
// THE SECOND HALF OF THE SELF-HEAL, and the publication latch cannot give
|
||||
// it: the latch answers "did a create for this handle GO OUT", which stays
|
||||
// true after MGPipeApplierReleaseObjectRecords has dropped every object
|
||||
// record - the scope a served context's teardown takes while the frontend
|
||||
// objects live on in the share group. The applier's REFUSAL is the only
|
||||
// signal that says "I hold nothing for this handle", and the acceptance
|
||||
// return is what makes it visible from here at all. One retry, never a
|
||||
// loop: a descriptor the applier refuses on its own merits (a target that
|
||||
// names no resource kind) is refused again and the flags stay set.
|
||||
const MGPResourceDesc healDesc = MGPipeBuildTextureResourceDesc(
|
||||
texture, handle, entry.BindMask, /*storageDefined=*/false, viewOf,
|
||||
bufferHandle, bufOffset, bufSize);
|
||||
NoteDesc(healDesc, /*isCreate=*/true);
|
||||
PublishCreate(MGPipeKind::Texture, handle, entry, healDesc);
|
||||
accepted = ApplyRespecify(desc);
|
||||
}
|
||||
}
|
||||
NoteRespecified(entry, desc, accepted);
|
||||
}
|
||||
|
||||
void EmitTextureParams(ITextureObject& texture) {
|
||||
@@ -628,15 +625,54 @@ namespace MobileGL::MG_Pipe {
|
||||
texture.GetExternalIndex());
|
||||
return;
|
||||
}
|
||||
// THE BUILT-IN SAMPLER'S CSO SLOT IS KEYED ON THE SamplerObject's OWN LIFETIME ID,
|
||||
// which is the same key ~SamplerObject's death helper resolves through
|
||||
// (MGPipeEmitSamplerCsoDestroyAndFree). The CALL that fills the record -
|
||||
// create_sampler_state - is the sampler package's; minting the handle is client
|
||||
// state and is this record's to name.
|
||||
// THE VERSION-FIRST SKIP, AND IT READS BOTH COUNTERS (clientsp-v2 rule 4, and it is
|
||||
// the M2 defect stated as a rule): glTexParameter* moves GetTextureParamsVersion()
|
||||
// AND lands on the built-in SamplerObject, but the three fields this record takes
|
||||
// off that object - MinLod, MaxLod, LodBias - are ALSO reachable through paths that
|
||||
// move only SamplerObject::GetVersion(). Latching on the texture's counter alone is
|
||||
// what let glTexParameterf(GL_TEXTURE_MIN_LOD) go stale. ForceParamsResync is the
|
||||
// third input because an ImageBindableHint transition moves neither counter.
|
||||
const Uint16 paramsVersion = texture.GetTextureParamsVersion();
|
||||
const Uint16 samplerVersion = sampler->GetVersion();
|
||||
if (entry.HasParamsLatch && entry.ParamsVersion == paramsVersion &&
|
||||
entry.SamplerVersion == samplerVersion && !entry.ForceParamsResync) {
|
||||
return;
|
||||
}
|
||||
entry.HasParamsLatch = true;
|
||||
entry.ParamsVersion = paramsVersion;
|
||||
entry.SamplerVersion = samplerVersion;
|
||||
|
||||
// ID-14 / ID-17: THE BUILT-IN SAMPLER COMES FROM C's CONTENT-ADDRESSED CACHE and is
|
||||
// never minted here. v1 took MGPipeSlots().Acquire(SamplerCso, the SamplerObject's
|
||||
// lifetime id), which is a slot no create_sampler_state ever names - so on the
|
||||
// integrated tree every texture's params record would have carried a handle the
|
||||
// applier holds nothing for. The cache mints and emits create_sampler_state on a
|
||||
// miss, so the texture's built-in sampler and a glBindSampler'd object with the
|
||||
// same value share ONE CSO and one server-side twin.
|
||||
//
|
||||
// EVERY Acquire TAKES A REFERENCE AND THIS ENTRY OWES EXACTLY ONE. The reference is
|
||||
// what stops the LRU pulling a handle out from under a standing MGPTextureParams
|
||||
// record: the applier deliberately does not resolve BuiltinSampler, and an eviction
|
||||
// is not a parameter change, so nothing would refuse and nothing would re-emit. The
|
||||
// previous handle is released when the content moves it, and the last one when the
|
||||
// slot is recycled (RetireIfRecycled) - which is the only moment this package can
|
||||
// see a texture die, the death helper being A's.
|
||||
MGPipeSamplerCsoCache& cache = MGPipeSamplerCsoCacheInstance();
|
||||
Uint64 samplerBytes = 0;
|
||||
const MGPipeHandle builtinSampler =
|
||||
MGPipeSlots().Acquire(MGPipeKind::SamplerCso, sampler->GetLifetimeId());
|
||||
cache.Acquire(sampler->GetAllSamplerParameters(), samplerBytes);
|
||||
m_samplerCsoPayloadBytes += samplerBytes;
|
||||
if (entry.BuiltinSampler == builtinSampler) {
|
||||
// The value did not move, so the cache handed back the handle this entry
|
||||
// already pins AND a second reference for it. Give that one straight back.
|
||||
cache.Release(builtinSampler);
|
||||
} else {
|
||||
cache.Release(entry.BuiltinSampler); // a no-op for the null handle
|
||||
entry.BuiltinSampler = builtinSampler;
|
||||
}
|
||||
|
||||
const MGPTextureParams params =
|
||||
MGPipeBuildTextureParams(texture, handle, builtinSampler, entry.ForceParamsResync);
|
||||
MGPipeBuildTextureParams(texture, handle, entry.BuiltinSampler, entry.ForceParamsResync);
|
||||
entry.ForceParamsResync = false;
|
||||
m_lastParams = params;
|
||||
++m_paramSets;
|
||||
@@ -649,11 +685,8 @@ namespace MobileGL::MG_Pipe {
|
||||
const MGPResourceDesc desc = MGPipeBuildRenderbufferResourceDesc(renderbuffer, handle,
|
||||
entry.BindMask,
|
||||
/*storageDefined=*/false);
|
||||
entry.Published = true;
|
||||
entry.LastDesc = desc;
|
||||
entry.HasLastDesc = true;
|
||||
NoteDesc(desc, /*isCreate=*/true);
|
||||
if constexpr (MGPipeTextureRecordsReachTheApplier()) MGPipeApplyResourceCreate(desc);
|
||||
PublishCreate(MGPipeKind::Renderbuffer, handle, entry, desc);
|
||||
}
|
||||
|
||||
// D-D2: THE RENDERBUFFER PUBLICATION HOLE, CLOSED BY EMISSION.
|
||||
@@ -673,17 +706,26 @@ namespace MobileGL::MG_Pipe {
|
||||
entry.BindMask,
|
||||
/*storageDefined=*/true);
|
||||
if (entry.HasLastDesc && std::memcmp(&entry.LastDesc, &desc, sizeof(desc)) == 0) return;
|
||||
if (!entry.Published) {
|
||||
if (!MGPipeHandleIsPublished(MGPipeKind::Renderbuffer, handle)) {
|
||||
const MGPResourceDesc createDesc = MGPipeBuildRenderbufferResourceDesc(
|
||||
renderbuffer, handle, entry.BindMask, /*storageDefined=*/false);
|
||||
entry.Published = true;
|
||||
NoteDesc(createDesc, /*isCreate=*/true);
|
||||
if constexpr (MGPipeTextureRecordsReachTheApplier()) MGPipeApplyResourceCreate(createDesc);
|
||||
PublishCreate(MGPipeKind::Renderbuffer, handle, entry, createDesc);
|
||||
}
|
||||
entry.LastDesc = desc;
|
||||
entry.HasLastDesc = true;
|
||||
NoteDesc(desc, /*isCreate=*/false);
|
||||
if constexpr (MGPipeTextureRecordsReachTheApplier()) MGPipeApplyResourceRespecify(desc, nullptr);
|
||||
Bool accepted = ApplyRespecify(desc);
|
||||
if constexpr (MGPipeTextureRecordsReachTheApplier()) {
|
||||
if (!accepted) {
|
||||
// See the texture twin: the applier's refusal is the only thing that can
|
||||
// say "I hold no record for this handle" once the latch has been set.
|
||||
const MGPResourceDesc healDesc = MGPipeBuildRenderbufferResourceDesc(
|
||||
renderbuffer, handle, entry.BindMask, /*storageDefined=*/false);
|
||||
NoteDesc(healDesc, /*isCreate=*/true);
|
||||
PublishCreate(MGPipeKind::Renderbuffer, handle, entry, healDesc);
|
||||
accepted = ApplyRespecify(desc);
|
||||
}
|
||||
}
|
||||
NoteRespecified(entry, desc, accepted);
|
||||
}
|
||||
|
||||
// ---- the drain list (D-D4) ----
|
||||
@@ -697,11 +739,27 @@ namespace MobileGL::MG_Pipe {
|
||||
// The per-slot key list is a short linear scan rather than a hash: a level count is
|
||||
// ~15, the cap on the rect list behind it is 96, and this runs on the glTexSubImage
|
||||
// path which has just memcpy'd texels.
|
||||
void NoteLevelDirty(ITextureObject& texture, MobileGL::TextureUploadTarget uploadTarget, Uint level) {
|
||||
// THE PARAMETER TYPES ARE THE CONTRACT'S (PipeMutation.h): Uint32 rather than
|
||||
// MobileGL::TextureUploadTarget and Uint, because that declaration is the one door
|
||||
// MG_State has into the client and it may not name a frontend enumeration.
|
||||
//
|
||||
// THERE IS NO CLEAN ARM, and that is a DECLARED DEVIATION rather than a dropped half.
|
||||
// v1 carried a second entry point for MarkStorageDirty(..., false); the contract's hook
|
||||
// has no `dirty` parameter, and asking A to widen it would put a second signature in
|
||||
// MG_Pipe/PipeMutation.h for something the drain already collects. A level that goes
|
||||
// clean stays on the list until the NEXT drain walks it, where
|
||||
// `!mipmap->IsStorageDirty(...)` is the first test EmitOneLevel makes and returns
|
||||
// "nothing owed", so the entry is dropped from both lists there. The cost is one
|
||||
// IsStorageDirty call per cleaned level per drain, the list is bounded by the (texture,
|
||||
// level) pairs dirtied since the last validate point, and a re-dirty before that drain
|
||||
// is already covered by the entry still standing. What it must NOT be confused with is
|
||||
// dropping the level's TEXELS: nothing here clears a dirty flag.
|
||||
void NoteLevelDirty(ITextureObject& texture, Uint32 uploadTarget, Uint32 level) {
|
||||
if (m_draining) return;
|
||||
const MGPipeHandle handle = AcquireTexture(texture.GetLifetimeId(), &texture);
|
||||
Entry& entry = EntryFor(m_textures, handle);
|
||||
const Uint32 key = PackLevelKey(uploadTarget, level);
|
||||
const Uint32 key = PackLevelKey(static_cast<MobileGL::TextureUploadTarget>(uploadTarget),
|
||||
static_cast<Uint>(level));
|
||||
for (const Uint32 present : entry.DrainKeys) {
|
||||
if (present == key) return;
|
||||
}
|
||||
@@ -709,26 +767,6 @@ namespace MobileGL::MG_Pipe {
|
||||
m_drain.push_back(DrainEntry{handle, key});
|
||||
}
|
||||
|
||||
// MarkStorageDirty(..., false) from outside the drain - a level respecified, truncated
|
||||
// or explicitly marked clean. The entry stops describing anything and is dropped from
|
||||
// the per-slot list; the global list is compacted at the next drain, which is where
|
||||
// walking it is already paid for.
|
||||
void NoteLevelClean(ITextureObject& texture, MobileGL::TextureUploadTarget uploadTarget, Uint level) {
|
||||
if (m_draining) return;
|
||||
const MGPipeHandle handle = FindTexture(texture);
|
||||
if (MGPipeHandleIsNull(handle)) return;
|
||||
const SizeT slot = handle.Slot;
|
||||
if (slot >= m_textures.size()) return;
|
||||
Entry& entry = m_textures[slot];
|
||||
const Uint32 key = PackLevelKey(uploadTarget, level);
|
||||
for (SizeT i = 0; i < entry.DrainKeys.size(); ++i) {
|
||||
if (entry.DrainKeys[i] != key) continue;
|
||||
entry.DrainKeys[i] = entry.DrainKeys.back();
|
||||
entry.DrainKeys.pop_back();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// The DRAIN, at the validate point: one resource_subdata per dirty (storage owner,
|
||||
// upload target, level).
|
||||
//
|
||||
@@ -780,6 +818,20 @@ namespace MobileGL::MG_Pipe {
|
||||
Uint64 RespecifyCount() const { return m_respecifies; }
|
||||
Uint64 ParamCount() const { return m_paramSets; }
|
||||
Uint64 SubDataCount() const { return m_subDatas; }
|
||||
// Records the applier REFUSED. The dirty flag survives one of these, which is the whole
|
||||
// of D-D5 step 1 - so a case that wants to prove the flag survived asserts on this.
|
||||
Uint64 RefusedSubDataCount() const { return m_refusedSubDatas; }
|
||||
// What create_sampler_state put on the wire on this emitter's behalf, so the csob-blob
|
||||
// accounting does not under-report 100 bytes per built-in sampler mint. set_texture_params
|
||||
// itself returns no byte count - it is not emitted from the validate point's payload
|
||||
// histogram - so this is where the cache's answer lands.
|
||||
Uint64 SamplerCsoPayloadBytes() const { return m_samplerCsoPayloadBytes; }
|
||||
MGPipeHandle BuiltinSamplerOf(MGPipeHandle handle) const {
|
||||
const SizeT slot = handle.Slot;
|
||||
if (MGPipeHandleIsNull(handle) || slot >= m_textures.size()) return kMGPipeNullHandle;
|
||||
const Entry& entry = m_textures[slot];
|
||||
return entry.Gen == handle.Gen ? entry.BuiltinSampler : kMGPipeNullHandle;
|
||||
}
|
||||
SizeT DrainListSize() const { return m_drain.size(); }
|
||||
|
||||
// A fresh context: what the server has is no longer what this emitter last sent. Only
|
||||
@@ -792,25 +844,24 @@ namespace MobileGL::MG_Pipe {
|
||||
// uploads a context switch has not flushed yet.
|
||||
void Reset() {}
|
||||
|
||||
void ResetCounters() { m_creates = m_respecifies = m_paramSets = m_subDatas = 0; }
|
||||
|
||||
// ---- the arm (see kMGPipeWiredTextureSubsystem) ----
|
||||
//
|
||||
// "Does this build's texture family emit at all", initialised from the wired constant.
|
||||
// It is a RUNTIME latch and not a constant only because the flip is blocked on the wire
|
||||
// package's `w1` while the conversion below is finished: a unit case arms it, drives a
|
||||
// frontend mutation and asserts on the record the emitter built, so the conversion is
|
||||
// gated by a test on a base whose applier could not yet hold that record. Once the
|
||||
// constant is flipped this stays true for the life of the process and ArmForTest is
|
||||
// redundant rather than wrong.
|
||||
Bool Armed() const { return m_armed; }
|
||||
void ArmForTest(Bool armed) { m_armed = armed; }
|
||||
void ResetCounters() {
|
||||
m_creates = m_respecifies = m_paramSets = m_subDatas = 0;
|
||||
m_refusedSubDatas = 0;
|
||||
m_samplerCsoPayloadBytes = 0;
|
||||
}
|
||||
|
||||
// A unit fixture's per-case reset; the library never calls it. See
|
||||
// MGPipeResourceTracker::ResetForTest for the rule this restates: a texture handle and
|
||||
// the applier record it names are SHARE-GROUP OBJECT STATE, so nothing here is
|
||||
// per-context and no re-publication path exists or may exist.
|
||||
void ResetForTest() {
|
||||
// EVERY REFERENCE THIS EMITTER OWES IS GIVEN BACK FIRST. A case that dropped the
|
||||
// table without releasing would pin cache entries for the rest of the process and
|
||||
// the next case's LRU would mint over capacity for reasons it cannot see.
|
||||
for (Entry& entry : m_textures) {
|
||||
MGPipeSamplerCsoCacheInstance().Release(entry.BuiltinSampler);
|
||||
entry.BuiltinSampler = kMGPipeNullHandle;
|
||||
}
|
||||
m_textures.clear();
|
||||
m_renderbuffers.clear();
|
||||
m_drain.clear();
|
||||
@@ -819,7 +870,6 @@ namespace MobileGL::MG_Pipe {
|
||||
m_lastDesc = MGPResourceDesc{};
|
||||
m_lastParams = MGPTextureParams{};
|
||||
m_lastSubData = MGPSubData{};
|
||||
m_armed = (kMGPipeWiredTextureSubsystem & kMGPipeSubsystemTextureResources) != 0;
|
||||
ResetCounters();
|
||||
}
|
||||
|
||||
@@ -828,9 +878,19 @@ namespace MobileGL::MG_Pipe {
|
||||
ITextureObject* Texture = nullptr;
|
||||
Uint32 Gen = 0;
|
||||
Uint16 BindMask = 0;
|
||||
Bool Published = false;
|
||||
Bool ForceParamsResync = false;
|
||||
Bool HasLastDesc = false;
|
||||
// ID-14/ID-17: the CSO C's content-addressed cache handed this texture's BUILT-IN
|
||||
// sampler, and the ONE reference this emitter owes a Release for. Null until the
|
||||
// first set_texture_params. There is no Published flag beside it: c0b's
|
||||
// {kind, slot, gen} latch is the one answer both halves read.
|
||||
MGPipeHandle BuiltinSampler{};
|
||||
// The version-first skip for set_texture_params, and it reads BOTH counters
|
||||
// (clientsp-v2 rule 4): glTexParameter* moves GetTextureParamsVersion(), a write
|
||||
// that lands on the built-in SamplerObject moves only SamplerObject::GetVersion().
|
||||
Bool HasParamsLatch = false;
|
||||
Uint16 ParamsVersion = 0;
|
||||
Uint16 SamplerVersion = 0;
|
||||
MGPResourceDesc LastDesc{};
|
||||
Vector<Uint32> DrainKeys;
|
||||
};
|
||||
@@ -857,15 +917,72 @@ namespace MobileGL::MG_Pipe {
|
||||
const SizeT slot = handle.Slot;
|
||||
return slot < table.size() ? table[slot].BindMask : Uint16{0};
|
||||
}
|
||||
static Bool PublishedIn(const Vector<Entry>& table, MGPipeHandle handle) {
|
||||
if (MGPipeHandleIsNull(handle)) return false;
|
||||
const SizeT slot = handle.Slot;
|
||||
return slot < table.size() && table[slot].Published && table[slot].Gen == handle.Gen;
|
||||
// A SLOT THE ALLOCATOR HAS HANDED OUT AGAIN CARRIES ITS PREDECESSOR'S ENTRY, and every
|
||||
// field in it is a lie about the new object (m4). The sticky BindMask is the one that
|
||||
// bites: the framebuffer emitter ORs RENDER_TARGET / DEPTH_STENCIL into these entries
|
||||
// whether or not the texture family is on, so a recycled slot's new texture inherited
|
||||
// the dead one's mask and its first descriptor said so. The generation is what
|
||||
// distinguishes them and the reset is here because AcquireTexture is the one door.
|
||||
//
|
||||
// IT IS ALSO THE ONLY MOMENT THIS PACKAGE CAN SEE A TEXTURE DIE. The death helper is
|
||||
// A's (MGPipeEmitTextureDestroyAndFree) and does not forward to this emitter, so the
|
||||
// built-in sampler's cache reference is dropped here - bounded by the number of live
|
||||
// texture slots rather than unbounded, which is the shape ID-17 rule 3 names.
|
||||
void RetireIfRecycled(Entry& entry, MGPipeHandle handle) {
|
||||
if (entry.Gen == handle.Gen) return;
|
||||
MGPipeSamplerCsoCacheInstance().Release(entry.BuiltinSampler);
|
||||
entry = Entry{};
|
||||
}
|
||||
static void Retire(Vector<Entry>& table, MGPipeHandle handle) {
|
||||
const SizeT slot = handle.Slot;
|
||||
if (slot >= table.size() || table[slot].Gen != handle.Gen) return;
|
||||
table[slot] = Entry{};
|
||||
|
||||
// resource_create, and the LATCH IS TAKEN ONLY WHERE THE CREATE ACTUALLY WENT OUT
|
||||
// (D-I1, c0b): MGPipeHandleIsPublished is what the death helper reads, so latching on
|
||||
// a call the applier refused would emit a resource_destroy for a record that does not
|
||||
// exist - a refused call the applier asserts on in a verify build.
|
||||
void PublishCreate(MGPipeKind kind, MGPipeHandle handle, Entry& entry,
|
||||
const MGPResourceDesc& desc) {
|
||||
Bool accepted = false;
|
||||
Bool dispatched = false;
|
||||
if constexpr (MGPipeTextureRecordsReachTheApplier()) {
|
||||
dispatched = true;
|
||||
accepted = MGPipeApplyResourceCreate(desc);
|
||||
}
|
||||
if (dispatched && !accepted) return;
|
||||
MGPipeNoteHandlePublished(kind, handle);
|
||||
entry.LastDesc = desc;
|
||||
entry.HasLastDesc = true;
|
||||
}
|
||||
|
||||
static Bool ApplyRespecify(const MGPResourceDesc& desc) {
|
||||
if constexpr (MGPipeTextureRecordsReachTheApplier()) {
|
||||
return MGPipeApplyResourceRespecify(desc, nullptr);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void NoteRespecified(Entry& entry, const MGPResourceDesc& desc, Bool accepted) {
|
||||
if constexpr (MGPipeTextureRecordsReachTheApplier()) {
|
||||
if (!accepted) return;
|
||||
}
|
||||
entry.LastDesc = desc;
|
||||
entry.HasLastDesc = true;
|
||||
}
|
||||
|
||||
// THE METADATA RESPECIFY (ID-18 M4). Every storage-defining field is the stored
|
||||
// descriptor's own byte for byte - the record IS entry.LastDesc with a new mask - which
|
||||
// is what makes the applier classify it as a metadata update: the descriptor is
|
||||
// replaced so the mask and the hint take their new values, the serial advances, and no
|
||||
// pending upload is dropped.
|
||||
void RepublishMask(MGPipeKind kind, MGPipeHandle handle, Entry& entry) {
|
||||
if (!MGPipeTextureSubsystemEnabled()) return;
|
||||
// Nothing has described this object to the applier yet, so the create or the first
|
||||
// respecify carries the new mask anyway - both read entry.BindMask.
|
||||
if (!entry.HasLastDesc || !MGPipeHandleIsPublished(kind, handle)) return;
|
||||
MGPResourceDesc desc = entry.LastDesc;
|
||||
desc.BindMask = entry.BindMask;
|
||||
desc.ImageBindableHint = (entry.BindMask & kMGPipeBindShaderImage) != 0 ? 1 : 0;
|
||||
if (std::memcmp(&entry.LastDesc, &desc, sizeof(desc)) == 0) return;
|
||||
NoteDesc(desc, /*isCreate=*/false);
|
||||
NoteRespecified(entry, desc, ApplyRespecify(desc));
|
||||
}
|
||||
|
||||
void NoteDesc(const MGPResourceDesc& desc, Bool isCreate) {
|
||||
@@ -915,8 +1032,11 @@ namespace MobileGL::MG_Pipe {
|
||||
|
||||
m_lastSubData = MGPSubData{};
|
||||
m_lastSubData.Res = pending.Handle;
|
||||
// The contract's packer takes two Uint32s (c0c keeps MGPipeTypes.h backend-neutral),
|
||||
// so the frontend enumeration is widened here rather than there.
|
||||
m_lastSubData.Target = MGPipePackSubDataTarget(
|
||||
MGPipeResourceTargetForTextureTarget(texture->GetTarget()), uploadTarget);
|
||||
static_cast<Uint32>(MGPipeResourceTargetForTextureTarget(texture->GetTarget())),
|
||||
static_cast<Uint32>(uploadTarget));
|
||||
m_lastSubData.Level = static_cast<Uint16>(level);
|
||||
// ALWAYS 1 ON THE CLIENT SIDE. The conversion fallbacks (the packed-norm, widened
|
||||
// and fallback upload preparers) are the server's and run there, so the bytes this
|
||||
@@ -936,17 +1056,44 @@ namespace MobileGL::MG_Pipe {
|
||||
m_lastSubData.Blob.Offset = static_cast<Uint64>(reinterpret_cast<std::uintptr_t>(shadow));
|
||||
m_lastSubData.Blob.Size = 0;
|
||||
|
||||
// THE REGION LIST IS THE CALL'S VARIABLE TAIL AND IT IS HANDED OVER (M1). v1 built
|
||||
// m_regions, wrote its size into RegionCount and passed nothing, so on this base -
|
||||
// where the applier's tail exists - every scattered upload would have declared N
|
||||
// regions and supplied none (the applier faults on exactly that). A null tail is
|
||||
// correct ONLY for the whole-level shape, where RegionCount is 0.
|
||||
Bool accepted = false;
|
||||
Bool dispatched = false;
|
||||
if constexpr (MGPipeTextureRecordsReachTheApplier()) {
|
||||
MGPipeApplyResourceSubData(m_lastSubData, shadow);
|
||||
dispatched = true;
|
||||
accepted = MGPipeApplyResourceSubData(m_lastSubData, shadow,
|
||||
m_regions.empty() ? nullptr : m_regions.data());
|
||||
}
|
||||
++m_subDatas;
|
||||
if (MG_Util::PipeStats::Enabled()) {
|
||||
MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::ClientTextureUploadEmissions, 1);
|
||||
}
|
||||
bytes += sizeof(MGPSubData) + m_regions.size() * sizeof(MGPSubRegion);
|
||||
// THE CLIENT CLEARS ITS OWN FLAG, and only now (D-D5's inversion): the record was
|
||||
// accepted, the applier holds the shape, and MG_Impl contains no reader of this
|
||||
// texture's dirty state at all - the frontend never reads it back.
|
||||
// THE CLIENT CLEARS ITS OWN FLAG ONLY FOR A LEVEL THE APPLIER ACCEPTED (D-D5 step 1
|
||||
// read literally; ID-18 M3). v1 cleared on DISPATCH - and, with the wired constant
|
||||
// still 0, even on a call the `if constexpr` had discarded - so any refusal left the
|
||||
// server with nothing and the client with a clean flag, and since MG_Impl contains
|
||||
// no reader of a texture's dirty state the level simply stopped updating for the
|
||||
// life of the texture. The two refusal paths are invisible from here without this
|
||||
// answer: a dead or stale handle is a counted no-op and a corrupt record is a Fatal
|
||||
// that deliberately moves no counter.
|
||||
//
|
||||
// A REFUSED LEVEL STAYS DIRTY AND STAYS ON THE DRAIN LIST, which is the safe
|
||||
// direction and self-heals: the ordinary cause is a record the applier does not
|
||||
// hold, and the next respecify's self-healing create gives it one. It is LOUD
|
||||
// because a permanently refused level would otherwise re-emit once per verb for
|
||||
// ever with nothing to show for it.
|
||||
if (dispatched && !accepted) {
|
||||
++m_refusedSubDatas;
|
||||
MGLOG_E_ONCE("MGPipe: resource_subdata for texture {slot=%u, gen=%u} level %u was refused; "
|
||||
"the level stays dirty and is retried at the next validate point",
|
||||
pending.Handle.Slot, pending.Handle.Gen, static_cast<Uint>(level));
|
||||
return false;
|
||||
}
|
||||
mipmap->MarkStorageDirty(uploadTarget, level, false);
|
||||
return true;
|
||||
}
|
||||
@@ -965,7 +1112,8 @@ namespace MobileGL::MG_Pipe {
|
||||
Uint64 m_respecifies = 0;
|
||||
Uint64 m_paramSets = 0;
|
||||
Uint64 m_subDatas = 0;
|
||||
Bool m_armed = (kMGPipeWiredTextureSubsystem & kMGPipeSubsystemTextureResources) != 0;
|
||||
Uint64 m_refusedSubDatas = 0;
|
||||
Uint64 m_samplerCsoPayloadBytes = 0;
|
||||
};
|
||||
|
||||
inline MGPipeTextureEmitter& MGPipeTextureEmitterInstance() {
|
||||
@@ -978,107 +1126,33 @@ namespace MobileGL::MG_Pipe {
|
||||
}
|
||||
|
||||
inline Bool MGPipeTextureSubsystemEnabled() {
|
||||
return MGPipeTextureEmitterInstance().Armed() &&
|
||||
return (kMGPipeWiredTextureSubsystem & kMGPipeSubsystemTextureResources) != 0 &&
|
||||
(MG_Config::Features.PipePush & kMGPipeSubsystemTextureResources) != 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// The six entry points MG_State calls. See this file's header comment for why they are
|
||||
// here rather than in MG_Pipe/PipeMutation.h.
|
||||
// WHAT USED TO BE HERE, AND WHY IT IS NOT (c0b, ID-13)
|
||||
// ---------------------------------------------------------------------------------
|
||||
|
||||
// From TextureObjectBase's constructor. The mint is unconditional in a push build; the
|
||||
// CALL is what the subsystem predicate gates.
|
||||
inline void MGPipeMintAndCreateTexture(MG_State::GLState::ITextureObject* object, Uint64 lifetimeId,
|
||||
MobileGL::TextureTarget target, Uint externalIndex) {
|
||||
MGPipeTextureEmitter& emitter = MGPipeTextureEmitterInstance();
|
||||
if (!MGPipeTextureSubsystemEnabled()) {
|
||||
emitter.AcquireTexture(lifetimeId, object);
|
||||
return;
|
||||
}
|
||||
emitter.EmitTextureCreateFromBase(lifetimeId, object, target, externalIndex);
|
||||
}
|
||||
|
||||
inline void MGPipeEmitTextureRespecify(MG_State::GLState::ITextureObject& texture) {
|
||||
if (!MGPipeTextureSubsystemEnabled()) return;
|
||||
MGPipeTextureEmitterInstance().EmitTextureRespecify(texture);
|
||||
}
|
||||
|
||||
inline void MGPipeEmitTextureParams(MG_State::GLState::ITextureObject& texture) {
|
||||
if (!MGPipeTextureSubsystemEnabled()) return;
|
||||
MGPipeTextureEmitterInstance().EmitTextureParams(texture);
|
||||
}
|
||||
|
||||
inline void MGPipeNoteTextureLevelDirty(MG_State::GLState::ITextureObject& texture,
|
||||
MobileGL::TextureUploadTarget uploadTarget, Uint level, Bool dirty) {
|
||||
if (!MGPipeTextureSubsystemEnabled()) return;
|
||||
MGPipeTextureEmitter& emitter = MGPipeTextureEmitterInstance();
|
||||
if (dirty) {
|
||||
emitter.NoteLevelDirty(texture, uploadTarget, level);
|
||||
} else {
|
||||
emitter.NoteLevelClean(texture, uploadTarget, level);
|
||||
}
|
||||
}
|
||||
|
||||
inline void MGPipeMintAndCreateRenderbuffer(MG_State::GLState::RenderbufferObject& renderbuffer) {
|
||||
MGPipeTextureEmitter& emitter = MGPipeTextureEmitterInstance();
|
||||
if (!MGPipeTextureSubsystemEnabled()) {
|
||||
emitter.AcquireRenderbuffer(renderbuffer.GetLifetimeId());
|
||||
return;
|
||||
}
|
||||
emitter.EmitRenderbufferCreate(renderbuffer);
|
||||
}
|
||||
|
||||
inline void MGPipeEmitRenderbufferRespecify(MG_State::GLState::RenderbufferObject& renderbuffer) {
|
||||
if (!MGPipeTextureSubsystemEnabled()) return;
|
||||
MGPipeTextureEmitterInstance().EmitRenderbufferRespecify(renderbuffer);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// STEP 1 OF THE THREE-STEP DEATH ORDER, and it is here rather than inside the contract's
|
||||
// MGPipeEmitTextureDestroyAndFree for the same ownership reason the six entry points above
|
||||
// are: MG_Impl/Pipe/PipeFill.cpp, where that helper lives, is the contract package's for
|
||||
// the whole phase, and at the tag it hard-codes `published = false` with the note "the
|
||||
// texture emitter publishes nothing yet". This client package cannot edit that line, so it
|
||||
// supplies the answer from the destructor instead, one statement BEFORE the helper:
|
||||
//
|
||||
// ~TextureObjectBase / ~RenderbufferObject
|
||||
// -> MGPipeEmit<Kind>ResourceDestroy(lifetimeId) // 1. the wire delete
|
||||
// -> MGPipeEmit<Kind>DestroyAndFree(lifetimeId) // 2. the notice, 3. the slot
|
||||
// v1 carried eight free functions - MGPipeMintAndCreateTexture, MGPipeEmitTextureRespecify,
|
||||
// MGPipeEmitTextureParams, MGPipeNoteTextureLevelDirty, MGPipeMintAndCreateRenderbuffer,
|
||||
// MGPipeEmitRenderbufferRespecify and the two ...ResourceDestroy halves - because at the
|
||||
// contract tag MG_Pipe/PipeMutation.h declared no texture birth hook and
|
||||
// MG_Impl/Pipe/PipeFill.cpp's death helpers hard-coded `published = false`. Every one of
|
||||
// them is now A's:
|
||||
//
|
||||
// which is EXACTLY the order PipeMutation.h fixes and not a variation on it: the applier's
|
||||
// record is dropped while nothing can have re-handed the slot out, the death notice is
|
||||
// raised while the handle still resolves, and the slot goes back last. The integrator can
|
||||
// fold these two functions into the helpers' bodies in one mechanical commit once the
|
||||
// phase's file ownership relaxes.
|
||||
// * the four MINTS and the nine EMISSIONS are declared in PipeMutation.h and defined in
|
||||
// PipeFill.cpp, which gates them on FamilyIsLive(bit, kMGPipeWiredTextureSubsystem) and
|
||||
// forwards to this class through ForwardWhenWired. MGPipeEmitTextureParams in
|
||||
// particular was a NAME COLLISION - the contract declares that exact signature - so
|
||||
// keeping the inline definition here would not have compiled at all;
|
||||
// * step 1 of the death order is inside MGPipeEmitTextureDestroyAndFree /
|
||||
// ...RenderbufferDestroyAndFree, which read MGPipeHandleIsPublished and emit the
|
||||
// resource_destroy themselves, so the destructors call ONE helper and not two;
|
||||
// * the publication latch is PipeFill.cpp's {kind, slot, gen} table, written by
|
||||
// PublishCreate above and read by those helpers.
|
||||
//
|
||||
// PUBLISHED-GATED RATHER THAN SLOT-GATED, for the reason PipeFill.cpp states in full: a
|
||||
// slot is not evidence of a record, because a backend twin table mints one through
|
||||
// MGPipeSlots().Acquire whether or not the subsystem ever asked this client to emit a
|
||||
// create - which is exactly what a MOBILEGL_PIPE_PUSH lane with P4a's bits clear runs - and
|
||||
// a resource_destroy on such a handle is a refused call the applier counts and asserts on.
|
||||
inline Bool MGPipeEmitTextureResourceDestroy(Uint64 lifetimeId) {
|
||||
const MGPipeHandle handle = MGPipeSlots().FindByLifetimeId(MGPipeKind::Texture, lifetimeId);
|
||||
MGPipeTextureEmitter& emitter = MGPipeTextureEmitterInstance();
|
||||
if (!emitter.TextureRecordIsPublished(handle)) return false;
|
||||
MGPHandleOnly only{};
|
||||
only.Handle = handle;
|
||||
only.Kind = static_cast<Uint32>(MGPipeKind::Texture);
|
||||
if constexpr (MGPipeTextureRecordsReachTheApplier()) MGPipeApplyResourceDestroy(only);
|
||||
emitter.NoteTextureRecordDestroyed(handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline Bool MGPipeEmitRenderbufferResourceDestroy(Uint64 lifetimeId) {
|
||||
const MGPipeHandle handle = MGPipeSlots().FindByLifetimeId(MGPipeKind::Renderbuffer, lifetimeId);
|
||||
MGPipeTextureEmitter& emitter = MGPipeTextureEmitterInstance();
|
||||
if (!emitter.RenderbufferRecordIsPublished(handle)) return false;
|
||||
MGPHandleOnly only{};
|
||||
only.Handle = handle;
|
||||
only.Kind = static_cast<Uint32>(MGPipeKind::Renderbuffer);
|
||||
if constexpr (MGPipeTextureRecordsReachTheApplier()) MGPipeApplyResourceDestroy(only);
|
||||
emitter.NoteRenderbufferRecordDestroyed(handle);
|
||||
return true;
|
||||
}
|
||||
// The self-healing create in EmitResourceRespecify stays this file's: c0b provides no such
|
||||
// path and it is what repairs a texture born while the subsystem bit was clear.
|
||||
} // namespace MobileGL::MG_Pipe
|
||||
#endif // MOBILEGL_PIPE_PUSH
|
||||
|
||||
@@ -9,13 +9,10 @@
|
||||
#include "RenderbufferObject.h"
|
||||
#include <MG_Util/Metrics/TextureMetrics.h>
|
||||
#include <MG_State/GLState/StateObjectDeathNotice.h>
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// The second and last MG_State translation unit that sees the client's texture emitter. A
|
||||
// renderbuffer has no base class to hang protected helpers on and exactly one .cpp, so the
|
||||
// include is the whole coupling; see MG_Impl/Pipe/TextureEmit.h's header comment for why the
|
||||
// declaration cannot live in MG_Pipe/PipeMutation.h this phase.
|
||||
#include <MG_Impl/Pipe/TextureEmit.h>
|
||||
#endif
|
||||
// The contract's own door, exactly as the texture half takes it (c0b): the four renderbuffer
|
||||
// hooks this file calls are declared in MG_Pipe/PipeMutation.h and defined in
|
||||
// MG_Impl/Pipe/PipeFill.cpp, so no MG_State translation unit includes the client's emitter.
|
||||
#include <MG_Pipe/PipeMutation.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
@@ -39,7 +36,12 @@ namespace MobileGL {
|
||||
// shape with textures and buffers and nothing else - and its handle is minted
|
||||
// whatever the subsystem bitmask says, because MGPSurface::Res names it out of the
|
||||
// framebuffer subsystem.
|
||||
MG_Pipe::MGPipeMintAndCreateRenderbuffer(*this);
|
||||
// TWO CALLS AND NOT ONE (c0b): the mint is unconditional in a push build
|
||||
// because a renderbuffer is named by handle out of the framebuffer subsystem
|
||||
// whether or not its own family is switched on; the create is what the gate in
|
||||
// PipeFill.cpp decides.
|
||||
MG_Pipe::MGPipeMintRenderbufferHandle(*this);
|
||||
MG_Pipe::MGPipeEmitRenderbufferResourceCreate(*this);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -51,7 +53,8 @@ namespace MobileGL {
|
||||
// still resolves - and the slot last. Steps 2 and 3 are the contract's helper;
|
||||
// step 1 is this package's, one statement earlier, because the helper's file
|
||||
// belongs to the contract package for the whole phase.
|
||||
MG_Pipe::MGPipeEmitRenderbufferResourceDestroy(m_lifetimeId);
|
||||
// All three steps are the contract's helper (c0b); v1's separate step-1 call
|
||||
// is deleted, not kept, for the reason ~TextureObjectBase states in full.
|
||||
MG_Pipe::MGPipeEmitRenderbufferDestroyAndFree(m_lifetimeId);
|
||||
}
|
||||
#endif
|
||||
@@ -145,7 +148,7 @@ namespace MobileGL {
|
||||
// The emitter dedupes on the built descriptor, so glRenderbufferStorage's three-setter
|
||||
// sequence publishes once rather than three times.
|
||||
void RenderbufferObject::PipePublishDescriptor() {
|
||||
MG_Pipe::MGPipeEmitRenderbufferRespecify(*this);
|
||||
MG_Pipe::MGPipeEmitRenderbufferResourceRespecify(*this);
|
||||
}
|
||||
#endif
|
||||
} // namespace GLState
|
||||
|
||||
@@ -12,15 +12,13 @@
|
||||
#include "MG_Util/Types.h"
|
||||
#include <MG_Util/Metrics/TextureMetrics.h>
|
||||
#include <MG_Pipe/PipeMutation.h>
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// THE ONE MG_State TRANSLATION UNIT THAT SEES THE CLIENT'S TEXTURE EMITTER, on purpose: the
|
||||
// three PipePublish* helpers below are declared on TextureObjectBase and defined here, so the
|
||||
// cube's, the view's and the buffer texture's translation units call an inherited member and
|
||||
// still see only a declaration. That is the layering MG_Pipe/PipeMutation.h gives the buffer
|
||||
// family; P4a cannot use that header because it belongs to the contract package for the whole
|
||||
// phase and carries no texture row (TextureEmit.h's header comment has the full argument).
|
||||
#include <MG_Impl/Pipe/TextureEmit.h>
|
||||
#endif
|
||||
// NO MG_State TRANSLATION UNIT SEES THE CLIENT'S EMITTER ANY MORE (c0b, ID-13). v1 included
|
||||
// MG_Impl/Pipe/TextureEmit.h here and in RenderbufferObject.cpp because at the contract tag
|
||||
// MG_Pipe/PipeMutation.h carried no texture row; it now declares the four mints, the nine
|
||||
// emissions and the publication latch, so this file sees a DECLARATION exactly as
|
||||
// BufferObject.cpp does and the closure gate's mutation-header probe has nothing to find.
|
||||
// The three PipePublish* helpers stay on TextureObjectBase so the cube's, the view's and the
|
||||
// buffer texture's translation units keep calling an inherited member.
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_State {
|
||||
@@ -53,29 +51,31 @@ namespace MobileGL {
|
||||
// name and leaves a still-bound object very much alive;
|
||||
// 3. the slot LAST, and a double free on a stale generation is a proven no-op.
|
||||
//
|
||||
// Steps 2 and 3 - plus the SamplerViewCso minted off this same lifetime id - are
|
||||
// the contract's helper. The BUILT-IN SAMPLER is deliberately not released from
|
||||
// here: it is a real SamplerObject with its own lifetime id and its own
|
||||
// destructor, which runs immediately after this body and takes the same helper
|
||||
// shape. Step 1 is the client package's, one statement earlier, because the
|
||||
// helper's file belongs to the contract package for the whole phase.
|
||||
MG_Pipe::MGPipeEmitTextureResourceDestroy(m_lifetimeId);
|
||||
// ALL THREE STEPS ARE THE CONTRACT'S HELPER (c0b): it reads the publication
|
||||
// latch, emits the resource_destroy itself, raises the notice while the handle
|
||||
// still resolves and frees the slot last. v1 emitted step 1 from a second
|
||||
// statement here because at the tag the helper hard-coded `published = false`;
|
||||
// that statement is deleted rather than kept, since a second delete for a
|
||||
// record the helper has already dropped is a refused call the applier asserts
|
||||
// on. The SamplerViewCso minted off this same lifetime id goes with it. The
|
||||
// BUILT-IN SAMPLER does not: it is a real SamplerObject with its own lifetime
|
||||
// id and its own destructor, which takes the same helper shape.
|
||||
MG_Pipe::MGPipeEmitTextureDestroyAndFree(m_lifetimeId);
|
||||
}
|
||||
|
||||
// ---- P4a's three client emission points (see TextureObject.h) ----
|
||||
|
||||
void TextureObjectBase::PipePublishDescriptor() {
|
||||
MG_Pipe::MGPipeEmitTextureRespecify(*this);
|
||||
MG_Pipe::MGPipeEmitTextureResourceRespecify(*this);
|
||||
}
|
||||
|
||||
void TextureObjectBase::PipePublishParams() {
|
||||
MG_Pipe::MGPipeEmitTextureParams(*this);
|
||||
}
|
||||
|
||||
void TextureObjectBase::PipeNoteLevelDirty(TextureUploadTarget uploadTarget, Uint mipmapLevel,
|
||||
Bool dirty) {
|
||||
MG_Pipe::MGPipeNoteTextureLevelDirty(*this, uploadTarget, mipmapLevel, dirty);
|
||||
void TextureObjectBase::PipeNoteLevelDirty(TextureUploadTarget uploadTarget, Uint mipmapLevel) {
|
||||
MG_Pipe::MGPipeNoteTextureLevelDirty(*this, static_cast<Uint32>(uploadTarget),
|
||||
static_cast<Uint32>(mipmapLevel));
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -115,15 +115,25 @@ namespace MobileGL {
|
||||
// because set_framebuffer_state and set_sampler_views name this texture by handle
|
||||
// out of two different subsystems.
|
||||
//
|
||||
// NOTHING VIRTUAL IS TOUCHED HERE and that is a correctness requirement rather
|
||||
// than a style: the derived object does not exist yet, and
|
||||
// ITextureObject::GetStorageType is PURE - calling it from a base constructor is
|
||||
// undefined behaviour. The target and the GL name are the two facts a create
|
||||
// carries and both are plain members by this point; the storage kind is derived
|
||||
// from the target, which is exact (TextureObjectBuffer is the only class that
|
||||
// reports Buffer and TextureBuffer is the only target it is constructed with).
|
||||
MG_Pipe::MGPipeMintAndCreateTexture(static_cast<ITextureObject*>(this), m_lifetimeId, target,
|
||||
externalIndex);
|
||||
// NOTHING THE DERIVED CLASS IMPLEMENTS IS TOUCHED HERE and that is a
|
||||
// correctness requirement rather than a style: the derived object does not exist
|
||||
// yet, so ITextureObject::GetStorageType and ::GetUploadTargets - PURE, with no
|
||||
// body on this base - would be undefined behaviour. The emitter reads GetTarget()
|
||||
// and GetExternalIndex(), which TextureObjectBase itself overrides and which
|
||||
// therefore dispatch to this class's own bodies over members the mem-init list
|
||||
// has already written; the storage kind is derived from the target, which is
|
||||
// exact (TextureObjectBuffer is the only class that reports Buffer and
|
||||
// TextureBuffer is the only target it is constructed with).
|
||||
//
|
||||
// TWO CALLS AND NOT ONE (c0b): the MINT is unconditional in a push build -
|
||||
// set_framebuffer_state and set_sampler_views name this texture by handle out of
|
||||
// two other subsystems, so gating it would make them emit null handles in exactly
|
||||
// the A/B arm that exists to isolate the families - and the CREATE is what the
|
||||
// subsystem gate in PipeFill.cpp decides.
|
||||
(void)target;
|
||||
(void)externalIndex;
|
||||
MG_Pipe::MGPipeMintTextureHandle(*this);
|
||||
MG_Pipe::MGPipeEmitTextureResourceCreate(*this);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -498,7 +508,9 @@ namespace MobileGL {
|
||||
}
|
||||
m_textureStorage.MarkDirty(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, dirty);
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
PipeNoteLevelDirty(uploadTarget, mipmapLevel, dirty);
|
||||
// THE DRAIN LIST HAS NO CLEAN ARM (see TextureEmit.h): a level that goes clean
|
||||
// is collected at the next drain, whose first test is IsStorageDirty.
|
||||
if (dirty) PipeNoteLevelDirty(uploadTarget, mipmapLevel);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -517,7 +529,7 @@ namespace MobileGL {
|
||||
// forwards this call to the OWNER's method after remapping the level and the
|
||||
// region, so an upload through a view and an upload through the owner arrive here
|
||||
// on the same object with the same owner-side coordinates.
|
||||
PipeNoteLevelDirty(uploadTarget, mipmapLevel, true);
|
||||
PipeNoteLevelDirty(uploadTarget, mipmapLevel);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -215,13 +215,11 @@ namespace MobileGL::MG_State::GLState {
|
||||
// that reaches the pull build is inside this guard, so the pull build's symbol set is
|
||||
// byte-for-byte the one it had before the phase.
|
||||
//
|
||||
// DECLARED HERE AND DEFINED IN TextureObject.cpp, which is the ONE MG_State
|
||||
// translation unit that includes the client's MG_Impl/Pipe/TextureEmit.h. Every other
|
||||
// texture .cpp - the cube's, the view's, the buffer texture's - calls the inherited
|
||||
// helper and still sees only a declaration, which is the same layering
|
||||
// MG_Pipe/PipeMutation.h gives the buffer family (that header is the contract
|
||||
// package's for the whole phase and carries no texture row, which is why the
|
||||
// declaration lives here instead; see TextureEmit.h's header comment).
|
||||
// DECLARED HERE AND DEFINED IN TextureObject.cpp, which calls the contract's own hooks
|
||||
// in MG_Pipe/PipeMutation.h - the same door BufferObject.cpp uses, and no MG_State
|
||||
// translation unit sees MG_Impl/Pipe/TextureEmit.h at all (c0b, ID-13). They stay
|
||||
// members rather than free calls so the cube's, the view's and the buffer texture's
|
||||
// translation units keep calling an inherited helper.
|
||||
//
|
||||
// resource_respecify. Called from BumpShapeVersion and from the three parameter
|
||||
// setters that move a DESCRIPTOR field without moving the shape (immutable levels,
|
||||
@@ -230,9 +228,11 @@ namespace MobileGL::MG_State::GLState {
|
||||
void PipePublishDescriptor();
|
||||
// set_texture_params, from every mutator that bumps m_textureParamsVersion.
|
||||
void PipePublishParams();
|
||||
// The sub-data DRAIN LIST. `dirty` false is a level going clean - a respecify, a
|
||||
// truncation, or the emitter's own clear after an accepted record.
|
||||
void PipeNoteLevelDirty(TextureUploadTarget uploadTarget, Uint mipmapLevel, Bool dirty);
|
||||
// The sub-data DRAIN LIST's append, on a level's first dirty mark. There is no clean
|
||||
// arm: the contract's hook (MG_Pipe/PipeMutation.h) carries no `dirty` flag, and a
|
||||
// level that goes clean is collected at the next drain, where !IsStorageDirty is the
|
||||
// first test EmitOneLevel makes.
|
||||
void PipeNoteLevelDirty(TextureUploadTarget uploadTarget, Uint mipmapLevel);
|
||||
#endif
|
||||
|
||||
const Uint m_externalIndex;
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace MobileGL {
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// SIX FACES, SIX BLOBS, SIX DRAIN KEYS: the upload target is the face, and it is
|
||||
// what the sub-data record's Target byte carries beside the resource target.
|
||||
PipeNoteLevelDirty(uploadTarget, mipmapLevel, dirty);
|
||||
if (dirty) PipeNoteLevelDirty(uploadTarget, mipmapLevel);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace MobileGL {
|
||||
m_textureStorage.MarkDirtyRegion(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, offset,
|
||||
size);
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
PipeNoteLevelDirty(uploadTarget, mipmapLevel, true);
|
||||
PipeNoteLevelDirty(uploadTarget, mipmapLevel);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user