mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +09:00
[Fix] (Espryt): land a SubData into an adopted store as a GPU-ordered copy - the in-place coherent write tore the frames still reading the old section bytes
This commit is contained in:
@@ -783,6 +783,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
resource.storageInitialized = true;
|
||||
resource.pendingRespecify = false;
|
||||
resource.pendingRanges.clear();
|
||||
resource.pendingResidentWrites.clear();
|
||||
resource.syncedChangeSerial = bufferObject.GetChangeSerial();
|
||||
// A GROWN store keeps its indexed bindings, and BindBufferBaseCached skips a
|
||||
// rebind whenever the shadow already records this id at that index - so on a
|
||||
@@ -926,6 +927,49 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
}
|
||||
|
||||
// Land the app bytes queued for an ADOPTED store on the GPU timeline: staged
|
||||
// into the upload ring and delivered by glCopyBufferSubData. The destination
|
||||
// is the IMMUTABLE persistent store, which the driver can neither rename nor
|
||||
// ghost, so the copy is plain job ordering - after every in-flight reader,
|
||||
// before the next consumer - which is exactly glBufferSubData's contract.
|
||||
// (The in-place host write these bytes replaced tore the frames still
|
||||
// reading the old vertex data: one-frame wrong geometry during fast camera
|
||||
// movement.) Fallback: direct glBufferSubData - the adopted store carries
|
||||
// DYNAMIC_STORAGE, and immutability again forbids the whole-store ghost.
|
||||
void DrainResidentWritesNow(GLESBufferResource& resource, BufferObject& bufferObject) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
Vector<GLESBufferResource::PendingResidentWrite> writes;
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(resource.pendingMutex);
|
||||
if (resource.pendingResidentWrites.empty()) return;
|
||||
writes = std::move(resource.pendingResidentWrites);
|
||||
resource.pendingResidentWrites.clear();
|
||||
}
|
||||
const SizeT limit = resource.storageSize;
|
||||
const Bool ringUsable = UploadRingUsableNow();
|
||||
for (const auto& write : writes) {
|
||||
if (write.offset >= limit) continue;
|
||||
const SizeT size = std::min(write.bytes.size(), limit - write.offset);
|
||||
if (size == 0) continue;
|
||||
SizeT ringOffset = 0;
|
||||
if (ringUsable && size <= kUploadRingMaxBytes &&
|
||||
RingAllocate(g_uploadRing, size, ringOffset)) {
|
||||
Memcpy(g_uploadRing.store.mappedPtr + ringOffset, write.bytes.data(), size);
|
||||
BindBufferId(GL_COPY_READ_BUFFER, g_uploadRing.store.id);
|
||||
BindBufferId(GL_COPY_WRITE_BUFFER, resource.id);
|
||||
g_GLESFuncs.glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER,
|
||||
(GLintptr)ringOffset, (GLintptr)write.offset,
|
||||
(GLsizeiptr)size);
|
||||
} else {
|
||||
BindBufferId(TempBufferTarget, resource.id);
|
||||
g_GLESFuncs.glBufferSubData(TempBufferTarget, (GLintptr)write.offset, (GLsizeiptr)size,
|
||||
write.bytes.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EXT_buffer_storage bit values (same numeric values as the desktop ARB
|
||||
// tokens); defined locally so this compiles regardless of which GLES headers
|
||||
// expose the EXT tokens.
|
||||
@@ -1011,6 +1055,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(resource->pendingMutex);
|
||||
resource->pendingRanges.clear();
|
||||
resource->pendingResidentWrites.clear();
|
||||
}
|
||||
resource->syncedChangeSerial = bufferObject.GetChangeSerial();
|
||||
return ptr;
|
||||
@@ -1048,12 +1093,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
resource->storageSize = 0;
|
||||
resource->pendingRespecify = true;
|
||||
resource->pendingRanges.clear();
|
||||
resource->pendingResidentWrites.clear();
|
||||
return;
|
||||
}
|
||||
if (!CanTouchGLNow() || resource->id == 0 ||
|
||||
resource->contextGeneration != g_bufferContextGeneration) {
|
||||
resource->pendingRespecify = true;
|
||||
resource->pendingRanges.clear();
|
||||
resource->pendingResidentWrites.clear();
|
||||
return;
|
||||
}
|
||||
if (bufferObject.GetSize() == 0) {
|
||||
@@ -1061,6 +1108,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
resource->storageSize = 0;
|
||||
resource->pendingRespecify = false;
|
||||
resource->pendingRanges.clear();
|
||||
resource->pendingResidentWrites.clear();
|
||||
return;
|
||||
}
|
||||
RespecifyStorageNow(*resource, bufferObject);
|
||||
@@ -1101,6 +1149,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
resource->pendingRanges.Add({offset, offset + size});
|
||||
}
|
||||
|
||||
// App bytes for an ADOPTED store: queue them untouched-by-the-mapping; the
|
||||
// draw-time sync (or a readback) lands them GPU-ordered through
|
||||
// DrainResidentWritesNow. No GL here, so the op is thread-agnostic.
|
||||
void Ops_ResidentSubData(BufferObject& bufferObject, SizeT offset, DataPtr data) {
|
||||
auto* resource = ResourceOf(bufferObject);
|
||||
if (!resource || data.size == 0) return;
|
||||
const std::lock_guard<std::mutex> lock(resource->pendingMutex);
|
||||
auto& write = resource->pendingResidentWrites.emplace_back();
|
||||
write.offset = offset;
|
||||
const auto* bytes = static_cast<const Uint8*>(data.data);
|
||||
write.bytes.assign(bytes, bytes + data.size);
|
||||
}
|
||||
|
||||
void Ops_FlushMappedRange(BufferObject& bufferObject, Range1D range,
|
||||
Flags<BufferMappingAccessBit> appAccess) {
|
||||
auto* resource = ResourceOf(bufferObject);
|
||||
@@ -1174,8 +1235,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (!resource || resource->id == 0 || !resource->storageInitialized) return;
|
||||
if (!CanTouchGLNow() || resource->contextGeneration != g_bufferContextGeneration) return;
|
||||
if (resource->persistentMapped) {
|
||||
// Host writes to a persistent map must not race shader writes already queued
|
||||
// on this context. There is no backend copy to read back in this case.
|
||||
// Queued resident SubData bytes land first (GPU-ordered), then the
|
||||
// finish makes them - and any shader writes already queued on this
|
||||
// context - visible through the coherent mapping the reads use.
|
||||
// There is no backend copy to read back in this case.
|
||||
DrainResidentWritesNow(*resource, bufferObject);
|
||||
if (g_GLESFuncs.glFinish) g_GLESFuncs.glFinish();
|
||||
return;
|
||||
}
|
||||
@@ -1241,6 +1305,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Ops_SubData(bufferObject, offset, size);
|
||||
BumpBufferMutationEpoch();
|
||||
}
|
||||
void Ops_ResidentSubDataTracked(BufferObject& bufferObject, SizeT offset, DataPtr data) {
|
||||
Ops_ResidentSubData(bufferObject, offset, data);
|
||||
BumpBufferMutationEpoch();
|
||||
}
|
||||
void Ops_FlushMappedRangeTracked(BufferObject& bufferObject, Range1D range,
|
||||
Flags<BufferMappingAccessBit> appAccess) {
|
||||
Ops_FlushMappedRange(bufferObject, range, appAccess);
|
||||
@@ -1265,6 +1333,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
const BufferBackendOps g_glesBufferBackendOps = {
|
||||
.Respecify = Ops_RespecifyTracked,
|
||||
.SubData = Ops_SubDataTracked,
|
||||
.ResidentSubData = Ops_ResidentSubDataTracked,
|
||||
.FlushMappedRange = Ops_FlushMappedRangeTracked,
|
||||
.OnDestroy = Ops_OnDestroyTracked,
|
||||
.AcquirePersistentMap = Ops_AcquirePersistentMapTracked,
|
||||
@@ -1366,7 +1435,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (resource->id == 0) return false;
|
||||
// Zero-copy coherent persistent store: EnsureBufferResource's own early-out —
|
||||
// the app writes straight into the mapped GPU storage, nothing to sync.
|
||||
if (resource->persistentMapped) return resource->persistentPtr != nullptr;
|
||||
// Except queued resident SubData bytes, which land through the sync path
|
||||
// (same unlocked emptiness probe as pendingRanges below).
|
||||
if (resource->persistentMapped) {
|
||||
return resource->persistentPtr != nullptr && resource->pendingResidentWrites.empty();
|
||||
}
|
||||
// A live non-zero-copy map may owe a per-draw SyncPersistentMappedRange push
|
||||
// (persistent maps mutate the shadow without bumping the change serial).
|
||||
if (frontend->IsMapped()) return false;
|
||||
@@ -1398,6 +1471,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
resource->storageSize = 0;
|
||||
resource->pendingRespecify = true;
|
||||
resource->pendingRanges.clear();
|
||||
resource->pendingResidentWrites.clear();
|
||||
resource->contextGeneration = g_bufferContextGeneration;
|
||||
// The persistent map (and its pointer) died with the old context; the
|
||||
// frontend re-acquires a fresh one on its next map.
|
||||
@@ -1427,6 +1501,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// persistently mapped immutable store, so there is nothing to (re)upload at
|
||||
// draw time. This is where the per-draw whole-buffer glBufferSubData used to run.
|
||||
if (resource->persistentMapped && resource->persistentPtr && resource->id != 0) {
|
||||
DrainResidentWritesNow(*resource, *bufferObject);
|
||||
return resource;
|
||||
}
|
||||
|
||||
@@ -1448,6 +1523,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(resource->pendingMutex);
|
||||
resource->pendingRanges.clear();
|
||||
resource->pendingResidentWrites.clear();
|
||||
}
|
||||
resource->syncedChangeSerial = bufferObject->GetChangeSerial();
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user