Move Presenting, Dumping and ImGui out of Renderer
This commit is contained in:
parent
c38c76abad
commit
0d4537d60f
|
@ -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)
|
||||
|
|
|
@ -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<BootParameters> 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));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -639,6 +639,7 @@
|
|||
<ClInclude Include="VideoCommon\FramebufferManager.h" />
|
||||
<ClInclude Include="VideoCommon\FramebufferShaderGen.h" />
|
||||
<ClInclude Include="VideoCommon\FrameDump.h" />
|
||||
<ClInclude Include="VideoCommon\FrameDumper.h" />
|
||||
<ClInclude Include="VideoCommon\FreeLookCamera.h" />
|
||||
<ClInclude Include="VideoCommon\GeometryShaderGen.h" />
|
||||
<ClInclude Include="VideoCommon\GeometryShaderManager.h" />
|
||||
|
@ -668,6 +669,7 @@
|
|||
<ClInclude Include="VideoCommon\NetPlayChatUI.h" />
|
||||
<ClInclude Include="VideoCommon\NetPlayGolfUI.h" />
|
||||
<ClInclude Include="VideoCommon\OnScreenDisplay.h" />
|
||||
<ClInclude Include="VideoCommon\OnScreenUI.h" />
|
||||
<ClInclude Include="VideoCommon\OpcodeDecoding.h" />
|
||||
<ClInclude Include="VideoCommon\PerfQueryBase.h" />
|
||||
<ClInclude Include="VideoCommon\PerformanceMetrics.h" />
|
||||
|
@ -676,6 +678,7 @@
|
|||
<ClInclude Include="VideoCommon\PixelShaderGen.h" />
|
||||
<ClInclude Include="VideoCommon\PixelShaderManager.h" />
|
||||
<ClInclude Include="VideoCommon\PostProcessing.h" />
|
||||
<ClInclude Include="VideoCommon\Present.h" />
|
||||
<ClInclude Include="VideoCommon\RenderBase.h" />
|
||||
<ClInclude Include="VideoCommon\RenderState.h" />
|
||||
<ClInclude Include="VideoCommon\ShaderCache.h" />
|
||||
|
@ -1232,6 +1235,7 @@
|
|||
<ClCompile Include="VideoCommon\FramebufferManager.cpp" />
|
||||
<ClCompile Include="VideoCommon\FramebufferShaderGen.cpp" />
|
||||
<ClCompile Include="VideoCommon\FrameDump.cpp" />
|
||||
<ClCompile Include="VideoCommon\FrameDumper.cpp" />
|
||||
<ClCompile Include="VideoCommon\FreeLookCamera.cpp" />
|
||||
<ClCompile Include="VideoCommon\GeometryShaderGen.cpp" />
|
||||
<ClCompile Include="VideoCommon\GeometryShaderManager.cpp" />
|
||||
|
@ -1254,6 +1258,7 @@
|
|||
<ClCompile Include="VideoCommon\NetPlayChatUI.cpp" />
|
||||
<ClCompile Include="VideoCommon\NetPlayGolfUI.cpp" />
|
||||
<ClCompile Include="VideoCommon\OnScreenDisplay.cpp" />
|
||||
<ClCompile Include="VideoCommon\OnScreenUI.cpp" />
|
||||
<ClCompile Include="VideoCommon\OpcodeDecoding.cpp" />
|
||||
<ClCompile Include="VideoCommon\PerfQueryBase.cpp" />
|
||||
<ClCompile Include="VideoCommon\PerformanceMetrics.cpp" />
|
||||
|
@ -1262,6 +1267,7 @@
|
|||
<ClCompile Include="VideoCommon\PixelShaderGen.cpp" />
|
||||
<ClCompile Include="VideoCommon\PixelShaderManager.cpp" />
|
||||
<ClCompile Include="VideoCommon\PostProcessing.cpp" />
|
||||
<ClCompile Include="VideoCommon\Present.cpp" />
|
||||
<ClCompile Include="VideoCommon\RenderBase.cpp" />
|
||||
<ClCompile Include="VideoCommon\RenderState.cpp" />
|
||||
<ClCompile Include="VideoCommon\ShaderCache.cpp" />
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#include <climits>
|
||||
#include <cstdio>
|
||||
|
||||
#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;
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ static constexpr auto X_None = None;
|
|||
#include <X11/Xutil.h>
|
||||
#include <X11/keysym.h>
|
||||
#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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<std::string> Host_GetPreferredLocales()
|
||||
|
|
|
@ -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<const QKeyEvent*>(event);
|
||||
const bool is_down = event->type() == QEvent::KeyPress;
|
||||
const u32 key = static_cast<u32>(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<const QMouseEvent*>(event)->pos().x() * scale;
|
||||
ImGui::GetIO().MousePos.y = static_cast<const QMouseEvent*>(event)->pos().y() * scale;
|
||||
float x = static_cast<const QMouseEvent*>(event)->pos().x() * scale;
|
||||
float y = static_cast<const QMouseEvent*>(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<u32>(static_cast<const QMouseEvent*>(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<std::array<int, 2>, 21> key_map{{
|
||||
static std::array<std::array<int, 2>, 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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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<CAMetalLayer*> 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<CAMetalLayer*>(m_new_surface_handle));
|
||||
m_new_surface_handle = nullptr;
|
||||
m_layer = MRCRetain(static_cast<CAMetalLayer*>(g_presenter->GetNewSurfaceHandle()));
|
||||
SetupSurface();
|
||||
}
|
||||
|
||||
void Metal::Renderer::CheckForSurfaceResize()
|
||||
{
|
||||
if (!m_surface_resized.TestAndClear())
|
||||
if (!g_presenter->SurfaceResizedTestAndClear())
|
||||
return;
|
||||
SetupSurface();
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 <X11/Xlib.h>
|
||||
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<FrameDumpContext>();
|
||||
|
||||
|
@ -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<AVPacket, std::function<void(AVPacket*)>>(
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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 {};
|
||||
}
|
||||
|
|
|
@ -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<int>& src_rect,
|
||||
const MathUtil::Rectangle<int>& 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<int> 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<AbstractStagingTexture>& 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<u8*>(output->GetMappedPointer()), output->GetConfig().width,
|
||||
output->GetConfig().height, static_cast<int>(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<std::mutex> 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<std::mutex> 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<FrameDumper> g_frame_dumper;
|
|
@ -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<int>& src_rect,
|
||||
const MathUtil::Rectangle<int>& 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<AbstractTexture> m_frame_dump_render_texture;
|
||||
std::unique_ptr<AbstractFramebuffer> m_frame_dump_render_framebuffer;
|
||||
|
||||
// Double buffer:
|
||||
std::unique_ptr<AbstractStagingTexture> m_frame_dump_readback_texture;
|
||||
std::unique_ptr<AbstractStagingTexture> 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<FrameDumper> g_frame_dumper;
|
|
@ -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 <inttypes.h>
|
||||
#include <mutex>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <implot.h>
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
bool OnScreenUI::Initialize(u32 width, u32 height, float scale)
|
||||
{
|
||||
std::unique_lock<std::mutex> 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<AbstractTexture> 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<std::mutex> 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<AbstractShader> vertex_shader = g_renderer->CreateShaderFromSource(
|
||||
ShaderStage::Vertex, FramebufferShaderGen::GenerateImGuiVertexShader(),
|
||||
"ImGui vertex shader");
|
||||
std::unique_ptr<AbstractShader> 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<AbstractShader> 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<std::mutex> 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<float>(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<float>(m_backbuffer_width), static_cast<float>(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<float>(m_backbuffer_width),
|
||||
static_cast<float>(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<int>(
|
||||
static_cast<int>(cmd.ClipRect.x), static_cast<int>(cmd.ClipRect.y),
|
||||
static_cast<int>(cmd.ClipRect.z), static_cast<int>(cmd.ClipRect.w)),
|
||||
g_renderer->GetCurrentFramebuffer()));
|
||||
g_renderer->SetTexture(0, reinterpret_cast<const AbstractTexture*>(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<int>(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<std::mutex> OnScreenUI::GetImGuiLock()
|
||||
{
|
||||
return std::unique_lock<std::mutex>(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<const std::array<int, 2>> 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
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright 2023 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#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<std::mutex> 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<const std::array<int, 2>> 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<NativeVertexFormat> m_imgui_vertex_format;
|
||||
std::vector<std::unique_ptr<AbstractTexture>> m_imgui_textures;
|
||||
std::unique_ptr<AbstractPipeline> 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
|
|
@ -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<int>& 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 = {
|
||||
|
|
|
@ -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<VideoCommon::Presenter> 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<VideoCommon::PostProcessing>();
|
||||
if (!m_post_processor->Initialize(m_backbuffer_format))
|
||||
return false;
|
||||
|
||||
m_onscreen_ui = std::make_unique<OnScreenUI>();
|
||||
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<std::mutex> guard(m_swap_mutex);
|
||||
g_renderer->PresentBackbuffer();
|
||||
g_renderer->EndUtilityDrawing();
|
||||
}
|
||||
|
||||
m_onscreen_ui->BeginImGuiFrame(m_backbuffer_width, m_backbuffer_height);
|
||||
}
|
||||
|
||||
std::tuple<MathUtil::Rectangle<int>, MathUtil::Rectangle<int>>
|
||||
Presenter::ConvertStereoRectangle(const MathUtil::Rectangle<int>& 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<float>(m_backbuffer_width) / static_cast<float>(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<int>* target_rect,
|
||||
MathUtil::Rectangle<int>* 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<std::mutex> lock(m_swap_mutex);
|
||||
m_new_surface_handle = new_surface_handle;
|
||||
m_surface_changed.Set();
|
||||
}
|
||||
|
||||
void Presenter::ResizeSurface()
|
||||
{
|
||||
std::lock_guard<std::mutex> 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<float, float> 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<float>(m_backbuffer_width);
|
||||
const float win_height = static_cast<float>(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<int>(std::ceil(draw_width)) % 4;
|
||||
draw_height = std::ceil(draw_height) - static_cast<int>(std::ceil(draw_height)) % 4;
|
||||
|
||||
m_target_rectangle.left = static_cast<int>(std::round(win_width / 2.0 - draw_width / 2.0));
|
||||
m_target_rectangle.top = static_cast<int>(std::round(win_height / 2.0 - draw_height / 2.0));
|
||||
m_target_rectangle.right = m_target_rectangle.left + static_cast<int>(draw_width);
|
||||
m_target_rectangle.bottom = m_target_rectangle.top + static_cast<int>(draw_height);
|
||||
}
|
||||
|
||||
std::tuple<float, float> 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<float>(width);
|
||||
float scaled_height = static_cast<float>(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<int, int> 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<int>(std::ceil(scaled_width));
|
||||
height = static_cast<int>(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<int>& target_rc,
|
||||
const AbstractTexture* source_texture,
|
||||
const MathUtil::Rectangle<int>& 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<int>& 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<int> 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<int>(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<std::mutex> 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<std::array<int, 2>> 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
|
|
@ -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 <array>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <tuple>
|
||||
|
||||
class AbstractTexture;
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
class OnScreenUI;
|
||||
class PostProcessing;
|
||||
|
||||
class Presenter
|
||||
{
|
||||
public:
|
||||
using ClearColor = std::array<float, 4>;
|
||||
|
||||
Presenter();
|
||||
virtual ~Presenter();
|
||||
|
||||
bool SubmitXFB(RcTcacheEntry xfb_entry, MathUtil::Rectangle<int>& xfb_rect, u64 ticks,
|
||||
int frame_count);
|
||||
void Present();
|
||||
void ClearLastXfbId() { m_last_xfb_id = std::numeric_limits<u64>::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<int>* target_rect,
|
||||
MathUtil::Rectangle<int>* 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<int>& target_rc,
|
||||
const AbstractTexture* source_texture,
|
||||
const MathUtil::Rectangle<int>& 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<std::array<int, 2>> 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<int>& GetTargetRectangle() const { return m_target_rectangle; }
|
||||
|
||||
private:
|
||||
std::tuple<int, int> CalculateOutputDimensions(int width, int height) const;
|
||||
std::tuple<float, float> ApplyStandardAspectCrop(float width, float height) const;
|
||||
std::tuple<float, float> ScaleToDisplayAspectRatio(int width, int height) const;
|
||||
|
||||
// Use this to convert a single target rectangle to two stereo rectangles
|
||||
std::tuple<MathUtil::Rectangle<int>, MathUtil::Rectangle<int>>
|
||||
ConvertStereoRectangle(const MathUtil::Rectangle<int>& 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<int> m_target_rectangle = {};
|
||||
|
||||
RcTcacheEntry m_xfb_entry;
|
||||
MathUtil::Rectangle<int> m_xfb_rect;
|
||||
|
||||
// Tracking of XFB textures so we don't render duplicate frames.
|
||||
u64 m_last_xfb_id = std::numeric_limits<u64>::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<VideoCommon::PostProcessing> m_post_processor;
|
||||
std::unique_ptr<VideoCommon::OnScreenUI> m_onscreen_ui;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
|
||||
extern std::unique_ptr<VideoCommon::Presenter> g_presenter;
|
File diff suppressed because it is too large
Load Diff
|
@ -23,16 +23,10 @@
|
|||
#include <vector>
|
||||
|
||||
#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<int> ConvertEFBRectangle(const MathUtil::Rectangle<int>& rc) const;
|
||||
|
||||
const MathUtil::Rectangle<int>& 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<int>* target_rect,
|
||||
MathUtil::Rectangle<int>* source_rect, int fb_width,
|
||||
int fb_height);
|
||||
|
||||
std::tuple<float, float> ScaleToDisplayAspectRatio(int width, int height) const;
|
||||
void UpdateDrawRectangle();
|
||||
|
||||
std::tuple<float, float> ApplyStandardAspectCrop(float width, float height) const;
|
||||
|
||||
// Use this to convert a single target rectangle to two stereo rectangles
|
||||
std::tuple<MathUtil::Rectangle<int>, MathUtil::Rectangle<int>>
|
||||
ConvertStereoRectangle(const MathUtil::Rectangle<int>& 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<int>& 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<int>& target_rc,
|
||||
const AbstractTexture* source_texture,
|
||||
const MathUtil::Rectangle<int>& source_rc);
|
||||
// A simple presentation fallback, only used by video software
|
||||
virtual void ShowImage(const AbstractTexture* source_texture,
|
||||
const MathUtil::Rectangle<int>& 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<std::mutex> 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<int, int> 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<BoundingBox> 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<int> m_target_rectangle = {};
|
||||
int m_frame_count = 0;
|
||||
|
||||
std::unique_ptr<VideoCommon::PostProcessing> 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<NativeVertexFormat> m_imgui_vertex_format;
|
||||
std::vector<std::unique_ptr<AbstractTexture>> m_imgui_textures;
|
||||
std::unique_ptr<AbstractPipeline> m_imgui_pipeline;
|
||||
std::mutex m_imgui_mutex;
|
||||
u64 m_imgui_last_frame_time;
|
||||
|
||||
private:
|
||||
std::tuple<int, int> 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<AbstractTexture> m_frame_dump_render_texture;
|
||||
std::unique_ptr<AbstractFramebuffer> m_frame_dump_render_framebuffer;
|
||||
|
||||
// Double buffer:
|
||||
std::unique_ptr<AbstractStagingTexture> m_frame_dump_readback_texture;
|
||||
std::unique_ptr<AbstractStagingTexture> 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<u64>::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<u16, 4> 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<int>& 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<NetPlayChatUI> m_netplay_chat_ui;
|
||||
|
||||
Common::Flag m_force_reload_textures;
|
||||
|
||||
GraphicsModManager m_graphics_mod_manager;
|
||||
|
|
|
@ -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 <typename SerializedUidType, typename UidType>
|
||||
|
|
|
@ -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<VideoCommon::Presenter>();
|
||||
|
||||
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<FrameDumper>();
|
||||
|
||||
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)
|
||||
|
|
Loading…
Reference in New Issue