diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp index 41b9398b..b96aedab 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp @@ -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'", diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp index 22297278..28a884cd 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp @@ -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 diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 295389f9..d7303faf 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -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(); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 4b0221f7..a6a80df3 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -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 diff --git a/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp b/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp index af971f34..c4ebb279 100644 --- a/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp +++ b/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp @@ -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; } diff --git a/MobileGL/MG_State/GLState/BufferState/BufferObject.h b/MobileGL/MG_State/GLState/BufferState/BufferObject.h index d6268970..c689614e 100644 --- a/MobileGL/MG_State/GLState/BufferState/BufferObject.h +++ b/MobileGL/MG_State/GLState/BufferState/BufferObject.h @@ -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.