Merge branch 'feat/disaggregated' into p5/c1

This commit is contained in:
2026-09-16 05:35:48 -04:00
3 changed files with 72 additions and 7 deletions
@@ -67,6 +67,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
}
void Ops_ResidentSubData(BufferObject& bufferObject, SizeT offset, DataPtr data) {
if (g_activeBufferManager) {
g_activeBufferManager->OnResidentSubData(bufferObject, offset, data);
}
}
void Ops_FlushMappedRange(BufferObject& bufferObject, Range1D range,
Flags<BufferMappingAccessBit> appAccess) {
if (g_activeBufferManager) {
@@ -78,11 +84,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// host-visible GPU storage (EnsureGpuResidentStorage adopts it when the buffer is
// bound as a shader storage buffer), so nothing needs copying - but coherence only
// says the writes are visible once they have happened, so the work has to retire
// first.
// first, including copies already submitted by a sync-point flush.
void Ops_ReadbackFromGpu(BufferObject& bufferObject) {
(void)bufferObject;
if (pVulkanRenderer) {
pVulkanRenderer->FinishPendingGpuWork();
pVulkanRenderer->WaitForSubmitIndex(
pVulkanRenderer->GetSyncPointSubmitIndex(), UINT64_MAX, true);
}
}
@@ -106,6 +113,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const BufferBackendOps g_vulkanBufferBackendOps = {
.Respecify = Ops_Respecify,
.SubData = Ops_SubData,
.ResidentSubData = Ops_ResidentSubData,
.FlushMappedRange = Ops_FlushMappedRange,
.OnDestroy = Ops_OnDestroy,
.AcquirePersistentMap = Ops_AcquirePersistentMap,
@@ -378,13 +386,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true;
}
Bool VkBufferManager::StagedRangeCopy(VkBufferResource& resource, MG_State::GLState::BufferObject& bufferObject,
Bool VkBufferManager::StagedRangeCopy(VkBufferResource& resource, const void* data,
SizeT offset, SizeT size) {
if (!m_copyProvider) {
return false;
}
BufferSlice staging{};
if (!m_transientUploadArena.Upload(m_currentFrameIndex, bufferObject.MappedData() + offset,
if (!m_transientUploadArena.Upload(m_currentFrameIndex, data,
static_cast<VkDeviceSize>(size), 16, staging)) {
return false;
}
@@ -499,11 +507,32 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Busy partial write: stage + GPU copy preserves GL ordering within the
// frame and leaves bytes outside the range (possibly GPU-written, e.g.
// SSBO) intact. Fall back to a storage swap if staging is unavailable.
if (!StagedRangeCopy(*resource, bufferObject, offset, size)) {
if (!StagedRangeCopy(*resource, bufferObject.MappedData() + offset, offset, size)) {
SwapStorageAndUploadAll(*resource, bufferObject);
}
}
void VkBufferManager::OnResidentSubData(MG_State::GLState::BufferObject& bufferObject,
SizeT offset, DataPtr data) {
auto* resource = ResourceOf(bufferObject);
MOBILEGL_ASSERT(resource && resource->persistentMapped && resource->buffer.IsValid(),
"OnResidentSubData requires adopted Vulkan storage");
// The mapping is also the GPU's storage. Copy the supplied bytes onto the
// command timeline before touching it: earlier draws must keep seeing the
// old contents, including draws recorded but not yet submitted. The buffer
// cannot be orphaned because the application may hold its mapped pointer.
if (StagedRangeCopy(*resource, data.data, offset, data.size)) {
return;
}
// Allocation failure: a host write is safe only after all prior work retires.
if (pVulkanRenderer && pVulkanRenderer->WaitForSubmitIndex(
pVulkanRenderer->GetSyncPointSubmitIndex(), UINT64_MAX, true)) {
resource->buffer.Upload(data.data, data.size, offset);
} else {
MGLOG_E_ONCE("VkBufferManager::OnResidentSubData: ordered upload failed");
}
}
void VkBufferManager::OnFlushMappedRange(MG_State::GLState::BufferObject& bufferObject, Range1D range,
Flags<BufferMappingAccessBit> appAccess) {
auto* resource = ResourceOf(bufferObject);
@@ -536,7 +565,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return;
}
if (!StagedRangeCopy(*resource, bufferObject, offset, size)) {
if (!StagedRangeCopy(*resource, bufferObject.MappedData() + offset, offset, size)) {
SwapStorageAndUploadAll(*resource, bufferObject);
}
}
@@ -160,6 +160,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Immediate ops, dispatched from the frontend BufferBackendOps table.
void OnRespecify(MG_State::GLState::BufferObject& bufferObject);
void OnSubData(MG_State::GLState::BufferObject& bufferObject, SizeT offset, SizeT size);
void OnResidentSubData(MG_State::GLState::BufferObject& bufferObject, SizeT offset, DataPtr data);
void OnFlushMappedRange(MG_State::GLState::BufferObject& bufferObject, Range1D range,
Flags<BufferMappingAccessBit> appAccess);
void OnResourceDestroyed(SharedPtr<MG_State::GLState::BackendBufferResource>&& resource);
@@ -189,7 +190,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool SwapStorageAndUploadAll(VkBufferResource& resource, MG_State::GLState::BufferObject& bufferObject);
// Record a staging-slice copy into the resident storage, ordered against
// in-flight and already-recorded GPU work.
Bool StagedRangeCopy(VkBufferResource& resource, MG_State::GLState::BufferObject& bufferObject,
Bool StagedRangeCopy(VkBufferResource& resource, const void* data,
SizeT offset, SizeT size);
void DeferRelease(VkBufferObject&& buffer);
void CollectDeferredReleases(Uint32 frameIndex);
@@ -123,6 +123,41 @@ static void mobilegl_capture_record_request(void) {
if brace < 0:
raise SystemExit("eglSwapBuffers wrapper has no function body")
text = text[:brace + 1] + "\n mobilegl_capture_record_request();" + text[brace + 1:]
# A buffer copy consumes coherent mapped writes just like a draw. Upstream
# only commits shadows at draws, so Minecraft's staging-buffer copies could
# consume the previous frame's vertices before the current writes landed.
for name in ("glCopyBufferSubData", "glCopyNamedBufferSubData", "glNamedCopyBufferSubDataEXT"):
start = text.find(f"void APIENTRY {name}(")
if start < 0:
raise SystemExit(f"generated egltrace.cpp has no {name} wrapper to patch")
brace = text.index("{", start)
text = (text[:brace + 1] +
"\n GLMemoryShadow::commitAllWrites(gltrace::getContext(), trace::fakeMemcpy);" +
text[brace + 1:])
# Deleting a mapped buffer implicitly unmaps it. Retire its shadow before
# the real storage is freed; a later draw must not commit to a deleted map,
# and reuse of the GL name must not leave a dangling dirtyShadows entry.
retire_shadows = r'''
auto *_ctx = gltrace::getContext();
for (GLsizei i = 0; i < n; ++i) {
auto it = _ctx->sharedRes->bufferToShadowMemory.find(buffers[i]);
if (it != _ctx->sharedRes->bufferToShadowMemory.end()) {
if (it->second->getMapFlags() != 0) {
it->second->unmap(trace::fakeMemcpy);
}
_ctx->sharedRes->bufferToShadowMemory.erase(it);
}
}
'''
for name in ("glDeleteBuffers", "glDeleteBuffersARB"):
start = text.find(f"void APIENTRY {name}(")
if start < 0:
raise SystemExit(f"generated egltrace.cpp has no {name} wrapper to patch")
brace = text.index("{", start)
text = text[:brace + 1] + retire_shadows + text[brace + 1:]
generated.write_text(text, encoding="utf-8", newline="\n")
return generated