From 0c73ff8452da2ce24b129ba199628e585ca66e1d Mon Sep 17 00:00:00 2001 From: Samuliak Date: Mon, 12 Aug 2024 14:27:28 +0200 Subject: [PATCH] use multiple command buffers per frame --- .../Metal/LatteTextureReadbackMtl.cpp | 10 +- .../Renderer/Metal/LatteTextureReadbackMtl.h | 3 + .../HW/Latte/Renderer/Metal/MetalRenderer.cpp | 110 ++++++++++++------ .../HW/Latte/Renderer/Metal/MetalRenderer.h | 22 +++- 4 files changed, 105 insertions(+), 40 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Metal/LatteTextureReadbackMtl.cpp b/src/Cafe/HW/Latte/Renderer/Metal/LatteTextureReadbackMtl.cpp index 608ff050..ef157664 100644 --- a/src/Cafe/HW/Latte/Renderer/Metal/LatteTextureReadbackMtl.cpp +++ b/src/Cafe/HW/Latte/Renderer/Metal/LatteTextureReadbackMtl.cpp @@ -23,18 +23,22 @@ void LatteTextureReadbackInfoMtl::StartTransfer() auto blitCommandEncoder = m_mtlr->GetBlitCommandEncoder(); blitCommandEncoder->copyFromTexture(baseTexture->GetTexture(), 0, 0, MTL::Origin{0, 0, 0}, MTL::Size{(uint32)baseTexture->width, (uint32)baseTexture->height, 1}, m_mtlr->GetTextureReadbackBuffer(), m_bufferOffset, bytesPerRow, bytesPerImage); + + m_commandBuffer = m_mtlr->GetCurrentCommandBuffer(); + // TODO: uncomment + //m_mtlr->RequestSoonCommit(); } bool LatteTextureReadbackInfoMtl::IsFinished() { - // TODO: implement - + // HACK: just return true for now, otherwise the game would freeze + //return m_mtlr->CommandBufferCompleted(m_commandBuffer); return true; } void LatteTextureReadbackInfoMtl::ForceFinish() { - // TODO: implement + m_mtlr->WaitForCommandBufferCompletion(m_commandBuffer); } uint8* LatteTextureReadbackInfoMtl::GetData() diff --git a/src/Cafe/HW/Latte/Renderer/Metal/LatteTextureReadbackMtl.h b/src/Cafe/HW/Latte/Renderer/Metal/LatteTextureReadbackMtl.h index a03bbd49..19ca6574 100644 --- a/src/Cafe/HW/Latte/Renderer/Metal/LatteTextureReadbackMtl.h +++ b/src/Cafe/HW/Latte/Renderer/Metal/LatteTextureReadbackMtl.h @@ -1,5 +1,6 @@ #pragma once +#include "Cafe/HW/Latte/Renderer/Metal/MetalCommon.h" #include "Cafe/HW/Latte/Core/LatteTextureReadbackInfo.h" class LatteTextureReadbackInfoMtl : public LatteTextureReadbackInfo @@ -18,5 +19,7 @@ public: private: class MetalRenderer* m_mtlr; + MTL::CommandBuffer* m_commandBuffer = nullptr; + uint32 m_bufferOffset = 0; }; diff --git a/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp index ba50f072..ca74a1d8 100644 --- a/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp @@ -15,9 +15,12 @@ #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/Core/LatteIndices.h" #include "Cemu/Logging/CemuDebugLogging.h" +#include "Common/precompiled.h" #include "Metal/MTLPixelFormat.hpp" #include "gui/guiWrapper.h" +#define COMMIT_TRESHOLD 256 + extern bool hasValidFramebufferAttached; float supportBufferData[512 * 4]; @@ -113,7 +116,7 @@ MetalRenderer::~MetalRenderer() m_device->release(); } -// TODO: don't ignore "mainWindow" argument +// TODO: don't ignore "mainWindow" argument and respect size void MetalRenderer::InitializeLayer(const Vector2i& size, bool mainWindow) { const auto& windowInfo = gui_getWindowInfo().window_main; @@ -168,19 +171,23 @@ void MetalRenderer::DrawEmptyFrame(bool mainWindow) void MetalRenderer::SwapBuffers(bool swapTV, bool swapDRC) { - EndEncoding(); if (m_drawable) { - EnsureCommandBuffer(); - m_commandBuffer->presentDrawable(m_drawable); - } else + auto commandBuffer = GetCommandBuffer(); + commandBuffer->presentDrawable(m_drawable); + } + else { debug_printf("skipped present!\n"); } m_drawable = nullptr; + // Release all the command buffers CommitCommandBuffer(); + for (uint32 i = 0; i < m_commandBuffers.size(); i++) + m_commandBuffers[i].m_commandBuffer->release(); + m_commandBuffers.clear(); // Reset temporary buffers m_memoryManager->ResetTemporaryBuffers(); @@ -223,18 +230,20 @@ bool MetalRenderer::BeginFrame(bool mainWindow) void MetalRenderer::Flush(bool waitIdle) { - // TODO: should we? - CommitCommandBuffer(); + // TODO: commit if commit on idle is requested + if (m_recordedDrawcalls > 0) + CommitCommandBuffer(); if (waitIdle) { - // TODO + // TODO: shouldn't we wait for all command buffers? + WaitForCommandBufferCompletion(GetCurrentCommandBuffer()); } } void MetalRenderer::NotifyLatteCommandProcessorIdle() { - // TODO: should we? - CommitCommandBuffer(); + // TODO: commit if commit on idle is requested + //CommitCommandBuffer(); } void MetalRenderer::AppendOverlayDebugInfo() @@ -452,7 +461,7 @@ LatteTextureReadbackInfo* MetalRenderer::texture_createReadback(LatteTextureView void MetalRenderer::surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* sourceTexture, sint32 srcMip, sint32 srcSlice, LatteTexture* destinationTexture, sint32 dstMip, sint32 dstSlice, sint32 width, sint32 height) { - EnsureCommandBuffer(); + GetCommandBuffer(); // scale copy size to effective size sint32 effectiveCopyWidth = width; @@ -809,11 +818,15 @@ void MetalRenderer::draw_endSequence() if (pixelShader) LatteRenderTarget_trackUpdates(); bool hasReadback = LatteTextureReadback_Update(); - //m_recordedDrawcalls++; - //if (m_recordedDrawcalls >= m_submitThreshold || hasReadback) - //{ - // SubmitCommandBuffer(); - //} + m_recordedDrawcalls++; + // The number of draw calls needs to twice as big, since we are interrupting the render pass + if (m_recordedDrawcalls >= COMMIT_TRESHOLD * 2 || hasReadback) + { + CommitCommandBuffer(); + + // TODO: where should this be called? + LatteTextureReadback_UpdateFinishedTransfers(false); + } } void* MetalRenderer::indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex) @@ -830,22 +843,38 @@ void MetalRenderer::indexData_uploadIndexMemory(uint32 offset, uint32 size) // Do nothing, since the buffer has shared storage mode } -void MetalRenderer::EnsureCommandBuffer() +MTL::CommandBuffer* MetalRenderer::GetCommandBuffer() { - if (!m_commandBuffer) + bool needsNewCommandBuffer = (m_commandBuffers.empty() || m_commandBuffers.back().m_commited); + if (needsNewCommandBuffer) { // Debug //m_commandQueue->insertDebugCaptureBoundary(); - m_commandBuffer = m_commandQueue->commandBuffer(); + MTL::CommandBuffer* mtlCommandBuffer = m_commandQueue->commandBuffer(); + m_commandBuffers.push_back({mtlCommandBuffer}); + + return mtlCommandBuffer; } + else + { + return m_commandBuffers.back().m_commandBuffer; + } +} + +bool MetalRenderer::CommandBufferCompleted(MTL::CommandBuffer* commandBuffer) +{ + return commandBuffer->status() == MTL::CommandBufferStatusCompleted; +} + +void MetalRenderer::WaitForCommandBufferCompletion(MTL::CommandBuffer* commandBuffer) +{ + commandBuffer->waitUntilCompleted(); } // Some render passes clear the attachments, forceRecreate is supposed to be used in those cases MTL::RenderCommandEncoder* MetalRenderer::GetRenderCommandEncoder(MTL::RenderPassDescriptor* renderPassDescriptor, MTL::Texture* colorRenderTargets[8], MTL::Texture* depthRenderTarget, bool forceRecreate, bool rebindStateIfNewEncoder) { - EnsureCommandBuffer(); - // Check if we need to begin a new render pass if (m_commandEncoder) { @@ -881,6 +910,8 @@ MTL::RenderCommandEncoder* MetalRenderer::GetRenderCommandEncoder(MTL::RenderPas EndEncoding(); } + auto commandBuffer = GetCommandBuffer(); + // Update state m_state.m_lastUsedFBO = m_state.m_activeFBO; for (uint8 i = 0; i < 8; i++) @@ -889,7 +920,7 @@ MTL::RenderCommandEncoder* MetalRenderer::GetRenderCommandEncoder(MTL::RenderPas } m_state.m_depthRenderTarget = depthRenderTarget; - auto renderCommandEncoder = m_commandBuffer->renderCommandEncoder(renderPassDescriptor); + auto renderCommandEncoder = commandBuffer->renderCommandEncoder(renderPassDescriptor); m_commandEncoder = renderCommandEncoder; m_encoderType = MetalEncoderType::Render; @@ -914,7 +945,9 @@ MTL::ComputeCommandEncoder* MetalRenderer::GetComputeCommandEncoder() EndEncoding(); } - auto computeCommandEncoder = m_commandBuffer->computeCommandEncoder(); + auto commandBuffer = GetCommandBuffer(); + + auto computeCommandEncoder = commandBuffer->computeCommandEncoder(); m_commandEncoder = computeCommandEncoder; m_encoderType = MetalEncoderType::Compute; @@ -933,7 +966,9 @@ MTL::BlitCommandEncoder* MetalRenderer::GetBlitCommandEncoder() EndEncoding(); } - auto blitCommandEncoder = m_commandBuffer->blitCommandEncoder(); + auto commandBuffer = GetCommandBuffer(); + + auto blitCommandEncoder = commandBuffer->blitCommandEncoder(); m_commandEncoder = blitCommandEncoder; m_encoderType = MetalEncoderType::Blit; @@ -942,30 +977,35 @@ MTL::BlitCommandEncoder* MetalRenderer::GetBlitCommandEncoder() void MetalRenderer::EndEncoding() { - if (m_commandEncoder) + if (m_encoderType != MetalEncoderType::None) { m_commandEncoder->endEncoding(); m_commandEncoder->release(); - m_commandEncoder = nullptr; m_encoderType = MetalEncoderType::None; + + // Commit the command buffer if enough draw calls have been recorded + if (m_recordedDrawcalls >= COMMIT_TRESHOLD) + CommitCommandBuffer(); } } void MetalRenderer::CommitCommandBuffer() { - EndEncoding(); + m_recordedDrawcalls = 0; - if (m_commandBuffer) + if (m_commandBuffers.size() != 0) { - m_commandBuffer->commit(); - m_commandBuffer->release(); - m_commandBuffer = nullptr; + EndEncoding(); - // TODO: where should this be called? - LatteTextureReadback_UpdateFinishedTransfers(false); + auto& commandBuffer = m_commandBuffers.back(); + if (!commandBuffer.m_commited) + { + commandBuffer.m_commandBuffer->commit(); + commandBuffer.m_commited = true; - // Debug - //m_commandQueue->insertDebugCaptureBoundary(); + // Debug + //m_commandQueue->insertDebugCaptureBoundary(); + } } } diff --git a/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h index 3868ae70..78e1daca 100644 --- a/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h @@ -7,6 +7,8 @@ #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalMemoryManager.h" +#include "Common/precompiled.h" +#include "Metal/MTLCommandBuffer.hpp" #define MAX_MTL_BUFFERS 31 #define GET_MTL_VERTEX_BUFFER_INDEX(index) (MAX_MTL_BUFFERS - index - 2) @@ -47,6 +49,12 @@ struct MetalState MTL::ScissorRect m_scissor = {0, 0, 0, 0}; }; +struct MetalCommandBuffer +{ + MTL::CommandBuffer* m_commandBuffer; + bool m_commited = false; +}; + enum class MetalEncoderType { None, @@ -231,7 +239,16 @@ public: } // Helpers - void EnsureCommandBuffer(); + MTL::CommandBuffer* GetCurrentCommandBuffer() + { + cemu_assert_debug(m_commandBuffers.size() != 0); + + return m_commandBuffers[m_commandBuffers.size() - 1].m_commandBuffer; + } + + MTL::CommandBuffer* GetCommandBuffer(); + bool CommandBufferCompleted(MTL::CommandBuffer* commandBuffer); + void WaitForCommandBufferCompletion(MTL::CommandBuffer* commandBuffer); MTL::RenderCommandEncoder* GetRenderCommandEncoder(MTL::RenderPassDescriptor* renderPassDescriptor, MTL::Texture* colorRenderTargets[8], MTL::Texture* depthRenderTarget, bool forceRecreate = false, bool rebindStateIfNewEncoder = true); MTL::ComputeCommandEncoder* GetComputeCommandEncoder(); MTL::BlitCommandEncoder* GetBlitCommandEncoder(); @@ -280,7 +297,8 @@ private: MTL::Buffer* m_xfbRingBuffer; // Active objects - MTL::CommandBuffer* m_commandBuffer = nullptr; + std::vector m_commandBuffers; + uint32 m_recordedDrawcalls = 0; MetalEncoderType m_encoderType = MetalEncoderType::None; MTL::CommandEncoder* m_commandEncoder = nullptr; CA::MetalDrawable* m_drawable = nullptr;