mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-14 07:08:32 +09:00
[Fix, Test] (MG_State, MG_Backend, MG_Impl, MG_Test): review wave - hand the mapping back instead of renewing it, retire an immutable ES store on respecify, and tag glTexStorage2D levels too
This commit is contained in:
@@ -358,8 +358,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// Require working fences: recycling is gated on the frame-completion
|
||||
// watermark, which only advances if Present can insert/poll fences.
|
||||
return g_GLESFuncs.glFenceSync != nullptr && g_GLESFuncs.glGetSynciv != nullptr &&
|
||||
r.id != 0 && !r.persistentMapped && r.contextGeneration == g_bufferContextGeneration &&
|
||||
r.storageInitialized && r.storageSize > 0 && r.storageSize <= kMaxPoolableBufferBytes;
|
||||
r.id != 0 && !r.persistentMapped && !r.immutableStorage &&
|
||||
r.contextGeneration == g_bufferContextGeneration && r.storageInitialized &&
|
||||
r.storageSize > 0 && r.storageSize <= kMaxPoolableBufferBytes;
|
||||
}
|
||||
|
||||
// Retire a buffer id into the pool (owning thread; caller verified IsPoolable).
|
||||
@@ -555,6 +556,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
resource = created.get();
|
||||
bufferObject.SetBackendResource(std::move(created));
|
||||
}
|
||||
// Before the generation is stamped, not after: everything on the resource
|
||||
// describes a context that is gone, and the idempotency check below would
|
||||
// otherwise hand the caller the dead context's mapped pointer.
|
||||
if (resource->contextGeneration != g_bufferContextGeneration) {
|
||||
resource->id = 0;
|
||||
resource->persistentMapped = false;
|
||||
resource->persistentPtr = nullptr;
|
||||
resource->immutableStorage = false;
|
||||
resource->storageInitialized = false;
|
||||
resource->storageSize = 0;
|
||||
}
|
||||
resource->contextGeneration = g_bufferContextGeneration;
|
||||
|
||||
if (resource->persistentMapped && resource->persistentPtr && resource->storageSize == size) {
|
||||
@@ -564,9 +576,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// Need a fresh id: glBufferStorage fails on a buffer that already has
|
||||
// immutable storage, and any prior mutable store is replaced anyway.
|
||||
if (resource->id != 0) {
|
||||
ScrubBufferBindingShadowsForId(resource->id);
|
||||
NoteBufferIdDeleted(resource->id);
|
||||
g_GLESFuncs.glDeleteBuffers(1, &resource->id);
|
||||
resource->id = 0;
|
||||
resource->immutableStorage = false;
|
||||
}
|
||||
g_GLESFuncs.glGenBuffers(1, &resource->id);
|
||||
if (resource->id == 0) return nullptr;
|
||||
@@ -578,6 +591,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
g_GLESFuncs.glBufferStorageEXT(TempBufferTarget, static_cast<GLsizeiptr>(size), initial,
|
||||
GL_MAP_WRITE_BIT | kMapPersistentBit | kMapCoherentBit |
|
||||
kDynamicStorageBit);
|
||||
// Set as soon as the store exists, not once the map succeeds: the failure
|
||||
// path below leaves this id holding immutable storage, and whoever touches
|
||||
// it next has to know that glBufferData cannot redefine it.
|
||||
resource->immutableStorage = true;
|
||||
void* ptr = g_GLESFuncs.glMapBufferRange(TempBufferTarget, 0, static_cast<GLsizeiptr>(size),
|
||||
GL_MAP_WRITE_BIT | kMapPersistentBit | kMapCoherentBit);
|
||||
if (!ptr) {
|
||||
@@ -603,29 +620,36 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
void Ops_Respecify(BufferObject& bufferObject) {
|
||||
auto* resource = ResourceOf(bufferObject);
|
||||
if (!resource) return; // lazy: EnsureBufferResource full-uploads on creation
|
||||
if (resource->persistentMapped) {
|
||||
// The frontend writes straight into the storage mapped here, and it
|
||||
// renewed that mapping for the redefined store before writing to it
|
||||
// (BufferObject::RedefineStorage), so the new contents already are
|
||||
// where a respecification would put them.
|
||||
if (bufferObject.IsBackendPersistentMapped()) return;
|
||||
// Renewal declined (a zero-sized store, or the map could not be
|
||||
// retaken): the buffer is back on its CPU shadow and needs an ordinary
|
||||
// store again. Not this id's, though - it carries IMMUTABLE storage
|
||||
// (glBufferStorageEXT), which glBufferData below would refuse. Drop it
|
||||
// and let the lazy EnsureBufferResource path mint a mutable one with a
|
||||
// full upload; nothing points into the old mapping any more, because
|
||||
// the frontend has just given the adoption back.
|
||||
// The frontend hands an adopted mapping back before it redefines the store
|
||||
// (BufferObject::RedefineStorage), so a resource that still carries the
|
||||
// persistent state here describes the OLD store - and its storage is
|
||||
// IMMUTABLE (glBufferStorageEXT), which the glBufferData below cannot
|
||||
// respecify and which the driver would refuse in silence. Retire the id so
|
||||
// EnsureBufferResource mints a mutable one, with a full upload from the
|
||||
// shadow the frontend has just filled.
|
||||
//
|
||||
// Keyed on the STORAGE, not on persistentMapped: a glMapBufferRange that
|
||||
// failed after its glBufferStorageEXT succeeded clears persistentMapped and
|
||||
// still leaves an immutable store behind, and that one reached glBufferData.
|
||||
if (resource->immutableStorage) {
|
||||
resource->persistentMapped = false;
|
||||
resource->persistentPtr = nullptr;
|
||||
if (resource->id != 0 && CanTouchGLNow() &&
|
||||
resource->contextGeneration == g_bufferContextGeneration) {
|
||||
ScrubBufferBindingShadowsForId(resource->id);
|
||||
NoteBufferIdDeleted(resource->id);
|
||||
g_GLESFuncs.glDeleteBuffers(1, &resource->id);
|
||||
resource->id = 0;
|
||||
resource->immutableStorage = false;
|
||||
}
|
||||
resource->id = 0;
|
||||
// Off the context thread the id cannot be deleted here, and dropping it
|
||||
// would leak an immutable, persistently mapped store. It stays put, and
|
||||
// stays flagged, until EnsureBufferResource retires it on the thread
|
||||
// that owns the context.
|
||||
resource->storageInitialized = false;
|
||||
resource->storageSize = 0;
|
||||
resource->pendingRespecify = true;
|
||||
resource->pendingRanges.clear();
|
||||
return;
|
||||
}
|
||||
if (!CanTouchGLNow() || resource->id == 0 ||
|
||||
resource->contextGeneration != g_bufferContextGeneration) {
|
||||
@@ -922,6 +946,21 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// frontend re-acquires a fresh one on its next map.
|
||||
resource->persistentMapped = false;
|
||||
resource->persistentPtr = nullptr;
|
||||
resource->immutableStorage = false;
|
||||
}
|
||||
|
||||
// An immutable store nothing maps any more: a respecification of a buffer that
|
||||
// had been persistently mapped, which Ops_Respecify could not retire because it
|
||||
// ran off the context thread. glBufferData cannot redefine it, so it is retired
|
||||
// here, on the thread that can, and the id is re-minted below.
|
||||
if (resource->immutableStorage && !resource->persistentMapped && resource->id != 0) {
|
||||
NoteBufferIdDeleted(resource->id);
|
||||
g_GLESFuncs.glDeleteBuffers(1, &resource->id);
|
||||
resource->id = 0;
|
||||
resource->immutableStorage = false;
|
||||
resource->storageInitialized = false;
|
||||
resource->storageSize = 0;
|
||||
resource->pendingRespecify = true;
|
||||
}
|
||||
|
||||
// Zero-copy coherent persistent buffer: the app writes straight into the
|
||||
|
||||
@@ -286,6 +286,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// context loss.
|
||||
Bool persistentMapped = false;
|
||||
void* persistentPtr = nullptr;
|
||||
// The GL store behind `id` was created with glBufferStorageEXT and is
|
||||
// therefore IMMUTABLE - glBufferData cannot respecify it and it must never be
|
||||
// recycled through the size-keyed buffer pool. Tracked separately from
|
||||
// persistentMapped because the two come apart: a glMapBufferRange that fails
|
||||
// after its glBufferStorageEXT succeeded leaves immutable storage behind with
|
||||
// no map, and a respecification then has to retire the id rather than hand it
|
||||
// to glBufferData, which the driver would silently refuse.
|
||||
Bool immutableStorage = false;
|
||||
};
|
||||
|
||||
// Registered as the frontend's BufferBackendOps at backend init and on
|
||||
|
||||
@@ -379,23 +379,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
BumpSliceEpoch(*resource);
|
||||
// Any cached streaming slice refers to the previous contents.
|
||||
resource->transientFrameSerial = 0;
|
||||
if (resource->persistentMapped) {
|
||||
if (bufferObject.IsBackendPersistentMapped()) {
|
||||
// The frontend renewed its adoption of this storage for the redefined
|
||||
// store before writing a byte of it (BufferObject::RedefineStorage), so
|
||||
// the new contents are already HERE and there is no second copy to
|
||||
// update. Swapping the storage is what must not happen: the mapping the
|
||||
// frontend holds, and every read that resolves through it, would keep
|
||||
// addressing the storage being released - which is how a transform
|
||||
// feedback capture came to be written to one buffer and read back out
|
||||
// of another.
|
||||
return;
|
||||
}
|
||||
// The renewal did not happen (a zero-sized store, or the storage could not
|
||||
// be created): the frontend is back on its CPU shadow, so this is an
|
||||
// ordinary resident buffer again and the handling below applies.
|
||||
resource->persistentMapped = false;
|
||||
}
|
||||
// Redefining the store hands any adopted mapping back to the CPU shadow
|
||||
// (BufferObject::RedefineStorage), so a buffer that reaches here persistent-mapped
|
||||
// is an ordinary resident one again: it needs the busy-tracking and conditional
|
||||
// orphan below, and the next AcquirePersistentMap has to mint storage for the new
|
||||
// store rather than hand back a mapping of the old one.
|
||||
resource->persistentMapped = false;
|
||||
if (!resource->buffer.IsValid()) {
|
||||
return; // streaming-only resource: shadow + serial are enough
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user