From 0d4537d60f695e50379c1542d6c4b1e643e8a5a0 Mon Sep 17 00:00:00 2001 From: Scott Mansell Date: Fri, 27 Jan 2023 17:03:15 +1300 Subject: [PATCH] Move Presenting, Dumping and ImGui out of Renderer --- Source/Android/jni/MainAndroid.cpp | 11 +- Source/Core/Core/Core.cpp | 10 +- Source/Core/DolphinLib.props | 6 + Source/Core/DolphinNoGUI/PlatformWin32.cpp | 6 +- Source/Core/DolphinNoGUI/PlatformX11.cpp | 6 +- .../Graphics/PostProcessingConfigWindow.cpp | 8 +- Source/Core/DolphinQt/Host.cpp | 9 +- Source/Core/DolphinQt/RenderWidget.cpp | 35 +- Source/Core/VideoBackends/D3D/D3DRender.cpp | 8 +- .../VideoBackends/D3D12/D3D12Renderer.cpp | 8 +- .../Core/VideoBackends/Metal/MTLRenderer.mm | 8 +- Source/Core/VideoBackends/OGL/OGLRender.cpp | 25 +- .../VideoBackends/Software/SWRenderer.cpp | 6 +- .../Core/VideoBackends/Vulkan/VKRenderer.cpp | 11 +- .../Core/VideoBackends/Vulkan/VKSwapChain.cpp | 6 +- Source/Core/VideoCommon/CMakeLists.txt | 6 + Source/Core/VideoCommon/FrameDump.cpp | 29 +- Source/Core/VideoCommon/FrameDump.h | 30 +- Source/Core/VideoCommon/FrameDumper.cpp | 354 ++++++ Source/Core/VideoCommon/FrameDumper.h | 121 ++ Source/Core/VideoCommon/OnScreenUI.cpp | 386 ++++++ Source/Core/VideoCommon/OnScreenUI.h | 78 ++ Source/Core/VideoCommon/PostProcessing.cpp | 3 +- Source/Core/VideoCommon/Present.cpp | 524 ++++++++ Source/Core/VideoCommon/Present.h | 132 ++ Source/Core/VideoCommon/RenderBase.cpp | 1121 +---------------- Source/Core/VideoCommon/RenderBase.h | 196 +-- Source/Core/VideoCommon/ShaderCache.cpp | 9 +- Source/Core/VideoCommon/VideoBackendBase.cpp | 8 + 29 files changed, 1766 insertions(+), 1394 deletions(-) create mode 100644 Source/Core/VideoCommon/FrameDumper.cpp create mode 100644 Source/Core/VideoCommon/FrameDumper.h create mode 100644 Source/Core/VideoCommon/OnScreenUI.cpp create mode 100644 Source/Core/VideoCommon/OnScreenUI.h create mode 100644 Source/Core/VideoCommon/Present.cpp create mode 100644 Source/Core/VideoCommon/Present.h diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index 428870d02f..d305878f99 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -59,6 +59,7 @@ #include "UICommon/UICommon.h" #include "VideoCommon/OnScreenDisplay.h" +#include "VideoCommon/Present.h" #include "VideoCommon/RenderBase.h" #include "VideoCommon/VideoBackendBase.h" @@ -456,8 +457,8 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceChang if (s_surf == nullptr) __android_log_print(ANDROID_LOG_ERROR, DOLPHIN_TAG, "Error: Surface is null."); - if (g_renderer) - g_renderer->ChangeSurface(s_surf); + if (g_presenter) + g_presenter->ChangeSurface(s_surf); } JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceDestroyed(JNIEnv*, @@ -483,8 +484,8 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceDestr std::lock_guard surface_guard(s_surface_lock); - if (g_renderer) - g_renderer->ChangeSurface(nullptr); + if (g_presenter) + g_presenter->ChangeSurface(nullptr); if (s_surf) { @@ -503,7 +504,7 @@ JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_HasSurfa JNIEXPORT jfloat JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetGameAspectRatio(JNIEnv*, jclass) { - return g_renderer->CalculateDrawAspectRatio(); + return g_presenter->CalculateDrawAspectRatio(); } JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_RefreshWiimotes(JNIEnv*, jclass) diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 7757414834..7d7264bd3c 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -84,9 +84,11 @@ #include "VideoCommon/AsyncRequests.h" #include "VideoCommon/Fifo.h" +#include "VideoCommon/FrameDumper.h" #include "VideoCommon/HiresTextures.h" #include "VideoCommon/OnScreenDisplay.h" #include "VideoCommon/PerformanceMetrics.h" +#include "VideoCommon/Present.h" #include "VideoCommon/RenderBase.h" #include "VideoCommon/VideoBackendBase.h" @@ -540,8 +542,8 @@ static void EmuThread(std::unique_ptr boot, WindowSystemInfo wsi // Render a single frame without anything on it to clear the screen. // This avoids the game list being displayed while the core is finishing initializing. - g_renderer->BeginUIFrame(); - g_renderer->EndUIFrame(); + g_presenter->BeginUIFrame(); + g_presenter->EndUIFrame(); if (cpu_info.HTT) Config::SetBaseOrCurrent(Config::MAIN_DSP_THREAD, cpu_info.num_cores > 4); @@ -731,13 +733,13 @@ static std::string GenerateScreenshotName() void SaveScreenShot() { - Core::RunAsCPUThread([] { g_renderer->SaveScreenshot(GenerateScreenshotName()); }); + Core::RunAsCPUThread([] { g_frame_dumper->SaveScreenshot(GenerateScreenshotName()); }); } void SaveScreenShot(std::string_view name) { Core::RunAsCPUThread([&name] { - g_renderer->SaveScreenshot(fmt::format("{}{}.png", GenerateScreenshotFolderPath(), name)); + g_frame_dumper->SaveScreenshot(fmt::format("{}{}.png", GenerateScreenshotFolderPath(), name)); }); } diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 613e5a5e18..69e8f2873e 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -639,6 +639,7 @@ + @@ -668,6 +669,7 @@ + @@ -676,6 +678,7 @@ + @@ -1232,6 +1235,7 @@ + @@ -1254,6 +1258,7 @@ + @@ -1262,6 +1267,7 @@ + diff --git a/Source/Core/DolphinNoGUI/PlatformWin32.cpp b/Source/Core/DolphinNoGUI/PlatformWin32.cpp index d4a940bce5..a34ace70ce 100644 --- a/Source/Core/DolphinNoGUI/PlatformWin32.cpp +++ b/Source/Core/DolphinNoGUI/PlatformWin32.cpp @@ -13,7 +13,7 @@ #include #include -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/Present.h" #include "resource.h" namespace @@ -181,8 +181,8 @@ LRESULT PlatformWin32::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam case WM_SIZE: { - if (g_renderer) - g_renderer->ResizeSurface(); + if (g_presenter) + g_presenter->ResizeSurface(); } break; diff --git a/Source/Core/DolphinNoGUI/PlatformX11.cpp b/Source/Core/DolphinNoGUI/PlatformX11.cpp index 8dcd93bf52..3f3ea57bc6 100644 --- a/Source/Core/DolphinNoGUI/PlatformX11.cpp +++ b/Source/Core/DolphinNoGUI/PlatformX11.cpp @@ -25,7 +25,7 @@ static constexpr auto X_None = None; #include #include #include "UICommon/X11Utils.h" -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/Present.h" #ifndef HOST_NAME_MAX #define HOST_NAME_MAX _POSIX_HOST_NAME_MAX @@ -263,8 +263,8 @@ void PlatformX11::ProcessEvents() break; case ConfigureNotify: { - if (g_renderer) - g_renderer->ResizeSurface(); + if (g_presenter) + g_presenter->ResizeSurface(); } break; } diff --git a/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp b/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp index 59907bd39b..c6842c7351 100644 --- a/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp +++ b/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp @@ -21,7 +21,7 @@ #include "DolphinQt/Config/Graphics/EnhancementsWidget.h" #include "VideoCommon/PostProcessing.h" -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/Present.h" #include "VideoCommon/VideoConfig.h" using ConfigurationOption = VideoCommon::PostProcessingConfiguration::ConfigurationOption; @@ -31,9 +31,9 @@ PostProcessingConfigWindow::PostProcessingConfigWindow(EnhancementsWidget* paren const std::string& shader) : QDialog(parent), m_shader(shader) { - if (g_renderer && g_renderer->GetPostProcessor()) + if (g_presenter && g_presenter->GetPostProcessor()) { - m_post_processor = g_renderer->GetPostProcessor()->GetConfig(); + m_post_processor = g_presenter->GetPostProcessor()->GetConfig(); } else { @@ -52,7 +52,7 @@ PostProcessingConfigWindow::PostProcessingConfigWindow(EnhancementsWidget* paren PostProcessingConfigWindow::~PostProcessingConfigWindow() { m_post_processor->SaveOptionsConfiguration(); - if (!(g_renderer && g_renderer->GetPostProcessor())) + if (!(g_presenter && g_presenter->GetPostProcessor())) { delete m_post_processor; } diff --git a/Source/Core/DolphinQt/Host.cpp b/Source/Core/DolphinQt/Host.cpp index 01dcc46dc1..5eb2055486 100644 --- a/Source/Core/DolphinQt/Host.cpp +++ b/Source/Core/DolphinQt/Host.cpp @@ -37,6 +37,7 @@ #include "UICommon/DiscordPresence.h" #include "VideoCommon/Fifo.cpp" +#include "VideoCommon/Present.h" #include "VideoCommon/RenderBase.h" #include "VideoCommon/VideoConfig.h" @@ -76,9 +77,9 @@ void Host::SetRenderHandle(void* handle) return; m_render_handle = handle; - if (g_renderer) + if (g_presenter) { - g_renderer->ChangeSurface(handle); + g_presenter->ChangeSurface(handle); g_controller_interface.ChangeWindow(handle); } } @@ -190,8 +191,8 @@ void Host::SetRenderFullscreen(bool fullscreen) void Host::ResizeSurface(int new_width, int new_height) { - if (g_renderer) - g_renderer->ResizeSurface(); + if (g_presenter) + g_presenter->ResizeSurface(); } std::vector Host_GetPreferredLocales() diff --git a/Source/Core/DolphinQt/RenderWidget.cpp b/Source/Core/DolphinQt/RenderWidget.cpp index 891d3614c8..cebbd490c3 100644 --- a/Source/Core/DolphinQt/RenderWidget.cpp +++ b/Source/Core/DolphinQt/RenderWidget.cpp @@ -32,7 +32,7 @@ #include "InputCommon/ControllerInterface/ControllerInterface.h" -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/Present.h" #include "VideoCommon/VideoConfig.h" #ifdef _WIN32 @@ -487,38 +487,40 @@ void RenderWidget::PassEventToImGui(const QEvent* event) const QKeyEvent* key_event = static_cast(event); const bool is_down = event->type() == QEvent::KeyPress; const u32 key = static_cast(key_event->key() & 0x1FF); - auto lock = g_renderer->GetImGuiLock(); - if (key < std::size(ImGui::GetIO().KeysDown)) - ImGui::GetIO().KeysDown[key] = is_down; + + const char* chars = nullptr; if (is_down) { auto utf8 = key_event->text().toUtf8(); - ImGui::GetIO().AddInputCharactersUTF8(utf8.constData()); + + if (utf8.size()) + chars = utf8.constData(); } + + // Pass the key onto ImGui + g_presenter->SetKey(key, is_down, chars); } break; case QEvent::MouseMove: { - auto lock = g_renderer->GetImGuiLock(); - // Qt multiplies all coordinates by the scaling factor in highdpi mode, giving us "scaled" mouse // coordinates (as if the screen was standard dpi). We need to update the mouse position in // native coordinates, as the UI (and game) is rendered at native resolution. const float scale = devicePixelRatio(); - ImGui::GetIO().MousePos.x = static_cast(event)->pos().x() * scale; - ImGui::GetIO().MousePos.y = static_cast(event)->pos().y() * scale; + float x = static_cast(event)->pos().x() * scale; + float y = static_cast(event)->pos().y() * scale; + + g_presenter->SetMousePos(x, y); } break; case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: { - auto lock = g_renderer->GetImGuiLock(); const u32 button_mask = static_cast(static_cast(event)->buttons()); - for (size_t i = 0; i < std::size(ImGui::GetIO().MouseDown); i++) - ImGui::GetIO().MouseDown[i] = (button_mask & (1u << i)) != 0; + g_presenter->SetMousePress(button_mask); } break; @@ -529,7 +531,7 @@ void RenderWidget::PassEventToImGui(const QEvent* event) void RenderWidget::SetImGuiKeyMap() { - static constexpr std::array, 21> key_map{{ + static std::array, 21> key_map{{ {ImGuiKey_Tab, Qt::Key_Tab}, {ImGuiKey_LeftArrow, Qt::Key_Left}, {ImGuiKey_RightArrow, Qt::Key_Right}, @@ -552,11 +554,6 @@ void RenderWidget::SetImGuiKeyMap() {ImGuiKey_Y, Qt::Key_Y}, {ImGuiKey_Z, Qt::Key_Z}, }}; - auto lock = g_renderer->GetImGuiLock(); - if (!ImGui::GetCurrentContext()) - return; - - for (auto [imgui_key, qt_key] : key_map) - ImGui::GetIO().KeyMap[imgui_key] = (qt_key & 0x1FF); + g_presenter->SetKeyMap(key_map); } diff --git a/Source/Core/VideoBackends/D3D/D3DRender.cpp b/Source/Core/VideoBackends/D3D/D3DRender.cpp index 988f9fb6ad..6e045979cb 100644 --- a/Source/Core/VideoBackends/D3D/D3DRender.cpp +++ b/Source/Core/VideoBackends/D3D/D3DRender.cpp @@ -30,6 +30,7 @@ #include "VideoCommon/BPFunctions.h" #include "VideoCommon/FramebufferManager.h" #include "VideoCommon/PostProcessing.h" +#include "VideoCommon/Present.h" #include "VideoCommon/RenderState.h" #include "VideoCommon/VideoConfig.h" #include "VideoCommon/XFMemory.h" @@ -178,16 +179,15 @@ void Renderer::OnConfigChanged(u32 bits) void Renderer::CheckForSwapChainChanges() { - const bool surface_changed = m_surface_changed.TestAndClear(); + const bool surface_changed = g_presenter->SurfaceChangedTestAndClear(); const bool surface_resized = - m_surface_resized.TestAndClear() || m_swap_chain->CheckForFullscreenChange(); + g_presenter->SurfaceResizedTestAndClear() || m_swap_chain->CheckForFullscreenChange(); if (!surface_changed && !surface_resized) return; if (surface_changed) { - m_swap_chain->ChangeSurface(m_new_surface_handle); - m_new_surface_handle = nullptr; + m_swap_chain->ChangeSurface(g_presenter->GetNewSurfaceHandle()); } else { diff --git a/Source/Core/VideoBackends/D3D12/D3D12Renderer.cpp b/Source/Core/VideoBackends/D3D12/D3D12Renderer.cpp index f4ef717c07..ab6b1a464b 100644 --- a/Source/Core/VideoBackends/D3D12/D3D12Renderer.cpp +++ b/Source/Core/VideoBackends/D3D12/D3D12Renderer.cpp @@ -15,6 +15,7 @@ #include "VideoBackends/D3D12/DX12Texture.h" #include "VideoBackends/D3D12/DX12VertexFormat.h" #include "VideoBackends/D3D12/DescriptorHeapManager.h" +#include "VideoCommon/Present.h" #include "VideoCommon/VideoConfig.h" namespace DX12 @@ -419,9 +420,9 @@ void Renderer::BindBackbuffer(const ClearColor& clear_color) void Renderer::CheckForSwapChainChanges() { - const bool surface_changed = m_surface_changed.TestAndClear(); + const bool surface_changed = g_presenter->SurfaceChangedTestAndClear(); const bool surface_resized = - m_surface_resized.TestAndClear() || m_swap_chain->CheckForFullscreenChange(); + g_presenter->SurfaceResizedTestAndClear() || m_swap_chain->CheckForFullscreenChange(); if (!surface_changed && !surface_resized) return; @@ -429,8 +430,7 @@ void Renderer::CheckForSwapChainChanges() WaitForGPUIdle(); if (surface_changed) { - m_swap_chain->ChangeSurface(m_new_surface_handle); - m_new_surface_handle = nullptr; + m_swap_chain->ChangeSurface(g_presenter->GetNewSurfaceHandle()); } else { diff --git a/Source/Core/VideoBackends/Metal/MTLRenderer.mm b/Source/Core/VideoBackends/Metal/MTLRenderer.mm index 7e915f045a..9d8ae1d532 100644 --- a/Source/Core/VideoBackends/Metal/MTLRenderer.mm +++ b/Source/Core/VideoBackends/Metal/MTLRenderer.mm @@ -13,6 +13,7 @@ #include "VideoBackends/Metal/MTLVertexManager.h" #include "VideoCommon/FramebufferManager.h" +#include "VideoCommon/Present.h" #include "VideoCommon/VideoBackendBase.h" Metal::Renderer::Renderer(MRCOwned layer, int width, int height, float layer_scale) @@ -480,16 +481,15 @@ std::unique_ptr<::BoundingBox> Metal::Renderer::CreateBoundingBox() const void Metal::Renderer::CheckForSurfaceChange() { - if (!m_surface_changed.TestAndClear()) + if (!g_presenter->SurfaceChangedTestAndClear()) return; - m_layer = MRCRetain(static_cast(m_new_surface_handle)); - m_new_surface_handle = nullptr; + m_layer = MRCRetain(static_cast(g_presenter->GetNewSurfaceHandle())); SetupSurface(); } void Metal::Renderer::CheckForSurfaceResize() { - if (!m_surface_resized.TestAndClear()) + if (!g_presenter->SurfaceResizedTestAndClear()) return; SetupSurface(); } diff --git a/Source/Core/VideoBackends/OGL/OGLRender.cpp b/Source/Core/VideoBackends/OGL/OGLRender.cpp index b9864ac7a0..fba72ba11b 100644 --- a/Source/Core/VideoBackends/OGL/OGLRender.cpp +++ b/Source/Core/VideoBackends/OGL/OGLRender.cpp @@ -32,6 +32,7 @@ #include "VideoCommon/FramebufferManager.h" #include "VideoCommon/OnScreenDisplay.h" #include "VideoCommon/PostProcessing.h" +#include "VideoCommon/Present.h" #include "VideoCommon/RenderState.h" #include "VideoCommon/VideoCommon.h" #include "VideoCommon/VideoConfig.h" @@ -973,7 +974,7 @@ void Renderer::SelectRightBuffer() glDrawBuffer(GL_BACK_RIGHT); } - void Renderer::SelectMainBuffer() +void Renderer::SelectMainBuffer() { glDrawBuffer(GL_BACK); } @@ -1074,27 +1075,29 @@ void Renderer::WaitForGPUIdle() void Renderer::CheckForSurfaceChange() { - if (!m_surface_changed.TestAndClear()) + if (!g_presenter->SurfaceChangedTestAndClear()) return; - m_main_gl_context->UpdateSurface(m_new_surface_handle); - m_new_surface_handle = nullptr; + m_main_gl_context->UpdateSurface(g_presenter->GetNewSurfaceHandle()); + + u32 width = m_main_gl_context->GetBackBufferWidth(); + u32 height = m_main_gl_context->GetBackBufferHeight(); // With a surface change, the window likely has new dimensions. - m_backbuffer_width = m_main_gl_context->GetBackBufferWidth(); - m_backbuffer_height = m_main_gl_context->GetBackBufferHeight(); - m_system_framebuffer->UpdateDimensions(m_backbuffer_width, m_backbuffer_height); + g_presenter->SetBackbuffer(width, height); + m_system_framebuffer->UpdateDimensions(width, height); } void Renderer::CheckForSurfaceResize() { - if (!m_surface_resized.TestAndClear()) + if (!g_presenter->SurfaceResizedTestAndClear()) return; m_main_gl_context->Update(); - m_backbuffer_width = m_main_gl_context->GetBackBufferWidth(); - m_backbuffer_height = m_main_gl_context->GetBackBufferHeight(); - m_system_framebuffer->UpdateDimensions(m_backbuffer_width, m_backbuffer_height); + u32 width = m_main_gl_context->GetBackBufferWidth(); + u32 height = m_main_gl_context->GetBackBufferHeight(); + g_presenter->SetBackbuffer(width, height); + m_system_framebuffer->UpdateDimensions(width, height); } void Renderer::BeginUtilityDrawing() diff --git a/Source/Core/VideoBackends/Software/SWRenderer.cpp b/Source/Core/VideoBackends/Software/SWRenderer.cpp index 5d233a9b87..887bce177f 100644 --- a/Source/Core/VideoBackends/Software/SWRenderer.cpp +++ b/Source/Core/VideoBackends/Software/SWRenderer.cpp @@ -23,6 +23,7 @@ #include "VideoCommon/AbstractTexture.h" #include "VideoCommon/NativeVertexFormat.h" #include "VideoCommon/PixelEngine.h" +#include "VideoCommon/Present.h" #include "VideoCommon/VideoBackendBase.h" #include "VideoCommon/VideoCommon.h" @@ -63,13 +64,12 @@ SWRenderer::CreateFramebuffer(AbstractTexture* color_attachment, AbstractTexture void SWRenderer::BindBackbuffer(const ClearColor& clear_color) { // Look for framebuffer resizes - if (!m_surface_resized.TestAndClear()) + if (!g_presenter->SurfaceResizedTestAndClear()) return; GLContext* context = m_window->GetContext(); context->Update(); - m_backbuffer_width = context->GetBackBufferWidth(); - m_backbuffer_height = context->GetBackBufferHeight(); + g_presenter->SetBackbuffer(context->GetBackBufferWidth(), context->GetBackBufferHeight()); } class SWShader final : public AbstractShader diff --git a/Source/Core/VideoBackends/Vulkan/VKRenderer.cpp b/Source/Core/VideoBackends/Vulkan/VKRenderer.cpp index 5256d6ada3..19c2b19778 100644 --- a/Source/Core/VideoBackends/Vulkan/VKRenderer.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKRenderer.cpp @@ -33,6 +33,7 @@ #include "VideoCommon/DriverDetails.h" #include "VideoCommon/FramebufferManager.h" +#include "VideoCommon/Present.h" #include "VideoCommon/RenderState.h" #include "VideoCommon/VertexManagerBase.h" #include "VideoCommon/VideoBackendBase.h" @@ -379,7 +380,7 @@ void Renderer::ExecuteCommandBuffer(bool submit_off_thread, bool wait_for_comple void Renderer::CheckForSurfaceChange() { - if (!m_surface_changed.TestAndClear() || !m_swap_chain) + if (!g_presenter->SurfaceChangedTestAndClear() || !m_swap_chain) return; // Submit the current draws up until rendering the XFB. @@ -389,9 +390,8 @@ void Renderer::CheckForSurfaceChange() g_command_buffer_mgr->CheckLastPresentFail(); // Recreate the surface. If this fails we're in trouble. - if (!m_swap_chain->RecreateSurface(m_new_surface_handle)) + if (!m_swap_chain->RecreateSurface(g_presenter->GetNewSurfaceHandle())) PanicAlertFmt("Failed to recreate Vulkan surface. Cannot continue."); - m_new_surface_handle = nullptr; // Handle case where the dimensions are now different. OnSwapChainResized(); @@ -399,7 +399,7 @@ void Renderer::CheckForSurfaceChange() void Renderer::CheckForSurfaceResize() { - if (!m_surface_resized.TestAndClear()) + if (!g_presenter->SurfaceResizedTestAndClear()) return; // If we don't have a surface, how can we resize the swap chain? @@ -450,8 +450,7 @@ void Renderer::OnConfigChanged(u32 bits) void Renderer::OnSwapChainResized() { - m_backbuffer_width = m_swap_chain->GetWidth(); - m_backbuffer_height = m_swap_chain->GetHeight(); + g_presenter->SetBackbuffer(m_swap_chain->GetWidth(), m_swap_chain->GetHeight()); } void Renderer::BindFramebuffer(VKFramebuffer* fb) diff --git a/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp b/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp index a7f5bb929d..71fb6bf9ba 100644 --- a/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp @@ -15,7 +15,7 @@ #include "VideoBackends/Vulkan/ObjectCache.h" #include "VideoBackends/Vulkan/VKTexture.h" #include "VideoBackends/Vulkan/VulkanContext.h" -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/Present.h" #if defined(VK_USE_PLATFORM_XLIB_KHR) #include @@ -265,8 +265,8 @@ bool SwapChain::CreateSwapChain() VkExtent2D size = surface_capabilities.currentExtent; if (size.width == UINT32_MAX) { - size.width = std::max(g_renderer->GetBackbufferWidth(), 1); - size.height = std::max(g_renderer->GetBackbufferHeight(), 1); + size.width = std::max(g_presenter->GetBackbufferWidth(), 1); + size.height = std::max(g_presenter->GetBackbufferHeight(), 1); } size.width = std::clamp(size.width, surface_capabilities.minImageExtent.width, surface_capabilities.maxImageExtent.width); diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index 4e8bff36fc..1cc23ba7aa 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -34,6 +34,8 @@ add_library(videocommon FramebufferManager.h FramebufferShaderGen.cpp FramebufferShaderGen.h + FrameDumper.cpp + FrameDumper.h FreeLookCamera.cpp FreeLookCamera.h GeometryShaderGen.cpp @@ -82,6 +84,8 @@ add_library(videocommon NetPlayGolfUI.h OnScreenDisplay.cpp OnScreenDisplay.h + OnScreenUI.cpp + OnScreenUI.h OpcodeDecoding.cpp OpcodeDecoding.h PerfQueryBase.cpp @@ -98,6 +102,8 @@ add_library(videocommon PixelShaderManager.h PostProcessing.cpp PostProcessing.h + Present.cpp + Present.h RenderBase.cpp RenderBase.h RenderState.cpp diff --git a/Source/Core/VideoCommon/FrameDump.cpp b/Source/Core/VideoCommon/FrameDump.cpp index 55e0e3daa6..c5edffc04e 100644 --- a/Source/Core/VideoCommon/FrameDump.cpp +++ b/Source/Core/VideoCommon/FrameDump.cpp @@ -37,6 +37,7 @@ extern "C" { #include "Core/HW/SystemTimers.h" #include "Core/HW/VideoInterface.h" +#include "VideoCommon/FrameDumper.h" #include "VideoCommon/OnScreenDisplay.h" #include "VideoCommon/VideoConfig.h" @@ -157,7 +158,7 @@ std::string AVErrorString(int error) } // namespace -bool FrameDump::Start(int w, int h, u64 start_ticks) +bool FFMpegFrameDump::Start(int w, int h, u64 start_ticks) { if (IsStarted()) return true; @@ -169,7 +170,7 @@ bool FrameDump::Start(int w, int h, u64 start_ticks) return PrepareEncoding(w, h, start_ticks, m_savestate_index); } -bool FrameDump::PrepareEncoding(int w, int h, u64 start_ticks, u32 savestate_index) +bool FFMpegFrameDump::PrepareEncoding(int w, int h, u64 start_ticks, u32 savestate_index) { m_context = std::make_unique(); @@ -189,7 +190,7 @@ bool FrameDump::PrepareEncoding(int w, int h, u64 start_ticks, u32 savestate_ind return success; } -bool FrameDump::CreateVideoFile() +bool FFMpegFrameDump::CreateVideoFile() { const std::string& format = g_Config.sDumpFormat; @@ -335,12 +336,12 @@ bool FrameDump::CreateVideoFile() return true; } -bool FrameDump::IsFirstFrameInCurrentFile() const +bool FFMpegFrameDump::IsFirstFrameInCurrentFile() const { return m_context->last_pts == AV_NOPTS_VALUE; } -void FrameDump::AddFrame(const FrameData& frame) +void FFMpegFrameDump::AddFrame(const FrameData& frame) { // Are we even dumping? if (!IsStarted()) @@ -402,7 +403,7 @@ void FrameDump::AddFrame(const FrameData& frame) ProcessPackets(); } -void FrameDump::ProcessPackets() +void FFMpegFrameDump::ProcessPackets() { auto pkt = std::unique_ptr>( av_packet_alloc(), [](AVPacket* packet) { av_packet_free(&packet); }); @@ -440,7 +441,7 @@ void FrameDump::ProcessPackets() } } -void FrameDump::Stop() +void FFMpegFrameDump::Stop() { if (!IsStarted()) return; @@ -457,12 +458,12 @@ void FrameDump::Stop() OSD::AddMessage("Stopped dumping frames"); } -bool FrameDump::IsStarted() const +bool FFMpegFrameDump::IsStarted() const { return m_context != nullptr; } -void FrameDump::CloseVideoFile() +void FFMpegFrameDump::CloseVideoFile() { av_frame_free(&m_context->src_frame); av_frame_free(&m_context->scaled_frame); @@ -480,13 +481,13 @@ void FrameDump::CloseVideoFile() m_context.reset(); } -void FrameDump::DoState(PointerWrap& p) +void FFMpegFrameDump::DoState(PointerWrap& p) { if (p.IsReadMode()) ++m_savestate_index; } -void FrameDump::CheckForConfigChange(const FrameData& frame) +void FFMpegFrameDump::CheckForConfigChange(const FrameData& frame) { bool restart_dump = false; @@ -524,7 +525,7 @@ void FrameDump::CheckForConfigChange(const FrameData& frame) } } -FrameDump::FrameState FrameDump::FetchState(u64 ticks, int frame_number) const +FrameState FFMpegFrameDump::FetchState(u64 ticks, int frame_number) const { FrameState state; state.ticks = ticks; @@ -537,9 +538,9 @@ FrameDump::FrameState FrameDump::FetchState(u64 ticks, int frame_number) const return state; } -FrameDump::FrameDump() = default; +FFMpegFrameDump::FFMpegFrameDump() = default; -FrameDump::~FrameDump() +FFMpegFrameDump::~FFMpegFrameDump() { Stop(); } diff --git a/Source/Core/VideoCommon/FrameDump.h b/Source/Core/VideoCommon/FrameDump.h index 23e3bead20..18c98c7720 100644 --- a/Source/Core/VideoCommon/FrameDump.h +++ b/Source/Core/VideoCommon/FrameDump.h @@ -10,32 +10,14 @@ struct FrameDumpContext; class PointerWrap; +struct FrameData; +struct FrameState; -class FrameDump +class FFMpegFrameDump { public: - FrameDump(); - ~FrameDump(); - - // Holds relevant emulation state during a rendered frame for - // when it is later asynchronously written. - struct FrameState - { - u64 ticks = 0; - int frame_number = 0; - u32 savestate_index = 0; - int refresh_rate_num = 0; - int refresh_rate_den = 0; - }; - - struct FrameData - { - const u8* data = nullptr; - int width = 0; - int height = 0; - int stride = 0; - FrameState state; - }; + FFMpegFrameDump(); + ~FFMpegFrameDump(); bool Start(int w, int h, u64 start_ticks); void AddFrame(const FrameData&); @@ -68,7 +50,7 @@ private: inline FrameDump::FrameDump() = default; inline FrameDump::~FrameDump() = default; -inline FrameDump::FrameState FrameDump::FetchState(u64 ticks, int frame_number) const +inline FrameState FrameDump::FetchState(u64 ticks, int frame_number) const { return {}; } diff --git a/Source/Core/VideoCommon/FrameDumper.cpp b/Source/Core/VideoCommon/FrameDumper.cpp new file mode 100644 index 0000000000..db0d099376 --- /dev/null +++ b/Source/Core/VideoCommon/FrameDumper.cpp @@ -0,0 +1,354 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/FrameDumper.h" + +#include "Common/Assert.h" +#include "Common/FileUtil.h" +#include "Common/Image.h" + +#include "Core/Config/GraphicsSettings.h" +#include "Core/Config/MainSettings.h" + +#include "VideoCommon/AbstractFramebuffer.h" +#include "VideoCommon/AbstractStagingTexture.h" +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/OnScreenDisplay.h" +#include "VideoCommon/Present.h" +#include "VideoCommon/RenderBase.h" +#include "VideoCommon/VideoConfig.h" + +static bool DumpFrameToPNG(const FrameData& frame, const std::string& file_name) +{ + return Common::ConvertRGBAToRGBAndSavePNG(file_name, frame.data, frame.width, frame.height, + frame.stride, + Config::Get(Config::GFX_PNG_COMPRESSION_LEVEL)); +} + +FrameDumper::FrameDumper() +{ +} + +FrameDumper::~FrameDumper() +{ + ShutdownFrameDumping(); +} + +void FrameDumper::DumpCurrentFrame(const AbstractTexture* src_texture, + const MathUtil::Rectangle& src_rect, + const MathUtil::Rectangle& target_rect, u64 ticks, + int frame_number) +{ + int source_width = src_rect.GetWidth(); + int source_height = src_rect.GetHeight(); + int target_width = target_rect.GetWidth(); + int target_height = target_rect.GetHeight(); + + // We only need to render a copy if we need to stretch/scale the XFB copy. + MathUtil::Rectangle copy_rect = src_rect; + if (source_width != target_width || source_height != target_height) + { + if (!CheckFrameDumpRenderTexture(target_width, target_height)) + return; + + g_renderer->ScaleTexture(m_frame_dump_render_framebuffer.get(), + m_frame_dump_render_framebuffer->GetRect(), src_texture, src_rect); + src_texture = m_frame_dump_render_texture.get(); + copy_rect = src_texture->GetRect(); + } + + if (!CheckFrameDumpReadbackTexture(target_width, target_height)) + return; + + m_frame_dump_readback_texture->CopyFromTexture(src_texture, copy_rect, 0, 0, + m_frame_dump_readback_texture->GetRect()); + m_last_frame_state = m_ffmpeg_dump.FetchState(ticks, frame_number); + m_frame_dump_needs_flush = true; +} + +bool FrameDumper::CheckFrameDumpRenderTexture(u32 target_width, u32 target_height) +{ + // Ensure framebuffer exists (we lazily allocate it in case frame dumping isn't used). + // Or, resize texture if it isn't large enough to accommodate the current frame. + if (m_frame_dump_render_texture && m_frame_dump_render_texture->GetWidth() == target_width && + m_frame_dump_render_texture->GetHeight() == target_height) + { + return true; + } + + // Recreate texture, but release before creating so we don't temporarily use twice the RAM. + m_frame_dump_render_framebuffer.reset(); + m_frame_dump_render_texture.reset(); + m_frame_dump_render_texture = g_renderer->CreateTexture( + TextureConfig(target_width, target_height, 1, 1, 1, AbstractTextureFormat::RGBA8, + AbstractTextureFlag_RenderTarget), + "Frame dump render texture"); + if (!m_frame_dump_render_texture) + { + PanicAlertFmt("Failed to allocate frame dump render texture"); + return false; + } + m_frame_dump_render_framebuffer = + g_renderer->CreateFramebuffer(m_frame_dump_render_texture.get(), nullptr); + ASSERT(m_frame_dump_render_framebuffer); + return true; +} + +bool FrameDumper::CheckFrameDumpReadbackTexture(u32 target_width, u32 target_height) +{ + std::unique_ptr& rbtex = m_frame_dump_readback_texture; + if (rbtex && rbtex->GetWidth() == target_width && rbtex->GetHeight() == target_height) + return true; + + rbtex.reset(); + rbtex = g_renderer->CreateStagingTexture( + StagingTextureType::Readback, + TextureConfig(target_width, target_height, 1, 1, 1, AbstractTextureFormat::RGBA8, 0)); + if (!rbtex) + return false; + + return true; +} + +void FrameDumper::FlushFrameDump() +{ + if (!m_frame_dump_needs_flush) + return; + + // Ensure dumping thread is done with output texture before swapping. + FinishFrameData(); + + std::swap(m_frame_dump_output_texture, m_frame_dump_readback_texture); + + // Queue encoding of the last frame dumped. + auto& output = m_frame_dump_output_texture; + output->Flush(); + if (output->Map()) + { + DumpFrameData(reinterpret_cast(output->GetMappedPointer()), output->GetConfig().width, + output->GetConfig().height, static_cast(output->GetMappedStride())); + } + else + { + ERROR_LOG_FMT(VIDEO, "Failed to map texture for dumping."); + } + + m_frame_dump_needs_flush = false; + + // Shutdown frame dumping if it is no longer active. + if (!IsFrameDumping()) + ShutdownFrameDumping(); +} + +void FrameDumper::ShutdownFrameDumping() +{ + // Ensure the last queued readback has been sent to the encoder. + FlushFrameDump(); + + if (!m_frame_dump_thread_running.IsSet()) + return; + + // Ensure previous frame has been encoded. + FinishFrameData(); + + // Wake thread up, and wait for it to exit. + m_frame_dump_thread_running.Clear(); + m_frame_dump_start.Set(); + if (m_frame_dump_thread.joinable()) + m_frame_dump_thread.join(); + m_frame_dump_render_framebuffer.reset(); + m_frame_dump_render_texture.reset(); + + m_frame_dump_readback_texture.reset(); + m_frame_dump_output_texture.reset(); +} + +void FrameDumper::DumpFrameData(const u8* data, int w, int h, int stride) +{ + m_frame_dump_data = FrameData{data, w, h, stride, m_last_frame_state}; + + if (!m_frame_dump_thread_running.IsSet()) + { + if (m_frame_dump_thread.joinable()) + m_frame_dump_thread.join(); + m_frame_dump_thread_running.Set(); + m_frame_dump_thread = std::thread(&FrameDumper::FrameDumpThreadFunc, this); + } + + // Wake worker thread up. + m_frame_dump_start.Set(); + m_frame_dump_frame_running = true; +} + +void FrameDumper::FinishFrameData() +{ + if (!m_frame_dump_frame_running) + return; + + m_frame_dump_done.Wait(); + m_frame_dump_frame_running = false; + + m_frame_dump_output_texture->Unmap(); +} + +void FrameDumper::FrameDumpThreadFunc() +{ + Common::SetCurrentThreadName("FrameDumping"); + + bool dump_to_ffmpeg = !g_ActiveConfig.bDumpFramesAsImages; + bool frame_dump_started = false; + +// If Dolphin was compiled without ffmpeg, we only support dumping to images. +#if !defined(HAVE_FFMPEG) + if (dump_to_ffmpeg) + { + WARN_LOG_FMT(VIDEO, "FrameDump: Dolphin was not compiled with FFmpeg, using fallback option. " + "Frames will be saved as PNG images instead."); + dump_to_ffmpeg = false; + } +#endif + + while (true) + { + m_frame_dump_start.Wait(); + if (!m_frame_dump_thread_running.IsSet()) + break; + + auto frame = m_frame_dump_data; + + // Save screenshot + if (m_screenshot_request.TestAndClear()) + { + std::lock_guard lk(m_screenshot_lock); + + if (DumpFrameToPNG(frame, m_screenshot_name)) + OSD::AddMessage("Screenshot saved to " + m_screenshot_name); + + // Reset settings + m_screenshot_name.clear(); + m_screenshot_completed.Set(); + } + + if (Config::Get(Config::MAIN_MOVIE_DUMP_FRAMES)) + { + if (!frame_dump_started) + { + if (dump_to_ffmpeg) + frame_dump_started = StartFrameDumpToFFMPEG(frame); + else + frame_dump_started = StartFrameDumpToImage(frame); + + // Stop frame dumping if we fail to start. + if (!frame_dump_started) + Config::SetCurrent(Config::MAIN_MOVIE_DUMP_FRAMES, false); + } + + // If we failed to start frame dumping, don't write a frame. + if (frame_dump_started) + { + if (dump_to_ffmpeg) + DumpFrameToFFMPEG(frame); + else + DumpFrameToImage(frame); + } + } + + m_frame_dump_done.Set(); + } + + if (frame_dump_started) + { + // No additional cleanup is needed when dumping to images. + if (dump_to_ffmpeg) + StopFrameDumpToFFMPEG(); + } +} + +#if defined(HAVE_FFMPEG) + +bool FrameDumper::StartFrameDumpToFFMPEG(const FrameData& frame) +{ + // If dumping started at boot, the start time must be set to the boot time to maintain audio sync. + // TODO: Perhaps we should care about this when starting dumping in the middle of emulation too, + // but it's less important there since the first frame to dump usually gets delivered quickly. + const u64 start_ticks = frame.state.frame_number == 0 ? 0 : frame.state.ticks; + return m_ffmpeg_dump.Start(frame.width, frame.height, start_ticks); +} + +void FrameDumper::DumpFrameToFFMPEG(const FrameData& frame) +{ + m_ffmpeg_dump.AddFrame(frame); +} + +void FrameDumper::StopFrameDumpToFFMPEG() +{ + m_ffmpeg_dump.Stop(); +} + +#else + +bool FrameDump::StartFrameDumpToFFMPEG(const FrameData&) +{ + return false; +} + +void FrameDump::DumpFrameToFFMPEG(const FrameData&) +{ +} + +void FrameDump::StopFrameDumpToFFMPEG() +{ +} + +#endif // defined(HAVE_FFMPEG) + +std::string FrameDumper::GetFrameDumpNextImageFileName() const +{ + return fmt::format("{}framedump_{}.png", File::GetUserPath(D_DUMPFRAMES_IDX), + m_frame_dump_image_counter); +} + +bool FrameDumper::StartFrameDumpToImage(const FrameData&) +{ + m_frame_dump_image_counter = 1; + if (!Config::Get(Config::MAIN_MOVIE_DUMP_FRAMES_SILENT)) + { + // Only check for the presence of the first image to confirm overwriting. + // A previous run will always have at least one image, and it's safe to assume that if the user + // has allowed the first image to be overwritten, this will apply any remaining images as well. + std::string filename = GetFrameDumpNextImageFileName(); + if (File::Exists(filename)) + { + if (!AskYesNoFmtT("Frame dump image(s) '{0}' already exists. Overwrite?", filename)) + return false; + } + } + + return true; +} + +void FrameDumper::DumpFrameToImage(const FrameData& frame) +{ + DumpFrameToPNG(frame, GetFrameDumpNextImageFileName()); + m_frame_dump_image_counter++; +} + +void FrameDumper::SaveScreenshot(std::string filename) +{ + std::lock_guard lk(m_screenshot_lock); + m_screenshot_name = std::move(filename); + m_screenshot_request.Set(); +} + +bool FrameDumper::IsFrameDumping() const +{ + if (m_screenshot_request.IsSet()) + return true; + + if (Config::Get(Config::MAIN_MOVIE_DUMP_FRAMES)) + return true; + + return false; +} + +std::unique_ptr g_frame_dumper; diff --git a/Source/Core/VideoCommon/FrameDumper.h b/Source/Core/VideoCommon/FrameDumper.h new file mode 100644 index 0000000000..2051346ef9 --- /dev/null +++ b/Source/Core/VideoCommon/FrameDumper.h @@ -0,0 +1,121 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Common/CommonTypes.h" +#include "Common/Event.h" +#include "Common/Flag.h" +#include "Common/MathUtil.h" +#include "Common/Thread.h" + +#include "VideoCommon/FrameDump.h" + +class AbstractStagingTexture; +class AbstractTexture; +class AbstractFramebuffer; + +// Holds relevant emulation state during a rendered frame for +// when it is later asynchronously written. +struct FrameState +{ + u64 ticks = 0; + int frame_number = 0; + u32 savestate_index = 0; + int refresh_rate_num = 0; + int refresh_rate_den = 0; +}; + +struct FrameData +{ + const u8* data = nullptr; + int width = 0; + int height = 0; + int stride = 0; + FrameState state; +}; + +class FrameDumper +{ +public: + FrameDumper(); + ~FrameDumper(); + + // Ensures all rendered frames are queued for encoding. + void FlushFrameDump(); + + // Fills the frame dump staging texture with the current XFB texture. + void DumpCurrentFrame(const AbstractTexture* src_texture, + const MathUtil::Rectangle& src_rect, + const MathUtil::Rectangle& target_rect, u64 ticks, int frame_number); + + void SaveScreenshot(std::string filename); + + bool IsFrameDumping() const; + + void DoState(PointerWrap& p) { m_ffmpeg_dump.DoState(p); } + +private: + // NOTE: The methods below are called on the framedumping thread. + void FrameDumpThreadFunc(); + bool StartFrameDumpToFFMPEG(const FrameData&); + void DumpFrameToFFMPEG(const FrameData&); + void StopFrameDumpToFFMPEG(); + std::string GetFrameDumpNextImageFileName() const; + bool StartFrameDumpToImage(const FrameData&); + void DumpFrameToImage(const FrameData&); + + void ShutdownFrameDumping(); + + // Checks that the frame dump render texture exists and is the correct size. + bool CheckFrameDumpRenderTexture(u32 target_width, u32 target_height); + + // Checks that the frame dump readback texture exists and is the correct size. + bool CheckFrameDumpReadbackTexture(u32 target_width, u32 target_height); + + // Asynchronously encodes the specified pointer of frame data to the frame dump. + void DumpFrameData(const u8* data, int w, int h, int stride); + + // Ensures all encoded frames have been written to the output file. + void FinishFrameData(); + + std::thread m_frame_dump_thread; + Common::Flag m_frame_dump_thread_running; + + // Used to kick frame dump thread. + Common::Event m_frame_dump_start; + + // Set by frame dump thread on frame completion. + Common::Event m_frame_dump_done; + + // Holds emulation state during the last swap when dumping. + FrameState m_last_frame_state; + + // Communication of frame between video and dump threads. + FrameData m_frame_dump_data; + + // Texture used for screenshot/frame dumping + std::unique_ptr m_frame_dump_render_texture; + std::unique_ptr m_frame_dump_render_framebuffer; + + // Double buffer: + std::unique_ptr m_frame_dump_readback_texture; + std::unique_ptr m_frame_dump_output_texture; + // Set when readback texture holds a frame that needs to be dumped. + bool m_frame_dump_needs_flush = false; + // Set when thread is processing output texture. + bool m_frame_dump_frame_running = false; + + // Used to generate screenshot names. + u32 m_frame_dump_image_counter = 0; + + FFMpegFrameDump m_ffmpeg_dump; + + // Screenshots + Common::Flag m_screenshot_request; + Common::Event m_screenshot_completed; + std::mutex m_screenshot_lock; + std::string m_screenshot_name; +}; + +extern std::unique_ptr g_frame_dumper; diff --git a/Source/Core/VideoCommon/OnScreenUI.cpp b/Source/Core/VideoCommon/OnScreenUI.cpp new file mode 100644 index 0000000000..72f0c8f08a --- /dev/null +++ b/Source/Core/VideoCommon/OnScreenUI.cpp @@ -0,0 +1,386 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/OnScreenUI.h" + +#include "Common/Profiler.h" +#include "Common/Timer.h" + +#include "Core/Config/MainSettings.h" +#include "Core/Config/NetplaySettings.h" +#include "Core/Movie.h" + +#include "VideoCommon/AbstractPipeline.h" +#include "VideoCommon/AbstractShader.h" +#include "VideoCommon/FramebufferShaderGen.h" +#include "VideoCommon/NetPlayChatUI.h" +#include "VideoCommon/NetPlayGolfUI.h" +#include "VideoCommon/OnScreenDisplay.h" +#include "VideoCommon/PerformanceMetrics.h" +#include "VideoCommon/Present.h" +#include "VideoCommon/RenderBase.h" +#include "VideoCommon/Statistics.h" +#include "VideoCommon/VertexManagerBase.h" +#include "VideoCommon/VideoConfig.h" + +#include +#include + +#include +#include + +namespace VideoCommon +{ +bool OnScreenUI::Initialize(u32 width, u32 height, float scale) +{ + std::unique_lock imgui_lock(m_imgui_mutex); + + if (!IMGUI_CHECKVERSION()) + { + PanicAlertFmt("ImGui version check failed"); + return false; + } + if (!ImGui::CreateContext()) + { + PanicAlertFmt("Creating ImGui context failed"); + return false; + } + if (!ImPlot::CreateContext()) + { + PanicAlertFmt("Creating ImPlot context failed"); + return false; + } + + // Don't create an ini file. TODO: Do we want this in the future? + ImGui::GetIO().IniFilename = nullptr; + SetScale(scale); + ImGui::GetStyle().WindowRounding = 7.0f; + + PortableVertexDeclaration vdecl = {}; + vdecl.position = {ComponentFormat::Float, 2, offsetof(ImDrawVert, pos), true, false}; + vdecl.texcoords[0] = {ComponentFormat::Float, 2, offsetof(ImDrawVert, uv), true, false}; + vdecl.colors[0] = {ComponentFormat::UByte, 4, offsetof(ImDrawVert, col), true, false}; + vdecl.stride = sizeof(ImDrawVert); + m_imgui_vertex_format = g_renderer->CreateNativeVertexFormat(vdecl); + if (!m_imgui_vertex_format) + { + PanicAlertFmt("Failed to create ImGui vertex format"); + return false; + } + + // Font texture(s). + { + ImGuiIO& io = ImGui::GetIO(); + u8* font_tex_pixels; + int font_tex_width, font_tex_height; + io.Fonts->GetTexDataAsRGBA32(&font_tex_pixels, &font_tex_width, &font_tex_height); + + TextureConfig font_tex_config(font_tex_width, font_tex_height, 1, 1, 1, + AbstractTextureFormat::RGBA8, 0); + std::unique_ptr font_tex = + g_renderer->CreateTexture(font_tex_config, "ImGui font texture"); + if (!font_tex) + { + PanicAlertFmt("Failed to create ImGui texture"); + return false; + } + font_tex->Load(0, font_tex_width, font_tex_height, font_tex_width, font_tex_pixels, + sizeof(u32) * font_tex_width * font_tex_height); + + io.Fonts->TexID = font_tex.get(); + + m_imgui_textures.push_back(std::move(font_tex)); + } + + if (!RecompileImGuiPipeline()) + return false; + + m_imgui_last_frame_time = Common::Timer::NowUs(); + m_ready = true; + BeginImGuiFrameUnlocked(width, height); // lock is already held + + return true; +} + +OnScreenUI::~OnScreenUI() +{ + std::unique_lock imgui_lock(m_imgui_mutex); + + ImGui::EndFrame(); + ImPlot::DestroyContext(); + ImGui::DestroyContext(); +} + +bool OnScreenUI::RecompileImGuiPipeline() +{ + if (g_presenter->GetBackbufferFormat() == AbstractTextureFormat::Undefined) + { + // No backbuffer (nogui) means no imgui rendering will happen + // Some backends don't like making pipelines with no render targets + return true; + } + + std::unique_ptr vertex_shader = g_renderer->CreateShaderFromSource( + ShaderStage::Vertex, FramebufferShaderGen::GenerateImGuiVertexShader(), + "ImGui vertex shader"); + std::unique_ptr pixel_shader = g_renderer->CreateShaderFromSource( + ShaderStage::Pixel, FramebufferShaderGen::GenerateImGuiPixelShader(), "ImGui pixel shader"); + if (!vertex_shader || !pixel_shader) + { + PanicAlertFmt("Failed to compile ImGui shaders"); + return false; + } + + // GS is used to render the UI to both eyes in stereo modes. + std::unique_ptr geometry_shader; + if (g_renderer->UseGeometryShaderForUI()) + { + geometry_shader = g_renderer->CreateShaderFromSource( + ShaderStage::Geometry, FramebufferShaderGen::GeneratePassthroughGeometryShader(1, 1), + "ImGui passthrough geometry shader"); + if (!geometry_shader) + { + PanicAlertFmt("Failed to compile ImGui geometry shader"); + return false; + } + } + + AbstractPipelineConfig pconfig = {}; + pconfig.vertex_format = m_imgui_vertex_format.get(); + pconfig.vertex_shader = vertex_shader.get(); + pconfig.geometry_shader = geometry_shader.get(); + pconfig.pixel_shader = pixel_shader.get(); + pconfig.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles); + pconfig.depth_state = RenderState::GetNoDepthTestingDepthState(); + pconfig.blending_state = RenderState::GetNoBlendingBlendState(); + pconfig.blending_state.blendenable = true; + pconfig.blending_state.srcfactor = SrcBlendFactor::SrcAlpha; + pconfig.blending_state.dstfactor = DstBlendFactor::InvSrcAlpha; + pconfig.blending_state.srcfactoralpha = SrcBlendFactor::Zero; + pconfig.blending_state.dstfactoralpha = DstBlendFactor::One; + pconfig.framebuffer_state.color_texture_format = g_presenter->GetBackbufferFormat(); + pconfig.framebuffer_state.depth_texture_format = AbstractTextureFormat::Undefined; + pconfig.framebuffer_state.samples = 1; + pconfig.framebuffer_state.per_sample_shading = false; + pconfig.usage = AbstractPipelineUsage::Utility; + m_imgui_pipeline = g_renderer->CreatePipeline(pconfig); + if (!m_imgui_pipeline) + { + PanicAlertFmt("Failed to create imgui pipeline"); + return false; + } + + return true; +} + +void OnScreenUI::BeginImGuiFrame(u32 width, u32 height) +{ + std::unique_lock imgui_lock(m_imgui_mutex); + BeginImGuiFrameUnlocked(width, height); +} + +void OnScreenUI::BeginImGuiFrameUnlocked(u32 width, u32 height) +{ + m_backbuffer_width = width; + m_backbuffer_height = height; + + const u64 current_time_us = Common::Timer::NowUs(); + const u64 time_diff_us = current_time_us - m_imgui_last_frame_time; + const float time_diff_secs = static_cast(time_diff_us / 1000000.0); + m_imgui_last_frame_time = current_time_us; + + // Update I/O with window dimensions. + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize = + ImVec2(static_cast(m_backbuffer_width), static_cast(m_backbuffer_height)); + io.DeltaTime = time_diff_secs; + + ImGui::NewFrame(); +} + +void OnScreenUI::DrawImGui() +{ + ImDrawData* draw_data = ImGui::GetDrawData(); + if (!draw_data) + return; + + g_renderer->SetViewport(0.0f, 0.0f, static_cast(m_backbuffer_width), + static_cast(m_backbuffer_height), 0.0f, 1.0f); + + // Uniform buffer for draws. + struct ImGuiUbo + { + float u_rcp_viewport_size_mul2[2]; + float padding[2]; + }; + ImGuiUbo ubo = {{1.0f / m_backbuffer_width * 2.0f, 1.0f / m_backbuffer_height * 2.0f}}; + + // Set up common state for drawing. + g_renderer->SetPipeline(m_imgui_pipeline.get()); + g_renderer->SetSamplerState(0, RenderState::GetPointSamplerState()); + g_vertex_manager->UploadUtilityUniforms(&ubo, sizeof(ubo)); + + for (int i = 0; i < draw_data->CmdListsCount; i++) + { + const ImDrawList* cmdlist = draw_data->CmdLists[i]; + if (cmdlist->VtxBuffer.empty() || cmdlist->IdxBuffer.empty()) + return; + + u32 base_vertex, base_index; + g_vertex_manager->UploadUtilityVertices(cmdlist->VtxBuffer.Data, sizeof(ImDrawVert), + cmdlist->VtxBuffer.Size, cmdlist->IdxBuffer.Data, + cmdlist->IdxBuffer.Size, &base_vertex, &base_index); + + for (const ImDrawCmd& cmd : cmdlist->CmdBuffer) + { + if (cmd.UserCallback) + { + cmd.UserCallback(cmdlist, &cmd); + continue; + } + + g_renderer->SetScissorRect(g_renderer->ConvertFramebufferRectangle( + MathUtil::Rectangle( + static_cast(cmd.ClipRect.x), static_cast(cmd.ClipRect.y), + static_cast(cmd.ClipRect.z), static_cast(cmd.ClipRect.w)), + g_renderer->GetCurrentFramebuffer())); + g_renderer->SetTexture(0, reinterpret_cast(cmd.TextureId)); + g_renderer->DrawIndexed(base_index, cmd.ElemCount, base_vertex); + base_index += cmd.ElemCount; + } + } + + // Some capture software (such as OBS) hooks SwapBuffers and uses glBlitFramebuffer to copy our + // back buffer just before swap. Because glBlitFramebuffer honors the scissor test, the capture + // itself will be clipped to whatever bounds were last set by ImGui, resulting in a rather useless + // capture whenever any ImGui windows are open. We'll reset the scissor rectangle to the entire + // viewport here to avoid this problem. + g_renderer->SetScissorRect(g_renderer->ConvertFramebufferRectangle( + MathUtil::Rectangle(0, 0, m_backbuffer_width, m_backbuffer_height), + g_renderer->GetCurrentFramebuffer())); +} + +// Create On-Screen-Messages +void OnScreenUI::DrawDebugText() +{ + const bool show_movie_window = + Config::Get(Config::MAIN_SHOW_FRAME_COUNT) || Config::Get(Config::MAIN_SHOW_LAG) || + Config::Get(Config::MAIN_MOVIE_SHOW_INPUT_DISPLAY) || + Config::Get(Config::MAIN_MOVIE_SHOW_RTC) || Config::Get(Config::MAIN_MOVIE_SHOW_RERECORD); + if (show_movie_window) + { + // Position under the FPS display. + ImGui::SetNextWindowPos( + ImVec2(ImGui::GetIO().DisplaySize.x - 10.f * m_backbuffer_scale, 80.f * m_backbuffer_scale), + ImGuiCond_FirstUseEver, ImVec2(1.0f, 0.0f)); + ImGui::SetNextWindowSizeConstraints( + ImVec2(150.0f * m_backbuffer_scale, 20.0f * m_backbuffer_scale), + ImGui::GetIO().DisplaySize); + if (ImGui::Begin("Movie", nullptr, ImGuiWindowFlags_NoFocusOnAppearing)) + { + if (Movie::IsPlayingInput()) + { + ImGui::Text("Frame: %" PRIu64 " / %" PRIu64, Movie::GetCurrentFrame(), + Movie::GetTotalFrames()); + ImGui::Text("Input: %" PRIu64 " / %" PRIu64, Movie::GetCurrentInputCount(), + Movie::GetTotalInputCount()); + } + else if (Config::Get(Config::MAIN_SHOW_FRAME_COUNT)) + { + ImGui::Text("Frame: %" PRIu64, Movie::GetCurrentFrame()); + ImGui::Text("Input: %" PRIu64, Movie::GetCurrentInputCount()); + } + if (Config::Get(Config::MAIN_SHOW_LAG)) + ImGui::Text("Lag: %" PRIu64 "\n", Movie::GetCurrentLagCount()); + if (Config::Get(Config::MAIN_MOVIE_SHOW_INPUT_DISPLAY)) + ImGui::TextUnformatted(Movie::GetInputDisplay().c_str()); + if (Config::Get(Config::MAIN_MOVIE_SHOW_RTC)) + ImGui::TextUnformatted(Movie::GetRTCDisplay().c_str()); + if (Config::Get(Config::MAIN_MOVIE_SHOW_RERECORD)) + ImGui::TextUnformatted(Movie::GetRerecords().c_str()); + } + ImGui::End(); + } + + if (g_ActiveConfig.bOverlayStats) + g_stats.Display(); + + if (g_ActiveConfig.bShowNetPlayMessages && g_netplay_chat_ui) + g_netplay_chat_ui->Display(); + + if (Config::Get(Config::NETPLAY_GOLF_MODE_OVERLAY) && g_netplay_golf_ui) + g_netplay_golf_ui->Display(); + + if (g_ActiveConfig.bOverlayProjStats) + g_stats.DisplayProj(); + + if (g_ActiveConfig.bOverlayScissorStats) + g_stats.DisplayScissor(); + + const std::string profile_output = Common::Profiler::ToString(); + if (!profile_output.empty()) + ImGui::TextUnformatted(profile_output.c_str()); +} + +void OnScreenUI::Finalize() +{ + auto lock = GetImGuiLock(); + + g_perf_metrics.DrawImGuiStats(m_backbuffer_scale); + DrawDebugText(); + OSD::DrawMessages(); + ImGui::Render(); +} + +std::unique_lock OnScreenUI::GetImGuiLock() +{ + return std::unique_lock(m_imgui_mutex); +} + +void OnScreenUI::SetScale(float backbuffer_scale) +{ + ImGui::GetIO().DisplayFramebufferScale.x = backbuffer_scale; + ImGui::GetIO().DisplayFramebufferScale.y = backbuffer_scale; + ImGui::GetIO().FontGlobalScale = backbuffer_scale; + ImGui::GetStyle().ScaleAllSizes(backbuffer_scale); + + m_backbuffer_scale = backbuffer_scale; +} +void OnScreenUI::SetKeyMap(std::span> key_map) +{ + auto lock = GetImGuiLock(); + + if (!ImGui::GetCurrentContext()) + return; + + for (auto [imgui_key, qt_key] : key_map) + ImGui::GetIO().KeyMap[imgui_key] = (qt_key & 0x1FF); +} + +void OnScreenUI::SetKey(u32 key, bool is_down, const char* chars) +{ + auto lock = GetImGuiLock(); + if (key < std::size(ImGui::GetIO().KeysDown)) + ImGui::GetIO().KeysDown[key] = is_down; + + if (chars) + ImGui::GetIO().AddInputCharactersUTF8(chars); +} + +void OnScreenUI::SetMousePos(float x, float y) +{ + auto lock = GetImGuiLock(); + + ImGui::GetIO().MousePos.x = x; + ImGui::GetIO().MousePos.y = y; +} + +void OnScreenUI::SetMousePress(u32 button_mask) +{ + auto lock = GetImGuiLock(); + + for (size_t i = 0; i < std::size(ImGui::GetIO().MouseDown); i++) + ImGui::GetIO().MouseDown[i] = (button_mask & (1u << i)) != 0; +} + +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/OnScreenUI.h b/Source/Core/VideoCommon/OnScreenUI.h new file mode 100644 index 0000000000..ea8eeb6630 --- /dev/null +++ b/Source/Core/VideoCommon/OnScreenUI.h @@ -0,0 +1,78 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "Common/CommonTypes.h" + +class NativeVertexFormat; +class AbstractTexture; +class AbstractPipeline; + +namespace VideoCommon +{ +class OnScreenUI +{ +public: + OnScreenUI() = default; + ~OnScreenUI(); + + // ImGui initialization depends on being able to create textures and pipelines, so do it last. + bool Initialize(u32 width, u32 height, float scale); + + // Returns a lock for the ImGui mutex, enabling data structures to be modified from outside. + // Use with care, only non-drawing functions should be called from outside the video thread, + // as the drawing is tied to a "frame". + std::unique_lock GetImGuiLock(); + + bool IsReady() { return m_ready; } + + // Sets up ImGui state for the next frame. + // This function itself acquires the ImGui lock, so it should not be held. + void BeginImGuiFrame(u32 width, u32 height); + + // Same as above but without locking the ImGui lock. + void BeginImGuiFrameUnlocked(u32 width, u32 height); + + // Renders ImGui windows to the currently-bound framebuffer. + // Should be called with the ImGui lock held. + void DrawImGui(); + + // Recompiles ImGui pipeline - call when stereo mode changes. + bool RecompileImGuiPipeline(); + + void SetScale(float backbuffer_scale); + + void Finalize(); + + // Receive keyboard and mouse from QT + void SetKeyMap(std::span> key_map); + void SetKey(u32 key, bool is_down, const char* chars); + void SetMousePos(float x, float y); + void SetMousePress(u32 button_mask); + +private: + // Destroys all ImGui GPU resources, must do before shutdown. + void ShutdownImGui(); + + void DrawDebugText(); + + // ImGui resources. + std::unique_ptr m_imgui_vertex_format; + std::vector> m_imgui_textures; + std::unique_ptr m_imgui_pipeline; + std::mutex m_imgui_mutex; + u64 m_imgui_last_frame_time; + + u32 m_backbuffer_width = 1; + u32 m_backbuffer_height = 1; + float m_backbuffer_scale = 1.0; + + bool m_ready = false; +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/PostProcessing.cpp b/Source/Core/VideoCommon/PostProcessing.cpp index 0a1e995c15..f14b8f892b 100644 --- a/Source/Core/VideoCommon/PostProcessing.cpp +++ b/Source/Core/VideoCommon/PostProcessing.cpp @@ -24,6 +24,7 @@ #include "VideoCommon/AbstractShader.h" #include "VideoCommon/AbstractTexture.h" #include "VideoCommon/FramebufferManager.h" +#include "VideoCommon/Present.h" #include "VideoCommon/RenderBase.h" #include "VideoCommon/ShaderCache.h" #include "VideoCommon/VertexManagerBase.h" @@ -627,7 +628,7 @@ size_t PostProcessing::CalculateUniformsSize() const void PostProcessing::FillUniformBuffer(const MathUtil::Rectangle& src, const AbstractTexture* src_tex, int src_layer) { - const auto& window_rect = g_renderer->GetTargetRectangle(); + const auto& window_rect = g_presenter->GetTargetRectangle(); const float rcp_src_width = 1.0f / src_tex->GetWidth(); const float rcp_src_height = 1.0f / src_tex->GetHeight(); BuiltinUniforms builtin_uniforms = { diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp new file mode 100644 index 0000000000..5cddf38aa3 --- /dev/null +++ b/Source/Core/VideoCommon/Present.cpp @@ -0,0 +1,524 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/Present.h" + +#include "Core/HW/VideoInterface.h" +#include "Core/Host.h" + +#include "InputCommon/ControllerInterface/ControllerInterface.h" + +#include "VideoCommon/FrameDumper.h" +#include "VideoCommon/OnScreenUI.h" +#include "VideoCommon/PostProcessing.h" +#include "VideoCommon/RenderBase.h" +#include "VideoCommon/Statistics.h" +#include "VideoCommon/VertexManagerBase.h" +#include "VideoCommon/VideoConfig.h" + +std::unique_ptr g_presenter; + +namespace VideoCommon +{ +static float AspectToWidescreen(float aspect) +{ + return aspect * ((16.0f / 9.0f) / (4.0f / 3.0f)); +} + +Presenter::Presenter() +{ +} + +Presenter::~Presenter() +{ + // Disable ControllerInterface's aspect ratio adjustments so mapping dialog behaves normally. + g_controller_interface.SetAspectRatioAdjustment(1); +} + +bool Presenter::Initialize() +{ + UpdateDrawRectangle(); + + m_post_processor = std::make_unique(); + if (!m_post_processor->Initialize(m_backbuffer_format)) + return false; + + m_onscreen_ui = std::make_unique(); + if (!m_onscreen_ui->Initialize(m_backbuffer_width, m_backbuffer_height, m_backbuffer_scale)) + return false; + + return true; +} + +void Presenter::SetBackbuffer(int backbuffer_width, int backbuffer_height) +{ + m_backbuffer_width = backbuffer_width; + m_backbuffer_height = backbuffer_height; + UpdateDrawRectangle(); +} + +void Presenter::SetBackbuffer(int backbuffer_width, int backbuffer_height, float backbuffer_scale, + AbstractTextureFormat backbuffer_format) +{ + m_backbuffer_width = backbuffer_width; + m_backbuffer_height = backbuffer_height; + m_backbuffer_scale = backbuffer_scale; + m_backbuffer_format = backbuffer_format; + UpdateDrawRectangle(); +} + +void Presenter::CheckForConfigChanges(u32 changed_bits) +{ + // Check for post-processing shader changes. Done up here as it doesn't affect anything outside + // the post-processor. Note that options are applied every frame, so no need to check those. + if (m_post_processor->GetConfig()->GetShader() != g_ActiveConfig.sPostProcessingShader) + { + // The existing shader must not be in use when it's destroyed + g_renderer->WaitForGPUIdle(); + + m_post_processor->RecompileShader(); + } + + // Stereo mode change requires recompiling our post processing pipeline and imgui pipelines for + // rendering the UI. + if (changed_bits & Renderer::ConfigChangeBits::CONFIG_CHANGE_BIT_STEREO_MODE) + { + m_onscreen_ui->RecompileImGuiPipeline(); + m_post_processor->RecompilePipeline(); + } +} + +void Presenter::BeginUIFrame() +{ + if (g_renderer->IsHeadless()) + return; + + g_renderer->BeginUtilityDrawing(); + g_renderer->BindBackbuffer({0.0f, 0.0f, 0.0f, 1.0f}); +} + +void Presenter::EndUIFrame() +{ + m_onscreen_ui->Finalize(); + + if (g_renderer->IsHeadless()) + { + m_onscreen_ui->DrawImGui(); + + std::lock_guard guard(m_swap_mutex); + g_renderer->PresentBackbuffer(); + g_renderer->EndUtilityDrawing(); + } + + m_onscreen_ui->BeginImGuiFrame(m_backbuffer_width, m_backbuffer_height); +} + +std::tuple, MathUtil::Rectangle> +Presenter::ConvertStereoRectangle(const MathUtil::Rectangle& rc) const +{ + // Resize target to half its original size + auto draw_rc = rc; + if (g_ActiveConfig.stereo_mode == StereoMode::TAB) + { + // The height may be negative due to flipped rectangles + int height = rc.bottom - rc.top; + draw_rc.top += height / 4; + draw_rc.bottom -= height / 4; + } + else + { + int width = rc.right - rc.left; + draw_rc.left += width / 4; + draw_rc.right -= width / 4; + } + + // Create two target rectangle offset to the sides of the backbuffer + auto left_rc = draw_rc; + auto right_rc = draw_rc; + if (g_ActiveConfig.stereo_mode == StereoMode::TAB) + { + left_rc.top -= m_backbuffer_height / 4; + left_rc.bottom -= m_backbuffer_height / 4; + right_rc.top += m_backbuffer_height / 4; + right_rc.bottom += m_backbuffer_height / 4; + } + else + { + left_rc.left -= m_backbuffer_width / 4; + left_rc.right -= m_backbuffer_width / 4; + right_rc.left += m_backbuffer_width / 4; + right_rc.right += m_backbuffer_width / 4; + } + + return std::make_tuple(left_rc, right_rc); +} + +float Presenter::CalculateDrawAspectRatio() const +{ + const auto aspect_mode = g_ActiveConfig.aspect_mode; + + // If stretch is enabled, we prefer the aspect ratio of the window. + if (aspect_mode == AspectMode::Stretch) + return (static_cast(m_backbuffer_width) / static_cast(m_backbuffer_height)); + + const float aspect_ratio = VideoInterface::GetAspectRatio(); + + if (aspect_mode == AspectMode::AnalogWide || + (aspect_mode == AspectMode::Auto && g_renderer->IsGameWidescreen())) + { + return AspectToWidescreen(aspect_ratio); + } + + return aspect_ratio; +} + +void Presenter::AdjustRectanglesToFitBounds(MathUtil::Rectangle* target_rect, + MathUtil::Rectangle* source_rect, int fb_width, + int fb_height) +{ + const int orig_target_width = target_rect->GetWidth(); + const int orig_target_height = target_rect->GetHeight(); + const int orig_source_width = source_rect->GetWidth(); + const int orig_source_height = source_rect->GetHeight(); + if (target_rect->left < 0) + { + const int offset = -target_rect->left; + target_rect->left = 0; + source_rect->left += offset * orig_source_width / orig_target_width; + } + if (target_rect->right > fb_width) + { + const int offset = target_rect->right - fb_width; + target_rect->right -= offset; + source_rect->right -= offset * orig_source_width / orig_target_width; + } + if (target_rect->top < 0) + { + const int offset = -target_rect->top; + target_rect->top = 0; + source_rect->top += offset * orig_source_height / orig_target_height; + } + if (target_rect->bottom > fb_height) + { + const int offset = target_rect->bottom - fb_height; + target_rect->bottom -= offset; + source_rect->bottom -= offset * orig_source_height / orig_target_height; + } +} + +void Presenter::ReleaseXFBContentLock() +{ + if (m_xfb_entry) + m_xfb_entry->ReleaseContentLock(); +} + +void Presenter::ChangeSurface(void* new_surface_handle) +{ + std::lock_guard lock(m_swap_mutex); + m_new_surface_handle = new_surface_handle; + m_surface_changed.Set(); +} + +void Presenter::ResizeSurface() +{ + std::lock_guard lock(m_swap_mutex); + m_surface_resized.Set(); +} + +void* Presenter::GetNewSurfaceHandle() +{ + return m_new_surface_handle; + m_new_surface_handle = nullptr; +} + +void Presenter::SetWindowSize(int width, int height) +{ + const auto [out_width, out_height] = g_presenter->CalculateOutputDimensions(width, height); + + // Track the last values of width/height to avoid sending a window resize event every frame. + if (out_width == m_last_window_request_width && out_height == m_last_window_request_height) + return; + + m_last_window_request_width = out_width; + m_last_window_request_height = out_height; + Host_RequestRenderWindowSize(out_width, out_height); +} + +// Crop to exactly 16:9 or 4:3 if enabled and not AspectMode::Stretch. +std::tuple Presenter::ApplyStandardAspectCrop(float width, float height) const +{ + const auto aspect_mode = g_ActiveConfig.aspect_mode; + + if (!g_ActiveConfig.bCrop || aspect_mode == AspectMode::Stretch) + return {width, height}; + + // Force 4:3 or 16:9 by cropping the image. + const float current_aspect = width / height; + const float expected_aspect = + (aspect_mode == AspectMode::AnalogWide || + (aspect_mode == AspectMode::Auto && g_renderer->IsGameWidescreen())) ? + (16.0f / 9.0f) : + (4.0f / 3.0f); + if (current_aspect > expected_aspect) + { + // keep height, crop width + width = height * expected_aspect; + } + else + { + // keep width, crop height + height = width / expected_aspect; + } + + return {width, height}; +} + +void Presenter::UpdateDrawRectangle() +{ + const float draw_aspect_ratio = CalculateDrawAspectRatio(); + + // Update aspect ratio hack values + // Won't take effect until next frame + // Don't know if there is a better place for this code so there isn't a 1 frame delay + if (g_ActiveConfig.bWidescreenHack) + { + float source_aspect = VideoInterface::GetAspectRatio(); + if (g_renderer && g_renderer->IsGameWidescreen()) + source_aspect = AspectToWidescreen(source_aspect); + + const float adjust = source_aspect / draw_aspect_ratio; + if (adjust > 1) + { + // Vert+ + g_Config.fAspectRatioHackW = 1; + g_Config.fAspectRatioHackH = 1 / adjust; + } + else + { + // Hor+ + g_Config.fAspectRatioHackW = adjust; + g_Config.fAspectRatioHackH = 1; + } + } + else + { + // Hack is disabled. + g_Config.fAspectRatioHackW = 1; + g_Config.fAspectRatioHackH = 1; + } + + // The rendering window size + const float win_width = static_cast(m_backbuffer_width); + const float win_height = static_cast(m_backbuffer_height); + + // FIXME: this breaks at very low widget sizes + // Make ControllerInterface aware of the render window region actually being used + // to adjust mouse cursor inputs. + g_controller_interface.SetAspectRatioAdjustment(draw_aspect_ratio / (win_width / win_height)); + + float draw_width = draw_aspect_ratio; + float draw_height = 1; + + // Crop the picture to a standard aspect ratio. (if enabled) + auto [crop_width, crop_height] = ApplyStandardAspectCrop(draw_width, draw_height); + + // scale the picture to fit the rendering window + if (win_width / win_height >= crop_width / crop_height) + { + // the window is flatter than the picture + draw_width *= win_height / crop_height; + crop_width *= win_height / crop_height; + draw_height *= win_height / crop_height; + crop_height = win_height; + } + else + { + // the window is skinnier than the picture + draw_width *= win_width / crop_width; + draw_height *= win_width / crop_width; + crop_height *= win_width / crop_width; + crop_width = win_width; + } + + // ensure divisibility by 4 to make it compatible with all the video encoders + draw_width = std::ceil(draw_width) - static_cast(std::ceil(draw_width)) % 4; + draw_height = std::ceil(draw_height) - static_cast(std::ceil(draw_height)) % 4; + + m_target_rectangle.left = static_cast(std::round(win_width / 2.0 - draw_width / 2.0)); + m_target_rectangle.top = static_cast(std::round(win_height / 2.0 - draw_height / 2.0)); + m_target_rectangle.right = m_target_rectangle.left + static_cast(draw_width); + m_target_rectangle.bottom = m_target_rectangle.top + static_cast(draw_height); +} + +std::tuple Presenter::ScaleToDisplayAspectRatio(const int width, + const int height) const +{ + // Scale either the width or height depending the content aspect ratio. + // This way we preserve as much resolution as possible when scaling. + float scaled_width = static_cast(width); + float scaled_height = static_cast(height); + const float draw_aspect = CalculateDrawAspectRatio(); + if (scaled_width / scaled_height >= draw_aspect) + scaled_height = scaled_width / draw_aspect; + else + scaled_width = scaled_height * draw_aspect; + return std::make_tuple(scaled_width, scaled_height); +} + +std::tuple Presenter::CalculateOutputDimensions(int width, int height) const +{ + width = std::max(width, 1); + height = std::max(height, 1); + + auto [scaled_width, scaled_height] = ScaleToDisplayAspectRatio(width, height); + + // Apply crop if enabled. + std::tie(scaled_width, scaled_height) = ApplyStandardAspectCrop(scaled_width, scaled_height); + + width = static_cast(std::ceil(scaled_width)); + height = static_cast(std::ceil(scaled_height)); + + // UpdateDrawRectangle() makes sure that the rendered image is divisible by four for video + // encoders, so do that here too to match it + width -= width % 4; + height -= height % 4; + + return std::make_tuple(width, height); +} + +void Presenter::RenderXFBToScreen(const MathUtil::Rectangle& target_rc, + const AbstractTexture* source_texture, + const MathUtil::Rectangle& source_rc) +{ + if (!g_ActiveConfig.backend_info.bSupportsPostProcessing) + { + g_renderer->ShowImage(source_texture, source_rc); + return; + } + + if (g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer && + g_ActiveConfig.backend_info.bUsesExplictQuadBuffering) + { + // Quad-buffered stereo is annoying on GL. + g_renderer->SelectLeftBuffer(); + m_post_processor->BlitFromTexture(target_rc, source_rc, source_texture, 0); + + g_renderer->SelectRightBuffer(); + m_post_processor->BlitFromTexture(target_rc, source_rc, source_texture, 1); + + g_renderer->SelectMainBuffer(); + } + else if (g_ActiveConfig.stereo_mode == StereoMode::SBS || + g_ActiveConfig.stereo_mode == StereoMode::TAB) + { + const auto [left_rc, right_rc] = ConvertStereoRectangle(target_rc); + + m_post_processor->BlitFromTexture(left_rc, source_rc, source_texture, 0); + m_post_processor->BlitFromTexture(right_rc, source_rc, source_texture, 1); + } + else + { + m_post_processor->BlitFromTexture(target_rc, source_rc, source_texture, 0); + } +} + +bool Presenter::SubmitXFB(RcTcacheEntry xfb_entry, MathUtil::Rectangle& xfb_rect, u64 ticks, + int frame_count) +{ + m_xfb_entry = std::move(xfb_entry); + m_xfb_rect = xfb_rect; + bool is_duplicate_frame = m_last_xfb_id == m_xfb_entry->id; + + if (!is_duplicate_frame || !g_ActiveConfig.bSkipPresentingDuplicateXFBs) + { + Present(); + + if (g_frame_dumper->IsFrameDumping()) + { + MathUtil::Rectangle target_rect; + if (!g_ActiveConfig.bInternalResolutionFrameDumps && !g_renderer->IsHeadless()) + { + target_rect = GetTargetRectangle(); + } + else + { + int width, height; + std::tie(width, height) = + CalculateOutputDimensions(m_xfb_rect.GetWidth(), m_xfb_rect.GetHeight()); + target_rect = MathUtil::Rectangle(0, 0, width, height); + } + + g_frame_dumper->DumpCurrentFrame(m_xfb_entry->texture.get(), m_xfb_rect, target_rect, ticks, + frame_count); + } + } + + return is_duplicate_frame; +} + +void Presenter::Present() +{ + m_last_xfb_id = m_xfb_entry->id; + + // Since we use the common pipelines here and draw vertices if a batch is currently being + // built by the vertex loader, we end up trampling over its pointer, as we share the buffer + // with the loader, and it has not been unmapped yet. Force a pipeline flush to avoid this. + g_vertex_manager->Flush(); + + // Render any UI elements to the draw list. + m_onscreen_ui->Finalize(); + + // Render the XFB to the screen. + g_renderer->BeginUtilityDrawing(); + if (!g_renderer->IsHeadless()) + { + g_renderer->BindBackbuffer({{0.0f, 0.0f, 0.0f, 1.0f}}); + + UpdateDrawRectangle(); + + // Adjust the source rectangle instead of using an oversized viewport to render the XFB. + auto render_target_rc = GetTargetRectangle(); + auto render_source_rc = m_xfb_rect; + AdjustRectanglesToFitBounds(&render_target_rc, &render_source_rc, m_backbuffer_width, + m_backbuffer_height); + RenderXFBToScreen(render_target_rc, m_xfb_entry->texture.get(), render_source_rc); + + m_onscreen_ui->DrawImGui(); + + // Present to the window system. + { + std::lock_guard guard(m_swap_mutex); + g_renderer->PresentBackbuffer(); + } + + // Update the window size based on the frame that was just rendered. + // Due to depending on guest state, we need to call this every frame. + SetWindowSize(m_xfb_rect.GetWidth(), m_xfb_rect.GetHeight()); + } + + m_onscreen_ui->BeginImGuiFrame(m_backbuffer_width, m_backbuffer_height); + + g_renderer->EndUtilityDrawing(); +} + +void Presenter::SetKeyMap(std::span> key_map) +{ + m_onscreen_ui->SetKeyMap(key_map); +} + +void Presenter::SetKey(u32 key, bool is_down, const char* chars) +{ + m_onscreen_ui->SetKey(key, is_down, chars); +} + +void Presenter::SetMousePos(float x, float y) +{ + m_onscreen_ui->SetMousePos(x, y); +} + +void Presenter::SetMousePress(u32 button_mask) +{ + m_onscreen_ui->SetMousePress(button_mask); +} + +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Present.h b/Source/Core/VideoCommon/Present.h new file mode 100644 index 0000000000..5f535f7208 --- /dev/null +++ b/Source/Core/VideoCommon/Present.h @@ -0,0 +1,132 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Common/Flag.h" +#include "Common/MathUtil.h" + +#include "VideoCommon/TextureCacheBase.h" +#include "VideoCommon/TextureConfig.h" + +#include +#include +#include +#include +#include + +class AbstractTexture; + +namespace VideoCommon +{ +class OnScreenUI; +class PostProcessing; + +class Presenter +{ +public: + using ClearColor = std::array; + + Presenter(); + virtual ~Presenter(); + + bool SubmitXFB(RcTcacheEntry xfb_entry, MathUtil::Rectangle& xfb_rect, u64 ticks, + int frame_count); + void Present(); + void ClearLastXfbId() { m_last_xfb_id = std::numeric_limits::max(); } + + bool Initialize(); + + void CheckForConfigChanges(u32 changed_bits); + + // Begins/presents a "UI frame". UI frames do not draw any of the console XFB, but this could + // change in the future. + void BeginUIFrame(); + void EndUIFrame(); + + // Display resolution + int GetBackbufferWidth() const { return m_backbuffer_width; } + int GetBackbufferHeight() const { return m_backbuffer_height; } + float GetBackbufferScale() const { return m_backbuffer_scale; } + AbstractTextureFormat GetBackbufferFormat() const { return m_backbuffer_format; } + void SetWindowSize(int width, int height); + void SetBackbuffer(int backbuffer_width, int backbuffer_height); + void SetBackbuffer(int backbuffer_width, int backbuffer_height, float backbuffer_scale, + AbstractTextureFormat backbuffer_format); + + void UpdateDrawRectangle(); + + float CalculateDrawAspectRatio() const; + + // Crops the target rectangle to the framebuffer dimensions, reducing the size of the source + // rectangle if it is greater. Works even if the source and target rectangles don't have a + // 1:1 pixel mapping, scaling as appropriate. + void AdjustRectanglesToFitBounds(MathUtil::Rectangle* target_rect, + MathUtil::Rectangle* source_rect, int fb_width, + int fb_height); + + void ReleaseXFBContentLock(); + + // Draws the specified XFB buffer to the screen, performing any post-processing. + // Assumes that the backbuffer has already been bound and cleared. + virtual void RenderXFBToScreen(const MathUtil::Rectangle& target_rc, + const AbstractTexture* source_texture, + const MathUtil::Rectangle& source_rc); + + VideoCommon::PostProcessing* GetPostProcessor() const { return m_post_processor.get(); } + // Final surface changing + // This is called when the surface is resized (WX) or the window changes (Android). + void ChangeSurface(void* new_surface_handle); + void ResizeSurface(); + bool SurfaceResizedTestAndClear() { return m_surface_resized.TestAndClear(); } + bool SurfaceChangedTestAndClear() { return m_surface_changed.TestAndClear(); } + void* GetNewSurfaceHandle(); + + void SetKeyMap(std::span> key_map); + + void SetKey(u32 key, bool is_down, const char* chars); + void SetMousePos(float x, float y); + void SetMousePress(u32 button_mask); + + const MathUtil::Rectangle& GetTargetRectangle() const { return m_target_rectangle; } + +private: + std::tuple CalculateOutputDimensions(int width, int height) const; + std::tuple ApplyStandardAspectCrop(float width, float height) const; + std::tuple ScaleToDisplayAspectRatio(int width, int height) const; + + // Use this to convert a single target rectangle to two stereo rectangles + std::tuple, MathUtil::Rectangle> + ConvertStereoRectangle(const MathUtil::Rectangle& rc) const; + + std::mutex m_swap_mutex; + + // Backbuffer (window) size and render area + int m_backbuffer_width = 0; + int m_backbuffer_height = 0; + float m_backbuffer_scale = 1.0f; + AbstractTextureFormat m_backbuffer_format = AbstractTextureFormat::Undefined; + + void* m_new_surface_handle = nullptr; + Common::Flag m_surface_changed; + Common::Flag m_surface_resized; + + MathUtil::Rectangle m_target_rectangle = {}; + + RcTcacheEntry m_xfb_entry; + MathUtil::Rectangle m_xfb_rect; + + // Tracking of XFB textures so we don't render duplicate frames. + u64 m_last_xfb_id = std::numeric_limits::max(); + + // These will be set on the first call to SetWindowSize. + int m_last_window_request_width = 0; + int m_last_window_request_height = 0; + + std::unique_ptr m_post_processor; + std::unique_ptr m_onscreen_ui; +}; + +} // namespace VideoCommon + +extern std::unique_ptr g_presenter; diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp index fcfde79bda..86725f534e 100644 --- a/Source/Core/VideoCommon/RenderBase.cpp +++ b/Source/Core/VideoCommon/RenderBase.cpp @@ -14,34 +14,20 @@ #include "VideoCommon/RenderBase.h" #include -#include #include #include -#include -#include #include #include -#include -#include #include "Common/Assert.h" #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" #include "Common/Config/Config.h" -#include "Common/FileUtil.h" -#include "Common/Flag.h" -#include "Common/Image.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" -#include "Common/Profiler.h" -#include "Common/StringUtil.h" -#include "Common/Thread.h" -#include "Common/Timer.h" #include "Core/Config/GraphicsSettings.h" -#include "Core/Config/MainSettings.h" -#include "Core/Config/NetplaySettings.h" #include "Core/Config/SYSCONFSettings.h" #include "Core/ConfigManager.h" #include "Core/Core.h" @@ -49,72 +35,41 @@ #include "Core/FifoPlayer/FifoRecorder.h" #include "Core/FreeLookConfig.h" #include "Core/HW/SystemTimers.h" -#include "Core/HW/VideoInterface.h" -#include "Core/Host.h" -#include "Core/Movie.h" #include "Core/System.h" -#include "InputCommon/ControllerInterface/ControllerInterface.h" - #include "VideoCommon/AbstractFramebuffer.h" -#include "VideoCommon/AbstractStagingTexture.h" #include "VideoCommon/AbstractTexture.h" -#include "VideoCommon/BPFunctions.h" -#include "VideoCommon/BPMemory.h" #include "VideoCommon/BoundingBox.h" -#include "VideoCommon/CPMemory.h" #include "VideoCommon/CommandProcessor.h" -#include "VideoCommon/FrameDump.h" +#include "VideoCommon/FrameDumper.h" #include "VideoCommon/FramebufferManager.h" -#include "VideoCommon/FramebufferShaderGen.h" #include "VideoCommon/FreeLookCamera.h" #include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h" -#include "VideoCommon/NetPlayChatUI.h" -#include "VideoCommon/NetPlayGolfUI.h" #include "VideoCommon/OnScreenDisplay.h" -#include "VideoCommon/OpcodeDecoding.h" +#include "VideoCommon/PerformanceMetrics.h" #include "VideoCommon/PixelEngine.h" #include "VideoCommon/PixelShaderManager.h" -#include "VideoCommon/PostProcessing.h" +#include "VideoCommon/Present.h" #include "VideoCommon/ShaderCache.h" #include "VideoCommon/ShaderGenCommon.h" #include "VideoCommon/Statistics.h" -#include "VideoCommon/TextureCacheBase.h" -#include "VideoCommon/TextureDecoder.h" -#include "VideoCommon/VertexLoaderManager.h" #include "VideoCommon/VertexManagerBase.h" -#include "VideoCommon/VertexShaderManager.h" #include "VideoCommon/VideoBackendBase.h" -#include "VideoCommon/VideoCommon.h" #include "VideoCommon/VideoConfig.h" -#include "VideoCommon/XFMemory.h" std::unique_ptr g_renderer; -static float AspectToWidescreen(float aspect) -{ - return aspect * ((16.0f / 9.0f) / (4.0f / 3.0f)); -} - -static bool DumpFrameToPNG(const FrameDump::FrameData& frame, const std::string& file_name) -{ - return Common::ConvertRGBAToRGBAndSavePNG(file_name, frame.data, frame.width, frame.height, - frame.stride, - Config::Get(Config::GFX_PNG_COMPRESSION_LEVEL)); -} - Renderer::Renderer(int backbuffer_width, int backbuffer_height, float backbuffer_scale, AbstractTextureFormat backbuffer_format) - : m_backbuffer_width(backbuffer_width), m_backbuffer_height(backbuffer_height), - m_backbuffer_scale(backbuffer_scale), - m_backbuffer_format(backbuffer_format), m_last_xfb_width{MAX_XFB_WIDTH}, m_last_xfb_height{ - MAX_XFB_HEIGHT} + : m_last_xfb_width{MAX_XFB_WIDTH}, m_last_xfb_height{MAX_XFB_HEIGHT} { UpdateActiveConfig(); FreeLook::UpdateActiveConfig(); - UpdateDrawRectangle(); CalculateTargetSize(); + g_presenter->SetBackbuffer(backbuffer_width, backbuffer_height, backbuffer_scale, + backbuffer_format); + m_is_game_widescreen = SConfig::GetInstance().bWii && Config::Get(Config::SYSCONF_WIDESCREEN); g_freelook_camera.SetControlType(FreeLook::GetActiveConfig().camera_config.control_type); } @@ -123,13 +78,6 @@ Renderer::~Renderer() = default; bool Renderer::Initialize() { - if (!InitializeImGui()) - return false; - - m_post_processor = std::make_unique(); - if (!m_post_processor->Initialize(m_backbuffer_format)) - return false; - m_bounding_box = CreateBoundingBox(); if (g_ActiveConfig.backend_info.bSupportsBBox && !m_bounding_box->Initialize()) { @@ -153,19 +101,11 @@ bool Renderer::Initialize() m_graphics_mod_manager.Load(*g_ActiveConfig.graphics_mod_config); } - return true; + return g_presenter->Initialize(); } void Renderer::Shutdown() { - // Disable ControllerInterface's aspect ratio adjustments so mapping dialog behaves normally. - g_controller_interface.SetAspectRatioAdjustment(1); - - // First stop any framedumping, which might need to dump the last xfb frame. This process - // can require additional graphics sub-systems so it needs to be done first - ShutdownFrameDumping(); - ShutdownImGui(); - m_post_processor.reset(); m_bounding_box.reset(); } @@ -399,9 +339,10 @@ bool Renderer::CalculateTargetSize() { if (g_ActiveConfig.iEFBScale == EFB_SCALE_AUTO_INTEGRAL) { + auto target_rectangle = g_presenter->GetTargetRectangle(); // Set a scale based on the window size - int width = EFB_WIDTH * m_target_rectangle.GetWidth() / m_last_xfb_width; - int height = EFB_HEIGHT * m_target_rectangle.GetHeight() / m_last_xfb_height; + int width = EFB_WIDTH * target_rectangle.GetWidth() / m_last_xfb_width; + int height = EFB_HEIGHT * target_rectangle.GetHeight() / m_last_xfb_height; m_efb_scale = std::max((width - 1) / EFB_WIDTH + 1, (height - 1) / EFB_HEIGHT + 1); } else @@ -429,53 +370,6 @@ bool Renderer::CalculateTargetSize() return false; } -std::tuple, MathUtil::Rectangle> -Renderer::ConvertStereoRectangle(const MathUtil::Rectangle& rc) const -{ - // Resize target to half its original size - auto draw_rc = rc; - if (g_ActiveConfig.stereo_mode == StereoMode::TAB) - { - // The height may be negative due to flipped rectangles - int height = rc.bottom - rc.top; - draw_rc.top += height / 4; - draw_rc.bottom -= height / 4; - } - else - { - int width = rc.right - rc.left; - draw_rc.left += width / 4; - draw_rc.right -= width / 4; - } - - // Create two target rectangle offset to the sides of the backbuffer - auto left_rc = draw_rc; - auto right_rc = draw_rc; - if (g_ActiveConfig.stereo_mode == StereoMode::TAB) - { - left_rc.top -= m_backbuffer_height / 4; - left_rc.bottom -= m_backbuffer_height / 4; - right_rc.top += m_backbuffer_height / 4; - right_rc.bottom += m_backbuffer_height / 4; - } - else - { - left_rc.left -= m_backbuffer_width / 4; - left_rc.right -= m_backbuffer_width / 4; - right_rc.left += m_backbuffer_width / 4; - right_rc.right += m_backbuffer_width / 4; - } - - return std::make_tuple(left_rc, right_rc); -} - -void Renderer::SaveScreenshot(std::string filename) -{ - std::lock_guard lk(m_screenshot_lock); - m_screenshot_name = std::move(filename); - m_screenshot_request.Set(); -} - void Renderer::CheckForConfigChanges() { const ShaderHostConfig old_shader_host_config = ShaderHostConfig::GetCurrent(); @@ -515,16 +409,6 @@ void Renderer::CheckForConfigChanges() if (old_efb_access_tile_size != g_ActiveConfig.iEFBAccessTileSize) g_framebuffer_manager->SetEFBCacheTileSize(std::max(g_ActiveConfig.iEFBAccessTileSize, 0)); - // Check for post-processing shader changes. Done up here as it doesn't affect anything outside - // the post-processor. Note that options are applied every frame, so no need to check those. - if (m_post_processor->GetConfig()->GetShader() != g_ActiveConfig.sPostProcessingShader) - { - // The existing shader must not be in use when it's destroyed - WaitForGPUIdle(); - - m_post_processor->RecompileShader(); - } - // Determine which (if any) settings have changed. ShaderHostConfig new_host_config = ShaderHostConfig::GetCurrent(); u32 changed_bits = 0; @@ -545,6 +429,8 @@ void Renderer::CheckForConfigChanges() if (CalculateTargetSize()) changed_bits |= CONFIG_CHANGE_BIT_TARGET_SIZE; + g_presenter->CheckForConfigChanges(changed_bits); + // No changes? if (changed_bits == 0) return; @@ -581,129 +467,6 @@ void Renderer::CheckForConfigChanges() { BPFunctions::SetScissorAndViewport(); } - - // Stereo mode change requires recompiling our post processing pipeline and imgui pipelines for - // rendering the UI. - if (changed_bits & CONFIG_CHANGE_BIT_STEREO_MODE) - { - RecompileImGuiPipeline(); - m_post_processor->RecompilePipeline(); - } -} - -// Create On-Screen-Messages -void Renderer::DrawDebugText() -{ - const bool show_movie_window = - Config::Get(Config::MAIN_SHOW_FRAME_COUNT) || Config::Get(Config::MAIN_SHOW_LAG) || - Config::Get(Config::MAIN_MOVIE_SHOW_INPUT_DISPLAY) || - Config::Get(Config::MAIN_MOVIE_SHOW_RTC) || Config::Get(Config::MAIN_MOVIE_SHOW_RERECORD); - if (show_movie_window) - { - // Position under the FPS display. - ImGui::SetNextWindowPos( - ImVec2(ImGui::GetIO().DisplaySize.x - 10.f * m_backbuffer_scale, 80.f * m_backbuffer_scale), - ImGuiCond_FirstUseEver, ImVec2(1.0f, 0.0f)); - ImGui::SetNextWindowSizeConstraints( - ImVec2(150.0f * m_backbuffer_scale, 20.0f * m_backbuffer_scale), - ImGui::GetIO().DisplaySize); - if (ImGui::Begin("Movie", nullptr, ImGuiWindowFlags_NoFocusOnAppearing)) - { - if (Movie::IsPlayingInput()) - { - ImGui::Text("Frame: %" PRIu64 " / %" PRIu64, Movie::GetCurrentFrame(), - Movie::GetTotalFrames()); - ImGui::Text("Input: %" PRIu64 " / %" PRIu64, Movie::GetCurrentInputCount(), - Movie::GetTotalInputCount()); - } - else if (Config::Get(Config::MAIN_SHOW_FRAME_COUNT)) - { - ImGui::Text("Frame: %" PRIu64, Movie::GetCurrentFrame()); - ImGui::Text("Input: %" PRIu64, Movie::GetCurrentInputCount()); - } - if (Config::Get(Config::MAIN_SHOW_LAG)) - ImGui::Text("Lag: %" PRIu64 "\n", Movie::GetCurrentLagCount()); - if (Config::Get(Config::MAIN_MOVIE_SHOW_INPUT_DISPLAY)) - ImGui::TextUnformatted(Movie::GetInputDisplay().c_str()); - if (Config::Get(Config::MAIN_MOVIE_SHOW_RTC)) - ImGui::TextUnformatted(Movie::GetRTCDisplay().c_str()); - if (Config::Get(Config::MAIN_MOVIE_SHOW_RERECORD)) - ImGui::TextUnformatted(Movie::GetRerecords().c_str()); - } - ImGui::End(); - } - - if (g_ActiveConfig.bOverlayStats) - g_stats.Display(); - - if (g_ActiveConfig.bShowNetPlayMessages && g_netplay_chat_ui) - g_netplay_chat_ui->Display(); - - if (Config::Get(Config::NETPLAY_GOLF_MODE_OVERLAY) && g_netplay_golf_ui) - g_netplay_golf_ui->Display(); - - if (g_ActiveConfig.bOverlayProjStats) - g_stats.DisplayProj(); - - if (g_ActiveConfig.bOverlayScissorStats) - g_stats.DisplayScissor(); - - const std::string profile_output = Common::Profiler::ToString(); - if (!profile_output.empty()) - ImGui::TextUnformatted(profile_output.c_str()); -} - -float Renderer::CalculateDrawAspectRatio() const -{ - const auto aspect_mode = g_ActiveConfig.aspect_mode; - - // If stretch is enabled, we prefer the aspect ratio of the window. - if (aspect_mode == AspectMode::Stretch) - return (static_cast(m_backbuffer_width) / static_cast(m_backbuffer_height)); - - const float aspect_ratio = VideoInterface::GetAspectRatio(); - - if (aspect_mode == AspectMode::AnalogWide || - (aspect_mode == AspectMode::Auto && m_is_game_widescreen)) - { - return AspectToWidescreen(aspect_ratio); - } - - return aspect_ratio; -} - -void Renderer::AdjustRectanglesToFitBounds(MathUtil::Rectangle* target_rect, - MathUtil::Rectangle* source_rect, int fb_width, - int fb_height) -{ - const int orig_target_width = target_rect->GetWidth(); - const int orig_target_height = target_rect->GetHeight(); - const int orig_source_width = source_rect->GetWidth(); - const int orig_source_height = source_rect->GetHeight(); - if (target_rect->left < 0) - { - const int offset = -target_rect->left; - target_rect->left = 0; - source_rect->left += offset * orig_source_width / orig_target_width; - } - if (target_rect->right > fb_width) - { - const int offset = target_rect->right - fb_width; - target_rect->right -= offset; - source_rect->right -= offset * orig_source_width / orig_target_width; - } - if (target_rect->top < 0) - { - const int offset = -target_rect->top; - target_rect->top = 0; - source_rect->top += offset * orig_source_height / orig_target_height; - } - if (target_rect->bottom > fb_height) - { - const int offset = target_rect->bottom - fb_height; - target_rect->bottom -= offset; - source_rect->bottom -= offset * orig_source_height / orig_target_height; - } } bool Renderer::IsHeadless() const @@ -711,19 +474,6 @@ bool Renderer::IsHeadless() const return true; } -void Renderer::ChangeSurface(void* new_surface_handle) -{ - std::lock_guard lock(m_swap_mutex); - m_new_surface_handle = new_surface_handle; - m_surface_changed.Set(); -} - -void Renderer::ResizeSurface() -{ - std::lock_guard lock(m_swap_mutex); - m_surface_resized.Set(); -} - void Renderer::SetViewportAndScissor(const MathUtil::Rectangle& rect, float min_depth, float max_depth) { @@ -804,160 +554,6 @@ MathUtil::Rectangle Renderer::ConvertEFBRectangle(const MathUtil::Rectangle return result; } -std::tuple Renderer::ScaleToDisplayAspectRatio(const int width, - const int height) const -{ - // Scale either the width or height depending the content aspect ratio. - // This way we preserve as much resolution as possible when scaling. - float scaled_width = static_cast(width); - float scaled_height = static_cast(height); - const float draw_aspect = CalculateDrawAspectRatio(); - if (scaled_width / scaled_height >= draw_aspect) - scaled_height = scaled_width / draw_aspect; - else - scaled_width = scaled_height * draw_aspect; - return std::make_tuple(scaled_width, scaled_height); -} - -void Renderer::UpdateDrawRectangle() -{ - const float draw_aspect_ratio = CalculateDrawAspectRatio(); - - // Update aspect ratio hack values - // Won't take effect until next frame - // Don't know if there is a better place for this code so there isn't a 1 frame delay - if (g_ActiveConfig.bWidescreenHack) - { - float source_aspect = VideoInterface::GetAspectRatio(); - if (m_is_game_widescreen) - source_aspect = AspectToWidescreen(source_aspect); - - const float adjust = source_aspect / draw_aspect_ratio; - if (adjust > 1) - { - // Vert+ - g_Config.fAspectRatioHackW = 1; - g_Config.fAspectRatioHackH = 1 / adjust; - } - else - { - // Hor+ - g_Config.fAspectRatioHackW = adjust; - g_Config.fAspectRatioHackH = 1; - } - } - else - { - // Hack is disabled. - g_Config.fAspectRatioHackW = 1; - g_Config.fAspectRatioHackH = 1; - } - - // The rendering window size - const float win_width = static_cast(m_backbuffer_width); - const float win_height = static_cast(m_backbuffer_height); - - // FIXME: this breaks at very low widget sizes - // Make ControllerInterface aware of the render window region actually being used - // to adjust mouse cursor inputs. - g_controller_interface.SetAspectRatioAdjustment(draw_aspect_ratio / (win_width / win_height)); - - float draw_width = draw_aspect_ratio; - float draw_height = 1; - - // Crop the picture to a standard aspect ratio. (if enabled) - auto [crop_width, crop_height] = ApplyStandardAspectCrop(draw_width, draw_height); - - // scale the picture to fit the rendering window - if (win_width / win_height >= crop_width / crop_height) - { - // the window is flatter than the picture - draw_width *= win_height / crop_height; - crop_width *= win_height / crop_height; - draw_height *= win_height / crop_height; - crop_height = win_height; - } - else - { - // the window is skinnier than the picture - draw_width *= win_width / crop_width; - draw_height *= win_width / crop_width; - crop_height *= win_width / crop_width; - crop_width = win_width; - } - - // ensure divisibility by 4 to make it compatible with all the video encoders - draw_width = std::ceil(draw_width) - static_cast(std::ceil(draw_width)) % 4; - draw_height = std::ceil(draw_height) - static_cast(std::ceil(draw_height)) % 4; - - m_target_rectangle.left = static_cast(std::round(win_width / 2.0 - draw_width / 2.0)); - m_target_rectangle.top = static_cast(std::round(win_height / 2.0 - draw_height / 2.0)); - m_target_rectangle.right = m_target_rectangle.left + static_cast(draw_width); - m_target_rectangle.bottom = m_target_rectangle.top + static_cast(draw_height); -} - -void Renderer::SetWindowSize(int width, int height) -{ - const auto [out_width, out_height] = CalculateOutputDimensions(width, height); - - // Track the last values of width/height to avoid sending a window resize event every frame. - if (out_width == m_last_window_request_width && out_height == m_last_window_request_height) - return; - - m_last_window_request_width = out_width; - m_last_window_request_height = out_height; - Host_RequestRenderWindowSize(out_width, out_height); -} - -// Crop to exactly 16:9 or 4:3 if enabled and not AspectMode::Stretch. -std::tuple Renderer::ApplyStandardAspectCrop(float width, float height) const -{ - const auto aspect_mode = g_ActiveConfig.aspect_mode; - - if (!g_ActiveConfig.bCrop || aspect_mode == AspectMode::Stretch) - return {width, height}; - - // Force 4:3 or 16:9 by cropping the image. - const float current_aspect = width / height; - const float expected_aspect = (aspect_mode == AspectMode::AnalogWide || - (aspect_mode == AspectMode::Auto && m_is_game_widescreen)) ? - (16.0f / 9.0f) : - (4.0f / 3.0f); - if (current_aspect > expected_aspect) - { - // keep height, crop width - width = height * expected_aspect; - } - else - { - // keep width, crop height - height = width / expected_aspect; - } - - return {width, height}; -} - -std::tuple Renderer::CalculateOutputDimensions(int width, int height) const -{ - width = std::max(width, 1); - height = std::max(height, 1); - - auto [scaled_width, scaled_height] = ScaleToDisplayAspectRatio(width, height); - - // Apply crop if enabled. - std::tie(scaled_width, scaled_height) = ApplyStandardAspectCrop(scaled_width, scaled_height); - - width = static_cast(std::ceil(scaled_width)); - height = static_cast(std::ceil(scaled_height)); - - // UpdateDrawRectangle() makes sure that the rendered image is divisible by four for video - // encoders, so do that here too to match it - width -= width % 4; - height -= height % 4; - - return std::make_tuple(width, height); -} - void Renderer::CheckFifoRecording() { const bool was_recording = OpcodeDecoder::g_record_fifo_data; @@ -994,278 +590,14 @@ void Renderer::RecordVideoMemory() texMem); } -bool Renderer::InitializeImGui() -{ - std::unique_lock imgui_lock(m_imgui_mutex); - - if (!IMGUI_CHECKVERSION()) - { - PanicAlertFmt("ImGui version check failed"); - return false; - } - if (!ImGui::CreateContext()) - { - PanicAlertFmt("Creating ImGui context failed"); - return false; - } - if (!ImPlot::CreateContext()) - { - PanicAlertFmt("Creating ImPlot context failed"); - return false; - } - - // Don't create an ini file. TODO: Do we want this in the future? - ImGui::GetIO().IniFilename = nullptr; - ImGui::GetIO().DisplayFramebufferScale.x = m_backbuffer_scale; - ImGui::GetIO().DisplayFramebufferScale.y = m_backbuffer_scale; - ImGui::GetIO().FontGlobalScale = m_backbuffer_scale; - ImGui::GetStyle().ScaleAllSizes(m_backbuffer_scale); - ImGui::GetStyle().WindowRounding = 7.0f; - - PortableVertexDeclaration vdecl = {}; - vdecl.position = {ComponentFormat::Float, 2, offsetof(ImDrawVert, pos), true, false}; - vdecl.texcoords[0] = {ComponentFormat::Float, 2, offsetof(ImDrawVert, uv), true, false}; - vdecl.colors[0] = {ComponentFormat::UByte, 4, offsetof(ImDrawVert, col), true, false}; - vdecl.stride = sizeof(ImDrawVert); - m_imgui_vertex_format = CreateNativeVertexFormat(vdecl); - if (!m_imgui_vertex_format) - { - PanicAlertFmt("Failed to create ImGui vertex format"); - return false; - } - - // Font texture(s). - { - ImGuiIO& io = ImGui::GetIO(); - u8* font_tex_pixels; - int font_tex_width, font_tex_height; - io.Fonts->GetTexDataAsRGBA32(&font_tex_pixels, &font_tex_width, &font_tex_height); - - TextureConfig font_tex_config(font_tex_width, font_tex_height, 1, 1, 1, - AbstractTextureFormat::RGBA8, 0); - std::unique_ptr font_tex = - CreateTexture(font_tex_config, "ImGui font texture"); - if (!font_tex) - { - PanicAlertFmt("Failed to create ImGui texture"); - return false; - } - font_tex->Load(0, font_tex_width, font_tex_height, font_tex_width, font_tex_pixels, - sizeof(u32) * font_tex_width * font_tex_height); - - io.Fonts->TexID = font_tex.get(); - - m_imgui_textures.push_back(std::move(font_tex)); - } - - if (!RecompileImGuiPipeline()) - return false; - - m_imgui_last_frame_time = Common::Timer::NowUs(); - BeginImGuiFrameUnlocked(); // lock is already held - return true; -} - -bool Renderer::RecompileImGuiPipeline() -{ - if (m_backbuffer_format == AbstractTextureFormat::Undefined) - { - // No backbuffer (nogui) means no imgui rendering will happen - // Some backends don't like making pipelines with no render targets - return true; - } - - std::unique_ptr vertex_shader = - CreateShaderFromSource(ShaderStage::Vertex, FramebufferShaderGen::GenerateImGuiVertexShader(), - "ImGui vertex shader"); - std::unique_ptr pixel_shader = CreateShaderFromSource( - ShaderStage::Pixel, FramebufferShaderGen::GenerateImGuiPixelShader(), "ImGui pixel shader"); - if (!vertex_shader || !pixel_shader) - { - PanicAlertFmt("Failed to compile ImGui shaders"); - return false; - } - - // GS is used to render the UI to both eyes in stereo modes. - std::unique_ptr geometry_shader; - if (UseGeometryShaderForUI()) - { - geometry_shader = CreateShaderFromSource( - ShaderStage::Geometry, FramebufferShaderGen::GeneratePassthroughGeometryShader(1, 1), - "ImGui passthrough geometry shader"); - if (!geometry_shader) - { - PanicAlertFmt("Failed to compile ImGui geometry shader"); - return false; - } - } - - AbstractPipelineConfig pconfig = {}; - pconfig.vertex_format = m_imgui_vertex_format.get(); - pconfig.vertex_shader = vertex_shader.get(); - pconfig.geometry_shader = geometry_shader.get(); - pconfig.pixel_shader = pixel_shader.get(); - pconfig.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles); - pconfig.depth_state = RenderState::GetNoDepthTestingDepthState(); - pconfig.blending_state = RenderState::GetNoBlendingBlendState(); - pconfig.blending_state.blendenable = true; - pconfig.blending_state.srcfactor = SrcBlendFactor::SrcAlpha; - pconfig.blending_state.dstfactor = DstBlendFactor::InvSrcAlpha; - pconfig.blending_state.srcfactoralpha = SrcBlendFactor::Zero; - pconfig.blending_state.dstfactoralpha = DstBlendFactor::One; - pconfig.framebuffer_state.color_texture_format = m_backbuffer_format; - pconfig.framebuffer_state.depth_texture_format = AbstractTextureFormat::Undefined; - pconfig.framebuffer_state.samples = 1; - pconfig.framebuffer_state.per_sample_shading = false; - pconfig.usage = AbstractPipelineUsage::Utility; - m_imgui_pipeline = CreatePipeline(pconfig); - if (!m_imgui_pipeline) - { - PanicAlertFmt("Failed to create imgui pipeline"); - return false; - } - - return true; -} - -void Renderer::ShutdownImGui() -{ - std::unique_lock imgui_lock(m_imgui_mutex); - - ImGui::EndFrame(); - ImPlot::DestroyContext(); - ImGui::DestroyContext(); - m_imgui_pipeline.reset(); - m_imgui_vertex_format.reset(); - m_imgui_textures.clear(); -} - -void Renderer::BeginImGuiFrame() -{ - std::unique_lock imgui_lock(m_imgui_mutex); - BeginImGuiFrameUnlocked(); -} - -void Renderer::BeginImGuiFrameUnlocked() -{ - const u64 current_time_us = Common::Timer::NowUs(); - const u64 time_diff_us = current_time_us - m_imgui_last_frame_time; - const float time_diff_secs = static_cast(time_diff_us / 1000000.0); - m_imgui_last_frame_time = current_time_us; - - // Update I/O with window dimensions. - ImGuiIO& io = ImGui::GetIO(); - io.DisplaySize = - ImVec2(static_cast(m_backbuffer_width), static_cast(m_backbuffer_height)); - io.DeltaTime = time_diff_secs; - - ImGui::NewFrame(); -} - -void Renderer::DrawImGui() -{ - ImDrawData* draw_data = ImGui::GetDrawData(); - if (!draw_data) - return; - - SetViewport(0.0f, 0.0f, static_cast(m_backbuffer_width), - static_cast(m_backbuffer_height), 0.0f, 1.0f); - - // Uniform buffer for draws. - struct ImGuiUbo - { - float u_rcp_viewport_size_mul2[2]; - float padding[2]; - }; - ImGuiUbo ubo = {{1.0f / m_backbuffer_width * 2.0f, 1.0f / m_backbuffer_height * 2.0f}}; - - // Set up common state for drawing. - SetPipeline(m_imgui_pipeline.get()); - SetSamplerState(0, RenderState::GetPointSamplerState()); - g_vertex_manager->UploadUtilityUniforms(&ubo, sizeof(ubo)); - - for (int i = 0; i < draw_data->CmdListsCount; i++) - { - const ImDrawList* cmdlist = draw_data->CmdLists[i]; - if (cmdlist->VtxBuffer.empty() || cmdlist->IdxBuffer.empty()) - return; - - u32 base_vertex, base_index; - g_vertex_manager->UploadUtilityVertices(cmdlist->VtxBuffer.Data, sizeof(ImDrawVert), - cmdlist->VtxBuffer.Size, cmdlist->IdxBuffer.Data, - cmdlist->IdxBuffer.Size, &base_vertex, &base_index); - - for (const ImDrawCmd& cmd : cmdlist->CmdBuffer) - { - if (cmd.UserCallback) - { - cmd.UserCallback(cmdlist, &cmd); - continue; - } - - SetScissorRect(ConvertFramebufferRectangle( - MathUtil::Rectangle( - static_cast(cmd.ClipRect.x), static_cast(cmd.ClipRect.y), - static_cast(cmd.ClipRect.z), static_cast(cmd.ClipRect.w)), - m_current_framebuffer)); - SetTexture(0, reinterpret_cast(cmd.TextureId)); - DrawIndexed(base_index, cmd.ElemCount, base_vertex); - base_index += cmd.ElemCount; - } - } - - // Some capture software (such as OBS) hooks SwapBuffers and uses glBlitFramebuffer to copy our - // back buffer just before swap. Because glBlitFramebuffer honors the scissor test, the capture - // itself will be clipped to whatever bounds were last set by ImGui, resulting in a rather useless - // capture whenever any ImGui windows are open. We'll reset the scissor rectangle to the entire - // viewport here to avoid this problem. - SetScissorRect(ConvertFramebufferRectangle( - MathUtil::Rectangle(0, 0, m_backbuffer_width, m_backbuffer_height), - m_current_framebuffer)); -} - bool Renderer::UseGeometryShaderForUI() const { // OpenGL doesn't render to a 2-layer backbuffer like D3D/Vulkan for quad-buffered stereo, - // instead drawing twice and the eye selected by glDrawBuffer() (see - // OGL::Renderer::RenderXFBToScreen). + // instead drawing twice and the eye selected by glDrawBuffer() (see Presenter::RenderXFBToScreen) return g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer && g_ActiveConfig.backend_info.api_type != APIType::OpenGL; } -std::unique_lock Renderer::GetImGuiLock() -{ - return std::unique_lock(m_imgui_mutex); -} - -void Renderer::BeginUIFrame() -{ - if (IsHeadless()) - return; - - BeginUtilityDrawing(); - BindBackbuffer({0.0f, 0.0f, 0.0f, 1.0f}); -} - -void Renderer::EndUIFrame() -{ - { - auto lock = GetImGuiLock(); - ImGui::Render(); - } - - if (!IsHeadless()) - { - DrawImGui(); - - std::lock_guard guard(m_swap_mutex); - PresentBackbuffer(); - EndUtilityDrawing(); - } - - BeginImGuiFrame(); -} - void Renderer::ForceReloadTextures() { m_force_reload_textures.Set(); @@ -1344,11 +676,12 @@ void Renderer::Swap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u6 else if (aspect_mode == AspectMode::AnalogWide) m_is_game_widescreen = true; } + UpdateWidescreenHeuristic(); // Ensure the last frame was written to the dump. // This is required even if frame dumping has stopped, since the frame dump is one frame // behind the renderer. - FlushFrameDump(); + g_frame_dumper->FlushFrameDump(); if (g_ActiveConfig.bGraphicMods) { @@ -1360,61 +693,18 @@ void Renderer::Swap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u6 if (xfb_addr && fb_width && fb_stride && fb_height) { // Get the current XFB from texture cache + + g_presenter->ReleaseXFBContentLock(); + MathUtil::Rectangle xfb_rect; - const auto xfb_entry = + RcTcacheEntry xfb_entry = g_texture_cache->GetXFBTexture(xfb_addr, fb_width, fb_height, fb_stride, &xfb_rect); - const bool is_duplicate_frame = xfb_entry->id == m_last_xfb_id; - if (xfb_entry && (!g_ActiveConfig.bSkipPresentingDuplicateXFBs || !is_duplicate_frame)) + bool is_duplicate_frame = + g_presenter->SubmitXFB(std::move(xfb_entry), xfb_rect, ticks, m_frame_count); + + if (!g_ActiveConfig.bSkipPresentingDuplicateXFBs || !is_duplicate_frame) { - m_last_xfb_id = xfb_entry->id; - - // Since we use the common pipelines here and draw vertices if a batch is currently being - // built by the vertex loader, we end up trampling over its pointer, as we share the buffer - // with the loader, and it has not been unmapped yet. Force a pipeline flush to avoid this. - g_vertex_manager->Flush(); - - // Render any UI elements to the draw list. - { - auto lock = GetImGuiLock(); - - g_perf_metrics.DrawImGuiStats(m_backbuffer_scale); - DrawDebugText(); - OSD::DrawMessages(); - ImGui::Render(); - } - - // Render the XFB to the screen. - BeginUtilityDrawing(); - if (!IsHeadless()) - { - BindBackbuffer({{0.0f, 0.0f, 0.0f, 1.0f}}); - - if (!is_duplicate_frame) - UpdateWidescreenHeuristic(); - - UpdateDrawRectangle(); - - // Adjust the source rectangle instead of using an oversized viewport to render the XFB. - auto render_target_rc = GetTargetRectangle(); - auto render_source_rc = xfb_rect; - AdjustRectanglesToFitBounds(&render_target_rc, &render_source_rc, m_backbuffer_width, - m_backbuffer_height); - RenderXFBToScreen(render_target_rc, xfb_entry->texture.get(), render_source_rc); - - DrawImGui(); - - // Present to the window system. - { - std::lock_guard guard(m_swap_mutex); - PresentBackbuffer(); - } - - // Update the window size based on the frame that was just rendered. - // Due to depending on guest state, we need to call this every frame. - SetWindowSize(xfb_rect.GetWidth(), xfb_rect.GetHeight()); - } - if (!is_duplicate_frame) { DolphinAnalytics::PerformanceSample perf_sample; @@ -1423,9 +713,6 @@ void Renderer::Swap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u6 perf_sample.num_draw_calls = g_stats.this_frame.num_draw_calls; DolphinAnalytics::Instance().ReportPerformanceInfo(std::move(perf_sample)); - if (IsFrameDumping()) - DumpCurrentFrame(xfb_entry->texture.get(), xfb_rect, ticks, m_frame_count); - // Begin new frame m_frame_count++; g_stats.ResetFrame(); @@ -1433,7 +720,6 @@ void Renderer::Swap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u6 g_shader_cache->RetrieveAsyncShaders(); g_vertex_manager->OnEndFrame(); - BeginImGuiFrame(); // We invalidate the pipeline object at the start of the frame. // This is for the rare case where only a single pipeline configuration is used, @@ -1467,8 +753,6 @@ void Renderer::Swap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u6 // Handle any config changes, this gets propagated to the backend. CheckForConfigChanges(); g_Config.iSaveTargetId = 0; - - EndUtilityDrawing(); } else { @@ -1488,359 +772,6 @@ void Renderer::Swap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u6 } } -void Renderer::RenderXFBToScreen(const MathUtil::Rectangle& target_rc, - const AbstractTexture* source_texture, - const MathUtil::Rectangle& source_rc) -{ - if (!g_ActiveConfig.backend_info.bSupportsPostProcessing) - { - ShowImage(source_texture, source_rc); - return - } - - if (g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer && - g_ActiveConfig.backend_info.bUsesExplictQuadBuffering) - { - // Quad-buffered stereo is annoying on GL. - SelectLeftBuffer(); - m_post_processor->BlitFromTexture(target_rc, source_rc, source_texture, 0); - - SelectRightBuffer(); - m_post_processor->BlitFromTexture(target_rc, source_rc, source_texture, 1); - - SelectMainBuffer(); - } - else if (g_ActiveConfig.stereo_mode == StereoMode::SBS || - g_ActiveConfig.stereo_mode == StereoMode::TAB) - { - const auto [left_rc, right_rc] = ConvertStereoRectangle(target_rc); - - m_post_processor->BlitFromTexture(left_rc, source_rc, source_texture, 0); - m_post_processor->BlitFromTexture(right_rc, source_rc, source_texture, 1); - } - else - { - m_post_processor->BlitFromTexture(target_rc, source_rc, source_texture, 0); - } -} - -bool Renderer::IsFrameDumping() const -{ - if (m_screenshot_request.IsSet()) - return true; - - if (Config::Get(Config::MAIN_MOVIE_DUMP_FRAMES)) - return true; - - return false; -} - -void Renderer::DumpCurrentFrame(const AbstractTexture* src_texture, - const MathUtil::Rectangle& src_rect, u64 ticks, - int frame_number) -{ - int source_width = src_rect.GetWidth(); - int source_height = src_rect.GetHeight(); - int target_width, target_height; - if (!g_ActiveConfig.bInternalResolutionFrameDumps && !IsHeadless()) - { - auto target_rect = GetTargetRectangle(); - target_width = target_rect.GetWidth(); - target_height = target_rect.GetHeight(); - } - else - { - std::tie(target_width, target_height) = CalculateOutputDimensions(source_width, source_height); - } - - // We only need to render a copy if we need to stretch/scale the XFB copy. - MathUtil::Rectangle copy_rect = src_rect; - if (source_width != target_width || source_height != target_height) - { - if (!CheckFrameDumpRenderTexture(target_width, target_height)) - return; - - ScaleTexture(m_frame_dump_render_framebuffer.get(), m_frame_dump_render_framebuffer->GetRect(), - src_texture, src_rect); - src_texture = m_frame_dump_render_texture.get(); - copy_rect = src_texture->GetRect(); - } - - if (!CheckFrameDumpReadbackTexture(target_width, target_height)) - return; - - m_frame_dump_readback_texture->CopyFromTexture(src_texture, copy_rect, 0, 0, - m_frame_dump_readback_texture->GetRect()); - m_last_frame_state = m_frame_dump.FetchState(ticks, frame_number); - m_frame_dump_needs_flush = true; -} - -bool Renderer::CheckFrameDumpRenderTexture(u32 target_width, u32 target_height) -{ - // Ensure framebuffer exists (we lazily allocate it in case frame dumping isn't used). - // Or, resize texture if it isn't large enough to accommodate the current frame. - if (m_frame_dump_render_texture && m_frame_dump_render_texture->GetWidth() == target_width && - m_frame_dump_render_texture->GetHeight() == target_height) - { - return true; - } - - // Recreate texture, but release before creating so we don't temporarily use twice the RAM. - m_frame_dump_render_framebuffer.reset(); - m_frame_dump_render_texture.reset(); - m_frame_dump_render_texture = - CreateTexture(TextureConfig(target_width, target_height, 1, 1, 1, - AbstractTextureFormat::RGBA8, AbstractTextureFlag_RenderTarget), - "Frame dump render texture"); - if (!m_frame_dump_render_texture) - { - PanicAlertFmt("Failed to allocate frame dump render texture"); - return false; - } - m_frame_dump_render_framebuffer = CreateFramebuffer(m_frame_dump_render_texture.get(), nullptr); - ASSERT(m_frame_dump_render_framebuffer); - return true; -} - -bool Renderer::CheckFrameDumpReadbackTexture(u32 target_width, u32 target_height) -{ - std::unique_ptr& rbtex = m_frame_dump_readback_texture; - if (rbtex && rbtex->GetWidth() == target_width && rbtex->GetHeight() == target_height) - return true; - - rbtex.reset(); - rbtex = CreateStagingTexture( - StagingTextureType::Readback, - TextureConfig(target_width, target_height, 1, 1, 1, AbstractTextureFormat::RGBA8, 0)); - if (!rbtex) - return false; - - return true; -} - -void Renderer::FlushFrameDump() -{ - if (!m_frame_dump_needs_flush) - return; - - // Ensure dumping thread is done with output texture before swapping. - FinishFrameData(); - - std::swap(m_frame_dump_output_texture, m_frame_dump_readback_texture); - - // Queue encoding of the last frame dumped. - auto& output = m_frame_dump_output_texture; - output->Flush(); - if (output->Map()) - { - DumpFrameData(reinterpret_cast(output->GetMappedPointer()), output->GetConfig().width, - output->GetConfig().height, static_cast(output->GetMappedStride())); - } - else - { - ERROR_LOG_FMT(VIDEO, "Failed to map texture for dumping."); - } - - m_frame_dump_needs_flush = false; - - // Shutdown frame dumping if it is no longer active. - if (!IsFrameDumping()) - ShutdownFrameDumping(); -} - -void Renderer::ShutdownFrameDumping() -{ - // Ensure the last queued readback has been sent to the encoder. - FlushFrameDump(); - - if (!m_frame_dump_thread_running.IsSet()) - return; - - // Ensure previous frame has been encoded. - FinishFrameData(); - - // Wake thread up, and wait for it to exit. - m_frame_dump_thread_running.Clear(); - m_frame_dump_start.Set(); - if (m_frame_dump_thread.joinable()) - m_frame_dump_thread.join(); - m_frame_dump_render_framebuffer.reset(); - m_frame_dump_render_texture.reset(); - - m_frame_dump_readback_texture.reset(); - m_frame_dump_output_texture.reset(); -} - -void Renderer::DumpFrameData(const u8* data, int w, int h, int stride) -{ - m_frame_dump_data = FrameDump::FrameData{data, w, h, stride, m_last_frame_state}; - - if (!m_frame_dump_thread_running.IsSet()) - { - if (m_frame_dump_thread.joinable()) - m_frame_dump_thread.join(); - m_frame_dump_thread_running.Set(); - m_frame_dump_thread = std::thread(&Renderer::FrameDumpThreadFunc, this); - } - - // Wake worker thread up. - m_frame_dump_start.Set(); - m_frame_dump_frame_running = true; -} - -void Renderer::FinishFrameData() -{ - if (!m_frame_dump_frame_running) - return; - - m_frame_dump_done.Wait(); - m_frame_dump_frame_running = false; - - m_frame_dump_output_texture->Unmap(); -} - -void Renderer::FrameDumpThreadFunc() -{ - Common::SetCurrentThreadName("FrameDumping"); - - bool dump_to_ffmpeg = !g_ActiveConfig.bDumpFramesAsImages; - bool frame_dump_started = false; - -// If Dolphin was compiled without ffmpeg, we only support dumping to images. -#if !defined(HAVE_FFMPEG) - if (dump_to_ffmpeg) - { - WARN_LOG_FMT(VIDEO, "FrameDump: Dolphin was not compiled with FFmpeg, using fallback option. " - "Frames will be saved as PNG images instead."); - dump_to_ffmpeg = false; - } -#endif - - while (true) - { - m_frame_dump_start.Wait(); - if (!m_frame_dump_thread_running.IsSet()) - break; - - auto frame = m_frame_dump_data; - - // Save screenshot - if (m_screenshot_request.TestAndClear()) - { - std::lock_guard lk(m_screenshot_lock); - - if (DumpFrameToPNG(frame, m_screenshot_name)) - OSD::AddMessage("Screenshot saved to " + m_screenshot_name); - - // Reset settings - m_screenshot_name.clear(); - m_screenshot_completed.Set(); - } - - if (Config::Get(Config::MAIN_MOVIE_DUMP_FRAMES)) - { - if (!frame_dump_started) - { - if (dump_to_ffmpeg) - frame_dump_started = StartFrameDumpToFFMPEG(frame); - else - frame_dump_started = StartFrameDumpToImage(frame); - - // Stop frame dumping if we fail to start. - if (!frame_dump_started) - Config::SetCurrent(Config::MAIN_MOVIE_DUMP_FRAMES, false); - } - - // If we failed to start frame dumping, don't write a frame. - if (frame_dump_started) - { - if (dump_to_ffmpeg) - DumpFrameToFFMPEG(frame); - else - DumpFrameToImage(frame); - } - } - - m_frame_dump_done.Set(); - } - - if (frame_dump_started) - { - // No additional cleanup is needed when dumping to images. - if (dump_to_ffmpeg) - StopFrameDumpToFFMPEG(); - } -} - -#if defined(HAVE_FFMPEG) - -bool Renderer::StartFrameDumpToFFMPEG(const FrameDump::FrameData& frame) -{ - // If dumping started at boot, the start time must be set to the boot time to maintain audio sync. - // TODO: Perhaps we should care about this when starting dumping in the middle of emulation too, - // but it's less important there since the first frame to dump usually gets delivered quickly. - const u64 start_ticks = frame.state.frame_number == 0 ? 0 : frame.state.ticks; - return m_frame_dump.Start(frame.width, frame.height, start_ticks); -} - -void Renderer::DumpFrameToFFMPEG(const FrameDump::FrameData& frame) -{ - m_frame_dump.AddFrame(frame); -} - -void Renderer::StopFrameDumpToFFMPEG() -{ - m_frame_dump.Stop(); -} - -#else - -bool Renderer::StartFrameDumpToFFMPEG(const FrameDump::FrameData&) -{ - return false; -} - -void Renderer::DumpFrameToFFMPEG(const FrameDump::FrameData&) -{ -} - -void Renderer::StopFrameDumpToFFMPEG() -{ -} - -#endif // defined(HAVE_FFMPEG) - -std::string Renderer::GetFrameDumpNextImageFileName() const -{ - return fmt::format("{}framedump_{}.png", File::GetUserPath(D_DUMPFRAMES_IDX), - m_frame_dump_image_counter); -} - -bool Renderer::StartFrameDumpToImage(const FrameDump::FrameData&) -{ - m_frame_dump_image_counter = 1; - if (!Config::Get(Config::MAIN_MOVIE_DUMP_FRAMES_SILENT)) - { - // Only check for the presence of the first image to confirm overwriting. - // A previous run will always have at least one image, and it's safe to assume that if the user - // has allowed the first image to be overwritten, this will apply any remaining images as well. - std::string filename = GetFrameDumpNextImageFileName(); - if (File::Exists(filename)) - { - if (!AskYesNoFmtT("Frame dump image(s) '{0}' already exists. Overwrite?", filename)) - return false; - } - } - - return true; -} - -void Renderer::DumpFrameToImage(const FrameDump::FrameData& frame) -{ - DumpFrameToPNG(frame, GetFrameDumpNextImageFileName()); - m_frame_dump_image_counter++; -} - bool Renderer::UseVertexDepthRange() const { // We can't compute the depth range in the vertex shader if we don't support depth clamp. @@ -1877,7 +808,7 @@ void Renderer::DoState(PointerWrap& p) if (p.IsReadMode()) { // Force the next xfb to be displayed. - m_last_xfb_id = std::numeric_limits::max(); + g_presenter->ClearLastXfbId(); m_was_orthographically_anamorphic = false; @@ -1886,7 +817,7 @@ void Renderer::DoState(PointerWrap& p) } #if defined(HAVE_FFMPEG) - m_frame_dump.DoState(p); + g_frame_dumper->DoState(p); #endif } diff --git a/Source/Core/VideoCommon/RenderBase.h b/Source/Core/VideoCommon/RenderBase.h index 853a840f39..17fe407618 100644 --- a/Source/Core/VideoCommon/RenderBase.h +++ b/Source/Core/VideoCommon/RenderBase.h @@ -23,16 +23,10 @@ #include #include "Common/CommonTypes.h" -#include "Common/Event.h" #include "Common/Flag.h" #include "Common/MathUtil.h" -#include "VideoCommon/AsyncShaderCompiler.h" -#include "VideoCommon/BPMemory.h" -#include "VideoCommon/FrameDump.h" #include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h" -#include "VideoCommon/PerformanceMetrics.h" #include "VideoCommon/RenderState.h" -#include "VideoCommon/TextureConfig.h" class AbstractFramebuffer; class AbstractPipeline; @@ -41,23 +35,22 @@ class AbstractTexture; class AbstractStagingTexture; class BoundingBox; class NativeVertexFormat; -class NetPlayChatUI; class PixelShaderManager; class PointerWrap; -struct TextureConfig; struct ComputePipelineConfig; struct AbstractPipelineConfig; struct PortableVertexDeclaration; +struct TextureConfig; +enum class AbstractTextureFormat : u32; enum class ShaderStage; enum class EFBAccessType; enum class EFBReinterpretType; enum class StagingTextureType; -enum class AspectMode; namespace VideoCommon { -class PostProcessing; -} // namespace VideoCommon +class AsyncShaderCompiler; +} struct EfbPokeData { @@ -145,11 +138,6 @@ public: // Ideal internal resolution - multiple of the native EFB resolution int GetTargetWidth() const { return m_target_width; } int GetTargetHeight() const { return m_target_height; } - // Display resolution - int GetBackbufferWidth() const { return m_backbuffer_width; } - int GetBackbufferHeight() const { return m_backbuffer_height; } - float GetBackbufferScale() const { return m_backbuffer_scale; } - void SetWindowSize(int width, int height); // Sets viewport and scissor to the specified rectangle. rect is assumed to be in framebuffer // coordinates, i.e. lower-left origin in OpenGL. @@ -174,25 +162,6 @@ public: // Use this to convert a whole native EFB rect to backbuffer coordinates MathUtil::Rectangle ConvertEFBRectangle(const MathUtil::Rectangle& rc) const; - const MathUtil::Rectangle& GetTargetRectangle() const { return m_target_rectangle; } - float CalculateDrawAspectRatio() const; - - // Crops the target rectangle to the framebuffer dimensions, reducing the size of the source - // rectangle if it is greater. Works even if the source and target rectangles don't have a - // 1:1 pixel mapping, scaling as appropriate. - void AdjustRectanglesToFitBounds(MathUtil::Rectangle* target_rect, - MathUtil::Rectangle* source_rect, int fb_width, - int fb_height); - - std::tuple ScaleToDisplayAspectRatio(int width, int height) const; - void UpdateDrawRectangle(); - - std::tuple ApplyStandardAspectCrop(float width, float height) const; - - // Use this to convert a single target rectangle to two stereo rectangles - std::tuple, MathUtil::Rectangle> - ConvertStereoRectangle(const MathUtil::Rectangle& rc) const; - unsigned int GetEFBScale() const; // Use this to upscale native EFB coordinates to IDEAL internal resolution @@ -203,10 +172,6 @@ public: float EFBToScaledXf(float x) const; float EFBToScaledYf(float y) const; - // Random utilities - void SaveScreenshot(std::string filename); - void DrawDebugText(); - virtual void ClearScreen(const MathUtil::Rectangle& rc, bool colorEnable, bool alphaEnable, bool zEnable, u32 color, u32 z); virtual void ReinterpretPixelData(EFBReinterpretType convtype); @@ -230,12 +195,18 @@ public: void Swap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks); void UpdateWidescreenHeuristic(); + bool IsGameWidescreen() const { return m_is_game_widescreen; } - // Draws the specified XFB buffer to the screen, performing any post-processing. - // Assumes that the backbuffer has already been bound and cleared. - virtual void RenderXFBToScreen(const MathUtil::Rectangle& target_rc, - const AbstractTexture* source_texture, - const MathUtil::Rectangle& source_rc); + // A simple presentation fallback, only used by video software + virtual void ShowImage(const AbstractTexture* source_texture, + const MathUtil::Rectangle& source_rc) + { + } + + // For opengl's glDrawBuffer + virtual void SelectLeftBuffer() {} + virtual void SelectRightBuffer() {} + virtual void SelectMainBuffer() {} // Called when the configuration changes, and backend structures need to be updated. virtual void OnConfigChanged(u32 bits) {} @@ -243,11 +214,7 @@ public: PixelFormat GetPrevPixelFormat() const { return m_prev_efb_format; } void StorePixelFormat(PixelFormat new_format) { m_prev_efb_format = new_format; } bool EFBHasAlphaChannel() const; - VideoCommon::PostProcessing* GetPostProcessor() const { return m_post_processor.get(); } - // Final surface changing - // This is called when the surface is resized (WX) or the window changes (Android). - void ChangeSurface(void* new_surface_handle); - void ResizeSurface(); + bool UseVertexDepthRange() const; void DoState(PointerWrap& p); @@ -257,22 +224,11 @@ public: // interface and final XFB. bool UseGeometryShaderForUI() const; - // Returns a lock for the ImGui mutex, enabling data structures to be modified from outside. - // Use with care, only non-drawing functions should be called from outside the video thread, - // as the drawing is tied to a "frame". - std::unique_lock GetImGuiLock(); - - // Begins/presents a "UI frame". UI frames do not draw any of the console XFB, but this could - // change in the future. - void BeginUIFrame(); - void EndUIFrame(); - // Will forcibly reload all textures on the next swap void ForceReloadTextures(); const GraphicsModManager& GetGraphicsModManager() const; -protected: // Bitmask containing information about which configuration has changed for the backend. enum ConfigChangeBits : u32 { @@ -286,6 +242,7 @@ protected: CONFIG_CHANGE_BIT_BBOX = (1 << 7) }; +protected: std::tuple CalculateTargetScale(int x, int y) const; bool CalculateTargetSize(); @@ -294,36 +251,11 @@ protected: void CheckFifoRecording(); void RecordVideoMemory(); - // ImGui initialization depends on being able to create textures and pipelines, so do it last. - bool InitializeImGui(); - - // Recompiles ImGui pipeline - call when stereo mode changes. - bool RecompileImGuiPipeline(); - - // Sets up ImGui state for the next frame. - // This function itself acquires the ImGui lock, so it should not be held. - void BeginImGuiFrame(); - - // Same as above but without locking the ImGui lock. - void BeginImGuiFrameUnlocked(); - - // Destroys all ImGui GPU resources, must do before shutdown. - void ShutdownImGui(); - - // Renders ImGui windows to the currently-bound framebuffer. - // Should be called with the ImGui lock held. - void DrawImGui(); - virtual std::unique_ptr CreateBoundingBox() const = 0; AbstractFramebuffer* m_current_framebuffer = nullptr; const AbstractPipeline* m_current_pipeline = nullptr; - Common::Flag m_screenshot_request; - Common::Event m_screenshot_completed; - std::mutex m_screenshot_lock; - std::string m_screenshot_name; - bool m_is_game_widescreen = false; bool m_was_orthographically_anamorphic = false; @@ -331,72 +263,12 @@ protected: int m_target_width = 1; int m_target_height = 1; - // Backbuffer (window) size and render area - int m_backbuffer_width = 0; - int m_backbuffer_height = 0; - float m_backbuffer_scale = 1.0f; - AbstractTextureFormat m_backbuffer_format = AbstractTextureFormat::Undefined; - MathUtil::Rectangle m_target_rectangle = {}; int m_frame_count = 0; - std::unique_ptr m_post_processor; - - void* m_new_surface_handle = nullptr; - Common::Flag m_surface_changed; - Common::Flag m_surface_resized; - std::mutex m_swap_mutex; - - // ImGui resources. - std::unique_ptr m_imgui_vertex_format; - std::vector> m_imgui_textures; - std::unique_ptr m_imgui_pipeline; - std::mutex m_imgui_mutex; - u64 m_imgui_last_frame_time; - private: - std::tuple CalculateOutputDimensions(int width, int height) const; - PixelFormat m_prev_efb_format = PixelFormat::INVALID_FMT; unsigned int m_efb_scale = 1; - // These will be set on the first call to SetWindowSize. - int m_last_window_request_width = 0; - int m_last_window_request_height = 0; - - // frame dumping: - FrameDump m_frame_dump; - std::thread m_frame_dump_thread; - Common::Flag m_frame_dump_thread_running; - - // Used to kick frame dump thread. - Common::Event m_frame_dump_start; - - // Set by frame dump thread on frame completion. - Common::Event m_frame_dump_done; - - // Holds emulation state during the last swap when dumping. - FrameDump::FrameState m_last_frame_state; - - // Communication of frame between video and dump threads. - FrameDump::FrameData m_frame_dump_data; - - // Texture used for screenshot/frame dumping - std::unique_ptr m_frame_dump_render_texture; - std::unique_ptr m_frame_dump_render_framebuffer; - - // Double buffer: - std::unique_ptr m_frame_dump_readback_texture; - std::unique_ptr m_frame_dump_output_texture; - // Set when readback texture holds a frame that needs to be dumped. - bool m_frame_dump_needs_flush = false; - // Set when thread is processing output texture. - bool m_frame_dump_frame_running = false; - - // Used to generate screenshot names. - u32 m_frame_dump_image_counter = 0; - - // Tracking of XFB textures so we don't render duplicate frames. - u64 m_last_xfb_id = std::numeric_limits::max(); u64 m_last_xfb_ticks = 0; u32 m_last_xfb_addr = 0; u32 m_last_xfb_width = 0; @@ -417,40 +289,6 @@ private: // Ultimate Spider-Man to crash std::array m_bounding_box_fallback = {}; - // NOTE: The methods below are called on the framedumping thread. - void FrameDumpThreadFunc(); - bool StartFrameDumpToFFMPEG(const FrameDump::FrameData&); - void DumpFrameToFFMPEG(const FrameDump::FrameData&); - void StopFrameDumpToFFMPEG(); - std::string GetFrameDumpNextImageFileName() const; - bool StartFrameDumpToImage(const FrameDump::FrameData&); - void DumpFrameToImage(const FrameDump::FrameData&); - - void ShutdownFrameDumping(); - - bool IsFrameDumping() const; - - // Checks that the frame dump render texture exists and is the correct size. - bool CheckFrameDumpRenderTexture(u32 target_width, u32 target_height); - - // Checks that the frame dump readback texture exists and is the correct size. - bool CheckFrameDumpReadbackTexture(u32 target_width, u32 target_height); - - // Fills the frame dump staging texture with the current XFB texture. - void DumpCurrentFrame(const AbstractTexture* src_texture, - const MathUtil::Rectangle& src_rect, u64 ticks, int frame_number); - - // Asynchronously encodes the specified pointer of frame data to the frame dump. - void DumpFrameData(const u8* data, int w, int h, int stride); - - // Ensures all rendered frames are queued for encoding. - void FlushFrameDump(); - - // Ensures all encoded frames have been written to the output file. - void FinishFrameData(); - - std::unique_ptr m_netplay_chat_ui; - Common::Flag m_force_reload_textures; GraphicsModManager m_graphics_mod_manager; diff --git a/Source/Core/VideoCommon/ShaderCache.cpp b/Source/Core/VideoCommon/ShaderCache.cpp index 59ae917686..1be1ad1167 100644 --- a/Source/Core/VideoCommon/ShaderCache.cpp +++ b/Source/Core/VideoCommon/ShaderCache.cpp @@ -14,6 +14,7 @@ #include "VideoCommon/DriverDetails.h" #include "VideoCommon/FramebufferManager.h" #include "VideoCommon/FramebufferShaderGen.h" +#include "VideoCommon/Present.h" #include "VideoCommon/RenderBase.h" #include "VideoCommon/Statistics.h" #include "VideoCommon/VertexLoaderManager.h" @@ -162,7 +163,7 @@ void ShaderCache::WaitForAsyncCompiler() bool running = true; constexpr auto update_ui_progress = [](size_t completed, size_t total) { - g_renderer->BeginUIFrame(); + g_presenter->BeginUIFrame(); const float center_x = ImGui::GetIO().DisplaySize.x * 0.5f; const float center_y = ImGui::GetIO().DisplaySize.y * 0.5f; @@ -183,7 +184,7 @@ void ShaderCache::WaitForAsyncCompiler() } ImGui::End(); - g_renderer->EndUIFrame(); + g_presenter->EndUIFrame(); }; while (running && @@ -195,8 +196,8 @@ void ShaderCache::WaitForAsyncCompiler() } // Just render nothing to clear the screen - g_renderer->BeginUIFrame(); - g_renderer->EndUIFrame(); + g_presenter->BeginUIFrame(); + g_presenter->EndUIFrame(); } template diff --git a/Source/Core/VideoCommon/VideoBackendBase.cpp b/Source/Core/VideoCommon/VideoBackendBase.cpp index 8ba3e14d58..167848b425 100644 --- a/Source/Core/VideoCommon/VideoBackendBase.cpp +++ b/Source/Core/VideoCommon/VideoBackendBase.cpp @@ -44,12 +44,14 @@ #include "VideoCommon/CPMemory.h" #include "VideoCommon/CommandProcessor.h" #include "VideoCommon/Fifo.h" +#include "VideoCommon/FrameDumper.h" #include "VideoCommon/FramebufferManager.h" #include "VideoCommon/GeometryShaderManager.h" #include "VideoCommon/IndexGenerator.h" #include "VideoCommon/OpcodeDecoding.h" #include "VideoCommon/PixelEngine.h" #include "VideoCommon/PixelShaderManager.h" +#include "VideoCommon/Present.h" #include "VideoCommon/RenderBase.h" #include "VideoCommon/TMEM.h" #include "VideoCommon/TextureCacheBase.h" @@ -322,6 +324,8 @@ void VideoBackendBase::InitializeShared() // do not initialize again for the config window m_initialized = true; + g_presenter = std::make_unique(); + auto& system = Core::System::GetInstance(); auto& command_processor = system.GetCommandProcessor(); command_processor.Init(system); @@ -333,6 +337,7 @@ void VideoBackendBase::InitializeShared() system.GetGeometryShaderManager().Init(); system.GetPixelShaderManager().Init(); TMEM::Init(); + g_frame_dumper = std::make_unique(); g_Config.VerifyValidity(); UpdateActiveConfig(); @@ -340,6 +345,9 @@ void VideoBackendBase::InitializeShared() void VideoBackendBase::ShutdownShared() { + g_frame_dumper.reset(); + g_presenter.reset(); + if (g_shader_cache) g_shader_cache->Shutdown(); if (g_renderer)