[Refactor] (P13 W5 S8): MOBILEGL_BUILD_RECORD_ARM is folded away - the record arm is unconditional - and retired_switches keeps it from coming back.

This commit is contained in:
2026-10-06 15:03:11 -04:00
parent a0b9ca0772
commit 7fb18840dc
91 changed files with 46 additions and 2725 deletions
+18 -29
View File
@@ -553,12 +553,9 @@ if (MOBILEGL_BUILD_DISAGGREGATED AND
set(MOBILEGL_BUILD_DISAGGREGATED_INPROC OFF)
endif()
# P13 W5 (notes/p13/W5-DESIGN.md): THE RECORD ARM'S OWN GUARD. A plain variable, not an option and
# not a cache entry, so no build tree can keep a stale value (W3a's lesson). It equalled
# MOBILEGL_BUILD_DISAGGREGATED while the record arm's guards were reclassified one by one; since S7
# it is on for every build, so the library without MG_Remote (the FCL-embedded one) runs the record
# arm too. S8 removes the variable and the guards it still names. Always defined, to 0 or 1.
set(MOBILEGL_BUILD_RECORD_ARM ON)
# P13 W5 (notes/p13/W5-DESIGN.md): the record arm is part of every library - the one without
# MG_Remote (FCL's) runs it too. Its temporary guard, MOBILEGL_BUILD_RECORD_ARM, was folded away at
# S8 (scripts/ci/retired_switches.py keeps it from coming back).
message(STATUS "MobileGL: PipeInputs push ON, appending the MGPipe fill sources")
list(APPEND SOURCE_FILES
@@ -684,23 +681,21 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
)
endif()
# P13 W5: the record arm's own sources, compiled wherever the record arm is (MG_Remote or not).
if (MOBILEGL_BUILD_RECORD_ARM)
list(APPEND SOURCE_FILES
# P7 gate 5 (g5-msprobe): the Magma wire arm's two multisample depth/stencil resolve
# arms and the probe that measures the first against the second, shared by the
# renderer's bring-up and the Vulkan POST.
MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.cpp
# P5 b1: the block-granularity persistent-map push the record arm makes mandatory (T2).
MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.cpp
# P13 W5: the verb emitters and the monolith verb port, and the client GPU-write set
# they mark before a draw or a dispatch (MG_Record; EmitTables.cpp resolves the session).
MobileGL/MG_Impl/Pipe/Verb/VerbPort.cpp
MobileGL/MG_Impl/Pipe/Verb/GpuWriteSet.cpp
# The record verbs' consumer (ServerVerbSink under a transport is this plus the session's).
MobileGL/MG_Backend/Record/RecordVerbSink.cpp
)
endif()
# P13 W5: the record arm's own sources, compiled into every library (MG_Remote or not).
list(APPEND SOURCE_FILES
# P7 gate 5 (g5-msprobe): the Magma wire arm's two multisample depth/stencil resolve
# arms and the probe that measures the first against the second, shared by the
# renderer's bring-up and the Vulkan POST.
MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.cpp
# P5 b1: the block-granularity persistent-map push the record arm makes mandatory (T2).
MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.cpp
# P13 W5: the verb emitters and the monolith verb port, and the client GPU-write set
# they mark before a draw or a dispatch (MG_Record; EmitTables.cpp resolves the session).
MobileGL/MG_Impl/Pipe/Verb/VerbPort.cpp
MobileGL/MG_Impl/Pipe/Verb/GpuWriteSet.cpp
# The record verbs' consumer (ServerVerbSink under a transport is this plus the session's).
MobileGL/MG_Backend/Record/RecordVerbSink.cpp
)
if (APPLE AND NOT MOBILEGL_IOS)
list(APPEND SOURCE_FILES
@@ -764,12 +759,6 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
list(APPEND MOBILEGL_COMPILE_DEF -DMOBILEGL_BUILD_DISAGGREGATED=1)
endif()
if (MOBILEGL_BUILD_RECORD_ARM)
list(APPEND MOBILEGL_COMPILE_DEF -DMOBILEGL_BUILD_RECORD_ARM=1)
else()
list(APPEND MOBILEGL_COMPILE_DEF -DMOBILEGL_BUILD_RECORD_ARM=0)
endif()
if (MOBILEGL_PIPE_VERIFY)
list(APPEND MOBILEGL_COMPILE_DEF -DMOBILEGL_PIPE_VERIFY=1)
endif()
-9
View File
@@ -462,7 +462,6 @@ namespace MobileGL::MG_Config {
NamedPipe = 4, // P6: Windows named pipe (Endpoint = <name>)
};
#if MOBILEGL_BUILD_RECORD_ARM
// P13 W5: THE RECORD ARM'S OWN KNOBS. They kept their MOBILEGL_IPC_* names (logs, CI lanes and
// notes spell them so), but they tune the record arm - the persistent-map push and Magma's wire
// stores - which runs in a library without a transport too, so they are not IpcTable's.
@@ -498,7 +497,6 @@ namespace MobileGL::MG_Config {
Uint32 PersistentMprotect = 1;
};
extern RecordArmTable RecordArm;
#endif
#if MOBILEGL_BUILD_DISAGGREGATED
// Parsed once by MG_ConfigLoader::Init(). Defaults to Monolith even here: building the
@@ -759,7 +757,6 @@ namespace MobileGL::MG_Config {
inline constexpr TransportMode Transport = TransportMode::Monolith;
#endif
#if MOBILEGL_BUILD_RECORD_ARM
// P13 W4: THE DATA-ARM QUESTION - do the backends read RECORDS (the applier's twins, the staged
// stores, the verb's own handles) rather than frontend objects? Under a wire the answer has
// always been yes; P13 makes it yes for monolith too ("monolith is the push variant"), one
@@ -779,10 +776,4 @@ namespace MobileGL::MG_Config {
inline Bool RecordArmAliasesFrontend() {
return Transport == TransportMode::Monolith && MonolithTakesRecordArm;
}
#else
// A build without the record arm reads frontend objects everywhere (the negative control a
// library built with MOBILEGL_BUILD_RECORD_ARM=0 keeps until W6 deletes the frontend arms).
inline constexpr Bool DataArmIsRecord() { return false; }
inline constexpr Bool RecordArmAliasesFrontend() { return false; }
#endif
} // namespace MobileGL::MG_Config
-8
View File
@@ -39,10 +39,8 @@ namespace MobileGL::MG_Config {
Bool SplitTransportRequestedByConfig = false;
IpcTable Ipc;
#endif
#if MOBILEGL_BUILD_RECORD_ARM
Bool MonolithTakesRecordArm = false;
RecordArmTable RecordArm;
#endif
} // namespace MobileGL::MG_Config
namespace MobileGL::MG_ConfigLoader {
@@ -678,7 +676,6 @@ namespace MobileGL::MG_ConfigLoader {
}
#endif
#if MOBILEGL_BUILD_RECORD_ARM
// P13 W4 (Config.h DataArmIsRecord). Read after InitTransport: `frontend` names monolith's
// arm only - a wire has no frontend objects on its server half to read, so there it is
// ignored by name rather than half-honoured.
@@ -737,7 +734,6 @@ namespace MobileGL::MG_ConfigLoader {
"the persistent-map push is OFF and a coherent-map scenario must go red");
}
}
#endif
void Init() {
MGLOG_D("Loading configuration from environment variables...");
@@ -745,17 +741,13 @@ namespace MobileGL::MG_ConfigLoader {
InitBackendType();
InitFeatures();
#if MOBILEGL_BUILD_RECORD_ARM
InitRecordArm();
#endif
#if MOBILEGL_BUILD_DISAGGREGATED
// After InitFeatures, so the one line InitIpc logs is the last word on this run's
// configuration, and before the accepted-env map is destroyed just below.
InitTransport();
#endif
#if MOBILEGL_BUILD_RECORD_ARM
InitDataArm();
#endif
#if MOBILEGL_BUILD_DISAGGREGATED
InitIpc();
#endif
-4
View File
@@ -24,9 +24,7 @@
#if MOBILEGL_PIPE_VERIFY
#include <MG_Backend/MGPipe/PipeInputs.h> // MGPipeVerifyFlushSummary, before Debug::Close in DestroyImpl
#endif
#if MOBILEGL_BUILD_RECORD_ARM
#include <MG_Impl/Pipe/Verb/VerbPort.h>
#endif
#if MOBILEGL_BUILD_DISAGGREGATED
#include <MG_Remote/Client/ServerProbe.h>
#endif
@@ -185,7 +183,6 @@ namespace MobileGL {
#endif
{
MG_Backend::Init();
#if MOBILEGL_BUILD_RECORD_ARM
// P13 W4: monolith's record arm reads the verb's own state, which only a verb sink
// writes; the port routes the ported verbs through one (MG_Impl/Pipe/Verb/VerbPort.cpp).
// Installed HERE, by the library's role-neutral init, rather than by MG_Backend's: the
@@ -193,7 +190,6 @@ namespace MobileGL {
if (MG_Config::RecordArmAliasesFrontend() && MG_Backend::pActiveBackendObject) {
MG_Record::InstallMonolithVerbPort(MG_Backend::gBackendFunctionsTable);
}
#endif
}
MGLOG_D("MG_Backend initialized");
MG_Impl::Init();
-8
View File
@@ -10,9 +10,7 @@
#include <Includes.h>
#include "MG_State/GLState/TextureState/TextureEnum.h"
#include <MG_Util/Damage/Damage.h>
#if MOBILEGL_BUILD_RECORD_ARM
#include <MG_Pipe/MGPipeHandles.h>
#endif
namespace MobileGL {
namespace MG_State::GLState {
@@ -37,22 +35,16 @@ namespace MobileGL {
struct CopyImageEndpoint {
SharedPtr<MG_State::GLState::ITextureObject> Texture;
SharedPtr<MG_State::GLState::RenderbufferObject> Renderbuffer;
#if MOBILEGL_BUILD_RECORD_ARM
MG_Pipe::MGPipeHandle TextureHandle = MG_Pipe::kMGPipeNullHandle;
MG_Pipe::MGPipeHandle RenderbufferHandle = MG_Pipe::kMGPipeNullHandle;
#endif
Bool IsRenderbuffer() const {
#if MOBILEGL_BUILD_RECORD_ARM
if (!MG_Pipe::MGPipeHandleIsNull(RenderbufferHandle)) return true;
#endif
return Renderbuffer != nullptr;
}
Bool Exists() const {
#if MOBILEGL_BUILD_RECORD_ARM
if (!MG_Pipe::MGPipeHandleIsNull(TextureHandle)) return true;
if (!MG_Pipe::MGPipeHandleIsNull(RenderbufferHandle)) return true;
#endif
return Texture != nullptr || Renderbuffer != nullptr;
}
};
@@ -32,7 +32,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
return draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
}
#if MOBILEGL_BUILD_RECORD_ARM
// ID-54 / ID-67 (v1, under ID-52/ID-59's grant for this file). Which VIRTUAL (dpy, draw,
// read, ctx) the process's one native ES context is currently bound FOR, on the apply
// thread. DirectGLES has one native context and one native surface (g_Context, g_Surface)
@@ -78,7 +77,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
return false;
}
#endif
void ClearGLErrors(const MG_External::GLESFunctionsTable& gl) {
if (!gl.glGetError) return;
@@ -860,7 +858,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
Int maxSamples = 0;
const SizeT formatIndex = static_cast<SizeT>(logicalFormat);
#if MOBILEGL_BUILD_RECORD_ARM
// C6 / ID-52: read the ROLE's own cache. Under split that is the server's private backend
// (ActiveBackendFormatCaps), not pActiveBackendObject, which holds the client's mirror.
const FormatCapabilityCache* activeCaps = ActiveBackendFormatCaps();
@@ -872,17 +869,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
maxSamples = probedCounts.front();
}
}
#else
if (pActiveBackendObject && targetIndex < kFormatCapabilityTargetCount &&
formatIndex < kFormatCapabilityFormatCount) {
// Descending, so the head is the largest count this device actually allocated.
const Vector<Int>& probedCounts =
pActiveBackendObject->GetFormatCapabilities().SampleCounts[targetIndex][formatIndex];
if (!probedCounts.empty()) {
maxSamples = probedCounts.front();
}
}
#endif
if (maxSamples <= 0) {
maxSamples = GetGLESFormatMaxSamples(g_GLESCapabilities, logicalFormat, imageFormat);
}
@@ -1085,7 +1071,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
ResetEGLRuntimeState();
}
#if MOBILEGL_BUILD_RECORD_ARM
// ID-54 / ID-67: as for the window surface - InitPbufferSurface bound natively. The pull
// arm below is the original statement, byte for byte (G1).
const Bool created = BackendObject::CreateEGLPbufferSurface(surface, width, height);
@@ -1098,9 +1083,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
NoteNativeContextGone();
}
return created;
#else
return BackendObject::CreateEGLPbufferSurface(surface, width, height);
#endif
}
Bool BackendObject_DirectGLES::ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) {
@@ -1139,9 +1121,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (!DirectGLES::ReleaseCurrent()) {
return false;
}
#if MOBILEGL_BUILD_RECORD_ARM
NoteNativeContextGone();
#endif
return BackendObject::MakeEGLCurrent(dpy, draw, read, ctx);
}
@@ -1212,9 +1192,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (!BackendObject::MakeEGLCurrent(dpy, draw, read, ctx)) {
(void)DirectGLES::ReleaseCurrent();
#if MOBILEGL_BUILD_RECORD_ARM
NoteNativeContextGone();
#endif
return false;
}
return true;
@@ -1267,9 +1245,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
void BackendObject_DirectGLES::ReleaseEGLResources() {
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
DestroyEGLContext();
#if MOBILEGL_BUILD_RECORD_ARM
NoteNativeContextGone();
#endif
m_suspendedWindowSurfaces.clear();
BackendObject::ReleaseEGLResources();
}
@@ -1295,10 +1271,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
m_suspendedWindowSurfaces.push_back(client);
}
#if MOBILEGL_BUILD_RECORD_ARM
// The native binding moved onto the placeholder: the next make-current binds for real.
NoteNativeContextGone();
#endif
MGLOG_I("DirectGLES: %zu window surface(s) on server window %p now draw into placeholder pbuffers; the "
"contexts and their objects stay",
onWindow.size(), window);
@@ -1327,9 +1301,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (surface == EGL_NO_SURFACE) return false;
native->second = surface;
}
#if MOBILEGL_BUILD_RECORD_ARM
NoteNativeContextGone();
#endif
MGLOG_I("DirectGLES: %zu suspended window surface(s) rebuilt on server window %p (%ux%u)", suspended.size(),
window, width, height);
return true;
@@ -1366,9 +1338,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// every object the client had built - which a multi-window client hit on its first closed
// popup.
(void)surface;
#if MOBILEGL_BUILD_RECORD_ARM
NoteNativeContextGone();
#endif
}
void BackendObject_DirectGLES::OnEGLSurfaceForgotten(EGLSurface surface) {
File diff suppressed because it is too large Load Diff
@@ -30,7 +30,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bool ReadTextureLevelTight(GLuint texture, TextureTarget target, TextureUploadTarget uploadTarget,
TextureInternalFormat logicalFormat, GLint level, const IntVec3& logicalExtent,
Bool sourceUsesImageCarrier, GLenum format, GLenum type, Vector<Uint8>& bytes);
#if MOBILEGL_BUILD_RECORD_ARM
Bool ReadTextureImageWire(const MG_Pipe::MGPReadbackInfo& info, Vector<Uint8>& bytes);
// Whether the NATIVE texture currently holds `level` of `uploadTarget` at `logicalExtent`
// (glGetTexLevelParameteriv). False for a level the driver never allocated - one defined after
@@ -38,7 +37,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// refuses answers true, keeping the caller on its readback path.
Bool NativeTextureLevelHasExtent(GLuint texture, TextureTarget target, TextureUploadTarget uploadTarget,
GLint level, const IntVec3& logicalExtent);
#endif
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
@@ -432,12 +430,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
// native context already current on this thread IS that one - so the single-context world pays
// nothing for it. Called from the bind_context path on the apply thread.
Bool MakeNativeContextCurrentForBoundToken();
#if MOBILEGL_BUILD_RECORD_ARM
// P14: the server's backend turn moved to the calling apply thread (ServerLoop.cpp's
// BackendTurnLock). Takes the backend's GL ownership for this thread and drops every
// binding shadow, which still describes the previous holder's native context.
void OnBackendTurnHandoff();
#endif
// How many native contexts this process holds, and how many of them belong to one session. The
// test observables for "a second context did not tear the first one down".
Uint64 NativeContextCount();
-185
View File
@@ -18,12 +18,10 @@
// needs only the name - the deleter is captured where the archive is constructed - but the
// program build reads through it, so the one translation unit that does needs the whole type.
#include <MG_State/GLState/ProgramState/ProgramArtifactsCodec.h>
#if MOBILEGL_BUILD_RECORD_ARM
// R-11's server-owned staging copy. Header-only and package v1's; see its own header block for
// why GLESBufferResource does not simply gain a member.
#include <MG_Backend/Record/StagedShadow.h>
#include <MG_Backend/Record/StagedTextureStore.h>
#endif
#if MOBILEGL_BUILD_DISAGGREGATED
#include <MG_Remote/Server/ServerLoop.h>
// P5c (ct): object_death's producer (CONTRACT-P5C.md §5.2) - the death notice's split arm
@@ -1256,7 +1254,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
return StorageMatchesSize(resource, bufferObject.GetSize());
}
#if MOBILEGL_BUILD_RECORD_ARM
// -------------------------------------------------------------------------------
// R-11 - THE SERVER'S OWN COPY OF THE STAGED BYTES. Package v1.
//
@@ -1304,7 +1301,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
ServerStaged().RequireCoverage(&resource, hostBase, start, end, site);
}
}
#endif // MOBILEGL_BUILD_RECORD_ARM
#if MOBILEGL_BUILD_DISAGGREGATED
// -------------------------------------------------------------------------------
@@ -1385,7 +1381,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The four hostBytes sites read the same in both builds. The non-split expansion is the
// ORIGINAL EXPRESSION, character for character - `raw - offset` - so a push or verify build
// compiles exactly what it compiled before R-11 and the split arm is the only new behaviour.
#if MOBILEGL_BUILD_RECORD_ARM
#define MGL_SERVER_STAGED_ADOPT(res, width, bytes, offset, size) \
ServerStaged().Adopt(&(res), (width), (bytes), (offset), (size))
#define MGL_SERVER_STAGED_DROP(res) ServerStaged().Drop(&(res))
@@ -1394,14 +1389,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
ServerStaged().RequireCoverage(&(res), (base), (start), (end), (site))
#define MGL_SERVER_STAGED_REQUIRE_PENDING(res, base, frontendSize, site) \
RequireStagedCoverageForPendingRanges((res), (base), (frontendSize), (site))
#else
#define MGL_SERVER_STAGED_ADOPT(res, width, bytes, offset, size) \
(static_cast<const Uint8*>(bytes) - (offset))
#define MGL_SERVER_STAGED_DROP(res) ((void)0)
#define MGL_SERVER_STAGED_DROP_ALL() ((void)0)
#define MGL_SERVER_STAGED_REQUIRE(res, base, start, end, site) ((void)0)
#define MGL_SERVER_STAGED_REQUIRE_PENDING(res, base, frontendSize, site) ((void)0)
#endif
// The host bytes a range upload/flush reads. On the legacy arm the frontend object's
// shadow; on the handle arm the base the last content-carrying call handed over.
@@ -1410,9 +1397,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (start >= end || hostBase == nullptr) return;
#if MOBILEGL_BUILD_RECORD_ARM
MGL_SERVER_STAGED_REQUIRE(resource, hostBase, start, end, "upload_range");
#endif
BindBufferId(TempBufferTarget, resource.id);
g_GLESFuncs.glBufferSubData(TempBufferTarget, (GLintptr)start, (GLsizeiptr)(end - start),
hostBase + start);
@@ -1987,9 +1972,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
auto* glesResource = static_cast<GLESBufferResource*>(resource.get());
if (glesResource->contextGeneration != g_bufferContextGeneration) {
glesResource->id = 0; // id belonged to a destroyed context
#if MOBILEGL_BUILD_RECORD_ARM
RetireT0Import(*glesResource); // P11 B2: its GL store died with that context
#endif
return;
}
if (CanTouchGLNow()) {
@@ -2005,10 +1988,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_GLESFuncs.glDeleteBuffers(1, &glesResource->id);
glesResource->id = 0;
}
#if MOBILEGL_BUILD_RECORD_ARM
// P11 B2: a T0 store's AHardwareBuffer goes only after a fence behind the delete.
RetireT0Import(*glesResource);
#endif
return;
}
const std::lock_guard<std::mutex> lock(g_deferredBufferReleasesMutex);
@@ -2112,7 +2093,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
void Ops_H_Respecify(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPResourceDesc& desc,
const void* initialBytes) {
auto* resource = FindBufferResourceForHandle(res);
#if MOBILEGL_BUILD_RECORD_ARM
// P13 W4a (§1.4): ON MONOLITH'S RECORD ARM THE TWIN IS MINTED HERE, not lazily at
// the first draw. The record arm's readers have no frontend object to re-read a
// base from, so the base this call carries - the client's shadow, for an orphaning
@@ -2122,9 +2102,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (resource == nullptr && aliasesFrontend && initialBytes != nullptr) {
resource = GetOrCreateBufferResourceForHandle(res);
}
#else
constexpr Bool aliasesFrontend = false;
#endif
if (resource != nullptr) {
// A respecify's companion pointer IS the shadow base (offset 0) - and an
// ORPHANING one (glBufferData with NULL) carries HasDefinedContent clear and
@@ -2155,11 +2132,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGL_SERVER_STAGED_DROP(*resource);
resource->hostBytes = nullptr;
}
#if MOBILEGL_BUILD_RECORD_ARM
// P8-C: whatever a shader wrote belonged to the store this call replaces.
resource->serverGpuWritten = false;
resource->contentSinceRespecify = false;
#endif
}
if (!resource) return; // lazy: the ensure path full-uploads on creation
if (resource->immutableStorage) {
@@ -2178,13 +2153,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
resource->id = 0;
resource->immutableStorage = false;
}
#if MOBILEGL_BUILD_RECORD_ARM
// P11 B2: a respecified T0 store ends here on the server's side too. Its
// AHardwareBuffer is released only after a fence behind the delete (or at once
// when the store's context is already gone) - never under a frame in flight,
// however the client's own release (at this respecify) raced it.
RetireT0Import(*resource);
#endif
resource->storageInitialized = false;
resource->storageSize = 0;
resource->pendingRespecify = true;
@@ -2219,7 +2192,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
const SizeT offset = static_cast<SizeT>(MG_Pipe::MGPipeSubDataBufferOffset(record));
const SizeT size = static_cast<SizeT>(MG_Pipe::MGPipeSubDataBufferSize(record));
auto* resource = FindBufferResourceForHandle(res);
#if MOBILEGL_BUILD_RECORD_ARM
// R-11 / ID-52 item 3: under an ACTIVE TRANSPORT this record's bytes are the ONLY
// delivery of the buffer's content - the client sends no companion pointer and the
// server has no MappedData to fall back on. But the twin is created LAZILY at the
@@ -2233,9 +2205,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MG_Config::DataArmIsRecord()) {
resource = GetOrCreateBufferResourceForHandle(res);
}
#endif
if (!resource) return;
#if MOBILEGL_BUILD_RECORD_ARM
// P11 B2: A T0 STORE TAKES A SERVER-ORIGINATED WRITE GPU-ORDERED. Its pages are the
// client's AHardwareBuffer, so there is no staged copy to refresh and the adopted
// early return below ("the coherent map already has the bytes") is false for bytes
@@ -2294,7 +2264,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (resource != nullptr) resource->serverGpuWritten = true;
return;
}
#endif
// M-2: UNDER pendingMutex, because this line runs BEFORE the CanTouchGLNow()
// test below - i.e. on the arm D-A2 deliberately keeps reachable off the render
// thread - while every reader of hostBytes (Ops_H_Readback's drain, the
@@ -2317,9 +2286,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// ("these bytes, at this base") and the drain takes this mutex to lift them.
resource->hostBytes =
MGL_SERVER_STAGED_ADOPT(*resource, ResourceWidthOf(res), bytes, offset, size);
#if MOBILEGL_BUILD_RECORD_ARM
if (size != 0) resource->contentSinceRespecify = true;
#endif
}
if (resource->pendingRespecify) return; // full re-upload pending anyway
if (!CanTouchGLNow() || resource->id == 0 ||
@@ -2380,9 +2347,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const std::lock_guard<std::mutex> lock(resource->pendingMutex);
resource->hostBytes = MGL_SERVER_STAGED_ADOPT(*resource, ResourceWidthOf(res), bytes,
start, end - start);
#if MOBILEGL_BUILD_RECORD_ARM
if (end > start) resource->contentSinceRespecify = true;
#endif
}
if (resource->pendingRespecify) return;
if (!CanTouchGLNow() || resource->id == 0 ||
@@ -2413,14 +2378,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
#if MOBILEGL_BUILD_RECORD_ARM
// The kill-switch arm's own Memcpy, and the one that passes
// GL_MAP_INVALIDATE_RANGE_BIT - i.e. the one that tells the driver the old
// bytes are dead. Bytes outside the staged coverage are exactly the ones
// that claim is false for.
MGL_SERVER_STAGED_REQUIRE(*resource, resource->hostBytes, start, end,
"flush_range_invalidate_map");
#endif
BindBufferId(TempBufferTarget, resource->id);
void* mappedData = g_GLESFuncs.glMapBufferRange(
TempBufferTarget, (GLintptr)start, (GLsizeiptr)(end - start),
@@ -2440,15 +2403,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
void Ops_H_Readback(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPReadback& record) {
#if MOBILEGL_BUILD_RECORD_ARM
// An empty capture may conservatively request a writeback before
// any draw materialized this buffer. Resolve its staged resource
// exactly as a GPU consumer would, then return the real bytes.
auto* resource = MG_Config::DataArmIsRecord()
? EnsureBufferResourceForHandle(nullptr, res) : FindBufferResourceForHandle(res);
#else
auto* resource = FindBufferResourceForHandle(res);
#endif
if (!resource || resource->id == 0 || !resource->storageInitialized) return;
if (!CanTouchGLNow() || resource->contextGeneration != g_bufferContextGeneration) return;
if (resource->persistentMapped) {
@@ -2517,7 +2476,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The shadow now matches the backend byte for byte; without this the next draw
// would see a newer serial and re-upload the readback over it.
resource->syncedChangeSerial = ResourceSerialOf(res);
#if MOBILEGL_BUILD_RECORD_ARM
// P13 W4a: on monolith's record arm the base this twin reads IS the shadow the
// writeback just filled, so a whole-store readback is also the refresh
// SplitHostBytesForCpuRead would owe. (A copying store's copy is not the
@@ -2525,7 +2483,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (MG_Config::RecordArmAliasesFrontend() && readOffset == 0 && size >= ResourceWidthOf(res)) {
resource->serverGpuWritten = false;
}
#endif
}
void Ops_H_Destroy(MG_Pipe::MGPipeHandle res) {
@@ -3052,7 +3009,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
#endif // MOBILEGL_BUILD_DISAGGREGATED && __ANDROID__
#if MOBILEGL_BUILD_RECORD_ARM
// ---- P5c (tx): the TEXTURE half of the resource family, staged server-side --------
//
// The buffer half's R-11 pattern (ServerStaged() above), for texture levels: the
@@ -3110,7 +3066,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// disaggregation did not compile. The narrower fix (guarding the macro block too) is wrong:
// that block also carries TextureDiagName and MGB_TEXTURE_RECORD_ARM_SELECTED, which the push
// flavour does need. Caught by the `flavours` gate step; see ID-124.
#endif // MOBILEGL_BUILD_RECORD_ARM
// The inverse of MG_Pipe::MGPipeResourceTargetForTextureTarget, for the sync's
// target reads (ConvertTextureTargetToBackendGLEnum and MapToBackendTextureTarget
// both want the frontend enum).
@@ -3130,7 +3085,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
default: return TextureTarget::Unknown;
}
}
#if MOBILEGL_BUILD_RECORD_ARM
void Ops_H_TextureSubData(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPSubData& record,
const void* bytes, const MG_Pipe::MGPSubRegion* regions) {
@@ -3232,7 +3186,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
MG_Record::ServerStagedTexture().Drop(
MG_Record::StagedTextureStore::KeyForHandle(res));
}
#endif // MOBILEGL_BUILD_RECORD_ARM
const MG_Pipe::MGPipeResourceOps g_glesResourceOps = {
.Create = Ops_H_Create,
@@ -3244,7 +3197,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
.Destroy = Ops_H_DestroyTracked,
.MapPersistent = Ops_H_MapPersistentTracked,
.UnmapPersistent = Ops_H_UnmapPersistent,
#if MOBILEGL_BUILD_RECORD_ARM
.TextureSubData = Ops_H_TextureSubData,
.TextureRespecify = Ops_H_TextureRespecify,
.TextureDestroy = Ops_H_TextureDestroy,
@@ -3252,7 +3204,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// P11 B2 (T0). Android only: nowhere else is there an AHardwareBuffer to import.
.ImportExternal = Ops_H_ImportExternalTracked,
.SelfTestExternal = Ops_H_SelfTestExternal,
#endif
#endif
};
@@ -3512,7 +3463,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
return twin ? twin->get() : nullptr;
}
#if MOBILEGL_BUILD_RECORD_ARM
void RequireStagedCoverage(GLESBufferResource& resource, const Uint8* hostBase, SizeT start,
SizeT end, const char* site) {
ServerStaged().RequireCoverage(&resource, hostBase, start, end, site);
@@ -3678,7 +3628,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
const std::lock_guard<std::mutex> lock(resource.pendingMutex);
resource.hostBytes = MGL_SERVER_STAGED_ADOPT(resource, ResourceWidthOf(res), bytes, offset, size);
}
#endif
MG_Pipe::MGPipeHandle HandleOfBuffer(const MG_State::GLState::BufferObject* bufferObject) {
if (bufferObject == nullptr) return MG_Pipe::kMGPipeNullHandle;
@@ -3690,7 +3639,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// allocator probe on a miss - and remembers the answer, which the minting overload
// was previously the only writer of. A null answer is deliberately never memoised
// (see the comment at HandleOf).
#if MOBILEGL_BUILD_RECORD_ARM
// P5c merge coordination (CONTRACT-P5C §3.1's named exemption): every caller of
// this function is a site whose handle-carrying records (set_shader_buffers /
// set_stream_output_targets) the client does not emit until sb, so the probe
@@ -3706,7 +3654,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// IsBufferDrawCleanByHandle / MarkBufferGpuWrittenByHandle, which is the last
// caller set this function has.
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
#endif
return g_backendBufferResources.HandleOf(bufferObject);
}
@@ -3816,7 +3763,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// generation, and a shadow that outlived its twin would be looked up by a RECYCLED
// address on the next allocation - which is the quietest possible wrong answer.
MGL_SERVER_STAGED_DROP_ALL();
#if MOBILEGL_BUILD_RECORD_ARM
// tx's texture shadows die for the same reason, keyed by handle rather than address
// but with the same recycled-identity failure mode: a new context's allocator may
// hand out a {slot, gen} the old one's store still answers for.
@@ -3827,7 +3773,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
#if MOBILEGL_BUILD_DISAGGREGATED
ProcessDeferredT0Retires();
SweepT0Retired(/*contextGone=*/true);
#endif
#endif
}
@@ -3929,11 +3874,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
const auto* record = ResourceRecordOf(res);
if (record == nullptr) return false;
#if MOBILEGL_PIPE_VERIFY && !MOBILEGL_BUILD_RECORD_ARM
MOBILEGL_ASSERT(!record->HasLiveHostWrites,
"MGPipeResourceRecord::HasLiveHostWrites is set, but this build has no producer "
"for it - P5 b1's producer is MGPSubData::HasLiveHostWrites and is split-only");
#endif
if (record->HasLiveHostWrites) return false;
// THE LAST FRONTEND READ IN THIS FUNCTION, AND P5 b1 RETIRES IT - under split
// only, because that is the only build where it is both wrong and replaceable.
@@ -4012,7 +3952,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// fallback for the drains that have NO object (the readback flush), and it is
// nulled at both of those events.
const auto liveHostBase = [&resource, &bufferObject]() -> const Uint8* {
#if MOBILEGL_BUILD_RECORD_ARM
// M-2 / codex 3 / ID-52 item 3: under an ACTIVE TRANSPORT the server's staged
// copy (resource->hostBytes, filled by MGL_SERVER_STAGED_ADOPT in Ops_H_SubData /
// Ops_H_FlushRange) is the ONLY authoritative base. Preferring the frontend
@@ -4024,7 +3963,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (MG_Config::DataArmIsRecord()) {
return resource->hostBytes;
}
#endif
if (bufferObject) return bufferObject->MappedData();
return resource->hostBytes;
};
@@ -4041,7 +3979,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
resource->persistentMapped = false;
resource->persistentPtr = nullptr;
resource->immutableStorage = false;
#if MOBILEGL_BUILD_RECORD_ARM
// m-5 / codex 5: OnBackendContextDestroyed ran MGL_SERVER_STAGED_DROP_ALL(), which
// frees every server shadow but does NOT null the hostBytes that name them - so a
// twin that SURVIVES a context loss still carries a base into the freed allocation.
@@ -4084,7 +4021,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
RetireT0Import(*resource);
}
}
#endif
#endif
}
@@ -4129,12 +4065,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
// application ORPHANED the gap is its own undefined content and the upload is
// legal (ResourceContentIsDeclared, above). RequireCoverage is a no-op for the
// legacy arm's MappedData() and for a non-copying store.
#if MOBILEGL_BUILD_RECORD_ARM
if (ResourceContentIsDeclared(res)) {
MGL_SERVER_STAGED_REQUIRE(*resource, liveHostBase(), 0, poolSize,
"pool_reuse_whole_store");
}
#endif
g_GLESFuncs.glBufferSubData(TempBufferTarget, 0, (GLsizeiptr)poolSize, liveHostBase());
if (MG_Util::PipeStats::Enabled()) {
MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageBuffer,
@@ -4161,7 +4095,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// D-N keeps this call here for P3a. It can queue ranges and move the record, so
// everything below is read AFTER it.
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (hd, CONTRACT-P5C §3.8): under an active transport the frontend accessor is a
// layer-1 surface ("buffer-legacy-arm") and the client's own pre-verb
// persistent-map push is the only producer; the call is skipped outright. Under
@@ -4169,9 +4102,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (bufferObject && !MG_Config::DataArmIsRecord()) {
bufferObject->SyncPersistentMappedRange();
}
#else
if (bufferObject) bufferObject->SyncPersistentMappedRange();
#endif
const auto* record = ResourceRecordOf(res);
const SizeT size = record != nullptr ? static_cast<SizeT>(record->Desc.Width) : 0;
@@ -4214,13 +4144,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
// the last orphan". With no coverage and a clear bit the store is undefined by the
// application's own declaration and the upload stays a pure NULL reallocation.
const Bool shadowHasContent =
#if MOBILEGL_BUILD_RECORD_ARM
MG_Config::DataArmIsRecord()
? (record->Desc.HasDefinedContent != 0 ||
(ServerStaged().CopiesIntoServerStorage() ? ServerStaged().CoveredRunCount(resource) != 0
: resource->contentSinceRespecify))
:
#endif
(bufferObject ? bufferObject->HasDefinedContent() : (record->Desc.HasDefinedContent != 0));
const void* initialData = shadowHasContent ? hostBase : nullptr;
// M-3 / codex 4: a RespecifyStorageWith(..., initialData != nullptr) is a WHOLE-STORE
@@ -4235,14 +4163,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
// the bytes it did not write are undefined by its own glBufferData(NULL) - the rule at
// ResourceContentIsDeclared. Round 2 refused that idiom and aborted six joint entries.
const auto requireWholeStoreCoverage = [&]() {
#if MOBILEGL_BUILD_RECORD_ARM
if (initialData != nullptr && record->Desc.HasDefinedContent != 0) {
MGL_SERVER_STAGED_REQUIRE(*resource, static_cast<const Uint8*>(initialData), 0,
size, "respecify_whole_store");
}
#else
(void)initialData;
#endif
};
if (resource->pendingRespecify || !resource->storageInitialized || resource->storageSize != size) {
@@ -4942,7 +4866,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
return true;
}
#if MOBILEGL_BUILD_RECORD_ARM
// THE SAFETY ARGUMENT P4a MADE AND NEVER RAN (P3b/P4b espryt D1 slice 5).
//
// "Running the legacy arm" is a real answer in a monolith and a POSTPONED CRASH under a
@@ -4974,7 +4897,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
bit, detail);
BufferImpl::StopOnArmlessPipeSubsystem(what);
}
#endif
// The release-build VOICE for StateBackendObjectRegistry::GetOrCreateByHandle's two
// silent refusals, shared by the kinds P4a re-keys so the wording cannot drift between
@@ -5096,7 +5018,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
"MOBILEGL_PIPE_PUSH leaves kMGPipeSubsystemFramebuffer (bit 9) clear (or refuses "
"it) and MOBILEGL_PIPE_LEGACY_MEMOS=0 disables the pre-handle g_fboSynced* arm");
}
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord() &&
verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) {
StopOnTransportWithoutRecordArm(
@@ -5107,7 +5028,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
"to reach it would raise Fatal{UnmigratedPipeInput, "
"\"GetFramebufferBindingSlot@<verb>\"} mid-frame instead");
}
#endif
const Bool enabled = verdict == BufferImpl::PipeSubsystemArmVerdict::Handles;
MGLOG_D("MGPipe: Espryt framebuffer family runs the %s arm", enabled ? "handle" : "legacy");
return enabled;
@@ -5141,7 +5061,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
"refuses it) and MOBILEGL_PIPE_LEGACY_MEMOS=0 disables the pre-handle texture "
"cheap-gate trio");
}
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord() &&
verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) {
StopOnTransportWithoutRecordArm(
@@ -5150,7 +5069,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
"objects and their binding slots, which under a transport are client memory (rule "
"E) rather than anything this side owns");
}
#endif
const Bool enabled = verdict == BufferImpl::PipeSubsystemArmVerdict::Handles;
MGLOG_D("MGPipe: Espryt texture-resource family runs the %s arm", enabled ? "handle" : "legacy");
return enabled;
@@ -5177,7 +5095,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
"it) and MOBILEGL_PIPE_LEGACY_MEMOS=0 disables UnitSamplerLookupMemo's WeakPtr "
"arm and SamplerPassMemo's raw-pointer rows");
}
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord() &&
verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) {
StopOnTransportWithoutRecordArm(
@@ -5187,7 +5104,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
"this side to key on - a WeakPtr that can never lock is not a slower arm, it is a "
"unit that samples through whatever it last held");
}
#endif
const Bool enabled = verdict == BufferImpl::PipeSubsystemArmVerdict::Handles;
MGLOG_D("MGPipe: Espryt sampler family runs the %s arm", enabled ? "handle" : "legacy");
return enabled;
@@ -5206,7 +5122,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
"MOBILEGL_PIPE_PUSH leaves kMGPipeSubsystemPrograms (bit 12) clear and "
"MOBILEGL_PIPE_LEGACY_MEMOS=0 disables g_programTwinLookupMemo");
}
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord() &&
verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) {
StopOnTransportWithoutRecordArm(
@@ -5216,7 +5131,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
"ShaderCso handle with its archive beside the record and there is no frontend "
"program here at all");
}
#endif
const Bool enabled = verdict == BufferImpl::PipeSubsystemArmVerdict::Handles;
MGLOG_D("MGPipe: Espryt program family runs the %s arm", enabled ? "handle" : "legacy");
return enabled;
@@ -5487,11 +5401,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// MGPipeSlots().FindByLifetimeId, which is a hash lookup. HandleOf answers
// identically - the same allocator probe on a miss - and remembers the answer; a
// null answer is deliberately never memoised (SlotTables.h, at HandleOf).
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named debt inside
// the scope - P3b/P4b rekeys the sampler-view registry onto handles.
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
#endif
return g_backendSamplerViews.HandleOf(textureObject);
}
} // namespace SamplerViewImpl
@@ -6366,15 +6278,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
// recorded shadow base for such a resource read freed memory; hostBytes is nulled
// at adoption for the same reason, so the fallback below is a null and a refusal
// rather than a stale read.
#if MOBILEGL_BUILD_RECORD_ARM
const Bool sourceGpuWritten =
MG_Config::DataArmIsRecord() && resource->serverGpuWritten;
if (sourceGpuWritten) {
BufferImpl::SplitHostBytesForCpuRead(*resource, binding.Res, "fp64_vertex_narrowing");
}
#else
constexpr Bool sourceGpuWritten = false;
#endif
const Uint8* const sourceBase =
(resource->persistentMapped && resource->persistentPtr != nullptr)
? static_cast<const Uint8*>(resource->persistentPtr)
@@ -6553,7 +6461,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (m_imageBindableStorageRequired) {
return;
}
#if MOBILEGL_BUILD_RECORD_ARM
// P13 W4b (defect h): monolith's record arm takes the body too; its old arm only
// cleared m_isInitialized and replayed the frontend shadow at the next sync.
if (MG_Config::DataArmIsRecord()) {
@@ -6704,10 +6611,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::TextureRemintPulls, 1);
}
}
#else
(void)res;
(void)record;
#endif
// Every defined byte now belongs to the server and every required
// replay is in its pending set. The existing upload path builds and
// fills the immutable/widened carrier from those records.
@@ -6948,11 +6851,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
const MG_Pipe::MGPipeHandle rearmRes =
TextureResourceSubsystemEnabled()
? [&]() {
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed resolution, named
// debt inside the scope - P3b/P4b rekeys the registry.
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
#endif
return g_backendTextureObjects.HandleOf(stateTextureObject.get());
}()
: MG_Pipe::kMGPipeNullHandle;
@@ -7171,11 +7072,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
static inline GLint s_skipImages = 0;
};
#if MOBILEGL_BUILD_RECORD_ARM
void ExerciseDefaultUnpackScopeForTesting() {
const ScopedDefaultUnpackState scope;
}
#endif
// --- Unpack-ring staging (see BufferImpl::UnpackRingAvailable) ---------------
// One rectangular (or whole-level) region of a level shadow, repacked TIGHTLY
@@ -7859,12 +7758,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// read). Sites that were previously correct only because every writer of a handle-note happens
// to be transport-gated now say so themselves, so a future by-handle noter cannot quietly
// re-open ID-107's hole somewhere else.
#if MOBILEGL_BUILD_RECORD_ARM
#define MGB_TEXTURE_RECORD_ARM_SELECTED() \
(MG_Config::DataArmIsRecord() && TextureResourceSubsystemEnabled())
#else
#define MGB_TEXTURE_RECORD_ARM_SELECTED() (false)
#endif
// Returns true when the caller must decline (no handle noted: the caller's bug it always
// was, named by the caller's own log line), false when the record arm can serve the call,
@@ -8017,7 +7912,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
StampViewSyncKeys(stateTextureObject);
}
#if MOBILEGL_BUILD_RECORD_ARM
// 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(
@@ -8102,7 +7996,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The storage's, for the steady arm's reason above (P3b/P4b D3).
m_syncedResourceSerial = storageSerial;
}
#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
@@ -8126,7 +8019,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// pre-P4a expression exactly when MOBILEGL_PIPE_PUSH is off, so the pull build's
// preprocessed text, and therefore its object code, is unchanged. Both are #undef'd
// immediately after the function.
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (tx): THE READS THE FOUR UPLOAD ARMS MAKE, re-sourced. With an active transport the
// apply thread may not name the client's TextureObjectMipmap at all (rule E), so on the
// handle arm:
@@ -8172,15 +8064,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MG_Record::StagedTextureStore::KeyForHandle(pushedRes), static_cast<Uint16>(tgt), \
static_cast<Uint16>(lvl), site) \
: (obj)->MapMipmapData(tgt, lvl))
#else
#define MGB_TEXTURE_TARGET(obj) ((obj)->GetTarget())
#define MGB_UPLOAD_TARGETS(obj) ((obj)->GetUploadTargets())
#define MGB_LEVEL_TEXEL_SIZE(obj, tgt, lvl) ((obj)->GetMipmapTexelSize(tgt, lvl))
#define MGB_LEVEL_BYTE_SIZE(obj, tgt, lvl) ((obj)->GetMipmapByteSize(tgt, lvl))
#define MGB_LEVEL_TEXELS(obj, tgt, lvl, site) ((obj)->MapMipmapData(tgt, lvl))
#endif
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (tx): the disaggregated pair adds ONE term and ONE clear to the P4a shapes - the
// staged-texture store's GPU-dirty mark (T5: a level the GPU generated dirties the SERVER's
// shadow, and the pending set cannot see it). The mark is never set on Espryt today - its
@@ -8210,23 +8094,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
(obj)->MarkStorageDirty(tgt, lvl, false); \
} \
} while (0)
#else
#define MGB_LEVEL_NEEDS_UPLOAD(obj, tgt, lvl) \
(pushedStorage != nullptr \
? FindPipeTextureUpload(*pushedStorage, static_cast<Uint16>(tgt), static_cast<Uint16>(lvl)) != nullptr \
: (obj)->IsStorageDirty(tgt, lvl))
// Re-resolves the record itself, so it is safe after any amount of driver work - and it
// invalidates any PendingUpload* taken earlier for THIS texture, which is why every such pointer
// is used and dropped inside one level's iteration.
#define MGB_LEVEL_UPLOAD_DONE(obj, tgt, lvl) \
do { \
if (pushedStorage != nullptr) { \
ConsumePipeTextureUpload(pushedRes, static_cast<Uint16>(tgt), static_cast<Uint16>(lvl)); \
} else { \
(obj)->MarkStorageDirty(tgt, lvl, false); \
} \
} while (0)
#endif
// THE STORAGE SHAPE, and on the handle arm it is the DESCRIPTOR's (C.3's d2: "the
// m_prevTextureInfo probe re-keyed onto the resource record's Serial AND Desc"; review
@@ -8480,7 +8347,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
return;
}
}
#if MOBILEGL_BUILD_RECORD_ARM
#if MOBILEGL_BUILD_DISAGGREGATED
// SHARED IMAGES: ahead of the view test and of the staged-store and serial gates below,
// none of which knows the texels belong to an image - the staged store holds none of
@@ -8515,7 +8381,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (MG_Config::DataArmIsRecord() && 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
@@ -8563,14 +8428,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
pushedStorage->Desc.ImageBindableHint != 0 ||
(pushedStorage->Desc.BindMask & MG_Pipe::kMGPipeBindShaderImage) != 0;
if (pushedWantsImageBindableStorage && !m_imageBindableStorageRequired) {
#if MOBILEGL_BUILD_RECORD_ARM
// 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::DataArmIsRecord()) {
RequireImageBindableStorageByHandle(pushedRes, *pushedStorage);
} else
#endif
RequireImageBindableStorage(stateTextureObject);
}
@@ -8638,7 +8501,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// left the backend name with no levels whatsoever, so the level that WAS defined could
// never be sampled or read back. Sync whenever some level holds an image; the per-level
// loops below skip the degenerate ones individually.
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (gt, CONTRACT-P5C §6 layer 1): on the staged arm the same question is answered
// out of the SERVER's staged-texture store, never out of the frontend object - the
// store's Defined-ness (fed by the respecify hook and the sub-data adoption) IS "some
@@ -8679,7 +8541,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
return;
}
} else
#endif
if (!stateTextureObject->IsComplete() && !HasAnyDefinedMipmapLevel(stateTextureObject.get())) {
MGLOG_D("Texture object with ID: %u has no defined image level, skipping sync.",
stateTextureObject->GetExternalIndex());
@@ -9263,7 +9124,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
: nullptr;
const auto dirtyRegion = [&]() -> MG_State::GLState::MipmapDirtyRegion {
if (pendingUpload == nullptr) {
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (tx): a level this arm owes with NO pending upload behind it
// was dirtied by the GPU (T5), and the dirty answer is the
// server's own mark on the staged shadow - the whole level,
@@ -9276,7 +9136,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
static_cast<Uint16>(uploadTarget), static_cast<Uint16>(level));
return MG_State::GLState::MipmapDirtyRegion{IntVec3{0, 0, 0}, gpuExtent};
}
#endif
return textureMipmapObject->GetStorageDirtyRegion(uploadTarget, level);
}
// MGPBox is {origin, extent}; MipmapDirtyRegion is {lo, hi}. The
@@ -9469,13 +9328,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
region.Z + static_cast<Int32>(region.D)}};
}
} else
#if MOBILEGL_BUILD_RECORD_ARM
// tx: with an active transport the rect list is the record's (the
// pendingUpload arm above) or nothing - the server's GPU-dirty mark
// is whole-level and has no scatter refinement to hand out, and the
// frontend's rect model is not this side's to read.
if (!MGB_STAGED_TEXTURE_LIVE)
#endif
dirtyRectCount = textureMipmapObject->GetStorageDirtyRects(
uploadTarget, level, dirtyRects,
MG_State::GLState::MipmapStorage::kMaxDirtyRects);
@@ -9512,14 +9369,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
region.Z + static_cast<Int32>(region.D)}});
}
} else
#if MOBILEGL_BUILD_RECORD_ARM
// With an active transport and no record behind the level, the
// server's own whole-level GPU-dirty mark is the whole story (the
// same reason the rect list above is not read here).
if (MGB_STAGED_TEXTURE_LIVE) {
dirtyFootprint.clear();
} else
#endif
textureMipmapObject->GetStorageDirtyFootprint(uploadTarget, level, dirtyFootprint);
// One box is the union box itself, which the branches below already
// upload exactly (as the whole level when it is the whole level).
@@ -10052,9 +9907,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
#undef MGB_LEVEL_TEXEL_SIZE
#undef MGB_LEVEL_BYTE_SIZE
#undef MGB_LEVEL_TEXELS
#if MOBILEGL_BUILD_RECORD_ARM
#undef MGB_STAGED_TEXTURE_LIVE
#endif
// 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
@@ -10066,11 +9919,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
MG_Pipe::MGPipeHandle res = m_pushedSyncHandle;
if (MG_Pipe::MGPipeHandleIsNull(res)) {
if (!stateTextureObject) return nullptr;
#if MOBILEGL_BUILD_RECORD_ARM
// 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
res = g_backendTextureObjects.HandleOf(stateTextureObject.get());
}
return PipeTextureRecordForHandle(res);
@@ -10687,18 +10538,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
storage = record->Desc.ViewOf;
}
if (auto* twin = ResolveTextureTwin(storage)) twin->NoteDriverSideWrite();
#if MOBILEGL_BUILD_RECORD_ARM
// P8-E: the staged store stops standing for this texture's content (a no-op on a
// monolith arm, where the store does not copy).
if (!storeFollowsTheWrite && !MG_Pipe::MGPipeHandleIsNull(storage)) {
MG_Record::ServerStagedTexture().MarkDriverWritten(
MG_Record::StagedTextureStore::KeyForHandle(storage));
}
#else
(void)storeFollowsTheWrite;
#endif
}
#if MOBILEGL_BUILD_RECORD_ARM
Bool FollowCopyImageInStagedStore(MG_Pipe::MGPipeHandle source, Uint32 sourceLevel, const IntVec3& sourceOrigin,
MG_Pipe::MGPipeHandle destination, Uint32 destinationLevel,
const IntVec3& destinationOrigin, const IntVec3& size) {
@@ -10737,7 +10583,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
destinationTarget, static_cast<Uint16>(destinationLevel), destinationOrigin,
size);
}
#endif
} // namespace TextureImpl
namespace FramebufferImpl {
@@ -10900,11 +10745,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (attachmentObject.IsTexture()) {
const auto& textureObject = attachmentObject.GetTexture();
SharedPtr<TextureImpl::BackendTextureObject> backendTextureObject;
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution (and the mint on
// a first attach), named debt inside the scope - P3b/P4b rekeys the registry.
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
#endif
if (auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get())) {
backendTextureObject = *backendTextureSlot;
} else {
@@ -10957,11 +10800,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
} else if (attachmentObject.IsRenderbuffer()) {
const auto& renderbufferObject = attachmentObject.GetRenderbuffer();
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution (and the mint on
// a first attach), named debt inside the scope - P3b/P4b rekeys the registry.
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
#endif
SharedPtr<RenderbufferImpl::BackendRenderbufferObject> backendRenderbufferObject;
if (auto* backendRenderbufferSlot =
RenderbufferImpl::g_backendRenderbufferObjects.Find(renderbufferObject.get())) {
@@ -11295,14 +11136,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
// push-monolith arm keeps the frontend sync it had at f6cfcbd3, token for token,
// because tx2's record arm inside SyncMipmapsToBackend is itself selected by
// `Transport != Monolith` and cannot answer here.
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord()) {
backendTextureObject->SyncMipmapsToBackendByHandle(surface.Res);
// From here on every draw, clear and blit into this framebuffer can write the
// texture behind the staged shadow's back (the frontend arm's note, by handle).
TextureImpl::NoteDriverSideTextureWriteByHandle(surface.Res);
} else
#endif
{
const SharedPtr<MG_State::GLState::ITextureObject> monolithTexture =
(monolithAttachment != nullptr && monolithAttachment->IsTexture())
@@ -11384,11 +11223,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// GLContext (RecordError) and the by-handle form deliberately does not, because
// under a transport there is no application on this side to report it to. On the
// push-monolith arm there is, so the monolith arm keeps the object form.
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord()) {
backendRenderbufferObject->SyncToBackendByHandle(surface.Res);
} else
#endif
{
const SharedPtr<MG_State::GLState::RenderbufferObject> monolithRenderbuffer =
(monolithAttachment != nullptr && monolithAttachment->IsRenderbuffer())
@@ -11436,12 +11273,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
// (the three-channel colour-renderable widening). GL answers such a read with 1.0, but
// the storage holds whatever the draw wrote there, so the readback has to overwrite it.
Bool IsAlphaWidenedFallbackReadAttachment() {
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord()) {
const auto* record = MG_Pipe::MGPipeApplier().ReadFramebuffer();
return record && IsAlphaWidenedColorSurface(record->ReadSurface);
}
#endif
const auto* attachmentObject = GetReadColorAttachment();
if (attachmentObject == nullptr) {
return false;
@@ -11450,13 +11285,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
Bool IsFixedPointFallbackReadAttachment() {
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord()) {
const auto* record = MG_Pipe::MGPipeApplier().ReadFramebuffer();
return record && record->ReadSurface.Kind != MG_Pipe::kMGPipeSurfaceKindNone &&
IsSnormFormat(static_cast<TextureInternalFormat>(record->ReadSurface.InternalFormat));
}
#endif
const auto& readFBO =
MG_Pipe::gPipeInputs.GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject();
if (!readFBO) {
@@ -11623,11 +11456,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// P5c (hd): with an active transport the handle is the caller's
// (m_pushedSyncHandle), never the client allocator's - see SyncToBackend.
const MG_Pipe::MGPipeHandle fbo =
#if MOBILEGL_BUILD_RECORD_ARM
MG_Config::DataArmIsRecord()
? m_pushedSyncHandle
:
#endif
g_backendFramebufferObjects.HandleOf(stateFBOObject.get());
// ID-19: the OBJECT's record, not "the record of whatever is bound to READ". A
// framebuffer whose read buffer is being pushed need not be the read binding at
@@ -11801,11 +11632,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// is never probed (T2). A null there means a caller reached this arm without a
// record, which the null-record refusal below names.
const MG_Pipe::MGPipeHandle fbo =
#if MOBILEGL_BUILD_RECORD_ARM
MG_Config::DataArmIsRecord()
? m_pushedSyncHandle
:
#endif
g_backendFramebufferObjects.HandleOf(stateFBOObject.get());
pushedRecord = PushedFramebufferRecord(fbo);
if (pushedRecord == nullptr) {
@@ -12083,11 +11912,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Verify that the backend object's name and parameters match the frontend attachment state
if (attachmentObject.IsTexture()) {
const auto& textureObject = attachmentObject.GetTexture();
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named
// debt inside the scope - P3b/P4b rekeys the registry.
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
#endif
auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get());
MOBILEGL_ASSERT(backendTextureSlot != nullptr && *backendTextureSlot != nullptr,
"No backend texture found while framebuffer reports texture attachment.");
@@ -12104,11 +11931,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
"Attachment texture level mismatch between GLES and state object.");
} else if (attachmentObject.IsRenderbuffer()) {
const auto& renderbufferObject = attachmentObject.GetRenderbuffer();
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named
// debt inside the scope - P3b/P4b rekeys the registry.
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
#endif
auto* backendRboSlot =
RenderbufferImpl::g_backendRenderbufferObjects.Find(renderbufferObject.get());
MOBILEGL_ASSERT(
@@ -12253,11 +12078,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
fbo.Slot, fbo.Gen);
return;
}
#if MOBILEGL_BUILD_RECORD_ARM
// Kept in step for the monolith-glue overloads that still resolve their record
// through it (SyncReadBufferToBackend's push arm).
m_pushedSyncHandle = fbo;
#endif
const GLenum glFBOTarget = MG_Util::ConvertFramebufferTargetToGLEnum(asTarget);
Bind(asTarget);
@@ -13109,7 +12932,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// outside the frontend's array, which cannot be addressed at all.
Uint BoundImageUnitFormat(Int unit) {
if (unit < 0 || unit >= MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS) return 0;
#if MOBILEGL_BUILD_RECORD_ARM
// P5e (fb, §5.4): the format-less image bake asks what format the APPLICATION
// named at this unit, and under a transport the record is the only statement of
// it - MGPImageView::InternalFormat is that same GLenum, copied by the client
@@ -13120,7 +12942,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (static_cast<SizeT>(unit) >= st.BoundShaderImages.size()) return 0;
return static_cast<Uint>(st.BoundShaderImages[static_cast<SizeT>(unit)].InternalFormat);
}
#endif
return static_cast<Uint>(MG_Pipe::gPipeInputs.GetImageTextureBinding(unit).Format);
}
@@ -15241,14 +15062,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
// nine-clause rebuild condition reads on the handle arm; the clause COUNT does not
// shrink, its inputs move (D-H5).
if (ProgramSubsystemEnabled()) {
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (G6, CONTRACT-P5C §5.4): MONOLITH GLUE ONLY. This is the frontend-keyed
// lookup the phase exists to retire, and it survives here for the reason
// ruling 1 (ID-81) gives: under Transport=monolith the push build keeps its
// frontend arms token for token, and this arm is only ever reached from them.
// The handle half below names no frontend identity at all.
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
#endif
// The reader hides the composite band, so a program-pipeline composite - which
// the server must never learn is one - resolves through the same call.
const MG_Pipe::MGPipeHandle cso = g_backendProgramObjects.HandleOf(stateProgramObject.get());
@@ -15593,12 +15412,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
// the values are taken from the object, which in monolith are the very
// values the client content-addressed, so the picture stays right while
// the gap is visible rather than silent.
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (G6, CONTRACT-P5C §5.4): the registration probe below resolves the
// sampler's handle by frontend identity - frontend-keyed twin resolution,
// named debt inside the scope - P3b/P4b rekeys the registry onto handles.
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
#endif
if (!MG_Pipe::MGPipeHandleIsNull(
g_backendSamplerObjects.HandleOf(stateSamplerObject.get()))) {
MGLOG_E_ONCE("MGPipe: sampler %u was synced without its unit's SamplerCso handle, so "
@@ -15849,12 +15666,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
const MG_Pipe::MGPipeResourceRecord* pushedRecord = nullptr;
if (TextureResourceSubsystemEnabled()) {
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (G6, CONTRACT-P5C §5.4): the renderbuffer's handle is resolved by
// frontend identity below - frontend-keyed twin resolution, named debt inside
// the scope - P3b/P4b rekeys the registry onto handles.
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
#endif
// MONOLITH GLUE, named as such: the Renderbuffer handle of an object this
// backend still arrives holding. Under a real split it rides in the payload.
const MG_Pipe::MGPipeHandle res = g_backendRenderbufferObjects.HandleOf(stateRBOObject.get());
-30
View File
@@ -478,27 +478,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (!EsprytSlotTablesEnabled()) return nullptr;
if (MG_Pipe::MGPipeHandleIsNull(handle)) return nullptr;
if (handle.Slot >= SlotTable::kMaxHandleSlot) {
#if MOBILEGL_BUILD_RECORD_ARM
MG_Pipe::MGPipeRecordFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12
MG_Pipe::MGFatalFamily::ProtocolCorruption,
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.HandleSlot\"} - "
"GetOrCreateByHandle named slot %u, past this table's %u bound",
handle.Slot, SlotTable::kMaxHandleSlot);
#else
return nullptr;
#endif
}
const Uint32 liveGen = m_slotTables.LiveGenAt(handle.Slot);
if (liveGen != 0 && liveGen > handle.Gen) {
#if MOBILEGL_BUILD_RECORD_ARM
MG_Pipe::MGPipeRecordFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12
MG_Pipe::MGFatalFamily::ProtocolCorruption,
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.Generation\"} - "
"GetOrCreateByHandle named generation %u at slot %u, behind live generation %u",
handle.Gen, handle.Slot, liveGen);
#else
return nullptr;
#endif
}
return &m_slotTables.GetOrCreate(handle);
}
@@ -940,7 +932,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// AcquirePersistentMap or (FLUSH_EXPLICIT) publish only via FlushMappedRange.
Uint64 CurrentBufferMutationEpoch();
void BumpBufferMutationEpoch();
#if MOBILEGL_BUILD_RECORD_ARM
// P5e (vi), CONTRACT-P5E §5.1's first pin: how many bumps came from a thread other than
// the apply thread WHILE an apply thread was running, under a live transport. The
// record arm's clean gate stamps this counter and skips its probes while the stamp
@@ -985,7 +976,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
Uint64 Serial = 0;
};
DrawIndexBufferRequest ResolveDrawIndexBufferFromRecord(const MG_Pipe::MGPipeApplierState& st);
#endif
// The DirectGLES storage behind one frontend buffer. Owned (refcounted) by
// the frontend BufferObject; immediate BufferBackendOps keep it current, so
@@ -1073,7 +1063,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// the legacy arm did; this member exists for the drains that have no object, which
// in P3a is the readback flush and the fp64 narrowing.
const Uint8* hostBytes = nullptr;
#if MOBILEGL_BUILD_RECORD_ARM
// P11 B2 (T0): the client's AHardwareBuffer this store IS - imported with
// glBufferStorageExternalEXT, so `id` is immutable, persistentMapped and
// persistentPtr is this server's own coherent map of the client's pages. The
@@ -1092,7 +1081,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// record arm) keeps no coverage, so the twin says it - the streaming idiom (orphan, then
// glBufferSubData) defines content no descriptor declares.
Bool contentSinceRespecify = false;
#endif
};
// P3a (D-A4): the SEVENTH Espryt slot table, and the first one keyed by a handle the
@@ -1152,7 +1140,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
GLESBufferResource* GetOrCreateBufferResourceForHandle(MG_Pipe::MGPipeHandle res);
GLESBufferResource* FindBufferResourceForHandle(MG_Pipe::MGPipeHandle res);
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (hd): the staged-coverage assertion (StagedShadowStore::RequireCoverage) for a
// read of the server shadow outside the upload ladders - the indirect command-byte
// resolver. A no-op for a base that is not this resource's server shadow.
@@ -1201,7 +1188,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// ladder and DirectGLES.cpp's XFB scatter are its two callers and they must not be able
// to answer it differently.
Bool ResourceContentIsDeclared(MG_Pipe::MGPipeHandle res);
#endif
// MONOLITH GLUE, and named as such: the handle of a resource this backend is looking
// at through a frontend object, resolved through the client allocator's lifetime-id
@@ -1326,12 +1312,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// conjunction in MultiDraw.cpp would satisfy in letter while giving the family two
// selectors that can drift apart. One definition, spelled at every site that reads it.
inline Bool VertexInputReadsRecords() {
#if MOBILEGL_BUILD_RECORD_ARM
return MG_Config::DataArmIsRecord() &&
VertexInputSubsystemEnabled();
#else
return false;
#endif
}
// Registered as the frontend's BufferBackendOps at backend init and on
@@ -1830,10 +1812,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
} // namespace VertexArrayImpl
namespace TextureImpl {
#if MOBILEGL_BUILD_RECORD_ARM
// Drives the actual unpack save/restore helper without needing a texture upload.
void ExerciseDefaultUnpackScopeForTesting();
#endif
inline Bool IsSupportedTextureTarget(TextureTarget target) {
// Every desktop-only target is stored on an ES one; see MapToBackendTextureTarget.
(void)target;
@@ -1968,7 +1948,6 @@ 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_RECORD_ARM
// 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
@@ -1983,7 +1962,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
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
@@ -2367,7 +2345,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// glCopyImageSubData passes: it tries FollowCopyImageInStagedStore first and marks the store
// itself when that fails.
void NoteDriverSideTextureWriteByHandle(MG_Pipe::MGPipeHandle texture, Bool storeFollowsTheWrite = false);
#if MOBILEGL_BUILD_RECORD_ARM
// P8-E (notes/p8/E.md): make glCopyImageSubData's texel-block move in the server's staged
// store too (StagedTextureStore::FollowCopy), so the destination level's bytes stay its
// content. Box in each image's own texel grid. False when the store cannot follow: a cube
@@ -2377,7 +2354,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bool FollowCopyImageInStagedStore(MG_Pipe::MGPipeHandle source, Uint32 sourceLevel, const IntVec3& sourceOrigin,
MG_Pipe::MGPipeHandle destination, Uint32 destinationLevel,
const IntVec3& destinationOrigin, const IntVec3& size);
#endif
// Brings every texture the next draw reads - the touched units' bindings and the draw
// FBO's texture attachments - onto the backend, through the two borrowed-pair memos
// documented at their definitions. Declared here so tests can drive those memos directly.
@@ -2424,14 +2400,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
// "one object is bound to BOTH bindings" skip, where the DRAW pass already did the
// attachment work and only glReadBuffer is READ-target-specific.
void SyncReadBufferToBackendByHandle(MG_Pipe::MGPipeHandle fbo);
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (hd, CONTRACT-P5C §3.2): the framebuffer handle the CURRENT sync is keyed on.
// A caller applying a record sets it before SyncToBackend / SyncReadBufferToBackend,
// which then read the applier's record for THAT handle instead of probing the
// client's slot allocator for the frontend object's lifetime id (T2). Read only
// with an active transport; monolith resolves through HandleOf as it always did.
MG_Pipe::MGPipeHandle m_pushedSyncHandle = MG_Pipe::kMGPipeNullHandle;
#endif
void InvalidateSyncedState();
Uint GetBackendFramebufferId() const { return m_backendFBOId; }
void Bind(FramebufferTarget target) const;
@@ -3124,11 +3098,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Same for gl_BaseVertex: only a program that reads it pays for the per-draw
// uniform write, and only such a program needs the reset after one.
Bool ReadsBaseVertex() const { return m_baseVertexUniformLocation >= 0; }
#if MOBILEGL_BUILD_RECORD_ARM
// P8-C: same for the gl_BaseInstance uniform - which an indirect draw feeds from the
// command on the CPU only when the mg_IndirectParams view is absent.
Bool ReadsBaseInstance() const { return m_baseInstanceUniformLocation >= 0; }
#endif
// Which viewport indices the next draw's fragments may keep, one bit each. Written
// once per replay pass; see ForEachViewportRoutingPass.
void SetViewportPassMask(Uint32 indexMask) const;
@@ -3672,7 +3644,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_backendRenderbufferObjects;
} // namespace RenderbufferImpl
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (ct), CONTRACT-P5C.md §5.2: object_death's per-kind release, one entry point for all
// seven kinds for the same reason the notice switch is one - the answer is the same for
// all of them: every holder of the kind's twin table lets go of the twin at this handle.
@@ -3704,5 +3675,4 @@ namespace MobileGL::MG_Backend::DirectGLES {
// destructor calls into the driver (there is no current context, and the ids name nothing).
// A no-op once exit() has begun (the statics may already be gone). Monolith never calls it.
void DropEveryTwinForEndedServerSession();
#endif
} // namespace MobileGL::MG_Backend::DirectGLES
@@ -125,7 +125,6 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
const Uint8* HostBytes = nullptr; // HostBytes: null when there is no CPU copy
};
#if MOBILEGL_BUILD_RECORD_ARM
// A missing record on the handle arm is a NAMED refusal and never a quiet fall-back to
// the frontend (TASK-mv requirement 2; the shape is Managers.cpp's
// RefuseNullFrontendTextureOffTheHandleArm). The frontend element slot is not a second
@@ -175,11 +174,9 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
if (!candidate.Live || candidate.Gen != res.Gen) return false;
return candidate.Desc.HasDefinedContent != 0;
}
#endif
BoundIndexBufferView ResolveBoundIndexBuffer(IndexBufferQuestion question, const char* entry) {
BoundIndexBufferView view;
#if MOBILEGL_BUILD_RECORD_ARM
if (BufferImpl::VertexInputReadsRecords()) {
const auto& st = MG_Pipe::MGPipeApplier();
const MG_Pipe::MGPipeHandle res = BufferImpl::ResolveDrawIndexBufferFromRecord(st).Res;
@@ -219,7 +216,6 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
}
return view;
}
#endif
// MONOLITH GLUE from here down, token for token what each call site did before.
const auto& vao = MG_Pipe::gPipeInputs.GetBoundVertexArray();
if (!vao) return view;
@@ -293,7 +289,6 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
// an empty slot, and not the arm test (ID-110: the arm was decided by the transport
// above, never inferred from a null).
Uint BoundDrawIndirectBufferId() {
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord()) {
const MG_Pipe::MGPipeHandle res = MG_Pipe::MGPipeApplier().VerbIndirectBuffer;
if (MG_Pipe::MGPipeHandleIsNull(res)) return 0;
@@ -303,7 +298,6 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
}
return resource->id;
}
#endif
// MONOLITH GLUE from here down, token for token what this function did before.
const auto& indirect =
MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
-32
View File
@@ -37,16 +37,13 @@
#include <regex>
namespace MobileGL::MG_Backend::DirectGLES {
#if MOBILEGL_BUILD_RECORD_ARM
// read_pixels writes server reply scratch. The client refuses PACK_BUFFER before
// emission, so no frontend pack binding can affect this destination.
static const SharedPtr<MG_State::GLState::BufferObject>& SplitReadbackPackBuffer() {
static const SharedPtr<MG_State::GLState::BufferObject> none;
return none;
}
#endif
#if MOBILEGL_BUILD_RECORD_ARM
const FormatCapabilityCache* ActiveBackendFormatCaps() {
// Under a live split session the SERVER's private backend is what owns the context on the
// apply thread (and these reads all run there), so its cache is the authoritative one.
@@ -70,7 +67,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
#endif
return pActiveBackendObject ? &pActiveBackendObject->GetFormatCapabilities() : nullptr;
}
#endif
namespace {
Flags<PixelFormatNormalizeOptionBit> GetForcedPixelFormatNormalizeOptions() {
@@ -110,26 +106,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
SizeT targetIndex,
Bool caveat,
FormatCapability capability) {
#if MOBILEGL_BUILD_RECORD_ARM
const FormatCapabilityCache* activeCaps = ActiveBackendFormatCaps();
if (activeCaps == nullptr || targetIndex >= kFormatCapabilityTargetCount) {
return false;
}
#else
if (!pActiveBackendObject || targetIndex >= kFormatCapabilityTargetCount) {
return false;
}
#endif
const SizeT formatIndex = static_cast<SizeT>(internalFormat);
if (formatIndex >= kFormatCapabilityFormatCount) {
return false;
}
#if MOBILEGL_BUILD_RECORD_ARM
const FormatCapabilityCache& cache = *activeCaps;
#else
const FormatCapabilityCache& cache = pActiveBackendObject->GetFormatCapabilities();
#endif
const FormatCapabilityFlags caps =
caveat ? cache.CaveatCaps[targetIndex][formatIndex] : cache.FullCaps[targetIndex][formatIndex];
return HasFormatCapability(caps, capability);
@@ -173,11 +159,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
using namespace MobileGL::MG_Util::TextureFormatProcessor;
const GLenum requestedInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(internalFormat);
Flags<PixelFormatNormalizeOptionBit> options;
#if MOBILEGL_BUILD_RECORD_ARM
if (ActiveBackendFormatCaps() == nullptr || ShouldUseCaveatFormat(internalFormat, targetIndex)) {
#else
if (!pActiveBackendObject || ShouldUseCaveatFormat(internalFormat, targetIndex)) {
#endif
options = GetRuntimeFallbackNormalizeOptions(
requestedInternalFormat,
TextureImpl::GetRenderTargetNormalizeOptions(g_GLESCapabilities, targetIndex));
@@ -271,15 +253,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// not be resolved yet - a probe run then would latch "cannot tell" as "clean"
// forever. Once the backend exists, the first narrow-format image this process
// creates runs the probe on a live context.
#if MOBILEGL_BUILD_RECORD_ARM
if (ActiveBackendFormatCaps() == nullptr) {
return false;
}
#else
if (pActiveBackendObject == nullptr) {
return false;
}
#endif
return MG_Util::SelfTest::CopyImageMirrorsPacked16FieldOrder(g_GLESFuncs);
}
@@ -317,15 +293,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (!TargetRequiresRenderableFormat(targetIndex)) {
return false;
}
#if MOBILEGL_BUILD_RECORD_ARM
if (ActiveBackendFormatCaps() != nullptr && !ShouldUseCaveatFormat(internalFormat, targetIndex)) {
return false;
}
#else
if (pActiveBackendObject && !ShouldUseCaveatFormat(internalFormat, targetIndex)) {
return false;
}
#endif
const GLenum requestedInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(internalFormat);
const Flags<PixelFormatNormalizeOptionBit> options = GetRuntimeFallbackNormalizeOptions(
requestedInternalFormat, GetRenderTargetNormalizeOptions(g_GLESCapabilities, targetIndex));
@@ -2364,10 +2334,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
static Bool StoreClientRows(SizeT dstPixelBytes, SizeT swapGroupSize, GLsizei width, GLsizei sliceHeight,
GLsizei sliceCount, void* pixels, Bool applyPackImageParams, FillRow&& fillRow) {
const auto& pixelPackBufferObject =
#if MOBILEGL_BUILD_RECORD_ARM
MG_Config::DataArmIsRecord()
? SplitReadbackPackBuffer() :
#endif
MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
// Destination layout is computed from the client-side PACK parameters; only the actual pixel
-2
View File
@@ -13,7 +13,6 @@
#include <MG_Util/Texture/TextureFormatProcessor.h>
namespace MobileGL::MG_Backend::DirectGLES {
#if MOBILEGL_BUILD_RECORD_ARM
// C6 / ID-52 / CONTRACT-P5 table 3's pActiveBackendObject row. The format-capability cache of
// THIS ROLE's backend. Under an active transport the server's own private
// BackendObject_DirectGLES owns the context on the apply thread, so a backend-internal format
@@ -24,7 +23,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// (five in Utils.cpp, ClampSamplesToBackendSupport in BackendObject_DirectGLES.cpp) go through
// this instead of dereferencing pActiveBackendObject directly.
const FormatCapabilityCache* ActiveBackendFormatCaps();
#endif
namespace DebugImpl {
class ErrorLopper {
@@ -281,7 +281,6 @@
return true;
}
#if MOBILEGL_BUILD_RECORD_ARM
// P9 W2-a. The store's Defined-ness follows the client's respecifies; the driver's follows the
// twin's last sync. A re-mint asks this before reading a level back, because a level defined
// after that sync (null data, or null data plus a partial upload) is not on the driver yet and
@@ -460,4 +459,3 @@
bytes = Move(result);
return true;
}
#endif // MOBILEGL_BUILD_DISAGGREGATED
@@ -423,11 +423,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
Bool BackendObject_DirectVulkan::LatchIfGpuFaulted() {
#if MOBILEGL_BUILD_RECORD_ARM
return pVulkanRenderer != nullptr && pVulkanRenderer->LatchIfGpuHung("apply");
#else
return false;
#endif
}
void BackendObject_DirectVulkan::Initialize() {
@@ -38,16 +38,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
RendererSlot pVulkanRenderer;
namespace {
#if MOBILEGL_BUILD_RECORD_ARM
void RejectWireLegacyBuffer() {
if (MG_Config::DataArmIsRecord()) {
MGLOG_F("MGPipe: Fatal{RoleViolation, \"buffer-legacy-arm\"} (Magma P7 buffer consumer)");
std::abort();
}
}
#else
inline void RejectWireLegacyBuffer() {}
#endif
// Generation of the live VulkanRenderer instance, mirroring
// DirectGLES's g_syncContextGeneration. BackendObject_DirectVulkan
// bumps it (BumpRendererGeneration) wherever pVulkanRenderer is reset
@@ -138,53 +134,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
namespace {
#if !MOBILEGL_BUILD_RECORD_ARM
// P7 wave 2 package C, OQ-8: THE PULL BUILD'S HALF, VERBATIM. A disaggregated build
// answers GetShaderStorageBlock{Index,Binding} out of LinkArtifacts::storageBlocks
// instead (see those functions below), which retires this whole cache - the map, the
// SPIRV-Reflect rebuild, the teardown clear and the rehash hazard. It cannot be
// retired HERE because the archive member it reads is itself disaggregated-only, for
// G1: one more Vector in LinkArtifacts moves the pull build's .text, which is pinned
// byte-for-byte at 0xa52203.
//
// NOTHING OUTSIDE THIS FILE READS ANY OF IT (measured: BufferVariableResource,
// bufferVariables, activeVariables and StorageBlockResource have no reference anywhere
// else in the tree, and `dataSize` is written and never read - GL_BUFFER_VARIABLE and
// GL_ACTIVE_VARIABLES are answered by MG_Impl/GLImpl/Program/ProgramInterface.cpp out
// of glslang's own reflection, which is a different index space). So the two arms
// below differ in exactly the two functions their callers use, and in nothing else.
struct BufferVariableResource {
String name;
GLuint blockIndex = 0;
GLint offset = 0;
GLint size = 0;
};
struct StorageBlockResource {
String name;
GLuint binding = 0;
GLint dataSize = 0;
Vector<GLuint> activeVariables;
};
struct ProgramResourceCache {
// Lifetime id of the program the cached reflection belongs to. GL names are
// recycled (IndexGenerator hands freed indices straight back), and a
// recreated program's backendStateVersion restarts at the same small values,
// so the version alone can collide; the never-reused lifetime id makes the
// slot's ownership unambiguous.
Uint64 programLifetimeId = 0;
Uint32 backendStateVersion = 0;
// glShaderStorageBlockBinding deliberately does NOT bump the backend state
// version, and the pipeline composite is unnamed so the in-place patch in
// DirectVulkan::ShaderStorageBlockBinding can never reach its slot - the
// mirror replay bumps only the program's block-binding version. Without this
// key the composite's slot kept serving the pre-rebind block.binding.
Uint32 blockBindingVersion = 0;
Vector<StorageBlockResource> storageBlocks;
Vector<BufferVariableResource> bufferVariables;
};
#endif // !MOBILEGL_BUILD_RECORD_ARM
struct DrawElementsIndirectCommand {
Uint32 count = 0;
@@ -201,14 +150,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint32 baseInstance = 0;
};
#if !MOBILEGL_BUILD_RECORD_ARM
// Keyed by GL program name so the freed-name reuse in IndexGenerator bounds the
// map at the peak-simultaneous-program high-water mark; each slot's ownership is
// checked against the program's lifetime id before it is served (see
// GetProgramResourceCache). Cleared wholesale at EGL teardown via
// ClearProgramResourceCaches.
UnorderedMap<GLuint, ProgramResourceCache> g_programResourceCaches;
#endif
void ClearReadPixelsOutput(GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) {
if (!pixels || width <= 0 || height <= 0) {
@@ -223,160 +164,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
}
#if !MOBILEGL_BUILD_RECORD_ARM
String NormalizeDescriptorName(const SpvReflectDescriptorBinding& binding) {
const char* rawName = binding.name;
if (binding.type_description != nullptr && binding.type_description->type_name != nullptr) {
rawName = binding.type_description->type_name;
}
if (rawName == nullptr) {
return {};
}
String name = rawName;
const auto arraySuffix = name.find("[0]");
if (arraySuffix != String::npos) {
name = name.substr(0, arraySuffix);
}
return name;
}
void AddBufferVariablesRecursive(const SpvReflectBlockVariable& variable, const String& prefix,
GLuint blockIndex, Vector<BufferVariableResource>& variables,
Vector<GLuint>& activeVariables) {
for (Uint32 memberIndex = 0; memberIndex < variable.member_count; ++memberIndex) {
const auto& member = variable.members[memberIndex];
String name = prefix;
if (!name.empty()) {
name += ".";
}
name += member.name ? member.name : "";
if (member.member_count > 0) {
AddBufferVariablesRecursive(member, name, blockIndex, variables, activeVariables);
continue;
}
BufferVariableResource resource{};
resource.name = name;
resource.blockIndex = blockIndex;
resource.offset = static_cast<GLint>(member.offset);
resource.size = static_cast<GLint>(member.size);
const GLuint variableIndex = static_cast<GLuint>(variables.size());
variables.push_back(resource);
activeVariables.push_back(variableIndex);
}
}
ProgramResourceCache& GetProgramResourceCache(const MG_State::GLState::ProgramObject& program) {
auto& cache = g_programResourceCaches[program.GetExternalIndex()];
const Uint64 programLifetimeId = program.GetLifetimeId();
const Uint32 backendStateVersion = program.GetBackendStateVersion();
const Uint32 blockBindingVersion = program.GetBlockBindingVersion();
// The lifetime id must match too: a new program that reuses a deleted
// program's name and happens to land on the same backendStateVersion (both
// count from zero) would otherwise be served the dead program's reflection.
if (cache.programLifetimeId == programLifetimeId &&
cache.backendStateVersion == backendStateVersion &&
(!cache.storageBlocks.empty() || !cache.bufferVariables.empty())) {
if (cache.blockBindingVersion != blockBindingVersion) {
// Only the block bindings moved (glShaderStorageBlockBinding, or the
// pipeline composite's mirror replay - neither touches the backend
// state version): the reflection itself is unchanged, so re-apply the
// overrides by name instead of re-running spirv-reflect. Overrides
// only ever accumulate, so a block without one still holds its
// declared binding.
for (auto& block : cache.storageBlocks) {
const Int rebound = program.GetShaderStorageBlockBindingOverride(block.name);
if (rebound >= 0) block.binding = static_cast<Uint32>(rebound);
}
cache.blockBindingVersion = blockBindingVersion;
}
return cache;
}
cache = {};
cache.programLifetimeId = programLifetimeId;
cache.backendStateVersion = backendStateVersion;
cache.blockBindingVersion = blockBindingVersion;
Vector<SpvReflectShaderModule> modules;
Vector<Bool> validModules;
const auto& spirvs = program.GetGeneratedSpirv();
for (const auto& spirv : spirvs) {
if (spirv.empty()) {
continue;
}
SpvReflectShaderModule module{};
const SpvReflectResult result =
spvReflectCreateShaderModule(spirv.size() * sizeof(Uint), spirv.data(), &module);
if (result != SPV_REFLECT_RESULT_SUCCESS) {
continue;
}
modules.push_back(module);
validModules.push_back(true);
}
for (auto& module : modules) {
uint32_t bindingCount = 0;
SpvReflectResult result = spvReflectEnumerateDescriptorBindings(&module, &bindingCount, nullptr);
if (result != SPV_REFLECT_RESULT_SUCCESS || bindingCount == 0) {
continue;
}
Vector<SpvReflectDescriptorBinding*> bindings(bindingCount);
result = spvReflectEnumerateDescriptorBindings(&module, &bindingCount, bindings.data());
if (result != SPV_REFLECT_RESULT_SUCCESS) {
continue;
}
std::sort(bindings.begin(), bindings.end(), [](const auto* lhs, const auto* rhs) {
const String lhsName = lhs ? NormalizeDescriptorName(*lhs) : String();
const String rhsName = rhs ? NormalizeDescriptorName(*rhs) : String();
if (lhsName != rhsName) return lhsName < rhsName;
return lhs->binding < rhs->binding;
});
for (const auto* binding : bindings) {
if (binding == nullptr ||
binding->descriptor_type != SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER) {
continue;
}
const String blockName = NormalizeDescriptorName(*binding);
if (blockName.empty()) {
continue;
}
const auto existing = std::find_if(
cache.storageBlocks.begin(), cache.storageBlocks.end(),
[&](const StorageBlockResource& block) { return block.name == blockName; });
if (existing != cache.storageBlocks.end()) {
continue;
}
StorageBlockResource block{};
block.name = blockName;
block.binding = binding->binding;
// glShaderStorageBlockBinding survives every rebuild of this cache: the
// authoritative record of a rebound block lives on the program (it is what
// GL_BUFFER_BINDING reports), and only the shader's declared binding is
// recoverable from the SPIR-V. Without this, any unrelated state-version
// bump would silently revert the block to its declared binding.
const Int rebound = program.GetShaderStorageBlockBindingOverride(blockName);
if (rebound >= 0) block.binding = static_cast<Uint32>(rebound);
block.dataSize = static_cast<GLint>(binding->block.size);
const GLuint blockIndex = static_cast<GLuint>(cache.storageBlocks.size());
AddBufferVariablesRecursive(binding->block, blockName, blockIndex, cache.bufferVariables,
block.activeVariables);
cache.storageBlocks.push_back(block);
}
}
for (SizeT i = 0; i < modules.size(); ++i) {
if (validModules[i]) {
spvReflectDestroyShaderModule(&modules[i]);
}
}
return cache;
}
#endif // !MOBILEGL_BUILD_RECORD_ARM
#if MOBILEGL_BUILD_RECORD_ARM
// The verb's handles identify server stores; never inspect a client binding.
//
// P8-D: THE GPU READS THE WORDS (VulkanRenderer::DrawWireIndirectNative, WireDraw.inc), as
@@ -456,7 +244,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
pVulkanRenderer->MultiDrawArrays(payload);
}
}
#endif
MG_State::GLState::ProgramObject* TryGetDirectVulkanProgram(GLuint program) {
if (!MG_Pipe::gPipeInputs.ValidateProgramName(program)) {
@@ -495,7 +282,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} // namespace
#if MOBILEGL_BUILD_RECORD_ARM
// P7 wave 2 package C, OQ-8 (CONTRACT-P7 §5.3): THE MONOLITH CONSUMER READS THE ARCHIVE
// TOO, so one published list serves both of this backend's arms.
//
@@ -555,43 +341,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (rebound >= 0) return static_cast<GLuint>(rebound);
return blocks[blockIndex].binding;
}
#else
void ClearProgramResourceCaches() {
// Called from EGL teardown while the backend's m_eglStateMutex is held; GL
// calls are serialized in this codebase (contexts migrate threads but never
// run concurrently), so no other thread can be inside the unsynchronized map.
// Live programs in another context self-heal: their entry rebuilds from the
// retained generated SPIR-V on the next resource query.
g_programResourceCaches.clear();
}
GLuint GetShaderStorageBlockIndex(const MG_State::GLState::ProgramObject& program, const String& name) {
auto& cache = GetProgramResourceCache(program);
auto find = [&cache](const String& key) {
return std::find_if(cache.storageBlocks.begin(), cache.storageBlocks.end(),
[&](const StorageBlockResource& block) { return block.name == key; });
};
auto it = find(name);
if (it == cache.storageBlocks.end()) {
// Cache names are normalized (NormalizeDescriptorName drops the array suffix), so
// an arrayed block that GL enumerates per element - "B[0]", "B[1]" - is one entry
// here, spelled "B". Retry against the bare name before giving up.
const auto bracket = name.rfind('[');
if (bracket == String::npos || name.empty() || name.back() != ']') return GL_INVALID_INDEX;
it = find(name.substr(0, bracket));
if (it == cache.storageBlocks.end()) return GL_INVALID_INDEX;
}
return static_cast<GLuint>(std::distance(cache.storageBlocks.begin(), it));
}
GLuint GetShaderStorageBlockBinding(const MG_State::GLState::ProgramObject& program, GLuint blockIndex) {
auto& cache = GetProgramResourceCache(program);
if (blockIndex >= cache.storageBlocks.size()) {
return 0;
}
return cache.storageBlocks[blockIndex].binding;
}
#endif
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::ClearBufferfi called with null VulkanRenderer");
@@ -646,23 +395,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) {
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord()) {
DrawWireIndirect(mode, type, indirect, drawcount, stride, true);
return;
}
#endif
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawElementsIndirect called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::MultiDrawElementsIndirect called with null GL context");
pVulkanRenderer->MultiDrawElementsIndirect(mode, type, indirect, drawcount, stride);
}
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) {
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord()) {
DrawWireIndirect(mode, 0, indirect, drawcount, stride, false);
return;
}
#endif
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawArraysIndirect called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::MultiDrawArraysIndirect called with null GL context");
@@ -714,24 +459,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount,
GLsizei maxdrawcount, GLsizei stride) {
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord()) {
DrawWireIndirect(mode, type, indirect, maxdrawcount, stride, true, true, drawcount);
return;
}
#endif
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawElementsIndirectCount called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::MultiDrawElementsIndirectCount called with null GL context");
pVulkanRenderer->MultiDrawElementsIndirectCount(mode, type, indirect, drawcount, maxdrawcount, stride);
}
void MultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount,
GLsizei maxdrawcount, GLsizei stride) {
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord()) {
DrawWireIndirect(mode, 0, indirect, maxdrawcount, stride, false, true, drawcount);
return;
}
#endif
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawArraysIndirectCount called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::MultiDrawArraysIndirectCount called with null GL context");
@@ -804,12 +545,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
DrawElementsInstancedBaseVertexBaseInstance(mode, count, type, indices, instancecount, 0, 0);
}
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect) {
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord()) {
DrawWireIndirect(mode, type, indirect, 1, 0, true);
return;
}
#endif
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawElementsIndirect called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::DrawElementsIndirect called with null GL context");
@@ -869,12 +608,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
DrawArraysInstancedBaseInstance(mode, first, count, instancecount, 0);
}
void DrawArraysIndirect(GLenum mode, const void* indirect) {
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord()) {
DrawWireIndirect(mode, 0, indirect, 1, 0, false);
return;
}
#endif
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawArraysIndirect called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::DrawArraysIndirect called with null GL context");
@@ -943,7 +680,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
void DispatchComputeIndirect(GLintptr indirect) {
#if MOBILEGL_BUILD_RECORD_ARM
// P8-SV: THE GPU READS THE GROUP COUNTS on the wire arm too - VulkanRenderer's
// DispatchComputeIndirect takes its transport branch into DispatchWireComputeIndirect
// (WireDraw.inc), which issues vkCmdDispatchIndirect from the verb's store. The CPU read
@@ -952,7 +688,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
pVulkanRenderer->DispatchComputeIndirect(indirect);
return;
}
#endif
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DispatchComputeIndirect called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::DispatchComputeIndirect called with null GL context");
pVulkanRenderer->DispatchComputeIndirect(indirect);
@@ -1014,7 +749,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
void ShaderStorageBlockBinding(GLuint program, const GLchar* storageBlockName, GLuint storageBlockBinding) {
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord()) {
auto& state = MG_Pipe::MGPipeApplier();
const auto handle = state.VerbStorageBlockProgram;
@@ -1032,7 +766,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
++record.BindingsSerial;
return;
}
#endif
auto* programObject = TryGetDirectVulkanProgram(program);
if (!programObject || storageBlockName == nullptr) return;
const Int maxBindings =
@@ -1058,7 +791,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MakeUnique<GenericErrorInfo>("DirectVulkan", __func__, "Shader storage binding is out of range."));
return;
}
#if MOBILEGL_BUILD_RECORD_ARM
// P7 wave 2 package C, OQ-8: NOTHING LEFT TO PATCH, and the hazard goes with it.
//
// The frontend has already recorded the new binding on the program, and that record is
@@ -1078,26 +810,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// cache is the retirement. The pull build below keeps both.
(void)storageBlockBinding;
}
#else
// The frontend already validated that the name denotes an active block, and has
// already recorded the new binding on the program - which is what reseeds this cache
// whenever it is rebuilt. Writing the entry here as well keeps an ALREADY-BUILT cache
// (the common case: the very next draw reads it) from having to be thrown away.
//
// Resolve the index BEFORE taking the reference, and bounds-check the way the
// sibling getter does. GetShaderStorageBlockIndex re-enters GetProgramResourceCache,
// which indexes g_programResourceCaches and can therefore insert - and that map is
// open-addressed, so a rehash MOVES its entries and a reference taken before the
// call is left dangling. Binding a program's storage block
// while another program's entry was still absent from the cache was a reproducible
// segfault (ProgramPipelineScenario's two storage-block cases, in one process).
const GLuint blockIndex = GetShaderStorageBlockIndex(*programObject, storageBlockName);
if (blockIndex == GL_INVALID_INDEX) return;
auto& cache = GetProgramResourceCache(*programObject);
if (blockIndex >= cache.storageBlocks.size()) return;
cache.storageBlocks[blockIndex].binding = storageBlockBinding;
}
#endif
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::ReadPixels called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::ReadPixels called with null GL context");
@@ -1140,7 +852,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// memory) into uint32 values with the loop-closing first index appended.
static Bool BuildClosedLineLoopIndices(GLsizei count, GLenum type, const void* indices,
Vector<Uint32>& outIndices) {
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord()) {
const SizeT width = MG_Util::GetGLTypeSize(type);
if ((width != 1 && width != 2 && width != 4) || count < 2) return false;
@@ -1163,7 +874,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
outIndices[count] = outIndices[0];
return true;
}
#endif
const SizeT indexSize = MG_Util::GetGLTypeSize(type);
if (indexSize == 0 || count < 2) {
return false;
@@ -1318,15 +1028,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// MultiDrawIndexedCmd left the client-memory shape addressing a view whose byte
// offset is a hardcoded 0, so UploadAndBindIndexBuffer saw a null client pointer,
// declined the whole batch and painted nothing.)
#if MOBILEGL_BUILD_RECORD_ARM
const Bool noIndexBuffer = MG_Config::DataArmIsRecord()
? MG_Pipe::MGPipeHandleIsNull(MG_Pipe::MGPipeApplier().IndexBuffer.Res)
: MG_Pipe::gPipeInputs.GetBoundVertexArray()->GetIndexBufferBindingSlot().GetBoundObject() == nullptr;
if (noIndexBuffer) {
#else
const auto& vao = *MG_Pipe::gPipeInputs.GetBoundVertexArray();
if (vao.GetIndexBufferBindingSlot().GetBoundObject() == nullptr) {
#endif
for (GLsizei i = 0; i < drawcount; ++i) {
if (count[i] <= 0) {
continue;
@@ -1512,7 +1217,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
// Pure status read (glGetSynciv must not flush).
if (pVulkanRenderer->IsSubmitIndexComplete(sync->submitIndex)) return true;
#if MOBILEGL_BUILD_RECORD_ARM
// P13 W4: A STATUS POLL THAT CAN NEVER BE ANSWERED. On the record arm a frame's draws stay
// in one command buffer (and one render pass) until something submits it, so a client that
// only polls - glGetSynciv in a loop, no flush bit, no swap - waits for work nothing will
@@ -1526,7 +1230,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
pVulkanRenderer->FlushForSyncPoint(sync->submitIndex);
return pVulkanRenderer->IsSubmitIndexComplete(sync->submitIndex);
}
#endif
return false;
}
@@ -45,7 +45,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint64 GetRendererGeneration();
void BumpRendererGeneration();
#if MOBILEGL_BUILD_RECORD_ARM
// P7 wave 2 package C (CONTRACT-P7 §5.5). Magma's StateObjectDeathOps table, installed
// from BackendObject_DirectVulkan::Initialize() so that GetStateObjectDeathOps() is
// non-null on every Magma role that owns a backend. See the definition for what the one
@@ -55,7 +54,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// True when the notice currently installed is this backend's. Read by MG_Test so the
// install can be asserted without a death having to happen first.
Bool StateObjectDeathOpsInstalled();
#endif
// Drops every cached program-resource reflection entry (CPU-side strings/vectors
// only, no Vulkan handles). Called at EGL teardown next to the renderer reset;
@@ -7,9 +7,7 @@
// End of Source File Header
#include "BufferArena.h"
#if MOBILEGL_BUILD_RECORD_ARM
#include <Config.h>
#endif
namespace MobileGL::MG_Backend::DirectVulkan {
Bool BufferArena::Initialize(const BufferArenaDesc& desc) {
@@ -128,14 +126,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
bufferDesc.usage = m_desc.usage;
bufferDesc.memoryUsage = m_desc.memoryUsage;
bufferDesc.allocationFlags = m_desc.allocationFlags;
#if MOBILEGL_BUILD_RECORD_ARM
// Split staging may also be the destination of a GPU UBO range copy.
// Persistent host writes must be coherent: Upload deliberately writes
// the mapped pointer without a per-range VMA flush.
if (MG_Config::DataArmIsRecord() && m_desc.persistentlyMapped) {
bufferDesc.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
}
#endif
if (!buffer.Create(bufferDesc)) {
return false;
}
@@ -7,9 +7,7 @@
// End of Source File Header
#include "FrameContext.h"
#if MOBILEGL_BUILD_RECORD_ARM
#include <Config.h>
#endif
namespace MobileGL::MG_Backend::DirectVulkan {
VkResult FrameContext::Initialize(VkDevice device, VkCommandPool commandPool, Uint32 frameCount) {
@@ -224,13 +222,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkImageMemoryBarrier presentBarrier{};
presentBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
presentBarrier.srcAccessMask = 0;
#if MOBILEGL_BUILD_RECORD_ARM
// A layout transition writes the image too. Order it after the wire
// render pass's storeOp, clears and copies instead of relying on the old
// per-draw queue-idle path. This applies even when contents are discarded.
if (MG_Config::DataArmIsRecord())
presentBarrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
#endif
presentBarrier.dstAccessMask = 0;
presentBarrier.oldLayout = oldLayout;
presentBarrier.newLayout = presentLayout;
@@ -243,12 +239,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
presentBarrier.subresourceRange.baseArrayLayer = 0;
presentBarrier.subresourceRange.layerCount = 1;
vkCmdPipelineBarrier(commandBuffer,
#if MOBILEGL_BUILD_RECORD_ARM
MG_Config::DataArmIsRecord()
? VK_PIPELINE_STAGE_ALL_COMMANDS_BIT : VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
#else
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
#endif
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0,
nullptr, 0, nullptr, 1, &presentBarrier);
@@ -3,7 +3,6 @@
#include <MG_State/GLState/ProgramState/ProgramObject.h>
#if MOBILEGL_BUILD_RECORD_ARM
#include <MG_Pipe/PipeApply.h>
#include <MG_State/GLState/ProgramState/ProgramArtifactsCodec.h>
#include "../DirectVulkanResourceState.h"
@@ -12,10 +11,8 @@
// the archive (LinkArtifacts::storageBlocks), so the SPIR-V reflector is off the Magma wire
// draw path entirely. A future edit that re-adds the include is re-adding a per-draw reflect,
// because a MagmaProgramSource is constructed once per draw and can memoise nothing.
#endif
namespace MobileGL::MG_Backend::DirectVulkan {
#if MOBILEGL_BUILD_RECORD_ARM
// A borrowed view, valid only while its frontend object or server record lives.
// It never constructs a ProgramObject on the server. Post-link mutable values
// come from the record tails, not from the archive's link-time defaults.
@@ -218,7 +215,4 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// also why they never helped: a view lives for one draw, so the latch was cold every
// time it was read.
};
#else
using MagmaProgramSource = MG_State::GLState::ProgramObject;
#endif
}
@@ -198,14 +198,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.programHash, sizeof(payload.programHash)));
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.vertexInputHash, sizeof(payload.vertexInputHash)));
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.pipelineLayout, sizeof(payload.pipelineLayout)));
#if MOBILEGL_BUILD_RECORD_ARM
if (payload.wireRenderPassCompatibilityId != 0) {
constexpr Uint64 wireDomain = 0x5749524552504b59ull;
XXHASH_VERIFY(XXH64_update(m_hashState, &wireDomain, sizeof(wireDomain)));
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.wireRenderPassCompatibilityId,
sizeof(payload.wireRenderPassCompatibilityId)));
} else
#endif
{
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.renderPass, sizeof(payload.renderPass)));
}
@@ -286,12 +284,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return VK_NULL_HANDLE;
}
m_cache.emplace(hash, PipelineCacheEntry{pipeline, payload.programHash,
#if MOBILEGL_BUILD_RECORD_ARM
// Wire pipelines outlive their creator pass and
// may serve another compatible pass in flight.
// Never evict them by a recycled native handle.
payload.wireRenderPassCompatibilityId != 0 ? VK_NULL_HANDLE :
#endif
payload.renderPass,
m_frameCounter});
return pipeline;
@@ -35,11 +35,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
HashType vertexInputHash = 0;
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
VkRenderPass renderPass = VK_NULL_HANDLE;
#if MOBILEGL_BUILD_RECORD_ARM
// Nonzero is an exact, renderer-lifetime compatibility identity.
// renderPass remains the valid creation handle, not the cache key.
Uint64 wireRenderPassCompatibilityId = 0;
#endif
Uint32 colorAttachmentCount = 1;
VkSampleCountFlagBits rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
// glEnable(GL_SAMPLE_SHADING) + glMinSampleShading, which Vulkan bakes into the
@@ -3199,11 +3199,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (descriptorKind == DescriptorBindingKind::StorageBuffer) {
const GLuint blockIndex =
#if MOBILEGL_BUILD_RECORD_ARM
program.GetShaderStorageBlockIndex(uniformName);
#else
GetShaderStorageBlockIndex(program, uniformName);
#endif
if (blockIndex == GL_INVALID_INDEX) {
MGLOG_D("ProgramFactory::ReflectLayout: skipping inactive SSBO '%s' at binding %u",
uniformName.c_str(), binding);
@@ -3495,13 +3491,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
updateAfterBindSampledImages + updateAfterBindStorageImages;
const auto& uab = m_updateAfterBindLimits;
entry.usesUpdateAfterBind =
#if MOBILEGL_BUILD_RECORD_ARM
// Vulkan forbids ANY update-after-bind binding in a set layout that
// contains a dynamic buffer descriptor (VUID 03001/03011). Wire draws
// retain UniformManager's per-frame, content-versioned descriptor sets;
// this selects the legal ordinary pool, not in-place mutation of a live set.
(!program.IsWire() || entry.dynamicBindings.empty()) &&
#endif
uab.enabled && updateAfterBindSamplers <= uab.maxPerStageSamplers &&
updateAfterBindUniformBuffers <= uab.maxPerStageUniformBuffers &&
updateAfterBindStorageBuffers <= uab.maxPerStageStorageBuffers &&
@@ -28,13 +28,11 @@
#include "MG_Util/Metrics/TextureMetrics.h"
#include "MG_Util/ShaderTranspiler/Types.h"
#include <Config.h>
#if MOBILEGL_BUILD_RECORD_ARM
#include <MG_Pipe/PipeApply.h>
// P7 wave 0: the seam WireDescriptorFatal dies through. See MG_Pipe/PipeSessionFail.h.
#include <MG_Pipe/PipeSessionFail.h>
// P7 wave 2 package B3: rule I's tally for this file's silent wire-draw exits.
#include "WireDeclineTally.h"
#endif
#include <vulkan/utility/vk_format_utils.h>
#include <algorithm>
#include <cstdio>
@@ -43,7 +41,6 @@
#include <limits>
namespace MobileGL::MG_Backend::DirectVulkan {
#if MOBILEGL_BUILD_RECORD_ARM
SizeT UniformManager::WireImageViewKeyHash::operator()(const WireImageViewKey& key) const {
SizeT hash = std::hash<VkImage>{}(key.image);
const auto mix = [&hash](Uint64 value) {
@@ -323,7 +320,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
return true;
}
#endif
namespace {
constexpr Uint kFallbackTexture2DExternalIndex = 0xFFFFFF00u;
// One id for every storage-image placeholder. They are never reachable through GL - no
@@ -590,7 +586,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_bufferManager = bufferManager;
m_programFactory = programFactory;
m_minDynamicOffsetAlignment = std::max<VkDeviceSize>(1, minUniformBufferOffsetAlignment);
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord()) {
VkPhysicalDeviceProperties properties{};
vkGetPhysicalDeviceProperties(m_physicalDevice, &properties);
@@ -600,7 +595,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_wireMaxStorageRange = properties.limits.maxStorageBufferRange;
m_wireMaxTexelElements = properties.limits.maxTexelBufferElements;
}
#endif
m_frameCount = frameCount;
m_maxBindings = maxBindings;
m_samplerResolveMemo.assign(m_maxBindings, SamplerResolveMemo{});
@@ -656,11 +650,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_unboundStorageImageTextures.clear();
for (auto& frame : m_frames) {
if (m_device != VK_NULL_HANDLE) {
#if MOBILEGL_BUILD_RECORD_ARM
frame.wireImageViewCache.clear();
for (const auto view : frame.wireImageViews) vkDestroyImageView(m_device, view, nullptr);
frame.wireImageViews.clear();
#endif
for (auto& view : frame.texelBufferViews) {
if (view != VK_NULL_HANDLE) {
vkDestroyBufferView(m_device, view, nullptr);
@@ -683,12 +675,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
frame.quietEpochs = 0;
}
m_frames.clear();
#if MOBILEGL_BUILD_RECORD_ARM
for (auto& entry : m_wirePlaceholderImages) DestroyWirePlaceholderImage(entry.second);
m_wirePlaceholderImages.clear();
m_wirePrivateStoragePlaceholders = 0;
m_wireInvalidStorageImagesBindNull = false;
#endif
m_epochSlot = 0;
m_epochOpen = false;
@@ -712,12 +702,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void UniformManager::BeginFrame(Uint32 frameIndex) {
MOBILEGL_ASSERT(frameIndex < m_frames.size(), "UniformDescriptorBinder::BeginFrame invalid frame index");
auto& frame = m_frames[frameIndex];
#if MOBILEGL_BUILD_RECORD_ARM
m_wireFrameIndex = frameIndex;
frame.wireImageViewCache.clear();
for (const auto view : frame.wireImageViews) vkDestroyImageView(m_device, view, nullptr);
frame.wireImageViews.clear();
#endif
for (auto& view : frame.texelBufferViews) {
if (view != VK_NULL_HANDLE) {
vkDestroyBufferView(m_device, view, nullptr);
@@ -766,7 +754,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
}
#if MOBILEGL_BUILD_RECORD_ARM
Bool UniformManager::WireDescriptorSetBudgetReached(Uint32 frameIndex) const {
MOBILEGL_ASSERT(frameIndex < m_frames.size(), "WireDescriptorSetBudgetReached invalid frame index");
return m_frames[frameIndex].allocatedSetsThisFrame >= kWireDescriptorSetBudget;
@@ -799,7 +786,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
return cachedSets;
}
#endif
void UniformManager::InvalidateDescriptorSetMemos() {
// Every VkDescriptorSet handle held outside the per-layout caches - in this backend they all
@@ -907,9 +893,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint32 binding, Uint32 element,
VkDescriptorImageInfo& outImageInfo,
Bool trustUnchangedHint) const {
#if MOBILEGL_BUILD_RECORD_ARM
if (program.IsWire()) return ResolveWireImageDescriptor(commandBuffer, program, programObj, binding, element, false, outImageInfo);
#endif
MOBILEGL_ASSERT(m_textureManager != nullptr, "ResolveSamplerDescriptor: texture manager is null");
MOBILEGL_ASSERT(m_samplerManager != nullptr, "ResolveSamplerDescriptor: sampler manager is null");
// The whole-descriptor memo below is keyed by binding alone, so it describes a binding
@@ -1192,9 +1176,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool UniformManager::ProgramSamplesOnlySingleLevelTextures(
const MagmaProgramSource& program, const ProgramFactory::VkProgramObject& programObj) {
#if MOBILEGL_BUILD_RECORD_ARM
if (program.IsWire()) return false; // Conservative optimization gate; no frontend probe.
#endif
// A declined program never draws (see VkProgramObject::declinedDescriptors), and its
// declined binding has no resolvable uniform location - so there is nothing to prove
// about the textures it would have sampled.
@@ -1305,7 +1287,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return texture;
}
#if MOBILEGL_BUILD_RECORD_ARM
Bool UniformManager::ResolveWireTexelBufferDescriptor(const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Uint32 frameIndex,
Bool storage, VkBufferView& out) {
@@ -1407,16 +1388,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_bufferManager->MarkWireBufferGpuWritten(buffer, start, size);
return true;
}
#endif
Bool UniformManager::ResolveTexelBufferDescriptor(const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj,
Uint32 binding, Uint32 frameIndex,
VkBufferView& outBufferView) {
#if MOBILEGL_BUILD_RECORD_ARM
if (program.IsWire())
return ResolveWireTexelBufferDescriptor(program, programObj, binding, frameIndex, false, outBufferView);
#endif
outBufferView = VK_NULL_HANDLE;
MOBILEGL_ASSERT(m_bufferManager != nullptr, "ResolveTexelBufferDescriptor: buffer manager is null");
MOBILEGL_ASSERT(frameIndex < m_frames.size(), "ResolveTexelBufferDescriptor: frame index out of range");
@@ -1541,10 +1519,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const ProgramFactory::VkProgramObject& programObj,
Uint32 binding, Uint32 frameIndex,
VkBufferView& outBufferView) {
#if MOBILEGL_BUILD_RECORD_ARM
if (program.IsWire())
return ResolveWireTexelBufferDescriptor(program, programObj, binding, frameIndex, true, outBufferView);
#endif
outBufferView = VK_NULL_HANDLE;
MOBILEGL_ASSERT(m_bufferManager != nullptr, "ResolveStorageTexelBufferDescriptor: buffer manager is null");
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "ResolveStorageTexelBufferDescriptor: GL context is null");
@@ -1712,18 +1688,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "ResolveStorageBufferDescriptor: GL context is null");
MOBILEGL_ASSERT(binding < programObj.storageBlockIndexByBinding.size(),
"ResolveStorageBufferDescriptor: binding %u out of range", binding);
#if MOBILEGL_BUILD_RECORD_ARM
if (program.IsWire() && (binding >= programObj.storageBlockIndexByBinding.size() ||
binding >= programObj.storageBlockNameByBinding.size()))
WireDescriptorFatal("storage-buffer-reflection-binding");
#endif
const Int blockIndex = programObj.storageBlockIndexByBinding[binding];
MOBILEGL_ASSERT(blockIndex >= 0, "ResolveStorageBufferDescriptor: no SSBO block mapped to binding %u",
binding);
#if MOBILEGL_BUILD_RECORD_ARM
if (program.IsWire() && blockIndex < 0) WireDescriptorFatal("storage-buffer-reflection-block");
#endif
// An atomic counter is not an SSBO the application ever declared: glslang lowers every
// atomic_uint onto a synthesized gl_AtomicCounterBlock_<N> storage block, where N is the
// GL ATOMIC-COUNTER binding. That block arrives here auto-mapped to an arbitrary
@@ -1753,13 +1725,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
isAtomicCounterBlock
? static_cast<GLuint>(atomicCounterBinding)
:
#if MOBILEGL_BUILD_RECORD_ARM
(program.IsWire() ? program.GetShaderStorageBlockBinding(static_cast<GLuint>(blockIndex)) :
GetShaderStorageBlockBinding(*program.Frontend(), static_cast<GLuint>(blockIndex))) + element;
#else
GetShaderStorageBlockBinding(program, static_cast<GLuint>(blockIndex)) + element;
#endif
#if MOBILEGL_BUILD_RECORD_ARM
if (program.IsWire()) {
const auto& state = MG_Pipe::MGPipeApplier();
const Uint32 cls = isAtomicCounterBlock ? MG_Pipe::kMGPipeShaderBufferClassAtomicCounter
@@ -1835,7 +1802,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_bufferManager->MarkWireBufferGpuWritten(range.Res, start, size);
return true;
}
#endif
const Uint32 bindingPointCount =
static_cast<Uint32>(MG_Pipe::gPipeInputs.GetBufferBindingPointCount(bufferTarget));
MOBILEGL_ASSERT(frontendBinding < bindingPointCount,
@@ -1914,9 +1880,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const ProgramFactory::VkProgramObject& programObj,
Uint32 binding, Uint32 element,
VkDescriptorImageInfo& outImageInfo) const {
#if MOBILEGL_BUILD_RECORD_ARM
if (program.IsWire()) return ResolveWireImageDescriptor(commandBuffer, program, programObj, binding, element, true, outImageInfo);
#endif
outImageInfo = {};
MOBILEGL_ASSERT(m_textureManager != nullptr, "ResolveStorageImageDescriptor: texture manager is null");
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "ResolveStorageImageDescriptor: GL context is null");
@@ -2369,9 +2333,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const ProgramFactory::VkProgramObject& programObj,
Vector<MG_State::GLState::ITextureObject*>& outTextures,
Vector<SampledBindingRecord>* outBindingRecords) {
#if MOBILEGL_BUILD_RECORD_ARM
if (program.IsWire()) { outTextures.clear(); if (outBindingRecords) outBindingRecords->clear(); return true; }
#endif
outTextures.clear();
if (outBindingRecords != nullptr) {
outBindingRecords->clear();
@@ -2418,9 +2380,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool UniformManager::SampledBindingsUnchanged(const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj,
const Vector<SampledBindingRecord>& previousRecords) const {
#if MOBILEGL_BUILD_RECORD_ARM
if (program.IsWire()) return false;
#endif
// A declined program takes the full path every time and is refused there.
if (programObj.declinedDescriptors) {
return false;
@@ -2464,9 +2424,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj,
Vector<MG_State::GLState::ITextureObject*>& outTextures) const {
#if MOBILEGL_BUILD_RECORD_ARM
if (program.IsWire()) { outTextures.clear(); return true; }
#endif
outTextures.clear();
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(),
"CollectStorageImageTextures: GL context is null");
@@ -2549,9 +2507,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj,
Vector<SamplerImageFeedbackBinding>& outBindings) const {
#if MOBILEGL_BUILD_RECORD_ARM
if (program.IsWire()) { outBindings.clear(); return true; }
#endif
outBindings.clear();
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(),
"CollectSamplerImageFeedback: GL context is null");
@@ -2627,7 +2583,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true;
}
#if MOBILEGL_BUILD_RECORD_ARM
Bool UniformManager::ResolveWireUniformBufferPayload(const MagmaProgramSource& program,
Uint32 blockIndex, Uint32 bindingPoint, UboBindResult& out) const {
if (bindingPoint >= MG_Pipe::kMGPipeMaxBufferBindingPoints)
@@ -2722,7 +2677,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
out.dynamicOffset = padded.offset;
return true;
}
#endif
Bool UniformManager::ResolveUniformBufferPayload(const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
@@ -2752,10 +2706,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MOBILEGL_ASSERT(binding < programObj.uniformBlockIndexByBinding.size(),
"ResolveUniformBufferPayload: UBO mapping binding %u out of range", binding);
#if MOBILEGL_BUILD_RECORD_ARM
if (program.IsWire() && binding >= programObj.uniformBlockIndexByBinding.size())
WireDescriptorFatal("uniform-buffer-reflection-binding");
#endif
Int blockIndex = programObj.uniformBlockIndexByBinding[binding];
if (arrayElement > 0) {
const auto arrayIt = programObj.arrayedUniformBlockIndicesByBinding.find(binding);
@@ -2776,16 +2728,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MOBILEGL_ASSERT(static_cast<Uint32>(blockIndex) < activeUniformBlockCount,
"ResolveUniformBufferPayload: uniform block index %d out of range (count=%u)", blockIndex,
activeUniformBlockCount);
#if MOBILEGL_BUILD_RECORD_ARM
if (program.IsWire() && (blockIndex < 0 || static_cast<Uint32>(blockIndex) >= activeUniformBlockCount))
WireDescriptorFatal("uniform-buffer-reflection-block");
#endif
const Uint32 frontendBinding = program.GetUniformBlockBinding(static_cast<Uint32>(blockIndex));
#if MOBILEGL_BUILD_RECORD_ARM
if (program.IsWire())
return ResolveWireUniformBufferPayload(program, static_cast<Uint32>(blockIndex), frontendBinding, out);
#endif
const Uint32 uniformBindingPointCount =
static_cast<Uint32>(MG_Pipe::gPipeInputs.GetBufferBindingPointCount(BufferTarget::Uniform));
MOBILEGL_ASSERT(frontendBinding < uniformBindingPointCount,
@@ -3059,9 +3007,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint32& outDynamicOffset) {
UboBindResult ubo{};
const Bool hasPayload = ResolveUniformBufferPayload(program, programObj, binding, arrayElement, ubo);
#if MOBILEGL_BUILD_RECORD_ARM
if (program.IsWire() && !hasPayload) return false;
#endif
MOBILEGL_ASSERT(hasPayload && (ubo.directBindable || (ubo.payload != nullptr && ubo.payloadSize > 0)),
"UniformDescriptorBinder::ResolveDynamicUboDescriptor failed: missing UBO payload on binding %u element %u",
binding, arrayElement);
@@ -3150,7 +3096,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
}
#if MOBILEGL_BUILD_RECORD_ARM
Bool UniformManager::PrepareWireTextureResources(const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj) {
if (!program.IsWire()) return true;
@@ -3204,7 +3149,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_textureManager->FlushPendingUploads();
return true;
}
#endif
Bool UniformManager::BindProgramUniformBuffers(VkCommandBuffer commandBuffer,
const MagmaProgramSource& program,
@@ -3244,12 +3188,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
(samplerBindingOverrides == nullptr || samplerBindingOverrides->empty());
if (cacheable && samplerDescriptorsUnchangedHint && m_fastRebindMemo.valid &&
m_fastRebindMemo.frameIndex == frameIndex &&
#if MOBILEGL_BUILD_RECORD_ARM
// A wire store destroyed since the memo was taken may have handed its handle
// value to a later mint (see FastRebindMemo): "same VkBuffer" then names a
// different store, so the memo is refused and the full walk re-records it.
m_fastRebindMemo.wireStoreDestroyEpoch == m_bufferManager->GetWireStoreDestroyEpoch() &&
#endif
m_fastRebindMemo.programLifetimeId == program.GetLifetimeId() &&
m_fastRebindMemo.programHash == programObj.hash) {
VkBuffer uboBuffer = VK_NULL_HANDLE;
@@ -3546,7 +3488,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
};
mixWords(&programObj.descriptorSetLayout, sizeof(programObj.descriptorSetLayout));
#if MOBILEGL_BUILD_RECORD_ARM
// P7 M2 round 2 (ID-P7-43): the buffer infos below name WIRE stores by VkBuffer
// handle, and a wire store can be destroyed mid-frame (VkBufferManager::
// DeferredWireRelease) with its handle value re-minted before this frame's memo
@@ -3555,7 +3496,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// store. NOT the slice epoch: that moves on every glBufferSubData and would make
// the memo miss on every draw.
mix64(m_bufferManager->GetWireStoreDestroyEpoch());
#endif
for (const auto& write : writes) {
mix64((static_cast<Uint64>(write.dstBinding) << 40) ^
(static_cast<Uint64>(write.descriptorType) << 8) ^
@@ -3611,9 +3551,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
/*valid=*/true, frameIndex, program.GetLifetimeId(), programObj.hash,
fastRebindUboBinding, bufferInfos[0].buffer,
bufferInfos[0].range, descriptorSet,
#if MOBILEGL_BUILD_RECORD_ARM
m_bufferManager->GetWireStoreDestroyEpoch(),
#endif
};
} else {
m_fastRebindMemo.valid = false;
@@ -56,7 +56,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void Shutdown();
void BeginFrame(Uint32 frameIndex);
#if MOBILEGL_BUILD_RECORD_ARM
// A present-less wire frame may run arbitrarily many draws. Before its
// descriptor cursor reaches this budget, the renderer retires the
// submission that last used the sets and rewinds every layout cursor.
@@ -74,7 +73,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// store inside one pass reading zero, nor privacy past kWirePrivateStoragePlaceholderCap
// placeholders (CONTRACT-P7 §12). Called after Initialize.
void SetWireInvalidStorageImageArm(Bool deviceNullDescriptor);
#endif
// A command buffer (re)began recording: descriptor bindings recorded into
// the previous buffer do not carry over, so drop the bind-dedup shadow.
void OnCommandBufferBoundary() { m_lastBindValid = false; }
@@ -123,13 +121,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Vector<SamplerImageFeedbackBinding>& outBindings) const;
static Bool SamplerOverlapsWritableImageSubresource(Int samplerBaseLevel, Int samplerMaxLevel,
GLint imageLevel, GLenum imageAccess);
#if MOBILEGL_BUILD_RECORD_ARM
// Resolve lazy texture uploads/promotions before the caller captures the
// command buffer passed to BindProgramUniformBuffers. Preparation may
// submit older work, but descriptor recording must never rotate it.
Bool PrepareWireTextureResources(const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj);
#endif
// samplerDescriptorsUnchangedHint: the caller (SetupDraw fast path) proved that
// every input of every combined-image-sampler resolution is unchanged since the
// previous draw's resolve - same (texture, sampler) per binding, texture params
@@ -215,7 +211,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint32 cursor = 0;
};
#if MOBILEGL_BUILD_RECORD_ARM
// ResolveWireImageDescriptor emits no pNext chain. Key every view-create
// value explicitly: hash collisions must never alias different windows,
// formats or swizzles. The root handle and allocation epoch also prevent
@@ -273,7 +268,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Bool storage,
VkDescriptorImageInfo& out, VkFormat boundStorageFormat = VK_FORMAT_UNDEFINED, Uint32 unit = ~0u) const;
void DestroyWirePlaceholderImage(WirePlaceholderImage& image) const;
#endif
// descriptorSetCacheByLayout is the per-frame set cache: BeginFrame rewinds every cursor, so
// a slot re-writes the sets it allocated in earlier frames instead of allocating new ones, and
@@ -283,12 +277,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Vector<DescriptorPoolBucket> descriptorPools;
UnorderedMap<VkDescriptorSetLayout, DescriptorSetCacheEntry> descriptorSetCacheByLayout;
Vector<VkBufferView> texelBufferViews;
#if MOBILEGL_BUILD_RECORD_ARM
mutable Vector<VkImageView> wireImageViews;
// Own views in the vector above until this slot's fence/idle proof.
// Cache hits preserve the handle, enabling descriptor-content reuse.
mutable UnorderedMap<WireImageViewKey, VkImageView, WireImageViewKeyHash> wireImageViewCache;
#endif
Uint32 activeDescriptorPoolIndex = 0;
Uint32 allocatedSetsThisFrame = 0;
Uint32 peakAllocatedSetsThisFrame = 0;
@@ -366,7 +358,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
Uint32 element, VkDescriptorImageInfo& outImageInfo,
Bool trustUnchangedHint = false) const;
#if MOBILEGL_BUILD_RECORD_ARM
Bool ResolveWireImageDescriptor(VkCommandBuffer commandBuffer, const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
Uint32 element, Bool storage, VkDescriptorImageInfo& out) const;
@@ -379,7 +370,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkDeviceSize m_wireMaxUniformRange = ~VkDeviceSize{0};
VkDeviceSize m_wireMaxStorageRange = ~VkDeviceSize{0};
Uint32 m_wireMaxTexelElements = ~Uint32{0};
#endif
Bool ResolveSamplerDescriptorOverride(const SamplerBindingOverride& samplerBindingOverride,
VkDescriptorImageInfo& outImageInfo) const;
Bool ResolveTexelBufferDescriptor(const MagmaProgramSource& program,
@@ -415,10 +405,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const void* payload = nullptr; // fallback UploadTransient path
VkDeviceSize payloadSize = 0;
};
#if MOBILEGL_BUILD_RECORD_ARM
Bool ResolveWireUniformBufferPayload(const MagmaProgramSource& program, Uint32 blockIndex,
Uint32 bindingPoint, UboBindResult& out) const;
#endif
Bool ResolveUniformBufferPayload(const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
Uint32 arrayElement, UboBindResult& out) const;
@@ -563,9 +551,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkBuffer uboBuffer = VK_NULL_HANDLE;
VkDeviceSize uboRange = 0;
VkDescriptorSet set = VK_NULL_HANDLE;
#if MOBILEGL_BUILD_RECORD_ARM
Uint64 wireStoreDestroyEpoch = 0;
#endif
};
FastRebindMemo m_fastRebindMemo;
@@ -394,7 +394,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return entry;
}
#if MOBILEGL_BUILD_RECORD_ARM
Bool VertexInputStateFactory::BuildWireVertexInput(const MG_Pipe::MGPipeVertexElementsRecord& elements,
const MG_Pipe::MGPipeApplierState& state, Uint32 activeMask, BackendVertexInputState& out) const {
out = BackendVertexInputState{};
@@ -532,7 +531,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
return true;
}
#endif
void VertexInputStateFactory::OnFrameBoundary() {
++m_frameBoundaryCounter;
@@ -802,7 +800,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (m_physicalDevice == VK_NULL_HANDLE || format == VK_FORMAT_UNDEFINED) {
return false;
}
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord()) {
const auto found = m_wireVertexFormatSupport.find(format);
if (found != m_wireVertexFormatSupport.end()) return found->second;
@@ -812,7 +809,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_wireVertexFormatSupport.emplace(format, supported);
return supported;
}
#endif
VkFormatProperties properties{};
vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &properties);
return (properties.bufferFeatures & VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT) != 0;
@@ -10,9 +10,7 @@
// MG_Pipe::MGPipeHandle for the P2 D12.5 memo table below. A header of constexpr constants,
// so the pull build gains nothing from it.
#include <MG_Pipe/MGPipeHandles.h>
#if MOBILEGL_BUILD_RECORD_ARM
#include <MG_Pipe/PipeApply.h>
#endif
#include "Config.h"
#include "MagmaPipeArms.h"
@@ -122,13 +120,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// normal types, and 4 (one packed word) for the 2_10_10_10 types and GL_BGRA. Returns 0 for
// an unknown/unsupported type.
static SizeT GetAttributeByteSize(DataType type, Int size, Bool isBgra);
#if MOBILEGL_BUILD_RECORD_ARM
// A wire VAO is a layout record plus a separate per-attribute buffer window.
// No frontend VAO identity, address or memo participates in this layout.
Bool BuildWireVertexInput(const MG_Pipe::MGPipeVertexElementsRecord& elements,
const MG_Pipe::MGPipeApplierState& state, Uint32 activeMask,
BackendVertexInputState& out) const;
#endif
private:
static VkFormat ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger, Bool isBgra = false,
@@ -175,11 +171,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const VulkanRendererConfig& m_config;
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
#if MOBILEGL_BUILD_RECORD_ARM
// Physical-device format capabilities never change during this factory's
// lifetime. The wire path rebuilds layouts without the legacy VAO memo.
mutable UnorderedMap<VkFormat, Bool> m_wireVertexFormatSupport;
#endif
// Values are heap-allocated: UnorderedMap is open-addressing, so INSERT
// invalidates references to stored values - and so does ERASE, which shifts
// the rest of the probe cluster into the hole and therefore moves entries
@@ -12,7 +12,6 @@
#include "VulkanRenderer.h"
#include "MG_Util/Metrics/PipeStats.h"
#if MOBILEGL_BUILD_RECORD_ARM
#include "MG_Pipe/MGPipeCallbacks.h"
#include "MG_Pipe/PipeApply.h"
#include <MG_Backend/Record/StagedShadow.h>
@@ -25,11 +24,9 @@
#include <Config.h>
#include <chrono>
#include <cstdlib>
#endif
namespace MobileGL::MG_Backend::DirectVulkan {
namespace {
#if MOBILEGL_BUILD_RECORD_ARM
// P7 wave 2 package B3, INVESTIGATION PROBE (split-only, read once, not in
// ConfigLoader's accepted-env table - package B's MGITEST_MAGMA_FORCE_SHADER_MIPMAP
// shape). MOBILEGL_MAGMA_WIREBUF_PROBE=1 prints one line per streamed-buffer event so
@@ -74,7 +71,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Counts only the writes the busy predicate wanted to ORDER, so the selector indexes
// the streamed subdata -> draw pairs and nothing else.
Uint64 g_orderedWireWriteCounter = 0;
#endif
constexpr VmaAllocationCreateFlags kResidentBufferAllocationFlags =
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
constexpr SizeT kLiveResourcePruneThreshold = 256;
@@ -181,7 +177,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.ReadbackFromGpu = Ops_ReadbackFromGpu,
};
#if MOBILEGL_BUILD_RECORD_ARM
// The session's manager, or null once its renderer is gone - a client's records can still
// arrive after its eglTerminate released it (or, surfaceless, before its first surface built
// one). Such a record is declined with one line rather than taking the whole server down.
@@ -237,10 +232,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
},
#endif
};
#endif
} // namespace
#if MOBILEGL_BUILD_RECORD_ARM
void VkBufferManager::RegisterWireResourceOps() {
if (MG_Config::DataArmIsRecord()) {
MG_Pipe::MGPipeSetResourceOps(&g_vulkanWireResourceOps);
@@ -1208,7 +1201,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_deferredWireReleases.clear();
m_deferredWireBytes = 0;
}
#endif
Bool VkBufferManager::Initialize(const VkBufferManagerInitInfo& initInfo) {
Shutdown();
@@ -1227,16 +1219,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
g_activeBufferManager.Get() = this;
MG_State::GLState::SetBufferBackendOps(&g_vulkanBufferBackendOps);
#if MOBILEGL_BUILD_RECORD_ARM
RegisterWireResourceOps();
#endif
return true;
}
void VkBufferManager::Shutdown() {
if (g_activeBufferManager.Get() == this) {
g_activeBufferManager.Get() = nullptr;
#if MOBILEGL_BUILD_RECORD_ARM
// Under a transport the table is the PROCESS's and serves every session: it dispatches
// to the calling session's own manager (WireManager), so one session's renderer going
// away must not take it from the others - their buffer creates would be dropped from
@@ -1245,7 +1234,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MG_Pipe::MGPipeGetResourceOps() == &g_vulkanWireResourceOps) {
MG_Pipe::MGPipeSetResourceOps(nullptr);
}
#endif
if (MG_State::GLState::GetBufferBackendOps() == &g_vulkanBufferBackendOps) {
MG_State::GLState::SetBufferBackendOps(nullptr);
}
@@ -1255,7 +1243,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_unboundTexelBuffer.Destroy();
DestroyAllDeferredReleases();
ReleaseAllLiveResources();
#if MOBILEGL_BUILD_RECORD_ARM
// DestroyAllDeferredReleases above emptied the parked list; this destroys the stores the
// records still hold, so nothing this arm minted outlives the count.
if (!m_wireBuffers.empty()) ++m_wireStoreDestroyEpoch;
@@ -1264,7 +1251,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_wireStoreCountPeak = 0;
m_deferredWireBytesPeak = 0;
m_wireDeferredSyncs = 0;
#endif
m_copyProvider = nullptr;
m_initInfo = {};
m_currentFrameIndex = 0;
@@ -1293,13 +1279,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
"VkBufferManager::BeginFrame frame index out of range");
m_currentFrameIndex = frameIndex;
++m_frameSerial;
#if MOBILEGL_BUILD_RECORD_ARM
if (WireBufProbeEnabled()) {
MGLOG_I("WBUF BEGINFRAME idx=%u newSerial=%llu floor=%llu (arena slot rewound)", frameIndex,
static_cast<unsigned long long>(m_frameSerial),
static_cast<unsigned long long>(m_completedSerialFloor));
}
#endif
CollectDeferredReleases(frameIndex);
m_transientUploadArena.BeginFrame(frameIndex);
}
@@ -1399,9 +1383,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.frameCount = m_initInfo.frameCount,
.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT |
#if MOBILEGL_BUILD_RECORD_ARM
(MG_Config::DataArmIsRecord() ? VkBufferUsageFlags{VK_BUFFER_USAGE_TRANSFER_DST_BIT} : 0u) |
#endif
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
.memoryUsage = m_initInfo.transientMemoryUsage,
.allocationFlags = m_initInfo.transientAllocationFlags,
@@ -1557,7 +1539,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true;
}
#if MOBILEGL_BUILD_RECORD_ARM
Bool VkBufferManager::StagedWireRangeCopy(WireBufferResource& resource, const void* data,
SizeT offset, SizeT size) {
if (!m_copyProvider) {
@@ -1610,7 +1591,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true;
}
#endif
void VkBufferManager::OnRespecify(MG_State::GLState::BufferObject& bufferObject) {
auto* resource = ResourceOf(bufferObject);
@@ -1987,12 +1967,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
"VkBufferManager::CollectDeferredReleases frame index out of range");
m_deferredBufferReleases[frameIndex].clear();
m_deferredResourceReleases[frameIndex].clear();
#if MOBILEGL_BUILD_RECORD_ARM
// The wire list is not per-slot and does not wait for a frame boundary; a boundary is
// just one more point to sweep at (BeginFrame after its slot fence, and every slot of
// the idle drain, where the renderer-idle rule empties the list).
SweepDeferredWireReleases();
#endif
}
BufferSlice VkBufferManager::AcquireUnboundStorageDescriptor() {
@@ -2088,10 +2066,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
void VkBufferManager::DestroyAllDeferredReleases() {
#if MOBILEGL_BUILD_RECORD_ARM
// Both callers (Shutdown, RecreateTransientArenas) have proven the device idle.
DestroyAllDeferredWireReleases();
#endif
for (auto& releases : m_deferredBufferReleases) {
for (auto& buffer : releases) {
buffer.Destroy();
@@ -13,10 +13,8 @@
#include "../VkIncludes.h"
#include <Includes.h>
#include <vk_mem_alloc.h>
#if MOBILEGL_BUILD_RECORD_ARM
#include "MG_Pipe/MGPipeTypes.h"
#include <unordered_map>
#endif
namespace MobileGL::MG_Backend::DirectVulkan {
enum class BufferKind : Uint8 {
@@ -103,7 +101,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool Initialize(const VkBufferManagerInitInfo& initInfo);
void Shutdown();
#if MOBILEGL_BUILD_RECORD_ARM
// Registered before caps publication; the initialized renderer owns the storage.
static void RegisterWireResourceOps();
// Transport resources are owned by their complete wire handle, never by a
@@ -158,7 +155,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void* DonateWireBuffer(MG_Pipe::MGPipeHandle res, Uint64 size, const void* seedBytes);
// The POST self-test of the sustained-lock pattern on this device (see the definition).
static Bool SelfTestWireImport(char* why, Uint64 whyBytes);
#endif
// Recreate all per-frame transient arenas
Bool RecreateTransientArenas(Uint32 frameCount);
@@ -232,13 +228,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// back, and none was persistently mapped, in between - so a memo of resolved
// slices needs no per-buffer re-check. See AcquirePersistentMap for the mapping half.
Uint64 GetSliceEpochCounter() const { return m_sliceEpochCounter; }
#if MOBILEGL_BUILD_RECORD_ARM
// Bumped every time this manager destroys a WIRE store's VkBuffer (see
// m_wireStoreDestroyEpoch). Unchanged since a memo was taken means no VkBuffer handle
// that memo names can have been freed and re-minted in between, which is the one
// fact a handle-keyed memo of wire descriptors needs and cannot read off the handle.
Uint64 GetWireStoreDestroyEpoch() const { return m_wireStoreDestroyEpoch; }
#endif
// Highest frame serial whose GPU work is known complete; serials at or
// below it may be considered signaled. Drives IsResourceBusy and the
// backend GL fence objects.
@@ -254,7 +248,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void DeferRelease(VkBufferObject&& buffer);
private:
#if MOBILEGL_BUILD_RECORD_ARM
struct WireBufferResource {
VkBufferObject buffer;
Uint64 size = 0;
@@ -374,7 +367,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// the stats channel is on; MagmaWireReclaimScenario reads them off the server's line.
void NoteWireStorePeaks();
void PublishWireReclaimGauges();
#endif
Bool InitializeTransientArenas();
static VkBufferUsageFlags GetVkBufferUsage(BufferKind kind);
VkBufferResource* GetOrCreateResource(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject);
@@ -387,9 +379,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// in-flight and already-recorded GPU work.
Bool StagedRangeCopy(VkBufferResource& resource, const void* data,
SizeT offset, SizeT size);
#if MOBILEGL_BUILD_RECORD_ARM
Bool StagedWireRangeCopy(WireBufferResource& resource, const void* data, SizeT offset, SizeT size);
#endif
void CollectDeferredReleases(Uint32 frameIndex);
void DestroyAllDeferredReleases();
void TrackLiveResource(const SharedPtr<VkBufferResource>& resource);
@@ -408,7 +398,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Vector<Vector<VkBufferObject>> m_deferredBufferReleases;
Vector<Vector<SharedPtr<VkBufferResource>>> m_deferredResourceReleases;
Vector<WeakPtr<VkBufferResource>> m_liveResources;
#if MOBILEGL_BUILD_RECORD_ARM
std::unordered_map<Uint64, WireBufferResource> m_wireBuffers;
// See DeferredWireRelease. ONE FLAT LIST rather than the per-frame-slot buckets above:
// the whole point is that these entries do not wait for a frame slot to come round.
@@ -427,7 +416,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// for m_sliceEpochCounter's reason: a memo taken before a re-initialize must not match
// a handle minted after it.
Uint64 m_wireStoreDestroyEpoch = 0;
#endif
// Size m_liveResources had just after the last sweep; the next sweep waits for it to double.
SizeT m_liveResourcesLastPruned = 0;
Uint32 m_currentFrameIndex = 0;
@@ -13,7 +13,6 @@
#endif
namespace MobileGL::MG_Backend::DirectVulkan {
#if MOBILEGL_BUILD_RECORD_ARM
void VkBufferObject::MoveExternalFrom(VkBufferObject& other) {
m_device = other.m_device;
m_externalMemory = other.m_externalMemory;
@@ -36,7 +35,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_externalCoherent = coherent;
m_externalAhb = ahb;
}
#endif
VkBufferObject::VkBufferObject(VkBufferObject&& other) noexcept {
m_allocator = other.m_allocator;
@@ -44,9 +42,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_allocation = other.m_allocation;
m_mappedData = other.m_mappedData;
m_size = other.m_size;
#if MOBILEGL_BUILD_RECORD_ARM
MoveExternalFrom(other);
#endif
other.m_allocator = nullptr;
other.m_buffer = VK_NULL_HANDLE;
@@ -67,9 +63,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_allocation = other.m_allocation;
m_mappedData = other.m_mappedData;
m_size = other.m_size;
#if MOBILEGL_BUILD_RECORD_ARM
MoveExternalFrom(other);
#endif
other.m_allocator = nullptr;
other.m_buffer = VK_NULL_HANDLE;
@@ -170,10 +164,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
void VkBufferObject::Unmap() {
#if MOBILEGL_BUILD_RECORD_ARM
// An imported store stays mapped for its life (Destroy unmaps it).
if (m_externalMemory != VK_NULL_HANDLE) return;
#endif
if (!IsValid() || m_mappedData == nullptr) {
m_mappedData = nullptr;
return;
@@ -200,7 +192,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
Memcpy(static_cast<Uint8*>(mapped) + offset, data, static_cast<SizeT>(size));
#if MOBILEGL_BUILD_RECORD_ARM
if (m_externalMemory != VK_NULL_HANDLE) {
if (m_externalCoherent) return true;
VkMappedMemoryRange range{VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE};
@@ -209,7 +200,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
range.size = VK_WHOLE_SIZE;
return vkFlushMappedMemoryRanges(m_device, 1, &range) == VK_SUCCESS;
}
#endif
const VkResult flushResult = vmaFlushAllocation(m_allocator, m_allocation, offset, size);
if (flushResult != VK_SUCCESS) {
MGLOG_E_ONCE("VkBufferObject::Upload failed: vmaFlushAllocation returned %d", flushResult);
@@ -234,7 +224,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (resolvedSize == 0) {
return true;
}
#if MOBILEGL_BUILD_RECORD_ARM
if (m_externalMemory != VK_NULL_HANDLE) {
if (m_externalCoherent) return true;
VkMappedMemoryRange range{VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE};
@@ -243,7 +232,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
range.size = VK_WHOLE_SIZE;
return vkInvalidateMappedMemoryRanges(m_device, 1, &range) == VK_SUCCESS;
}
#endif
const VkResult result = vmaInvalidateAllocation(m_allocator, m_allocation, offset, resolvedSize);
if (result != VK_SUCCESS) {
@@ -65,13 +65,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void* GetMappedData() const { return m_mappedData; }
Bool IsMapped() const { return m_mappedData != nullptr; }
Bool IsValid() const {
#if MOBILEGL_BUILD_RECORD_ARM
if (m_externalMemory != VK_NULL_HANDLE) return m_buffer != VK_NULL_HANDLE;
#endif
return m_allocator != nullptr && m_buffer != VK_NULL_HANDLE && m_allocation != nullptr;
}
#if MOBILEGL_BUILD_RECORD_ARM
// P11 B2 (T0): a store whose memory is NOT this allocator's - a client's AHardwareBuffer
// imported with VK_ANDROID_external_memory_android_hardware_buffer into `memory` (a
// dedicated allocation bound to `buffer`, mapped persistently at `mapped`). It behaves
@@ -82,7 +79,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void AdoptExternal(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize size,
void* mapped, Bool coherent, void* ahb);
Bool IsExternal() const { return m_externalMemory != VK_NULL_HANDLE; }
#endif
private:
VmaAllocator m_allocator = nullptr;
@@ -90,12 +86,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VmaAllocation m_allocation = nullptr;
void* m_mappedData = nullptr;
VkDeviceSize m_size = 0;
#if MOBILEGL_BUILD_RECORD_ARM
VkDevice m_device = VK_NULL_HANDLE;
VkDeviceMemory m_externalMemory = VK_NULL_HANDLE;
void* m_externalAhb = nullptr;
Bool m_externalCoherent = true;
void MoveExternalFrom(VkBufferObject& other);
#endif
};
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -15,7 +15,6 @@
namespace MobileGL::MG_Backend::DirectVulkan {
namespace {
#if MOBILEGL_BUILD_RECORD_ARM
// An accessor view over the wire's POD values, not a GL object with constructor,
// lifetime ID, allocator hooks or mutation callbacks. Both sources below use
// one sampler policy, including format-sensitive border-color conversion.
@@ -43,9 +42,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
template <class SamplerSource>
Bool UsesBorderColor(const SamplerSource& sampler) {
#else
Bool UsesBorderColor(const MG_State::GLState::SamplerObject& sampler) {
#endif
return sampler.GetWrapS() == SamplerWrapMode::ClampToBorder ||
sampler.GetWrapT() == SamplerWrapMode::ClampToBorder ||
sampler.GetWrapR() == SamplerWrapMode::ClampToBorder;
@@ -221,24 +217,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return std::fabs(lhs - rhs) <= 1e-6f;
}
#if MOBILEGL_BUILD_RECORD_ARM
template <class SamplerSource>
Float ResolveEffectiveMaxLod(const SamplerSource& sampler) {
#else
Float ResolveEffectiveMaxLod(const MG_State::GLState::SamplerObject& sampler) {
#endif
if (sampler.GetMipmapMode() == SamplerMipmapMode::None) {
return 0.0f;
}
return sampler.GetMaxLod();
}
#if MOBILEGL_BUILD_RECORD_ARM
template <class SamplerSource>
Float ResolveEffectiveMinLod(const SamplerSource& sampler, Float effectiveMaxLod) {
#else
Float ResolveEffectiveMinLod(const MG_State::GLState::SamplerObject& sampler, Float effectiveMaxLod) {
#endif
return std::min(sampler.GetMinLod(), effectiveMaxLod);
}
@@ -249,12 +237,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// GL's non-mipmapped minification - large enough for lambda to stay positive, small enough
// that a NEAREST mip mode still rounds down to level 0. Clamped rather than assigned, so a
// texture whose GL_TEXTURE_MAX_LOD really is 0 keeps magnifying as GL says it must.
#if MOBILEGL_BUILD_RECORD_ARM
template <class SamplerSource>
Float ResolveSingleLevelMaxLod(const SamplerSource& sampler, Bool singleLevelView) {
#else
Float ResolveSingleLevelMaxLod(const MG_State::GLState::SamplerObject& sampler, Bool singleLevelView) {
#endif
const Float maxLod = ResolveEffectiveMaxLod(sampler);
return singleLevelView ? std::min(maxLod, 0.25f) : maxLod;
}
@@ -275,12 +259,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true;
}
#if MOBILEGL_BUILD_RECORD_ARM
template <class SamplerSource>
Float VkSamplerManager::ResolveEffectiveMaxAnisotropy(const SamplerSource& sampler,
#else
Float VkSamplerManager::ResolveEffectiveMaxAnisotropy(const MG_State::GLState::SamplerObject& sampler,
#endif
Bool forceNearestFiltering) const {
if (!m_samplerAnisotropySupported) return 1.0f;
if (forceNearestFiltering) return 1.0f;
@@ -339,12 +319,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
}
#if MOBILEGL_BUILD_RECORD_ARM
template <class SamplerSource>
Uint64 VkSamplerManager::BuildSamplerKey(const SamplerSource& sampler,
#else
Uint64 VkSamplerManager::BuildSamplerKey(const MG_State::GLState::SamplerObject& sampler,
#endif
Bool forceNearestFiltering, Bool singleLevelView,
const ResolvedBorderColor& borderColor) const {
MOBILEGL_ASSERT(m_config != nullptr, "VkSamplerManager::BuildSamplerKey: m_config is null");
@@ -390,14 +366,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return XXH64_digest(m_hashState);
}
#if MOBILEGL_BUILD_RECORD_ARM
template <class SamplerSource>
VkSampler VkSamplerManager::GetOrCreateSamplerImpl(const SamplerSource& sampler,
TextureInternalFormat format,
#else
VkSampler VkSamplerManager::GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler,
const MG_State::GLState::ITextureObject& texture,
#endif
Bool forceNearestFiltering, Uint32 viewLevelCount) {
// A view that exposes a single mip level has no second level to blend with, so GL's
// *_MIPMAP_* minification filters degenerate to plain filtering on the base level -
@@ -409,11 +380,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// the default-framebuffer blit shader had to work around with an explicit-LOD sample.
const Bool singleLevelView = viewLevelCount == 1;
// Resolved once and used for both the key and the create-info; see ResolvedBorderColor.
#if MOBILEGL_BUILD_RECORD_ARM
const ResolvedBorderColor borderColor = ResolveBorderColor(sampler, format);
#else
const ResolvedBorderColor borderColor = ResolveBorderColor(sampler, texture);
#endif
const Uint64 key = BuildSamplerKey(sampler, forceNearestFiltering, singleLevelView, borderColor);
auto it = m_samplers.find(key);
if (it != m_samplers.end()) {
@@ -473,7 +440,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return vkSampler;
}
#if MOBILEGL_BUILD_RECORD_ARM
VkSampler VkSamplerManager::GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler,
const MG_State::GLState::ITextureObject& texture,
Bool forceNearestFiltering, Uint32 viewLevelCount) {
@@ -485,7 +451,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return GetOrCreateSamplerImpl(SamplerParametersSource{parameters}, format,
forceNearestFiltering, viewLevelCount);
}
#endif
VkFilter VkSamplerManager::ToVkFilter(SamplerFilterMode mode) {
return mode == SamplerFilterMode::Nearest ? VK_FILTER_NEAREST : VK_FILTER_LINEAR;
@@ -542,14 +507,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
}
#if MOBILEGL_BUILD_RECORD_ARM
template <class SamplerSource>
VkSamplerManager::ResolvedBorderColor VkSamplerManager::ResolveBorderColor(
const SamplerSource& sampler, TextureInternalFormat format) const {
#else
VkSamplerManager::ResolvedBorderColor VkSamplerManager::ResolveBorderColor(
const MG_State::GLState::SamplerObject& sampler, const MG_State::GLState::ITextureObject& texture) const {
#endif
ResolvedBorderColor resolved{};
if (!UsesBorderColor(sampler)) {
return resolved; // FLOAT_TRANSPARENT_BLACK, never sampled
@@ -557,9 +517,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Border colour is sampler state: a bound sampler object supplies its own, and a texture
// with none reaches the very same value through the sampler object it owns.
#if !MOBILEGL_BUILD_RECORD_ARM
const auto format = texture.GetFormat();
#endif
const auto domain = ResolveBorderColorDomain(format);
const Bool canUseCustom = m_customBorderColorSupported && m_maxCustomBorderColorSamplers > 0 &&
m_customBorderColorSamplerCount < m_maxCustomBorderColorSamplers;
@@ -46,12 +46,10 @@ public:
const MG_State::GLState::ITextureObject& texture,
Bool forceNearestFiltering = false,
Uint32 viewLevelCount = 0);
#if MOBILEGL_BUILD_RECORD_ARM
// The applier owns these values: effective bound/builtin sampler parameters and
// the sampled texture view's format/range. No frontend object is constructed.
VkSampler GetOrCreateSamplerFromParameters(const SamplerParameters& parameters,
TextureInternalFormat format, Bool forceNearestFiltering = false, Uint32 viewLevelCount = 0);
#endif
// Frame boundary hook: ages the sampler cache and destroys samplers not used
// for many frames. The key hashes continuous float state (lodBias, LOD clamps,
// anisotropy), so an app animating those would otherwise mint an unbounded
@@ -92,37 +90,24 @@ private:
Bool usesCustomBorderColor = false;
};
#if MOBILEGL_BUILD_RECORD_ARM
template <class SamplerSource>
VkSampler GetOrCreateSamplerImpl(const SamplerSource& sampler, TextureInternalFormat format,
Bool forceNearestFiltering, Uint32 viewLevelCount);
template <class SamplerSource>
Uint64 BuildSamplerKey(const SamplerSource& sampler, Bool forceNearestFiltering,
#else
Uint64 BuildSamplerKey(const MG_State::GLState::SamplerObject& sampler, Bool forceNearestFiltering,
#endif
Bool singleLevelView, const ResolvedBorderColor& borderColor) const;
static VkFilter ToVkFilter(SamplerFilterMode mode);
static VkSamplerMipmapMode ToVkMipmapMode(SamplerMipmapMode mode);
static VkSamplerAddressMode ToVkAddressMode(SamplerWrapMode mode);
static VkCompareOp ToVkCompareOp(SamplerCompareFunc func);
#if MOBILEGL_BUILD_RECORD_ARM
template <class SamplerSource>
ResolvedBorderColor ResolveBorderColor(const SamplerSource& sampler, TextureInternalFormat format) const;
#else
ResolvedBorderColor ResolveBorderColor(const MG_State::GLState::SamplerObject& sampler,
const MG_State::GLState::ITextureObject& texture) const;
#endif
// The anisotropy Vulkan will actually apply: 1.0 (i.e. disabled) unless the feature is on and
// the sampler filters linearly both ways, otherwise the GL request clamped to the device limit.
// GL happily carries GL_TEXTURE_MAX_ANISOTROPY on a NEAREST sampler (Blaze3D's blocks do exactly
// that) while Vulkan forbids anisotropyEnable there, so the GL value must never be forwarded raw.
#if MOBILEGL_BUILD_RECORD_ARM
template <class SamplerSource>
Float ResolveEffectiveMaxAnisotropy(const SamplerSource& sampler,
#else
Float ResolveEffectiveMaxAnisotropy(const MG_State::GLState::SamplerObject& sampler,
#endif
Bool forceNearestFiltering) const;
VkDevice m_device = VK_NULL_HANDLE;
@@ -18,7 +18,6 @@
#include "MG_Util/Metrics/PipeStats.h"
#include <Config.h>
#if MOBILEGL_BUILD_RECORD_ARM
// P5f (fm): the handle-keyed texture arm's two sources - the applier's resource records
// (shape) and the server's staged-texture store (texels).
#include <MG_Pipe/PipeApply.h>
@@ -26,7 +25,6 @@
#include "../DirectVulkan.h"
// P7 wave 2 package B3: rule I's tally for the silent exits on this file's wire arm.
#include "WireDeclineTally.h"
#endif
#if MOBILEGL_BUILD_DISAGGREGATED
// Shared images: a texture whose level 0 is a server-allocated AHardwareBuffer.
#include <MG_Remote/Server/SharedImageRegistry.h>
@@ -319,9 +317,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkQueue graphicsQueue,
const VkTextureManager::TextureResource& oldResource,
VkTextureManager::TextureResource& newResource
#if MOBILEGL_BUILD_RECORD_ARM
, Bool splitAspects = false
#endif
) {
MOBILEGL_ASSERT(device != VK_NULL_HANDLE, "PreserveTextureContentsOnRecreate: device is null");
MOBILEGL_ASSERT(commandPool != VK_NULL_HANDLE, "PreserveTextureContentsOnRecreate: commandPool is null");
@@ -380,7 +376,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
copy.extent.width = std::max(oldResource.extent.width >> level, 1u);
copy.extent.height = std::max(oldResource.extent.height >> level, 1u);
copy.extent.depth = std::max(oldResource.depth >> level, 1u);
#if MOBILEGL_BUILD_RECORD_ARM
if (splitAspects) {
for (const VkImageAspectFlags aspect : {VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_ASPECT_DEPTH_BIT,
@@ -391,7 +386,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
copyRegions.push_back(copy);
}
} else
#endif
copyRegions.push_back(copy);
}
@@ -723,12 +717,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
DestroyDeferredReleases();
++m_resourceEraseEpoch; // every memoized resource pointer dies with the map
m_textureResources.clear();
#if MOBILEGL_BUILD_RECORD_ARM
// Destroy images while the device/allocator still exist, including after context death.
m_wireTextureResources.clear();
m_wireRenderbufferResources.clear();
m_sharedImageUses.clear();
#endif
m_aliveObjects.clear();
m_storageImageTextures.clear();
@@ -1846,7 +1838,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
EraseTrackedTexture(identity);
}
prunedCount += orphanIdentities.size();
#if MOBILEGL_BUILD_RECORD_ARM
const auto pruneWire = [&](auto& resources, const auto& records) {
for (auto it = resources.begin(); it != resources.end();) {
const Uint32 slot = static_cast<Uint32>(it->first >> 32) & 0x7fffffffu;
@@ -1864,11 +1855,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const auto& applier = MG_Pipe::MGPipeApplier();
pruneWire(m_wireTextureResources, applier.TextureResources);
pruneWire(m_wireRenderbufferResources, applier.RenderbufferResources);
#endif
return prunedCount;
}
#if MOBILEGL_BUILD_RECORD_ARM
// ---------------------------------------------------------------------------------
// P5f (fm): THE HANDLE-KEYED TEXTURE ARM
// ---------------------------------------------------------------------------------
@@ -3275,7 +3264,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
std::construct_at(&resource, Move(grown));
return true;
}
#endif // MOBILEGL_BUILD_RECORD_ARM
Bool VkTextureManager::SyncTexture(MG_State::GLState::ITextureObject &texture,
TextureResource &outResource) {
@@ -3832,9 +3820,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
resource.perMipViews.empty() && resource.perMipSampledViews.empty() &&
resource.attachmentViews.empty() && resource.alternateSampledViews.empty() &&
resource.storageImageViews.empty()
#if MOBILEGL_BUILD_RECORD_ARM
&& resource.yuvSource == nullptr
#endif
) {
return;
}
@@ -4006,7 +3992,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return current->mapped + alignedCursor;
}
#if MOBILEGL_BUILD_RECORD_ARM
Bool VkTextureManager::WireUploadsAreIdle() {
// An open batch still owns references even before it has a fence. Poll
// submitted batches without waiting; reclamation is deferred if any lives.
@@ -4014,7 +3999,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
ReclaimCompletedUploads();
return m_pendingUploadReclaims.empty();
}
#endif
void VkTextureManager::FlushPendingUploads() {
if (!m_uploadBatchOpen) {
@@ -19,20 +19,16 @@
#include <unordered_set>
#include <Config.h>
#if MOBILEGL_BUILD_RECORD_ARM
// The handle-keyed arm's key type. Header-only POD, disagg-only, exactly the shape
// MagmaPipeArms.h already gives this directory.
#include <MG_Pipe/MGPipeHandles.h>
#endif
namespace MobileGL::MG_State::GLState {
class ITextureObject;
}
#if MOBILEGL_BUILD_RECORD_ARM
namespace MobileGL::MG_Pipe {
struct MGPipeResourceRecord;
}
#endif
namespace MobileGL::MG_Backend::DirectVulkan {
enum class SamplerNumericDomain : Uint8;
@@ -355,7 +351,6 @@ public:
// Serial at the last successful sync. 0 also means "this resource has only ever been
// synced by the frontend arm" - a wire record's Serial starts at 0 on a create and moves
// on every mutation, so the first handle sync of a fresh image always re-checks.
#if MOBILEGL_BUILD_RECORD_ARM
Uint64 syncedWireSerial = 0;
// SHARED IMAGES: level 0 is a server shared image (an imported AHardwareBuffer), not a
// VMA allocation. The resource owns the image and its dedicated imported memory, and
@@ -381,7 +376,6 @@ public:
SharedPtr<const void> yuvOwner;
SharedPtr<void> yuvSource;
Uint64 yuvConvertedFrame = 0;
#endif
TextureResource() = default;
TextureResource(const TextureResource&) = delete;
@@ -415,7 +409,6 @@ public:
std::swap(this->syncedContentVersion, that.syncedContentVersion);
std::swap(this->syncedMipLevelCount, that.syncedMipLevelCount);
std::swap(this->syncedShapeVersion, that.syncedShapeVersion);
#if MOBILEGL_BUILD_RECORD_ARM
std::swap(this->syncedWireSerial, that.syncedWireSerial);
std::swap(this->importedMemory, that.importedMemory);
std::swap(this->sharedImageId, that.sharedImageId);
@@ -428,7 +421,6 @@ public:
std::swap(this->yuvOwner, that.yuvOwner);
std::swap(this->yuvSource, that.yuvSource);
std::swap(this->yuvConvertedFrame, that.yuvConvertedFrame);
#endif
}
void Reset() {
@@ -466,7 +458,6 @@ public:
if (image != VK_NULL_HANDLE && allocation != nullptr) {
vmaDestroyImage(s_allocator.Get(), image, allocation);
}
#if MOBILEGL_BUILD_RECORD_ARM
if (importedMemory != VK_NULL_HANDLE) {
if (image != VK_NULL_HANDLE) vkDestroyImage(s_device.Get(), image, nullptr);
vkFreeMemory(s_device.Get(), importedMemory, nullptr);
@@ -482,7 +473,6 @@ public:
yuvOwner.reset();
yuvSource.reset();
yuvConvertedFrame = 0;
#endif
fullView = VK_NULL_HANDLE;
sampledView = VK_NULL_HANDLE;
perMipViews.clear();
@@ -510,9 +500,7 @@ public:
syncedContentVersion = 0;
syncedMipLevelCount = 0;
syncedShapeVersion = 0;
#if MOBILEGL_BUILD_RECORD_ARM
syncedWireSerial = 0;
#endif
}
~TextureResource() {
@@ -539,11 +527,9 @@ public:
// preserve-on-recreate copy are the existing callers. No-op when the
// batch is empty.
void FlushPendingUploads();
#if MOBILEGL_BUILD_RECORD_ARM
// Non-blocking idle proof for wire-object retirement. Independent texture
// submissions are not represented by VulkanRenderer's submit counter.
Bool WireUploadsAreIdle();
#endif
// Drains every frame slot's deferred image/view releases. Only valid when
// the caller has proven every queue submission complete; used by the
@@ -604,7 +590,6 @@ public:
TextureResource* SyncTextureAndGetDescriptor(
MG_State::GLState::ITextureObject& texture);
#if MOBILEGL_BUILD_RECORD_ARM
// P5f (fm): THE HANDLE-KEYED ARM of SyncTextureAndGetDescriptor. Under an active transport
// the apply thread may not name the client's ITextureObject (rule E), so the clear / blit /
// readback / mipmap verbs resolve their attachment textures from the wire handle the
@@ -679,7 +664,6 @@ public:
const auto it = m_wireTextureResources.find(key);
return it != m_wireTextureResources.end() && it->second.image != VK_NULL_HANDLE ? &it->second : nullptr;
}
#endif
VkImageView GetOrCreateViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel);
VkImageView GetOrCreateAttachmentViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel,
Uint32 baseArrayLayer, Uint32 layerCount,
@@ -856,7 +840,6 @@ private:
void EraseTrackedTexture(const TextureIdentity& identity);
void PruneStaleTextureAliases(MG_State::GLState::ITextureObject* texture);
SizeT PruneDeadTextures();
#if MOBILEGL_BUILD_RECORD_ARM
// The two halves of SyncTextureResourceByHandle: shape from the resource record's
// descriptor (create / recreate / compatibility), then the pending-upload walk against the
// server's staged store. UploadPendingWireLevels mutates the applier's record (consumed
@@ -879,7 +862,6 @@ private:
Vector<Uint64> m_sharedImageUses;
// Handles are per-kind; a renderbuffer and texture may have identical slot/gen.
std::unordered_map<Uint64, TextureResource> m_wireRenderbufferResources;
#endif
VkDevice m_device = VK_NULL_HANDLE;
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
File diff suppressed because it is too large Load Diff
@@ -22,13 +22,11 @@
#include "VkSamplerManager.h"
#include "VkTextureManager.h"
#include "VkTimerQueryManager.h"
#if MOBILEGL_BUILD_RECORD_ARM
#include "GpuProgressMarkers.h"
#include "WireRenderPassCompatibility.h"
#if MOBILEGL_BUILD_DISAGGREGATED
#include <MG_Remote/Server/SharedImageRegistry.h>
#endif
#endif
#include "MG_Util/Math/VectorTypes.h"
#include <Includes.h>
#include <MG_Backend/BackendObject.h>
@@ -150,7 +148,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// of the swapchain image acquired for it (SwapchainObject::BufferAgeOf). Asking makes the
// target keep its presented images' content from then on.
Int32 CurrentDrawBufferAge();
#if MOBILEGL_BUILD_RECORD_ARM
VkBufferManager& GetWireBufferManager() { return m_bufferManager; }
Bool FlushWirePendingCommandsForTextureUpdate() {
// A new texture upload must not race graphics work that samples
@@ -222,7 +219,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// P13 W5: no transport, no shared image (only a server allocates one), so a texture's
// sharedImageId is never set and there is nothing to acquire.
void AcquireSharedImage(VkTextureManager::TextureResource&) {}
#endif
#endif
// FrameContext::IRecordingObserver: prepares the frame's timer-query
@@ -296,11 +292,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth);
void GenerateMipmap(GLenum target);
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
#if MOBILEGL_BUILD_RECORD_ARM
// Server resource handle in, tightly packed owned bytes out. No frontend
// texture, pixel-pack state or PBO is consulted by this readback.
Bool ReadTextureImageWire(const MG_Pipe::MGPReadbackInfo& info, Vector<Uint8>& ownedBytes);
#endif
// GL_DEPTH_COMPONENT / GL_DEPTH_STENCIL / GL_STENCIL_INDEX readback from the
// read framebuffer's depth/stencil attachment (per-aspect buffer copies with
// CPU repacking into the requested client layout).
@@ -410,7 +404,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// loss here is this session's alone.
void NoteDeviceLoss(VkResult result, const char* where);
Bool IsDeviceLost();
#if MOBILEGL_BUILD_RECORD_ARM
// For a wire site whose submit or wait just failed: true when the failure is a lost
// device, in which case this session has been latched (MGPipeSessionLatch - it ends, the
// display server's other sessions do not) and the caller must return from its verb
@@ -422,7 +415,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// is latched, and the caller returns from its verb without submitting anything more. Asked
// before every frame submission and by the apply loop before it applies another record.
Bool LatchIfGpuHung(const char* site);
#endif
// Flush gated on usefulness: only flushes when `submitIndex` is still
// unsubmitted, so poll loops on already-submitted fences do not split
// the frame's render pass (a full tile load/store on TBDR GPUs).
@@ -581,26 +573,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// The structs stay, empty, so ShutdownBlitResources and the two `= {}` assignments read
// the same on both builds.
struct BlitResources {
#if !MOBILEGL_BUILD_RECORD_ARM
SharedPtr<MG_State::GLState::ProgramObject> program;
SharedPtr<MG_State::GLState::SamplerObject> nearestSampler;
SharedPtr<MG_State::GLState::SamplerObject> linearSampler;
Int srcRectLocation = -1;
Int dstRectLocation = -1;
Int surfaceTransformLocation = -1;
Uint32 samplerBinding = 0;
#endif
};
struct DepthMipmapResources {
#if !MOBILEGL_BUILD_RECORD_ARM
SharedPtr<MG_State::GLState::ProgramObject> program;
Int srcRectLocation = -1;
Int dstRectLocation = -1;
Int surfaceTransformLocation = -1;
Int srcTexelSizeLocation = -1;
Uint32 samplerBinding = 0;
#endif
};
// A single-sample staging image for multisample-resolve blits that also have to change
@@ -639,7 +614,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// See NoteDeviceLoss. Set once, never cleared: a lost VkDevice stays lost.
Bool m_deviceLost = false;
Bool m_deviceLossLatched = false;
#if MOBILEGL_BUILD_RECORD_ARM
// Brackets this device's frame submissions for the GPU hang watch (LatchIfGpuHung).
GpuProgressMarkers m_progressMarkers;
struct WireImage {
@@ -925,7 +899,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void CopyWireFramebufferToTexture(GLenum target, GLint level, GLint xoffset, GLint yoffset,
GLint x, GLint y, GLsizei width, GLsizei height);
#endif
void QueueClearBufferPayloadForFramebuffer(const MG_State::GLState::FramebufferObject& framebuffer,
GLenum buffer, GLint drawbuffer,
const ClearAttachmentPayload& clearPayload);
@@ -972,11 +945,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// was waited or the device was idled); drops their records and
// recycles pooled fences.
void OnSubmitsCompletedUpTo(Uint64 submitIndex);
#if MOBILEGL_BUILD_RECORD_ARM
// A later fence alone cannot retire earlier submissions. Wait for the
// whole still-live prefix before advancing the completed submit floor.
Bool WaitForSubmitsUpTo(Uint64 submitIndex, Uint64 timeoutNs);
#endif
VkFence AcquirePooledSubmitFence();
void DestroySubmitFencePool();
Bool HasPendingRecordedWork() const;
@@ -1160,12 +1131,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkDeviceSize countBufferOffset, Uint32 maxDrawCount,
Uint32 stride);
PFNDrawIndexedIndirectCountFunc s_vkCmdDrawIndexedIndirectCount = nullptr;
#if MOBILEGL_BUILD_RECORD_ARM
// P8-D: vkCmdDrawIndirectCount (same signature, same extension), for the wire arm's
// glMultiDrawArraysIndirectCount. The monolith arm never loads it: it reads that count on
// the CPU (DirectVulkan.cpp's MultiDrawArraysIndirectCount).
PFNDrawIndexedIndirectCountFunc s_vkCmdWireDrawIndirectCount = nullptr;
#endif
// VK_EXT_multi_draw entry points, loaded at device creation when the extension
// (and its multiDraw feature) is enabled; null otherwise.
PFN_vkCmdDrawMultiEXT s_vkCmdDrawMultiEXT = nullptr;
@@ -1233,9 +1202,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool BeginXfbCaptureWithBuffers(FrameContext::FrameData& frame, Uint32 count,
const VkBuffer* buffers, const VkDeviceSize* offsets,
const VkDeviceSize* sizes);
#if MOBILEGL_BUILD_RECORD_ARM
Bool BeginWireXfbCaptureForDraw(FrameContext::FrameData& frame);
#endif
Uint32 m_currentDrawXfbBufferCount = 0;
Uint32 m_currentDrawXfbBufferMask = 0;
void EndXfbCaptureForDraw(FrameContext::FrameData& frame, Bool began);
@@ -1383,9 +1350,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint64 programHash = 0;
Uint64 vertexInputHash = 0;
Uint64 renderPassHash = 0;
#if MOBILEGL_BUILD_RECORD_ARM
Uint64 wireRenderPassCompatibilityId = 0;
#endif
// The PRE-HANDLE arm's key component (P2 brief D12.1), and 0 in every entry the
// handle arm mints. VALUE hash of the pipeline-relevant fixed-function state (see
// ComputePipelineStateHash), not the monotonic pipeline-state version: the version
@@ -2158,12 +2123,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool MaterializePendingDepthStencilClearForDefaultFramebuffer(
VkCommandBuffer commandBuffer, const MG_State::GLState::FramebufferAttachmentObject& attachment,
const ClearAttachmentPayload& payload);
#if !MOBILEGL_BUILD_RECORD_ARM
// P7 wave 2-B2 (CONTRACT-P7 §5.2, (B')): the hidden blit program's pipeline. A
// disaggregated build has no hidden program on either arm, so this has neither a body
// nor a caller there.
VkPipeline GetOrCreateBlitPipeline(const RenderPassEntry& renderPassEntry);
#endif
Bool GenerateDepthMipmapWithShader(FrameContext::FrameData& frame,
MG_State::GLState::ITextureObject& texture,
VkTextureManager::TextureResource& resource,
@@ -1,6 +1,5 @@
// Included by VulkanRenderer.cpp inside the backend namespace, after WireFramebuffer.inc.
// The native blit owns no frontend object and never invokes the GL shader compiler.
#if MOBILEGL_BUILD_RECORD_ARM
#include "WireColorBlitSpirv.h"
struct VulkanRenderer::WireColorBlitResources {
@@ -405,4 +404,3 @@
if (destination.isDefault) m_swapchainObject.SetImageContentDefined(destination.swapchainImageIndex, true);
return true;
}
#endif
@@ -35,7 +35,6 @@
// GL's own (logical) rectangles for identity and all three rotations. BlitWireColorImage declines
// any other transform before it chooses a sampler; for one, this answers with the filter's own
// mapping, as before this package.
#if MOBILEGL_BUILD_RECORD_ARM
namespace MobileGL::MG_Backend::DirectVulkan {
// A glBlitFramebuffer rectangle as GL names it: two corners, either order.
struct WireBlitRect {
@@ -63,4 +62,3 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return VK_FILTER_LINEAR;
}
} // namespace MobileGL::MG_Backend::DirectVulkan
#endif
@@ -9,7 +9,6 @@
#pragma once
#include <Config.h>
#if MOBILEGL_BUILD_RECORD_ARM
#include <Includes.h>
#include <MG_Util/Debug/Log.h>
@@ -169,4 +168,3 @@ private:
} // namespace MobileGL::MG_Backend::DirectVulkan
#endif // MOBILEGL_BUILD_RECORD_ARM
@@ -15,7 +15,6 @@
// frontend objects and a GL compiler on the server's apply thread. The baked program below is
// the same shader with the same arithmetic, driven by a push constant and its own descriptor
// set, which is what lets the server generate a depth mip while owning nothing of the client.
#if MOBILEGL_BUILD_RECORD_ARM
#include "WireDepthMipmapSpirv.h"
struct VulkanRenderer::WireDepthMipmapResources {
@@ -326,4 +325,3 @@
RetireWireObjects(Move(retired));
return true;
}
#endif
@@ -21,7 +21,6 @@
#include <cstring>
#include <vulkan/vulkan_core.h>
#if MOBILEGL_BUILD_RECORD_ARM
namespace MobileGL::MG_Backend::DirectVulkan {
#include "WireMultisampleResolveSpirv.h"
@@ -993,4 +992,3 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return measurement;
}
} // namespace MobileGL::MG_Backend::DirectVulkan
#endif
@@ -27,7 +27,6 @@
//
// Disaggregated builds only: the monolith never records either arm, and the pull build's image
// must not change (P7 gate G1).
#if MOBILEGL_BUILD_RECORD_ARM
namespace MobileGL::MG_Backend::DirectVulkan {
// Every aspect a wire depth/stencil format has.
VkImageAspectFlags WireDepthStencilFormatAspects(VkFormat format);
@@ -118,4 +117,3 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint32 graphicsQueueFamilyIndex, const Vector<VkExtensionProperties>& deviceExtensions,
Bool& renderPassArmAvailable, Bool& shaderStencilExport);
} // namespace MobileGL::MG_Backend::DirectVulkan
#endif
@@ -1,5 +1,4 @@
// Server-owned draw/dispatch setup. The legacy setup remains the monolith implementation.
#if MOBILEGL_BUILD_RECORD_ARM
static MagmaProgramSource WireProgramSource(MG_Pipe::MGPipeHandle handle) {
auto& state = MG_Pipe::MGPipeApplier();
const Bool composite = handle.Slot >= MG_Pipe::kMGPipeShaderCsoCompositeSlotBase;
@@ -909,4 +908,3 @@
if (MG_Util::PipeStats::Enabled())
MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::WireIndirectNativeDispatches, 1);
}
#endif
@@ -1,6 +1,5 @@
// P5f fm: included by VulkanRenderer.cpp inside the backend namespace. All inputs here
// belong to the applier; no frontend object, lifetime-id lookup or allocator is used.
#if MOBILEGL_BUILD_RECORD_ARM
// P7 wave 0. THE MESSAGE IS UNCHANGED; where it goes is not. This funnel is how eleven of
// the fifteen P7-marked refusals die (the other four are WireDescriptorFatal's), and until now
// every one of them raised its own abort: no SessionFault frame for the peer, no
@@ -1730,4 +1729,3 @@
}
}
}
#endif
@@ -15,7 +15,6 @@
// shader arm fetches sample 0 - not an average. Two arms that answer differently on two devices
// would make "the wire arm is observably the monolith's" untestable, and GL 4.6 core 18.3.1
// requires a SELECTED sample for stencil anyway.
#if MOBILEGL_BUILD_RECORD_ARM
// The pass's modules, layouts, render passes and pipeline, and the draw it records, are
// WireDepthResolveProbe.cpp's (g5-msprobe): the multisample depth/stencil resolve probe runs
// this exact arm as its CONTROL, so it is defined once for both.
@@ -164,4 +163,3 @@
RetireWireObjects(Move(retired));
return true;
}
#endif
@@ -1,6 +1,5 @@
// UniformManager-owned native descriptors for wire bindings with no image.
// No GL object is constructed or inserted into the client's resource namespace.
#if MOBILEGL_BUILD_RECORD_ARM
void UniformManager::DestroyWirePlaceholderImage(WirePlaceholderImage& image) const {
if (m_device != VK_NULL_HANDLE) {
if (image.clearFramebuffer) vkDestroyFramebuffer(m_device, image.clearFramebuffer, nullptr);
@@ -245,4 +244,3 @@
out = {image.sampler, image.view, VK_IMAGE_LAYOUT_GENERAL};
return true;
}
#endif
@@ -12,7 +12,6 @@
// The pure half of UniformManager's wire placeholder cache (WirePlaceholderImages.inc): WHICH
// placeholder a binding with no usable image takes. A unit test pins it
// (MG_Test/Pipeline/PipelineQuirkTest.cpp); the Vulkan half that makes the image stays in the .inc.
#if MOBILEGL_BUILD_RECORD_ARM
#include <MG_Pipe/MGPipeTypes.h>
namespace MobileGL::MG_Backend::DirectVulkan {
@@ -59,4 +58,3 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return key;
}
} // namespace MobileGL::MG_Backend::DirectVulkan
#endif
@@ -2,7 +2,6 @@
#include <Includes.h>
#include "../VkIncludes.h"
#if MOBILEGL_BUILD_RECORD_ARM
namespace MobileGL::MG_Backend::DirectVulkan {
// The wire draw pass has one graphics subpass, no input attachments,
// multiview or extensions. Its Vulkan pipeline compatibility is formats,
@@ -46,4 +45,3 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint64 m_nextId = 1; // zero selects the native-handle pipeline domain
};
}
#endif
@@ -1,6 +1,5 @@
// Server-owned texture readback. Included after WireFramebuffer.inc so image
// layout tracking and frame retirement are the same as every other wire verb.
#if MOBILEGL_BUILD_RECORD_ARM
static Bool PackWireColorTight(const Uint8* source, SizeT sourceBytes, VkFormat format,
TextureInternalFormat logicalFormat, const MG_Pipe::MGPReadbackInfo& info,
Vector<Uint8>& result) {
@@ -211,4 +210,3 @@
ownedBytes = Move(result);
return true;
}
#endif
+1 -3
View File
@@ -16,10 +16,8 @@
#include <cstdint>
#include <cstring>
#if MOBILEGL_BUILD_RECORD_ARM
#include <Config.h>
#include <MG_Util/Metrics/PipeStats.h>
#endif
namespace MobileGL::MG_Pipe {
const char* MGPipeVerbName(MGPipeVerb verb) {
@@ -49,7 +47,7 @@ namespace MobileGL::MG_Pipe {
// The stamp is the poison's (m_filled, the generations): a build without MOBILEGL_PIPE_POISON
// compiles no read check that could ask any of this. Every transport build has the poison.
#if MOBILEGL_BUILD_RECORD_ARM && MOBILEGL_PIPE_POISON
#if MOBILEGL_PIPE_POISON
// ================================================================================
// P5: the server's verb stamp, the residual-pull counter, and the four-way read verdict
// ================================================================================
-26
View File
@@ -13,14 +13,12 @@
// the live context happens on the client side, in MG_Impl/Pipe/PipeFill.cpp.
#include <MG_State/GLState/Core.h>
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (rv, CONTRACT-P5C.md §5.3): the three texture shutters' server-side answer lives in the
// applier - MGPipeApplierTextureShutterSerial() / MGPipeApplierContextSerial(), declared here
// so the accessors below can answer with them under a server-stamped verb. MG_Pipe is below
// MG_Backend, so this direction is the layering's, and PipeApply.h forward-declares
// PipeInputs rather than including this header, so there is no cycle.
#include <MG_Pipe/PipeApply.h>
#endif
// MOBILEGL_PIPE_POISON: the per-verb generation stamps and the read-side
// Fatal{UnmigratedPipeInput} check. Derived here, once. The repository's debug gate is
@@ -53,7 +51,6 @@ namespace MobileGL::MG_Pipe {
Optional<MGPipeInputField> MGPipeFindInputField(const char* name);
Optional<MGPipeVerb> MGPipeFindVerb(const char* name);
#if MOBILEGL_BUILD_RECORD_ARM
// ---- P5: the split arm of the read check (R-7.2, R-7.3) ------------------------------
//
// A stale read stops being one answer and becomes FOUR, keyed on the field's table-2 class
@@ -87,13 +84,11 @@ namespace MobileGL::MG_Pipe {
// from either counting the same read twice or - worse, because the field's own class would
// not be BARRIER-PULLED - aborting a read the argument row had just declared legal.
Bool MGPipeInputArgumentRead(MGPipeInputField field, Uint32 arg0, MGPipeVerb verb, Bool serverStamped);
#endif
// The read-side poison check, on every non-forwarded accessor. Under MOBILEGL_PIPE_POISON
// a read of a field whose stamp is older than the current verb serial is
// Fatal{UnmigratedPipeInput, "Field@Verb"}; otherwise the accessor is a plain load.
#if MOBILEGL_PIPE_POISON
#if MOBILEGL_BUILD_RECORD_ARM
#define MGP_INPUT_CHECK(Field) \
do { \
if ((m_serverStampedVerb && ::MobileGL::MG_Pipe::MGPipeFieldOwnershipOf(Field) == \
@@ -110,15 +105,6 @@ namespace MobileGL::MG_Pipe {
} \
} while (0)
#else
#define MGP_INPUT_CHECK(Field) \
do { \
if (!::MobileGL::MG_Pipe::MGPipeInputFieldIsFresh(m_filled, (Field))) { \
::MobileGL::MG_Pipe::MGPipeInputPoisonFatalForVerb((Field), m_currentVerb); \
} \
} while (0)
#define MGP_INPUT_CHECK_ARG(Field, Arg0) MGP_INPUT_CHECK(Field)
#endif
#else
#define MGP_INPUT_CHECK(Field) ((void)0)
#define MGP_INPUT_CHECK_ARG(Field, Arg0) ((void)0)
#endif
@@ -266,7 +252,6 @@ namespace MobileGL::MG_Pipe {
#if MOBILEGL_PIPE_POISON
const MGPipeFilledState& FilledState() const { return m_filled; }
#endif
#if MOBILEGL_BUILD_RECORD_ARM
// TRUE between the server's verb-boundary stamp and whoever clears it. It is the
// arming condition of the whole split read path: only inside a server-stamped verb is
// a BARRIER-PULLED read counted rather than Fatal, and only there is a sticky forward
@@ -285,7 +270,6 @@ namespace MobileGL::MG_Pipe {
// case the sticky exemption was written for, would be counted and, under strict, would
// abort. So: PipeApplier clears on leaving the applier. Not optional.
Bool ServerStampedVerb() const { return m_serverStampedVerb; }
#endif
// ---- V: values ----
Int GetActiveTextureUnit() const {
@@ -491,9 +475,7 @@ namespace MobileGL::MG_Pipe {
Uint64 GetSamplingResolutionGeneration() const {
MGP_INPUT_CHECK(MGPipeInputField::GetSamplingResolutionGeneration);
MGP_INPUT_VERIFY_READ(MGPipeInputField::GetSamplingResolutionGeneration, 0, 0);
#if MOBILEGL_BUILD_RECORD_ARM
if (m_serverStampedVerb) return MGPipeApplierTextureShutterSerial();
#endif
return m_samplingResolutionGeneration;
}
const IntVec4& GetScissorBox() const {
@@ -509,21 +491,17 @@ namespace MobileGL::MG_Pipe {
Uint64 GetTextureBindGeneration() const {
MGP_INPUT_CHECK(MGPipeInputField::GetTextureBindGeneration);
MGP_INPUT_VERIFY_READ(MGPipeInputField::GetTextureBindGeneration, 0, 0);
#if MOBILEGL_BUILD_RECORD_ARM
// See GetSamplingResolutionGeneration: the server answers from its own Serial.
if (m_serverStampedVerb) return MGPipeApplierTextureShutterSerial();
#endif
return m_textureBindGeneration;
}
Uint64 GetTextureContextId() const {
MGP_INPUT_CHECK(MGPipeInputField::GetTextureContextId);
MGP_INPUT_VERIFY_READ(MGPipeInputField::GetTextureContextId, 0, 0);
#if MOBILEGL_BUILD_RECORD_ARM
// A context IDENTITY rather than a generation: stable within the served context,
// moved by every MGPipeApplierReset - which is all the backends' per-context memo
// keys ask of it.
if (m_serverStampedVerb) return MGPipeApplierContextSerial();
#endif
return m_textureContextId;
}
Uint64 GetTransformFeedbackCapturedVertices() const {
@@ -757,9 +735,7 @@ namespace MobileGL::MG_Pipe {
#if MOBILEGL_PIPE_POISON
MGPipeFilledState m_filled{};
#endif
#if MOBILEGL_BUILD_RECORD_ARM
Bool m_serverStampedVerb = false;
#endif
// ---- V ----
Int m_activeTextureUnit = 0;
@@ -1019,7 +995,6 @@ namespace MobileGL::MG_Pipe {
// The docs budget ~20 KB; the block is a few KB.
static_assert(sizeof(PipeInputs) < 20 * 1024, "PipeInputs outgrew its budget");
#if MOBILEGL_BUILD_RECORD_ARM
// ============================================================================
// P5: the server-side verb stamp, and the counter that sizes what it leaves behind
// ============================================================================
@@ -1094,7 +1069,6 @@ namespace MobileGL::MG_Pipe {
// object or write into the frontend. This is what puts them in `rsp` and, under
// MOBILEGL_IPC_STRICT_ERRORS=1, makes them Fatal like any other BARRIER-PULLED row.
void MGPipeStickyForwardPull(MGPipeInputField field);
#endif
#if MOBILEGL_PIPE_VERIFY
// PipeInputs.cpp. Per-field equality for the entry compare (P1 brief D8): V by value
@@ -130,7 +130,6 @@ namespace MobileGL::MG_Record {
const MG_Backend::GlobalBackendFunctionsTable* table = Table("blit");
if (table == nullptr) return false;
if (table->GL.BlitFramebuffer == nullptr) return false;
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (hd, CONTRACT-P5C §3.3): the record's handles cross to the backend as the verb's
// own state. The bound form carries two nulls and nothing changes; the named form's
// pair is what the backend's named-blit arm resolves - the sink no longer relies on
@@ -143,12 +142,10 @@ namespace MobileGL::MG_Record {
applierState.VerbBlitDrawFbo = blit.DrawFbo;
const Bool named = !MG_Pipe::MGPipeHandleIsNull(blit.ReadFbo) ||
!MG_Pipe::MGPipeHandleIsNull(blit.DrawFbo);
#endif
table->GL.BlitFramebuffer(blit.SrcX0, blit.SrcY0, blit.SrcX1, blit.SrcY1, blit.DstX0,
blit.DstY0, blit.DstX1, blit.DstY1,
static_cast<GLbitfield>(blit.Mask),
static_cast<GLenum>(blit.Filter));
#if MOBILEGL_BUILD_RECORD_ARM
if (named && !applierState.VerbBlitNamedConsumed) {
MGLOG_E_ONCE("MGPipe: a named blit (read {%u, %u}, draw {%u, %u}) reached a backend "
"with no named-blit arm; the verb is DECLINED rather than applied to "
@@ -158,7 +155,6 @@ namespace MobileGL::MG_Record {
applierState.VerbBlitDrawFbo = MG_Pipe::kMGPipeNullHandle;
return false;
}
#endif
++m_blits;
return true;
}
@@ -303,13 +299,11 @@ namespace MobileGL::MG_Record {
}
Bool RecordVerbSink::ReadTextureImageTight(const MG_Pipe::MGPReadbackInfo& image, Vector<Uint8>& bytes) {
#if MOBILEGL_BUILD_RECORD_ARM
if (m_backend && m_backend->GetBackendType() == BackendType::DirectGLES)
return MG_Backend::DirectGLES::ReadTextureImageWire(image, bytes);
if (m_backend && m_backend->GetBackendType() == BackendType::DirectVulkan &&
MG_Backend::DirectVulkan::pVulkanRenderer)
return MG_Backend::DirectVulkan::pVulkanRenderer->ReadTextureImageWire(image, bytes);
#endif
(void)image;
(void)bytes;
return false;
@@ -535,7 +529,6 @@ namespace MobileGL::MG_Record {
// with a span (our client stages client indices into an owned element buffer and never sets
// kDrawHasUserIndices) and a multi-draw that claims instancing (no GL entry point produces
// one; the client never sends it). Only a foreign peer reaches either.
#if MOBILEGL_BUILD_RECORD_ARM
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.
@@ -595,7 +588,6 @@ namespace MobileGL::MG_Record {
return true;
}
} // namespace
#endif
Bool RecordVerbSink::OnDrawVbo(const MG_Pipe::MGPDrawInfo& info,
const MG_Pipe::MGPDrawRange* ranges,
@@ -619,11 +611,9 @@ namespace MobileGL::MG_Record {
}
++m_drawRecords;
#if MOBILEGL_BUILD_RECORD_ARM
// Ruling 19 / ID-95: the window promise, checked at every draw, before the backend is
// asked to resolve anything out of the windows.
if (!CheckUnitWindows(MG_Pipe::MGPipeApplier(), "draw_vbo")) return false;
#endif
const MG_Backend::GlobalBackendFunctionsTable* table = Table("draw_vbo");
if (table == nullptr) return false;
const MG_Backend::GLFunctionsTable& gl = table->GL;
@@ -645,7 +635,6 @@ namespace MobileGL::MG_Record {
if (indirect != nullptr) {
// The layout already refused a record that sets both flags or declares ranges
// beside the block, so NumDraws is 0 and there is no span here.
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (hd, CONTRACT-P5C §3.5): the command/parameter buffer handles cross as the
// verb's own state; the backend's indirect arm resolves the buffer twins from
// them instead of reading the client's GL_DRAW_INDIRECT_BUFFER binding slot.
@@ -653,7 +642,6 @@ namespace MobileGL::MG_Record {
applierState.ClearVerbHandles();
applierState.VerbIndirectBuffer = indirect->Buffer;
applierState.VerbIndirectParameterBuffer = indirect->ParameterBuffer;
#endif
const auto offset = reinterpret_cast<const void*>(static_cast<std::uintptr_t>(indirect->Offset));
const auto drawCount = static_cast<GLsizei>(indirect->DrawCount);
const auto stride = static_cast<GLsizei>(indirect->Stride);
@@ -843,10 +831,8 @@ namespace MobileGL::MG_Record {
// identical - and it is also the honest statement of the debt, which `rsp` counts.
Bool RecordVerbSink::OnLaunchGrid(const MG_Pipe::MGPGridInfo& grid) {
#if MOBILEGL_BUILD_RECORD_ARM
// Ruling 19 / ID-95: a dispatch samples through the same unit windows a draw does.
if (!CheckUnitWindows(MG_Pipe::MGPipeApplier(), "launch_grid")) return false;
#endif
const MG_Backend::GlobalBackendFunctionsTable* table = Table("launch_grid");
if (table == nullptr) return false;
const MG_Backend::GLFunctionsTable& gl = table->GL;
@@ -868,14 +854,12 @@ namespace MobileGL::MG_Record {
// IndirectBuffer travels for P7's sake; the BINDING is server state, put there by
// the set_buffer_bindings record that preceded this one, exactly as OnClear's Fbo
// is not re-resolved here. glDispatchComputeIndirect takes only the offset.
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (hd, CONTRACT-P5C §3.5): the buffer handle itself is now also the verb's own
// state, so the backend's dispatch-indirect arm resolves the twin from the record
// rather than from the client's GL_DISPATCH_INDIRECT_BUFFER binding slot.
auto& applierState = MG_Pipe::MGPipeApplier();
applierState.ClearVerbHandles();
applierState.VerbDispatchIndirectBuffer = grid.IndirectBuffer;
#endif
gl.DispatchComputeIndirect(static_cast<GLintptr>(grid.IndirectOffset));
} else {
if (gl.DispatchCompute == nullptr) return false;
@@ -1116,7 +1100,6 @@ namespace MobileGL::MG_Record {
Bool RecordVerbSink::OnGenerateMipmap(const MG_Pipe::MGPMipPlan& plan) {
const auto* table = Table("GenerateMipmap");
if (table == nullptr || table->GL.GenerateMipmap == nullptr) return false;
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (hd, CONTRACT-P5C §3.2): the texture the client resolved at Target on the active
// unit crosses as the verb's own state; the backend's mip-descriptor check resolves
// the record from it instead of probing the client allocator for the bound object's
@@ -1126,7 +1109,6 @@ namespace MobileGL::MG_Record {
applierState.VerbMipRes = plan.Res;
applierState.VerbMipBaseLevel = plan.BaseLevel;
applierState.VerbMipLevelCount = plan.LevelCount;
#endif
table->GL.GenerateMipmap(plan.Target);
return true;
}
@@ -1134,14 +1116,12 @@ namespace MobileGL::MG_Record {
Bool RecordVerbSink::OnCopyFramebufferToTexture(const MG_Pipe::MGPCopyFromFramebuffer& copy) {
const auto* table = Table(copy.SubImage ? "CopyTexSubImage2D" : "CopyTexImage2D");
if (table == nullptr) return false;
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (hd, CONTRACT-P5C §3.4): the destination texture the client resolved at the
// active unit crosses as the verb's own state; the backend resolves its twin from the
// handle instead of reading the client's texture-unit binding slot (T4).
auto& applierState = MG_Pipe::MGPipeApplier();
applierState.ClearVerbHandles();
applierState.VerbCopyTexDst = copy.Dst;
#endif
if (copy.SubImage) {
if (table->GL.CopyTexSubImage2D == nullptr) return false;
table->GL.CopyTexSubImage2D(copy.Target, copy.Level, copy.XOffset, copy.YOffset,
@@ -114,9 +114,7 @@
#include <mutex>
#include <limits>
#if MOBILEGL_BUILD_RECORD_ARM
#include <Config.h>
#endif
namespace MobileGL::MG_Record {
@@ -355,7 +353,6 @@ namespace MobileGL::MG_Record {
const std::lock_guard<std::mutex> lock(m_mutex);
LevelShadow& shadow = m_shadows[bucketed].Levels[PackLevel(uploadTarget, level)];
RequireDeclaredLevel(shadow, key, uploadTarget, level, extent);
#if MOBILEGL_BUILD_RECORD_ARM
// A run can be the first piece of a redefined level. Old coverage
// belongs to the previous coordinate system even when the new run
// happens to touch its old range; retaining it invents bytes that
@@ -369,7 +366,6 @@ namespace MobileGL::MG_Record {
shadow.Alias = nullptr;
shadow.Covered.clear();
}
#endif
if (shadow.HoldsFollowedCopy) {
// P8-E: a run landed whole over a followed copy (AdoptClientBoxes is the spelling
// that keeps it), so the bytes no longer hold what the driver does.
@@ -1030,7 +1026,6 @@ namespace MobileGL::MG_Record {
std::atomic<Bool> m_any{false};
};
#if MOBILEGL_BUILD_RECORD_ARM
// ONE PER PROCESS, and its copying arm is decided ONCE at first use - StagedShadow's
// ServerStaged() ruling verbatim: the two arms hold the authoritative bytes in DIFFERENT
// places, so an answer that changed mid-run would strand every level already staged.
@@ -1043,6 +1038,5 @@ namespace MobileGL::MG_Record {
*new StagedTextureStore(MG_Config::Transport != MG_Config::TransportMode::Monolith);
return store;
}
#endif
} // namespace MobileGL::MG_Record
@@ -12,9 +12,7 @@
#include <MG_State/EGLState/Core.h>
#include <MG_Backend/BackendObjects.h>
#include <MG_Impl/Pipe/PipeFill.h>
#if MOBILEGL_BUILD_RECORD_ARM
#include <MG_Impl/Pipe/Verb/GpuWriteSet.h>
#endif
// CONTRACT-P5.md §7 / ID-14: a null check on a GLFunctionsTable slot may not survive into the
// client under split - it becomes a caps-mirror read. SlotCaps.h carries the rule and the test
// that decides which of its two spellings a site takes; in a pull build both expand to exactly
@@ -1332,7 +1330,6 @@ namespace MobileGL::MG_Impl::GLImpl {
static_cast<Uint>(bufferIndex));
const auto& buffer = bindingPoint.GetBoundObject();
if (buffer == nullptr) continue;
#if MOBILEGL_BUILD_RECORD_ARM
// This fixup READS the captured bytes back through the shadow, so it is the one
// consumer that cannot simply inherit the deferral EndTransformFeedback's dropped
// fence introduces. Under split it pays the reconciliation itself, which is the
@@ -1345,7 +1342,6 @@ namespace MobileGL::MG_Impl::GLImpl {
// caller still runs, so the readback it emits is pure new work on the monolith
// path and integration-gpu cannot see it.
if (MG_Config::DataArmIsRecord()) buffer->SyncGpuWrites();
#endif
const Range1D range = bindingPoint.GetRange();
const Uint8* mapped = buffer->MappedData();
if (mapped == nullptr) continue;
@@ -1386,12 +1382,10 @@ namespace MobileGL::MG_Impl::GLImpl {
MGP_FILL(EndTransformFeedback);
endXfb();
}
#if MOBILEGL_BUILD_RECORD_ARM
// P5 (b1), the second producer the client-side GPU-write set ADDS, and it has to be
// taken HERE - before GLContext::EndTransformFeedback clears the live bindings, since
// a mark taken after it marks nothing.
MG_Record::MarkEndTransformFeedbackCaptureTargets();
#endif
MG_State::pGLContext->EndTransformFeedback();
// Captured results must be visible to MapBuffer/GetBufferSubData after
// End; the capture targets are host-coherent GPU memory, so completing
@@ -49,11 +49,7 @@
// SyncGpuWrites, exactly as glMapBufferRange and glGetBufferSubData already do. Monolith keeps its
// read as it was: its ReadPixels maps the driver PBO back into the shadow inside the call, and the
// pull build compiles this to nothing (G1).
#if MOBILEGL_BUILD_RECORD_ARM
#define MGL_SYNC_PIXEL_UNPACK_SOURCE(buffer) do { if (MG_Config::DataArmIsRecord()) (buffer)->SyncGpuWrites(); } while (0)
#else
#define MGL_SYNC_PIXEL_UNPACK_SOURCE(buffer) ((void)0)
#endif
namespace MobileGL::MG_Impl::GLImpl {
static SharedPtr<MG_State::GLState::ITextureObject> nullTextureObject;
@@ -6121,10 +6117,8 @@ namespace MobileGL::MG_Impl::GLImpl {
viewObject->SetInternalFormat(viewInternalFormat);
viewObject->SetSamples(storageOwner->GetSamples());
viewObject->SetFixedSampleLocations(storageOwner->HasFixedSampleLocations());
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord())
MG_Pipe::MGPipeEmitSamplerViewCreate(*viewObject);
#endif
}
void TexStorage1D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width) {
@@ -6586,14 +6580,12 @@ namespace MobileGL::MG_Impl::GLImpl {
static void GetTextureImageForUploadTarget(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
TextureUploadTarget uploadTarget, GLint level, GLenum format,
GLenum type, GLsizei bufSize, void* pixels, const char* caller) {
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord()) {
MGP_FILL(GetTextureImage);
MG_Backend::gBackendFunctionsTable.GL.GetTextureImage(textureObject, uploadTarget, level, format, type,
bufSize, pixels);
return;
}
#endif
if (MG_Backend::pActiveBackendObject != nullptr &&
MG_Backend::pActiveBackendObject->GetBackendType() == BackendType::DirectVulkan &&
MGL_BACKEND_SLOT_LOCAL(GetTextureImage)) {
@@ -6614,12 +6606,10 @@ namespace MobileGL::MG_Impl::GLImpl {
return;
}
auto uploadTarget = GetPrimaryUploadTarget(textureObject);
#if MOBILEGL_BUILD_RECORD_ARM
// Unlike glGetTexImage's face target, this DSA call reads all six faces.
if (MG_Config::DataArmIsRecord() &&
textureObject->GetTarget() == TextureTarget::TextureCubeMap)
uploadTarget = TextureUploadTarget::Unknown;
#endif
GetTextureImageForUploadTarget(textureObject, uploadTarget, level, format, type,
bufSize, pixels, __func__);
}
@@ -6880,12 +6870,10 @@ namespace MobileGL::MG_Impl::GLImpl {
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) {
if (!GetTexImage_State(target, level, format, type, pixels)) return;
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord()) {
GetTexImage_Backend(target, level, format, type, pixels);
return;
}
#endif
if (MGL_BACKEND_SLOT_LOCAL(GetTexImage)) {
GetTexImage_Backend(target, level, format, type, pixels);
return;
-2
View File
@@ -9,7 +9,6 @@
// Client memory becomes ordinary owned buffer resources before a draw.
#pragma once
#if MOBILEGL_BUILD_RECORD_ARM
#include <MG_Impl/Pipe/ClientFetchPlan.h>
#include <MG_Impl/Pipe/VertexInputEmit.h>
#include <MG_State/GLState/BufferState/BufferObject.h>
@@ -207,4 +206,3 @@ namespace MobileGL::MG_Pipe {
Bool m_indexBindingChanged = false;
};
} // namespace MobileGL::MG_Pipe
#endif
+1 -25
View File
@@ -40,7 +40,7 @@
#include <MG_Pipe/PipeRoute.h>
#include <MG_Pipe/PipeMutation.h>
#include <Config.h>
#if MOBILEGL_BUILD_RECORD_ARM && MOBILEGL_PIPE_VERIFY
#if MOBILEGL_PIPE_VERIFY
#include <MG_Impl/Pipe/Verb/VerbPort.h>
#endif
@@ -96,7 +96,6 @@ namespace MobileGL::MG_Pipe {
return inputs.m_currentVertexAttribute;
}
#if MOBILEGL_BUILD_RECORD_ARM
// P5e (ra, CONTRACT-P5E §2.3). THE FOUR O-CLASS ROWS, AND ONLY THEY: these are the only
// members of this block that own a frontend object rather than point into one, so they
// are the only ones through which the apply thread can become a last owner. The raw
@@ -108,7 +107,6 @@ namespace MobileGL::MG_Pipe {
inputs.m_programForDraw.reset();
inputs.m_transformFeedbackProgram.reset();
}
#endif
static void CopyField(PipeInputs& dst, GLContext& ctx, MGPipeInputField field) {
using F = MGPipeInputField;
@@ -691,7 +689,6 @@ namespace MobileGL::MG_Pipe {
if (&self != &gPipeInputs || !g_verify.Enabled) return;
const auto index = static_cast<SizeT>(field);
if (kMGPipeInputFieldSticky[index]) return;
#if MOBILEGL_BUILD_RECORD_ARM
// P13 W5: the monolith verb port applies records without a server stamp, and its readback
// forces the neutral pack around the backend's read exactly as the server's does.
if ((self.ServerStampedVerb() || MG_Record::MonolithPortApplyingForVerify()) &&
@@ -711,7 +708,6 @@ namespace MobileGL::MG_Pipe {
}
return;
}
#endif
if (g_verify.InHook) return;
auto* ctx = LiveContext();
if (ctx == nullptr) return;
@@ -1106,7 +1102,6 @@ namespace MobileGL::MG_Pipe {
// one was emitted, so this cannot be false - but a zeroed record (null
// handle, size 0) is not the answer if that pre-pass is ever relaxed.
if (!MGPipeBuildSubDataRecord(handle, at, length, record, /*verbatimShadow=*/true)) return;
#if MOBILEGL_BUILD_RECORD_ARM
// P5 (b1): the live-host-writes bit rides the content record, because
// "someone may be writing these bytes without telling you" is a fact about the
// CONTENT and not about the storage. It is set from the object's PUBLISHED
@@ -1114,7 +1109,6 @@ namespace MobileGL::MG_Pipe {
// edge that announced it can never disagree. MGPipeBuildSubDataRecord does not
// take the object, which is why it is set here and not in the builder.
record.HasLiveHostWrites = buffer.HasLiveHostWritesForWire() ? 1 : 0;
#endif
// `length` is this chunk's byte count, which the record also declares
// (MGPipeBuildSubDataRecord writes it into the destination range) - passed
// rather than re-read so the staged run and the record's own claim come from
@@ -1140,7 +1134,6 @@ namespace MobileGL::MG_Pipe {
// store, or the pattern FillSubData expanded locally, and neither is this
// client's untransformed shadow of the level.
if (!MGPipeBuildSubDataRecord(handle, at, length, record, /*verbatimShadow=*/false)) return;
#if MOBILEGL_BUILD_RECORD_ARM
// P5 (b1): THE SECOND CONTENT EMITTER, and it has to speak for the same reason
// the first does. ApplyBufferWrite ASSIGNS the bit - a content record emitted
// while nothing maps the buffer is how the state goes back to false - so a
@@ -1148,7 +1141,6 @@ namespace MobileGL::MG_Pipe {
// map. `glBufferSubData` against a persistently mapped arena is legal and is
// the ordinary Flywheel/Create shape, so that is not a corner.
record.HasLiveHostWrites = buffer.HasLiveHostWritesForWire() ? 1 : 0;
#endif
// The application's STAGING store, valid for the duration of the call only.
MGPipeRouteBufferSubDataResident(record, base + (at - offset), length);
// P7 wave 2 package C, OQ-10: `rsd=`, counted HERE rather than at the call
@@ -2270,11 +2262,9 @@ namespace MobileGL::MG_Pipe {
}
Bool PipeInputs::HasOpenTransformFeedbackSpan(Uint64 lifetimeId) const {
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord()) {
return lifetimeId != 0 && MGPipeApplier().StreamOutputSpans.count(lifetimeId) != 0;
}
#endif
MGP_STICKY_FORWARD_PULL(HasOpenTransformFeedbackSpan);
const auto* ctx = LiveContext();
@@ -2369,9 +2359,7 @@ namespace MobileGL::MG_Pipe {
// stamped, so every stamp this verb made falls behind the serial.
++MGPipeFillAccess::Filled(inputs).CurrentVerbSerial;
#endif
#if MOBILEGL_BUILD_RECORD_ARM
MGPipeClientClearVerbBoundary();
#endif
MGPipeFillAccess::SetVerb(inputs, MGPipeVerb::kVerbCount);
// The pending base instance belongs to the verb that was about to run, so leaving
// one drops it.
@@ -2917,9 +2905,7 @@ namespace MobileGL::MG_Pipe {
Bool ApplierDerives = false;
Bool ContextValuesWireLive = false;
Bool P4aConsumer = false;
#if MOBILEGL_BUILD_RECORD_ARM
Uint64 CapsGeneration = 0;
#endif
// One bit per FIELD - not per verb class. The class mask is applied at the walk
// exactly as it always was, so "does this verb read this field" stays the walk's
// business and this stays a statement about EMISSION alone.
@@ -3125,9 +3111,7 @@ namespace MobileGL::MG_Pipe {
// `g_attribDefaultLastHeader`, which is what it just published; there is nothing
// the mirror could add that the wire does not already carry. A build that ever
// needs the repair arm again has to earn it with a barriered row.
#if MOBILEGL_BUILD_RECORD_ARM
if (ClientRunsAhead()) return sizeof(MGPVertexAttribDefaults) + header.Count * sizeof(MGPAttribValue);
#endif
const auto* mirror = MGPipeFillAccess::VertexAttribDefaultsOf(MGPipeClientInputs());
Bool reproduced = true;
for (SizeT i = 0; i < kAttribs && reproduced; ++i) {
@@ -3144,7 +3128,6 @@ namespace MobileGL::MG_Pipe {
return sizeof(MGPVertexAttribDefaults) + header.Count * sizeof(MGPAttribValue);
}
#if MOBILEGL_BUILD_RECORD_ARM
// set_context_values (P5c rv, CONTRACT-P5C.md §5.3): the residual-value record. One
// POD carrying every value-class field no other set_* supplies - the two texture-unit
// counters, the 15 per-target touched-buffer-binding counts and the five XFB values -
@@ -3201,7 +3184,6 @@ namespace MobileGL::MG_Pipe {
MGPipeFieldMaskHas(mask, MGPipeInputField::GetBoundTransformFeedbackLifetimeId) ||
MGPipeFieldMaskHas(mask, MGPipeInputField::GetTransformFeedbackCapturedVertices);
}
#endif
@@ -3403,7 +3385,6 @@ namespace MobileGL::MG_Pipe {
// is the CLIENT-role block and gPipeInputs is the server's alone; off, or under
// monolith transport, it folds back onto gPipeInputs and nothing below changes.
PipeInputs& inputs = MGPipeClientInputs();
#if MOBILEGL_BUILD_RECORD_ARM
// ---- P5e (ra), CONTRACT-P5E §3: WHO OWNS gPipeInputs FOR THIS VERB ----------------
//
// Under run-ahead the block is SERVER-ROLE MEMORY for an unbarriered record: the
@@ -3418,7 +3399,6 @@ namespace MobileGL::MG_Pipe {
// every verb) and the guard below then fires Fatal{RoleViolation, "gPipeInputs"} on
// the first unbarriered verb, by name.
const Bool runAhead = ClientRunsAhead();
#endif
ParsePoisonOmissionKnob();
#if MOBILEGL_PIPE_VERIFY
ArmVerify();
@@ -3479,7 +3459,6 @@ namespace MobileGL::MG_Pipe {
// Fatal{UnmigratedPipeInput, "<Field>@<none>"} rather than default storage.
++MGPipeFillAccess::Filled(inputs).CurrentVerbSerial;
#endif
#if MOBILEGL_BUILD_RECORD_ARM
// The client is filling, so whatever the server stamped at its last verb boundary
// is withdrawn: the stamps below are the CLIENT's again and a stale read is a
// defect, not a residual pull. Disarming here rather than at the end of the
@@ -3499,7 +3478,6 @@ namespace MobileGL::MG_Pipe {
// (PipeApplier::LeaveApplier) - which is CONTRACT-P5E §3.2's per-role stamp
// ownership, landed as the dual block rather than as moved fields.
MGPipeClientClearVerbBoundary();
#endif
MGPipeFillAccess::SetVerb(inputs, verb);
}
#if MOBILEGL_PIPE_POISON
@@ -3742,7 +3720,6 @@ namespace MobileGL::MG_Pipe {
payloadBytes += EmitVertexAttribDefaults(*ctx, tracker.FreshlyPrimed());
}
#if MOBILEGL_BUILD_RECORD_ARM
// P5c rv (CONTRACT-P5C.md §5.3): the residual-value record, emitted when any covered
// value moved. THE GATE IS THE SUBSYSTEM BIT PLUS THE WIRE BEING LIVE - the family has
// no dirty bit (NoDirtyBitOwnsTheResidualSubsystem) and no P4a consumer predicate (it
@@ -3753,7 +3730,6 @@ namespace MobileGL::MG_Pipe {
VerbMaskReadsContextValues(mask)) {
payloadBytes += EmitContextValues(*ctx);
}
#endif
// P3a's vertex segment, in the order the design fixes: vertex elements, then the
// vertex buffers that fill them, then the index binding. All three are LIVE now (m1):
-2
View File
@@ -37,9 +37,7 @@
// detection. Neither is reproduced here and neither may be.
//
// HEADER-ONLY, for the ownership reason Tracker.h and ResourceTracker.h both state.
#if MOBILEGL_BUILD_RECORD_ARM
#include <MG_Impl/EGLImpl/EGLImpl.h>
#endif
#include <MG_Impl/Pipe/SetHashSuppressor.h>
#include <MG_Impl/Pipe/SlotAllocator.h>
#include <MG_Impl/Pipe/Tracker.h>
-2
View File
@@ -899,7 +899,6 @@ namespace MobileGL::MG_Pipe {
MGPRespecifiedLevel key{};
key.UploadTarget = packedTarget;
key.Level = static_cast<Uint16>(firstLevel + i);
#if MOBILEGL_BUILD_RECORD_ARM
const auto* mipmap = MG_State::GLState::AsMipmapTexture(&texture);
if (mipmap == nullptr) {
MGLOG_E_ONCE("MGPipe: a per-level texture respecify had no mipmap storage; it was not emitted");
@@ -910,7 +909,6 @@ namespace MobileGL::MG_Pipe {
key.Width = static_cast<Uint32>(exactLevelExtent.x());
key.Height = static_cast<Uint32>(exactLevelExtent.y());
key.Depth = static_cast<Uint32>(exactLevelExtent.z());
#endif
accepted = RespecifyOnce(texture, handle, entry, desc, &key, viewOf, bufferHandle, bufOffset,
bufSize);
if (!accepted) break;
-4
View File
@@ -382,19 +382,15 @@ namespace MobileGL::MG_Pipe {
for (SizeT i = 0; i < kAttribs; ++i) {
m_attributes[i] = MGPipeBuildVertexAttribWire(vao.GetAttribute(static_cast<Uint>(i)),
vao.GetAttributeBindingIndex(static_cast<Uint>(i)));
#if MOBILEGL_BUILD_RECORD_ARM
// Client addresses never cross the transport. Draw emission snapshots
// their referenced elements into owned buffers with a zero byte origin.
if (MG_Config::DataArmIsRecord() &&
!vao.GetAttribute(static_cast<Uint>(i)).Buffer) m_attributes[i].Offset = 0;
#endif
}
for (SizeT i = 0; i < kBindings; ++i) {
m_bindingPoints[i] = MGPipeBuildVertexBindingPointWire(vao.GetBindingPoint(static_cast<Uint>(i)));
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord() &&
!vao.GetBindingPoint(static_cast<Uint>(i)).Buffer) m_bindingPoints[i].Offset = 0;
#endif
}
// Attributes first, then binding points, both ascending and contiguous.
constexpr SizeT kAttribBytes = kAttribs * sizeof(MGPVertexAttribWire);
+14 -22
View File
@@ -1939,9 +1939,7 @@ endif()
# not true. All this file has to decide now is whether that peek can be COMPILED, which is exactly
# "did this build compile MG_Remote", which is exactly the build option.
# P13 W5: the record arm's half of the peek (dataArmIsRecord) needs no transport.
if (MOBILEGL_BUILD_RECORD_ARM)
target_compile_definitions(MobileGLIntegrationTest PRIVATE -DMGITEST_RECORD_ARM_PEEK=1)
endif()
target_compile_definitions(MobileGLIntegrationTest PRIVATE -DMGITEST_RECORD_ARM_PEEK=1)
if (MOBILEGL_BUILD_DISAGGREGATED)
target_compile_definitions(MobileGLIntegrationTest PRIVATE -DMGITEST_SPLIT_RUNTIME_PEEK=1)
message(STATUS "Integration tests: the DirectGLES.Split. lanes arm from the RUNNING PROCESS - "
@@ -1965,25 +1963,19 @@ if (MOBILEGL_BUILD_DISAGGREGATED AND DEFINED MOBILEGL_LOG_ACTIVE_LEVEL
endif()
# Package b1's client-side persistent-map tracker, which is what Harness/PersistentMapPeek.cpp
# asks the membership question of (b1-v1.md 4.1 item 2). TWO halves, and both are needed:
# MOBILEGL_BUILD_RECORD_ARM because a build that never compiled the record arm cannot LINK the call
# (P13 W5: the tracker is record core, MG_Record, and no longer needs MG_Remote), and the content
# probe because b1 may not have landed it yet. __has_include in the peek would answer yes in a
# build without it - the header is in the source tree of every build - and turn a healthy build
# into a link error, which is why this decision is made here.
if (MOBILEGL_BUILD_RECORD_ARM)
mgl_itest_probe_for_symbol(MGL_ITEST_PERSISTENT_MAP_TRACKER
"${MGL_ITEST_ROOT}/MobileGL/MG_State/GLState/BufferState" "IsLivePersistentMap")
if (MGL_ITEST_PERSISTENT_MAP_TRACKER)
message(STATUS "Integration tests: package b1's persistent-map tracker is present "
"(${MGL_ITEST_PERSISTENT_MAP_TRACKER}) - PersistentCoherentMapScenario's "
"membership assertion is live")
target_compile_definitions(MobileGLIntegrationTest PRIVATE -DMGITEST_PERSISTENT_MAP_TRACKER=1)
else()
message(STATUS "Integration tests: no source under MobileGL/MG_State/GLState/BufferState names "
"IsLivePersistentMap - package b1's tracker has not landed, so "
"PersistentCoherentMapScenario's membership assertion SKIPS")
endif()
# asks the membership question of (b1-v1.md 4.1 item 2). Every library builds it since P13 W5 (the
# tracker is record core, MG_Record); the content probe stays so a tree without it skips by name.
mgl_itest_probe_for_symbol(MGL_ITEST_PERSISTENT_MAP_TRACKER
"${MGL_ITEST_ROOT}/MobileGL/MG_State/GLState/BufferState" "IsLivePersistentMap")
if (MGL_ITEST_PERSISTENT_MAP_TRACKER)
message(STATUS "Integration tests: package b1's persistent-map tracker is present "
"(${MGL_ITEST_PERSISTENT_MAP_TRACKER}) - PersistentCoherentMapScenario's "
"membership assertion is live")
target_compile_definitions(MobileGLIntegrationTest PRIVATE -DMGITEST_PERSISTENT_MAP_TRACKER=1)
else()
message(STATUS "Integration tests: no source under MobileGL/MG_State/GLState/BufferState names "
"IsLivePersistentMap - package b1's tracker has not landed, so "
"PersistentCoherentMapScenario's membership assertion SKIPS")
endif()
# ARCHITECTURE.md:543 - every new ctest ENVIRONMENT and add_trace_replay_test's SPLIT branch
-2
View File
@@ -76,7 +76,6 @@ namespace MobileGL::MG_Pipe {
// Null-initialized: a backend that installs nothing sends nothing.
inline MGPipeCallbacks gMGPipeCallbacks{};
#if MOBILEGL_BUILD_RECORD_ARM
// HOW MUCH OF ITSELF ONE REVERSE-CHANNEL RECORD MAY BE, and it is not a tuning knob: a
// producer that ignores it kills the server process.
//
@@ -113,5 +112,4 @@ namespace MobileGL::MG_Pipe {
const Uint64 quarter = capacity / 4;
return quarter < 4096 ? 4096 : quarter;
}
#endif
} // namespace MobileGL::MG_Pipe
-8
View File
@@ -463,25 +463,17 @@ namespace MobileGL::MG_Pipe {
desc.HasRespecifiedLevel = 1;
desc.RespecifiedUploadTarget = uploadTarget;
desc.RespecifiedLevel = level;
#if MOBILEGL_BUILD_RECORD_ARM
// Non-buffer texture targets leave this descriptor range unused. A named-level
// respecify repurposes it as exact W/H/D; TexBuffer cannot legally name a mip level.
desc.BufOffset = (static_cast<Uint64>(width) << 32) | static_cast<Uint64>(height);
desc.BufSize = depth;
#else
(void)width;
(void)height;
(void)depth;
#endif
}
inline constexpr void MGPipeClearRespecifiedLevel(MGPResourceDesc& desc) {
#if MOBILEGL_BUILD_RECORD_ARM
if (desc.HasRespecifiedLevel != 0 &&
desc.Target != static_cast<Uint8>(MGPipeResourceTarget::TexBuffer)) {
desc.BufOffset = 0;
desc.BufSize = 0;
}
#endif
desc.HasRespecifiedLevel = 0;
desc.RespecifiedUploadTarget = 0;
desc.RespecifiedLevel = 0;
-55
View File
@@ -16,11 +16,9 @@
#include <MG_Backend/MGPipe/PipeInputs.h>
#if MOBILEGL_BUILD_RECORD_ARM
// MGPipeUnmigratedEmulation's split arm reads MG_Config::Transport, which is what tells an
// emulation site whether it is running inside a server or in the monolith it was written for.
#include <Config.h>
#endif
#if MOBILEGL_PIPE_VERIFY
// THE ONE PLACE THE PROGRAM ARCHIVE'S CODEC IS CALLED, and it is compiled into the VERIFY
@@ -53,9 +51,7 @@
#include <MG_Remote/Server/ServerSession.h>
#include <MG_Remote/CapsCodec.h>
#endif
#if MOBILEGL_BUILD_RECORD_ARM
#include <MG_Backend/Record/StagedTextureStore.h>
#endif
#include <algorithm>
#include <atomic>
@@ -209,7 +205,6 @@ namespace MobileGL::MG_Pipe {
return g_clientLossConfirmer != nullptr && g_clientLossConfirmer(waitMs);
}
#if MOBILEGL_BUILD_RECORD_ARM
// ----------------------------------------------------------------------------
// THE BACKEND-SIDE SESSION-FAIL SEAM (P7 wave 0). PipeSessionFail.h holds the argument;
// this is the whole implementation.
@@ -250,9 +245,7 @@ namespace MobileGL::MG_Pipe {
std::abort();
}
#endif // MOBILEGL_BUILD_RECORD_ARM
#if MOBILEGL_BUILD_RECORD_ARM
namespace {
MGPipeSessionLatchHook g_sessionLatchHook = nullptr;
MGPipeSessionLatchArmedHook g_sessionLatchArmedHook = nullptr;
@@ -267,7 +260,6 @@ namespace MobileGL::MG_Pipe {
bool MGPipeSessionLatchArmed() { return g_sessionLatchArmedHook != nullptr && g_sessionLatchArmedHook(); }
#endif // MOBILEGL_BUILD_RECORD_ARM
#if MOBILEGL_BUILD_DISAGGREGATED
bool MGPipeDebugDeviceLossDue() {
@@ -349,7 +341,6 @@ namespace MobileGL::MG_Pipe {
#endif // MOBILEGL_BUILD_DISAGGREGATED
#if MOBILEGL_BUILD_RECORD_ARM
bool MGPipeSessionLatch(MGPipeFatalFamily family, const char* fmt, ...) {
char line[512];
va_list args;
@@ -364,7 +355,6 @@ namespace MobileGL::MG_Pipe {
// NO HOOK: no session to latch, so the death the fail seam would have given.
MGPipeSessionFail(family, "%s", line);
}
#endif
namespace {
// ----------------------------------------------------------------------------
@@ -1343,28 +1333,7 @@ namespace MobileGL::MG_Pipe {
return true;
}
#if MOBILEGL_PIPE_VERIFY && !MOBILEGL_BUILD_RECORD_ARM
// D-A4's pin, AND P5 (b1) IS THE PHASE IT WAS WAITING FOR. It said "HasLiveHostWrites
// is always false in this phase and is written by nobody: it exists so the phase that
// pushes persistent-mapped host writes can set it with no new record kind", and a
// verify build refused to let such a producer arrive unannounced.
//
// The producer is announced: MGPSubData::HasLiveHostWrites, set by
// MGPipeEmitResourceSubData from BufferObject::HasLiveHostWritesForWire and read by
// ApplyBufferWrite below. So the pin is LIFTED FOR A SPLIT BUILD ONLY, and left
// standing everywhere else - in a monolith build nothing sets the bit and the wire is
// still the thing that would say so if something started to. That is the whole value
// of the pin and it survives the phase it was written for.
void PinNoLiveHostWrites(const MGPipeResourceRecord& record, MGPipeHandle res, const char* call) {
if (!record.HasLiveHostWrites) return;
MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("PipeLiveHostWrites")
" %s {slot=%u, gen=%u}: the resource record says host writes are live, and "
"no path in this phase may set that",
call, res.Slot, res.Gen);
}
#else
void PinNoLiveHostWrites(const MGPipeResourceRecord&, MGPipeHandle, const char*) {}
#endif
#if MOBILEGL_PIPE_VERIFY
// P5's pin, RELAXED BY P7 WAVE 3 (CONTRACT-P7 §6) - and relaxed rather than deleted,
@@ -1492,7 +1461,6 @@ namespace MobileGL::MG_Pipe {
return false;
}
PinNoLiveHostWrites(*stored, record.Res, call);
#if MOBILEGL_BUILD_RECORD_ARM
// P5 (b1): THE PRODUCER. The record's own statement about the resource, applied
// before the serial moves so that a probe re-entered from inside the backend hook
// below already sees it. It is an assignment and not an OR: the bit is a STATE,
@@ -1501,7 +1469,6 @@ namespace MobileGL::MG_Pipe {
// (BufferObject::NotePersistentMapStateChanged) rather than relying on the next
// ordinary write to arrive.
stored->HasLiveHostWrites = record.HasLiveHostWrites != 0;
#endif
// THE SERIAL MOVES BEFORE THE BACKEND IS TOLD, and that order is load-bearing:
// the backend stamps its own synced serial from this record inside the hook, so a
@@ -1528,7 +1495,6 @@ namespace MobileGL::MG_Pipe {
return true;
}
#if MOBILEGL_BUILD_RECORD_ARM
// Texture records can have a server consumer without the buffer ops table (Magma).
// Their staged bytes still belong to the server before SEG_STAGE retires. Keep the
// existing backend hooks authoritative when present, and use this fallback otherwise.
@@ -1596,7 +1562,6 @@ namespace MobileGL::MG_Pipe {
default: break;
}
}
#endif
// The texture half of resource_subdata, and it DISPATCHES TO NOBODY at GL-call time:
// a texture write marks a level dirty and Espryt uploads it at its own sync point, out
@@ -1646,7 +1611,6 @@ namespace MobileGL::MG_Pipe {
// record was accumulated, so the texels are the server's now, and that is the true
// this returns.
++stored->Serial;
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (tx): THE STAGED BYTES ARE ADOPTED HERE, at the last instant `bytes` is known
// alive (rule C: SEG_STAGE retires when this record does, and PendingUpload
// deliberately holds no byte pointer). The hook copies the run into the server's
@@ -1660,7 +1624,6 @@ namespace MobileGL::MG_Pipe {
} else {
AdoptTextureWithoutBackendHook(*stored, record, bytes, regions);
}
#endif
return true;
}
@@ -1985,12 +1948,10 @@ namespace MobileGL::MG_Pipe {
MGPipeApplier().DrawProgram = kMGPipeNullHandle;
MGPipeApplier().DispatchProgram = kMGPipeNullHandle;
MGPipeApplier().BoundShaderCso = kMGPipeNullHandle;
#if MOBILEGL_BUILD_RECORD_ARM
MGPipeApplier().BoundStreamOutputLifetimeId = 0;
// Begin is emitted once per span, not on make-current. Its immutable object
// snapshot survives with the resource/program records until End or release;
// set_context_values restores the returning context's bound lifetime id.
#endif
// P5e (sb, CONTRACT-P5E.md §5.6): the three binding-point windows are per-context
// WORKING state and go with the rest of it - a returning context has its own
// glBindBufferBase history and may not inherit the one this applier was left holding.
@@ -2010,12 +1971,10 @@ namespace MobileGL::MG_Pipe {
}
void MGPipeApplierReleaseObjectRecords() {
#if MOBILEGL_BUILD_RECORD_ARM
if (MG_Config::DataArmIsRecord() &&
(g_resourceOps == nullptr || g_resourceOps->TextureDestroy == nullptr)) {
MG_Record::ServerStagedTexture().DropAll();
}
#endif
// The served context is going away and this applier with it. Under split that is one
// applier per served context; in the monolith there is one applier behind every
// context, so nothing wires this - see PipeApply.h. The two serials advance here for
@@ -2049,10 +2008,8 @@ namespace MobileGL::MG_Pipe {
MGPipeApplier().DrawProgram = kMGPipeNullHandle;
MGPipeApplier().DispatchProgram = kMGPipeNullHandle;
MGPipeApplier().BoundShaderCso = kMGPipeNullHandle;
#if MOBILEGL_BUILD_RECORD_ARM
MGPipeApplier().BoundStreamOutputLifetimeId = 0;
MGPipeApplier().StreamOutputSpans.clear();
#endif
// AND "THE WORKING HANDLES THEY COULD NAME" IS ALL OF THEM, NOT JUST THE THREE ABOVE.
// Every framebuffer record holds eleven MGPSurface::Res naming texture and renderbuffer
// records this function has just dropped - which is why the table itself goes above -
@@ -2215,9 +2172,7 @@ namespace MobileGL::MG_Pipe {
// memory the rule exists to stop it reading. One store, inert until the caps bit is
// published, and it is what makes the two roles' third clause the same clause.
MGPipeApplier().IsTransformFeedbackActive = values.IsTransformFeedbackActive != 0;
#if MOBILEGL_BUILD_RECORD_ARM
MGPipeApplier().BoundStreamOutputLifetimeId = values.BoundTransformFeedbackLifetimeId;
#endif
}
void MGPipeApplySetPatchState(const MGPPatchState& patch) {
@@ -2352,7 +2307,6 @@ namespace MobileGL::MG_Pipe {
// ================================================================================
Bool MGPipeApplyResourceCreate(const MGPResourceDesc& desc) {
#if MOBILEGL_BUILD_RECORD_ARM
if (!MGPipeRespecifyIsWholeResource(desc)) {
MGP_TRIP_WIRE_REPORT("MGPipe: Fatal{ProtocolCorruption, \"ResourceCreate.RespecifyScope\"} - "
"resource_create cannot carry a per-level respecify extent");
@@ -2362,7 +2316,6 @@ namespace MobileGL::MG_Pipe {
MGP_TRIP_WIRE_REPORT("MGPipe: Fatal{ProtocolCorruption, \"ResourceCreate.BufferRange\"} - "
"only a texture-buffer resource may carry BufOffset/BufSize");
}
#endif
MOBILEGL_ASSERT(desc.Resource.Slot >= kMGPipeFirstAllocatableSlot,
"resource_create named the reserved slot 0");
if (desc.Resource.Slot < kMGPipeFirstAllocatableSlot) return false;
@@ -2453,7 +2406,6 @@ namespace MobileGL::MG_Pipe {
if (record == nullptr) return false;
PinNoLiveHostWrites(*record, desc.Resource, "resource_respecify");
PinRespecifyScopeCoversItsDeclaration(desc, level, desc.Resource, "resource_respecify");
#if MOBILEGL_BUILD_RECORD_ARM
if (level != nullptr &&
(desc.Target == kMGPipeResourceTargetBuffer ||
desc.Target == static_cast<Uint8>(MGPipeResourceTarget::Renderbuffer) ||
@@ -2471,7 +2423,6 @@ namespace MobileGL::MG_Pipe {
MGP_TRIP_WIRE_REPORT("MGPipe: Fatal{ProtocolCorruption, \"ResourceRespecify.BufferRange\"} - "
"a whole non-buffer resource may not carry BufOffset/BufSize");
}
#endif
// IS THIS A REDEFINITION AT ALL? Asked BEFORE the descriptor is replaced, because the
// stored one is the only thing there is to compare against (ID-18 M4). See
@@ -2493,14 +2444,12 @@ namespace MobileGL::MG_Pipe {
level != nullptr ? "one level" : "whole resource");
record->SharedImageId = 0;
}
#if MOBILEGL_BUILD_RECORD_ARM
if (level != nullptr && desc.Target != static_cast<Uint8>(MGPipeResourceTarget::TexBuffer)) {
// Scope and BufOffset/BufSize are transient respecify carriers, not resource state.
MGPipeClearRespecifiedLevel(record->Desc);
record->Desc.BufOffset = 0;
record->Desc.BufSize = 0;
}
#endif
// THE SERIAL MOVES EITHER WAY, and for a metadata update it is the entire publication:
// the twin re-derives its storage flags from the new mask at its next sync and decides
// for itself whether the backend needs a recreate.
@@ -2561,7 +2510,6 @@ namespace MobileGL::MG_Pipe {
record->PendingUploads.clear();
}
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (tx): the staged-texture store's defined-ness and drop bookkeeping rides THE SAME
// scope rules as the pending-set drops above - a named level redefines that one
// (uploadTarget, level), a whole-resource respecify drops every level, and a metadata
@@ -2578,7 +2526,6 @@ namespace MobileGL::MG_Pipe {
else
DefineTextureWithoutBackendHook(desc, level);
}
#endif
// resource_respecify is the catalogue's only kNeedsAck call, and the per-record half
// of that flag is MGPipeResourceRespecifyNeedsAck(desc): glBufferStorage is a real
@@ -2780,7 +2727,6 @@ namespace MobileGL::MG_Pipe {
// AND ONLY A BUFFER IS HANDED ON, for resource_create's reason: the op table is the
// buffer family's, its Destroy takes a handle whose kind that backend registered for,
// and a texture's death is read out of the record at the sync that would have used it.
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (tx): with ONE exception - the staged-texture store is keyed by the handle, so
// the death must reach it or a recycled slot's stale levels would answer for the
// successor. This does not hand the texture to the buffer family's Destroy.
@@ -2791,7 +2737,6 @@ namespace MobileGL::MG_Pipe {
MG_Record::ServerStagedTexture().Drop(
MG_Record::StagedTextureStore::KeyForHandle(handle.Handle));
}
#endif
if (static_cast<MGPipeKind>(handle.Kind) != MGPipeKind::Buffer) return;
if (g_resourceOps != nullptr && g_resourceOps->Destroy != nullptr) {
g_resourceOps->Destroy(handle.Handle);
-14
View File
@@ -108,7 +108,6 @@ namespace MobileGL::MG_Pipe {
void (*Destroy)(MGPipeHandle res);
void* (*MapPersistent)(MGPipeHandle res, Uint64 size, const void* seedBytes);
void (*UnmapPersistent)(MGPipeHandle res);
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (tx): the TEXTURE half of the resource family, appended so every positional
// initialiser of the nine P3a members keeps its meaning. TextureSubData is called
// from ApplyTextureUpload AFTER the gate, the accumulation and the serial, while
@@ -143,7 +142,6 @@ namespace MobileGL::MG_Pipe {
// pattern of its own, and read that back through the held pointer (and through the
// backend's own host map of the import). False with the reason in `why`.
Bool (*SelfTestExternal)(char* why, Uint64 whyBytes);
#endif
};
// Install / read the table. A null argument uninstalls, which is what a backend does at
@@ -873,7 +871,6 @@ namespace MobileGL::MG_Pipe {
Uint64 TextureShutterSerial = 0;
Uint64 ContextSerial = 0;
#if MOBILEGL_BUILD_RECORD_ARM
// ---- P5c (hd, CONTRACT-P5C §3.2): THE CURRENT VERB'S OWN HANDLES. ----------------
//
// Server state, written by ServerVerbSink at the top of a verb's dispatch and read by
@@ -932,7 +929,6 @@ namespace MobileGL::MG_Pipe {
VerbIndirectParameterBuffer = kMGPipeNullHandle;
VerbDispatchIndirectBuffer = kMGPipeNullHandle;
}
#endif
};
@@ -1309,13 +1305,11 @@ namespace MobileGL::MG_Pipe {
struct MGPRespecifiedLevel {
Uint16 UploadTarget = 0;
Uint16 Level = 0;
#if MOBILEGL_BUILD_RECORD_ARM
// P7 PH-4: mutable mip dimensions do not follow from the base descriptor. These fields
// are carried by the split resource_respecify record; the pull helper remains 4 bytes.
Uint32 Width = 0;
Uint32 Height = 0;
Uint32 Depth = 0;
#endif
};
inline MGPRespecifiedLevel MGPipeMakeRespecifiedLevel(Uint16 uploadTarget, Uint16 level,
@@ -1324,15 +1318,9 @@ namespace MobileGL::MG_Pipe {
MGPRespecifiedLevel value{};
value.UploadTarget = uploadTarget;
value.Level = level;
#if MOBILEGL_BUILD_RECORD_ARM
value.Width = width;
value.Height = height;
value.Depth = depth;
#else
(void)width;
(void)height;
(void)depth;
#endif
return value;
}
@@ -1483,14 +1471,12 @@ namespace MobileGL::MG_Pipe {
// unmap_persistent: the donation ends. Never emitted by P3a's own paths; the call exists
// so the pair is complete and the transport has both halves.
void MGPipeApplyUnmapPersistent(const MGPHandleOnly& handle);
#if MOBILEGL_BUILD_RECORD_ARM
// P11 B2 (T0): the split server's map_persistent for a session that runs T0. The client's
// AHardwareBuffer `ahb` (`size` bytes) becomes buffer `handle`'s resident store through the
// backend's ImportExternal. A handle that is not a live buffer of that width, or a backend
// without the member, declines (false); the codec answers DECLINED and the store runs T2.
// MapPersistentRoundtrips is not moved: under split the server never counted map_persistent.
Bool MGPipeApplyAdoptExternal(const MGPHandleOnly& handle, void* ahb, Uint64 size);
#endif
// ---------------------------------------------------------------------------------
// P3a: the five vertex-input entry points (D-G, D-H, D-I)
-2
View File
@@ -310,7 +310,6 @@ namespace MobileGL::MG_Pipe {
// the monolith twin reads GetLinkedShaderStages() off the frontend object it is
// handed. Named and discarded rather than left out of the signature, so the two
// arms stay one row.
#if MOBILEGL_BUILD_RECORD_ARM
// P13 W4c: MONOLITH'S RECORD ARM READS THE RECORD'S ARCHIVE (ProgramHandleArm), so it
// gets one: the link's snapshot, taken here, once per link - the same thing the wire
// decodes into the record, without the codec. A copy rather than a borrow, because the
@@ -324,7 +323,6 @@ namespace MobileGL::MG_Pipe {
MGPipeApplyCreateShaderState(*desc, link, spirv, std::move(archive));
return;
}
#endif
(void)linkedStages;
(void)linkedStageCount;
// P5e (pg): NO ARCHIVE ON THIS ARM, and the null is the arm selection rather than a
-9
View File
@@ -294,15 +294,10 @@ namespace MobileGL::MG_Pipe {
// latch clear, so every later row for that object keeps taking the real answer. The cost of
// that case is throughput, never silence.
inline Bool MGPipeResourceRespecifyWantsItsReply(const MGPResourceDesc& desc) {
#if MOBILEGL_BUILD_RECORD_ARM
if (MGPipeInstalledArm() != MGPipeRouteArm::kClientWire) return true;
if (static_cast<MGPipeResourceTarget>(desc.Target) == MGPipeResourceTarget::Buffer)
return false;
return !MGPipeHandleIsPublished(MGPipeKind::Texture, desc.Resource);
#else
(void)desc;
return true;
#endif
}
inline Bool MGPipeRouteResourceRespecify(const MGPResourceDesc& desc, const void* initialBytes,
const MGPRespecifiedLevel* level = nullptr) {
@@ -372,12 +367,10 @@ namespace MobileGL::MG_Pipe {
// invariant MGPipeTypes.h asserts beside the packer and which the applier's own
// SubDataNamesABuffer already reads.
inline Bool MGPipeSubDataWantsItsReply(const MGPSubData& record) {
#if MOBILEGL_BUILD_RECORD_ARM
// THE WIRE ARM ONLY. Everything above is an argument about a client talking to a server
// over SEG_CMD/SEG_STAGE; a monolith run inside a split build (every unit and
// integration-gpu lane is one) keeps the answer it always gave.
if (MGPipeInstalledArm() == MGPipeRouteArm::kClientWire) return false;
#endif
// THE PULL BUILD PREPROCESSES TO EXACTLY THIS LINE AND NOTHING ELSE, which is what G1
// compares: the arm test above sits inside the build switch, so the pull arm emits the
// expression this function has always had.
@@ -396,10 +389,8 @@ namespace MobileGL::MG_Pipe {
MGPReplySlot reply = MGPipeMintReplySlot();
MGP_ResourceSubData(&record, bytes, byteCount, regions, record.RegionCount, &reply);
const Bool accepted = MGPipeTakeReplyBool(reply, "resource_subdata");
#if MOBILEGL_BUILD_RECORD_ARM
// A cancelled wire upload is declined even when no server reply was requested.
if (MGPipeInstalledArm() == MGPipeRouteArm::kClientWire) return accepted;
#endif
return MGPipeSubDataWantsItsReply(record) ? accepted : true;
}
inline void MGPipeRouteBufferSubDataResident(const MGPSubData& record, const void* bytes,
-4
View File
@@ -58,13 +58,11 @@ namespace MobileGL::MG_Pipe {
UnmigratedVerb,
// A call that reached an arm this build did not compile, or ran on the wrong role.
RoleViolation,
#if MOBILEGL_BUILD_RECORD_ARM
// A malformed peer record rejected by the server-side applier.
ProtocolCorruption,
// The GPU device a session's backend renders with is lost (a GPU fault or hang the driver
// reset). Raised only through MGPipeSessionLatch below: it ends THAT session.
DeviceLost,
#endif
};
// What the layer above installs. `line` is the FULLY FORMATTED message, so the hook forwards
@@ -131,7 +129,6 @@ namespace MobileGL::MG_Pipe {
[[noreturn]] void MGPipeProtocolFatal(const char* what, const char* detail);
[[noreturn]] void MGPipeProtocolFatalAt(const char* what, ::std::uint64_t got, ::std::uint64_t expected);
#if MOBILEGL_BUILD_RECORD_ARM
// ---- THE LATCH TWIN: A LOST GPU DEVICE ENDS ITS SESSION, NOT THE PROCESS ----------------
//
// MGPipeSessionFail is for a verb the backend cannot honour, and it does not return. A device
@@ -161,7 +158,6 @@ namespace MobileGL::MG_Pipe {
__attribute__((format(printf, 2, 3)))
#endif
MGPipeSessionLatch(MGPipeFatalFamily family, const char* fmt, ...);
#endif
#if MOBILEGL_BUILD_DISAGGREGATED
@@ -17,14 +17,12 @@
// role IS MG_State plus MG_Impl - and the edge exists only in a build that has the transport at all.
#include <MG_Remote/Client/GpuWritePending.h>
#endif
#if MOBILEGL_BUILD_RECORD_ARM
// The client role's persistent-map tracker (P13 W5: the record arm's, so beside this file).
#include <MG_State/GLState/BufferState/PersistentMapTracker.h>
#include <MG_Util/Debug/Log.h>
#include <cstdlib>
#include <MG_Util/Metrics/PipeStats.h>
#endif
namespace MobileGL::MG_State::GLState {
namespace {
@@ -32,7 +30,6 @@ namespace MobileGL::MG_State::GLState {
// Starts at 1 so a zero-initialized cache slot can never carry a live buffer's id.
std::atomic<Uint64> g_nextBufferLifetimeId{1};
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (hd, CONTRACT-P5C §3.8 / §6 layer 1): the frontend BufferObject's legacy
// accessors are a layer-1 surface. With an active transport, the pre-handle buffer
// arm that reads them (Managers.cpp's RespecifyStorageNow / UploadRangeNow /
@@ -66,10 +63,6 @@ namespace MobileGL::MG_State::GLState {
if (!MG_Record::PersistentMapTracker::OnServerRole()) return;
FatalLegacyBufferArmFromApplyThread(accessor);
}
#else
// No record arm, no apply thread to refuse.
inline void RefuseLegacyBufferArmFromApplyThread(const char*) {}
#endif
}
Uint64 BufferObject::AllocateLifetimeId() {
@@ -97,13 +90,11 @@ namespace MobileGL::MG_State::GLState {
}
BufferObject::~BufferObject() {
#if MOBILEGL_BUILD_RECORD_ARM
// Unconditional, not behind PushIsArmed(): the transport mode cannot change, but the
// tracker is a leaked singleton whose entries are raw pointers, and an entry that
// outlives its object is the one failure this set must not have. Forget is a no-op
// for a buffer that was never a member.
MG_Record::PersistentMapTracker::Instance().Forget(*this);
#endif
// P3a D-L: the buffer's death crosses as resource_destroy, which is the catalogue
// call for it - no seventh NotifyStateObjectDestroyed raiser is added, because that
// header exists for kinds that have no such call. The emit-then-free ORDER is fixed
@@ -215,12 +206,10 @@ namespace MobileGL::MG_State::GLState {
}
m_size = size;
m_resource.ResizeShadow(size);
#if MOBILEGL_BUILD_RECORD_ARM
// The store this buffer's membership was about no longer exists, and ResizeShadow is
// reserve+resize - a grow past the reserve reallocates - so a pushed block's source
// base has moved too. Both are the same event to the tracker: re-read the predicate.
NotePersistentMapStateChanged();
#endif
}
void BufferObject::Respecify(SizeT size, const void* data) {
@@ -364,12 +353,10 @@ namespace MobileGL::MG_State::GLState {
m_mappedRange = {0, 0};
m_stagingBias = 0;
m_ownsStagingData = false;
#if MOBILEGL_BUILD_RECORD_ARM
// AFTER the reset, so the predicate reads the post-unmap state, and after the landing
// above, so the last bytes of a write map are already on the wire when the record
// that says "no live writer" goes out behind them.
NotePersistentMapStateChanged();
#endif
}
void BufferObject::FlushMemoryRange(SizeT offset, SizeT length) {
@@ -406,7 +393,6 @@ namespace MobileGL::MG_State::GLState {
}
void BufferObject::SyncPersistentMappedRange() {
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (hd): the named refusal comes FIRST - a silent return here used to let a
// server-side caller slip through with one MGLOG_D's worth of evidence (B3).
//
@@ -431,7 +417,6 @@ namespace MobileGL::MG_State::GLState {
MG_Record::PersistentMapTracker::Instance().PushBlocksFor(*this);
return;
}
#endif
if (!m_isMapped) return;
// GPU-resident: the app already wrote directly into coherent GPU memory. This is
// the whole point of the persistent-map path - the per-draw whole-buffer re-upload
@@ -445,7 +430,6 @@ namespace MobileGL::MG_State::GLState {
NotifySubData(m_mappedRange.start, m_mappedRange.end - m_mappedRange.start);
}
#if MOBILEGL_BUILD_RECORD_ARM
void BufferObject::PushMappedSpanBlock(SizeT offset, SizeT size) {
// NotifySubData and not MGPipeEmitResourceSubData directly: the serial bump, the
// defined-content promotion and the legacy-ops fallback are what the monolith span
@@ -532,7 +516,6 @@ namespace MobileGL::MG_State::GLState {
Bool BufferObject::HasLiveHostWritesForWire() const {
return m_publishedLiveHostWrites;
}
#endif
void BufferObject::WritebackFromBackend(DataPtr data, SizeT atOffset) {
MOBILEGL_ASSERT(atOffset + data.size <= m_size,
@@ -541,7 +524,6 @@ namespace MobileGL::MG_State::GLState {
Memcpy(m_resource.Bytes() + atOffset, data.data, data.size);
++m_changeSerial;
MGP_NOTE_AGGREGATE(BufferChange);
#if MOBILEGL_BUILD_RECORD_ARM
// THE THIRD STATE'S ONLY EXIT. In a split build SyncGpuWrites does NOT clear the
// flag before emitting, because between the emission and the answer the shadow is
// stale and the object has no way to say so; the answer landing here is what makes it
@@ -556,7 +538,6 @@ namespace MobileGL::MG_State::GLState {
if (MG_Config::DataArmIsRecord() && atOffset == 0 && data.size >= m_size) {
m_gpuWritePending = false;
}
#endif
}
void BufferObject::MarkGpuWritten() {
@@ -824,12 +805,10 @@ namespace MobileGL::MG_State::GLState {
m_mappingAccess = (read ? BufferMappingAccessBit::Read : BufferMappingAccessBit::Null) |
(write ? BufferMappingAccessBit::Write : BufferMappingAccessBit::Null);
m_mappedRange = {0, m_size};
#if MOBILEGL_BUILD_RECORD_ARM
// glMapBuffer never takes the Persistent bit, so this buffer can never join the
// push set - but a WRITE map still mutates the shadow with no call, which is the
// half of the live-host-writes bit that is not about the push at all.
NotePersistentMapStateChanged();
#endif
if (m_mappingAccess & BufferMappingAccessBit::Write) {
// glMapBuffer maps from offset 0, so no bias: the allocation's own
@@ -909,7 +888,6 @@ namespace MobileGL::MG_State::GLState {
m_isMapped = true;
m_mappingAccess = access;
m_mappedRange = range;
#if MOBILEGL_BUILD_RECORD_ARM
// BEFORE the adoption attempt below, deliberately. Under R-6 the acquisition always
// declines, so the predicate this publishes is already final; if a later phase ever
// mints one, the adoption path notes the change itself (it does now - the call is
@@ -917,7 +895,6 @@ namespace MobileGL::MG_State::GLState {
// having been made, which is the order that keeps the set conservative under both
// answers.
NotePersistentMapStateChanged();
#endif
if (access & BufferMappingAccessBit::Persistent) {
m_ownsStagingData = false;
@@ -931,14 +908,11 @@ namespace MobileGL::MG_State::GLState {
!(access & BufferMappingAccessBit::FlushExplicit) &&
MG_Pipe::MGPipeResourceSubsystemEnabled()) {
if (void* pushedBase = MG_Pipe::MGPipeEmitMapPersistent(*this)) {
#if MOBILEGL_BUILD_RECORD_ARM
// P11 B2 made this reachable (a T0 adoption), so the tracker lets go of the
// shadow range WHILE THE SHADOW IS STILL OURS: AdoptPersistentMap frees it, and
// an untrack after that would mprotect pages the buffer no longer owns.
MG_Record::PersistentMapTracker::Instance().Forget(*this);
#endif
m_resource.AdoptPersistentMap(pushedBase);
#if MOBILEGL_BUILD_RECORD_ARM
// An adoption takes the buffer OUT of the push set and out of the
// live-host-writes state: the application now writes coherent GPU memory
// and there is nothing to ship. Under R-6 this is unreachable; it is here
@@ -946,7 +920,6 @@ namespace MobileGL::MG_State::GLState {
// entry and nothing did, which would have left the published bit true
// across an adoption until unmap (latent for P11).
NotePersistentMapStateChanged();
#endif
}
return m_resource.Bytes() + range.start;
}
@@ -994,7 +967,6 @@ namespace MobileGL::MG_State::GLState {
return m_resource.Bytes();
}
#if MOBILEGL_BUILD_RECORD_ARM
SizeT BufferObject::ShadowAllocationBytes() const {
// The extent of the very pointer MappedData() hands out, read by the same caller
// (the tracker's registration) on the same thread: the same rule-E surface, the
@@ -1002,7 +974,6 @@ namespace MobileGL::MG_State::GLState {
RefuseLegacyBufferArmFromApplyThread("ShadowAllocationBytes");
return m_resource.ShadowAllocationBytes();
}
#endif
Bool BufferObject::IsBackendPersistentMapped() const {
return m_resource.IsGpuResident();
@@ -187,7 +187,6 @@ namespace MobileGL {
// from every path that reads the shadow on the app's behalf.
void SyncGpuWrites();
#if MOBILEGL_BUILD_RECORD_ARM
// ---- P5 b1: the two things a split build has to do that a monolith does not ---
//
// Everything here is behind the build option AND behind
@@ -238,7 +237,6 @@ namespace MobileGL {
// layer-1 read of client memory like MappedData (CONTRACT-P5C rule E) and carries
// the same apply-thread refusal.
SizeT ShadowAllocationBytes() const;
#endif
Bool IsMapped() const;
Bool IsImmutableStorage() const;
@@ -321,12 +319,10 @@ namespace MobileGL {
// and, in a split build, cleared by WritebackFromBackend instead, because there
// the answer arrives later than the request. See SyncGpuWrites' definition.
Bool m_gpuWritePending = false;
#if MOBILEGL_BUILD_RECORD_ARM
// The last value of MGPResourceDesc::HasLiveHostWrites this object published.
// Behind the option so the pull build's layout - and therefore every inlined
// constructor and accessor in it - does not move (G1).
Bool m_publishedLiveHostWrites = false;
#endif
Range1D m_mappedRange;
// The write-map staging store. MapAlignedData because the application is handed a
// pointer into it, and biased by m_stagingBias because ARB_map_buffer_alignment
@@ -53,11 +53,9 @@ namespace MobileGL::MG_State::GLState {
// never exceed what was allocated. The pull build keeps the exact count and does not
// see this function at all: it has no tracker to spend the rounding on, and G1 holds
// its symbol set and .text byte-identical only if allocate()'s own text does not move.
#if MOBILEGL_BUILD_RECORD_ARM
inline constexpr SizeT ShadowAllocationBytesFor(SizeT count) {
return (count + SHADOW_ALLOCATION_ALIGNMENT - 1) & ~(SHADOW_ALLOCATION_ALIGNMENT - 1);
}
#endif
// Allocator that gives every allocation SHADOW_ALLOCATION_ALIGNMENT (and, in a split
// build, a page-granular size - see ShadowAllocationBytesFor). Deliberately minimal: the
@@ -73,13 +71,8 @@ namespace MobileGL::MG_State::GLState {
T* allocate(SizeT count) {
if (count == 0) return nullptr;
#if MOBILEGL_BUILD_RECORD_ARM
return static_cast<T*>(::operator new(ShadowAllocationBytesFor(count * sizeof(T)),
std::align_val_t{SHADOW_ALLOCATION_ALIGNMENT}));
#else
return static_cast<T*>(
::operator new(count * sizeof(T), std::align_val_t{SHADOW_ALLOCATION_ALIGNMENT}));
#endif
}
void deallocate(T* pointer, SizeT) noexcept {
::operator delete(pointer, std::align_val_t{SHADOW_ALLOCATION_ALIGNMENT});
@@ -141,7 +134,6 @@ namespace MobileGL::MG_State::GLState {
const SizeT reserved = std::bit_ceil(size == 0 ? SizeT{1} : size);
m_shadow->reserve(reserved);
m_shadow->resize(size);
#if MOBILEGL_BUILD_RECORD_ARM
// RECORDED HERE, NOT INFERRED FROM capacity(). `reserved` is the count this
// call asked the allocator for, so ShadowAllocationBytesFor(reserved) is the
// block the allocator handed back whenever this reserve reallocated - and when
@@ -155,14 +147,12 @@ namespace MobileGL::MG_State::GLState {
// under-report costs nothing: the tracker still needs rangeEnd <= extent, and
// size <= reserved always.
m_shadowExtent = ShadowAllocationBytesFor(reserved);
#endif
}
// Direct shadow access, used only by the backend's upload-from-shadow path,
// which never runs for a GPU-resident (persistent) buffer.
MapAlignedData& Shadow() { return *m_shadow; }
const MapAlignedData& Shadow() const { return *m_shadow; }
#if MOBILEGL_BUILD_RECORD_ARM
// The extent of the shadow's heap allocation in bytes, as ResizeShadow asked for
// it: every byte of [Bytes(), Bytes() + this) belongs to this shadow and to
// nothing else, and the extent is a whole number of SHADOW_ALLOCATION_ALIGNMENT
@@ -172,7 +162,6 @@ namespace MobileGL::MG_State::GLState {
// kill). Zero for an adopted (GPU-resident) store, whose shadow was released, and
// for a shadow that was never sized; the tracker excludes both.
SizeT ShadowAllocationBytes() const { return m_gpuMapped != nullptr ? 0 : m_shadowExtent; }
#endif
// Transition to persistent GPU residency: adopt the backend's coherent
// mapped base as the source of truth and drop the CPU shadow. The caller
@@ -182,10 +171,8 @@ namespace MobileGL::MG_State::GLState {
m_gpuMapped = mappedBase;
m_shadow->clear();
m_shadow->shrink_to_fit();
#if MOBILEGL_BUILD_RECORD_ARM
// The block is gone with the shrink; the next ResizeShadow records the next one.
m_shadowExtent = 0;
#endif
}
// Give the adoption back: the bytes resolve against the shadow again (which
@@ -209,9 +196,7 @@ namespace MobileGL::MG_State::GLState {
SharedPtr<MapAlignedData> m_shadow = MakeShared<MapAlignedData>();
void* m_gpuMapped = nullptr;
SharedPtr<BackendBufferResource> m_backend;
#if MOBILEGL_BUILD_RECORD_ARM
// See ShadowAllocationBytes. Split-only so the pull build's layout does not move (G1).
SizeT m_shadowExtent = 0;
#endif
};
} // namespace MobileGL::MG_State::GLState
-2
View File
@@ -1556,12 +1556,10 @@ namespace MobileGL::MG_State {
return it != m_transformFeedbackObjects.end() && it->second.everBound;
}
#if MOBILEGL_BUILD_RECORD_ARM
Uint64 GLContext::GetTransformFeedbackLifetimeId(Uint index) const {
const auto found = m_transformFeedbackObjects.find(index);
return found == m_transformFeedbackObjects.end() ? 0 : found->second.lifetimeId;
}
#endif
void GLContext::MarkTransformFeedbackObjectForDeletion(Uint index) {
if (index == 0 || !m_transformFeedbackNames.IsValid(index)) return;
-2
View File
@@ -571,9 +571,7 @@ namespace MobileGL {
// Never returns 0 - the counter starts at 1 so a zero-initialised memo slot cannot
// be mistaken for a live object.
Uint64 GetBoundTransformFeedbackLifetimeId() const { return m_boundTransformFeedbackLifetimeId; }
#if MOBILEGL_BUILD_RECORD_ARM
Uint64 GetTransformFeedbackLifetimeId(Uint index) const;
#endif
// Whether the object carrying this identity still has an OPEN capture span - one
// that glBeginTransformFeedback started and glEndTransformFeedback has not closed,
// paused or not. A backend that hands out a bounded set of per-object slots must
@@ -87,7 +87,6 @@ namespace MobileGL::MG_State::GLState {
return m_framebufferObjects.find(index) != m_framebufferObjects.end();
}
#if MOBILEGL_BUILD_RECORD_ARM
SharedPtr<FramebufferObject> FramebufferState::FindFramebufferObjectByLifetimeId(Uint64 lifetimeId) const {
for (const auto& [index, framebufferObject] : m_framebufferObjects) {
if (framebufferObject && framebufferObject->GetLifetimeId() == lifetimeId) {
@@ -96,5 +95,4 @@ namespace MobileGL::MG_State::GLState {
}
return nullptr;
}
#endif
} // namespace MobileGL::MG_State::GLState
@@ -28,7 +28,6 @@ namespace MobileGL::MG_State::GLState {
Bool ValidateName(Uint index) const;
Bool ValidateFramebufferObject(Uint index) const;
#if MOBILEGL_BUILD_RECORD_ARM
// P5c (G6, CONTRACT-P5C §3.3/§5.4): the reverse of HandleFor() for a server that holds
// only the handle - the named-blit consumer on a backend with no FBO twin registry
// (Magma) resolves the verb's ReadFbo/DrawFbo to the frontend object by the lifetime
@@ -36,7 +35,6 @@ namespace MobileGL::MG_State::GLState {
// MGPipeFrontendKeyedRegistryScope; P3b/P4b retire it by carrying the object identity
// in the record. nullptr when no live framebuffer owns the id.
SharedPtr<FramebufferObject> FindFramebufferObjectByLifetimeId(Uint64 lifetimeId) const;
#endif
// P2 brief D4: "did the attachment set or the default geometry of ANY framebuffer
// move". It does NOT cover a BIND - a bind writes a BindingSlot, not the object - so
@@ -90,7 +90,6 @@ namespace MobileGL::MG_State::GLState {
using PipeInputReflection = ResourceReflection;
using PipeOutputReflection = ResourceReflection;
#if MOBILEGL_BUILD_RECORD_ARM
// P7 wave 2 package C, OQ-8 (CONTRACT-P7 §5.3): THE SHADER STORAGE BLOCKS AS DIRECTVULKAN
// INDEXES THEM, published once at link time instead of re-derived per draw.
//
@@ -129,7 +128,6 @@ namespace MobileGL::MG_State::GLState {
// it; the wire consumer does not read it today.
Int32 dataSize = 0;
};
#endif
// Transform feedback (GL 3.0 core: glTransformFeedbackVaryings applies on
// the NEXT link; the linked snapshot below is what draws and queries see).
@@ -208,12 +206,10 @@ namespace MobileGL::MG_State::GLState {
// The owned reflection snapshot. Indexed by TProgram index; see the structs above.
Vector<UniformReflection> uniformReflection;
Vector<BlockReflection> blockReflection;
#if MOBILEGL_BUILD_RECORD_ARM
// P7 OQ-8: DirectVulkan's storage-block index space, filled beside blockReflection in
// SnapshotGlslangReflection. See StorageBlockReflection above for what the order is
// and why it is not blockReflection's.
Vector<StorageBlockReflection> storageBlocks;
#endif
Vector<PipeInputReflection> pipeInputReflection;
Vector<PipeOutputReflection> pipeOutputReflection;
// Program-level scalars glslang answers off the linked intermediates.
@@ -526,7 +522,6 @@ namespace MobileGL::MG_State::GLState {
v("blockMemberElement", a.blockMemberElement);
} // 11 fields
#if MOBILEGL_BUILD_RECORD_ARM
// P7 OQ-8. Three plain fields, so the generic codec arms carry it with no new arm of their
// own - which is the whole reason it is a table rather than hand-written bytes.
template <class Self, class V>
@@ -536,7 +531,6 @@ namespace MobileGL::MG_State::GLState {
v("binding", a.binding);
v("dataSize", a.dataSize);
} // 3 fields
#endif
// Every member EXCEPT `program`: it is null for every archived instance by construction
// (ProgramTranslationCache.h asserts that at insert) and must never be serialized - it is
@@ -547,11 +541,9 @@ namespace MobileGL::MG_State::GLState {
void VisitFields(Self& a, V&& v) {
v("uniformReflection", a.uniformReflection);
v("blockReflection", a.blockReflection);
#if MOBILEGL_BUILD_RECORD_ARM
// Beside blockReflection because that is where it is FILLED, and a visitor that walks
// the table in declaration order then reads the two together.
v("storageBlocks", a.storageBlocks);
#endif
v("pipeInputReflection", a.pipeInputReflection);
v("pipeOutputReflection", a.pipeOutputReflection);
v("lastStageIsFragment", a.lastStageIsFragment);
@@ -634,13 +626,9 @@ namespace MobileGL::MG_State::GLState {
#if defined(__GLIBCXX__) && !defined(_GLIBCXX_DEBUG) && (SIZE_MAX == UINT64_MAX)
#define MGL_RESOURCEREFLECTION_SIZE 128
#define MGL_XFBVARYING_SIZE 128
#if MOBILEGL_BUILD_RECORD_ARM
// P7 OQ-8 added storageBlocks, one more Vector, in the disaggregated build ONLY. The pull
// number below is the one G1 measures and it has not moved.
#define MGL_LINKARTIFACTS_SIZE 1080
#else
#define MGL_LINKARTIFACTS_SIZE 1056
#endif
#define MGL_SPIRVARTIFACTS_SIZE 88
#elif defined(_LIBCPP_VERSION) && (SIZE_MAX == UINT64_MAX) && defined(MGL_ARTIFACT_SIZES_LIBCXX_PINNED)
// The integrator pins these from the NDK build (brief C.4); until then this branch is inert.
@@ -85,7 +85,6 @@ namespace MobileGL::MG_State::GLState {
visit("floatValues", value.floatValues);
}
#if MOBILEGL_BUILD_RECORD_ARM
void SchemaWord(Uint64& hash, Uint64 word) {
for (unsigned i = 0; i < 8; ++i) {
hash ^= (word >> (i * 8)) & 255u;
@@ -151,7 +150,6 @@ namespace MobileGL::MG_State::GLState {
SchemaName(hash, "end-record");
}
}
#endif
// ---- the writer ----
@@ -200,20 +198,9 @@ namespace MobileGL::MG_State::GLState {
} else if constexpr (ArchiveVector<T>) {
WriteSequence(out, value);
} else if constexpr (ArchiveUniformInitializer<T>) {
#if MOBILEGL_BUILD_RECORD_ARM
VisitUniformInitializer(value, [&out](const char*, const auto& member) {
WriteValue(out, member);
});
#else
WriteValue(out, value.name);
WriteValue(out, value.basicType);
WriteValue(out, value.vectorSize);
WriteValue(out, value.matrixCols);
WriteValue(out, value.matrixRows);
WriteValue(out, value.arraySize);
WriteValue(out, value.intValues);
WriteValue(out, value.floatValues);
#endif
} else {
// The archive's own structs: TypeFacts, ResourceReflection, XfbVarying. ONE
// table serves both directions, so a member added to any of them is carried by
@@ -366,20 +353,9 @@ namespace MobileGL::MG_State::GLState {
if (!in.Ok) return;
}
} else if constexpr (ArchiveUniformInitializer<T>) {
#if MOBILEGL_BUILD_RECORD_ARM
VisitUniformInitializer(value, [&in](const char*, auto& member) {
if (in.Ok) ReadValue(in, member);
});
#else
ReadValue(in, value.name);
ReadValue(in, value.basicType);
ReadValue(in, value.vectorSize);
ReadValue(in, value.matrixCols);
ReadValue(in, value.matrixRows);
ReadValue(in, value.arraySize);
ReadValue(in, value.intValues);
ReadValue(in, value.floatValues);
#endif
} else {
VisitFields(value, [&in](const char*, auto& field) {
if (in.Ok) ReadValue(in, field);
@@ -389,16 +365,8 @@ namespace MobileGL::MG_State::GLState {
// v1 is retained for local monolith verification. Its native size echo is not a
// wire compatibility fact: libstdc++ writes 1056 while unpinned libc++ writes zero.
#if !MOBILEGL_BUILD_RECORD_ARM
#ifdef MGL_LINKARTIFACTS_SIZE
inline constexpr Uint64 kLinkArtifactsSizeEcho = MGL_LINKARTIFACTS_SIZE;
#else
inline constexpr Uint64 kLinkArtifactsSizeEcho = 0;
#endif
#endif
} // namespace
#if MOBILEGL_BUILD_RECORD_ARM
Uint64 ProgramArtifactsSchemaFingerprint() {
static const Uint64 fingerprint = [] {
Uint64 hash = 1469598103934665603ull;
@@ -412,16 +380,11 @@ namespace MobileGL::MG_State::GLState {
}();
return fingerprint;
}
#endif
void EncodeProgramArtifacts(const LinkArtifacts& link, const SpirvArtifacts& spirv,
Vector<Uint8>& out) {
PutRaw(out, kProgramArtifactsCodecVersion);
#if MOBILEGL_BUILD_RECORD_ARM
PutRaw(out, ProgramArtifactsSchemaFingerprint());
#else
PutRaw(out, kLinkArtifactsSizeEcho);
#endif
// `link` is walked through its own VisitFields table, which omits the live
// SharedPtr<glslang::TProgram>: 57 of the 58 members. There is no arm here for it and
// there must not be one - it points into a glslang arena that no archived instance
@@ -446,11 +409,7 @@ namespace MobileGL::MG_State::GLState {
// Refuse the declared wire shape before reading any field. In v2 the second word
// follows serialization types/order; C++ container object sizes are irrelevant.
if (version != kProgramArtifactsCodecVersion) return false;
#if MOBILEGL_BUILD_RECORD_ARM
if (schema != ProgramArtifactsSchemaFingerprint()) return false;
#else
if (schema != kLinkArtifactsSizeEcho) return false;
#endif
ReadValue(in, link);
ReadValue(in, spirv);
@@ -60,7 +60,6 @@ namespace MobileGL::MG_State::GLState {
// Bumped whenever the byte format changes in a way a previous reader would misread. A
// reader that sees a different word REFUSES; it never tries to guess a layout.
#if MOBILEGL_BUILD_RECORD_ARM
// 3 since P7 wave 2 package C (OQ-8): LinkArtifacts gained `storageBlocks`, so a v2 reader
// would run out of bytes in the middle of the stream rather than notice. The schema
// fingerprint beside the version would catch it on its own - it is derived from the
@@ -72,9 +71,6 @@ namespace MobileGL::MG_State::GLState {
// Derived from the actual VisitFields order/names, container element schemas and scalar
// representations. No native container size/offset is included. Also checked in Hello.
Uint64 ProgramArtifactsSchemaFingerprint();
#else
inline constexpr Uint32 kProgramArtifactsCodecVersion = 1;
#endif
// Appends the archive to `out` (which is not cleared, so a caller may frame it). Never
// fails: everything it walks is owned plain data.
@@ -106,7 +106,6 @@ namespace {
return bracket == MobileGL::String::npos ? name : name.substr(0, bracket);
}
#if MOBILEGL_BUILD_RECORD_ARM
// P7 OQ-8. ShaderStage -> EShLanguage, so a reflection record's `stages` mask (which is an
// EShLanguageMask) can be tested against the stage of a snapshotted shader. Written here
// rather than lifted into a shared header because this is the only caller and
@@ -127,7 +126,6 @@ namespace {
}
return EShLangCount;
}
#endif
// Element index of an arrayed interface-block instance: "GOKU[3]" -> 3, "GOKU" -> 0.
// Reflection spells arrayed instances exactly this way (glslang expands the instance
@@ -1758,17 +1756,14 @@ namespace MobileGL::MG_State::GLState {
}
SnapshotGlslangReflection();
#if MOBILEGL_BUILD_RECORD_ARM
// P7 OQ-8. Guarded at the CALL as well as at the definition, which is not belt and
// braces: LinkArtifacts::storageBlocks does not exist in a pull build, so neither can
// the function that fills it, and G1 requires this translation unit to be statement
// for statement what it was there.
SnapshotStorageBlockIndexSpace();
#endif
return true;
}
#if MOBILEGL_BUILD_RECORD_ARM
// P7 wave 2 package C, OQ-8 (CONTRACT-P7 §5.3): DIRECTVULKAN'S STORAGE-BLOCK INDEX SPACE,
// BUILT ONCE HERE INSTEAD OF PER DRAW.
//
@@ -1847,7 +1842,6 @@ namespace MobileGL::MG_State::GLState {
MGLOG_D("ProgramObject %u: Reflection - %zu storage block(s) in the backend index space",
in.externalIndex, artifacts.storageBlocks.size());
}
#endif
// The last thing DoReflection does, and the thing that lets everything after it stop
// caring that a glslang::TProgram ever existed: copy every reflection record the GL query
@@ -207,14 +207,12 @@ namespace MobileGL::MG_State::GLState {
// one and overwritten the qualifier. See the definition for why the invented binding is
// deliberately left in place for the backends' own use.
void SeedDefaultStorageBlockBindings();
#if MOBILEGL_BUILD_RECORD_ARM
// P7 wave 2 package C, OQ-8: builds LinkArtifacts::storageBlocks - DirectVulkan's own
// storage-block index space - out of the reflection snapshot the call above produced,
// so the backends stop re-deriving it with SPIRV-Reflect on every draw. Runs after
// SnapshotGlslangReflection because it reads blockReflection. See the definition for
// which order it has to reproduce and why each clause of it is derivable here.
void SnapshotStorageBlockIndexSpace();
#endif
Bool ValidateFragmentOutputLocations();
Bool ResolveTransformFeedbackVaryings();
void ResolveGsTriangleStripCapture(const glslang::TIntermediate* captureIntermediate);
@@ -48,10 +48,8 @@ namespace MobileGL::MG_State::GLState {
using BlockReflection = ResourceReflection;
using PipeInputReflection = ResourceReflection;
using PipeOutputReflection = ResourceReflection;
#if MOBILEGL_BUILD_RECORD_ARM
// P7 OQ-8: DirectVulkan's storage-block index space, carried in LinkArtifacts.
using StorageBlockReflection = MobileGL::MG_State::GLState::StorageBlockReflection;
#endif
using XfbVarying = MobileGL::MG_State::GLState::XfbVarying;
using LinkArtifacts = MobileGL::MG_State::GLState::LinkArtifacts;
using SpirvArtifacts = MobileGL::MG_State::GLState::SpirvArtifacts;
+1 -55
View File
@@ -1205,65 +1205,11 @@ namespace {
// always true) and the production-path probe pair in MG_Test/SanityTest.cpp and
// MG_Test/Buffer/SplitBufferTest.cpp, both of which go red when the producer is
// deleted. This skip is what the build-verify-split lane exists to make visible.
#elif MOBILEGL_BUILD_RECORD_ARM
#else
// P13 W5: the producer is the record arm's (BufferObject::NotePersistentMapStateChanged),
// so the wire is retired wherever that arm is built (PipeApply.cpp's pin follows it).
GTEST_SKIP() << "P5 gave HasLiveHostWrites a producer, so the always-false wire is retired "
"wherever the record arm is built; PinLiveHostWritesNamesABuffer replaces it";
#elif !MGTEST_HAVE_FORK
GTEST_SKIP() << "no fork on this platform; the wire's verdict is std::abort()";
#else
ApplierGuard guard;
const MGPipeHandle res{8, 4};
const Uint8 bytes[64] = {};
MGPipeApplyResourceCreate(BufferDesc(res, 0, 55));
MGPipeApplyResourceRespecify(BufferDesc(res, 256, 55), nullptr);
// The negative control: with the flag clear the same call is silent and answers
// normally, so what follows is the flag firing and not the call.
EXPECT_EQ(MGPipeApplyMapPersistent(BufferHandle(res), 256, bytes), nullptr);
EXPECT_EQ(ReadLog().find("PipeLiveHostWrites"), std::string::npos);
struct Drive {
MGPipeHandle Res;
const char* Call;
};
const Drive drives[] = {
{res, "map_persistent"}, {res, "resource_respecify"}, {res, "resource_subdata"},
{res, "resource_flush_range"}, {res, "resource_readback"},
};
for (const Drive& drive : drives) {
const ChildResult child = RunInChild([&drive, &bytes]() {
// Set in the CHILD: the parent's applier must stay honest for the next drive.
MGPipeApplier().Resources[drive.Res.Slot].HasLiveHostWrites = true;
const String call = drive.Call;
if (call == "map_persistent") {
MGPipeApplyMapPersistent(MGPHandleOnly{drive.Res, static_cast<Uint32>(MGPipeKind::Buffer), 0},
256, bytes);
} else if (call == "resource_respecify") {
MGPResourceDesc desc{};
desc.Resource = drive.Res;
desc.Width = 256;
desc.GlNameForDiag = 55;
MGPipeApplyResourceRespecify(desc, nullptr);
} else if (call == "resource_subdata") {
MGPSubData record{};
record.Res = drive.Res;
MGPipeSetSubDataBufferRange(record, 0, 64);
MGPipeApplyResourceSubData(record, bytes);
} else if (call == "resource_flush_range") {
MGPipeApplyResourceFlushRange(MGPFlushRange{drive.Res, 0, 64, 0, 0}, bytes);
} else {
MGPipeApplyResourceReadback(MGPReadback{drive.Res, 0, 256});
}
});
EXPECT_TRUE(DiedOfAbort(child))
<< drive.Call << ": " << DescribeStatus(child) << "; log: " << child.Log;
const std::string wanted =
std::string("Fatal{PipeLiveHostWrites} ") + drive.Call + " {slot=8, gen=4}";
EXPECT_NE(child.Log.find(wanted), std::string::npos)
<< "wanted \"" << wanted << "\"; log: " << child.Log;
}
#endif
}
@@ -529,14 +529,10 @@ TEST(ProgramArtifactsCodec, AVersionMismatchIsRefused) {
// TProgram, which is null for every archived instance by construction and points into an arena
// no archive owns. A codec arm for it would be a use-after-free waiting for a cache hit.
TEST(ProgramArtifactsCodec, TheTablesVisitEveryMemberExceptTheLiveProgram) {
#if MOBILEGL_BUILD_RECORD_ARM
// P7 OQ-8: 59 members, 58 visited. The 59th is still the live TProgram; the 58th is
// storageBlocks, which follows the record arm's guard (ProgramArtifacts.h).
EXPECT_EQ(ProgramArtifactsVisitedFieldCount<LinkArtifacts>(), 58u);
EXPECT_EQ(ProgramArtifactsVisitedFieldCount<StorageBlockReflection>(), 3u);
#else
EXPECT_EQ(ProgramArtifactsVisitedFieldCount<LinkArtifacts>(), 57u);
#endif
EXPECT_EQ(ProgramArtifactsVisitedFieldCount<SpirvArtifacts>(), 8u);
EXPECT_EQ(ProgramArtifactsVisitedFieldCount<ResourceReflection>(), 14u);
EXPECT_EQ(ProgramArtifactsVisitedFieldCount<XfbVarying>(), 11u);
@@ -81,14 +81,10 @@ namespace {
EXPECT_EQ(CountFields<TypeFacts>(), 20u);
EXPECT_EQ(CountFields<ResourceReflection>(), 14u);
EXPECT_EQ(CountFields<XfbVarying>(), 11u);
#if MOBILEGL_BUILD_RECORD_ARM
// P7 OQ-8 added LinkArtifacts::storageBlocks and its own three-field table; since P13 W5
// the member follows the record arm (ProgramArtifacts.h), which every library builds.
EXPECT_EQ(CountFields<StorageBlockReflection>(), 3u);
EXPECT_EQ(CountFields<LinkArtifacts>(), 58u);
#else
EXPECT_EQ(CountFields<LinkArtifacts>(), 57u);
#endif
EXPECT_EQ(CountFields<SpirvArtifacts>(), 8u);
}
@@ -24,18 +24,14 @@ namespace MobileGL::MG_Test {
class ScopedMonolithFrontendArm {
public:
ScopedMonolithFrontendArm() : m_savedTable(MG_Backend::gBackendFunctionsTable) {
#if MOBILEGL_BUILD_RECORD_ARM
m_saved = MG_Config::MonolithTakesRecordArm;
MG_Config::MonolithTakesRecordArm = false;
if (MG_Config::Transport == MG_Config::TransportMode::Monolith && MG_Backend::pActiveBackendObject) {
MG_Backend::gBackendFunctionsTable = MG_Backend::pActiveBackendObject->GetBackendFunctions();
}
#endif
}
~ScopedMonolithFrontendArm() {
#if MOBILEGL_BUILD_RECORD_ARM
MG_Config::MonolithTakesRecordArm = m_saved;
#endif
MG_Backend::gBackendFunctionsTable = m_savedTable;
}
ScopedMonolithFrontendArm(const ScopedMonolithFrontendArm&) = delete;
@@ -11,9 +11,7 @@
#include <Config.h>
#include <MG_Util/Debug/Log.h>
#if MOBILEGL_BUILD_RECORD_ARM
#include <MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.h>
#endif
#include <algorithm>
#include <cstring>
@@ -2479,7 +2477,6 @@ namespace MobileGL::MG_Util::SelfTest {
return findings;
}
#if MOBILEGL_BUILD_RECORD_ARM
Optional<DriverBugFinding> DescribeDepthStencilResolvePassBug(
const MG_Backend::DirectVulkan::WireDepthResolveProbeMeasurement& measurement, Bool shaderStencilExport) {
using namespace MG_Backend::DirectVulkan;
@@ -2546,5 +2543,4 @@ namespace MobileGL::MG_Util::SelfTest {
}
return findings;
}
#endif
} // namespace MobileGL::MG_Util::SelfTest
@@ -9,9 +9,7 @@
#pragma once
#include <Includes.h>
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
#if MOBILEGL_BUILD_RECORD_ARM
#include <MG_Backend/DirectVulkan/Renderer/WireDepthResolveArm.h>
#endif
namespace MobileGL::MG_Util::SelfTest {
// ===================== KNOWN DRIVER BUGS =====================
@@ -347,7 +345,6 @@ namespace MobileGL::MG_Util::SelfTest {
// so an unaffected device renders an empty section rather than a wall of "not affected".
Vector<DriverBugFinding> CollectGlesKnownDriverBugs(const MG_External::GLESFunctionsTable& gl);
#if MOBILEGL_BUILD_RECORD_ARM
// ===================== KNOWN VULKAN DRIVER BUGS =====================
//
// The DirectVulkan (Magma) backend's table, under the rules above: a control per probe, an
@@ -376,5 +373,4 @@ namespace MobileGL::MG_Util::SelfTest {
// Every known driver bug this Vulkan driver actually has.
Vector<DriverBugFinding> CollectVulkanKnownDriverBugs(const VulkanDriverBugProbeContext& context);
#endif
} // namespace MobileGL::MG_Util::SelfTest
-2
View File
@@ -3028,7 +3028,6 @@ namespace MobileGL::MG_Util::SelfTest {
graphicsQueueFamilyIndex, deviceExtensions, features,
vkGetPhysicalDeviceFeatures2Fn, vkGetPhysicalDeviceProperties2Fn);
#if MOBILEGL_BUILD_RECORD_ARM
// The "Known Driver Bugs" section's Vulkan table (DriverBugProbes.h): each probe makes its
// own throwaway device on the physical device picked above.
{
@@ -3040,7 +3039,6 @@ namespace MobileGL::MG_Util::SelfTest {
bugContext.deviceExtensions = &deviceExtensions;
builder.report.knownDriverBugs = CollectVulkanKnownDriverBugs(bugContext);
}
#endif
if (HasVkExtension(deviceExtensions, VK_KHR_DRAW_INDIRECT_COUNT_EXTENSION_NAME)) {
builder.Pass("VK_KHR_draw_indirect_count",
+10 -3
View File
@@ -23,6 +23,9 @@ RULES = (
'MOBILEGL_PIPE_LEGACY_MEMOS)'),
(re.compile(r'\bMGB_CTX(?:_LIVE|_IDENTITY)?\b'),
'MGB_CTX is back; backends read MG_Pipe::gPipeInputs directly since P13 W3b'),
(re.compile(r'^\s*#\s*(?:if|ifdef|ifndef|elif)\b.*\bMOBILEGL_BUILD_RECORD_ARM\b'),
'a preprocessor conditional names MOBILEGL_BUILD_RECORD_ARM, folded away at P13 W5 S8 - the '
'record arm is in every library; a transport-only block takes MOBILEGL_BUILD_DISAGGREGATED'),
)
@@ -49,12 +52,15 @@ def self_test():
planted = [('planted.cpp', '#if MOBILEGL_PIPE_PUSH\nx();\n#endif\n'),
('planted.h', ' # elif !MOBILEGL_PIPE_LEGACY_MEMOS && FOO\n'),
('planted.inc', 'auto v = MGB_CTX->GetX();\n'),
('planted2.cpp', 'if (MGB_CTX_LIVE) {}\n')]
('planted2.cpp', 'if (MGB_CTX_LIVE) {}\n'),
('planted3.cpp', '#if MOBILEGL_BUILD_RECORD_ARM && MOBILEGL_PIPE_POISON\n'),
('planted4.h', '#elif !MOBILEGL_BUILD_RECORD_ARM\n')]
for item in planted:
assert scan([item]), f'the gate missed a planted line: {item}'
clean = [('clean.cpp', 'features.PipePush = QueryEnvUint64("MOBILEGL_PIPE_PUSH", 0x3fff);\n'),
('clean.h', '// MOBILEGL_PIPE_PUSH=0x7f was the P2 mask\n'),
('clean.inc', 'MG_Pipe::gPipeInputs.GetX();\n')]
('clean.inc', 'MG_Pipe::gPipeInputs.GetX();\n'),
('clean2.cpp', '// MOBILEGL_BUILD_RECORD_ARM was W5\'s temporary guard\n')]
for item in clean:
assert not scan([item]), f'the gate fired on a legitimate line: {item}'
print(f'retired switches self-test: {len(planted)} planted lines caught, {len(clean)} clean lines left alone')
@@ -68,7 +74,8 @@ def main():
print(f'::error::{line}')
if findings:
return 1
print('retired switches: none of MOBILEGL_PIPE_PUSH / MOBILEGL_PIPE_LEGACY_MEMOS (as #if) or MGB_CTX in source')
print('retired switches: none of MOBILEGL_PIPE_PUSH / MOBILEGL_PIPE_LEGACY_MEMOS / MOBILEGL_BUILD_RECORD_ARM '
'(as #if) or MGB_CTX in source')
return 0
+1 -21
View File
@@ -82,9 +82,7 @@ FLOW_STOP = re.compile(r"^\s*(?:case\b|default\s*:|break\s*;|return\b|continue\s
PP_OPEN = re.compile(r"^\s*#\s*(?:if|ifdef|ifndef)\b")
PP_CLOSE = re.compile(r"^\s*#\s*endif\b")
PP_STOP = re.compile(r"^\s*#\s*(?:else|elif)\b")
# P13 W5: the record arm's own guard, MOBILEGL_BUILD_RECORD_ARM, is the same build-selecting kind of
# opener (it equals the disaggregated build until W5's flip and is unifdef'd away after it).
PP_DISAGGREGATED = re.compile(r"^\s*#\s*(?:if|ifdef)\s+MOBILEGL_BUILD_(?:DISAGGREGATED|RECORD_ARM)\s*$")
PP_DISAGGREGATED = re.compile(r"^\s*#\s*(?:if|ifdef)\s+MOBILEGL_BUILD_DISAGGREGATED\s*$")
def strip_code(text: str) -> str:
@@ -361,24 +359,6 @@ SELF_TEST_FIXTURES = (
"#endif\n"
" WireDeclineTally::Count(WireDeclineSite::GhostRow);\n"
"}\n", 1, NO_LOG),
("log only on the other side of a RECORD_ARM #else", _DEF_GHOST,
"void F(int x) {\n"
"#if MOBILEGL_BUILD_RECORD_ARM\n"
" MGLOG_W(\"x\");\n"
"#else\n"
" MGLOG_D(\"compiled away\");\n"
"#endif\n"
" WireDeclineTally::Count(WireDeclineSite::GhostRow);\n"
"}\n", 1, NO_LOG),
("count under its log across a column-0 RECORD_ARM #if/#endif", _DEF_GHOST,
"void F(int x) {\n"
" if (x) {\n"
" MGLOG_W(\"x\");\n"
"#if MOBILEGL_BUILD_RECORD_ARM\n"
" WireDeclineTally::Count(WireDeclineSite::GhostRow);\n"
"#endif\n"
" }\n"
"}\n", 0, "0 unlogged, 0 unknown"),
("log only under a #if 0 above the Count", _DEF_GHOST,
"void F(int x) {\n"
" if (x) {\n"