mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[MG_Backend, MG_State, MG_Remote] (Disaggregated): P5e tx2 - the texture family by handle, the unit window, and the guard that notices
- SyncTextureToBackendByHandle and SyncMipmapsToBackendByHandle get their bodies
(the c0e seams): record first, twin by GetOrCreateByHandle, three syncs from the
record, no frontend touch and no allocator probe on the path.
- BackendTextureObject carries m_pushedSyncHandle (CONTRACT-P5E §4.1's m_handle), which
is what the three sync prologues read instead of probing the client allocator with
HandleOf, and what selects the by-handle arm inside them.
- IsDrawSyncCleanByRecord replaces IsDrawSyncClean(frontend) on the memo-hit path: the
same conjunction over the record's serials and the twin's own, with BOTH halves of each
resync pair read and neither clearing the other's (D-E2, scout R4).
- The unit work list is built from st.BoundSamplerViews[] and keyed on
(ContextSerial, SamplerViewsSerial, window, backend context generation); PairingsIntact
is not carried on it, because a {slot, gen} has no stale-pairing hazard to catch.
- ResolveAndBindUnitTextures is one pass over the window with the target from the view
CSO, then an unbind driven by g_boundTexturesCache; the alias arbiter dies (the client
arbitrated, SamplerEmit.h).
- BindCurrentTextures' memo key is ruling 6's both halves, entry selection included.
- The sampler pass reads BoundSamplerStates / the built-in sampler CSO / the view's
InternalFormat; the apply-thread mint and ResolveUnitSamplerBackend become a named
refusal under a transport (§4.2).
- GenerateMipmap resolves VerbMipRes and never the active unit; the detach sweep is keyed
by the texture handle over the applier's framebuffer records. The row is NOT flipped to
kWaitNone: the RGB16F/RGB32F CPU filter still reaches the client's level shadows, so
ruling 14's escape applies and the flip is trailing.
- RequireImageBindableStorage gets a by-handle entry whose re-dirty is
Fatal{UnmigratedEmulation, "image-bindable-redirty"} at the entry rather than three
frames deep at a MarkStorageDirty guard.
- The buffer-texture arm resolves scout R5 with Desc.BufferForTexBuffer / BufOffset /
BufSize and EnsureBufferResourceForHandle(nullptr, h) - that function is client-free
under an active transport (kimi audit row 29).
- CheckUnitWindows(st) at draw and dispatch makes the §5.3 window promise checkable and
checked: narrower than [0, MaxTouchedTextureUnit] is
Fatal{ProtocolCorruption, "SetSamplerViews.Count"} / "BindSamplerStates.Count".
- MipmapStorage's guard list gains the read-only shape accessors P5c deliberately
exempted, now that nothing on the draw path reads them.
This commit is contained in:
@@ -284,13 +284,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return g_rawDepthFetchSamplerBackend.get();
|
||||
}
|
||||
|
||||
Bool NeedsRawDepthFetchSampler(const SharedPtr<MG_State::GLState::SamplerObject>& samplerObject,
|
||||
// P5e (tx2), CONTRACT-P5E §5.3: the substitution's decision, over the fifteen sampler VALUES
|
||||
// rather than over the object that happens to hold them. ARCHITECTURE.md §5.5 keeps the
|
||||
// substitution itself on the server; this is only where its inputs come from, and the CSO
|
||||
// record carries all three (MGPipeValueTypes.h). The SharedPtr overload below is the same
|
||||
// three lines with one dereference in front, so the two arms cannot part.
|
||||
Bool NeedsRawDepthFetchSampler(const SamplerParameters& samplerParams,
|
||||
TextureInternalFormat textureFormat) {
|
||||
if (!MG_Util::IsDepthFormatInternalFormat(textureFormat) || !samplerObject) {
|
||||
if (!MG_Util::IsDepthFormatInternalFormat(textureFormat)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& samplerParams = samplerObject->GetAllSamplerParameters();
|
||||
if (samplerParams.compareMode != SamplerCompareMode::None) {
|
||||
return false;
|
||||
}
|
||||
@@ -300,6 +303,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
samplerParams.magFilter != SamplerFilterMode::Nearest;
|
||||
}
|
||||
|
||||
Bool NeedsRawDepthFetchSampler(const SharedPtr<MG_State::GLState::SamplerObject>& samplerObject,
|
||||
TextureInternalFormat textureFormat) {
|
||||
if (!samplerObject) {
|
||||
return false;
|
||||
}
|
||||
return NeedsRawDepthFetchSampler(samplerObject->GetAllSamplerParameters(), textureFormat);
|
||||
}
|
||||
|
||||
// Frontend texture target a GLSL sampler uniform samples from. Only used to find
|
||||
// which of a unit's bindings carries the GL_TEXTURE_LOD_BIAS the shader needs;
|
||||
// targets with no mip chain map to Unknown so the lookup falls back to no bias.
|
||||
@@ -2023,6 +2034,25 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// mode here - a silent permanent fallback is, and that is what the line is for.
|
||||
// The caller's MINOR-4 gate tick still counts EVERY decline, loud or not.
|
||||
if (st.SamplerViewCount == 0 && st.SamplerStateCount == 0) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5e (tx2), CONTRACT-P5E §5.3: UNDER A LIVE WIRE THE DECLINE IS A REFUSAL.
|
||||
//
|
||||
// The fall-back below is the pre-handle snapshot walk over GetTextureUnitObject,
|
||||
// and on a run-ahead server that walk reads client memory for a record that has
|
||||
// already been answered - there is no wait left in which the answer could be
|
||||
// right. Ruling 19 makes the missing window unrepresentable at the sink; this is
|
||||
// the same statement one level down, for the backend that would have papered over
|
||||
// it. Under monolith the fall-back is correct and stays.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith && maxTouchedUnit >= 0) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"SetSamplerViews.Count\"} - a draw "
|
||||
"touches units 0..%d and neither a sampler-view nor a sampler-state "
|
||||
"window has ever been applied while the sampler subsystem bit is set; "
|
||||
"the pre-handle unit-bindings snapshot walk reads client memory and is "
|
||||
"refused under an active transport (CONTRACT-P5E.md §5.3)",
|
||||
static_cast<int>(maxTouchedUnit));
|
||||
std::abort();
|
||||
}
|
||||
#endif
|
||||
if (maxTouchedUnit >= 0) {
|
||||
MGLOG_E_ONCE("A sampler record does not describe the binding it names: a draw "
|
||||
"touches units 0..%d and neither a sampler-view nor a sampler-state "
|
||||
@@ -2137,6 +2167,49 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5e (tx2), CONTRACT-P5E §5.8 / ruling 1: THE ARM SELECTOR for every by-handle site in
|
||||
// this family. Under ANY active transport (lockstep or run-ahead, so RUN_AHEAD=0 stays a
|
||||
// pure wait-rule A/B on identical server code), and only with BOTH family bits set: a
|
||||
// unit's texture comes from the sampler-view window and its storage from the
|
||||
// texture-resource record, and half of each is not an arm. The push-monolith build keeps
|
||||
// the frontend walk token for token - there is no server to answer from there.
|
||||
static Bool UnitTexturesByHandle() {
|
||||
return MG_Config::Transport != MG_Config::TransportMode::Monolith && SamplerSubsystemEnabled() &&
|
||||
TextureResourceSubsystemEnabled();
|
||||
}
|
||||
|
||||
// P5e (tx2), CONTRACT-P5E §5.2: THE UNIT WORK LIST WITH NO BORROWED SLOT IN IT.
|
||||
//
|
||||
// The pre-P5e entry borrows a pointer INTO a frontend binding slot and remembers which
|
||||
// frontend object it was paired with, and PairingsIntact re-checks that pairing before
|
||||
// every replay. Both exist because the list's key is derived state and a silent slot swap
|
||||
// could leave a stale pairing driving texture A's twin from texture B's frontend state.
|
||||
//
|
||||
// A HANDLE HAS NO SUCH HAZARD. `Res` is {slot, gen}: a recycled slot arrives at a higher
|
||||
// Gen, which the twin table's forward-Gen rule answers by RESETTING the twin, and a
|
||||
// backward Gen is refused outright - so there is nothing for a pairing check to catch and
|
||||
// PairingsIntact is not carried on this list at all. The key is the applier's own
|
||||
// (ContextSerial, SamplerViewsSerial, window) plus the backend context generation, and
|
||||
// SamplerViewsSerial moves IFF the resolved per-unit view set moved, which the emitter's
|
||||
// content-hash suppressor is what makes sound.
|
||||
//
|
||||
// Its OWN list and its own key rather than a re-shape of UnitTextureSyncEntry, because
|
||||
// that struct is also the draw-FBO and read-FBO attachment lists' (fb's), and one name
|
||||
// carrying two shapes across two packages' worktrees is the merge trap the contract
|
||||
// package exists to avoid.
|
||||
struct UnitTextureSyncByHandleEntry {
|
||||
MG_Pipe::MGPipeHandle Res = MG_Pipe::kMGPipeNullHandle;
|
||||
BackendTextureObject* backend = nullptr;
|
||||
};
|
||||
static Vector<UnitTextureSyncByHandleEntry> g_unitTextureSyncListByHandle;
|
||||
static Bool g_unitTextureSyncListByHandleValid = false;
|
||||
static Uint64 g_unitTextureSyncListByHandleContextSerial = 0;
|
||||
static Uint64 g_unitTextureSyncListByHandleViewsSerial = 0;
|
||||
static Uint32 g_unitTextureSyncListByHandleStart = 0;
|
||||
static Uint32 g_unitTextureSyncListByHandleCount = 0;
|
||||
static Uint g_unitTextureSyncListByHandleContextGeneration = 0;
|
||||
#endif
|
||||
static Vector<UnitTextureSyncEntry> g_unitTextureSyncList;
|
||||
static Bool g_unitTextureSyncListValid = false;
|
||||
static Uint64 g_unitTextureSyncListContextId = 0;
|
||||
@@ -2332,6 +2405,68 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
const Int maxTouchedUnit = keys.maxTouchedUnit;
|
||||
const Uint64 samplingGeneration = keys.samplingGeneration;
|
||||
const Uint64 unitBindingsEpoch = keys.unitBindingsEpoch;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5e (tx2), CONTRACT-P5E §5.2. THE UNIT HALF, BY HANDLE.
|
||||
//
|
||||
// set_sampler_views is the RESOLVED answer to the question this walk asks - which
|
||||
// texture does each touched unit sample - and the client has already applied every
|
||||
// drop the walk below performs by hand: an image-less default texture and a texture
|
||||
// that samples as incomplete are both null in the record (SamplerEmit.h's two drops),
|
||||
// and the alias contest between two real textures on one native target was arbitrated
|
||||
// by the program's own sampler types. So the window IS the work list.
|
||||
//
|
||||
// NAMED BEHAVIOUR DELTA (P5e-5, contract §5.2): this syncs the PROGRAM-RESOLVED
|
||||
// texture per unit, where the frontend walk syncs every slot of every touched unit.
|
||||
// Coverage is unchanged - views UNION image units UNION the FBO attachment lists
|
||||
// UNION the waited texture ops still cover every texture anything reads - so it
|
||||
// narrows WORK and not what reaches the driver.
|
||||
if (UnitTexturesByHandle()) {
|
||||
const auto& st = MG_Pipe::MGPipeApplier();
|
||||
const Bool listValid = g_unitTextureSyncListByHandleValid &&
|
||||
g_unitTextureSyncListByHandleContextSerial == st.ContextSerial &&
|
||||
g_unitTextureSyncListByHandleViewsSerial == st.SamplerViewsSerial &&
|
||||
g_unitTextureSyncListByHandleStart == st.SamplerViewStart &&
|
||||
g_unitTextureSyncListByHandleCount == st.SamplerViewCount &&
|
||||
g_unitTextureSyncListByHandleContextGeneration == g_backendContextGeneration;
|
||||
if (listValid) {
|
||||
if (MG_Util::PipeStats::Enabled()) {
|
||||
MG_Util::PipeStats::CountGate(MG_Util::PipeStats::Gate::EsprytTextureSyncList, /*hit=*/true);
|
||||
}
|
||||
for (const auto& entry : g_unitTextureSyncListByHandle) {
|
||||
const auto* record = PipeTextureRecordForHandle(entry.Res);
|
||||
if (record == nullptr) continue;
|
||||
// The aggregate gate, from the twin's own serials against the record's -
|
||||
// no frontend version, no context id, no sampling generation.
|
||||
if (entry.backend->IsDrawSyncCleanByRecord(entry.Res, *record)) continue;
|
||||
SyncTextureToBackendByHandle(entry.Res);
|
||||
}
|
||||
} else {
|
||||
if (MG_Util::PipeStats::Enabled()) {
|
||||
MG_Util::PipeStats::CountGate(MG_Util::PipeStats::Gate::EsprytTextureSyncList, /*hit=*/false);
|
||||
}
|
||||
g_unitTextureSyncListByHandleValid = false;
|
||||
g_unitTextureSyncListByHandle.clear();
|
||||
const Uint32 windowEnd = st.SamplerViewStart + st.SamplerViewCount;
|
||||
for (Uint32 index = st.SamplerViewStart;
|
||||
index < windowEnd && index < st.BoundSamplerViews.size(); ++index) {
|
||||
const auto& view = st.BoundSamplerViews[index];
|
||||
if (MG_Pipe::MGPipeHandleIsNull(view.Texture)) continue;
|
||||
auto& twin = SyncTextureToBackendByHandle(view.Texture);
|
||||
if (!twin) continue;
|
||||
g_unitTextureSyncListByHandle.push_back({view.Texture, twin.get()});
|
||||
}
|
||||
g_unitTextureSyncListByHandleContextSerial = st.ContextSerial;
|
||||
g_unitTextureSyncListByHandleViewsSerial = st.SamplerViewsSerial;
|
||||
g_unitTextureSyncListByHandleStart = st.SamplerViewStart;
|
||||
g_unitTextureSyncListByHandleCount = st.SamplerViewCount;
|
||||
g_unitTextureSyncListByHandleContextGeneration = g_backendContextGeneration;
|
||||
g_unitTextureSyncListByHandleValid = true;
|
||||
}
|
||||
// The pre-handle list must not be replayed after this arm ran: its entries borrow
|
||||
// frontend slots this arm never refreshed.
|
||||
g_unitTextureSyncListValid = false;
|
||||
} else
|
||||
#endif
|
||||
// The epoch survives redundant re-binds; the sampling-resolution generation
|
||||
// covers the one membership input the epoch cannot see - a default texture's
|
||||
// image appearing or vanishing flips IsUndefinedDefaultTexture with no binding
|
||||
@@ -4839,6 +4974,82 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
ZoneScopedNC("ResolveAndBindUnitTextures", TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
Bool fullyResolved = true;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5e (tx2), CONTRACT-P5E §5.2. ONE PASS OVER THE SAMPLER-VIEW WINDOW, AND THEN AN
|
||||
// UNBIND OF EVERY TARGET THE WINDOW DID NOT CLAIM.
|
||||
//
|
||||
// The three-pass slot walk below exists to arbitrate: desktop 1D/1D-array targets alias
|
||||
// ES 2D/2D-array, a unit can hold a real texture on one of an aliased pair and a default
|
||||
// object on the other, and two REAL textures can want one native target - which the
|
||||
// program's sampler types decide. THE CLIENT HAS ALREADY DECIDED ALL OF IT
|
||||
// (SamplerEmit.h's per-unit resolution is keyed on exactly those sampler types), so the
|
||||
// arbiter dies here rather than being re-implemented over records: one texture per unit,
|
||||
// named by the record, with its frontend target in the view CSO.
|
||||
//
|
||||
// The unbind half keeps its shape and changes its driver: instead of walking the frontend
|
||||
// unit's binding slots it walks the fixed TextureTarget enum against g_boundTexturesCache,
|
||||
// which is SERVER memory and is the shadow every redundant-bind filter in this backend
|
||||
// already trusts. That is what makes "the window did not claim it" mean "unbind it" - the
|
||||
// same statement the pre-handle pass makes with `!boundBackendTargets[...]`.
|
||||
if (TextureImpl::UnitTexturesByHandle()) {
|
||||
const auto& st = MG_Pipe::MGPipeApplier();
|
||||
const Uint32 windowEnd = st.SamplerViewStart + st.SamplerViewCount;
|
||||
for (Int unit = 0; unit <= maxTouchedUnit; ++unit) {
|
||||
const Uint32 index = static_cast<Uint32>(unit);
|
||||
Array<Bool, (SizeT)TextureTarget::TextureTargetCount> boundBackendTargets{};
|
||||
if (index >= st.SamplerViewStart && index < windowEnd && index < st.BoundSamplerViews.size()) {
|
||||
const auto& view = st.BoundSamplerViews[index];
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(view.Texture)) {
|
||||
// The TARGET is the view CSO's, which is the frontend target the client
|
||||
// resolved this unit against - not a second derivation from the texture's
|
||||
// descriptor, which would be a second authority for one value.
|
||||
const auto* viewRecord = PipeSamplerViewRecordForHandle(view.View);
|
||||
const auto target = viewRecord != nullptr
|
||||
? static_cast<TextureTarget>(viewRecord->View.Target)
|
||||
: TextureTarget::Unknown;
|
||||
if (viewRecord == nullptr) {
|
||||
MGLOG_E_ONCE("MGPipe: unit %d's sampler view {%u, %u} has no applier record, "
|
||||
"so the target to bind texture {%u, %u} at is unknown; the unit "
|
||||
"keeps what it holds",
|
||||
static_cast<int>(unit), view.View.Slot, view.View.Gen,
|
||||
view.Texture.Slot, view.Texture.Gen);
|
||||
fullyResolved = false;
|
||||
} else if (!TextureImpl::IsSupportedTextureTarget(target)) {
|
||||
MGLOG_D(" Texture target %s is not supported, skipping.",
|
||||
MG_Util::ConvertTextureTargetToString(target).c_str());
|
||||
} else if (auto* twin = TextureImpl::ResolveTextureTwin(view.Texture)) {
|
||||
const GLenum targetGL = TextureImpl::ConvertTextureTargetToBackendGLEnum(target);
|
||||
twin->Bind(targetGL, unit);
|
||||
boundBackendTargets[static_cast<SizeT>(
|
||||
TextureImpl::MapToBackendTextureTarget(target))] = true;
|
||||
} else {
|
||||
// No twin yet: the sync pass that would have built it declined and
|
||||
// named why. Not memoisable - a later draw would bind it without any
|
||||
// key moving.
|
||||
fullyResolved = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// The backend half of glBindTexture(..., 0) for every native target this unit no
|
||||
// longer claims, driven by the server's own binding shadow.
|
||||
Array<Bool, (SizeT)TextureTarget::TextureTargetCount> visitedBackendTargets{};
|
||||
for (SizeT t = 0; t < (SizeT)TextureTarget::TextureTargetCount; ++t) {
|
||||
const auto target = static_cast<TextureTarget>(t);
|
||||
if (!TextureImpl::IsSupportedTextureTarget(target)) continue;
|
||||
const auto backendTargetIndex =
|
||||
static_cast<SizeT>(TextureImpl::MapToBackendTextureTarget(target));
|
||||
if (visitedBackendTargets[backendTargetIndex]) continue;
|
||||
visitedBackendTargets[backendTargetIndex] = true;
|
||||
if (boundBackendTargets[backendTargetIndex]) continue;
|
||||
if (TextureImpl::g_boundTexturesCache[static_cast<SizeT>(unit)][backendTargetIndex] == nullptr) {
|
||||
continue;
|
||||
}
|
||||
TextureImpl::UnbindTexture(unit, TextureImpl::ConvertTextureTargetToBackendGLEnum(target));
|
||||
}
|
||||
}
|
||||
return fullyResolved;
|
||||
}
|
||||
#endif
|
||||
// Frontend target the current program samples at a given unit; resolves an
|
||||
// aliased native binding when two real textures compete for it (see below).
|
||||
// Only consulted on a conflict, so the ordinary unit costs nothing.
|
||||
@@ -4988,8 +5199,27 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
static SamplerImpl::BackendSamplerObject* ResolveUnitSamplerBackend(
|
||||
Int unit, const SharedPtr<MG_State::GLState::SamplerObject>& samplerObject) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5e (tx2), CONTRACT-P5E §4.2's last paragraph: THE IDENTITY SAMPLER FAMILY IS DELETED
|
||||
// UNDER A TRANSPORT AND BECOMES A NAMED REFUSAL.
|
||||
//
|
||||
// This function is the apply-thread MINT for a unit the record set does not cover: it
|
||||
// probes the client allocator with HandleOf(samplerObject), keeps a WeakPtr to a frontend
|
||||
// sampler in a per-unit memo, and its caller in the program pass creates a twin off the
|
||||
// frontend object when the lookup misses. Every one of those is a rule-F violation, and
|
||||
// the window rule (§5.3) makes the case it exists for UNREPRESENTABLE: a unit whose
|
||||
// BoundSamplerStates[u] is null while the frontend held a sampler is a MISSING RECORD,
|
||||
// not a unit to be served from the client's object.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"BindSamplerStates.Count\"} - unit %d holds a "
|
||||
"frontend sampler object that the applied bind_sampler_states window does not "
|
||||
"describe. Minting a twin for it would probe the client's allocator from the "
|
||||
"apply thread; CONTRACT-P5E.md §4.2 deletes the identity sampler family under a "
|
||||
"transport and §5.3 makes the missing entry unrepresentable",
|
||||
static_cast<int>(unit));
|
||||
std::abort();
|
||||
}
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named debt inside the
|
||||
// scope - P3b/P4b rekeys the registry onto handles.
|
||||
// scope - the refusal above is what retires it under a transport.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
auto& memo = g_unitSamplerLookupMemos[static_cast<SizeT>(unit)];
|
||||
@@ -5099,6 +5329,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// empty window is the correct emission. All 161 hits of the unnarrowed line were
|
||||
// that shape - the log said `units 0..-1` on every one of them.
|
||||
if (st.SamplerStateCount == 0 && maxTouchedUnit >= 0) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5e (tx2), CONTRACT-P5E §5.3: the same refusal CurrentUnitBindingsEpoch makes -
|
||||
// the frontend walk below reads GetTextureUnitObject per unit, which an active
|
||||
// transport may not do for a record whose client has moved on.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"BindSamplerStates.Count\"} - a draw "
|
||||
"touches units 0..%d and no bind_sampler_states has ever been applied "
|
||||
"while the sampler subsystem bit is set; the pre-handle sampler walk "
|
||||
"reads client memory and is refused under an active transport "
|
||||
"(CONTRACT-P5E.md §5.3)",
|
||||
static_cast<int>(maxTouchedUnit));
|
||||
std::abort();
|
||||
}
|
||||
#endif
|
||||
MGLOG_E_ONCE("A sampler record does not describe the binding it names: a draw touches "
|
||||
"units 0..%d and no bind_sampler_states has ever been applied while the "
|
||||
"sampler subsystem bit is set; running the pre-handle sampler walk.",
|
||||
@@ -5222,6 +5466,24 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Uint32 programBackendStateVersion = 0;
|
||||
Bool programLinked = false;
|
||||
Uint contextGeneration = 0;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5e (tx2), CONTRACT-P5E §5.3 / ruling 6 (ID-86): THE KEY IS BOTH HALVES.
|
||||
//
|
||||
// S2 argued SamplerViewsSerial alone - it already mixes the program-resolved opaque units,
|
||||
// so a resolution that moved moves it - and S4 argued the program's rows. The integrator
|
||||
// ruled BOTH, for the case neither covers alone: a RELINK whose resolution happens to be
|
||||
// unchanged still needs the twin's new unit assignments, and SamplerViewsSerial does not
|
||||
// move for it (the emitter's content hash is over the resolved set, which did not change).
|
||||
//
|
||||
// The frontend program rows above (pointer, lifetime id, backend-state version, link
|
||||
// status) stay for the monolith arm and are simply not read on this one.
|
||||
MG_Pipe::MGPipeHandle drawProgram = MG_Pipe::kMGPipeNullHandle;
|
||||
Uint64 shaderCsoSerial = 0;
|
||||
Uint64 bindingsSerial = 0;
|
||||
Uint64 samplerViewsSerial = 0;
|
||||
Uint64 contextSerial = 0;
|
||||
Bool byHandle = false;
|
||||
#endif
|
||||
decltype(TextureImpl::g_boundTexturesCache) boundTextures{};
|
||||
};
|
||||
// Small per-PROGRAM memo set, not one global: the program is part of the key
|
||||
@@ -5250,6 +5512,27 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// Units past the frontend's high-water mark have provably-empty slots.
|
||||
const Int maxTouchedUnit = keys.maxTouchedUnit;
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5e (tx2) / ruling 6: on the handle arm the key is the applier's rows and nothing
|
||||
// frontend - INCLUDING the entry selection, which used the frontend program's address.
|
||||
// maxTouchedUnit stays in it because the unbind sweep this memo replays is bounded by it.
|
||||
const Bool byHandle = TextureImpl::UnitTexturesByHandle();
|
||||
MG_Pipe::MGPipeHandle drawProgram = MG_Pipe::kMGPipeNullHandle;
|
||||
Uint64 shaderCsoSerial = 0;
|
||||
Uint64 bindingsSerial = 0;
|
||||
Uint64 samplerViewsSerial = 0;
|
||||
Uint64 contextSerial = 0;
|
||||
if (byHandle) {
|
||||
const auto& st = MG_Pipe::MGPipeApplier();
|
||||
drawProgram = st.DrawProgram;
|
||||
samplerViewsSerial = st.SamplerViewsSerial;
|
||||
contextSerial = st.ContextSerial;
|
||||
if (const auto* cso = PipeShaderCsoRecordForHandle(drawProgram)) {
|
||||
shaderCsoSerial = cso->Serial;
|
||||
bindingsSerial = cso->BindingsSerial;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// Entry selection by program pointer; a missing program takes the round-robin
|
||||
// victim. WHICH entry is used is only a performance choice - correctness sits
|
||||
// entirely in the full key + shadow compare below, unchanged from the single
|
||||
@@ -5257,6 +5540,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
const void* programKey = static_cast<const void*>(currentProgram.get());
|
||||
ResolvedTextureBindingMemo* memoSlot = nullptr;
|
||||
for (auto& candidate : g_resolvedTextureBindingMemos) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (byHandle) {
|
||||
if (candidate.valid && candidate.byHandle && candidate.drawProgram.Slot == drawProgram.Slot &&
|
||||
candidate.drawProgram.Gen == drawProgram.Gen) {
|
||||
memoSlot = &candidate;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
if (candidate.valid && candidate.program == programKey) {
|
||||
memoSlot = &candidate;
|
||||
break;
|
||||
@@ -5271,7 +5564,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
const SizeT shadowBytes =
|
||||
static_cast<SizeT>(maxTouchedUnit + 1) * sizeof(TextureImpl::g_boundTexturesCache[0]);
|
||||
const Uint64 unitBindingsEpoch = keys.unitBindingsEpoch;
|
||||
const Bool keysMatch = memo.valid && memo.glContextId == keys.contextId &&
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
const Bool keysMatch =
|
||||
byHandle ? (memo.valid && memo.byHandle && memo.maxTouchedUnit == maxTouchedUnit &&
|
||||
memo.contextSerial == contextSerial && memo.samplerViewsSerial == samplerViewsSerial &&
|
||||
memo.drawProgram.Slot == drawProgram.Slot && memo.drawProgram.Gen == drawProgram.Gen &&
|
||||
memo.shaderCsoSerial == shaderCsoSerial && memo.bindingsSerial == bindingsSerial &&
|
||||
memo.contextGeneration == g_backendContextGeneration)
|
||||
:
|
||||
#endif
|
||||
(memo.valid && memo.glContextId == keys.contextId &&
|
||||
memo.maxTouchedUnit == maxTouchedUnit &&
|
||||
memo.unitBindingsEpoch == unitBindingsEpoch &&
|
||||
memo.samplingResolutionGeneration == keys.samplingGeneration &&
|
||||
@@ -5280,7 +5582,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
memo.programBackendStateVersion ==
|
||||
(currentProgram ? currentProgram->GetBackendStateVersion() : 0) &&
|
||||
memo.programLinked == (currentProgram && currentProgram->GetLinkStatus()) &&
|
||||
memo.contextGeneration == g_backendContextGeneration;
|
||||
memo.contextGeneration == g_backendContextGeneration);
|
||||
// Short-circuited: the shadow compare is only meaningful once the key (and with it the
|
||||
// snapshotted row count) matches.
|
||||
if (!keysMatch || std::memcmp(memo.boundTextures.data(), TextureImpl::g_boundTexturesCache.data(),
|
||||
@@ -5296,6 +5598,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
memo.programBackendStateVersion = currentProgram ? currentProgram->GetBackendStateVersion() : 0;
|
||||
memo.programLinked = currentProgram && currentProgram->GetLinkStatus();
|
||||
memo.contextGeneration = g_backendContextGeneration;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
memo.byHandle = byHandle;
|
||||
memo.drawProgram = drawProgram;
|
||||
memo.shaderCsoSerial = shaderCsoSerial;
|
||||
memo.bindingsSerial = bindingsSerial;
|
||||
memo.samplerViewsSerial = samplerViewsSerial;
|
||||
memo.contextSerial = contextSerial;
|
||||
#endif
|
||||
std::memcpy(memo.boundTextures.data(), TextureImpl::g_boundTexturesCache.data(), shadowBytes);
|
||||
memo.valid = true;
|
||||
}
|
||||
@@ -5559,6 +5869,84 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
samplerBinding.lastAssignedUnit = unit;
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5e (tx2), CONTRACT-P5E §5.3. THE SAMPLER PASS, FROM RECORDS.
|
||||
//
|
||||
// Four frontend reads used to live in this loop body and every one of
|
||||
// them has a carrier: the unit's sampler object is
|
||||
// st.BoundSamplerStates[u] (null = the texture's built-in sampler, which
|
||||
// is TextureResources[view.Texture].Params.BuiltinSampler); its fifteen
|
||||
// values - lodBias among them - are the CSO record's
|
||||
// (MGPipeValueTypes.h); and the sampled texture's FORMAT, which decides
|
||||
// the raw-depth-fetch substitution, is
|
||||
// SamplerViewCsos[View].View.InternalFormat.
|
||||
//
|
||||
// THE SUBSTITUTION ITSELF STAYS ON THE SERVER (ARCHITECTURE.md §5.5
|
||||
// assigns it there by name); what moves is where its two inputs come
|
||||
// from.
|
||||
if (TextureImpl::UnitTexturesByHandle()) {
|
||||
const auto& st = MG_Pipe::MGPipeApplier();
|
||||
const auto index = static_cast<Uint32>(unit);
|
||||
const Bool inViewWindow = index >= st.SamplerViewStart &&
|
||||
index - st.SamplerViewStart < st.SamplerViewCount &&
|
||||
index < st.BoundSamplerViews.size();
|
||||
const MG_Pipe::MGPBoundView view =
|
||||
inViewWindow ? st.BoundSamplerViews[index] : MG_Pipe::MGPBoundView{};
|
||||
const Bool inStateWindow = index >= st.SamplerStateStart &&
|
||||
index - st.SamplerStateStart < st.SamplerStateCount &&
|
||||
index < st.BoundSamplerStates.size();
|
||||
const MG_Pipe::MGPipeHandle unitSampler =
|
||||
inStateWindow ? st.BoundSamplerStates[index] : MG_Pipe::kMGPipeNullHandle;
|
||||
|
||||
// The EFFECTIVE sampler of the unit: the bound CSO if there is one,
|
||||
// else the sampled texture's built-in sampler CSO - exactly the
|
||||
// override order GL states and the pre-handle body spells with
|
||||
// `samplerObject ? ... : texture->GetSamplerObject()`.
|
||||
MG_Pipe::MGPipeHandle effectiveCso = unitSampler;
|
||||
if (MG_Pipe::MGPipeHandleIsNull(effectiveCso) &&
|
||||
!MG_Pipe::MGPipeHandleIsNull(view.Texture)) {
|
||||
if (const auto* texRecord = PipeTextureRecordForHandle(view.Texture)) {
|
||||
effectiveCso = texRecord->Params.BuiltinSampler;
|
||||
}
|
||||
}
|
||||
const MG_Pipe::MGPipeSamplerCsoRecord* effectiveCsoRecord =
|
||||
PipeSamplerCsoRecordForHandle(effectiveCso);
|
||||
|
||||
if (samplerBinding.lodBiasLocation >= 0) {
|
||||
const Float lodBias =
|
||||
effectiveCsoRecord != nullptr ? effectiveCsoRecord->Params.lodBias : 0.0f;
|
||||
if (lodBias != samplerBinding.lastAssignedLodBias) {
|
||||
g_GLESFuncs.glUniform1f(samplerBinding.lodBiasLocation, lodBias);
|
||||
samplerBinding.lastAssignedLodBias = lodBias;
|
||||
}
|
||||
}
|
||||
|
||||
const auto* viewRecord = PipeSamplerViewRecordForHandle(view.View);
|
||||
const Bool sampledIsTexture2D =
|
||||
viewRecord != nullptr &&
|
||||
static_cast<TextureTarget>(viewRecord->View.Target) == TextureTarget::Texture2D;
|
||||
if (samplerBinding.uniformType == GL_SAMPLER_2D && sampledIsTexture2D &&
|
||||
effectiveCsoRecord != nullptr &&
|
||||
NeedsRawDepthFetchSampler(
|
||||
effectiveCsoRecord->Params,
|
||||
static_cast<TextureInternalFormat>(viewRecord->View.InternalFormat))) {
|
||||
GetRawDepthFetchSampler()->Bind(unit);
|
||||
MGLOG_D("Using raw depth fetch sampler on unit %d.", unit);
|
||||
} else if (!MG_Pipe::MGPipeHandleIsNull(unitSampler)) {
|
||||
// THE MINT IS GONE (§4.2): the unit's sampler is the CSO's own
|
||||
// twin, the same one BindCurrentUnitSamplers binds, so the two
|
||||
// arms cannot hand a unit back and forth between two driver
|
||||
// samplers - and there is no arm left that creates a twin from a
|
||||
// frontend SamplerObject.
|
||||
if (auto* csoTwin = SamplerImpl::ResolveSamplerCsoTwin(unitSampler)) {
|
||||
csoTwin->Bind(unit);
|
||||
}
|
||||
} else {
|
||||
SamplerImpl::UnbindSampler(unit);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
auto& textureUnit = MGB_CTX->GetTextureUnitObject(unit);
|
||||
auto& samplerObject = textureUnit.GetSamplerObject();
|
||||
const auto& texture2D =
|
||||
@@ -8725,6 +9113,55 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return true;
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5e (tx2): the inverse of MG_Pipe::MGPipeResourceTargetForTextureTarget, for the two
|
||||
// descriptor reads this file makes (Managers.cpp has the same inverse beside the storage
|
||||
// sync; it is file-local there, and a header for eleven cases would be the wrong trade).
|
||||
static TextureTarget TextureTargetForPipeResourceTarget(Uint8 pipeResourceTarget) {
|
||||
switch (static_cast<MG_Pipe::MGPipeResourceTarget>(pipeResourceTarget)) {
|
||||
case MG_Pipe::MGPipeResourceTarget::Tex1D: return TextureTarget::Texture1D;
|
||||
case MG_Pipe::MGPipeResourceTarget::Tex2D: return TextureTarget::Texture2D;
|
||||
case MG_Pipe::MGPipeResourceTarget::Tex3D: return TextureTarget::Texture3D;
|
||||
case MG_Pipe::MGPipeResourceTarget::Tex1DArray: return TextureTarget::Texture1DArray;
|
||||
case MG_Pipe::MGPipeResourceTarget::Tex2DArray: return TextureTarget::Texture2DArray;
|
||||
case MG_Pipe::MGPipeResourceTarget::TexCube: return TextureTarget::TextureCubeMap;
|
||||
case MG_Pipe::MGPipeResourceTarget::TexCubeArray: return TextureTarget::TextureCubeMapArray;
|
||||
case MG_Pipe::MGPipeResourceTarget::Tex2DMS: return TextureTarget::Texture2DMultisample;
|
||||
case MG_Pipe::MGPipeResourceTarget::Tex2DMSArray: return TextureTarget::Texture2DMultisampleArray;
|
||||
case MG_Pipe::MGPipeResourceTarget::TexRect: return TextureTarget::TextureRectangle;
|
||||
case MG_Pipe::MGPipeResourceTarget::TexBuffer: return TextureTarget::TextureBuffer;
|
||||
default: return TextureTarget::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
// P5c (hd), P5e (tx2): the verification half, with the RECORD passed in instead of re-read
|
||||
// from the verb stash - the caller has it and the two must not disagree about which texture
|
||||
// is being verified.
|
||||
static void EnsureGenerateMipmapStorageDescribed(const MG_Pipe::MGPipeResourceRecord& record) {
|
||||
const auto& desc = record.Desc;
|
||||
if (desc.Width == 0 || desc.Levels == 0) {
|
||||
MG_Pipe::MGPipeUnmigratedEmulation("generate-mipmap-storage");
|
||||
}
|
||||
Uint maxDimension = desc.Width;
|
||||
const auto target = static_cast<MG_Pipe::MGPipeResourceTarget>(desc.Target);
|
||||
if (target != MG_Pipe::MGPipeResourceTarget::Tex1D &&
|
||||
target != MG_Pipe::MGPipeResourceTarget::Tex1DArray) {
|
||||
maxDimension = std::max(maxDimension, desc.Height);
|
||||
}
|
||||
if (target == MG_Pipe::MGPipeResourceTarget::Tex3D) {
|
||||
maxDimension = std::max(maxDimension, desc.Depth);
|
||||
}
|
||||
Uint requiredLevels = 1;
|
||||
while (maxDimension > 1) {
|
||||
maxDimension /= 2;
|
||||
++requiredLevels;
|
||||
}
|
||||
if (desc.Levels < requiredLevels) {
|
||||
MG_Pipe::MGPipeUnmigratedEmulation("generate-mipmap-storage");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static Bool EnsureGenerateMipmapStorageAllocated(const SharedPtr<MG_State::GLState::ITextureObject>& texture) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
@@ -8735,27 +9172,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// (MGPMipPlan::Res, the verb stash) - the client allocator is never probed (T2).
|
||||
const auto handle = MG_Pipe::MGPipeApplier().VerbMipRes;
|
||||
const auto* record = PipeTextureRecordForHandle(handle);
|
||||
if (record == nullptr || record->Desc.Width == 0 || record->Desc.Levels == 0) {
|
||||
MG_Pipe::MGPipeUnmigratedEmulation("generate-mipmap-storage");
|
||||
}
|
||||
const auto& desc = record->Desc;
|
||||
Uint maxDimension = desc.Width;
|
||||
const auto target = static_cast<MG_Pipe::MGPipeResourceTarget>(desc.Target);
|
||||
if (target != MG_Pipe::MGPipeResourceTarget::Tex1D &&
|
||||
target != MG_Pipe::MGPipeResourceTarget::Tex1DArray) {
|
||||
maxDimension = std::max(maxDimension, desc.Height);
|
||||
}
|
||||
if (target == MG_Pipe::MGPipeResourceTarget::Tex3D) {
|
||||
maxDimension = std::max(maxDimension, desc.Depth);
|
||||
}
|
||||
Uint requiredLevels = 1;
|
||||
while (maxDimension > 1) {
|
||||
maxDimension /= 2;
|
||||
++requiredLevels;
|
||||
}
|
||||
if (desc.Levels < requiredLevels) {
|
||||
if (record == nullptr) {
|
||||
MG_Pipe::MGPipeUnmigratedEmulation("generate-mipmap-storage");
|
||||
}
|
||||
EnsureGenerateMipmapStorageDescribed(*record);
|
||||
return false; // No server-side shadow allocation was necessary.
|
||||
}
|
||||
#endif
|
||||
@@ -8825,6 +9245,80 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
class ScopedDetachedTextureFramebufferAttachments {
|
||||
public:
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5e (tx2). THE SAME SWEEP, KEYED BY THE TEXTURE'S HANDLE.
|
||||
//
|
||||
// The frontend constructor below answers "which framebuffers attach this texture" by
|
||||
// walking every live FBO twin's frontend FramebufferObject and comparing attachment
|
||||
// SharedPtrs - kimi audit row 63, an unmemoised walk of client objects. The applier
|
||||
// already holds the answer: every framebuffer's record carries its eleven MGPSurfaces
|
||||
// and each one names its texture BY HANDLE (`MGPSurface::Res`), so the comparison is
|
||||
// {slot, gen} against {slot, gen} and the level / layered / upload-target details the
|
||||
// detach needs come off the same surface.
|
||||
//
|
||||
// This is the same reverse question fb's texture -> FBO-slot index answers for its
|
||||
// attachment sync; here it is asked over the records directly, because the walk is on a
|
||||
// rare path (per glGenerateMipmap) and does not need an index to be cheap.
|
||||
explicit ScopedDetachedTextureFramebufferAttachments(MG_Pipe::MGPipeHandle textureHandle) {
|
||||
if (MG_Pipe::MGPipeHandleIsNull(textureHandle)) return;
|
||||
auto* twin = TextureImpl::ResolveTextureTwin(textureHandle);
|
||||
if (twin == nullptr) return;
|
||||
const GLuint backendTextureId = twin->GetBackendTextureId();
|
||||
const auto& st = MG_Pipe::MGPipeApplier();
|
||||
|
||||
FramebufferImpl::g_backendFramebufferObjects.ForEachLive(
|
||||
[&](MG_Pipe::MGPipeHandle fbo,
|
||||
const SharedPtr<FramebufferImpl::BackendFramebufferObject>& backendFBO) {
|
||||
if (!backendFBO) return;
|
||||
const auto* record = st.FramebufferRecordFor(fbo);
|
||||
if (record == nullptr || record->IsDefault != 0) return;
|
||||
|
||||
const auto detachPoint = [&](const MG_Pipe::MGPSurface& surface,
|
||||
FramebufferAttachmentType frontendType) {
|
||||
if (surface.Kind != MG_Pipe::kMGPipeSurfaceKindTexture) return;
|
||||
if (surface.Res.Slot != textureHandle.Slot || surface.Res.Gen != textureHandle.Gen) {
|
||||
return;
|
||||
}
|
||||
GLenum backendAttachment = GL_NONE;
|
||||
if (frontendType >= FramebufferAttachmentType::Color0 &&
|
||||
frontendType <= FramebufferAttachmentType::Color31) {
|
||||
backendAttachment = backendFBO->GetBackendAttachmentType(frontendType);
|
||||
} else {
|
||||
backendAttachment = MG_Util::ConvertFramebufferAttachmentTypeToGLEnum(frontendType);
|
||||
}
|
||||
if (backendAttachment == GL_NONE || backendAttachment == GL_UNKNOWN_MGL) return;
|
||||
|
||||
GLenum textureTarget = TextureImpl::ConvertTextureUploadTargetToBackendGLEnum(
|
||||
static_cast<TextureUploadTarget>(surface.UploadTarget));
|
||||
if (textureTarget == GL_UNKNOWN_MGL) {
|
||||
textureTarget = TextureImpl::ConvertTextureTargetToBackendGLEnum(
|
||||
static_cast<TextureTarget>(surface.TextureTarget));
|
||||
}
|
||||
const GLuint backendFBOId = backendFBO->GetBackendFramebufferId();
|
||||
FramebufferImpl::BindFramebufferId(GL_DRAW_FRAMEBUFFER, backendFBOId);
|
||||
if (surface.Layered != 0) {
|
||||
g_GLESFuncs.glFramebufferTexture(GL_DRAW_FRAMEBUFFER, backendAttachment, 0, 0);
|
||||
} else {
|
||||
g_GLESFuncs.glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, backendAttachment,
|
||||
textureTarget, 0, 0);
|
||||
}
|
||||
ClearGLErrors();
|
||||
m_detachedAttachments.push_back({backendFBOId, backendAttachment, textureTarget,
|
||||
backendTextureId, static_cast<GLint>(surface.Level),
|
||||
surface.Layered != 0});
|
||||
};
|
||||
|
||||
for (SizeT i = 0; i < 8; ++i) {
|
||||
detachPoint(record->Color[i],
|
||||
static_cast<FramebufferAttachmentType>(
|
||||
static_cast<SizeT>(FramebufferAttachmentType::Color0) + i));
|
||||
}
|
||||
detachPoint(record->Depth, FramebufferAttachmentType::Depth);
|
||||
detachPoint(record->Stencil, FramebufferAttachmentType::Stencil);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
explicit ScopedDetachedTextureFramebufferAttachments(
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& texture) {
|
||||
if (texture == nullptr) {
|
||||
@@ -9146,6 +9640,46 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
g_GLESFuncs.glBindTexture(dstTarget, cachedBound ? cachedBound->GetBackendTextureId() : 0);
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5e (tx2): the two CPU-blit mip chains, driven by the RECORD the verb named. What changes
|
||||
// against the P5c arms below is only the resolution: those took the frontend texture and
|
||||
// re-derived its handle with a scoped `HandleOf` probe (the two sites §4.4 listed as
|
||||
// barriered), and the handle is now simply passed in - the extent derivation, the per-level
|
||||
// blits and the format question are §1's descriptor reads exactly as P5c wrote them.
|
||||
static void GenerateDepthTexture2DMipmapByRecord(const MG_Pipe::MGPipeResourceRecord& record,
|
||||
const SharedPtr<TextureImpl::BackendTextureObject>& backendTexture) {
|
||||
const auto& desc = record.Desc;
|
||||
const GLuint textureId = backendTexture->GetBackendTextureId();
|
||||
for (Uint32 level = 1; level < desc.Levels; ++level) {
|
||||
const IntVec3 srcSize = MG_Remote::Server::StagedTextureMipExtent(desc.Target, desc.Width, desc.Height,
|
||||
desc.Depth, level - 1);
|
||||
const IntVec3 dstSize = MG_Remote::Server::StagedTextureMipExtent(desc.Target, desc.Width, desc.Height,
|
||||
desc.Depth, level);
|
||||
BlitDepthTexture2D(textureId, static_cast<GLint>(level - 1), 0, 0,
|
||||
static_cast<GLsizei>(srcSize.x()), static_cast<GLsizei>(srcSize.y()), textureId,
|
||||
static_cast<GLint>(level), 0, 0, static_cast<GLsizei>(dstSize.x()),
|
||||
static_cast<GLsizei>(dstSize.y()));
|
||||
}
|
||||
}
|
||||
|
||||
static void GenerateColorTexture2DMipmapByRecord(const MG_Pipe::MGPipeResourceRecord& record,
|
||||
const SharedPtr<TextureImpl::BackendTextureObject>& backendTexture) {
|
||||
const auto& desc = record.Desc;
|
||||
const GLenum filter =
|
||||
IsIntegerColorFormat(static_cast<TextureInternalFormat>(desc.InternalFormat)) ? GL_NEAREST : GL_LINEAR;
|
||||
const GLuint textureId = backendTexture->GetBackendTextureId();
|
||||
for (Uint32 level = 1; level < desc.Levels; ++level) {
|
||||
const IntVec3 srcSize = MG_Remote::Server::StagedTextureMipExtent(desc.Target, desc.Width, desc.Height,
|
||||
desc.Depth, level - 1);
|
||||
const IntVec3 dstSize = MG_Remote::Server::StagedTextureMipExtent(desc.Target, desc.Width, desc.Height,
|
||||
desc.Depth, level);
|
||||
BlitColorTexture2D(textureId, static_cast<GLint>(level - 1), 0, 0, static_cast<GLsizei>(srcSize.x()),
|
||||
static_cast<GLsizei>(srcSize.y()), textureId, static_cast<GLint>(level), 0, 0,
|
||||
static_cast<GLsizei>(dstSize.x()), static_cast<GLsizei>(dstSize.y()), filter);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void GenerateDepthTexture2DMipmap(
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& texture,
|
||||
const SharedPtr<TextureImpl::BackendTextureObject>& backendTexture) {
|
||||
@@ -9158,36 +9692,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
auto* mipmapTexture = dynamic_cast<MG_State::GLState::TextureObjectMipmap*>(texture.get());
|
||||
MOBILEGL_ASSERT(mipmapTexture != nullptr, "Depth mipmap generation requires mipmap storage.");
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (tx): the per-level extents are §1's derivation from the descriptor under an
|
||||
// active transport - GetMipmapTexelSize is the client's per-level shape and the apply
|
||||
// thread may not name it (rule E). Texture2D only, so x and y shrink and z stays 1.
|
||||
// The record resolution is the same registry lookup texture sync and
|
||||
// EnsureGenerateMipmapStorageAllocated's disaggregated arm already make.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named debt inside
|
||||
// the scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
const auto pushedHandle = TextureImpl::g_backendTextureObjects.HandleOf(texture.get());
|
||||
const auto* pushedRecord = PipeTextureRecordForHandle(pushedHandle);
|
||||
if (pushedRecord == nullptr || pushedRecord->Desc.Width == 0 || pushedRecord->Desc.Levels == 0) {
|
||||
MG_Pipe::MGPipeUnmigratedEmulation("generate-mipmap-shape");
|
||||
}
|
||||
const auto& desc = pushedRecord->Desc;
|
||||
const GLuint textureId = backendTexture->GetBackendTextureId();
|
||||
for (Uint32 level = 1; level < desc.Levels; ++level) {
|
||||
const IntVec3 srcSize = MG_Remote::Server::StagedTextureMipExtent(
|
||||
desc.Target, desc.Width, desc.Height, desc.Depth, level - 1);
|
||||
const IntVec3 dstSize = MG_Remote::Server::StagedTextureMipExtent(
|
||||
desc.Target, desc.Width, desc.Height, desc.Depth, level);
|
||||
BlitDepthTexture2D(textureId, static_cast<GLint>(level - 1), 0, 0,
|
||||
static_cast<GLsizei>(srcSize.x()), static_cast<GLsizei>(srcSize.y()),
|
||||
textureId, static_cast<GLint>(level), 0, 0,
|
||||
static_cast<GLsizei>(dstSize.x()), static_cast<GLsizei>(dstSize.y()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
// P5e (tx2): the transport arm that stood here - a scoped `HandleOf(texture.get())`
|
||||
// probe to re-derive a handle the generate_mipmap record already carried (CONTRACT-P5E
|
||||
// §4.4's site 9161) - is GenerateDepthTexture2DMipmapByRecord above, reached from
|
||||
// GenerateMipmapByRecord with the handle passed in. This overload is the monolith body.
|
||||
|
||||
const Uint mipLevelCount = mipmapTexture->GetMipmapLevelCount();
|
||||
MOBILEGL_ASSERT(mipLevelCount > 0, "Depth mipmap generation requires allocated storage.");
|
||||
@@ -9215,37 +9723,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
auto* mipmapTexture = dynamic_cast<MG_State::GLState::TextureObjectMipmap*>(texture.get());
|
||||
MOBILEGL_ASSERT(mipmapTexture != nullptr, "Color mipmap generation requires mipmap storage.");
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (tx): GenerateDepthTexture2DMipmap's descriptor arm, for the color filter path -
|
||||
// same §1 extent derivation, same registry resolution, same refusal when the handle
|
||||
// arm has no record to read.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named debt inside
|
||||
// the scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
const auto pushedHandle = TextureImpl::g_backendTextureObjects.HandleOf(texture.get());
|
||||
const auto* pushedRecord = PipeTextureRecordForHandle(pushedHandle);
|
||||
if (pushedRecord == nullptr || pushedRecord->Desc.Width == 0 || pushedRecord->Desc.Levels == 0) {
|
||||
MG_Pipe::MGPipeUnmigratedEmulation("generate-mipmap-shape");
|
||||
}
|
||||
const auto& desc = pushedRecord->Desc;
|
||||
const GLenum filter =
|
||||
IsIntegerColorFormat(static_cast<TextureInternalFormat>(desc.InternalFormat)) ? GL_NEAREST
|
||||
: GL_LINEAR;
|
||||
const GLuint textureId = backendTexture->GetBackendTextureId();
|
||||
for (Uint32 level = 1; level < desc.Levels; ++level) {
|
||||
const IntVec3 srcSize = MG_Remote::Server::StagedTextureMipExtent(
|
||||
desc.Target, desc.Width, desc.Height, desc.Depth, level - 1);
|
||||
const IntVec3 dstSize = MG_Remote::Server::StagedTextureMipExtent(
|
||||
desc.Target, desc.Width, desc.Height, desc.Depth, level);
|
||||
BlitColorTexture2D(textureId, static_cast<GLint>(level - 1), 0, 0,
|
||||
static_cast<GLsizei>(srcSize.x()), static_cast<GLsizei>(srcSize.y()),
|
||||
textureId, static_cast<GLint>(level), 0, 0,
|
||||
static_cast<GLsizei>(dstSize.x()), static_cast<GLsizei>(dstSize.y()), filter);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
// P5e (tx2): the transport arm that stood here is GenerateColorTexture2DMipmapByRecord
|
||||
// above (CONTRACT-P5E §4.4's site 9216); this overload is the monolith body.
|
||||
|
||||
const Uint mipLevelCount = mipmapTexture->GetMipmapLevelCount();
|
||||
MOBILEGL_ASSERT(mipLevelCount > 0, "Color mipmap generation requires allocated storage.");
|
||||
@@ -9560,6 +10039,65 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return true;
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5e (tx2), CONTRACT-P5E §5.2: glGenerateMipmap's body once the texture came from
|
||||
// VerbMipRes. Same four arms as the frontend body, with every shape question answered from
|
||||
// the descriptor.
|
||||
//
|
||||
// RULING 14, AND WHY THE ROW IS NOT FLIPPED HERE. The unit read this arm retires is what the
|
||||
// wait existed for, but ONE frontend contact is left on the path and it is not tx2's to
|
||||
// retire: the RGB16F/RGB32F CPU filter reaches into the client's level shadows
|
||||
// (GenerateThreeChannelFloatMipmapOnCpu, kimi audit row 61) and P8/P9 own the pull. So the
|
||||
// brief's escape applies - the arm is not complete, the row stays kWaitApplied and the flip
|
||||
// is listed as trailing - and the record stays barriered, which is what keeps the CPU arm's
|
||||
// refusal a refusal rather than a wrong picture.
|
||||
static void GenerateMipmapByRecord(GLenum target, MG_Pipe::MGPipeHandle mipRes,
|
||||
const MG_Pipe::MGPipeResourceRecord& record,
|
||||
const SharedPtr<TextureImpl::BackendTextureObject>& backendTexture) {
|
||||
const auto format = static_cast<TextureInternalFormat>(record.Desc.InternalFormat);
|
||||
const auto textureTarget = TextureTargetForPipeResourceTarget(record.Desc.Target);
|
||||
|
||||
if (format == TextureInternalFormat::R11FG11FB10F || IsDepthOnlyFormat(format) ||
|
||||
format == TextureInternalFormat::RGB16F || format == TextureInternalFormat::RGB32F) {
|
||||
// The storage verification EnsureGenerateMipmapStorageAllocated's disaggregated arm
|
||||
// already makes, with the record passed in rather than re-read from VerbMipRes: the
|
||||
// frontend defined the generated chain before emitting this verb, so the server only
|
||||
// checks that the descriptor it was given can carry it.
|
||||
EnsureGenerateMipmapStorageDescribed(record);
|
||||
}
|
||||
if (format == TextureInternalFormat::RGB16F || format == TextureInternalFormat::RGB32F) {
|
||||
// The CPU filter reads and writes the CLIENT's level shadows. It already aborts
|
||||
// three frames deep at the MipmapStorage guard under a transport; naming it here
|
||||
// says which emulation, which is what the guard cannot.
|
||||
MG_Pipe::MGPipeUnmigratedEmulation("generate-mipmap-cpu-filter");
|
||||
}
|
||||
if (IsDepthOnlyFormat(format)) {
|
||||
GenerateDepthTexture2DMipmapByRecord(record, backendTexture);
|
||||
return;
|
||||
}
|
||||
if (format == TextureInternalFormat::R11FG11FB10F && textureTarget == TextureTarget::Texture2D) {
|
||||
GenerateColorTexture2DMipmapByRecord(record, backendTexture);
|
||||
return;
|
||||
}
|
||||
|
||||
const GLenum backendTarget =
|
||||
TextureImpl::ConvertTextureTargetToBackendGLEnum(MG_Util::ConvertGLEnumToTextureTarget(target));
|
||||
// TempTextureUnit, not the active unit: the scratch bind is the backend's own and
|
||||
// BindCurrentTextures re-establishes the sampling bindings regardless. The ACTIVE unit
|
||||
// was never anything but the frontend's way of naming the texture, and the record names
|
||||
// it now.
|
||||
backendTexture->Bind(backendTarget, TextureImpl::TempTextureUnit);
|
||||
// ANGLE/Mesa may validate the currently bound FBO while generating mipmaps, so the
|
||||
// source texture is detached from every framebuffer that attaches it - by handle, over
|
||||
// the applier's own framebuffer records.
|
||||
ScopedDetachedTextureFramebufferAttachments detachedAttachments(mipRes);
|
||||
ScopedCompleteFramebufferBinding completeFramebuffer;
|
||||
ClearGLErrors();
|
||||
g_GLESFuncs.glGenerateMipmap(backendTarget);
|
||||
RecordGLError("glGenerateMipmap", backendTarget, format);
|
||||
}
|
||||
#endif
|
||||
|
||||
void PatchParameteri(GLenum pname, GLint value) {
|
||||
if (g_GLESFuncs.glPatchParameteri == nullptr) return;
|
||||
g_GLESFuncs.glPatchParameteri(pname, value);
|
||||
@@ -9568,6 +10106,32 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
void GenerateMipmap(GLenum target) {
|
||||
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER
|
||||
DebugImpl::OpenGLScopeMarker marker(__func__);
|
||||
#endif
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5e (tx2), CONTRACT-P5E §5.2 / ruling 14 (ID-92's neighbour): GENERATE_MIPMAP RESOLVES
|
||||
// ITS TEXTURE FROM VerbMipRes, NEVER FROM THE ACTIVE UNIT.
|
||||
//
|
||||
// The verb's own record names the texture (MGPMipPlan::Res, stashed as VerbMipRes) and
|
||||
// EnsureGenerateMipmapStorageAllocated's disaggregated arm has read it since P5c. What
|
||||
// stood here instead was GetActiveTextureUnit + GetTextureUnitObject + the target's
|
||||
// binding slot - three BARRIER_PULLED reads to re-derive a handle the record carried -
|
||||
// and they are the ONLY reason this row still waits: ClientSession.cpp parks the client
|
||||
// so that the unit it reads is the unit the call was made on. With the unit read gone the
|
||||
// wait has nothing left to protect, which is what lets PipeCalls.def's WaitClass column
|
||||
// for GenerateMipmap move to kWaitNone.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
const auto mipRes = MG_Pipe::MGPipeApplier().VerbMipRes;
|
||||
auto& backendTexture = TextureImpl::SyncTextureToBackendByHandle(mipRes);
|
||||
const auto* record = PipeTextureRecordForHandle(mipRes);
|
||||
if (!backendTexture || record == nullptr) {
|
||||
MGLOG_E_ONCE("MGPipe: generate_mipmap names texture {%u, %u}, which has no applier "
|
||||
"record or no driver texture; the call is dropped",
|
||||
mipRes.Slot, mipRes.Gen);
|
||||
return;
|
||||
}
|
||||
GenerateMipmapByRecord(target, mipRes, *record, backendTexture);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
auto unitIndex = MGB_CTX->GetActiveTextureUnit();
|
||||
auto& unit = MGB_CTX->GetTextureUnitObject(unitIndex);
|
||||
|
||||
@@ -5874,6 +5874,59 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return m_backendTextureId;
|
||||
}
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P5e (tx2), CONTRACT-P5E §5.2 / scout G-S2-5. See the declaration.
|
||||
void BackendTextureObject::RequireImageBindableStorageByHandle(
|
||||
MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPipeResourceRecord& record) {
|
||||
if (m_imageBindableStorageRequired) {
|
||||
return;
|
||||
}
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// THE REFUSAL, AT THE ENTRY. The predicate is the SAME one the frontend overload
|
||||
// hoisted to ahead of its first guarded contact - "would any level actually be
|
||||
// replayed", answered from the server's own staged-texture store - so this changes
|
||||
// WHERE the abort happens and not WHETHER it happens. What it buys is that the
|
||||
// reason can still be stated: three frames deeper it was a MarkStorageDirty guard
|
||||
// naming "texture-legacy-arm", which says nothing about image bindability.
|
||||
//
|
||||
// A texture with nothing defined yet is NOT refused: it is allocated image-bindable
|
||||
// up front and pulls nothing, which is exactly what ImageBindableHint exists to make
|
||||
// the common case.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
auto& store = MG_Remote::Server::ServerStagedTexture();
|
||||
const Uint64 key = MG_Remote::Server::StagedTextureStore::KeyForHandle(res);
|
||||
Bool anyLevelWouldReplay = false;
|
||||
for (const auto& uploadTarget :
|
||||
BufferImpl::StagedUploadTargetsForPipeTarget(record.Desc.Target)) {
|
||||
for (Uint32 level = 0; level < record.Desc.Levels; ++level) {
|
||||
if (store.IsLevelDefined(key, static_cast<Uint16>(uploadTarget),
|
||||
static_cast<Uint16>(level))) {
|
||||
anyLevelWouldReplay = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (anyLevelWouldReplay) break;
|
||||
}
|
||||
if (anyLevelWouldReplay) {
|
||||
MG_Pipe::MGPipeUnmigratedEmulation("image-bindable-redirty");
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)res;
|
||||
(void)record;
|
||||
#endif
|
||||
// Nothing to replay, so the transition is just the two sticky flags plus the
|
||||
// parameter resync the widening's swizzle needs - the whole of the frontend
|
||||
// overload's first half, with its second half refused above.
|
||||
m_imageBindableStorageRequired = true;
|
||||
m_isInitialized = false;
|
||||
// The widened carrier's swizzle override, exactly as the frontend overload's tail
|
||||
// sets it and for the same reason: the parameter sync is gated on a params version
|
||||
// this transition does not move.
|
||||
m_forceTextureParamsResync = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void BackendTextureObject::RequireImageBindableStorage(
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject) {
|
||||
if (m_imageBindableStorageRequired) {
|
||||
@@ -6802,6 +6855,44 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P5e (tx2): the GL name a log line wants, from whichever side of the arm can answer it.
|
||||
// Desc.GlNameForDiag is the declared diagnostics carrier (MGPipeTypes.h) and it is the
|
||||
// ONLY thing the by-handle arm may say about a texture's frontend name - a GL name is
|
||||
// never an identity, never a memo key and never part of a hash (section 4.2.1). The
|
||||
// twenty-odd GetExternalIndex() log reads this family carried (scout G-S2-4) all become
|
||||
// this call.
|
||||
static Uint32 TextureDiagName(const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject,
|
||||
const MG_Pipe::MGPipeResourceRecord* record) {
|
||||
if (stateTextureObject) return static_cast<Uint32>(stateTextureObject->GetExternalIndex());
|
||||
return record != nullptr ? record->Desc.GlNameForDiag : 0u;
|
||||
}
|
||||
// True while this twin has NO noted handle, i.e. it is the monolith-glue half and the frontend
|
||||
// object below is the only thing that can answer. A macro and not a member function because the
|
||||
// pull build has no such member at all and this has to fold to a constant there (D-P: the pull
|
||||
// build's preprocessed text may not gain a call).
|
||||
#define MGB_TEXTURE_HANDLE_ARM_OFF(twin) (MG_Pipe::MGPipeHandleIsNull((twin).PushedSyncHandle()))
|
||||
// P5e (tx2), CONTRACT-P5E §5.2: THE TEXTURE'S TARGET, from the descriptor whenever a record was
|
||||
// resolved. The two live `GetTarget()` reads left INSIDE the handle arm (scout G-S2-2, the
|
||||
// parameter sync and the built-in sampler sync) read Desc.Target here, through the same
|
||||
// StagedTextureTargetForPipeTarget mapping the storage sync already routes through - so all
|
||||
// three bodies answer the question from one carrier rather than two authorities.
|
||||
#define MGB_TEXPARAM_TARGET(rec, obj) \
|
||||
((rec) != nullptr ? BufferImpl::StagedTextureTargetForPipeTarget((rec)->Desc.Target) : (obj)->GetTarget())
|
||||
// The ~20 GetExternalIndex() reads this family makes in LOG LINES (scout G-S2-4). The pull
|
||||
// expansion is the pre-P5e expression with one pair of parentheses around the receiver.
|
||||
#define MGB_TEXTURE_DIAG_NAME(rec, obj) (TextureDiagName((obj), (rec)))
|
||||
// The storage KIND, from the descriptor whenever one was resolved - the backstop arm of the
|
||||
// storage switch used to ask the frontend object for a value it had just read from the record.
|
||||
#define MGB_TEXTURE_STORAGE_KIND(rec, obj) \
|
||||
((rec) != nullptr ? static_cast<TextureStorageType>((rec)->Desc.StorageKind) : (obj)->GetStorageType())
|
||||
#else
|
||||
#define MGB_TEXTURE_HANDLE_ARM_OFF(twin) (true)
|
||||
#define MGB_TEXPARAM_TARGET(rec, obj) ((obj)->GetTarget())
|
||||
#define MGB_TEXTURE_DIAG_NAME(rec, obj) ((obj)->GetExternalIndex())
|
||||
#define MGB_TEXTURE_STORAGE_KIND(rec, obj) ((obj)->GetStorageType())
|
||||
#endif
|
||||
|
||||
// The ES entry point for EXT/OES_texture_view, whichever spelling this driver brought.
|
||||
// Callers must have checked g_GLESCapabilities.SupportsTextureView first - the capability
|
||||
// is the extension AND the pointer, because eglGetProcAddress hands back live-looking
|
||||
@@ -6918,6 +7009,60 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
StampViewSyncKeys(stateTextureObject);
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5e (tx2): see the declaration for the split between what the record answers and what
|
||||
// the wire still cannot say about a view.
|
||||
void BackendTextureObject::SyncTextureViewToBackendByRecord(
|
||||
MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPipeResourceRecord& record,
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject) {
|
||||
if (!g_GLESCapabilities.SupportsTextureView) {
|
||||
// Unreachable through the API - the frontend refuses glTextureView without
|
||||
// GL_ARB_texture_view - and kept for the reason the frontend arm keeps its twin.
|
||||
MGLOG_E_ONCE("Texture view %u reached the backend on a driver without "
|
||||
"EXT/OES_texture_view.",
|
||||
record.Desc.GlNameForDiag);
|
||||
return;
|
||||
}
|
||||
// The storage owner by handle, through the same by-handle entry every other draw-path
|
||||
// texture takes: no frontend object, no allocator probe.
|
||||
const SharedPtr<BackendTextureObject> storageBackendObject =
|
||||
SyncTextureToBackendByHandle(record.Desc.ViewOf, m_imageBindableStorageRequired);
|
||||
if (!storageBackendObject) {
|
||||
MGLOG_E_ONCE("Failed to sync the storage texture {%u, %u} of view %u on the handle arm.",
|
||||
record.Desc.ViewOf.Slot, record.Desc.ViewOf.Gen, record.Desc.GlNameForDiag);
|
||||
return;
|
||||
}
|
||||
const Uint storageBackendTextureId = storageBackendObject->GetBackendTextureId();
|
||||
if (storageBackendTextureId == 0) {
|
||||
MGLOG_D("Storage texture of view %u has no ES name yet.", record.Desc.GlNameForDiag);
|
||||
return;
|
||||
}
|
||||
if (m_isInitialized && m_viewSourceBackendTextureId == storageBackendTextureId) {
|
||||
// The steady case, and the only one this arm has to be fast at: one record read,
|
||||
// one integer compare and the serial stamp that keeps the aggregate clean gate
|
||||
// (IsDrawSyncCleanByRecord) answering true for the next draw.
|
||||
m_syncedResourceSerial = record.Serial;
|
||||
return;
|
||||
}
|
||||
// TRAILING (tx2 report, "left barriered"): the glTextureView CALL needs the view
|
||||
// window and MGPResourceDesc has no carrier for it. The frontend object is taken when
|
||||
// the monolith-glue note still reaches one - which is every case a single-process
|
||||
// split has - and the refusal is NAMED rather than silent for the case that does not.
|
||||
if (stateTextureObject) {
|
||||
SyncTextureViewToBackend(stateTextureObject);
|
||||
m_syncedResourceSerial = record.Serial;
|
||||
return;
|
||||
}
|
||||
MGLOG_E_ONCE("MGPipe: texture view {%u, %u} (GL %u) has to be (re-)created as a view of "
|
||||
"{%u, %u}, and MGPResourceDesc carries no view window - minLevel, "
|
||||
"numLevels, minLayer, numLayers have no wire field - so the by-handle arm "
|
||||
"cannot issue glTextureView. P5e leaves view CREATION on the frontend arm; "
|
||||
"this name will sample empty until it gets one.",
|
||||
res.Slot, res.Gen, record.Desc.GlNameForDiag, record.Desc.ViewOf.Slot,
|
||||
record.Desc.ViewOf.Gen);
|
||||
}
|
||||
#endif
|
||||
|
||||
// P4a (D-D5), the dirty-ownership inversion, applied at the two places the upload loops
|
||||
// below actually ask: "does this (uploadTarget, level) owe an upload" and "it has been
|
||||
// uploaded". ARCHITECTURE.md:253 puts the rect model and the emission cursor on the
|
||||
@@ -7109,11 +7254,59 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
void BackendTextureObject::SyncMipmapsToBackend(
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject) {
|
||||
if (!stateTextureObject) {
|
||||
// P5e (tx2): null IS the by-handle arm once a handle has been noted on this twin;
|
||||
// see the twin note in SyncBuiltinSamplerToBackend.
|
||||
if (!stateTextureObject && MGB_TEXTURE_HANDLE_ARM_OFF(*this)) {
|
||||
MGLOG_E_ONCE("State texture object is null, cannot sync to backend.");
|
||||
return;
|
||||
}
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P4a (D-B3/D-D5): on the handle arm the record IS the state this function reads.
|
||||
// P5e (tx2) hoists the resolution to the TOP of the function, because the view test
|
||||
// below needs it before anything else does; nothing else about it moves.
|
||||
//
|
||||
// The record pointer stays valid for the whole function: only a create/respecify for
|
||||
// a NEW slot grows MGPipeApplierState::TextureResources, and nothing this function
|
||||
// calls emits one. Pointers INTO record->PendingUploads do not - the consume is a
|
||||
// swap-and-pop - so every one of them is taken, used and dropped inside one level's
|
||||
// iteration, and never held across a consume.
|
||||
const MG_Pipe::MGPipeResourceRecord* pushedStorage = nullptr;
|
||||
MG_Pipe::MGPipeHandle pushedRes = MG_Pipe::kMGPipeNullHandle;
|
||||
if (TextureResourceSubsystemEnabled()) {
|
||||
pushedStorage = ResolveOwnRecord(stateTextureObject);
|
||||
pushedRes = !MG_Pipe::MGPipeHandleIsNull(m_pushedSyncHandle)
|
||||
? m_pushedSyncHandle
|
||||
: (pushedStorage != nullptr ? pushedStorage->Desc.Resource
|
||||
: MG_Pipe::kMGPipeNullHandle);
|
||||
if (pushedStorage == nullptr) {
|
||||
MGLOG_E_ONCE("MGPipe: texture %u has no applier record on the handle arm, so its "
|
||||
"storage and uploads cannot be driven from the pushed descriptor "
|
||||
"(handle {%u, %u})",
|
||||
TextureDiagName(stateTextureObject, pushedStorage), pushedRes.Slot,
|
||||
pushedRes.Gen);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5e (tx2), CONTRACT-P5E §5.2 (scout G-S2-2): THE VIEW TEST MOVES BEHIND THE
|
||||
// RECORD. `IsTextureView()` was the one frontend read this function made BEFORE it
|
||||
// had resolved anything at all, and Desc.ViewOf is the carrier - it names the storage
|
||||
// owner BY HANDLE, so both the question and its answer are server-owned.
|
||||
//
|
||||
// Ruling 1's arm: the push-monolith build keeps the frontend test below token for
|
||||
// token, because there the two answers are the same answer and the verify comparator
|
||||
// needs the frontend arm.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith && pushedStorage != nullptr &&
|
||||
!MG_Pipe::MGPipeHandleIsNull(pushedStorage->Desc.ViewOf)) {
|
||||
SyncTextureViewToBackendByRecord(pushedRes, *pushedStorage, stateTextureObject);
|
||||
return;
|
||||
}
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith && pushedStorage != nullptr) {
|
||||
// Not a view: fall through to the storage body with the frontend test skipped.
|
||||
} else
|
||||
#endif
|
||||
// A texture created by glTextureView owns no storage: the levels, the format and
|
||||
// every texel belong to the texture it views, and this name only has to be made to
|
||||
// ALIAS them. Everything below - storage allocation, respecification, per-level
|
||||
@@ -7138,24 +7331,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// consume is a swap-and-pop - so every one of them is taken, used and dropped
|
||||
// inside one level's iteration, and never held across a consume.
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
const MG_Pipe::MGPipeResourceRecord* pushedStorage = nullptr;
|
||||
MG_Pipe::MGPipeHandle pushedRes = MG_Pipe::kMGPipeNullHandle;
|
||||
if (TextureResourceSubsystemEnabled()) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): the sync arrives holding the frontend object
|
||||
// (the object-class rows) and resolves its handle by frontend identity -
|
||||
// named debt inside the scope, P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
pushedRes = g_backendTextureObjects.HandleOf(stateTextureObject.get());
|
||||
pushedStorage = PipeTextureRecordForHandle(pushedRes);
|
||||
if (pushedStorage == nullptr) {
|
||||
MGLOG_E_ONCE("MGPipe: texture %u has no applier record on the handle arm, so its "
|
||||
"storage and uploads cannot be driven from the pushed descriptor "
|
||||
"(handle {%u, %u})",
|
||||
stateTextureObject->GetExternalIndex(), pushedRes.Slot, pushedRes.Gen);
|
||||
return;
|
||||
}
|
||||
if (pushedStorage != nullptr) {
|
||||
// THE METADATA RESPECIFY, HONOURED (ID-18 M4, review M-2). BindMask and
|
||||
// ImageBindableHint are STICKY facts the client discovers AFTER allocation - a
|
||||
// texture first bound as a shader image - and an IMMUTABLE texture never has a
|
||||
@@ -7179,6 +7355,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
pushedStorage->Desc.ImageBindableHint != 0 ||
|
||||
(pushedStorage->Desc.BindMask & MG_Pipe::kMGPipeBindShaderImage) != 0;
|
||||
if (pushedWantsImageBindableStorage && !m_imageBindableStorageRequired) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5e (tx2), ruling 1's arm: under a transport the re-dirty half is a NAMED
|
||||
// refusal at its entry (§5.2) instead of a walk of the client's level shadows;
|
||||
// the push-monolith build keeps the frontend transition exactly as it is.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
RequireImageBindableStorageByHandle(pushedRes, *pushedStorage);
|
||||
} else
|
||||
#endif
|
||||
RequireImageBindableStorage(stateTextureObject);
|
||||
}
|
||||
|
||||
@@ -7225,7 +7409,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
MGLOG_D("Syncing texture mipmaps with backend ID %u to backend for state ID %u", m_backendTextureId,
|
||||
stateTextureObject->GetExternalIndex());
|
||||
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject));
|
||||
|
||||
GLenum target = ConvertTextureTargetToBackendGLEnum(MGB_TEXTURE_TARGET(stateTextureObject));
|
||||
auto targetInternal = MGB_TEXTURE_TARGET(stateTextureObject);
|
||||
@@ -7286,7 +7470,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
if (!anyDefined) {
|
||||
MGLOG_D("Texture object with ID: %u has no defined image level, skipping sync.",
|
||||
stateTextureObject->GetExternalIndex());
|
||||
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject));
|
||||
return;
|
||||
}
|
||||
} else
|
||||
@@ -7916,7 +8100,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
"different level pitches (%u/%u and %u/%u) - a pitch is "
|
||||
"the LEVEL's, so the carried pair is dropped and the "
|
||||
"extent is used instead",
|
||||
stateTextureObject->GetExternalIndex(),
|
||||
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject),
|
||||
static_cast<Uint>(level), firstRegion.SrcRowStride,
|
||||
firstRegion.SrcSliceStride, region.SrcRowStride,
|
||||
region.SrcSliceStride);
|
||||
@@ -7964,7 +8148,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
"%llu is not its own origin (%d,%d,%d) at the level's pitch "
|
||||
"(%llu expected) - the carried offsets are dropped and every "
|
||||
"rect is read from the shadow instead",
|
||||
stateTextureObject->GetExternalIndex(), static_cast<Uint>(level),
|
||||
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject), static_cast<Uint>(level),
|
||||
static_cast<unsigned long long>(region.SrcOffset), region.X,
|
||||
region.Y, region.Z,
|
||||
static_cast<unsigned long long>(derivedOffset));
|
||||
@@ -8283,32 +8467,48 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
case TextureStorageType::Buffer: {
|
||||
auto* textureBufferObject =
|
||||
static_cast<MG_State::GLState::TextureObjectBuffer*>(stateTextureObject.get());
|
||||
auto& slot = textureBufferObject->GetBufferBindingSlot();
|
||||
auto& buffer = slot.GetBoundObject();
|
||||
if (!buffer) {
|
||||
MGLOG_D("Texture buffer object with ID: %u has no bound buffer, skipping sync.",
|
||||
stateTextureObject->GetExternalIndex());
|
||||
return;
|
||||
}
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// M-2: THE BUFFER-TEXTURE FIELDS ARE THE DESCRIPTOR'S TOO - BufferForTexBuffer
|
||||
// names the backing store and BufOffset/BufSize name the window. The frontend
|
||||
// binding slot above is still what hands over the BufferObject, because
|
||||
// EnsureBufferResourceForHandle takes it as the one question P3a's record cannot
|
||||
// answer (an emulated persistent map, D-A4's `frontend` argument) - but WHICH
|
||||
// buffer, and which window of it, are read from the record.
|
||||
// P5e (tx2), scout R5 RESOLVED. THE BACKING BUFFER IS THE DESCRIPTOR'S, AND SO IS
|
||||
// ITS WINDOW - `Desc.BufferForTexBuffer` names the store and `Desc.BufOffset /
|
||||
// BufSize` name the range, so the frontend binding slot below is not read at all
|
||||
// on the handle arm.
|
||||
//
|
||||
// The one thing that argument had to answer is D-A4's `frontend` parameter of
|
||||
// EnsureBufferResourceForHandle, which existed for the emulated-persistent-map
|
||||
// question. The E note's reading is confirmed at this head: that function's three
|
||||
// frontend reads (Managers.cpp's MappedData / SyncPersistentMappedRange /
|
||||
// HasDefinedContent) are all inside its MONOLITH arm - under an active transport
|
||||
// it is client-free (kimi audit row 29) - so `nullptr` is not a hole, it is the
|
||||
// whole of the split answer, and it is what §4.2's Buffer row already prescribes
|
||||
// (`EnsureBufferResourceForHandle(nullptr, h)`).
|
||||
//
|
||||
// WHOLE-VS-RANGE likewise stops asking the buffer object for its size: the
|
||||
// sentinel kMGPipeWholeBuffer IS "the whole store", which is the question
|
||||
// `rangeSize == buffer->GetSize()` was spelling the long way round.
|
||||
MG_Pipe::MGPipeHandle pushedTexBuffer = MG_Pipe::kMGPipeNullHandle;
|
||||
if (pushedStorage != nullptr) {
|
||||
pushedTexBuffer = pushedStorage->Desc.BufferForTexBuffer;
|
||||
if (MG_Pipe::MGPipeHandleIsNull(pushedTexBuffer)) {
|
||||
MGLOG_E_ONCE("MGPipe: buffer texture %u's descriptor names no backing buffer on "
|
||||
"the handle arm, so it cannot be backed (handle {%u, %u})",
|
||||
stateTextureObject->GetExternalIndex(), pushedRes.Slot, pushedRes.Gen);
|
||||
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject), pushedRes.Slot, pushedRes.Gen);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const Bool texBufferByRecord =
|
||||
pushedStorage != nullptr && MGB_TEXTURE_HANDLE_ARM_OFF(*this) == false;
|
||||
#else
|
||||
const Bool texBufferByRecord = false;
|
||||
#endif
|
||||
auto bufferIndex = buffer->GetExternalIndex();
|
||||
static const SharedPtr<MG_State::GLState::BufferObject> kNoFrontendBuffer{};
|
||||
auto& buffer = texBufferByRecord ? kNoFrontendBuffer
|
||||
: textureBufferObject->GetBufferBindingSlot().GetBoundObject();
|
||||
if (!buffer && !texBufferByRecord) {
|
||||
MGLOG_D("Texture buffer object with ID: %u has no bound buffer, skipping sync.",
|
||||
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject));
|
||||
return;
|
||||
}
|
||||
auto bufferIndex = buffer ? buffer->GetExternalIndex() : 0u;
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// The memo key is the HANDLE's slot on the handle arm: a GL name is never an
|
||||
// identity (section 4.2.1), and the record's Serial covers a slot recycled at a
|
||||
@@ -8331,7 +8531,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
#endif
|
||||
if (!backendBufferResource || backendBufferResource->id == 0) {
|
||||
MGLOG_E_ONCE("Failed to sync backing buffer for texture buffer with ID: %u",
|
||||
stateTextureObject->GetExternalIndex());
|
||||
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8379,13 +8579,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
"shader declaring a samplerBuffer will fail to compile. MobileGL "
|
||||
"still advertises GL_MAX_TEXTURE_BUFFER_SIZE = %d because an "
|
||||
"OpenGL 4.x context may not report 0.",
|
||||
stateTextureObject->GetExternalIndex(), GetBufferTextureTierName(),
|
||||
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject), GetBufferTextureTierName(),
|
||||
g_GLESCapabilities.MaxTextureBufferSize);
|
||||
break;
|
||||
}
|
||||
MGLOG_D("Texture state changed significantly or not initialized, regenerating texture buffer with "
|
||||
"ID: %u, buffer ID: %u, buffer size: %zu, format: %s",
|
||||
m_backendTextureId, backendId, buffer->GetSize(),
|
||||
m_backendTextureId, backendId, buffer ? buffer->GetSize() : rangeSize,
|
||||
MG_Util::ConvertGLEnumToString(glInternalFormat).c_str());
|
||||
// A texture that names a window of the buffer needs the range form; the
|
||||
// whole-buffer forms report offset 0 and the buffer's current size, which
|
||||
@@ -8400,13 +8600,24 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
const SizeT rangeOffset = pushedStorage != nullptr
|
||||
? static_cast<SizeT>(pushedStorage->Desc.BufOffset)
|
||||
: textureBufferObject->GetBufferRangeOffset();
|
||||
// P5e (tx2): "the whole store" is the SENTINEL on the handle arm, not a size
|
||||
// compare against a frontend BufferObject this arm may not name. The monolith
|
||||
// arm keeps resolving the sentinel live, exactly as its comment says.
|
||||
const Bool wholeBufferRange =
|
||||
pushedStorage != nullptr
|
||||
? (pushedStorage->Desc.BufOffset == 0 &&
|
||||
pushedStorage->Desc.BufSize == MG_Pipe::kMGPipeWholeBuffer)
|
||||
: (textureBufferObject->GetBufferRangeOffset() == 0 &&
|
||||
textureBufferObject->GetBufferRangeSizeInBytes() == buffer->GetSize());
|
||||
const SizeT rangeSize =
|
||||
pushedStorage == nullptr
|
||||
? textureBufferObject->GetBufferRangeSizeInBytes()
|
||||
: (pushedStorage->Desc.BufSize == MG_Pipe::kMGPipeWholeBuffer
|
||||
? buffer->GetSize()
|
||||
? (buffer ? buffer->GetSize() : 0u)
|
||||
: static_cast<SizeT>(pushedStorage->Desc.BufSize));
|
||||
#else
|
||||
const Bool wholeBufferRange = textureBufferObject->GetBufferRangeOffset() == 0 &&
|
||||
textureBufferObject->GetBufferRangeSizeInBytes() == buffer->GetSize();
|
||||
const SizeT rangeOffset = textureBufferObject->GetBufferRangeOffset();
|
||||
const SizeT rangeSize = textureBufferObject->GetBufferRangeSizeInBytes();
|
||||
#endif
|
||||
@@ -8414,14 +8625,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// the unsuffixed entry points are the ES 3.2 core spelling, and a driver
|
||||
// whose buffer textures come from EXT/OES_texture_buffer exports the
|
||||
// suffixed ones instead. The dispatchers pick whichever this tier ships.
|
||||
if (rangeOffset == 0 && rangeSize == buffer->GetSize()) {
|
||||
if (wholeBufferRange) {
|
||||
CallTexBuffer(GL_TEXTURE_BUFFER, glInternalFormat, backendId);
|
||||
} else if (!CallTexBufferRange(GL_TEXTURE_BUFFER, glInternalFormat, backendId,
|
||||
static_cast<GLintptr>(rangeOffset),
|
||||
static_cast<GLsizeiptr>(rangeSize))) {
|
||||
MGLOG_W_ONCE("Texture buffer %u names a sub-range but the driver has no "
|
||||
"glTexBufferRange; binding the whole buffer instead",
|
||||
stateTextureObject->GetExternalIndex());
|
||||
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject));
|
||||
CallTexBuffer(GL_TEXTURE_BUFFER, glInternalFormat, backendId);
|
||||
}
|
||||
DebugImpl::ErrorLopper::Loop(
|
||||
@@ -8443,10 +8654,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (m_bufferImageSplitViewId == 0) {
|
||||
MGLOG_E_ONCE("Failed to generate the buffer-image split view for texture %u; "
|
||||
"its image binding will read the unsplit view.",
|
||||
stateTextureObject->GetExternalIndex());
|
||||
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject));
|
||||
} else {
|
||||
g_GLESFuncs.glBindTexture(GL_TEXTURE_BUFFER, m_bufferImageSplitViewId);
|
||||
if (rangeOffset == 0 && rangeSize == buffer->GetSize()) {
|
||||
if (wholeBufferRange) {
|
||||
CallTexBuffer(GL_TEXTURE_BUFFER, bufferImageSplitFormat, backendId);
|
||||
} else if (!CallTexBufferRange(GL_TEXTURE_BUFFER, bufferImageSplitFormat, backendId,
|
||||
static_cast<GLintptr>(rangeOffset),
|
||||
@@ -8470,8 +8681,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// renders wrong; throwing unwinds through the C GL ABI and kills the process.
|
||||
MGLOG_E_ONCE("DirectGLES texture sync: no upload path for storage type %d on texture %u; "
|
||||
"skipping this sync",
|
||||
static_cast<int>(stateTextureObject->GetStorageType()),
|
||||
stateTextureObject->GetExternalIndex());
|
||||
static_cast<int>(MGB_TEXTURE_STORAGE_KIND(pushedStorage, stateTextureObject)),
|
||||
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -8530,23 +8741,34 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
#endif
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
const SamplerParameters* BackendTextureObject::ResolvePushedBuiltinSampler(
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject) {
|
||||
// MONOLITH GLUE, named as such (HandleOfBuffer's shape): the Texture handle of an
|
||||
// object this backend still arrives holding. Under a real split the handle rides in
|
||||
// the payload; the client mints it off this object's lifetime id, so the allocator
|
||||
// resolves it here through the table's own single-entry front memo.
|
||||
// P5e (tx2), CONTRACT-P5E §5.2: THIS TWIN'S OWN RECORD. On the by-handle arm the handle
|
||||
// is already known - the caller resolved the record before it resolved this twin and
|
||||
// noted the handle on the twin itself - so the client allocator is not probed at all.
|
||||
// What is left below is the MONOLITH GLUE half (HandleOfBuffer's shape): a twin minted
|
||||
// from a frontend object and never adopted by handle still re-derives one.
|
||||
const MG_Pipe::MGPipeResourceRecord* BackendTextureObject::ResolveOwnRecord(
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject) const {
|
||||
MG_Pipe::MGPipeHandle res = m_pushedSyncHandle;
|
||||
if (MG_Pipe::MGPipeHandleIsNull(res)) {
|
||||
if (!stateTextureObject) return nullptr;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed resolution, named debt inside the
|
||||
// scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed resolution, named debt inside the
|
||||
// scope - the by-handle arm above is what retires it.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
const MG_Pipe::MGPipeHandle res = g_backendTextureObjects.HandleOf(stateTextureObject.get());
|
||||
const auto* record = PipeTextureRecordForHandle(res);
|
||||
res = g_backendTextureObjects.HandleOf(stateTextureObject.get());
|
||||
}
|
||||
return PipeTextureRecordForHandle(res);
|
||||
}
|
||||
|
||||
const SamplerParameters* BackendTextureObject::ResolvePushedBuiltinSampler(
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject,
|
||||
const MG_Pipe::MGPipeResourceRecord* record) {
|
||||
if (record == nullptr) {
|
||||
MGLOG_E_ONCE("MGPipe: texture %u has no applier record on the handle arm, so its "
|
||||
"built-in sampler cannot be pushed (handle {%u, %u})",
|
||||
stateTextureObject->GetExternalIndex(), res.Slot, res.Gen);
|
||||
TextureDiagName(stateTextureObject, record), m_pushedSyncHandle.Slot,
|
||||
m_pushedSyncHandle.Gen);
|
||||
return nullptr;
|
||||
}
|
||||
// NO set_texture_params HAS BEEN APPLIED FOR THIS TEXTURE YET, which is not the same
|
||||
@@ -8563,7 +8785,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (record->ParamsSerial == 0) {
|
||||
MGLOG_D("Texture %u has no set_texture_params record yet, so it has no built-in "
|
||||
"sampler to push.",
|
||||
stateTextureObject->GetExternalIndex());
|
||||
TextureDiagName(stateTextureObject, record));
|
||||
return nullptr;
|
||||
}
|
||||
// D-E1: kMGPipeNullHandle in a record that WAS written is Fatal{ProtocolCorruption}
|
||||
@@ -8576,14 +8798,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
MGLOG_E_ONCE("MGPipe: texture %u's set_texture_params names the null handle as its "
|
||||
"built-in sampler CSO; every texture owns a sampler object, so this "
|
||||
"record is malformed - declining the built-in sampler push",
|
||||
stateTextureObject->GetExternalIndex());
|
||||
TextureDiagName(stateTextureObject, record));
|
||||
return nullptr;
|
||||
}
|
||||
const auto* cso = PipeSamplerCsoRecordForHandle(builtin);
|
||||
if (cso == nullptr) {
|
||||
MGLOG_E_ONCE("MGPipe: texture %u's built-in sampler CSO {%u, %u} has no applier "
|
||||
"record - declining the built-in sampler push",
|
||||
stateTextureObject->GetExternalIndex(), builtin.Slot, builtin.Gen);
|
||||
TextureDiagName(stateTextureObject, record), builtin.Slot, builtin.Gen);
|
||||
return nullptr;
|
||||
}
|
||||
// THE HANDLE IS PART OF THE KEY, not just the serial. Sampler CSOs are
|
||||
@@ -8618,7 +8840,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
|
||||
if (!stateTextureObject) {
|
||||
// P5e (tx2): NULL IS THE BY-HANDLE ARM, not an error, once a handle has been noted on
|
||||
// this twin - the record and the built-in sampler's CSO answer everything below and
|
||||
// the frontend object is not consulted at all. Without a handle it is still the
|
||||
// caller's bug it always was.
|
||||
if (!stateTextureObject && MGB_TEXTURE_HANDLE_ARM_OFF(*this)) {
|
||||
MGLOG_E_ONCE("State texture object is null, cannot sync to backend.");
|
||||
return;
|
||||
}
|
||||
@@ -8635,8 +8861,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// measured at -2 bytes on this function, which is a G1 failure.
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
const SamplerParameters* pendingSamplerParams = nullptr;
|
||||
// P5e (tx2): resolved ONCE and shared with the target read below, which used to be
|
||||
// the frontend `GetTarget()` this arm was not allowed to make.
|
||||
const MG_Pipe::MGPipeResourceRecord* pushedDesc = nullptr;
|
||||
if (TextureResourceSubsystemEnabled()) {
|
||||
pendingSamplerParams = ResolvePushedBuiltinSampler(stateTextureObject);
|
||||
pushedDesc = ResolveOwnRecord(stateTextureObject);
|
||||
pendingSamplerParams = ResolvePushedBuiltinSampler(stateTextureObject, pushedDesc);
|
||||
if (pendingSamplerParams == nullptr) return;
|
||||
} else {
|
||||
#if !MOBILEGL_PIPE_LEGACY_MEMOS
|
||||
@@ -8675,11 +8905,18 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
m_forceSamplerResync = false;
|
||||
#endif
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
MGLOG_D("Syncing texture built-in sampler with backend ID %u to backend for state ID %u",
|
||||
m_backendTextureId, TextureDiagName(stateTextureObject, pushedDesc));
|
||||
auto targetInternal = MGB_TEXPARAM_TARGET(pushedDesc, stateTextureObject);
|
||||
GLenum target = ConvertTextureTargetToBackendGLEnum(targetInternal);
|
||||
#else
|
||||
MGLOG_D("Syncing texture built-in sampler with backend ID %u to backend for state ID %u",
|
||||
m_backendTextureId, stateTextureObject->GetExternalIndex());
|
||||
|
||||
GLenum target = ConvertTextureTargetToBackendGLEnum(stateTextureObject->GetTarget());
|
||||
auto targetInternal = stateTextureObject->GetTarget();
|
||||
#endif
|
||||
MGLOG_D(" Texture target for syncing is %s",
|
||||
MG_Util::ConvertTextureTargetToString(targetInternal).c_str());
|
||||
if (!IsSupportedTextureTarget(targetInternal)) {
|
||||
@@ -8772,17 +9009,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
const MG_Pipe::MGPipeResourceRecord* BackendTextureObject::ResolvePushedTextureParams(
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed resolution, named debt inside the
|
||||
// scope - P3b/P4b rekeys the registry onto handles.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
const MG_Pipe::MGPipeHandle res = g_backendTextureObjects.HandleOf(stateTextureObject.get());
|
||||
const auto* record = PipeTextureRecordForHandle(res);
|
||||
// P5e (tx2): the handle the caller adopted this twin with, and only failing that the
|
||||
// client allocator - ResolveOwnRecord holds the whole of that rule.
|
||||
const auto* record = ResolveOwnRecord(stateTextureObject);
|
||||
if (record == nullptr) {
|
||||
MGLOG_E_ONCE("MGPipe: texture %u has no applier record on the handle arm, so its "
|
||||
"parameters cannot be pushed (handle {%u, %u})",
|
||||
stateTextureObject->GetExternalIndex(), res.Slot, res.Gen);
|
||||
TextureDiagName(stateTextureObject, record), m_pushedSyncHandle.Slot,
|
||||
m_pushedSyncHandle.Gen);
|
||||
return nullptr;
|
||||
}
|
||||
// ParamsSerial + ForceResync replace m_syncedTextureParamsVersion +
|
||||
@@ -8866,7 +9100,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
|
||||
if (!stateTextureObject) {
|
||||
// P5e (tx2): null IS the by-handle arm once a handle has been noted on this twin;
|
||||
// see the twin note in SyncBuiltinSamplerToBackend.
|
||||
if (!stateTextureObject && MGB_TEXTURE_HANDLE_ARM_OFF(*this)) {
|
||||
MGLOG_E_ONCE("State texture object is null, cannot sync to backend.");
|
||||
return;
|
||||
}
|
||||
@@ -8899,7 +9135,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
borderColorReadable = false;
|
||||
MGLOG_E_ONCE("MGPipe: texture %u's built-in sampler CSO {%u, %u} has no applier "
|
||||
"record, so its border colour cannot be pushed",
|
||||
stateTextureObject->GetExternalIndex(),
|
||||
TextureDiagName(stateTextureObject, pushedRecord),
|
||||
pushedRecord->Params.BuiltinSampler.Slot,
|
||||
pushedRecord->Params.BuiltinSampler.Gen);
|
||||
}
|
||||
@@ -8931,11 +9167,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
m_forceTextureParamsResync = false;
|
||||
#endif
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
MGLOG_D("Syncing texture params with backend ID %u to backend for state ID %u", m_backendTextureId,
|
||||
TextureDiagName(stateTextureObject, pushedRecord));
|
||||
|
||||
auto targetInternal = MGB_TEXPARAM_TARGET(pushedRecord, stateTextureObject);
|
||||
GLenum target = ConvertTextureTargetToBackendGLEnum(targetInternal);
|
||||
#else
|
||||
MGLOG_D("Syncing texture params with backend ID %u to backend for state ID %u", m_backendTextureId,
|
||||
stateTextureObject->GetExternalIndex());
|
||||
|
||||
GLenum target = ConvertTextureTargetToBackendGLEnum(stateTextureObject->GetTarget());
|
||||
auto targetInternal = stateTextureObject->GetTarget();
|
||||
#endif
|
||||
MGLOG_D(" Texture target for syncing is %s",
|
||||
MG_Util::ConvertTextureTargetToString(targetInternal).c_str());
|
||||
if (!IsSupportedTextureTarget(targetInternal)) {
|
||||
@@ -13838,16 +14082,122 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
} // namespace
|
||||
|
||||
namespace TextureImpl {
|
||||
// ---- P5e (tx2): THE TEXTURE FAMILY'S BY-HANDLE ENTRY, CONTRACT-P5E §5.2 --------------
|
||||
//
|
||||
// SamplerImpl::ResolveSamplerCsoTwin is the shape (Managers.cpp, the sampler half was
|
||||
// already run-ahead clean on its record arm and this is that shape brought to the texture
|
||||
// half): RECORD FIRST, twin by GetOrCreateByHandle, three syncs driven from the record,
|
||||
// no frontend touch and no allocator probe anywhere on the path.
|
||||
//
|
||||
// WHAT THE FRONTEND ENTRY PAYS AND THIS ONE DOES NOT. SyncTextureObjectToBackend takes a
|
||||
// by-VALUE copy of the twin and does a second Find at the tail, because its registry's
|
||||
// Find returns a reference INTO an open-addressed map that a NESTED sync (a view syncing
|
||||
// its storage texture) can rehash out from under it. A slot-table entry is an ARRAY
|
||||
// ELEMENT: a nested GetOrCreateByHandle can only grow the vector, so the answer is
|
||||
// re-INDEXED at the tail rather than pinned by a refcount, and the copy is a refcount
|
||||
// this arm does not pay.
|
||||
//
|
||||
// A null return is a NAMED decline and never a mint: no record (the client named a
|
||||
// texture it never created, or one whose resource_destroy has already applied), or a slot
|
||||
// whose live generation is ahead of the handle's. A caller binds nothing and the unit
|
||||
// keeps what it holds.
|
||||
SharedPtr<BackendTextureObject>& SyncTextureToBackendByHandle(MG_Pipe::MGPipeHandle texture,
|
||||
Bool imageBindableStorageRequired) {
|
||||
(void)imageBindableStorageRequired;
|
||||
MGPipeP5eSeamNotLanded("TextureImpl::SyncTextureToBackendByHandle", "tx2", texture);
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
// The one storage every decline arm can return a reference to. Never written.
|
||||
static SharedPtr<BackendTextureObject> s_noTwin{};
|
||||
s_noTwin = nullptr;
|
||||
|
||||
const auto* record = PipeTextureRecordForHandle(texture);
|
||||
if (record == nullptr) {
|
||||
MGLOG_E_ONCE("MGPipe: texture {%u, %u} has no applier resource record on the handle "
|
||||
"arm, so no driver texture can be built for it and the caller keeps "
|
||||
"what it holds",
|
||||
texture.Slot, texture.Gen);
|
||||
return s_noTwin;
|
||||
}
|
||||
auto* slot = g_backendTextureObjects.GetOrCreateByHandle(texture);
|
||||
if (slot == nullptr) {
|
||||
MGLOG_E_ONCE("MGPipe: texture {%u, %u} cannot be adopted on the handle arm (the slot's "
|
||||
"live generation is %u), so no driver texture is built for it",
|
||||
texture.Slot, texture.Gen, g_backendTextureObjects.LiveGenAt(texture.Slot));
|
||||
return s_noTwin;
|
||||
}
|
||||
if (!*slot) *slot = MakeShared<BackendTextureObject>();
|
||||
// THE HANDLE IS NOTED BEFORE ANY SYNC RUNS. It is what the three prologues read
|
||||
// instead of probing the client allocator, and what selects the by-handle arm inside
|
||||
// them, so it has to be in place before the first of them is called.
|
||||
(*slot)->NotePushedSyncHandle(texture);
|
||||
|
||||
const SharedPtr<BackendTextureObject> twin = *slot;
|
||||
if (imageBindableStorageRequired) {
|
||||
twin->RequireImageBindableStorageByHandle(texture, *record);
|
||||
}
|
||||
static const SharedPtr<MG_State::GLState::ITextureObject> kNoFrontendTexture{};
|
||||
twin->SyncTextureParamsToBackend(kNoFrontendTexture);
|
||||
twin->SyncBuiltinSamplerToBackend(kNoFrontendTexture);
|
||||
twin->SyncMipmapsToBackend(kNoFrontendTexture);
|
||||
// The storage sync may RE-MINT the driver texture, which discards every parameter the
|
||||
// two calls above just pushed - the frontend entry's argument, unchanged.
|
||||
if (twin->NeedsParameterResync()) {
|
||||
twin->SyncTextureParamsToBackend(kNoFrontendTexture);
|
||||
twin->SyncBuiltinSamplerToBackend(kNoFrontendTexture);
|
||||
}
|
||||
// ONE re-index, and only because a nested adopt may have GROWN the vector; nothing on
|
||||
// this arm erases a live slot, so the entry itself cannot have gone.
|
||||
auto* refreshed = g_backendTextureObjects.FindByHandle(texture);
|
||||
if (refreshed != nullptr && *refreshed) {
|
||||
return *refreshed;
|
||||
}
|
||||
s_noTwin = twin;
|
||||
return s_noTwin;
|
||||
}
|
||||
|
||||
void BackendTextureObject::SyncMipmapsToBackendByHandle(MG_Pipe::MGPipeHandle texture) {
|
||||
MGPipeP5eSeamNotLanded("BackendTextureObject::SyncMipmapsToBackendByHandle", "tx2",
|
||||
texture);
|
||||
// The STORAGE half alone, for fb's attachment sync and the image sweep: an attachment
|
||||
// needs its levels on the driver, not its sampler parameters (§5.4). The handle note
|
||||
// is what routes the body below onto the record.
|
||||
NotePushedSyncHandle(texture);
|
||||
static const SharedPtr<MG_State::GLState::ITextureObject> kNoFrontendTexture{};
|
||||
SyncMipmapsToBackend(kNoFrontendTexture);
|
||||
}
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P5e (tx2), CONTRACT-P5E §5.2. See the declaration for the clause-by-clause derivation
|
||||
// and for why BOTH halves of each resync pair are read here.
|
||||
Bool BackendTextureObject::IsDrawSyncCleanByRecord(MG_Pipe::MGPipeHandle res,
|
||||
const MG_Pipe::MGPipeResourceRecord& record) const {
|
||||
(void)res;
|
||||
// SyncMipmapsToBackend's own early-out, verbatim.
|
||||
if (!m_isInitialized || m_syncedResourceSerial == 0 || m_syncedResourceSerial != record.Serial ||
|
||||
!record.PendingUploads.empty()) {
|
||||
return false;
|
||||
}
|
||||
// SyncTextureParamsToBackend's (ResolvePushedTextureParams' gate).
|
||||
if (m_syncedParamsSerial != record.ParamsSerial || record.Params.ForceResync != 0 ||
|
||||
m_forceTextureParamsResync) {
|
||||
return false;
|
||||
}
|
||||
// SyncBuiltinSamplerToBackend's (ResolvePushedBuiltinSampler's gate). A texture whose
|
||||
// params have never been set has no built-in sampler to push and that prologue
|
||||
// declines SILENTLY - so a zero ParamsSerial is clean here for the same reason, and
|
||||
// the CSO lookup below is only made when there is a CSO to look up.
|
||||
if (record.ParamsSerial != 0) {
|
||||
const MG_Pipe::MGPipeHandle builtin = record.Params.BuiltinSampler;
|
||||
if (m_syncedBuiltinSampler != builtin) return false;
|
||||
const auto* cso = PipeSamplerCsoRecordForHandle(builtin);
|
||||
if (cso == nullptr || m_syncedBuiltinSamplerSerial != cso->Serial) return false;
|
||||
if (record.Params.SamplerResync != 0 || m_forceSamplerResync) return false;
|
||||
} else if (m_forceSamplerResync) {
|
||||
return false;
|
||||
}
|
||||
// The storage-kind restriction the frontend gate carries, from the descriptor: a
|
||||
// buffer texture's backing store can move without any serial above noticing.
|
||||
return static_cast<TextureStorageType>(record.Desc.StorageKind) == TextureStorageType::Mipmap;
|
||||
}
|
||||
#endif
|
||||
} // namespace TextureImpl
|
||||
|
||||
namespace FramebufferImpl {
|
||||
|
||||
@@ -1641,6 +1641,26 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// parameter and sampler halves are unchanged and run on this name as on any other.
|
||||
void SyncTextureViewToBackend(const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject);
|
||||
void StampViewSyncKeys(const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5e (tx2). THE VIEW ARM UNDER A TRANSPORT, and what it can and cannot answer.
|
||||
//
|
||||
// `Desc.ViewOf` names the storage owner BY HANDLE, so the STEADY question - "is my ES
|
||||
// name still a view of the same storage name" - is answered entirely server-side: sync
|
||||
// the storage twin by handle, compare its id against m_viewSourceBackendTextureId,
|
||||
// stamp the record's Serial. That is the per-draw cost of every already-synced view
|
||||
// and it reads nothing from the client.
|
||||
//
|
||||
// The CREATION of the view is the gap and it is named rather than papered over: the
|
||||
// glTextureView call needs (minLevel, numLevels, minLayer, numLayers) and MGPResourceDesc
|
||||
// carries none of the four - `ViewOf` is the only view field on the wire. So a view whose
|
||||
// ES name has not been made a view YET, or whose storage was re-minted underneath it,
|
||||
// still needs the frontend object and takes it when one is reachable; with no object it
|
||||
// declines LOUDLY and the view samples its own (empty) name. Listed as trailing in the
|
||||
// tx2 report with the wire fields it needs.
|
||||
void SyncTextureViewToBackendByRecord(
|
||||
MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPipeResourceRecord& record,
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject);
|
||||
#endif
|
||||
// The storage half of the sync for a texture created by glTextureView. Instead of
|
||||
// allocating storage and replaying uploads, it makes this object's ES name BE a view
|
||||
// of the storage texture's ES name (EXT/OES_texture_view), which is what gives the
|
||||
@@ -1654,6 +1674,24 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// re-mint allocates fresh storage and only replays what the shadow still calls dirty.
|
||||
void RequireImageBindableStorage(
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject);
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P5e (tx2), CONTRACT-P5E §5.2 / G-S2-5. THE SAME TRANSITION WITH NO FRONTEND
|
||||
// ARGUMENT, and with the re-dirty NAMED AT THE ENTRY instead of three frames deep.
|
||||
//
|
||||
// The frontend overload's whole second half is the re-dirty: it walks the CLIENT's
|
||||
// level shadows to re-arm every level the widened carrier owes. A server has no
|
||||
// client address space to walk, so under a transport that half is not "not migrated
|
||||
// yet", it is not expressible - ImageBindableHint is the prevention (a texture that
|
||||
// has ever been image-bound is allocated in the carrier from the start) and P9 owns
|
||||
// the pull. The refusal is raised here, at the entry, where the reason can still be
|
||||
// stated, rather than at the MarkStorageDirty guard the loop would reach.
|
||||
//
|
||||
// A texture arriving here with NO backend storage yet pulls nothing - it is simply
|
||||
// allocated image-bindable up front - so that case is not refused, it is the whole
|
||||
// point of the hint.
|
||||
void RequireImageBindableStorageByHandle(MG_Pipe::MGPipeHandle res,
|
||||
const MG_Pipe::MGPipeResourceRecord& record);
|
||||
#endif
|
||||
// Whether this texture's ES storage was minted in an image carrier rather than in the
|
||||
// frontend format's own layout - the readback has to ask, because for a NORMALIZED
|
||||
// carrier the storage is an integer texture holding codes and glGetTexImage still owes
|
||||
@@ -1702,6 +1740,39 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return t->GetStorageType() == TextureStorageType::Mipmap;
|
||||
}
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P5e (tx2), CONTRACT-P5E §5.2. THE SAME AGGREGATE GATE WITH NO FRONTEND OBJECT IN
|
||||
// IT, and it is EXACTLY the conjunction of the three handle-arm prologues' own
|
||||
// early-outs - the relation IsDrawSyncClean states above for the frontend versions,
|
||||
// restated over the server's serials:
|
||||
//
|
||||
// SyncMipmapsToBackend m_isInitialized && m_syncedResourceSerial == Serial
|
||||
// && PendingUploads.empty()
|
||||
// SyncTextureParamsToBackend m_syncedParamsSerial == ParamsSerial
|
||||
// && !Params.ForceResync && !m_forceTextureParamsResync
|
||||
// SyncBuiltinSamplerToBackend m_syncedBuiltinSampler == Params.BuiltinSampler
|
||||
// && m_syncedBuiltinSamplerSerial == that CSO's Serial
|
||||
// && !Params.SamplerResync && !m_forceSamplerResync
|
||||
//
|
||||
// plus the storage-kind restriction the frontend gate carries, answered from
|
||||
// Desc.StorageKind. No new member and no new wire field: every input already
|
||||
// exists, which is the whole reason the memo-HIT path can stop being frontend-bound.
|
||||
//
|
||||
// BOTH SIDES OF THE RESYNC PAIR ARE READ AND NEITHER CLEARS THE OTHER'S (D-E2, now
|
||||
// load-bearing for a GATE rather than only for a push, scout R4). `Params.ForceResync`
|
||||
// and `Params.SamplerResync` are the CLIENT's bits: the server reads them, acts on
|
||||
// them and never writes them back. `m_forceTextureParamsResync`/`m_forceSamplerResync`
|
||||
// are the SERVER's: set by RecreateBackendTexture / RequireImageBindableStorage and
|
||||
// cleared only by the prologue that consumed them. A gate that read one side would
|
||||
// skip the sync the other side is asking for, which for the sampler half is not
|
||||
// mis-filtering but an INCOMPLETE texture sampling (0,0,0,1).
|
||||
//
|
||||
// The contextId / samplingGeneration arguments are GONE: the applier's ContextSerial
|
||||
// and TextureShutterSerial are what the caller's keys already carry.
|
||||
Bool IsDrawSyncCleanByRecord(MG_Pipe::MGPipeHandle res,
|
||||
const MG_Pipe::MGPipeResourceRecord& record) const;
|
||||
#endif
|
||||
|
||||
private:
|
||||
void RecreateBackendTexture();
|
||||
|
||||
@@ -1811,8 +1882,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// over, and quietly re-reading the object would hide a missing record behind a
|
||||
// picture that still looks right - which is precisely what the subsystem A/B exists
|
||||
// to expose (MarkBufferGpuWritten's note, P3a).
|
||||
//
|
||||
// P5e (tx2): the HANDLE comes from m_pushedSyncHandle when the caller adopted this
|
||||
// twin by handle, and only then from the client allocator - which is what retires
|
||||
// the two `HandleOf` probes this pair used to make on every feature path. The record
|
||||
// is resolved once by ResolveOwnRecord below and passed to the sampler half rather
|
||||
// than looked up twice.
|
||||
const MG_Pipe::MGPipeResourceRecord* ResolveOwnRecord(
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject) const;
|
||||
const SamplerParameters* ResolvePushedBuiltinSampler(
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject);
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject,
|
||||
const MG_Pipe::MGPipeResourceRecord* record);
|
||||
// Hands back the whole RECORD rather than its Params, because the parameter push
|
||||
// reads two things from beside them: Desc.InternalFormat, which decides the two
|
||||
// channel-widening swizzle compositions, and Params.BuiltinSampler, which is where
|
||||
@@ -1854,6 +1934,27 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// (two textures sharing one CSO, then one of them diverging).
|
||||
Uint64 m_syncedBuiltinSamplerSerial = 0;
|
||||
MG_Pipe::MGPipeHandle m_syncedBuiltinSampler = MG_Pipe::kMGPipeNullHandle;
|
||||
|
||||
// P5e (tx2), CONTRACT-P5E §4.1's `m_handle` (the m_pushedSyncHandle pattern the
|
||||
// framebuffer twin already carries). THE HANDLE THIS TWIN WAS ADOPTED FOR.
|
||||
//
|
||||
// It is what replaces `g_backendTextureObjects.HandleOf(stateTextureObject.get())`
|
||||
// in the three sync prologues: the by-handle entry point knows the handle before it
|
||||
// knows anything else, so the prologues stop probing the client allocator to
|
||||
// re-derive an answer the caller already had. It is ALSO the arm selector - a twin
|
||||
// with a handle noted syncs from the record and tolerates a null frontend object,
|
||||
// one without it is the monolith-glue half and reads the object as it always did.
|
||||
//
|
||||
// Stamped by SyncTextureToBackendByHandle / ResolveTextureTwin and never cleared:
|
||||
// a slot recycled to {s, g+1} RESETS the twin (SlotTables.h's forward-Gen rule), so
|
||||
// a stale handle cannot outlive the object it names.
|
||||
MG_Pipe::MGPipeHandle m_pushedSyncHandle = MG_Pipe::kMGPipeNullHandle;
|
||||
|
||||
public:
|
||||
void NotePushedSyncHandle(MG_Pipe::MGPipeHandle res) { m_pushedSyncHandle = res; }
|
||||
MG_Pipe::MGPipeHandle PushedSyncHandle() const { return m_pushedSyncHandle; }
|
||||
|
||||
private:
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -427,6 +427,66 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// with a span (the client refuses "MultiDrawElements+CLIENT_INDICES" first; P8's
|
||||
// HostResolve.cpp flattens it) and a multi-draw that claims instancing (no GL entry point
|
||||
// produces one; the client never sends it).
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
namespace {
|
||||
// P5e (tx2), CONTRACT-P5E §5.3 / ruling 19 (ID-95, A8 closed). THE TWO UNIT WINDOWS MUST
|
||||
// COVER [0, MaxTouchedTextureUnit], AND THIS IS WHERE THAT PROMISE IS CHECKED.
|
||||
//
|
||||
// Everything the texture and sampler families do per draw now reads
|
||||
// [SamplerViewStart, +SamplerViewCount) and [SamplerStateStart, +SamplerStateCount).
|
||||
// Under run-ahead a window NARROWER than the frontend's high-water mark silently drops a
|
||||
// sync of a texture the draw is about to sample, or leaves an earlier draw's sampler
|
||||
// object on a unit - and a decline is exactly what run-ahead cannot take, because the
|
||||
// client has already moved on and there is no wait in which to notice.
|
||||
//
|
||||
// A SERVER-SIDE RE-DERIVATION IS REFUSED, and that is the ruling's point: the client owns
|
||||
// the high-water mark (it is the `count` argument SamplerEmit.h passes, Start=0 /
|
||||
// Count=maxTouched+1) and the server's job is to check the promise, not to invent a
|
||||
// second authority for it. So this is a CHECK and its failure is corruption.
|
||||
//
|
||||
// WHERE THE MARK COMES FROM: MGPContextValues::MaxTouchedTextureUnit, applied by
|
||||
// set_context_values, which precedes the verb on the ring - the same ordering §2.1's
|
||||
// XFB clause leans on. It is RECORD_SUPPLIED, so reading it is reading what a record
|
||||
// put there and not client memory (rule F).
|
||||
//
|
||||
// WIDER IS FINE. A window larger than the mark costs a walk over provably-empty units;
|
||||
// only SMALLER is unrepresentable. An empty window on a draw that touches no unit at all
|
||||
// (mark 0 with nothing ever touched) is the correct and only possible emission, so a
|
||||
// count of 0 is admitted exactly while the applied counters say no unit was touched.
|
||||
//
|
||||
// THE ONE ADMITTED SILENCE, and it is the A/B rather than a hole: a client whose sampler
|
||||
// subsystem bit is CLEAR emits neither record, the backend then runs its pre-handle
|
||||
// frontend walk, and there is no window to check because there is no window. That is
|
||||
// "both counts are still 0", and it is distinguishable from the narrowing this refuses
|
||||
// (a narrowed window carries a non-zero count that is merely too small). The moment
|
||||
// either set has been received, the rule binds.
|
||||
void CheckUnitWindows(const MG_Pipe::MGPipeApplierState& st, const char* verb) {
|
||||
if (st.SamplerViewCount == 0 && st.SamplerStateCount == 0) return;
|
||||
const Int maxTouched = MG_Pipe::gPipeInputs.GetMaxTouchedTextureUnit();
|
||||
if (maxTouched < 0) return;
|
||||
const Uint32 required = static_cast<Uint32>(maxTouched) + 1u;
|
||||
if (st.SamplerViewStart != 0 || st.SamplerViewCount < required) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"SetSamplerViews.Count\"} - %s applies with "
|
||||
"units 0..%d touched, so set_sampler_views must carry Start=0 and Count >= %u "
|
||||
"(CONTRACT-P5E.md §5.3); the applied window is Start=%u Count=%u, which drops "
|
||||
"the sync of at least one texture this draw samples",
|
||||
verb, static_cast<int>(maxTouched), required, st.SamplerViewStart,
|
||||
st.SamplerViewCount);
|
||||
std::abort();
|
||||
}
|
||||
if (st.SamplerStateStart != 0 || st.SamplerStateCount < required) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"BindSamplerStates.Count\"} - %s applies with "
|
||||
"units 0..%d touched, so bind_sampler_states must carry Start=0 and Count >= %u "
|
||||
"(CONTRACT-P5E.md §5.3); the applied window is Start=%u Count=%u, which leaves "
|
||||
"an earlier draw's sampler object on at least one unit",
|
||||
verb, static_cast<int>(maxTouched), required, st.SamplerStateStart,
|
||||
st.SamplerStateCount);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
Bool ServerVerbSink::OnDrawVbo(const MG_Pipe::MGPDrawInfo& info,
|
||||
const MG_Pipe::MGPDrawRange* ranges,
|
||||
const MG_Pipe::MGHostSpan* userIndices,
|
||||
@@ -449,6 +509,11 @@ namespace MobileGL::MG_Remote::Server {
|
||||
}
|
||||
++m_drawRecords;
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// Ruling 19 / ID-95: the window promise, checked at every draw, before the backend is
|
||||
// asked to resolve anything out of the windows.
|
||||
CheckUnitWindows(MG_Pipe::MGPipeApplier(), "draw_vbo");
|
||||
#endif
|
||||
const MG_Backend::GlobalBackendFunctionsTable* table = Table("draw_vbo");
|
||||
if (table == nullptr) return false;
|
||||
const MG_Backend::GLFunctionsTable& gl = table->GL;
|
||||
@@ -668,6 +733,10 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// identical - and it is also the honest statement of the debt, which `rsp` counts.
|
||||
|
||||
Bool ServerVerbSink::OnLaunchGrid(const MG_Pipe::MGPGridInfo& grid) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// Ruling 19 / ID-95: a dispatch samples through the same unit windows a draw does.
|
||||
CheckUnitWindows(MG_Pipe::MGPipeApplier(), "launch_grid");
|
||||
#endif
|
||||
const MG_Backend::GlobalBackendFunctionsTable* table = Table("launch_grid");
|
||||
if (table == nullptr) return false;
|
||||
const MG_Backend::GLFunctionsTable& gl = table->GL;
|
||||
|
||||
@@ -36,6 +36,21 @@ namespace MobileGL {
|
||||
// The message names the TEXTURE-side method, not this storage's: the pinned
|
||||
// surface list (MG_Test/Wire/RemoteClientTest.cpp's RemoteGuards) is written
|
||||
// in TextureObjectMipmap's vocabulary.
|
||||
//
|
||||
// P5e (tx2): THE READ-ONLY SHAPE ACCESSORS JOIN THE LIST. P5c guarded the
|
||||
// MUTATORS and deliberately exempted the shape reads, with the reason written
|
||||
// into RemoteClientTest: "the per-draw binding walk reads them every draw", so
|
||||
// guarding them would have aborted a lane that was still correct. P5e is the
|
||||
// commit that makes that untrue - the unit work list, the clean gate and the
|
||||
// three sync bodies read the resource RECORD and the server's staged-texture
|
||||
// store, and nothing on the draw path asks the frontend object for a level count,
|
||||
// a level extent, a level byte size or a compressed level again. So the exemption
|
||||
// becomes a guard, and reverting any handle arm aborts here BY ACCESSOR NAME
|
||||
// instead of rendering from client memory.
|
||||
//
|
||||
// A client-thread caller is unaffected (this is the frontend's own state, and the
|
||||
// guard returns unless ServerLoop::OnApplyThread()); Magma's texture sync keeps
|
||||
// its named scope exemption; the pull build compiles none of it.
|
||||
void RefuseLegacyTextureArmFromApplyThread(const char* surface) {
|
||||
if (MG_Config::Transport == MG_Config::TransportMode::Monolith) return;
|
||||
if (!MG_Remote::Server::ServerLoop::OnApplyThread()) return;
|
||||
@@ -97,6 +112,9 @@ namespace MobileGL {
|
||||
#endif
|
||||
|
||||
SizeT MipmapStorage::GetLevelCount() const {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
RefuseLegacyTextureArmFromApplyThread("GetMipmapLevelCount");
|
||||
#endif
|
||||
return m_data.size();
|
||||
}
|
||||
|
||||
@@ -171,16 +189,25 @@ namespace MobileGL {
|
||||
}
|
||||
|
||||
GLenum MipmapStorage::GetCompressedFormat(Uint level) const {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
RefuseLegacyTextureArmFromApplyThread("GetCompressedFormat");
|
||||
#endif
|
||||
if (level >= m_compressedFormats.size()) return GL_NONE;
|
||||
return m_compressedFormats[level];
|
||||
}
|
||||
|
||||
SizeT MipmapStorage::GetCompressedByteSize(Uint level) const {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
RefuseLegacyTextureArmFromApplyThread("GetCompressedByteSize");
|
||||
#endif
|
||||
if (level >= m_compressedData.size()) return 0;
|
||||
return m_compressedData[level].size();
|
||||
}
|
||||
|
||||
const void* MipmapStorage::MapCompressedData(Uint level) const {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
RefuseLegacyTextureArmFromApplyThread("MapCompressedMipmapData");
|
||||
#endif
|
||||
if (level >= m_compressedData.size()) return nullptr;
|
||||
return m_compressedData[level].data();
|
||||
}
|
||||
@@ -191,6 +218,9 @@ namespace MobileGL {
|
||||
}
|
||||
|
||||
GLenum MipmapStorage::GetRequestedCompressedFormat(Uint level) const {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
RefuseLegacyTextureArmFromApplyThread("GetRequestedCompressedFormat");
|
||||
#endif
|
||||
if (level >= m_requestedCompressedFormats.size()) return GL_NONE;
|
||||
return m_requestedCompressedFormats[level];
|
||||
}
|
||||
@@ -239,12 +269,18 @@ namespace MobileGL {
|
||||
}
|
||||
|
||||
IntVec3 MipmapStorage::GetTexelSize(Uint level) const {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
RefuseLegacyTextureArmFromApplyThread("GetMipmapTexelSize");
|
||||
#endif
|
||||
auto& targetTexelSizes = m_texelSizes;
|
||||
if (level >= targetTexelSizes.size()) return {0, 0, 0};
|
||||
return targetTexelSizes[level];
|
||||
}
|
||||
|
||||
SizeT MipmapStorage::GetByteSize(Uint level) const {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
RefuseLegacyTextureArmFromApplyThread("GetMipmapByteSize");
|
||||
#endif
|
||||
if (level >= m_data.size()) return 0;
|
||||
return m_data[level].size();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user