[Refactor] (P13 W5 S3): record-arm declarations, role-guard stubs and the T0 split in the backends.

This commit is contained in:
2026-10-06 11:29:37 -04:00
parent c04347ef88
commit e544121eed
11 changed files with 176 additions and 23 deletions
+4 -4
View File
@@ -10,7 +10,7 @@
#include <Includes.h>
#include "MG_State/GLState/TextureState/TextureEnum.h"
#include <MG_Util/Damage/Damage.h>
#if MOBILEGL_BUILD_DISAGGREGATED
#if MOBILEGL_BUILD_RECORD_ARM
#include <MG_Pipe/MGPipeHandles.h>
#endif
@@ -37,19 +37,19 @@ namespace MobileGL {
struct CopyImageEndpoint {
SharedPtr<MG_State::GLState::ITextureObject> Texture;
SharedPtr<MG_State::GLState::RenderbufferObject> Renderbuffer;
#if MOBILEGL_BUILD_DISAGGREGATED
#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_DISAGGREGATED
#if MOBILEGL_BUILD_RECORD_ARM
if (!MG_Pipe::MGPipeHandleIsNull(RenderbufferHandle)) return true;
#endif
return Renderbuffer != nullptr;
}
Bool Exists() const {
#if MOBILEGL_BUILD_DISAGGREGATED
#if MOBILEGL_BUILD_RECORD_ARM
if (!MG_Pipe::MGPipeHandleIsNull(TextureHandle)) return true;
if (!MG_Pipe::MGPipeHandleIsNull(RenderbufferHandle)) return true;
#endif
@@ -1180,7 +1180,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
ProcessDeferredBufferReleases();
#if MOBILEGL_BUILD_RECORD_ARM
#if MOBILEGL_BUILD_DISAGGREGATED
ProcessDeferredT0Retires(); // P11 B2
#endif
@@ -1426,7 +1426,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
ProcessDeferredBufferReleases();
#if MOBILEGL_BUILD_RECORD_ARM
#if MOBILEGL_BUILD_DISAGGREGATED
ProcessDeferredT0Retires(); // P11 B2
#endif
// THE COMPUTE PATH IS THE ONE THAT NEEDS THE FRONTEND-INDEXED UNIFORM PASS, and it
+10 -3
View File
@@ -1254,7 +1254,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
return StorageMatchesSize(resource, bufferObject.GetSize());
}
#if MOBILEGL_BUILD_DISAGGREGATED
#if MOBILEGL_BUILD_RECORD_ARM
// -------------------------------------------------------------------------------
// R-11 - THE SERVER'S OWN COPY OF THE STAGED BYTES. Package v1.
//
@@ -1302,7 +1302,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
ServerStaged().RequireCoverage(&resource, hostBase, start, end, site);
}
}
#endif // MOBILEGL_BUILD_RECORD_ARM
#if MOBILEGL_BUILD_DISAGGREGATED
// -------------------------------------------------------------------------------
// P11 B2 (T0) - WHEN AN IMPORTED STORE'S AHardwareBuffer MAY BE LET GO.
//
@@ -1373,6 +1375,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_t0Retired.push_back(retired);
SweepT0Retired(false);
}
#else
// No transport, no imported store: nothing ever needs retiring (P13 W5).
inline void RetireT0Import(GLESBufferResource&) {}
#endif // MOBILEGL_BUILD_DISAGGREGATED
// The four hostBytes sites read the same in both builds. The non-split expansion is the
@@ -3809,7 +3814,7 @@ 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_DISAGGREGATED
#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.
@@ -3817,8 +3822,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
// P11 B2: every retired T0 import's fence named work on the context that just ended,
// so all of them let go now. LIVE imports keep their reference: the next ensure
// imports them again on the new context (EnsureBufferResourceForHandle).
#if MOBILEGL_BUILD_DISAGGREGATED
ProcessDeferredT0Retires();
SweepT0Retired(/*contextGone=*/true);
#endif
#endif
}
@@ -3854,7 +3861,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
}
#if MOBILEGL_BUILD_RECORD_ARM
#if MOBILEGL_BUILD_DISAGGREGATED
// P11 B2: the T0 half of the deferred-release drain, run right after it at both sync
// points (ProcessDeferredBufferReleases itself moves verbatim, G5). Retired imports whose
// fence signalled let go of their AHardwareBuffer; a deferred T0 destroy is retired once
+6 -6
View File
@@ -476,7 +476,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (!EsprytSlotTablesEnabled()) return nullptr;
if (MG_Pipe::MGPipeHandleIsNull(handle)) return nullptr;
if (handle.Slot >= SlotTable::kMaxHandleSlot) {
#if MOBILEGL_BUILD_DISAGGREGATED
#if MOBILEGL_BUILD_RECORD_ARM
MG_Pipe::MGPipeSessionFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12
MG_Pipe::MGPipeFatalFamily::ProtocolCorruption,
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.HandleSlot\"} - "
@@ -488,7 +488,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
const Uint32 liveGen = m_slotTables.LiveGenAt(handle.Slot);
if (liveGen != 0 && liveGen > handle.Gen) {
#if MOBILEGL_BUILD_DISAGGREGATED
#if MOBILEGL_BUILD_RECORD_ARM
MG_Pipe::MGPipeSessionFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12
MG_Pipe::MGPipeFatalFamily::ProtocolCorruption,
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.Generation\"} - "
@@ -938,7 +938,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// AcquirePersistentMap or (FLUSH_EXPLICIT) publish only via FlushMappedRange.
Uint64 CurrentBufferMutationEpoch();
void BumpBufferMutationEpoch();
#if MOBILEGL_BUILD_DISAGGREGATED
#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
@@ -1071,7 +1071,7 @@ 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_DISAGGREGATED
#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
@@ -1366,7 +1366,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Deletes GL buffers whose owning frontend objects died (possibly on a
// thread without a current ES context). Called from draw-time sync.
void ProcessDeferredBufferReleases();
#if MOBILEGL_BUILD_RECORD_ARM
#if MOBILEGL_BUILD_DISAGGREGATED
// P11 B2: the T0 half of that drain - run right after it (retired AHardwareBuffers).
void ProcessDeferredT0Retires();
#endif
@@ -3670,7 +3670,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_backendRenderbufferObjects;
} // namespace RenderbufferImpl
#if MOBILEGL_BUILD_DISAGGREGATED
#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.
@@ -215,7 +215,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint32 cursor = 0;
};
#if MOBILEGL_BUILD_DISAGGREGATED
#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
@@ -563,7 +563,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkBuffer uboBuffer = VK_NULL_HANDLE;
VkDeviceSize uboRange = 0;
VkDescriptorSet set = VK_NULL_HANDLE;
#if MOBILEGL_BUILD_DISAGGREGATED
#if MOBILEGL_BUILD_RECORD_ARM
Uint64 wireStoreDestroyEpoch = 0;
#endif
};
@@ -90,7 +90,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VmaAllocation m_allocation = nullptr;
void* m_mappedData = nullptr;
VkDeviceSize m_size = 0;
#if MOBILEGL_BUILD_DISAGGREGATED
#if MOBILEGL_BUILD_RECORD_ARM
VkDevice m_device = VK_NULL_HANDLE;
VkDeviceMemory m_externalMemory = VK_NULL_HANDLE;
void* m_externalAhb = nullptr;
@@ -604,7 +604,7 @@ public:
TextureResource* SyncTextureAndGetDescriptor(
MG_State::GLState::ITextureObject& texture);
#if MOBILEGL_BUILD_DISAGGREGATED
#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
@@ -856,7 +856,7 @@ private:
void EraseTrackedTexture(const TextureIdentity& identity);
void PruneStaleTextureAliases(MG_State::GLState::ITextureObject* texture);
SizeT PruneDeadTextures();
#if MOBILEGL_BUILD_DISAGGREGATED
#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
@@ -22,11 +22,13 @@
#include "VkSamplerManager.h"
#include "VkTextureManager.h"
#include "VkTimerQueryManager.h"
#if MOBILEGL_BUILD_DISAGGREGATED
#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>
@@ -148,7 +150,7 @@ 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_DISAGGREGATED
#if MOBILEGL_BUILD_RECORD_ARM
VkBufferManager& GetWireBufferManager() { return m_bufferManager; }
Bool FlushWirePendingCommandsForTextureUpdate() {
// A new texture upload must not race graphics work that samples
@@ -629,7 +631,7 @@ 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_DISAGGREGATED
#if MOBILEGL_BUILD_RECORD_ARM
// Brackets this device's frame submissions for the GPU hang watch (LatchIfGpuHung).
GpuProgressMarkers m_progressMarkers;
struct WireImage {
@@ -777,7 +779,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint64 acquireSubmit = 0;
};
Vector<SharedImageHold> m_sharedImagesHeld;
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Remote::Server::SharedImages::ReadTracker m_sharedImageReads;
#endif
Vector<Uint64> m_sharedImageUseScratch;
// What one vkQueueSubmit carries beyond its own semaphores (AttachSharedImageSync).
struct SharedImageSubmitSync {
+14
View File
@@ -211,5 +211,19 @@ namespace MobileGL::MG_Pipe {
MGPipeFrontendKeyedRegistryScope& operator=(const MGPipeFrontendKeyedRegistryScope&) = delete;
};
#else
// P13 W5: no transport, no apply thread - the role guards have nothing to refuse, and the two
// scopes nothing to mark (the record arm calls all of them in every build).
inline void MGPipeRefuseAllocatorFromApplyThread(const char*) {}
inline Bool MGPipeApplierIsUnbarrieredApply() { return false; }
inline void MGPipeRefuseFrontendKeyedRegistryFromApplyThread(const char*) {}
class MagmaP7AllocatorDebtScope {
public:
MagmaP7AllocatorDebtScope() = default;
};
class MGPipeFrontendKeyedRegistryScope {
public:
MGPipeFrontendKeyedRegistryScope() = default;
};
#endif
} // namespace MobileGL::MG_Pipe
@@ -0,0 +1,20 @@
# P13 W5 S3, round 1: the record arm's declarations (headers).
# Magma renderer: progress markers and the wire render-pass table are the record arm's; the shared-image
# registry is the transport's.
nest | MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h | #include <MG_Remote/Server/SharedImageRegistry.h> | #include <MG_Remote/Server/SharedImageRegistry.h>
open | MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h | #include "GpuProgressMarkers.h"
open | MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h | VkBufferManager& GetWireBufferManager()
open | MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h | GpuProgressMarkers m_progressMarkers;
nest | MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h | MG_Remote::Server::SharedImages::ReadTracker m_sharedImageReads; | MG_Remote::Server::SharedImages::ReadTracker m_sharedImageReads;
open | MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h | Bool ReadUnbackedWireLevel(
open | MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h | Bool SyncWireTextureShape(
open | MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.h | VkDeviceMemory m_externalMemory
open | MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h | struct WireImageViewKey {
open | MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h | Uint64 wireStoreDestroyEpoch = 0;
# Espryt managers: the record-keyed twin tables, the draw-input requests, the buffer twin's record fields,
# and the per-object death release (the monolith port drops its twins through it too).
open | MobileGL/MG_Backend/DirectGLES/Managers.h | "MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.HandleSlot\"} - "
open | MobileGL/MG_Backend/DirectGLES/Managers.h | "MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.Generation\"} - "
open | MobileGL/MG_Backend/DirectGLES/Managers.h | Uint64 BufferMutationEpochBumpsOffTheApplyThread();
open | MobileGL/MG_Backend/DirectGLES/Managers.h | Bool serverGpuWritten = false;
open | MobileGL/MG_Backend/DirectGLES/Managers.h | Bool ReleaseTwinsForWireObjectDeath(
@@ -0,0 +1,108 @@
#!/usr/bin/env python3
"""P13 W5 S3: hand-directed guard edits, by content rather than line number.
w5_regate.py <ops-file>
Each non-comment line of the ops file is one operation (fields separated by ' | '):
open | <path> | <text> the `#if MOBILEGL_BUILD_DISAGGREGATED` group whose first body line
contains <text> becomes `#if MOBILEGL_BUILD_RECORD_ARM`
(with its #else / #endif comments)
nest | <path> | <first> | <last> wrap the lines from the one containing <first> through the one
containing <last> (searched after <first>) in
`#if MOBILEGL_BUILD_DISAGGREGATED` / `#endif`
line | <path> | <text> | <new> replace the one line containing <text> with <new> verbatim
Every <text> must match exactly one place (for `open`: exactly one group).
"""
import pathlib
import re
import sys
OLD = 'MOBILEGL_BUILD_DISAGGREGATED'
NEW = 'MOBILEGL_BUILD_RECORD_ARM'
DIRECTIVE = re.compile(r'^\s*#\s*(if|ifdef|ifndef|elif|else|endif)\b(.*)$')
def load(path):
raw = pathlib.Path(path).read_bytes().decode('utf-8')
return '\r\n' in raw, raw.replace('\r\n', '\n').split('\n')
def save(path, crlf, lines):
text = '\n'.join(lines)
pathlib.Path(path).write_bytes((text.replace('\n', '\r\n') if crlf else text).encode('utf-8'))
def groups(lines):
stack, out = [], []
for i, line in enumerate(lines):
m = DIRECTIVE.match(line)
if not m:
continue
kind = m.group(1)
if kind in ('if', 'ifdef', 'ifndef'):
stack.append({'open': i, 'branches': []})
elif kind in ('elif', 'else'):
if stack:
stack[-1]['branches'].append(i)
elif stack:
g = stack.pop()
g['close'] = i
out.append(g)
return out
def op_open(lines, text):
hits = []
for g in groups(lines):
if not re.match(r'^\s*#\s*if\s+' + OLD + r'\s*(//.*)?$', lines[g['open']]):
continue
end = g['branches'][0] if g['branches'] else g['close']
if any(text in l for l in lines[g['open'] + 1:end]):
hits.append(g)
# The innermost group that contains the text: drop any group that contains another hit.
hits = [g for g in hits if not any(h is not g and g['open'] < h['open'] and h['close'] < g['close'] for h in hits)]
if len(hits) != 1:
raise SystemExit('open: %d groups contain %r' % (len(hits), text))
g = hits[0]
for i in [g['open']] + g['branches'] + [g['close']]:
lines[i] = lines[i].replace(OLD, NEW)
def find_one(lines, text, start=0):
idx = [i for i in range(start, len(lines)) if text in lines[i]]
if not idx:
raise SystemExit('no line contains %r' % text)
if start == 0 and len(idx) != 1:
raise SystemExit('%d lines contain %r' % (len(idx), text))
return idx[0]
def main():
ops = [l for l in pathlib.Path(sys.argv[1]).read_text(encoding='utf-8').splitlines()
if l.strip() and not l.lstrip().startswith('#')]
files = {}
for op in ops:
parts = [p.strip() for p in op.split(' | ')]
kind, path = parts[0], parts[1]
if path not in files:
files[path] = load(path)
crlf, lines = files[path]
if kind == 'open':
op_open(lines, parts[2])
elif kind == 'nest':
a = find_one(lines, parts[2])
b = find_one(lines, parts[3], a)
lines[b + 1:b + 1] = ['#endif']
lines[a:a] = ['#if ' + OLD]
elif kind == 'line':
a = find_one(lines, parts[2])
lines[a] = parts[3]
else:
raise SystemExit('unknown op ' + kind)
for path, (crlf, lines) in files.items():
save(path, crlf, lines)
print('applied', len(ops), 'op(s) to', len(files), 'file(s)')
if __name__ == '__main__':
main()