mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
[WIP] Mesa-style buffer overhaul: resource abstraction + immediate transfer ops
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -56,42 +56,81 @@ namespace MobileGL {
|
||||
Coherent = 0x80
|
||||
};
|
||||
|
||||
enum class BufferChangeBits : Uint8 {
|
||||
None = 0,
|
||||
DirtyBit = 1 << 0, // When not set, bits below are ignored and nothing should be synced to backend
|
||||
PreferReallocationBit =
|
||||
1 << 1, // <=> `glBufferData`; When set, ForbidInvalidationBit and ForbidUnsynchronizationBit are ignored
|
||||
ForbidInvalidationBit = 1 << 2, // Indidate that invalidation flags were not used during mapping, else we're
|
||||
// allowed to act as `GL_MAP_INVALIDATE_*` in backend
|
||||
ForbidUnsynchronizationBit = 1 << 3, // (the same description as above, but for unsynchronization)
|
||||
};
|
||||
|
||||
struct BufferChange {
|
||||
static constexpr int DEFAULT_RESERVED_DIRTY_RANGES_COUNT = 50;
|
||||
|
||||
Flags<BufferChangeBits> Bits = BufferChangeBits::None;
|
||||
VecRange1D DirtyRanges;
|
||||
};
|
||||
|
||||
namespace MG_State::GLState {
|
||||
class BufferObject;
|
||||
|
||||
// Opaque, refcounted handle to the backend's storage for one buffer
|
||||
// (the pipe_resource analogue). The frontend owns the reference; the
|
||||
// active backend derives from it and attaches its own payload.
|
||||
class BackendBufferResource {
|
||||
public:
|
||||
virtual ~BackendBufferResource() = default;
|
||||
};
|
||||
|
||||
// Immediate buffer transfer interface implemented by the active backend
|
||||
// (the pipe_context buffer-op analogue). Ops are invoked at GL call time,
|
||||
// right after the shadow copy has been updated; contents are always read
|
||||
// from the shadow so ops carry only ranges and flags.
|
||||
//
|
||||
// Every op must tolerate bufferObject.GetBackendResource() == nullptr:
|
||||
// resources are created lazily by the backend's draw/bind-time ensure
|
||||
// path, which performs a full upload from the shadow and thereby covers
|
||||
// all ops that happened before the resource existed.
|
||||
struct BufferBackendOps {
|
||||
// Storage (re)definition: glBufferData / glBufferStorage. The orphaning
|
||||
// point - the backend decides (busy-tracking) whether to swap storage
|
||||
// or write in place. Shadow already holds the new contents.
|
||||
void (*Respecify)(BufferObject& bufferObject) = nullptr;
|
||||
// Contents update of [offset, offset + size) from the shadow.
|
||||
void (*SubData)(BufferObject& bufferObject, SizeT offset, SizeT size) = nullptr;
|
||||
// Write-map flush (glUnmapBuffer / glFlushMappedBufferRange). Carries the
|
||||
// app's real mapping flags so the backend can honour INVALIDATE_* /
|
||||
// UNSYNCHRONIZED semantics per call instead of merging them.
|
||||
void (*FlushMappedRange)(BufferObject& bufferObject, Range1D range,
|
||||
Flags<BufferMappingAccessBit> appAccess) = nullptr;
|
||||
// Final release of the backend resource (called from ~BufferObject).
|
||||
// The backend defers actual destruction until the GPU is done with it.
|
||||
void (*OnDestroy)(SharedPtr<BackendBufferResource>&& resource) = nullptr;
|
||||
};
|
||||
|
||||
// Registered by the active backend at init, cleared at shutdown.
|
||||
// Null table (unit tests, benchmarks) => shadow-only state tracking.
|
||||
void SetBufferBackendOps(const BufferBackendOps* ops);
|
||||
const BufferBackendOps* GetBufferBackendOps();
|
||||
|
||||
class BufferObject {
|
||||
public:
|
||||
using TargetEnum = BufferTarget;
|
||||
|
||||
BufferObject(Uint externalIndex);
|
||||
~BufferObject();
|
||||
|
||||
BufferObject(const BufferObject&) = delete;
|
||||
BufferObject& operator=(const BufferObject&) = delete;
|
||||
|
||||
// Storage definition (single backend Respecify): glBufferData.
|
||||
void Respecify(SizeT size, const void* data);
|
||||
// Storage definition without contents; equivalent to Respecify(size, nullptr).
|
||||
void Resize(SizeT size);
|
||||
void AllocateImmutableStorage(SizeT size, const void* data, GLbitfield storageFlags);
|
||||
void UploadData(DataPtr data, SizeT atOffset);
|
||||
void SetUsage(BufferUsage usage);
|
||||
|
||||
void UploadData(DataPtr data, SizeT atOffset);
|
||||
void UploadSubData(DataPtr data, SizeT atOffset);
|
||||
void CopyDataFrom(const SharedPtr<BufferObject>& src, SizeT srcOffset, SizeT dstOffset, SizeT size);
|
||||
|
||||
void* AcquireMemory(Bool markMapped, Bool read, Bool write);
|
||||
void* AcquireMemoryRange(Range1D range, Flags<BufferMappingAccessBit> access);
|
||||
void ReleaseMemory();
|
||||
void FlushMemoryRange(SizeT offset, SizeT length);
|
||||
void MarkPersistentMappedRangeDirty();
|
||||
void UploadSubData(DataPtr data, SizeT atOffset);
|
||||
void CopyDataFrom(const SharedPtr<BufferObject>& src, SizeT srcOffset, SizeT dstOffset, SizeT size);
|
||||
void ClearDirty();
|
||||
|
||||
// Pushes the persistently-mapped write range to the backend; called by
|
||||
// backends at draw time (persistent maps mutate the shadow without API calls).
|
||||
void SyncPersistentMappedRange();
|
||||
// Shadow-only write used when the backend copies GPU results (e.g. ReadPixels
|
||||
// into a pixel-pack buffer) back into the frontend mirror. Does not issue a
|
||||
// backend op: the backend storage already holds these bytes.
|
||||
void WritebackFromBackend(DataPtr data, SizeT atOffset);
|
||||
|
||||
Bool IsMapped() const;
|
||||
Bool IsImmutableStorage() const;
|
||||
@@ -103,11 +142,18 @@ namespace MobileGL {
|
||||
Flags<BufferMappingAccessBit> GetMappingAccess() const;
|
||||
GLbitfield GetStorageFlags() const;
|
||||
Uint GetExternalIndex() const;
|
||||
const VecRange1D& GetDirtyRanges() const;
|
||||
Flags<BufferChangeBits> GetChangeBits() const;
|
||||
// Monotonic counter bumped on every shadow mutation; backends use it to
|
||||
// validate cached transient slices.
|
||||
Uint64 GetChangeSerial() const;
|
||||
|
||||
const SharedPtr<BackendBufferResource>& GetBackendResource() const;
|
||||
void SetBackendResource(SharedPtr<BackendBufferResource> resource);
|
||||
|
||||
private:
|
||||
void NotifyRespecify();
|
||||
void NotifySubData(SizeT offset, SizeT size);
|
||||
void NotifyFlushMappedRange(Range1D range, Flags<BufferMappingAccessBit> appAccess);
|
||||
|
||||
const Uint m_externalIndex = 0;
|
||||
SizeT m_size = 0;
|
||||
BufferUsage m_usage = BufferUsage::StaticDraw;
|
||||
@@ -116,11 +162,11 @@ namespace MobileGL {
|
||||
Flags<BufferMappingAccessBit> m_mappingAccess;
|
||||
Bool m_isImmutableStorage = false;
|
||||
GLbitfield m_storageFlags = 0;
|
||||
BufferChange m_change;
|
||||
Uint64 m_changeSerial = 0;
|
||||
Range1D m_mappedRange;
|
||||
Vector<Uint8> m_stagingData;
|
||||
Bool m_ownsStagingData;
|
||||
SharedPtr<BackendBufferResource> m_backendResource;
|
||||
};
|
||||
} // namespace MG_State::GLState
|
||||
} // namespace MobileGL
|
||||
|
||||
Reference in New Issue
Block a user