mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +09:00
[Refactor] (MG_State, MG_Backend): PipeResource storage layer + zero-copy coherent persistent maps
Introduce a Mesa pipe_resource-style PipeResource that owns a GL buffer's bytes and its backend GPU resource, abstracting WHERE the authoritative bytes live: - Shadow mode (non-persistent buffers): a CPU Vector; the backend keeps its own GPU copy in sync via BufferBackendOps, exactly as before. - Persistent mode (coherent GL_MAP_PERSISTENT maps): the backend's host-visible, COHERENT, persistently-mapped GPU memory is the single source of truth. The app writes into it directly, every reader resolves against it, and NO per-write backend transfer happens. The CPU shadow is released. BufferObject no longer owns a raw shadow Vector; it holds a PipeResource and exposes one accessor, MappedData(), that all readers go through. Every buffer-data consumer (UBO payload, PBO texture upload, indirect draws, resident/streamed uploads, both backends) was migrated from GetDataReadOnly()->data() to MappedData(), so a persistent buffer's readers see GPU memory - not a stale shadow. That stale-shadow inconsistency is what corrupted rendering (wrong UBOs -> misplaced/"lost" vertices) in the first zero-copy attempt (625c8a6, reverted in 896cafc); routing every consumer through one accessor makes it structurally impossible. Backends provide the map via BufferBackendOps::AcquirePersistentMap: - DirectVulkan: a HOST_VISIBLE|HOST_COHERENT (required, not just requested), persistently mapped resident VkBuffer carrying every usage, seeded from the shadow, never recreated; AcquireResidentSlice binds it directly. - DirectGLES: EXT_buffer_storage immutable persistent+coherent glMapBufferRange, falling back to the shadow when the extension is absent. Fixes the ~7GB GpuMemory OOM + 100%-CPU/ANR running modern Blaze3D Minecraft on both Magma and Espryt (per-draw whole-buffer re-upload of the coherent persistent ring buffer), without the coherency/stale-read hazards of the reverted attempt. BufferTest: zero-copy stress guard (15,360 draws -> 0 per-draw transfers, and every reader resolves to GPU memory) + a shadow-fallback test. Host suite: 203/203 pass. Device verification pending.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
// MobileGL - MobileGL/MG_State/GLState/BufferState/PipeResource.h
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
#include <MG_Util/Types.h>
|
||||
#include <bit>
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
// Opaque, refcounted handle to the backend's GPU storage for one buffer
|
||||
// (the driver-side resource). The active backend derives from it and attaches
|
||||
// its own payload (VkBufferResource / GLESBufferResource). Held by PipeResource.
|
||||
class BackendBufferResource {
|
||||
public:
|
||||
virtual ~BackendBufferResource() = default;
|
||||
};
|
||||
|
||||
// Mesa pipe_resource analogue for a GL buffer's storage. It owns the buffer's
|
||||
// bytes and its backend GPU resource, and abstracts WHERE the authoritative
|
||||
// bytes live so no caller has to branch on the mode:
|
||||
//
|
||||
// - Shadow mode (default, non-persistent buffers): the bytes live in a CPU
|
||||
// Vector (the shadow). GL writes mutate the shadow; the active backend keeps
|
||||
// its own GPU copy in sync via BufferBackendOps (glBufferData/SubData/...).
|
||||
//
|
||||
// - Persistent mode (coherent GL_MAP_PERSISTENT maps): the bytes live in the
|
||||
// backend's host-visible, COHERENT, persistently-mapped GPU memory. That GPU
|
||||
// buffer is the single source of truth - the app writes into it directly,
|
||||
// every read/write resolves against it, and NO per-write backend transfer
|
||||
// happens. The CPU shadow is released on adoption.
|
||||
//
|
||||
// Bytes() always returns a host-visible base pointer valid for [0, size) in both
|
||||
// modes, so readers/writers just call Bytes() (the size lives on the owning
|
||||
// BufferObject). (Named Bytes(), not Data(), to avoid colliding with the type
|
||||
// alias Data = Vector<Uint8> used for the shadow.)
|
||||
class PipeResource {
|
||||
public:
|
||||
Uint8* Bytes() { return m_gpuMapped != nullptr ? static_cast<Uint8*>(m_gpuMapped) : m_shadow->data(); }
|
||||
const Uint8* Bytes() const {
|
||||
return m_gpuMapped != nullptr ? static_cast<const Uint8*>(m_gpuMapped) : m_shadow->data();
|
||||
}
|
||||
|
||||
// True once the buffer's bytes have been adopted into backend GPU memory.
|
||||
Bool IsGpuResident() const { return m_gpuMapped != nullptr; }
|
||||
|
||||
// Shadow (re)allocation for non-persistent storage (glBufferData /
|
||||
// glBufferStorage before any persistent map). Mirrors the previous
|
||||
// power-of-two reserve + exact resize of the old m_dataPtr.
|
||||
void ResizeShadow(SizeT size) {
|
||||
m_shadow->reserve(std::bit_ceil(size == 0 ? SizeT{1} : size));
|
||||
m_shadow->resize(size);
|
||||
}
|
||||
// Direct shadow access, used only by the backend's upload-from-shadow path,
|
||||
// which never runs for a GPU-resident (persistent) buffer.
|
||||
Data& Shadow() { return *m_shadow; }
|
||||
const Data& Shadow() const { return *m_shadow; }
|
||||
|
||||
// Transition to persistent GPU residency: adopt the backend's coherent
|
||||
// mapped base as the source of truth and drop the CPU shadow. The caller
|
||||
// must have already seeded the GPU memory from the shadow (via the backend
|
||||
// AcquirePersistentMap op) before calling this.
|
||||
void AdoptPersistentMap(void* mappedBase) {
|
||||
m_gpuMapped = mappedBase;
|
||||
m_shadow->clear();
|
||||
m_shadow->shrink_to_fit();
|
||||
}
|
||||
|
||||
// Backend GPU resource, owned here in both modes.
|
||||
const SharedPtr<BackendBufferResource>& Backend() const { return m_backend; }
|
||||
void SetBackend(SharedPtr<BackendBufferResource> backend) { m_backend = std::move(backend); }
|
||||
SharedPtr<BackendBufferResource> ReleaseBackend() { return std::move(m_backend); }
|
||||
|
||||
private:
|
||||
SharedPtr<Data> m_shadow = MakeShared<Data>();
|
||||
void* m_gpuMapped = nullptr;
|
||||
SharedPtr<BackendBufferResource> m_backend;
|
||||
};
|
||||
} // namespace MobileGL::MG_State::GLState
|
||||
Reference in New Issue
Block a user