mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 20:28:32 +09:00
[Fix] (DirectVulkan, MG_State): make a shader-written storage buffer readable on Magma
Reading a buffer a compute shader wrote gave zeros: the frontend shadow that MapBuffer resolves against is only maintained by uploads, and Magma had no path back. Every KHR-GL40.texture_gather case ends by dispatching a compute shader into an SSBO and comparing the mapped result, so 66 of 75 failed on it. Magma needs no readback: EnsureGpuResidentStorage - the same host-visible coherent adoption the transform feedback capture already uses - makes the shadow BE the memory the shader writes, so binding a buffer as a shader storage buffer now adopts it. What coherence does not give is ordering: the writes are visible once they have happened, and the CPU was reading before the dispatch had retired. The readback op therefore submits the recorded work and waits. That exposed a mistake in the frontend flag this rides on: MarkGpuWritten skipped GPU-resident buffers, reasoning there was no shadow to refresh. True, but the wait is still needed - "reconcile with the GPU write" is not always "copy it back", and which of the two it is belongs to the backend. The flag now only says a write is outstanding; DirectGLES's readback still skips its persistent-mapped buffers when copying. KHR-GL40.texture_gather on Magma: 66 failures -> 19 (the rest are rectangle textures, mipmap completeness and tessellation, all still to do). Espryt stays at 75/75.
This commit is contained in:
@@ -650,6 +650,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The shader may write this buffer, and those writes land in GPU memory behind the
|
||||
// frontend's CPU shadow - which is what MapBuffer and GetBufferSubData read.
|
||||
// Host-visible coherent GPU residency makes the shadow BE that memory, so the
|
||||
// results are visible without a readback path, exactly as for a capture buffer.
|
||||
bufferObject->EnsureGpuResidentStorage();
|
||||
// ... and the read that follows has to wait for this draw or dispatch to retire.
|
||||
bufferObject->MarkGpuWritten();
|
||||
|
||||
BufferSlice slice{};
|
||||
if (!m_bufferManager->AcquireResidentSlice(BufferKind::ShaderStorage, bufferObject, slice) || !slice.IsValid()) {
|
||||
MGLOG_E("ResolveStorageBufferDescriptor: failed to sync GL buffer %u for block '%s'",
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
// End of Source File Header
|
||||
|
||||
#include "VkBufferManager.h"
|
||||
#include "../DirectVulkan.h"
|
||||
#include "VulkanRenderer.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
namespace {
|
||||
@@ -57,6 +59,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
}
|
||||
|
||||
// The CPU is about to read a buffer a shader wrote. Its bytes live in coherent
|
||||
// host-visible GPU storage (EnsureGpuResidentStorage adopts it when the buffer is
|
||||
// bound as a shader storage buffer), so nothing needs copying - but coherence only
|
||||
// says the writes are visible once they have happened, so the work has to retire
|
||||
// first.
|
||||
void Ops_ReadbackFromGpu(BufferObject& bufferObject) {
|
||||
(void)bufferObject;
|
||||
if (pVulkanRenderer) {
|
||||
pVulkanRenderer->FinishPendingGpuWork();
|
||||
}
|
||||
}
|
||||
|
||||
void* Ops_AcquirePersistentMap(BufferObject& bufferObject) {
|
||||
if (g_activeBufferManager) {
|
||||
return g_activeBufferManager->AcquirePersistentMap(bufferObject);
|
||||
@@ -80,6 +94,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
.FlushMappedRange = Ops_FlushMappedRange,
|
||||
.OnDestroy = Ops_OnDestroy,
|
||||
.AcquirePersistentMap = Ops_AcquirePersistentMap,
|
||||
.ReadbackFromGpu = Ops_ReadbackFromGpu,
|
||||
};
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -6919,6 +6919,17 @@ void main() {
|
||||
|
||||
}
|
||||
|
||||
Bool VulkanRenderer::FinishPendingGpuWork() {
|
||||
auto& frame = m_frameContext.GetCurrent();
|
||||
if (!frame.isCommandRecording) {
|
||||
return true;
|
||||
}
|
||||
if (VkRenderPassManager::GetActiveRenderPass() != nullptr) {
|
||||
VkRenderPassManager::EndRenderPass(frame.commandBuffer);
|
||||
}
|
||||
return SubmitReadbackCommandsAndWait(frame);
|
||||
}
|
||||
|
||||
Bool VulkanRenderer::SubmitReadbackCommandsAndWait(FrameContext::FrameData& frame) {
|
||||
if (frame.isCommandRecording) {
|
||||
m_frameContext.EndCommandRecording();
|
||||
|
||||
@@ -795,6 +795,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkImageLayout finalLayout);
|
||||
Bool SubmitReadbackCommandsAndWait(FrameContext::FrameData& frame);
|
||||
|
||||
public:
|
||||
// Submits whatever is recorded and waits for it. The CPU is about to read memory
|
||||
// a shader wrote (a mapped shader storage buffer), and coherent host-visible
|
||||
// storage only guarantees visibility once the work that produced it has retired.
|
||||
Bool FinishPendingGpuWork();
|
||||
|
||||
private:
|
||||
|
||||
void ShutdownSwapchain();
|
||||
|
||||
// Static functions
|
||||
|
||||
@@ -175,9 +175,6 @@ namespace MobileGL::MG_State::GLState {
|
||||
}
|
||||
|
||||
void BufferObject::MarkGpuWritten() {
|
||||
// A GPU-resident buffer has no separate shadow to refresh: reads already resolve
|
||||
// against the coherent map the shader wrote into.
|
||||
if (m_resource.IsGpuResident()) return;
|
||||
m_gpuWritePending = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -157,8 +157,10 @@ namespace MobileGL {
|
||||
void WritebackFromBackend(DataPtr data, SizeT atOffset);
|
||||
|
||||
// A draw or dispatch just ran with this buffer bound where a shader can write
|
||||
// it (shader storage / atomic counter): the GPU copy may now differ from the
|
||||
// shadow, so the next read has to pull it back.
|
||||
// it (shader storage / atomic counter). The next read has to reconcile with
|
||||
// that: pull the bytes back, or - when the shadow already IS coherent GPU
|
||||
// memory - wait for the work that wrote them to retire. Which of the two is
|
||||
// the backend's business; the flag only says a GPU write is outstanding.
|
||||
void MarkGpuWritten();
|
||||
// Refreshes the shadow from the backend when a GPU write is outstanding. Called
|
||||
// from every path that reads the shadow on the app's behalf.
|
||||
|
||||
Reference in New Issue
Block a user