mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +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:
@@ -283,6 +283,22 @@ namespace MobileGL::MG_State::GLState {
|
||||
"UploadSubData out of bounds: atOffset (%zu) + data.size (%zu) > m_size (%zu)", atOffset,
|
||||
data.size, m_size);
|
||||
|
||||
// An adopted store's Bytes() IS the memory in-flight frames are reading, and
|
||||
// GL orders a glBufferSubData after those already-submitted reads. A backend
|
||||
// that can land the bytes on the GPU timeline takes them here, untouched by
|
||||
// the mapping - the in-place host write below tore the frames still reading
|
||||
// the old bytes. The bytes are not current in the mapping until the backend's
|
||||
// ordered copy executes, so reads reconcile through the same gate GPU-written
|
||||
// buffers use.
|
||||
if (m_resource.IsGpuResident() && data.size > 0 && g_bufferBackendOps &&
|
||||
g_bufferBackendOps->ResidentSubData) {
|
||||
g_bufferBackendOps->ResidentSubData(*this, atOffset, data);
|
||||
m_hasDefinedContent = true;
|
||||
++m_changeSerial;
|
||||
m_gpuWritePending = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// An adopted store's Bytes() IS the memory the GPU reads, and a backend that
|
||||
// defers work (DirectVulkan's frame command buffer) may still be holding a
|
||||
// recorded-but-unsubmitted dispatch that GL orders this write AFTER. Writing
|
||||
@@ -311,6 +327,24 @@ namespace MobileGL::MG_State::GLState {
|
||||
"Cannot fill data while buffer is non-persistently mapped.");
|
||||
if (size == 0) return;
|
||||
|
||||
// An adopted store takes the same GPU-timeline landing as UploadSubData: the
|
||||
// in-place write below would tear in-flight readers of the mapping.
|
||||
if (m_resource.IsGpuResident() && g_bufferBackendOps && g_bufferBackendOps->ResidentSubData) {
|
||||
Vector<Uint8> expanded(size);
|
||||
if (pattern.size == 1) {
|
||||
Memset(expanded.data(), *static_cast<const Uint8*>(pattern.data), size);
|
||||
} else {
|
||||
for (SizeT at = 0; at < size; at += pattern.size) {
|
||||
Memcpy(expanded.data() + at, pattern.data, pattern.size);
|
||||
}
|
||||
}
|
||||
g_bufferBackendOps->ResidentSubData(*this, atOffset, {expanded.data(), size});
|
||||
m_hasDefinedContent = true;
|
||||
++m_changeSerial;
|
||||
m_gpuWritePending = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// A clear is ordered after all earlier GPU writes. Partial clears additionally need the
|
||||
// retained shadow bytes; whole-store clears need the same synchronization before writing
|
||||
// an adopted persistent mapping that the GPU may still be accessing.
|
||||
@@ -347,6 +381,17 @@ namespace MobileGL::MG_State::GLState {
|
||||
size, m_size);
|
||||
|
||||
src->SyncGpuWrites();
|
||||
// An adopted DESTINATION takes the same GPU-timeline landing as UploadSubData;
|
||||
// the in-place write below would tear in-flight readers of the mapping.
|
||||
if (m_resource.IsGpuResident() && size > 0 && g_bufferBackendOps &&
|
||||
g_bufferBackendOps->ResidentSubData) {
|
||||
g_bufferBackendOps->ResidentSubData(*this, dstOffset,
|
||||
{src->m_resource.Bytes() + srcOffset, size});
|
||||
m_hasDefinedContent = true;
|
||||
++m_changeSerial;
|
||||
m_gpuWritePending = true;
|
||||
return;
|
||||
}
|
||||
// The DESTINATION needs the same ordering as UploadSubData: an adopted store is
|
||||
// written in place, so pending recorded GPU writes to it must retire before the
|
||||
// copy lands or they would execute on top of it.
|
||||
|
||||
@@ -80,6 +80,17 @@ namespace MobileGL {
|
||||
void (*Respecify)(BufferObject& bufferObject) = nullptr;
|
||||
// Contents update of [offset, offset + size) from the shadow.
|
||||
void (*SubData)(BufferObject& bufferObject, SizeT offset, SizeT size) = nullptr;
|
||||
// Contents update of an ADOPTED (GPU-resident) store. `data` holds the app's
|
||||
// bytes; the frontend has NOT touched the resident mapping. GL orders a
|
||||
// glBufferSubData after already-submitted GPU reads of the store, and an
|
||||
// in-place host write into the coherent mapping tears the frames still
|
||||
// reading the old bytes (Minecraft patches LIVE chunk sections this way -
|
||||
// the tear shows as one-frame wrong geometry/UVs during fast movement). The
|
||||
// backend lands the bytes on the GPU timeline instead: after in-flight
|
||||
// readers, before the next consumer. The frontend marks the buffer
|
||||
// gpu-write-pending so reads reconcile through ReadbackFromGpu. Backends
|
||||
// without this op keep the legacy ordered in-place host write.
|
||||
void (*ResidentSubData)(BufferObject& bufferObject, SizeT offset, DataPtr data) = 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.
|
||||
|
||||
Reference in New Issue
Block a user