[WIP] Mesa-style buffer overhaul: resource abstraction + immediate transfer ops

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-08 14:47:51 +08:00
co-authored by Claude Fable 5
parent 2ed96e9678
commit b8ffd25148
15 changed files with 1048 additions and 642 deletions
@@ -33,6 +33,38 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool transientPersistentMapping = false;
};
// The DirectVulkan storage behind one frontend buffer (pipe_resource analogue).
// Owned (refcounted) by the frontend BufferObject; the manager holds only weak
// references (for shutdown) plus strong references on deferred-release lists.
class VkBufferResource : public MG_State::GLState::BackendBufferResource {
public:
~VkBufferResource() override = default;
// Resident storage (may be invalid for streaming-only buffers).
VkBufferObject buffer;
VkDeviceSize storageSize = 0;
VkBufferUsageFlags usageFlags = 0;
// Frame serial of the last GPU reference; drives busy tracking.
Uint64 lastUseSerial = 0;
// Set when an immediate op could not be applied; forces a full re-upload
// on the next AcquireResidentSlice.
Bool pendingFullUpload = false;
// Cached transient (streaming) slice for the current frame.
BufferSlice transientSlice{};
Uint64 transientFrameSerial = 0;
Uint64 transientChangeSerial = 0;
VkDeviceSize transientSize = 0;
};
// Supplies a command buffer that is recording and outside any render pass,
// for staged buffer-range copies. Implemented by VulkanRenderer.
class IBufferCopyCommandProvider {
public:
virtual ~IBufferCopyCommandProvider() = default;
virtual VkCommandBuffer AcquireBufferCopyCommandBuffer() = 0;
};
class VkBufferManager {
public:
Bool Initialize(const VkBufferManagerInitInfo& initInfo);
@@ -41,35 +73,61 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Recreate all per-frame transient arenas
Bool RecreateTransientArenas(Uint32 frameCount);
void BeginFrame(Uint32 frameIndex);
// All previously submitted GPU work has completed (vkDeviceWaitIdle).
void NotifyDeviceIdle();
void SetCopyCommandProvider(IBufferCopyCommandProvider* provider);
Bool UploadTransient(BufferKind kind, Uint32 frameIndex, const void* data, VkDeviceSize size,
VkDeviceSize alignment, BufferSlice& outSlice);
Bool SyncResidentBuffer(BufferKind kind, const SharedPtr<MG_State::GLState::BufferObject>& bufferObject,
BufferSlice& outSlice);
void DowngradeResidentBufferToTransient(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject);
// Draw-time acquire for resident (device-storage) buffers: ensures the
// resource exists and is fully uploaded, marks it used this frame.
Bool AcquireResidentSlice(BufferKind kind, const SharedPtr<MG_State::GLState::BufferObject>& bufferObject,
BufferSlice& outSlice);
// Draw-time acquire for streamed buffers: uploads the whole shadow into
// the per-frame arena (cached by change serial), releasing any resident
// storage the buffer may still own.
Bool AcquireStreamedSlice(BufferKind kind, const SharedPtr<MG_State::GLState::BufferObject>& bufferObject,
BufferSlice& outSlice);
// 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 OnFlushMappedRange(MG_State::GLState::BufferObject& bufferObject, Range1D range,
Flags<BufferMappingAccessBit> appAccess);
void OnResourceDestroyed(SharedPtr<MG_State::GLState::BackendBufferResource>&& resource);
Uint64 GetFrameSerial() const { return m_frameSerial; }
// Busy = potentially referenced by GPU work that has not been fenced yet
// (including commands recorded for the current, unsubmitted frame).
Bool IsResourceBusy(const VkBufferResource& resource) const;
private:
struct ResidentBufferEntry {
WeakPtr<MG_State::GLState::BufferObject> aliveRef;
VkBufferObject buffer;
VkDeviceSize size = 0;
VkBufferUsageFlags usage = 0;
};
Bool InitializeTransientArenas();
static VkBufferUsageFlags GetVkBufferUsage(BufferKind kind);
void DeferResidentRelease(VkBufferObject&& buffer);
void CollectDeferredResidentReleases(Uint32 frameIndex);
void CollectResidentGarbageIfNeeded();
void CollectResidentGarbageNow();
void DestroyDeferredResidentReleases();
void DestroyResidentBuffers();
SharedPtr<VkBufferResource> GetOrCreateResource(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject);
static VkBufferResource* ResourceOf(MG_State::GLState::BufferObject& bufferObject);
Bool CreateResidentStorage(VkBufferResource& resource, VkDeviceSize size, VkBufferUsageFlags usage);
// Swap storage (conditional orphan) and refill it from the shadow copy.
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,
SizeT offset, SizeT size);
void DeferRelease(VkBufferObject&& buffer);
void CollectDeferredReleases(Uint32 frameIndex);
void DestroyAllDeferredReleases();
void TrackLiveResource(const SharedPtr<VkBufferResource>& resource);
void ReleaseAllLiveResources();
VkBufferManagerInitInfo m_initInfo{};
BufferArena m_transientUploadArena;
UnorderedMap<MG_State::GLState::BufferObject*, ResidentBufferEntry> m_residentBuffers;
Vector<Vector<VkBufferObject>> m_deferredResidentReleases;
IBufferCopyCommandProvider* m_copyProvider = nullptr;
Vector<Vector<VkBufferObject>> m_deferredBufferReleases;
Vector<Vector<SharedPtr<VkBufferResource>>> m_deferredResourceReleases;
Vector<WeakPtr<VkBufferResource>> m_liveResources;
Uint32 m_currentFrameIndex = 0;
Uint32 m_residentGcTick = 0;
Uint64 m_frameSerial = 1;
Uint64 m_completedSerialFloor = 0;
};
} // namespace MobileGL::MG_Backend::DirectVulkan