mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-13 14:48:32 +09:00
[Fix] (MG_State, DirectGLES): read a shader-written storage buffer back before mapping it
Buffer contents live in a CPU shadow that every read - MapBuffer, MapBufferRange, GetBufferSubData, CopyBufferSubData - resolves against, and backend transfer ops only ever push the shadow outwards. Two paths already knew the GPU can write a buffer on its own and mirrored the result back by hand (ReadPixels into a pixel-pack buffer, the transform feedback capture at EndTransformFeedback); a shader storage buffer written by a draw or a dispatch had no such path at all, so the map handed the application the bytes from before the dispatch. Nothing exercised it until now because GL 3.3 has no compute stage. Every KHR-GL40.texture_gather case ends by dispatching a compute shader that writes its sampled texel into an SSBO and comparing the mapped result, and all 71 read back the zero-filled shadow. Adds the missing direction as a backend op: BufferObject::MarkGpuWritten flags a buffer the GPU may have moved ahead of the shadow, SyncGpuWrites pulls it back at every read point, and DirectGLES implements the readback with a plain read map of the ES buffer. The flag is raised where the storage-buffer points are bound for the upcoming draw or dispatch, which is the last moment the set of exposed buffers is known, and cleared by the readback - so a buffer nothing writes costs one bool test per map. Backends that cannot read their storage back leave the op null and keep today's behaviour; a GPU-resident (coherent persistent) buffer needs nothing, since its reads already resolve against the memory the shader wrote. Drops the texture_gather failures from 71/75 to 25/75 with no crashes left.
This commit is contained in:
@@ -295,6 +295,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
}
|
||||
|
||||
// Called once the storage-buffer points are bound and the draw/dispatch is about to
|
||||
// go out: whatever the shader writes there lands in the ES driver's buffers, behind
|
||||
// the frontend's CPU shadow. Flagging them makes the next MapBuffer/GetBufferSubData
|
||||
// pull the real contents back (BufferObject::SyncGpuWrites).
|
||||
void MarkShaderStorageBuffersGpuWritten() {
|
||||
const SizeT bindingPointCnt =
|
||||
MG_State::pGLContext->GetTouchedBufferBindingPointCount(BufferTarget::ShaderStorage);
|
||||
for (SizeT i = 0; i < bindingPointCnt; ++i) {
|
||||
const auto& obj =
|
||||
MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::ShaderStorage, i).GetBoundObject();
|
||||
if (obj) obj->MarkGpuWritten();
|
||||
}
|
||||
}
|
||||
|
||||
void SyncBoundBuffer(BufferTarget target, GLenum glTarget) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
@@ -367,6 +381,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// BindCurrentProgramWithResources binds no SSBO points, so this is their sole draw-path
|
||||
// binder (e.g. Flywheel's indirect vertex shaders pull instance data from storage buffers).
|
||||
SyncBufferBindingPoints(BufferTarget::ShaderStorage, GL_SHADER_STORAGE_BUFFER);
|
||||
MarkShaderStorageBuffersGpuWritten();
|
||||
}
|
||||
|
||||
void SyncComputeBuffers(Bool includeDispatchIndirectBuffer) {
|
||||
@@ -376,6 +391,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
ProcessDeferredBufferReleases();
|
||||
SyncBufferBindingPoints(BufferTarget::Uniform, GL_UNIFORM_BUFFER);
|
||||
SyncBufferBindingPoints(BufferTarget::ShaderStorage, GL_SHADER_STORAGE_BUFFER);
|
||||
MarkShaderStorageBuffersGpuWritten();
|
||||
if (includeDispatchIndirectBuffer) {
|
||||
SyncBoundBuffer(BufferTarget::DispatchIndirect, GL_DISPATCH_INDIRECT_BUFFER);
|
||||
}
|
||||
|
||||
@@ -630,6 +630,32 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
resource->syncedChangeSerial = bufferObject.GetChangeSerial();
|
||||
}
|
||||
|
||||
// A shader wrote this buffer through a storage/atomic-counter binding, so the ES
|
||||
// driver's copy is ahead of the frontend shadow. Pull the whole thing back so
|
||||
// MapBuffer/GetBufferSubData/CopyBufferSubData see the real results.
|
||||
void Ops_ReadbackFromGpu(BufferObject& bufferObject) {
|
||||
auto* resource = ResourceOf(bufferObject);
|
||||
if (!resource || resource->id == 0 || !resource->storageInitialized) return;
|
||||
if (resource->persistentMapped) return; // shadow already IS the GPU storage
|
||||
if (!CanTouchGLNow() || resource->contextGeneration != g_bufferContextGeneration) return;
|
||||
if (!g_GLESFuncs.glMapBufferRange || !g_GLESFuncs.glUnmapBuffer) return;
|
||||
const SizeT size = std::min<SizeT>(bufferObject.GetSize(), resource->storageSize);
|
||||
if (size == 0) return;
|
||||
|
||||
BindBufferId(TempBufferTarget, resource->id);
|
||||
void* mapped = g_GLESFuncs.glMapBufferRange(TempBufferTarget, 0, static_cast<GLsizeiptr>(size),
|
||||
GL_MAP_READ_BIT);
|
||||
if (mapped == nullptr) {
|
||||
MGLOG_E("Ops_ReadbackFromGpu: glMapBufferRange(read) failed for buffer %u", resource->id);
|
||||
return;
|
||||
}
|
||||
bufferObject.WritebackFromBackend({mapped, size}, 0);
|
||||
g_GLESFuncs.glUnmapBuffer(TempBufferTarget);
|
||||
// The shadow now matches the backend byte for byte; without this the next
|
||||
// draw would see a newer change serial and re-upload the readback over it.
|
||||
resource->syncedChangeSerial = bufferObject.GetChangeSerial();
|
||||
}
|
||||
|
||||
void Ops_OnDestroy(SharedPtr<BackendBufferResource>&& resource) {
|
||||
if (!resource) return;
|
||||
auto* glesResource = static_cast<GLESBufferResource*>(resource.get());
|
||||
@@ -662,6 +688,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
.FlushMappedRange = Ops_FlushMappedRange,
|
||||
.OnDestroy = Ops_OnDestroy,
|
||||
.AcquirePersistentMap = Ops_AcquirePersistentMap,
|
||||
.ReadbackFromGpu = Ops_ReadbackFromGpu,
|
||||
};
|
||||
} // namespace
|
||||
|
||||
|
||||
Reference in New Issue
Block a user