diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index 428870d02f..7902a9973f 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -59,7 +59,7 @@ #include "UICommon/UICommon.h" #include "VideoCommon/OnScreenDisplay.h" -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/Present.h" #include "VideoCommon/VideoBackendBase.h" #include "jni/AndroidCommon/AndroidCommon.h" @@ -456,8 +456,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 +483,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 +503,7 @@ JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_HasSurfa JNIEXPORT jfloat JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetGameAspectRatio(JNIEnv*, jclass) { - return g_renderer->CalculateDrawAspectRatio(); + return g_presenter->CalculateDrawAspectRatio(); } JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_RefreshWiimotes(JNIEnv*, jclass) diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index 6e49f7b7a6..30248f864f 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -62,6 +62,7 @@ add_library(common GekkoDisassembler.h Hash.cpp Hash.h + HookableEvent.h HttpRequest.cpp HttpRequest.h Image.cpp @@ -115,6 +116,7 @@ add_library(common SocketContext.cpp SocketContext.h SPSCQueue.h + StringLiteral.h StringUtil.cpp StringUtil.h SymbolDB.cpp diff --git a/Source/Core/Common/HookableEvent.h b/Source/Core/Common/HookableEvent.h new file mode 100644 index 0000000000..8c87303725 --- /dev/null +++ b/Source/Core/Common/HookableEvent.h @@ -0,0 +1,102 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Common/Logging/Log.h" +#include "Common/StringLiteral.h" + +#include +#include +#include +#include +#include +#include + +namespace Common +{ +struct HookBase +{ + virtual ~HookBase() = default; + +protected: + HookBase() = default; + + // This shouldn't be copied. And since we always wrap it in unique_ptr, no need to move it either + HookBase(const HookBase&) = delete; + HookBase(HookBase&&) = delete; + HookBase& operator=(const HookBase&) = delete; + HookBase& operator=(HookBase&&) = delete; +}; + +// EventHook is a handle a registered listener holds. +// When the handle is destroyed, the HookableEvent will automatically remove the listener. +using EventHook = std::unique_ptr; + +// A hookable event system. +// +// Define Events in a header as: +// +// using MyLoveyEvent = HookableEvent<"My lovely event", std::string, u32>; +// +// Register listeners anywhere you need them as: +// EventHook myHook = MyLoveyEvent::Register([](std::string foo, u32 bar) { +// fmt::print("I've been triggered with {} and {}", foo, bar) +// }, "NameOfHook"); +// +// The hook will be automatically unregistered when the EventHook object goes out of scope. +// Trigger events by calling Trigger as: +// +// MyLoveyEvent::Trigger("Hello world", 42); +// +template +class HookableEvent +{ +public: + using CallbackType = std::function; + +private: + struct HookImpl final : public HookBase + { + ~HookImpl() override { HookableEvent::Remove(this); } + HookImpl(CallbackType callback, std::string name) + : m_fn(std::move(callback)), m_name(std::move(name)) + { + } + CallbackType m_fn; + std::string m_name; + }; + +public: + // Returns a handle that will unregister the listener when destroyed. + static EventHook Register(CallbackType callback, std::string name) + { + std::lock_guard lock(m_mutex); + + DEBUG_LOG_FMT(COMMON, "Registering {} handler at {} event hook", name, EventName.value); + auto handle = std::make_unique(callback, std::move(name)); + m_listeners.push_back(handle.get()); + return handle; + } + + static void Trigger(const CallbackArgs&... args) + { + std::lock_guard lock(m_mutex); + + for (const auto& handle : m_listeners) + handle->m_fn(args...); + } + +private: + static void Remove(HookImpl* handle) + { + std::lock_guard lock(m_mutex); + + std::erase(m_listeners, handle); + } + + inline static std::vector m_listeners = {}; + inline static std::mutex m_mutex; +}; + +} // namespace Common diff --git a/Source/Core/Common/StringLiteral.h b/Source/Core/Common/StringLiteral.h new file mode 100644 index 0000000000..6da9afd24d --- /dev/null +++ b/Source/Core/Common/StringLiteral.h @@ -0,0 +1,20 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +namespace Common +{ +// A useful template for passing string literals as arguments to templates +// from: https://ctrpeach.io/posts/cpp20-string-literal-template-parameters/ +template +struct StringLiteral +{ + consteval StringLiteral(const char (&str)[N]) { std::copy_n(str, N, value); } + + char value[N]; +}; + +} // namespace Common diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 7757414834..1f627705e9 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -84,11 +84,13 @@ #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/RenderBase.h" +#include "VideoCommon/Present.h" #include "VideoCommon/VideoBackendBase.h" +#include "VideoCommon/VideoEvents.h" #ifdef ANDROID #include "jni/AndroidCommon/IDCache.h" @@ -130,6 +132,15 @@ static thread_local bool tls_is_gpu_thread = false; static void EmuThread(std::unique_ptr boot, WindowSystemInfo wsi); +static Common::EventHook s_frame_presented = AfterPresentEvent::Register( + [](auto& present_info) { + const double last_speed_denominator = g_perf_metrics.GetLastSpeedDenominator(); + // The denominator should always be > 0 but if it's not, just return 1 + const double last_speed = last_speed_denominator > 0.0 ? (1.0 / last_speed_denominator) : 1.0; + Core::Callback_FramePresented(last_speed); + }, + "Core Frame Presented"); + bool GetIsThrottlerTempDisabled() { return s_is_throttler_temp_disabled; @@ -538,11 +549,6 @@ static void EmuThread(std::unique_ptr boot, WindowSystemInfo wsi } Common::ScopeGuard video_guard{[] { g_video_backend->Shutdown(); }}; - // 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(); - if (cpu_info.HTT) Config::SetBaseOrCurrent(Config::MAIN_DSP_THREAD, cpu_info.num_cores > 4); else @@ -731,13 +737,13 @@ static std::string GenerateScreenshotName() void SaveScreenShot() { - Core::RunAsCPUThread([] { g_renderer->SaveScreenshot(GenerateScreenshotName()); }); + Core::RunAsCPUThread([] { g_frame_dumper->SaveScreenshot(GenerateScreenshotName()); }); } void SaveScreenShot(std::string_view name) { Core::RunAsCPUThread([&name] { - g_renderer->SaveScreenshot(fmt::format("{}{}.png", GenerateScreenshotFolderPath(), name)); + g_frame_dumper->SaveScreenshot(fmt::format("{}{}.png", GenerateScreenshotFolderPath(), name)); }); } diff --git a/Source/Core/Core/FifoPlayer/FifoRecorder.cpp b/Source/Core/Core/FifoPlayer/FifoRecorder.cpp index 0d12481420..15063c3f02 100644 --- a/Source/Core/Core/FifoPlayer/FifoRecorder.cpp +++ b/Source/Core/Core/FifoPlayer/FifoRecorder.cpp @@ -14,7 +14,12 @@ #include "Core/HW/Memmap.h" #include "Core/System.h" +#include "VideoCommon/BPMemory.h" +#include "VideoCommon/CommandProcessor.h" #include "VideoCommon/OpcodeDecoding.h" +#include "VideoCommon/TextureDecoder.h" +#include "VideoCommon/VideoEvents.h" +#include "VideoCommon/XFMemory.h" #include "VideoCommon/XFStructs.h" class FifoRecorder::FifoRecordAnalyzer : public OpcodeDecoder::Callback @@ -249,6 +254,42 @@ void FifoRecorder::StartRecording(s32 numFrames, CallbackFunc finishedCb) m_RequestedRecordingEnd = false; m_FinishedCb = finishedCb; + + m_end_of_frame_event = AfterFrameEvent::Register( + [this] { + const bool was_recording = OpcodeDecoder::g_record_fifo_data; + OpcodeDecoder::g_record_fifo_data = IsRecording(); + + if (!OpcodeDecoder::g_record_fifo_data) + return; + + if (!was_recording) + { + RecordInitialVideoMemory(); + } + + auto& system = Core::System::GetInstance(); + auto& command_processor = system.GetCommandProcessor(); + const auto& fifo = command_processor.GetFifo(); + EndFrame(fifo.CPBase.load(std::memory_order_relaxed), + fifo.CPEnd.load(std::memory_order_relaxed)); + }, + "FifoRecorder::EndFrame"); +} + +void FifoRecorder::RecordInitialVideoMemory() +{ + const u32* bpmem_ptr = reinterpret_cast(&bpmem); + u32 cpmem[256] = {}; + // The FIFO recording format splits XF memory into xfmem and xfregs; follow + // that split here. + const u32* xfmem_ptr = reinterpret_cast(&xfmem); + const u32* xfregs_ptr = reinterpret_cast(&xfmem) + FifoDataFile::XF_MEM_SIZE; + u32 xfregs_size = sizeof(XFMemory) / 4 - FifoDataFile::XF_MEM_SIZE; + + g_main_cp_state.FillCPMemoryArray(cpmem); + + SetVideoMemory(bpmem_ptr, cpmem, xfmem_ptr, xfregs_ptr, xfregs_size, texMem); } void FifoRecorder::StopRecording() @@ -391,11 +432,14 @@ void FifoRecorder::EndFrame(u32 fifoStart, u32 fifoEnd) m_SkipFutureData = true; // Signal video backend that it should not call this function when the next frame ends m_IsRecording = false; + + // Remove our frame end callback + m_end_of_frame_event.reset(); } } void FifoRecorder::SetVideoMemory(const u32* bpMem, const u32* cpMem, const u32* xfMem, - const u32* xfRegs, u32 xfRegsSize, const u8* texMem) + const u32* xfRegs, u32 xfRegsSize, const u8* texMem_ptr) { std::lock_guard lk(m_mutex); @@ -408,7 +452,7 @@ void FifoRecorder::SetVideoMemory(const u32* bpMem, const u32* cpMem, const u32* u32 xfRegsCopySize = std::min((u32)FifoDataFile::XF_REGS_SIZE, xfRegsSize); memcpy(m_File->GetXFRegs(), xfRegs, xfRegsCopySize * 4); - memcpy(m_File->GetTexMem(), texMem, FifoDataFile::TEX_MEM_SIZE); + memcpy(m_File->GetTexMem(), texMem_ptr, FifoDataFile::TEX_MEM_SIZE); } m_record_analyzer = std::make_unique(this, cpMem); diff --git a/Source/Core/Core/FifoPlayer/FifoRecorder.h b/Source/Core/Core/FifoPlayer/FifoRecorder.h index 3a28d05bce..b6330c2e28 100644 --- a/Source/Core/Core/FifoPlayer/FifoRecorder.h +++ b/Source/Core/Core/FifoPlayer/FifoRecorder.h @@ -9,6 +9,7 @@ #include #include "Common/Assert.h" +#include "Common/HookableEvent.h" #include "Core/FifoPlayer/FifoDataFile.h" class FifoRecorder @@ -50,6 +51,8 @@ public: private: class FifoRecordAnalyzer; + void RecordInitialVideoMemory(); + // Accessed from both GUI and video threads std::recursive_mutex m_mutex; @@ -72,4 +75,6 @@ private: std::vector m_FifoData; std::vector m_Ram; std::vector m_ExRam; + + Common::EventHook m_end_of_frame_event; }; diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index 405af72f4a..1329dbe070 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -42,7 +42,7 @@ #include "Core/PowerPC/PowerPC.h" #include "Core/System.h" -#include "VideoCommon/FrameDump.h" +#include "VideoCommon/FrameDumpFFMpeg.h" #include "VideoCommon/OnScreenDisplay.h" #include "VideoCommon/VideoBackendBase.h" @@ -96,7 +96,7 @@ static size_t s_state_writes_in_queue; static std::condition_variable s_state_write_queue_is_empty; // Don't forget to increase this after doing changes on the savestate system -constexpr u32 STATE_VERSION = 157; // Last changed in PR 11183 +constexpr u32 STATE_VERSION = 158; // Last changed in PR 11522 // Maps savestate versions to Dolphin versions. // Versions after 42 don't need to be added to this list, diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 613e5a5e18..f7f7606982 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -109,6 +109,7 @@ + @@ -145,6 +146,7 @@ + @@ -534,7 +536,7 @@ - + @@ -545,7 +547,7 @@ - + @@ -561,7 +563,7 @@ - + @@ -569,9 +571,10 @@ + + - @@ -587,6 +590,7 @@ + @@ -608,7 +612,7 @@ - + @@ -618,6 +622,7 @@ + @@ -638,7 +643,8 @@ - + + @@ -668,6 +674,8 @@ + + @@ -676,6 +684,7 @@ + @@ -707,7 +716,9 @@ + + @@ -1142,7 +1153,7 @@ - + @@ -1151,7 +1162,7 @@ - + @@ -1167,15 +1178,16 @@ - + + + - @@ -1189,6 +1201,7 @@ + @@ -1206,7 +1219,7 @@ - + @@ -1216,6 +1229,7 @@ + @@ -1231,7 +1245,8 @@ - + + @@ -1254,6 +1269,7 @@ + @@ -1262,6 +1278,7 @@ + @@ -1291,6 +1308,7 @@ + diff --git a/Source/Core/DolphinNoGUI/MainNoGUI.cpp b/Source/Core/DolphinNoGUI/MainNoGUI.cpp index 11bbf55da1..e8bb0958ab 100644 --- a/Source/Core/DolphinNoGUI/MainNoGUI.cpp +++ b/Source/Core/DolphinNoGUI/MainNoGUI.cpp @@ -33,7 +33,6 @@ #include "InputCommon/GCAdapter.h" -#include "VideoCommon/RenderBase.h" #include "VideoCommon/VideoBackendBase.h" static std::unique_ptr s_platform; diff --git a/Source/Core/DolphinNoGUI/PlatformFBDev.cpp b/Source/Core/DolphinNoGUI/PlatformFBDev.cpp index d965c6f99b..552a2aa9c4 100644 --- a/Source/Core/DolphinNoGUI/PlatformFBDev.cpp +++ b/Source/Core/DolphinNoGUI/PlatformFBDev.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -21,7 +22,6 @@ #include #include #include -#include "VideoCommon/RenderBase.h" namespace { diff --git a/Source/Core/DolphinNoGUI/PlatformWin32.cpp b/Source/Core/DolphinNoGUI/PlatformWin32.cpp index d4a940bce5..a34ace70ce 100644 --- a/Source/Core/DolphinNoGUI/PlatformWin32.cpp +++ b/Source/Core/DolphinNoGUI/PlatformWin32.cpp @@ -13,7 +13,7 @@ #include #include -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/Present.h" #include "resource.h" namespace @@ -181,8 +181,8 @@ LRESULT PlatformWin32::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam case WM_SIZE: { - if (g_renderer) - g_renderer->ResizeSurface(); + if (g_presenter) + g_presenter->ResizeSurface(); } break; diff --git a/Source/Core/DolphinNoGUI/PlatformX11.cpp b/Source/Core/DolphinNoGUI/PlatformX11.cpp index 8dcd93bf52..18784499e4 100644 --- a/Source/Core/DolphinNoGUI/PlatformX11.cpp +++ b/Source/Core/DolphinNoGUI/PlatformX11.cpp @@ -19,13 +19,14 @@ static constexpr auto X_None = None; #include #include #include +#include #include #include #include #include #include "UICommon/X11Utils.h" -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/Present.h" #ifndef HOST_NAME_MAX #define HOST_NAME_MAX _POSIX_HOST_NAME_MAX @@ -263,8 +264,8 @@ void PlatformX11::ProcessEvents() break; case ConfigureNotify: { - if (g_renderer) - g_renderer->ResizeSurface(); + if (g_presenter) + g_presenter->ResizeSurface(); } break; } diff --git a/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp b/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp index 59907bd39b..c6842c7351 100644 --- a/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp +++ b/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp @@ -21,7 +21,7 @@ #include "DolphinQt/Config/Graphics/EnhancementsWidget.h" #include "VideoCommon/PostProcessing.h" -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/Present.h" #include "VideoCommon/VideoConfig.h" using ConfigurationOption = VideoCommon::PostProcessingConfiguration::ConfigurationOption; @@ -31,9 +31,9 @@ PostProcessingConfigWindow::PostProcessingConfigWindow(EnhancementsWidget* paren const std::string& shader) : QDialog(parent), m_shader(shader) { - if (g_renderer && g_renderer->GetPostProcessor()) + if (g_presenter && g_presenter->GetPostProcessor()) { - m_post_processor = g_renderer->GetPostProcessor()->GetConfig(); + m_post_processor = g_presenter->GetPostProcessor()->GetConfig(); } else { @@ -52,7 +52,7 @@ PostProcessingConfigWindow::PostProcessingConfigWindow(EnhancementsWidget* paren PostProcessingConfigWindow::~PostProcessingConfigWindow() { m_post_processor->SaveOptionsConfiguration(); - if (!(g_renderer && g_renderer->GetPostProcessor())) + if (!(g_presenter && g_presenter->GetPostProcessor())) { delete m_post_processor; } diff --git a/Source/Core/DolphinQt/Host.cpp b/Source/Core/DolphinQt/Host.cpp index 01dcc46dc1..036dc2cffa 100644 --- a/Source/Core/DolphinQt/Host.cpp +++ b/Source/Core/DolphinQt/Host.cpp @@ -36,8 +36,9 @@ #include "UICommon/DiscordPresence.h" +#include "VideoCommon/AbstractGfx.h" #include "VideoCommon/Fifo.cpp" -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/Present.h" #include "VideoCommon/VideoConfig.h" static thread_local bool tls_is_host_thread = false; @@ -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); } } @@ -149,11 +150,11 @@ bool Host::GetRenderFullFocus() void Host::SetRenderFocus(bool focus) { m_render_focus = focus; - if (g_renderer && m_render_fullscreen && g_ActiveConfig.ExclusiveFullscreenEnabled()) + if (g_gfx && m_render_fullscreen && g_ActiveConfig.ExclusiveFullscreenEnabled()) { RunWithGPUThreadInactive([focus] { if (!Config::Get(Config::MAIN_RENDER_TO_MAIN)) - g_renderer->SetFullscreen(focus); + g_gfx->SetFullscreen(focus); }); } } @@ -181,17 +182,16 @@ void Host::SetRenderFullscreen(bool fullscreen) { m_render_fullscreen = fullscreen; - if (g_renderer && g_renderer->IsFullscreen() != fullscreen && - g_ActiveConfig.ExclusiveFullscreenEnabled()) + if (g_gfx && g_gfx->IsFullscreen() != fullscreen && g_ActiveConfig.ExclusiveFullscreenEnabled()) { - RunWithGPUThreadInactive([fullscreen] { g_renderer->SetFullscreen(fullscreen); }); + RunWithGPUThreadInactive([fullscreen] { g_gfx->SetFullscreen(fullscreen); }); } } void Host::ResizeSurface(int new_width, int new_height) { - if (g_renderer) - g_renderer->ResizeSurface(); + if (g_presenter) + g_presenter->ResizeSurface(); } std::vector Host_GetPreferredLocales() diff --git a/Source/Core/DolphinQt/HotkeyScheduler.cpp b/Source/Core/DolphinQt/HotkeyScheduler.cpp index 0b80d746a7..1e390c3796 100644 --- a/Source/Core/DolphinQt/HotkeyScheduler.cpp +++ b/Source/Core/DolphinQt/HotkeyScheduler.cpp @@ -43,7 +43,6 @@ #include "InputCommon/ControllerInterface/ControllerInterface.h" #include "VideoCommon/OnScreenDisplay.h" -#include "VideoCommon/RenderBase.h" #include "VideoCommon/VertexShaderManager.h" #include "VideoCommon/VideoConfig.h" diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index f4d52c0fa5..59fc19f6a3 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -1383,7 +1383,7 @@ void MainWindow::SetStateSlot(int slot) void MainWindow::IncrementSelectedStateSlot() { - int state_slot = m_state_slot + 1; + u32 state_slot = m_state_slot + 1; if (state_slot > State::NUM_STATES) state_slot = 1; m_menu_bar->SetStateSlot(state_slot); @@ -1391,7 +1391,7 @@ void MainWindow::IncrementSelectedStateSlot() void MainWindow::DecrementSelectedStateSlot() { - int state_slot = m_state_slot - 1; + u32 state_slot = m_state_slot - 1; if (state_slot < 1) state_slot = State::NUM_STATES; m_menu_bar->SetStateSlot(state_slot); diff --git a/Source/Core/DolphinQt/MainWindow.h b/Source/Core/DolphinQt/MainWindow.h index 953b6b1811..fde91a8ae2 100644 --- a/Source/Core/DolphinQt/MainWindow.h +++ b/Source/Core/DolphinQt/MainWindow.h @@ -217,7 +217,7 @@ private: bool m_exit_requested = false; bool m_fullscreen_requested = false; bool m_is_screensaver_inhibited = false; - int m_state_slot = 1; + u32 m_state_slot = 1; std::unique_ptr m_pending_boot; ControllersWindow* m_controllers_window = nullptr; diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp index 31a8de1488..4dec815de9 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp @@ -61,7 +61,6 @@ #include "VideoCommon/NetPlayChatUI.h" #include "VideoCommon/NetPlayGolfUI.h" -#include "VideoCommon/RenderBase.h" #include "VideoCommon/VideoConfig.h" namespace diff --git a/Source/Core/DolphinQt/RenderWidget.cpp b/Source/Core/DolphinQt/RenderWidget.cpp index 891d3614c8..c39621ad73 100644 --- a/Source/Core/DolphinQt/RenderWidget.cpp +++ b/Source/Core/DolphinQt/RenderWidget.cpp @@ -19,8 +19,6 @@ #include #include -#include - #include "Core/Config/MainSettings.h" #include "Core/Core.h" #include "Core/State.h" @@ -32,7 +30,8 @@ #include "InputCommon/ControllerInterface/ControllerInterface.h" -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/OnScreenUI.h" +#include "VideoCommon/Present.h" #include "VideoCommon/VideoConfig.h" #ifdef _WIN32 @@ -62,7 +61,7 @@ RenderWidget::RenderWidget(QWidget* parent) : QWidget(parent) connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this](Core::State state) { if (state == Core::State::Running) - SetImGuiKeyMap(); + SetPresenterKeyMap(); }); // We have to use Qt::DirectConnection here because we don't want those signals to get queued @@ -338,7 +337,7 @@ void RenderWidget::SetWaitingForMessageBox(bool waiting_for_message_box) bool RenderWidget::event(QEvent* event) { - PassEventToImGui(event); + PassEventToPresenter(event); switch (event->type()) { @@ -470,7 +469,7 @@ bool RenderWidget::event(QEvent* event) return QWidget::event(event); } -void RenderWidget::PassEventToImGui(const QEvent* event) +void RenderWidget::PassEventToPresenter(const QEvent* event) { if (!Core::IsRunningAndStarted()) return; @@ -487,38 +486,40 @@ void RenderWidget::PassEventToImGui(const QEvent* event) const QKeyEvent* key_event = static_cast(event); const bool is_down = event->type() == QEvent::KeyPress; const u32 key = static_cast(key_event->key() & 0x1FF); - auto lock = g_renderer->GetImGuiLock(); - if (key < std::size(ImGui::GetIO().KeysDown)) - ImGui::GetIO().KeysDown[key] = is_down; + + const char* chars = nullptr; if (is_down) { auto utf8 = key_event->text().toUtf8(); - ImGui::GetIO().AddInputCharactersUTF8(utf8.constData()); + + if (utf8.size()) + chars = utf8.constData(); } + + // Pass the key onto Presenter (for the imgui UI) + g_presenter->SetKey(key, is_down, chars); } break; case QEvent::MouseMove: { - auto lock = g_renderer->GetImGuiLock(); - // Qt multiplies all coordinates by the scaling factor in highdpi mode, giving us "scaled" mouse // coordinates (as if the screen was standard dpi). We need to update the mouse position in // native coordinates, as the UI (and game) is rendered at native resolution. const float scale = devicePixelRatio(); - ImGui::GetIO().MousePos.x = static_cast(event)->pos().x() * scale; - ImGui::GetIO().MousePos.y = static_cast(event)->pos().y() * scale; + float x = static_cast(event)->pos().x() * scale; + float y = static_cast(event)->pos().y() * scale; + + g_presenter->SetMousePos(x, y); } break; case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: { - auto lock = g_renderer->GetImGuiLock(); const u32 button_mask = static_cast(static_cast(event)->buttons()); - for (size_t i = 0; i < std::size(ImGui::GetIO().MouseDown); i++) - ImGui::GetIO().MouseDown[i] = (button_mask & (1u << i)) != 0; + g_presenter->SetMousePress(button_mask); } break; @@ -527,36 +528,16 @@ void RenderWidget::PassEventToImGui(const QEvent* event) } } -void RenderWidget::SetImGuiKeyMap() +void RenderWidget::SetPresenterKeyMap() { - static constexpr std::array, 21> key_map{{ - {ImGuiKey_Tab, Qt::Key_Tab}, - {ImGuiKey_LeftArrow, Qt::Key_Left}, - {ImGuiKey_RightArrow, Qt::Key_Right}, - {ImGuiKey_UpArrow, Qt::Key_Up}, - {ImGuiKey_DownArrow, Qt::Key_Down}, - {ImGuiKey_PageUp, Qt::Key_PageUp}, - {ImGuiKey_PageDown, Qt::Key_PageDown}, - {ImGuiKey_Home, Qt::Key_Home}, - {ImGuiKey_End, Qt::Key_End}, - {ImGuiKey_Insert, Qt::Key_Insert}, - {ImGuiKey_Delete, Qt::Key_Delete}, - {ImGuiKey_Backspace, Qt::Key_Backspace}, - {ImGuiKey_Space, Qt::Key_Space}, - {ImGuiKey_Enter, Qt::Key_Return}, - {ImGuiKey_Escape, Qt::Key_Escape}, - {ImGuiKey_A, Qt::Key_A}, - {ImGuiKey_C, Qt::Key_C}, - {ImGuiKey_V, Qt::Key_V}, - {ImGuiKey_X, Qt::Key_X}, - {ImGuiKey_Y, Qt::Key_Y}, - {ImGuiKey_Z, Qt::Key_Z}, - }}; - auto lock = g_renderer->GetImGuiLock(); + static constexpr DolphinKeyMap key_map = { + Qt::Key_Tab, Qt::Key_Left, Qt::Key_Right, Qt::Key_Up, Qt::Key_Down, + Qt::Key_PageUp, Qt::Key_PageDown, Qt::Key_Home, Qt::Key_End, Qt::Key_Insert, + Qt::Key_Delete, Qt::Key_Backspace, Qt::Key_Space, Qt::Key_Return, Qt::Key_Escape, + Qt::Key_Enter, // Keypad enter + Qt::Key_A, Qt::Key_C, Qt::Key_V, Qt::Key_X, Qt::Key_Y, + Qt::Key_Z, + }; - if (!ImGui::GetCurrentContext()) - return; - - for (auto [imgui_key, qt_key] : key_map) - ImGui::GetIO().KeyMap[imgui_key] = (qt_key & 0x1FF); + g_presenter->SetKeyMap(key_map); } diff --git a/Source/Core/DolphinQt/RenderWidget.h b/Source/Core/DolphinQt/RenderWidget.h index c8ea1e3a84..87235a3986 100644 --- a/Source/Core/DolphinQt/RenderWidget.h +++ b/Source/Core/DolphinQt/RenderWidget.h @@ -39,8 +39,8 @@ private: void OnLockCursorChanged(); void OnKeepOnTopChanged(bool top); void UpdateCursor(); - void PassEventToImGui(const QEvent* event); - void SetImGuiKeyMap(); + void PassEventToPresenter(const QEvent* event); + void SetPresenterKeyMap(); void dragEnterEvent(QDragEnterEvent* event) override; void dropEvent(QDropEvent* event) override; diff --git a/Source/Core/DolphinQt/Settings.cpp b/Source/Core/DolphinQt/Settings.cpp index 26e108e80a..bfc2f36ea1 100644 --- a/Source/Core/DolphinQt/Settings.cpp +++ b/Source/Core/DolphinQt/Settings.cpp @@ -46,7 +46,6 @@ #include "VideoCommon/NetPlayChatUI.h" #include "VideoCommon/NetPlayGolfUI.h" -#include "VideoCommon/RenderBase.h" Settings::Settings() { diff --git a/Source/Core/DolphinTool/ToolHeadlessPlatform.cpp b/Source/Core/DolphinTool/ToolHeadlessPlatform.cpp index 4034df5ae0..2bed042148 100644 --- a/Source/Core/DolphinTool/ToolHeadlessPlatform.cpp +++ b/Source/Core/DolphinTool/ToolHeadlessPlatform.cpp @@ -15,7 +15,6 @@ #include "Core/Host.h" // Begin stubs needed to satisfy Core dependencies -#include "VideoCommon/RenderBase.h" std::vector Host_GetPreferredLocales() { diff --git a/Source/Core/InputCommon/DynamicInputTextureManager.cpp b/Source/Core/InputCommon/DynamicInputTextureManager.cpp index 7b7811801c..8a325f8f7e 100644 --- a/Source/Core/InputCommon/DynamicInputTextureManager.cpp +++ b/Source/Core/InputCommon/DynamicInputTextureManager.cpp @@ -13,7 +13,7 @@ #include "InputCommon/DynamicInputTextures/DITConfiguration.h" #include "VideoCommon/HiresTextures.h" -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/TextureCacheBase.h" namespace InputCommon { @@ -48,7 +48,7 @@ void DynamicInputTextureManager::GenerateTextures(const IniFile& file, any_dirty |= configuration.GenerateTextures(file, controller_names); } - if (any_dirty && g_renderer && Core::GetState() != Core::State::Starting) - g_renderer->ForceReloadTextures(); + if (any_dirty && g_texture_cache && Core::GetState() != Core::State::Starting) + g_texture_cache->ForceReloadTextures(); } } // namespace InputCommon diff --git a/Source/Core/VideoBackends/D3D/CMakeLists.txt b/Source/Core/VideoBackends/D3D/CMakeLists.txt index f296040526..ed75b29b77 100644 --- a/Source/Core/VideoBackends/D3D/CMakeLists.txt +++ b/Source/Core/VideoBackends/D3D/CMakeLists.txt @@ -7,8 +7,8 @@ add_library(videod3d D3DNativeVertexFormat.cpp D3DPerfQuery.cpp D3DPerfQuery.h - D3DRender.cpp - D3DRender.h + D3DGfx.cpp + D3DGfx.h D3DState.cpp D3DState.h D3DSwapChain.cpp diff --git a/Source/Core/VideoBackends/D3D/D3DRender.cpp b/Source/Core/VideoBackends/D3D/D3DGfx.cpp similarity index 62% rename from Source/Core/VideoBackends/D3D/D3DRender.cpp rename to Source/Core/VideoBackends/D3D/D3DGfx.cpp index 988f9fb6ad..35be881e01 100644 --- a/Source/Core/VideoBackends/D3D/D3DRender.cpp +++ b/Source/Core/VideoBackends/D3D/D3DGfx.cpp @@ -1,7 +1,7 @@ // Copyright 2010 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "VideoBackends/D3D/D3DRender.h" +#include "VideoBackends/D3D/D3DGfx.h" #include #include @@ -30,48 +30,46 @@ #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" namespace DX11 { -Renderer::Renderer(std::unique_ptr swap_chain, float backbuffer_scale) - : ::Renderer(swap_chain ? swap_chain->GetWidth() : 0, swap_chain ? swap_chain->GetHeight() : 0, - backbuffer_scale, - swap_chain ? swap_chain->GetFormat() : AbstractTextureFormat::Undefined), - m_swap_chain(std::move(swap_chain)) +Gfx::Gfx(std::unique_ptr swap_chain, float backbuffer_scale) + : m_backbuffer_scale(backbuffer_scale), m_swap_chain(std::move(swap_chain)) { } -Renderer::~Renderer() = default; +Gfx::~Gfx() = default; -bool Renderer::IsHeadless() const +bool Gfx::IsHeadless() const { return !m_swap_chain; } -std::unique_ptr Renderer::CreateTexture(const TextureConfig& config, - std::string_view name) +std::unique_ptr Gfx::CreateTexture(const TextureConfig& config, + std::string_view name) { return DXTexture::Create(config, name); } -std::unique_ptr Renderer::CreateStagingTexture(StagingTextureType type, - const TextureConfig& config) +std::unique_ptr Gfx::CreateStagingTexture(StagingTextureType type, + const TextureConfig& config) { return DXStagingTexture::Create(type, config); } -std::unique_ptr Renderer::CreateFramebuffer(AbstractTexture* color_attachment, - AbstractTexture* depth_attachment) +std::unique_ptr Gfx::CreateFramebuffer(AbstractTexture* color_attachment, + AbstractTexture* depth_attachment) { return DXFramebuffer::Create(static_cast(color_attachment), static_cast(depth_attachment)); } std::unique_ptr -Renderer::CreateShaderFromSource(ShaderStage stage, std::string_view source, std::string_view name) +Gfx::CreateShaderFromSource(ShaderStage stage, std::string_view source, std::string_view name) { auto bytecode = DXShader::CompileShader(D3D::feature_level, stage, source); if (!bytecode) @@ -80,21 +78,20 @@ Renderer::CreateShaderFromSource(ShaderStage stage, std::string_view source, std return DXShader::CreateFromBytecode(stage, std::move(*bytecode), name); } -std::unique_ptr Renderer::CreateShaderFromBinary(ShaderStage stage, - const void* data, size_t length, - std::string_view name) +std::unique_ptr Gfx::CreateShaderFromBinary(ShaderStage stage, const void* data, + size_t length, std::string_view name) { return DXShader::CreateFromBytecode(stage, DXShader::CreateByteCode(data, length), name); } -std::unique_ptr Renderer::CreatePipeline(const AbstractPipelineConfig& config, - const void* cache_data, - size_t cache_data_length) +std::unique_ptr Gfx::CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data, + size_t cache_data_length) { return DXPipeline::Create(config); } -void Renderer::SetPipeline(const AbstractPipeline* pipeline) +void Gfx::SetPipeline(const AbstractPipeline* pipeline) { const DXPipeline* dx_pipeline = static_cast(pipeline); if (m_current_pipeline == dx_pipeline) @@ -122,7 +119,7 @@ void Renderer::SetPipeline(const AbstractPipeline* pipeline) } } -void Renderer::SetScissorRect(const MathUtil::Rectangle& rc) +void Gfx::SetScissorRect(const MathUtil::Rectangle& rc) { // TODO: Move to stateman const CD3D11_RECT rect(rc.left, rc.top, std::max(rc.right, rc.left + 1), @@ -130,75 +127,75 @@ void Renderer::SetScissorRect(const MathUtil::Rectangle& rc) D3D::context->RSSetScissorRects(1, &rect); } -void Renderer::SetViewport(float x, float y, float width, float height, float near_depth, - float far_depth) +void Gfx::SetViewport(float x, float y, float width, float height, float near_depth, + float far_depth) { // TODO: Move to stateman const CD3D11_VIEWPORT vp(x, y, width, height, near_depth, far_depth); D3D::context->RSSetViewports(1, &vp); } -void Renderer::Draw(u32 base_vertex, u32 num_vertices) +void Gfx::Draw(u32 base_vertex, u32 num_vertices) { D3D::stateman->Apply(); D3D::context->Draw(num_vertices, base_vertex); } -void Renderer::DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) +void Gfx::DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) { D3D::stateman->Apply(); D3D::context->DrawIndexed(num_indices, base_index, base_vertex); } -void Renderer::DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u32 groupsize_y, - u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z) +void Gfx::DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u32 groupsize_y, + u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z) { D3D::stateman->SetComputeShader(static_cast(shader)->GetD3DComputeShader()); D3D::stateman->SyncComputeBindings(); D3D::context->Dispatch(groups_x, groups_y, groups_z); } -void Renderer::BindBackbuffer(const ClearColor& clear_color) +void Gfx::BindBackbuffer(const ClearColor& clear_color) { CheckForSwapChainChanges(); SetAndClearFramebuffer(m_swap_chain->GetFramebuffer(), clear_color); } -void Renderer::PresentBackbuffer() +void Gfx::PresentBackbuffer() { m_swap_chain->Present(); } -void Renderer::OnConfigChanged(u32 bits) +void Gfx::OnConfigChanged(u32 bits) { + AbstractGfx::OnConfigChanged(bits); + // Quad-buffer changes require swap chain recreation. if (bits & CONFIG_CHANGE_BIT_STEREO_MODE && m_swap_chain) m_swap_chain->SetStereo(SwapChain::WantsStereo()); } -void Renderer::CheckForSwapChainChanges() +void Gfx::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 { m_swap_chain->ResizeSwapChain(); } - 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::SetFramebuffer(AbstractFramebuffer* framebuffer) +void Gfx::SetFramebuffer(AbstractFramebuffer* framebuffer) { if (m_current_framebuffer == framebuffer) return; @@ -219,13 +216,13 @@ void Renderer::SetFramebuffer(AbstractFramebuffer* framebuffer) m_current_framebuffer = fb; } -void Renderer::SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer) +void Gfx::SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer) { SetFramebuffer(framebuffer); } -void Renderer::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, - const ClearColor& color_value, float depth_value) +void Gfx::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, const ClearColor& color_value, + float depth_value) { SetFramebuffer(framebuffer); D3D::stateman->Apply(); @@ -242,53 +239,55 @@ void Renderer::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, } } -void Renderer::SetTexture(u32 index, const AbstractTexture* texture) +void Gfx::SetTexture(u32 index, const AbstractTexture* texture) { D3D::stateman->SetTexture(index, texture ? static_cast(texture)->GetD3DSRV() : nullptr); } -void Renderer::SetSamplerState(u32 index, const SamplerState& state) +void Gfx::SetSamplerState(u32 index, const SamplerState& state) { D3D::stateman->SetSampler(index, m_state_cache.Get(state)); } -void Renderer::SetComputeImageTexture(AbstractTexture* texture, bool read, bool write) +void Gfx::SetComputeImageTexture(AbstractTexture* texture, bool read, bool write) { D3D::stateman->SetComputeUAV(texture ? static_cast(texture)->GetD3DUAV() : nullptr); } -void Renderer::UnbindTexture(const AbstractTexture* texture) +void Gfx::UnbindTexture(const AbstractTexture* texture) { if (D3D::stateman->UnsetTexture(static_cast(texture)->GetD3DSRV()) != 0) D3D::stateman->ApplyTextures(); } -std::unique_ptr Renderer::CreateBoundingBox() const -{ - return std::make_unique(); -} - -void Renderer::Flush() +void Gfx::Flush() { D3D::context->Flush(); } -void Renderer::WaitForGPUIdle() +void Gfx::WaitForGPUIdle() { // There is no glFinish() equivalent in D3D. D3D::context->Flush(); } -void Renderer::SetFullscreen(bool enable_fullscreen) +void Gfx::SetFullscreen(bool enable_fullscreen) { if (m_swap_chain) m_swap_chain->SetFullscreen(enable_fullscreen); } -bool Renderer::IsFullscreen() const +bool Gfx::IsFullscreen() const { return m_swap_chain && m_swap_chain->GetFullscreen(); } +SurfaceInfo Gfx::GetSurfaceInfo() const +{ + return {m_swap_chain ? static_cast(m_swap_chain->GetWidth()) : 0, + m_swap_chain ? static_cast(m_swap_chain->GetHeight()) : 0, m_backbuffer_scale, + m_swap_chain ? m_swap_chain->GetFormat() : AbstractTextureFormat::Undefined}; +} + } // namespace DX11 diff --git a/Source/Core/VideoBackends/D3D/D3DRender.h b/Source/Core/VideoBackends/D3D/D3DGfx.h similarity index 92% rename from Source/Core/VideoBackends/D3D/D3DRender.h rename to Source/Core/VideoBackends/D3D/D3DGfx.h index a0d25bc270..853599224c 100644 --- a/Source/Core/VideoBackends/D3D/D3DRender.h +++ b/Source/Core/VideoBackends/D3D/D3DGfx.h @@ -6,7 +6,7 @@ #include #include #include "VideoBackends/D3D/D3DState.h" -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/AbstractGfx.h" class BoundingBox; @@ -16,11 +16,11 @@ class SwapChain; class DXTexture; class DXFramebuffer; -class Renderer : public ::Renderer +class Gfx final : public ::AbstractGfx { public: - Renderer(std::unique_ptr swap_chain, float backbuffer_scale); - ~Renderer() override; + Gfx(std::unique_ptr swap_chain, float backbuffer_scale); + ~Gfx() override; StateCache& GetStateCache() { return m_state_cache; } @@ -69,14 +69,14 @@ public: void OnConfigChanged(u32 bits) override; -protected: - std::unique_ptr CreateBoundingBox() const override; + SurfaceInfo GetSurfaceInfo() const override; private: void CheckForSwapChainChanges(); StateCache m_state_cache; + float m_backbuffer_scale; std::unique_ptr m_swap_chain; }; } // namespace DX11 diff --git a/Source/Core/VideoBackends/D3D/D3DMain.cpp b/Source/Core/VideoBackends/D3D/D3DMain.cpp index 755f0cf590..8256674ad5 100644 --- a/Source/Core/VideoBackends/D3D/D3DMain.cpp +++ b/Source/Core/VideoBackends/D3D/D3DMain.cpp @@ -13,12 +13,13 @@ #include "VideoBackends/D3D/D3DBase.h" #include "VideoBackends/D3D/D3DBoundingBox.h" +#include "VideoBackends/D3D/D3DGfx.h" #include "VideoBackends/D3D/D3DPerfQuery.h" -#include "VideoBackends/D3D/D3DRender.h" #include "VideoBackends/D3D/D3DSwapChain.h" #include "VideoBackends/D3D/D3DVertexManager.h" #include "VideoBackends/D3DCommon/D3DCommon.h" +#include "VideoCommon/AbstractGfx.h" #include "VideoCommon/FramebufferManager.h" #include "VideoCommon/ShaderCache.h" #include "VideoCommon/TextureCacheBase.h" @@ -143,7 +144,7 @@ bool VideoBackend::Initialize(const WindowSystemInfo& wsi) return false; FillBackendInfo(); - InitializeShared(); + UpdateActiveConfig(); std::unique_ptr swap_chain; if (wsi.render_surface && !(swap_chain = SwapChain::Create(wsi))) @@ -154,36 +155,17 @@ bool VideoBackend::Initialize(const WindowSystemInfo& wsi) return false; } - g_renderer = std::make_unique(std::move(swap_chain), wsi.render_surface_scale); - g_vertex_manager = std::make_unique(); - g_shader_cache = std::make_unique(); - g_framebuffer_manager = std::make_unique(); - g_texture_cache = std::make_unique(); - g_perf_query = std::make_unique(); - if (!g_vertex_manager->Initialize() || !g_shader_cache->Initialize() || - !g_renderer->Initialize() || !g_framebuffer_manager->Initialize() || - !g_texture_cache->Initialize()) - { - Shutdown(); - return false; - } + auto gfx = std::make_unique(std::move(swap_chain), wsi.render_surface_scale); + auto vertex_manager = std::make_unique(); + auto perf_query = std::make_unique(); + auto bounding_box = std::make_unique(); - g_shader_cache->InitializeShaderCache(); - return true; + return InitializeShared(std::move(gfx), std::move(vertex_manager), std::move(perf_query), + std::move(bounding_box)); } void VideoBackend::Shutdown() { - g_shader_cache->Shutdown(); - g_renderer->Shutdown(); - - g_perf_query.reset(); - g_texture_cache.reset(); - g_framebuffer_manager.reset(); - g_shader_cache.reset(); - g_vertex_manager.reset(); - g_renderer.reset(); - ShutdownShared(); D3D::Destroy(); } diff --git a/Source/Core/VideoBackends/D3D/D3DNativeVertexFormat.cpp b/Source/Core/VideoBackends/D3D/D3DNativeVertexFormat.cpp index aca5a177fa..54c1ae8220 100644 --- a/Source/Core/VideoBackends/D3D/D3DNativeVertexFormat.cpp +++ b/Source/Core/VideoBackends/D3D/D3DNativeVertexFormat.cpp @@ -7,7 +7,7 @@ #include "Common/EnumMap.h" #include "VideoBackends/D3D/D3DBase.h" -#include "VideoBackends/D3D/D3DRender.h" +#include "VideoBackends/D3D/D3DGfx.h" #include "VideoBackends/D3D/D3DState.h" #include "VideoBackends/D3D/D3DVertexManager.h" #include "VideoBackends/D3D/DXShader.h" @@ -18,7 +18,7 @@ namespace DX11 std::mutex s_input_layout_lock; std::unique_ptr -Renderer::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) +Gfx::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) { return std::make_unique(vtx_decl); } diff --git a/Source/Core/VideoBackends/D3D/D3DPerfQuery.cpp b/Source/Core/VideoBackends/D3D/D3DPerfQuery.cpp index e1535f7c32..fe34a8ff45 100644 --- a/Source/Core/VideoBackends/D3D/D3DPerfQuery.cpp +++ b/Source/Core/VideoBackends/D3D/D3DPerfQuery.cpp @@ -6,7 +6,7 @@ #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" #include "VideoBackends/D3D/D3DBase.h" -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/FramebufferManager.h" #include "VideoCommon/VideoCommon.h" namespace DX11 @@ -114,8 +114,8 @@ void PerfQuery::FlushOne() // NOTE: Reported pixel metrics should be referenced to native resolution // TODO: Dropping the lower 2 bits from this count should be closer to actual // hardware behavior when drawing triangles. - const u64 native_res_result = result * EFB_WIDTH / g_renderer->GetTargetWidth() * EFB_HEIGHT / - g_renderer->GetTargetHeight(); + const u64 native_res_result = result * EFB_WIDTH / g_framebuffer_manager->GetEFBWidth() * + EFB_HEIGHT / g_framebuffer_manager->GetEFBHeight(); m_results[entry.query_group].fetch_add(static_cast(native_res_result), std::memory_order_relaxed); @@ -143,8 +143,8 @@ void PerfQuery::WeakFlush() if (hr == S_OK) { // NOTE: Reported pixel metrics should be referenced to native resolution - const u64 native_res_result = result * EFB_WIDTH / g_renderer->GetTargetWidth() * EFB_HEIGHT / - g_renderer->GetTargetHeight(); + const u64 native_res_result = result * EFB_WIDTH / g_framebuffer_manager->GetEFBWidth() * + EFB_HEIGHT / g_framebuffer_manager->GetEFBHeight(); m_results[entry.query_group].store(static_cast(native_res_result), std::memory_order_relaxed); diff --git a/Source/Core/VideoBackends/D3D/D3DVertexManager.cpp b/Source/Core/VideoBackends/D3D/D3DVertexManager.cpp index ce41e90e31..4aa1c40266 100644 --- a/Source/Core/VideoBackends/D3D/D3DVertexManager.cpp +++ b/Source/Core/VideoBackends/D3D/D3DVertexManager.cpp @@ -13,7 +13,7 @@ #include "VideoBackends/D3D/D3DBase.h" #include "VideoBackends/D3D/D3DBoundingBox.h" -#include "VideoBackends/D3D/D3DRender.h" +#include "VideoBackends/D3D/D3DGfx.h" #include "VideoBackends/D3D/D3DState.h" #include "VideoBackends/D3DCommon/D3DCommon.h" diff --git a/Source/Core/VideoBackends/D3D/DXPipeline.cpp b/Source/Core/VideoBackends/D3D/DXPipeline.cpp index 0bdfeb0012..3553e8d512 100644 --- a/Source/Core/VideoBackends/D3D/DXPipeline.cpp +++ b/Source/Core/VideoBackends/D3D/DXPipeline.cpp @@ -7,7 +7,7 @@ #include "Common/Logging/Log.h" #include "VideoBackends/D3D/D3DBase.h" -#include "VideoBackends/D3D/D3DRender.h" +#include "VideoBackends/D3D/D3DGfx.h" #include "VideoBackends/D3D/D3DState.h" #include "VideoBackends/D3D/D3DVertexManager.h" #include "VideoBackends/D3D/DXShader.h" @@ -32,7 +32,7 @@ DXPipeline::~DXPipeline() = default; std::unique_ptr DXPipeline::Create(const AbstractPipelineConfig& config) { - StateCache& state_cache = static_cast(g_renderer.get())->GetStateCache(); + StateCache& state_cache = static_cast(g_gfx.get())->GetStateCache(); ID3D11RasterizerState* rasterizer_state = state_cache.Get(config.rasterization_state); ID3D11DepthStencilState* depth_state = state_cache.Get(config.depth_state); ID3D11BlendState* blend_state = state_cache.Get(config.blending_state); diff --git a/Source/Core/VideoBackends/D3D12/CMakeLists.txt b/Source/Core/VideoBackends/D3D12/CMakeLists.txt index 16f458e2e9..bc89b89df3 100644 --- a/Source/Core/VideoBackends/D3D12/CMakeLists.txt +++ b/Source/Core/VideoBackends/D3D12/CMakeLists.txt @@ -3,8 +3,8 @@ add_library(videod3d12 D3D12BoundingBox.h D3D12PerfQuery.cpp D3D12PerfQuery.h - D3D12Renderer.cpp - D3D12Renderer.h + D3D12Gfx.cpp + D3D12Gfx.h D3D12StreamBuffer.cpp D3D12StreamBuffer.h D3D12SwapChain.cpp diff --git a/Source/Core/VideoBackends/D3D12/D3D12BoundingBox.cpp b/Source/Core/VideoBackends/D3D12/D3D12BoundingBox.cpp index 4eec9f2e22..1627e39343 100644 --- a/Source/Core/VideoBackends/D3D12/D3D12BoundingBox.cpp +++ b/Source/Core/VideoBackends/D3D12/D3D12BoundingBox.cpp @@ -6,7 +6,7 @@ #include "Common/Assert.h" #include "Common/Logging/Log.h" -#include "VideoBackends/D3D12/D3D12Renderer.h" +#include "VideoBackends/D3D12/D3D12Gfx.h" #include "VideoBackends/D3D12/DX12Context.h" namespace DX12 @@ -22,7 +22,7 @@ bool D3D12BoundingBox::Initialize() if (!CreateBuffers()) return false; - Renderer::GetInstance()->SetPixelShaderUAV(m_gpu_descriptor.cpu_handle); + Gfx::GetInstance()->SetPixelShaderUAV(m_gpu_descriptor.cpu_handle); return true; } @@ -35,7 +35,7 @@ std::vector D3D12BoundingBox::Read(u32 index, u32 length) 0, BUFFER_SIZE); ResourceBarrier(g_dx_context->GetCommandList(), m_gpu_buffer.Get(), D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); - Renderer::GetInstance()->ExecuteCommandList(true); + Gfx::GetInstance()->ExecuteCommandList(true); // Read back to cached values. std::vector values(length); @@ -62,7 +62,7 @@ void D3D12BoundingBox::Write(u32 index, const std::vector& values) if (!m_upload_buffer.ReserveMemory(copy_size, sizeof(BBoxType))) { WARN_LOG_FMT(VIDEO, "Executing command list while waiting for space in bbox stream buffer"); - Renderer::GetInstance()->ExecuteCommandList(false); + Gfx::GetInstance()->ExecuteCommandList(false); if (!m_upload_buffer.ReserveMemory(copy_size, sizeof(BBoxType))) { PanicAlertFmt("Failed to allocate bbox stream buffer space"); diff --git a/Source/Core/VideoBackends/D3D12/D3D12Renderer.cpp b/Source/Core/VideoBackends/D3D12/D3D12Gfx.cpp similarity index 80% rename from Source/Core/VideoBackends/D3D12/D3D12Renderer.cpp rename to Source/Core/VideoBackends/D3D12/D3D12Gfx.cpp index f4ef717c07..b3ade2e5d3 100644 --- a/Source/Core/VideoBackends/D3D12/D3D12Renderer.cpp +++ b/Source/Core/VideoBackends/D3D12/D3D12Gfx.cpp @@ -1,7 +1,7 @@ // Copyright 2019 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "VideoBackends/D3D12/D3D12Renderer.h" +#include "VideoBackends/D3D12/D3D12Gfx.h" #include "Common/Logging/Log.h" @@ -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 @@ -27,11 +28,8 @@ static bool UsesDynamicVertexLoader(const AbstractPipeline* pipeline) (g_ActiveConfig.UseVSForLinePointExpand() && usage != AbstractPipelineUsage::Utility); } -Renderer::Renderer(std::unique_ptr swap_chain, float backbuffer_scale) - : ::Renderer(swap_chain ? swap_chain->GetWidth() : 0, swap_chain ? swap_chain->GetHeight() : 0, - backbuffer_scale, - swap_chain ? swap_chain->GetFormat() : AbstractTextureFormat::Undefined), - m_swap_chain(std::move(swap_chain)) +Gfx::Gfx(std::unique_ptr swap_chain, float backbuffer_scale) + : m_backbuffer_scale(backbuffer_scale), m_swap_chain(std::move(swap_chain)) { m_state.root_signature = g_dx_context->GetGXRootSignature(); @@ -43,98 +41,75 @@ Renderer::Renderer(std::unique_ptr swap_chain, float backbuffer_scale } } -Renderer::~Renderer() = default; +Gfx::~Gfx() = default; -bool Renderer::IsHeadless() const +bool Gfx::IsHeadless() const { return !m_swap_chain; } -bool Renderer::Initialize() -{ - if (!::Renderer::Initialize()) - return false; - - return true; -} - -void Renderer::Shutdown() -{ - m_swap_chain.reset(); - - ::Renderer::Shutdown(); -} - -std::unique_ptr Renderer::CreateTexture(const TextureConfig& config, - std::string_view name) +std::unique_ptr Gfx::CreateTexture(const TextureConfig& config, + std::string_view name) { return DXTexture::Create(config, name); } -std::unique_ptr Renderer::CreateStagingTexture(StagingTextureType type, - const TextureConfig& config) +std::unique_ptr Gfx::CreateStagingTexture(StagingTextureType type, + const TextureConfig& config) { return DXStagingTexture::Create(type, config); } -std::unique_ptr Renderer::CreateFramebuffer(AbstractTexture* color_attachment, - AbstractTexture* depth_attachment) +std::unique_ptr Gfx::CreateFramebuffer(AbstractTexture* color_attachment, + AbstractTexture* depth_attachment) { return DXFramebuffer::Create(static_cast(color_attachment), static_cast(depth_attachment)); } std::unique_ptr -Renderer::CreateShaderFromSource(ShaderStage stage, std::string_view source, std::string_view name) +Gfx::CreateShaderFromSource(ShaderStage stage, std::string_view source, std::string_view name) { return DXShader::CreateFromSource(stage, source, name); } -std::unique_ptr Renderer::CreateShaderFromBinary(ShaderStage stage, - const void* data, size_t length, - std::string_view name) +std::unique_ptr Gfx::CreateShaderFromBinary(ShaderStage stage, const void* data, + size_t length, std::string_view name) { return DXShader::CreateFromBytecode(stage, DXShader::CreateByteCode(data, length), name); } std::unique_ptr -Renderer::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) +Gfx::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) { return std::make_unique(vtx_decl); } -std::unique_ptr Renderer::CreatePipeline(const AbstractPipelineConfig& config, - const void* cache_data, - size_t cache_data_length) +std::unique_ptr Gfx::CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data, + size_t cache_data_length) { return DXPipeline::Create(config, cache_data, cache_data_length); } -std::unique_ptr Renderer::CreateBoundingBox() const -{ - return std::make_unique(); -} - -void Renderer::Flush() +void Gfx::Flush() { ExecuteCommandList(false); } -void Renderer::WaitForGPUIdle() +void Gfx::WaitForGPUIdle() { ExecuteCommandList(true); } -void Renderer::ClearScreen(const MathUtil::Rectangle& rc, bool color_enable, bool alpha_enable, - bool z_enable, u32 color, u32 z) +void Gfx::ClearRegion(const MathUtil::Rectangle& target_rc, bool color_enable, + bool alpha_enable, bool z_enable, u32 color, u32 z) { // Use a fast path without the shader if both color/alpha are enabled. - const bool fast_color_clear = color_enable && (alpha_enable || !EFBHasAlphaChannel()); + const bool fast_color_clear = color_enable && alpha_enable; if (fast_color_clear || z_enable) { - MathUtil::Rectangle native_rc = ConvertEFBRectangle(rc); - native_rc.ClampUL(0, 0, m_current_framebuffer->GetWidth(), m_current_framebuffer->GetHeight()); - const D3D12_RECT d3d_clear_rc{native_rc.left, native_rc.top, native_rc.right, native_rc.bottom}; + const D3D12_RECT d3d_clear_rc{target_rc.left, target_rc.top, target_rc.right, target_rc.bottom}; if (fast_color_clear) { @@ -169,10 +144,10 @@ void Renderer::ClearScreen(const MathUtil::Rectangle& rc, bool color_enable // Anything left over, fall back to clear triangle. if (color_enable || alpha_enable || z_enable) - ::Renderer::ClearScreen(rc, color_enable, alpha_enable, z_enable, color, z); + ::AbstractGfx::ClearRegion(target_rc, color_enable, alpha_enable, z_enable, color, z); } -void Renderer::SetPipeline(const AbstractPipeline* pipeline) +void Gfx::SetPipeline(const AbstractPipeline* pipeline) { const DXPipeline* dx_pipeline = static_cast(pipeline); if (m_current_pipeline == dx_pipeline) @@ -204,7 +179,7 @@ void Renderer::SetPipeline(const AbstractPipeline* pipeline) } } -void Renderer::BindFramebuffer(DXFramebuffer* fb) +void Gfx::BindFramebuffer(DXFramebuffer* fb) { if (fb->HasColorBuffer()) { @@ -225,7 +200,7 @@ void Renderer::BindFramebuffer(DXFramebuffer* fb) m_dirty_bits &= ~DirtyState_Framebuffer; } -void Renderer::SetFramebuffer(AbstractFramebuffer* framebuffer) +void Gfx::SetFramebuffer(AbstractFramebuffer* framebuffer) { if (m_current_framebuffer == framebuffer) return; @@ -234,7 +209,7 @@ void Renderer::SetFramebuffer(AbstractFramebuffer* framebuffer) m_dirty_bits |= DirtyState_Framebuffer; } -void Renderer::SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer) +void Gfx::SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer) { SetFramebuffer(framebuffer); @@ -253,8 +228,8 @@ void Renderer::SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer) } } -void Renderer::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, - const ClearColor& color_value, float depth_value) +void Gfx::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, const ClearColor& color_value, + float depth_value) { DXFramebuffer* dxfb = static_cast(framebuffer); BindFramebuffer(dxfb); @@ -272,7 +247,7 @@ void Renderer::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, } } -void Renderer::SetScissorRect(const MathUtil::Rectangle& rc) +void Gfx::SetScissorRect(const MathUtil::Rectangle& rc) { if (m_state.scissor.left == rc.left && m_state.scissor.right == rc.right && m_state.scissor.top == rc.top && m_state.scissor.bottom == rc.bottom) @@ -287,7 +262,7 @@ void Renderer::SetScissorRect(const MathUtil::Rectangle& rc) m_dirty_bits |= DirtyState_ScissorRect; } -void Renderer::SetTexture(u32 index, const AbstractTexture* texture) +void Gfx::SetTexture(u32 index, const AbstractTexture* texture) { const DXTexture* dxtex = static_cast(texture); if (m_state.textures[index].ptr == dxtex->GetSRVDescriptor().cpu_handle.ptr) @@ -300,7 +275,7 @@ void Renderer::SetTexture(u32 index, const AbstractTexture* texture) m_dirty_bits |= DirtyState_Textures; } -void Renderer::SetSamplerState(u32 index, const SamplerState& state) +void Gfx::SetSamplerState(u32 index, const SamplerState& state) { if (m_state.samplers.states[index] == state) return; @@ -309,7 +284,7 @@ void Renderer::SetSamplerState(u32 index, const SamplerState& state) m_dirty_bits |= DirtyState_Samplers; } -void Renderer::SetComputeImageTexture(AbstractTexture* texture, bool read, bool write) +void Gfx::SetComputeImageTexture(AbstractTexture* texture, bool read, bool write) { const DXTexture* dxtex = static_cast(texture); if (m_state.compute_image_texture == dxtex) @@ -322,7 +297,7 @@ void Renderer::SetComputeImageTexture(AbstractTexture* texture, bool read, bool m_dirty_bits |= DirtyState_ComputeImageTexture; } -void Renderer::UnbindTexture(const AbstractTexture* texture) +void Gfx::UnbindTexture(const AbstractTexture* texture) { const auto srv_shadow_descriptor = static_cast(texture)->GetSRVDescriptor().cpu_handle; @@ -341,8 +316,8 @@ void Renderer::UnbindTexture(const AbstractTexture* texture) } } -void Renderer::SetViewport(float x, float y, float width, float height, float near_depth, - float far_depth) +void Gfx::SetViewport(float x, float y, float width, float height, float near_depth, + float far_depth) { if (m_state.viewport.TopLeftX == x && m_state.viewport.TopLeftY == y && m_state.viewport.Width == width && m_state.viewport.Height == height && @@ -360,7 +335,7 @@ void Renderer::SetViewport(float x, float y, float width, float height, float ne m_dirty_bits |= DirtyState_Viewport; } -void Renderer::Draw(u32 base_vertex, u32 num_vertices) +void Gfx::Draw(u32 base_vertex, u32 num_vertices) { if (!ApplyState()) return; @@ -368,7 +343,7 @@ void Renderer::Draw(u32 base_vertex, u32 num_vertices) g_dx_context->GetCommandList()->DrawInstanced(num_vertices, 1, base_vertex, 0); } -void Renderer::DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) +void Gfx::DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) { if (!ApplyState()) return; @@ -380,8 +355,8 @@ void Renderer::DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) g_dx_context->GetCommandList()->DrawIndexedInstanced(num_indices, 1, base_index, base_vertex, 0); } -void Renderer::DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u32 groupsize_y, - u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z) +void Gfx::DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u32 groupsize_y, + u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z) { SetRootSignatures(); SetDescriptorHeaps(); @@ -411,17 +386,17 @@ void Renderer::DispatchComputeShader(const AbstractShader* shader, u32 groupsize m_dirty_bits |= DirtyState_Pipeline; } -void Renderer::BindBackbuffer(const ClearColor& clear_color) +void Gfx::BindBackbuffer(const ClearColor& clear_color) { CheckForSwapChainChanges(); SetAndClearFramebuffer(m_swap_chain->GetCurrentFramebuffer(), clear_color); } -void Renderer::CheckForSwapChainChanges() +void Gfx::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,19 +404,17 @@ 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 { m_swap_chain->ResizeSwapChain(); } - 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::PresentBackbuffer() +void Gfx::PresentBackbuffer() { m_current_framebuffer = nullptr; @@ -451,9 +424,16 @@ void Renderer::PresentBackbuffer() m_swap_chain->Present(); } -void Renderer::OnConfigChanged(u32 bits) +SurfaceInfo Gfx::GetSurfaceInfo() const { - ::Renderer::OnConfigChanged(bits); + return {m_swap_chain ? static_cast(m_swap_chain->GetWidth()) : 0, + m_swap_chain ? static_cast(m_swap_chain->GetHeight()) : 0, m_backbuffer_scale, + m_swap_chain ? m_swap_chain->GetFormat() : AbstractTextureFormat::Undefined}; +} + +void Gfx::OnConfigChanged(u32 bits) +{ + AbstractGfx::OnConfigChanged(bits); // For quad-buffered stereo we need to change the layer count, so recreate the swap chain. if (m_swap_chain && bits & CONFIG_CHANGE_BIT_STEREO_MODE) @@ -475,14 +455,14 @@ void Renderer::OnConfigChanged(u32 bits) g_dx_context->RecreateGXRootSignature(); } -void Renderer::ExecuteCommandList(bool wait_for_completion) +void Gfx::ExecuteCommandList(bool wait_for_completion) { PerfQuery::GetInstance()->ResolveQueries(); g_dx_context->ExecuteCommandList(wait_for_completion); m_dirty_bits = DirtyState_All; } -void Renderer::SetConstantBuffer(u32 index, D3D12_GPU_VIRTUAL_ADDRESS address) +void Gfx::SetConstantBuffer(u32 index, D3D12_GPU_VIRTUAL_ADDRESS address) { if (m_state.constant_buffers[index] == address) return; @@ -491,7 +471,7 @@ void Renderer::SetConstantBuffer(u32 index, D3D12_GPU_VIRTUAL_ADDRESS address) m_dirty_bits |= DirtyState_PS_CBV << index; } -void Renderer::SetTextureDescriptor(u32 index, D3D12_CPU_DESCRIPTOR_HANDLE handle) +void Gfx::SetTextureDescriptor(u32 index, D3D12_CPU_DESCRIPTOR_HANDLE handle) { if (m_state.textures[index].ptr == handle.ptr) return; @@ -500,7 +480,7 @@ void Renderer::SetTextureDescriptor(u32 index, D3D12_CPU_DESCRIPTOR_HANDLE handl m_dirty_bits |= DirtyState_Textures; } -void Renderer::SetPixelShaderUAV(D3D12_CPU_DESCRIPTOR_HANDLE handle) +void Gfx::SetPixelShaderUAV(D3D12_CPU_DESCRIPTOR_HANDLE handle) { if (m_state.ps_uav.ptr == handle.ptr) return; @@ -509,8 +489,8 @@ void Renderer::SetPixelShaderUAV(D3D12_CPU_DESCRIPTOR_HANDLE handle) m_dirty_bits |= DirtyState_PS_UAV; } -void Renderer::SetVertexBuffer(D3D12_GPU_VIRTUAL_ADDRESS address, D3D12_CPU_DESCRIPTOR_HANDLE srv, - u32 stride, u32 size) +void Gfx::SetVertexBuffer(D3D12_GPU_VIRTUAL_ADDRESS address, D3D12_CPU_DESCRIPTOR_HANDLE srv, + u32 stride, u32 size) { if (m_state.vertex_buffer.BufferLocation != address || m_state.vertex_buffer.StrideInBytes != stride || m_state.vertex_buffer.SizeInBytes != size) @@ -527,7 +507,7 @@ void Renderer::SetVertexBuffer(D3D12_GPU_VIRTUAL_ADDRESS address, D3D12_CPU_DESC } } -void Renderer::SetIndexBuffer(D3D12_GPU_VIRTUAL_ADDRESS address, u32 size, DXGI_FORMAT format) +void Gfx::SetIndexBuffer(D3D12_GPU_VIRTUAL_ADDRESS address, u32 size, DXGI_FORMAT format) { if (m_state.index_buffer.BufferLocation == address && m_state.index_buffer.SizeInBytes == size && m_state.index_buffer.Format == format) @@ -541,7 +521,7 @@ void Renderer::SetIndexBuffer(D3D12_GPU_VIRTUAL_ADDRESS address, u32 size, DXGI_ m_dirty_bits |= DirtyState_IndexBuffer; } -bool Renderer::ApplyState() +bool Gfx::ApplyState() { if (!m_current_framebuffer || !m_current_pipeline) return false; @@ -637,7 +617,7 @@ bool Renderer::ApplyState() return true; } -void Renderer::SetRootSignatures() +void Gfx::SetRootSignatures() { const u32 dirty_bits = m_dirty_bits; if (dirty_bits & DirtyState_RootSignature) @@ -650,7 +630,7 @@ void Renderer::SetRootSignatures() m_dirty_bits &= ~(DirtyState_RootSignature | DirtyState_ComputeRootSignature); } -void Renderer::SetDescriptorHeaps() +void Gfx::SetDescriptorHeaps() { if (m_dirty_bits & DirtyState_DescriptorHeaps) { @@ -660,7 +640,7 @@ void Renderer::SetDescriptorHeaps() } } -void Renderer::UpdateDescriptorTables() +void Gfx::UpdateDescriptorTables() { // Samplers force a full sync because any of the samplers could be in use. const bool texture_update_failed = @@ -684,7 +664,7 @@ void Renderer::UpdateDescriptorTables() } } -bool Renderer::UpdateSRVDescriptorTable() +bool Gfx::UpdateSRVDescriptorTable() { static constexpr std::array src_sizes = {1, 1, 1, 1, 1, 1, 1, 1}; DescriptorHandle dst_base_handle; @@ -700,7 +680,7 @@ bool Renderer::UpdateSRVDescriptorTable() return true; } -bool Renderer::UpdateSamplerDescriptorTable() +bool Gfx::UpdateSamplerDescriptorTable() { if (!g_dx_context->GetSamplerAllocator()->GetGroupHandle(m_state.samplers, &m_state.sampler_descriptor_base)) @@ -713,7 +693,7 @@ bool Renderer::UpdateSamplerDescriptorTable() return true; } -bool Renderer::UpdateUAVDescriptorTable() +bool Gfx::UpdateUAVDescriptorTable() { // We can skip writing the UAV descriptor if bbox isn't enabled, since it's not used otherwise. if (!g_ActiveConfig.bBBoxEnable) @@ -730,7 +710,7 @@ bool Renderer::UpdateUAVDescriptorTable() return true; } -bool Renderer::UpdateVSSRVDescriptorTable() +bool Gfx::UpdateVSSRVDescriptorTable() { if (!UsesDynamicVertexLoader(m_current_pipeline)) { @@ -748,7 +728,7 @@ bool Renderer::UpdateVSSRVDescriptorTable() return true; } -bool Renderer::UpdateComputeUAVDescriptorTable() +bool Gfx::UpdateComputeUAVDescriptorTable() { DescriptorHandle handle; if (!g_dx_context->GetDescriptorAllocator()->Allocate(1, &handle)) diff --git a/Source/Core/VideoBackends/D3D12/D3D12Renderer.h b/Source/Core/VideoBackends/D3D12/D3D12Gfx.h similarity index 93% rename from Source/Core/VideoBackends/D3D12/D3D12Renderer.h rename to Source/Core/VideoBackends/D3D12/D3D12Gfx.h index 19188a5acf..95774dd71d 100644 --- a/Source/Core/VideoBackends/D3D12/D3D12Renderer.h +++ b/Source/Core/VideoBackends/D3D12/D3D12Gfx.h @@ -6,9 +6,7 @@ #include #include "VideoBackends/D3D12/DescriptorAllocator.h" #include "VideoBackends/D3D12/DescriptorHeapManager.h" -#include "VideoCommon/RenderBase.h" - -class BoundingBox; +#include "VideoCommon/AbstractGfx.h" namespace DX12 { @@ -18,19 +16,16 @@ class DXShader; class DXPipeline; class SwapChain; -class Renderer final : public ::Renderer +class Gfx final : public ::AbstractGfx { public: - Renderer(std::unique_ptr swap_chain, float backbuffer_scale); - ~Renderer() override; + Gfx(std::unique_ptr swap_chain, float backbuffer_scale); + ~Gfx() override; - static Renderer* GetInstance() { return static_cast(g_renderer.get()); } + static Gfx* GetInstance() { return static_cast(g_gfx.get()); } bool IsHeadless() const override; - bool Initialize() override; - void Shutdown() override; - std::unique_ptr CreateTexture(const TextureConfig& config, std::string_view name) override; std::unique_ptr @@ -52,7 +47,7 @@ public: void Flush() override; void WaitForGPUIdle() override; - void ClearScreen(const MathUtil::Rectangle& rc, bool color_enable, bool alpha_enable, + void ClearRegion(const MathUtil::Rectangle& target_rc, bool color_enable, bool alpha_enable, bool z_enable, u32 color, u32 z) override; void SetPipeline(const AbstractPipeline* pipeline) override; @@ -74,6 +69,8 @@ public: void BindBackbuffer(const ClearColor& clear_color = {}) override; void PresentBackbuffer() override; + SurfaceInfo GetSurfaceInfo() const override; + // Completes the current render pass, executes the command buffer, and restores state ready for // next render. Use when you want to kick the current buffer to make room for new data. void ExecuteCommandList(bool wait_for_completion); @@ -98,8 +95,6 @@ public: protected: void OnConfigChanged(u32 bits) override; - std::unique_ptr CreateBoundingBox() const override; - private: static const u32 MAX_TEXTURES = 8; static const u32 NUM_CONSTANT_BUFFERS = 3; @@ -152,6 +147,8 @@ private: bool UpdateComputeUAVDescriptorTable(); bool UpdateSamplerDescriptorTable(); + float m_backbuffer_scale; + // Owned objects std::unique_ptr m_swap_chain; diff --git a/Source/Core/VideoBackends/D3D12/D3D12PerfQuery.cpp b/Source/Core/VideoBackends/D3D12/D3D12PerfQuery.cpp index 06f6f9dbcc..933f118ce9 100644 --- a/Source/Core/VideoBackends/D3D12/D3D12PerfQuery.cpp +++ b/Source/Core/VideoBackends/D3D12/D3D12PerfQuery.cpp @@ -9,8 +9,9 @@ #include "Common/Logging/Log.h" #include "VideoBackends/D3D12/Common.h" -#include "VideoBackends/D3D12/D3D12Renderer.h" +#include "VideoBackends/D3D12/D3D12Gfx.h" #include "VideoBackends/D3D12/DX12Context.h" +#include "VideoCommon/FramebufferManager.h" #include "VideoCommon/VideoCommon.h" namespace DX12 @@ -64,7 +65,7 @@ void PerfQuery::EnableQuery(PerfQueryGroup group) // This is because we can't leave a query open when submitting a command list, and the draw // call itself may need to execute a command list if we run out of descriptors. Note that // this assumes that the caller has bound all required state prior to enabling the query. - Renderer::GetInstance()->ApplyState(); + Gfx::GetInstance()->ApplyState(); if (group == PQG_ZCOMP_ZCOMPLOC || group == PQG_ZCOMP) { @@ -244,8 +245,8 @@ void PerfQuery::AccumulateQueriesFromBuffer(u32 query_count) // NOTE: Reported pixel metrics should be referenced to native resolution const u64 native_res_result = static_cast(result) * EFB_WIDTH / - g_renderer->GetTargetWidth() * EFB_HEIGHT / - g_renderer->GetTargetHeight(); + g_framebuffer_manager->GetEFBWidth() * EFB_HEIGHT / + g_framebuffer_manager->GetEFBHeight(); m_results[entry.query_group].fetch_add(static_cast(native_res_result), std::memory_order_relaxed); } @@ -261,7 +262,7 @@ void PerfQuery::PartialFlush(bool resolve, bool blocking) { // Submit a command buffer if there are unresolved queries (to write them to the buffer). if (resolve && m_unresolved_queries > 0) - Renderer::GetInstance()->ExecuteCommandList(false); + Gfx::GetInstance()->ExecuteCommandList(false); ReadbackQueries(blocking); } diff --git a/Source/Core/VideoBackends/D3D12/D3D12PerfQuery.h b/Source/Core/VideoBackends/D3D12/D3D12PerfQuery.h index b7409cedb5..5f3c725eef 100644 --- a/Source/Core/VideoBackends/D3D12/D3D12PerfQuery.h +++ b/Source/Core/VideoBackends/D3D12/D3D12PerfQuery.h @@ -17,7 +17,7 @@ public: static PerfQuery* GetInstance() { return static_cast(g_perf_query.get()); } - bool Initialize(); + bool Initialize() override; void ResolveQueries(); void EnableQuery(PerfQueryGroup group) override; diff --git a/Source/Core/VideoBackends/D3D12/D3D12VertexManager.cpp b/Source/Core/VideoBackends/D3D12/D3D12VertexManager.cpp index 3ea1008413..03e88e3867 100644 --- a/Source/Core/VideoBackends/D3D12/D3D12VertexManager.cpp +++ b/Source/Core/VideoBackends/D3D12/D3D12VertexManager.cpp @@ -10,7 +10,7 @@ #include "Core/System.h" -#include "VideoBackends/D3D12/D3D12Renderer.h" +#include "VideoBackends/D3D12/D3D12Gfx.h" #include "VideoBackends/D3D12/D3D12StreamBuffer.h" #include "VideoBackends/D3D12/DX12Context.h" @@ -92,7 +92,7 @@ void VertexManager::ResetBuffer(u32 vertex_stride) { // Flush any pending commands first, so that we can wait on the fences WARN_LOG_FMT(VIDEO, "Executing command list while waiting for space in vertex/index buffer"); - Renderer::GetInstance()->ExecuteCommandList(false); + Gfx::GetInstance()->ExecuteCommandList(false); // Attempt to allocate again, this may cause a fence wait if (!has_vbuffer_allocation) @@ -129,11 +129,11 @@ void VertexManager::CommitBuffer(u32 num_vertices, u32 vertex_stride, u32 num_in ADDSTAT(g_stats.this_frame.bytes_vertex_streamed, static_cast(vertex_data_size)); ADDSTAT(g_stats.this_frame.bytes_index_streamed, static_cast(index_data_size)); - Renderer::GetInstance()->SetVertexBuffer(m_vertex_stream_buffer.GetGPUPointer(), - m_vertex_srv.cpu_handle, vertex_stride, - m_vertex_stream_buffer.GetSize()); - Renderer::GetInstance()->SetIndexBuffer(m_index_stream_buffer.GetGPUPointer(), - m_index_stream_buffer.GetSize(), DXGI_FORMAT_R16_UINT); + Gfx::GetInstance()->SetVertexBuffer(m_vertex_stream_buffer.GetGPUPointer(), + m_vertex_srv.cpu_handle, vertex_stride, + m_vertex_stream_buffer.GetSize()); + Gfx::GetInstance()->SetIndexBuffer(m_index_stream_buffer.GetGPUPointer(), + m_index_stream_buffer.GetSize(), DXGI_FORMAT_R16_UINT); } void VertexManager::UploadUniforms() @@ -151,7 +151,7 @@ void VertexManager::UpdateVertexShaderConstants() if (!vertex_shader_manager.dirty || !ReserveConstantStorage()) return; - Renderer::GetInstance()->SetConstantBuffer(1, m_uniform_stream_buffer.GetCurrentGPUPointer()); + Gfx::GetInstance()->SetConstantBuffer(1, m_uniform_stream_buffer.GetCurrentGPUPointer()); std::memcpy(m_uniform_stream_buffer.GetCurrentHostPointer(), &vertex_shader_manager.constants, sizeof(VertexShaderConstants)); m_uniform_stream_buffer.CommitMemory(sizeof(VertexShaderConstants)); @@ -167,7 +167,7 @@ void VertexManager::UpdateGeometryShaderConstants() if (!geometry_shader_manager.dirty || !ReserveConstantStorage()) return; - Renderer::GetInstance()->SetConstantBuffer(2, m_uniform_stream_buffer.GetCurrentGPUPointer()); + Gfx::GetInstance()->SetConstantBuffer(2, m_uniform_stream_buffer.GetCurrentGPUPointer()); std::memcpy(m_uniform_stream_buffer.GetCurrentHostPointer(), &geometry_shader_manager.constants, sizeof(GeometryShaderConstants)); m_uniform_stream_buffer.CommitMemory(sizeof(GeometryShaderConstants)); @@ -183,7 +183,7 @@ void VertexManager::UpdatePixelShaderConstants() if (!pixel_shader_manager.dirty || !ReserveConstantStorage()) return; - Renderer::GetInstance()->SetConstantBuffer(0, m_uniform_stream_buffer.GetCurrentGPUPointer()); + Gfx::GetInstance()->SetConstantBuffer(0, m_uniform_stream_buffer.GetCurrentGPUPointer()); std::memcpy(m_uniform_stream_buffer.GetCurrentHostPointer(), &pixel_shader_manager.constants, sizeof(PixelShaderConstants)); m_uniform_stream_buffer.CommitMemory(sizeof(PixelShaderConstants)); @@ -204,7 +204,7 @@ bool VertexManager::ReserveConstantStorage() // The only places that call constant updates are safe to have state restored. WARN_LOG_FMT(VIDEO, "Executing command list while waiting for space in uniform buffer"); - Renderer::GetInstance()->ExecuteCommandList(false); + Gfx::GetInstance()->ExecuteCommandList(false); // Since we are on a new command buffer, all constants have been invalidated, and we need // to reupload them. We may as well do this now, since we're issuing a draw anyway. @@ -234,12 +234,12 @@ void VertexManager::UploadAllConstants() } // Update bindings - Renderer::GetInstance()->SetConstantBuffer(0, m_uniform_stream_buffer.GetCurrentGPUPointer() + - pixel_constants_offset); - Renderer::GetInstance()->SetConstantBuffer(1, m_uniform_stream_buffer.GetCurrentGPUPointer() + - vertex_constants_offset); - Renderer::GetInstance()->SetConstantBuffer(2, m_uniform_stream_buffer.GetCurrentGPUPointer() + - geometry_constants_offset); + Gfx::GetInstance()->SetConstantBuffer(0, m_uniform_stream_buffer.GetCurrentGPUPointer() + + pixel_constants_offset); + Gfx::GetInstance()->SetConstantBuffer(1, m_uniform_stream_buffer.GetCurrentGPUPointer() + + vertex_constants_offset); + Gfx::GetInstance()->SetConstantBuffer(2, m_uniform_stream_buffer.GetCurrentGPUPointer() + + geometry_constants_offset); auto& system = Core::System::GetInstance(); auto& pixel_shader_manager = system.GetPixelShaderManager(); @@ -271,12 +271,12 @@ void VertexManager::UploadUtilityUniforms(const void* data, u32 data_size) D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT)) { WARN_LOG_FMT(VIDEO, "Executing command buffer while waiting for ext space in uniform buffer"); - Renderer::GetInstance()->ExecuteCommandList(false); + Gfx::GetInstance()->ExecuteCommandList(false); } - Renderer::GetInstance()->SetConstantBuffer(0, m_uniform_stream_buffer.GetCurrentGPUPointer()); - Renderer::GetInstance()->SetConstantBuffer(1, m_uniform_stream_buffer.GetCurrentGPUPointer()); - Renderer::GetInstance()->SetConstantBuffer(2, m_uniform_stream_buffer.GetCurrentGPUPointer()); + Gfx::GetInstance()->SetConstantBuffer(0, m_uniform_stream_buffer.GetCurrentGPUPointer()); + Gfx::GetInstance()->SetConstantBuffer(1, m_uniform_stream_buffer.GetCurrentGPUPointer()); + Gfx::GetInstance()->SetConstantBuffer(2, m_uniform_stream_buffer.GetCurrentGPUPointer()); std::memcpy(m_uniform_stream_buffer.GetCurrentHostPointer(), data, data_size); m_uniform_stream_buffer.CommitMemory(data_size); ADDSTAT(g_stats.this_frame.bytes_uniform_streamed, data_size); @@ -293,7 +293,7 @@ bool VertexManager::UploadTexelBuffer(const void* data, u32 data_size, TexelBuff { // Try submitting cmdbuffer. WARN_LOG_FMT(VIDEO, "Submitting command buffer while waiting for space in texel buffer"); - Renderer::GetInstance()->ExecuteCommandList(false); + Gfx::GetInstance()->ExecuteCommandList(false); if (!m_texel_stream_buffer.ReserveMemory(data_size, elem_size)) { PanicAlertFmt("Failed to allocate {} bytes from texel buffer", data_size); @@ -305,7 +305,7 @@ bool VertexManager::UploadTexelBuffer(const void* data, u32 data_size, TexelBuff *out_offset = static_cast(m_texel_stream_buffer.GetCurrentOffset()) / elem_size; m_texel_stream_buffer.CommitMemory(data_size); ADDSTAT(g_stats.this_frame.bytes_uniform_streamed, data_size); - Renderer::GetInstance()->SetTextureDescriptor(0, m_texel_buffer_views[format].cpu_handle); + Gfx::GetInstance()->SetTextureDescriptor(0, m_texel_buffer_views[format].cpu_handle); return true; } @@ -323,7 +323,7 @@ bool VertexManager::UploadTexelBuffer(const void* data, u32 data_size, TexelBuff { // Try submitting cmdbuffer. WARN_LOG_FMT(VIDEO, "Submitting command buffer while waiting for space in texel buffer"); - Renderer::GetInstance()->ExecuteCommandList(false); + Gfx::GetInstance()->ExecuteCommandList(false); if (!m_texel_stream_buffer.ReserveMemory(reserve_size, elem_size)) { PanicAlertFmt("Failed to allocate {} bytes from texel buffer", reserve_size); @@ -342,8 +342,8 @@ bool VertexManager::UploadTexelBuffer(const void* data, u32 data_size, TexelBuff m_texel_stream_buffer.CommitMemory(palette_byte_offset + palette_size); ADDSTAT(g_stats.this_frame.bytes_uniform_streamed, palette_byte_offset + palette_size); - Renderer::GetInstance()->SetTextureDescriptor(0, m_texel_buffer_views[format].cpu_handle); - Renderer::GetInstance()->SetTextureDescriptor(1, m_texel_buffer_views[palette_format].cpu_handle); + Gfx::GetInstance()->SetTextureDescriptor(0, m_texel_buffer_views[format].cpu_handle); + Gfx::GetInstance()->SetTextureDescriptor(1, m_texel_buffer_views[palette_format].cpu_handle); return true; } diff --git a/Source/Core/VideoBackends/D3D12/DX12Texture.cpp b/Source/Core/VideoBackends/D3D12/DX12Texture.cpp index 69f516d161..49c5bd08a5 100644 --- a/Source/Core/VideoBackends/D3D12/DX12Texture.cpp +++ b/Source/Core/VideoBackends/D3D12/DX12Texture.cpp @@ -8,7 +8,7 @@ #include "Common/StringUtil.h" #include "VideoBackends/D3D12/Common.h" -#include "VideoBackends/D3D12/D3D12Renderer.h" +#include "VideoBackends/D3D12/D3D12Gfx.h" #include "VideoBackends/D3D12/D3D12StreamBuffer.h" #include "VideoBackends/D3D12/DX12Context.h" #include "VideoBackends/D3D12/DescriptorHeapManager.h" @@ -254,7 +254,7 @@ void DXTexture::Load(u32 level, u32 width, u32 height, u32 row_length, const u8* { WARN_LOG_FMT(VIDEO, "Executing command list while waiting for space in texture upload buffer"); - Renderer::GetInstance()->ExecuteCommandList(false); + Gfx::GetInstance()->ExecuteCommandList(false); if (!g_dx_context->GetTextureUploadBuffer().ReserveMemory( upload_size, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT)) { @@ -632,7 +632,7 @@ void DXStagingTexture::Flush() // the current list and wait for it to complete. This is the slowest path. Otherwise, if the // command list with the copy has been submitted, we only need to wait for the fence. if (m_completed_fence == g_dx_context->GetCurrentFenceValue()) - Renderer::GetInstance()->ExecuteCommandList(true); + Gfx::GetInstance()->ExecuteCommandList(true); else g_dx_context->WaitForFence(m_completed_fence); } diff --git a/Source/Core/VideoBackends/D3D12/VideoBackend.cpp b/Source/Core/VideoBackends/D3D12/VideoBackend.cpp index 1e7483fd6a..afc5bb96e8 100644 --- a/Source/Core/VideoBackends/D3D12/VideoBackend.cpp +++ b/Source/Core/VideoBackends/D3D12/VideoBackend.cpp @@ -11,8 +11,9 @@ #include "Core/ConfigManager.h" #include "VideoBackends/D3D12/Common.h" +#include "VideoBackends/D3D12/D3D12BoundingBox.h" +#include "VideoBackends/D3D12/D3D12Gfx.h" #include "VideoBackends/D3D12/D3D12PerfQuery.h" -#include "VideoBackends/D3D12/D3D12Renderer.h" #include "VideoBackends/D3D12/D3D12SwapChain.h" #include "VideoBackends/D3D12/D3D12VertexManager.h" #include "VideoBackends/D3D12/DX12Context.h" @@ -111,7 +112,7 @@ bool VideoBackend::Initialize(const WindowSystemInfo& wsi) } FillBackendInfo(); - InitializeShared(); + UpdateActiveConfig(); if (!g_dx_context->CreateGlobalResources()) { @@ -131,45 +132,22 @@ bool VideoBackend::Initialize(const WindowSystemInfo& wsi) } // Create main wrapper instances. - g_renderer = std::make_unique(std::move(swap_chain), wsi.render_surface_scale); - g_vertex_manager = std::make_unique(); - g_shader_cache = std::make_unique(); - g_framebuffer_manager = std::make_unique(); - g_texture_cache = std::make_unique(); - g_perf_query = std::make_unique(); + auto gfx = std::make_unique(std::move(swap_chain), wsi.render_surface_scale); + auto vertex_manager = std::make_unique(); + auto perf_query = std::make_unique(); + auto bounding_box = std::make_unique(); - if (!g_vertex_manager->Initialize() || !g_shader_cache->Initialize() || - !g_renderer->Initialize() || !g_framebuffer_manager->Initialize() || - !g_texture_cache->Initialize() || !PerfQuery::GetInstance()->Initialize()) - { - PanicAlertFmtT("Failed to initialize renderer classes"); - Shutdown(); - return false; - } - - g_shader_cache->InitializeShaderCache(); - return true; + return InitializeShared(std::move(gfx), std::move(vertex_manager), std::move(perf_query), + std::move(bounding_box)); } void VideoBackend::Shutdown() { // Keep the debug runtime happy... - if (g_renderer) - Renderer::GetInstance()->ExecuteCommandList(true); + if (g_gfx) + Gfx::GetInstance()->ExecuteCommandList(true); - if (g_shader_cache) - g_shader_cache->Shutdown(); - - if (g_renderer) - g_renderer->Shutdown(); - - g_perf_query.reset(); - g_texture_cache.reset(); - g_framebuffer_manager.reset(); - g_shader_cache.reset(); - g_vertex_manager.reset(); - g_renderer.reset(); - DXContext::Destroy(); ShutdownShared(); + DXContext::Destroy(); } } // namespace DX12 diff --git a/Source/Core/VideoBackends/Metal/CMakeLists.txt b/Source/Core/VideoBackends/Metal/CMakeLists.txt index 7be576f52a..db633a0380 100644 --- a/Source/Core/VideoBackends/Metal/CMakeLists.txt +++ b/Source/Core/VideoBackends/Metal/CMakeLists.txt @@ -2,6 +2,8 @@ add_library(videometal MRCHelpers.h MTLBoundingBox.mm MTLBoundingBox.h + MTLGfx.mm + MTLGfx.h MTLMain.mm MTLObjectCache.h MTLObjectCache.mm @@ -9,8 +11,6 @@ add_library(videometal MTLPerfQuery.h MTLPipeline.mm MTLPipeline.h - MTLRenderer.mm - MTLRenderer.h MTLShader.mm MTLShader.h MTLStateTracker.mm diff --git a/Source/Core/VideoBackends/Metal/MTLRenderer.h b/Source/Core/VideoBackends/Metal/MTLGfx.h similarity index 90% rename from Source/Core/VideoBackends/Metal/MTLRenderer.h rename to Source/Core/VideoBackends/Metal/MTLGfx.h index f8b97dc0d3..22795a0183 100644 --- a/Source/Core/VideoBackends/Metal/MTLRenderer.h +++ b/Source/Core/VideoBackends/Metal/MTLGfx.h @@ -6,7 +6,7 @@ #include #include -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/AbstractGfx.h" #include "VideoBackends/Metal/MRCHelpers.h" @@ -15,16 +15,14 @@ namespace Metal class Framebuffer; class Texture; -class Renderer final : public ::Renderer +class Gfx final : public ::AbstractGfx { public: - Renderer(MRCOwned layer, int width, int height, float layer_scale); - ~Renderer() override; + Gfx(MRCOwned layer); + ~Gfx() override; bool IsHeadless() const override; - bool Initialize() override; - std::unique_ptr CreateTexture(const TextureConfig& config, std::string_view name) override; std::unique_ptr @@ -49,7 +47,7 @@ public: void WaitForGPUIdle() override; void OnConfigChanged(u32 bits) override; - void ClearScreen(const MathUtil::Rectangle& rc, bool color_enable, bool alpha_enable, + void ClearRegion(const MathUtil::Rectangle& target_rc, bool color_enable, bool alpha_enable, bool z_enable, u32 color, u32 z) override; void SetPipeline(const AbstractPipeline* pipeline) override; @@ -71,8 +69,7 @@ public: void BindBackbuffer(const ClearColor& clear_color = {}) override; void PresentBackbuffer() override; -protected: - std::unique_ptr<::BoundingBox> CreateBoundingBox() const override; + SurfaceInfo GetSurfaceInfo() const override; private: MRCOwned m_layer; diff --git a/Source/Core/VideoBackends/Metal/MTLRenderer.mm b/Source/Core/VideoBackends/Metal/MTLGfx.mm similarity index 74% rename from Source/Core/VideoBackends/Metal/MTLRenderer.mm rename to Source/Core/VideoBackends/Metal/MTLGfx.mm index 7e915f045a..d6621396ec 100644 --- a/Source/Core/VideoBackends/Metal/MTLRenderer.mm +++ b/Source/Core/VideoBackends/Metal/MTLGfx.mm @@ -1,7 +1,7 @@ // Copyright 2022 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "VideoBackends/Metal/MTLRenderer.h" +#include "VideoBackends/Metal/MTLGfx.h" #include "VideoBackends/Metal/MTLBoundingBox.h" #include "VideoBackends/Metal/MTLObjectCache.h" @@ -13,36 +13,31 @@ #include "VideoBackends/Metal/MTLVertexManager.h" #include "VideoCommon/FramebufferManager.h" +#include "VideoCommon/Present.h" #include "VideoCommon/VideoBackendBase.h" -Metal::Renderer::Renderer(MRCOwned layer, int width, int height, float layer_scale) - : ::Renderer(width, height, layer_scale, Util::ToAbstract([layer pixelFormat])), - m_layer(std::move(layer)) +#include + +Metal::Gfx::Gfx(MRCOwned layer) : m_layer(std::move(layer)) { UpdateActiveConfig(); [m_layer setDisplaySyncEnabled:g_ActiveConfig.bVSyncActive]; + + SetupSurface(); + g_state_tracker->FlushEncoders(); } -Metal::Renderer::~Renderer() = default; +Metal::Gfx::~Gfx() = default; -bool Metal::Renderer::IsHeadless() const +bool Metal::Gfx::IsHeadless() const { return m_layer == nullptr; } -bool Metal::Renderer::Initialize() -{ - if (!::Renderer::Initialize()) - return false; - SetupSurface(); - g_state_tracker->FlushEncoders(); - return true; -} - // MARK: Texture Creation -std::unique_ptr Metal::Renderer::CreateTexture(const TextureConfig& config, - std::string_view name) +std::unique_ptr Metal::Gfx::CreateTexture(const TextureConfig& config, + std::string_view name) { @autoreleasepool { @@ -77,7 +72,7 @@ std::unique_ptr Metal::Renderer::CreateTexture(const TextureCon } std::unique_ptr -Metal::Renderer::CreateStagingTexture(StagingTextureType type, const TextureConfig& config) +Metal::Gfx::CreateStagingTexture(StagingTextureType type, const TextureConfig& config) { @autoreleasepool { @@ -98,8 +93,7 @@ Metal::Renderer::CreateStagingTexture(StagingTextureType type, const TextureConf } std::unique_ptr -Metal::Renderer::CreateFramebuffer(AbstractTexture* color_attachment, - AbstractTexture* depth_attachment) +Metal::Gfx::CreateFramebuffer(AbstractTexture* color_attachment, AbstractTexture* depth_attachment) { AbstractTexture* const either_attachment = color_attachment ? color_attachment : depth_attachment; return std::make_unique( @@ -110,9 +104,9 @@ Metal::Renderer::CreateFramebuffer(AbstractTexture* color_attachment, // MARK: Pipeline Creation -std::unique_ptr Metal::Renderer::CreateShaderFromSource(ShaderStage stage, - std::string_view source, - std::string_view name) +std::unique_ptr Metal::Gfx::CreateShaderFromSource(ShaderStage stage, + std::string_view source, + std::string_view name) { std::optional msl = Util::TranslateShaderToMSL(stage, source); if (!msl.has_value()) @@ -124,10 +118,9 @@ std::unique_ptr Metal::Renderer::CreateShaderFromSource(ShaderSt return CreateShaderFromMSL(stage, std::move(*msl), source, name); } -std::unique_ptr Metal::Renderer::CreateShaderFromBinary(ShaderStage stage, - const void* data, - size_t length, - std::string_view name) +std::unique_ptr Metal::Gfx::CreateShaderFromBinary(ShaderStage stage, + const void* data, size_t length, + std::string_view name) { return CreateShaderFromMSL(stage, std::string(static_cast(data), length), {}, name); } @@ -158,10 +151,9 @@ static NSString* GenericShaderName(ShaderStage stage) // clang-format on -std::unique_ptr Metal::Renderer::CreateShaderFromMSL(ShaderStage stage, - std::string msl, - std::string_view glsl, - std::string_view name) +std::unique_ptr Metal::Gfx::CreateShaderFromMSL(ShaderStage stage, std::string msl, + std::string_view glsl, + std::string_view name) { @autoreleasepool { @@ -243,7 +235,7 @@ std::unique_ptr Metal::Renderer::CreateShaderFromMSL(ShaderStage } std::unique_ptr -Metal::Renderer::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) +Metal::Gfx::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) { @autoreleasepool { @@ -251,14 +243,14 @@ Metal::Renderer::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_d } } -std::unique_ptr -Metal::Renderer::CreatePipeline(const AbstractPipelineConfig& config, const void* cache_data, - size_t cache_data_length) +std::unique_ptr Metal::Gfx::CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data, + size_t cache_data_length) { return g_object_cache->CreatePipeline(config); } -void Metal::Renderer::Flush() +void Metal::Gfx::Flush() { @autoreleasepool { @@ -266,7 +258,7 @@ void Metal::Renderer::Flush() } } -void Metal::Renderer::WaitForGPUIdle() +void Metal::Gfx::WaitForGPUIdle() { @autoreleasepool { @@ -275,8 +267,10 @@ void Metal::Renderer::WaitForGPUIdle() } } -void Metal::Renderer::OnConfigChanged(u32 bits) +void Metal::Gfx::OnConfigChanged(u32 bits) { + AbstractGfx::OnConfigChanged(bits); + if (bits & CONFIG_CHANGE_BIT_VSYNC) [m_layer setDisplaySyncEnabled:g_ActiveConfig.bVSyncActive]; @@ -287,14 +281,13 @@ void Metal::Renderer::OnConfigChanged(u32 bits) } } -void Metal::Renderer::ClearScreen(const MathUtil::Rectangle& rc, bool color_enable, - bool alpha_enable, bool z_enable, u32 color, u32 z) +void Metal::Gfx::ClearRegion(const MathUtil::Rectangle& target_rc, bool color_enable, + bool alpha_enable, bool z_enable, u32 color, u32 z) { - MathUtil::Rectangle target_rc = Renderer::ConvertEFBRectangle(rc); - target_rc.ClampUL(0, 0, m_target_width, m_target_height); - + u32 framebuffer_width = m_current_framebuffer->GetWidth(); + u32 framebuffer_height = m_current_framebuffer->GetHeight(); // All Metal render passes are fullscreen, so we can only run a fast clear if the target is too - if (target_rc == MathUtil::Rectangle(0, 0, m_target_width, m_target_height)) + if (target_rc == MathUtil::Rectangle(0, 0, framebuffer_width, framebuffer_height)) { // Determine whether the EFB has an alpha channel. If it doesn't, we can clear the alpha // channel to 0xFF. This hopefully allows us to use the fast path in most cases. @@ -334,16 +327,16 @@ void Metal::Renderer::ClearScreen(const MathUtil::Rectangle& rc, bool color } g_state_tracker->EnableEncoderLabel(false); - g_framebuffer_manager->ClearEFB(rc, color_enable, alpha_enable, z_enable, color, z); + AbstractGfx::ClearRegion(target_rc, color_enable, alpha_enable, z_enable, color, z); g_state_tracker->EnableEncoderLabel(true); } -void Metal::Renderer::SetPipeline(const AbstractPipeline* pipeline) +void Metal::Gfx::SetPipeline(const AbstractPipeline* pipeline) { g_state_tracker->SetPipeline(static_cast(pipeline)); } -void Metal::Renderer::SetFramebuffer(AbstractFramebuffer* framebuffer) +void Metal::Gfx::SetFramebuffer(AbstractFramebuffer* framebuffer) { // Shouldn't be bound as a texture. if (AbstractTexture* color = framebuffer->GetColorAttachment()) @@ -355,7 +348,7 @@ void Metal::Renderer::SetFramebuffer(AbstractFramebuffer* framebuffer) g_state_tracker->SetCurrentFramebuffer(static_cast(framebuffer)); } -void Metal::Renderer::SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer) +void Metal::Gfx::SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer) { @autoreleasepool { @@ -364,8 +357,8 @@ void Metal::Renderer::SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer) } } -void Metal::Renderer::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, - const ClearColor& color_value, float depth_value) +void Metal::Gfx::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, + const ClearColor& color_value, float depth_value) { @autoreleasepool { @@ -376,39 +369,39 @@ void Metal::Renderer::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, } } -void Metal::Renderer::SetScissorRect(const MathUtil::Rectangle& rc) +void Metal::Gfx::SetScissorRect(const MathUtil::Rectangle& rc) { g_state_tracker->SetScissor(rc); } -void Metal::Renderer::SetTexture(u32 index, const AbstractTexture* texture) +void Metal::Gfx::SetTexture(u32 index, const AbstractTexture* texture) { g_state_tracker->SetTexture( index, texture ? static_cast(texture)->GetMTLTexture() : nullptr); } -void Metal::Renderer::SetSamplerState(u32 index, const SamplerState& state) +void Metal::Gfx::SetSamplerState(u32 index, const SamplerState& state) { g_state_tracker->SetSampler(index, state); } -void Metal::Renderer::SetComputeImageTexture(AbstractTexture* texture, bool read, bool write) +void Metal::Gfx::SetComputeImageTexture(AbstractTexture* texture, bool read, bool write) { g_state_tracker->SetComputeTexture(static_cast(texture)); } -void Metal::Renderer::UnbindTexture(const AbstractTexture* texture) +void Metal::Gfx::UnbindTexture(const AbstractTexture* texture) { g_state_tracker->UnbindTexture(static_cast(texture)->GetMTLTexture()); } -void Metal::Renderer::SetViewport(float x, float y, float width, float height, float near_depth, - float far_depth) +void Metal::Gfx::SetViewport(float x, float y, float width, float height, float near_depth, + float far_depth) { g_state_tracker->SetViewport(x, y, width, height, near_depth, far_depth); } -void Metal::Renderer::Draw(u32 base_vertex, u32 num_vertices) +void Metal::Gfx::Draw(u32 base_vertex, u32 num_vertices) { @autoreleasepool { @@ -416,7 +409,7 @@ void Metal::Renderer::Draw(u32 base_vertex, u32 num_vertices) } } -void Metal::Renderer::DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) +void Metal::Gfx::DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) { @autoreleasepool { @@ -424,9 +417,9 @@ void Metal::Renderer::DrawIndexed(u32 base_index, u32 num_indices, u32 base_vert } } -void Metal::Renderer::DispatchComputeShader(const AbstractShader* shader, // - u32 groupsize_x, u32 groupsize_y, u32 groupsize_z, - u32 groups_x, u32 groups_y, u32 groups_z) +void Metal::Gfx::DispatchComputeShader(const AbstractShader* shader, // + u32 groupsize_x, u32 groupsize_y, u32 groupsize_z, + u32 groups_x, u32 groups_y, u32 groups_z) { @autoreleasepool { @@ -436,7 +429,7 @@ void Metal::Renderer::DispatchComputeShader(const AbstractShader* shader, // } } -void Metal::Renderer::BindBackbuffer(const ClearColor& clear_color) +void Metal::Gfx::BindBackbuffer(const ClearColor& clear_color) { @autoreleasepool { @@ -448,7 +441,7 @@ void Metal::Renderer::BindBackbuffer(const ClearColor& clear_color) } } -void Metal::Renderer::PresentBackbuffer() +void Metal::Gfx::PresentBackbuffer() { @autoreleasepool { @@ -473,40 +466,45 @@ void Metal::Renderer::PresentBackbuffer() } } -std::unique_ptr<::BoundingBox> Metal::Renderer::CreateBoundingBox() const +void Metal::Gfx::CheckForSurfaceChange() { - return std::make_unique(); -} - -void Metal::Renderer::CheckForSurfaceChange() -{ - if (!m_surface_changed.TestAndClear()) + if (!g_presenter->SurfaceChangedTestAndClear()) return; - m_layer = MRCRetain(static_cast(m_new_surface_handle)); - m_new_surface_handle = nullptr; + m_layer = MRCRetain(static_cast(g_presenter->GetNewSurfaceHandle())); SetupSurface(); } -void Metal::Renderer::CheckForSurfaceResize() +void Metal::Gfx::CheckForSurfaceResize() { - if (!m_surface_resized.TestAndClear()) + if (!g_presenter->SurfaceResizedTestAndClear()) return; SetupSurface(); } -void Metal::Renderer::SetupSurface() +void Metal::Gfx::SetupSurface() { - CGSize size = [m_layer bounds].size; - // TODO: Update m_backbuffer_scale (need to make doing that not break everything) - const float backbuffer_scale = [m_layer contentsScale]; - size.width *= backbuffer_scale; - size.height *= backbuffer_scale; - [m_layer setDrawableSize:size]; - m_backbuffer_width = size.width; - m_backbuffer_height = size.height; - TextureConfig cfg(m_backbuffer_width, m_backbuffer_height, 1, 1, 1, m_backbuffer_format, + auto info = GetSurfaceInfo(); + + [m_layer setDrawableSize:{static_cast(info.width), static_cast(info.height)}]; + + TextureConfig cfg(info.width, info.height, 1, 1, 1, info.format, AbstractTextureFlag_RenderTarget); m_bb_texture = std::make_unique(nullptr, cfg); m_backbuffer = std::make_unique(m_bb_texture.get(), nullptr, // - m_backbuffer_width, m_backbuffer_height, 1, 1); + info.width, info.height, 1, 1); + + if (g_presenter) + g_presenter->SetBackbuffer(info); +} + +SurfaceInfo Metal::Gfx::GetSurfaceInfo() const +{ + if (!m_layer) // Headless + return {}; + + CGSize size = [m_layer bounds].size; + const float scale = [m_layer contentsScale]; + + return {static_cast(size.width * scale), static_cast(size.height * scale), scale, + Util::ToAbstract([m_layer pixelFormat])}; } diff --git a/Source/Core/VideoBackends/Metal/MTLMain.mm b/Source/Core/VideoBackends/Metal/MTLMain.mm index ad646b9bf0..f799d5eea1 100644 --- a/Source/Core/VideoBackends/Metal/MTLMain.mm +++ b/Source/Core/VideoBackends/Metal/MTLMain.mm @@ -16,13 +16,15 @@ #include "Common/Common.h" #include "Common/MsgHandler.h" +#include "VideoBackends/Metal/MTLBoundingBox.h" +#include "VideoBackends/Metal/MTLGfx.h" #include "VideoBackends/Metal/MTLObjectCache.h" #include "VideoBackends/Metal/MTLPerfQuery.h" -#include "VideoBackends/Metal/MTLRenderer.h" #include "VideoBackends/Metal/MTLStateTracker.h" #include "VideoBackends/Metal/MTLUtil.h" #include "VideoBackends/Metal/MTLVertexManager.h" +#include "VideoCommon/AbstractGfx.h" #include "VideoCommon/FramebufferManager.h" #include "VideoCommon/VideoCommon.h" #include "VideoCommon/VideoConfig.h" @@ -93,57 +95,28 @@ bool Metal::VideoBackend::Initialize(const WindowSystemInfo& wsi) MRCOwned> adapter = std::move(devs[selected_adapter_index]); Util::PopulateBackendInfoFeatures(&g_Config, adapter); - // With the backend information populated, we can now initialize videocommon. - InitializeShared(); + UpdateActiveConfig(); MRCOwned layer = MRCRetain(static_cast(wsi.render_surface)); [layer setDevice:adapter]; if (Util::ToAbstract([layer pixelFormat]) == AbstractTextureFormat::Undefined) [layer setPixelFormat:MTLPixelFormatBGRA8Unorm]; - CGSize size = [layer bounds].size; - float scale = [layer contentsScale]; - if (!layer) // headless - scale = 1.0; ObjectCache::Initialize(std::move(adapter)); g_state_tracker = std::make_unique(); - g_renderer = std::make_unique(std::move(layer), size.width * scale, - size.height * scale, scale); - g_vertex_manager = std::make_unique(); - g_perf_query = std::make_unique(); - g_framebuffer_manager = std::make_unique(); - g_texture_cache = std::make_unique(); - g_shader_cache = std::make_unique(); - if (!g_vertex_manager->Initialize() || !g_shader_cache->Initialize() || - !g_renderer->Initialize() || !g_framebuffer_manager->Initialize() || - !g_texture_cache->Initialize()) - { - PanicAlertFmt("Failed to initialize renderer classes"); - Shutdown(); - return false; - } - - g_shader_cache->InitializeShaderCache(); - - return true; + return InitializeShared( + std::make_unique(std::move(layer)), std::make_unique(), + std::make_unique(), std::make_unique()); } } void Metal::VideoBackend::Shutdown() { - g_shader_cache->Shutdown(); - g_renderer->Shutdown(); + ShutdownShared(); - g_shader_cache.reset(); - g_texture_cache.reset(); - g_framebuffer_manager.reset(); - g_perf_query.reset(); - g_vertex_manager.reset(); - g_renderer.reset(); g_state_tracker.reset(); ObjectCache::Shutdown(); - ShutdownShared(); } void Metal::VideoBackend::InitBackendInfo() diff --git a/Source/Core/VideoBackends/Metal/MTLObjectCache.h b/Source/Core/VideoBackends/Metal/MTLObjectCache.h index d9f786b51f..f47a513116 100644 --- a/Source/Core/VideoBackends/Metal/MTLObjectCache.h +++ b/Source/Core/VideoBackends/Metal/MTLObjectCache.h @@ -8,6 +8,7 @@ #include "VideoBackends/Metal/MRCHelpers.h" +#include "VideoCommon/BPMemory.h" #include "VideoCommon/RenderState.h" struct AbstractPipelineConfig; diff --git a/Source/Core/VideoBackends/Metal/MTLPerfQuery.mm b/Source/Core/VideoBackends/Metal/MTLPerfQuery.mm index cd65b37b58..76bddc1dce 100644 --- a/Source/Core/VideoBackends/Metal/MTLPerfQuery.mm +++ b/Source/Core/VideoBackends/Metal/MTLPerfQuery.mm @@ -77,8 +77,9 @@ void Metal::PerfQuery::ReturnResults(const u64* data, const PerfQueryGroup* grou { for (size_t i = 0; i < count; ++i) { - u64 native_res_result = data[i] * (EFB_WIDTH * EFB_HEIGHT) / - (g_renderer->GetTargetWidth() * g_renderer->GetTargetHeight()); + u64 native_res_result = + data[i] * (EFB_WIDTH * EFB_HEIGHT) / + (g_framebuffer_manager->GetEFBWidth() * g_framebuffer_manager->GetEFBHeight()); native_res_result /= g_ActiveConfig.iMultisamples; diff --git a/Source/Core/VideoBackends/Metal/MTLStateTracker.h b/Source/Core/VideoBackends/Metal/MTLStateTracker.h index 3e0cb38afa..f425cd2884 100644 --- a/Source/Core/VideoBackends/Metal/MTLStateTracker.h +++ b/Source/Core/VideoBackends/Metal/MTLStateTracker.h @@ -17,8 +17,8 @@ #include "VideoBackends/Metal/MTLTexture.h" #include "VideoBackends/Metal/MTLUtil.h" +#include "VideoCommon/FramebufferManager.h" #include "VideoCommon/PerfQueryBase.h" -#include "VideoCommon/RenderBase.h" namespace Metal { diff --git a/Source/Core/VideoBackends/Metal/MTLTexture.mm b/Source/Core/VideoBackends/Metal/MTLTexture.mm index 69635e6792..17079b0c6e 100644 --- a/Source/Core/VideoBackends/Metal/MTLTexture.mm +++ b/Source/Core/VideoBackends/Metal/MTLTexture.mm @@ -6,7 +6,7 @@ #include "Common/Align.h" #include "Common/Assert.h" -#include "VideoBackends/Metal/MTLRenderer.h" +#include "VideoBackends/Metal/MTLGfx.h" #include "VideoBackends/Metal/MTLStateTracker.h" Metal::Texture::Texture(MRCOwned> tex, const TextureConfig& config) diff --git a/Source/Core/VideoBackends/Null/CMakeLists.txt b/Source/Core/VideoBackends/Null/CMakeLists.txt index e95877323f..e8ad21bbe8 100644 --- a/Source/Core/VideoBackends/Null/CMakeLists.txt +++ b/Source/Core/VideoBackends/Null/CMakeLists.txt @@ -1,8 +1,8 @@ add_library(videonull NullBackend.cpp NullBoundingBox.h - NullRender.cpp - NullRender.h + NullGfx.cpp + NullGfx.h NullTexture.cpp NullTexture.h NullVertexManager.cpp diff --git a/Source/Core/VideoBackends/Null/NullBackend.cpp b/Source/Core/VideoBackends/Null/NullBackend.cpp index 7cc8919ad0..900a911699 100644 --- a/Source/Core/VideoBackends/Null/NullBackend.cpp +++ b/Source/Core/VideoBackends/Null/NullBackend.cpp @@ -11,12 +11,14 @@ #include "Common/Common.h" #include "Common/MsgHandler.h" -#include "VideoBackends/Null/NullRender.h" +#include "VideoBackends/Null/NullBoundingBox.h" +#include "VideoBackends/Null/NullGfx.h" #include "VideoBackends/Null/NullVertexManager.h" #include "VideoBackends/Null/PerfQuery.h" #include "VideoBackends/Null/TextureCache.h" #include "VideoCommon/FramebufferManager.h" +#include "VideoCommon/Present.h" #include "VideoCommon/VideoBackendBase.h" #include "VideoCommon/VideoCommon.h" #include "VideoCommon/VideoConfig.h" @@ -69,39 +71,13 @@ void VideoBackend::InitBackendInfo() bool VideoBackend::Initialize(const WindowSystemInfo& wsi) { - InitializeShared(); - - g_renderer = std::make_unique(); - g_vertex_manager = std::make_unique(); - g_perf_query = std::make_unique(); - g_framebuffer_manager = std::make_unique(); - g_texture_cache = std::make_unique(); - g_shader_cache = std::make_unique(); - - if (!g_vertex_manager->Initialize() || !g_shader_cache->Initialize() || - !g_renderer->Initialize() || !g_framebuffer_manager->Initialize() || - !g_texture_cache->Initialize()) - { - PanicAlertFmt("Failed to initialize renderer classes"); - Shutdown(); - return false; - } - - g_shader_cache->InitializeShaderCache(); - return true; + return InitializeShared(std::make_unique(), std::make_unique(), + std::make_unique(), std::make_unique(), + std::make_unique(), std::make_unique()); } void VideoBackend::Shutdown() { - g_shader_cache->Shutdown(); - g_renderer->Shutdown(); - - g_texture_cache.reset(); - g_perf_query.reset(); - g_vertex_manager.reset(); - g_framebuffer_manager.reset(); - g_renderer.reset(); - ShutdownShared(); } diff --git a/Source/Core/VideoBackends/Null/NullGfx.cpp b/Source/Core/VideoBackends/Null/NullGfx.cpp new file mode 100644 index 0000000000..7449aae09c --- /dev/null +++ b/Source/Core/VideoBackends/Null/NullGfx.cpp @@ -0,0 +1,95 @@ +// Copyright 2015 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoBackends/Null/NullGfx.h" + +#include "VideoBackends/Null/NullBoundingBox.h" +#include "VideoBackends/Null/NullTexture.h" + +#include "VideoCommon/AbstractPipeline.h" +#include "VideoCommon/AbstractShader.h" +#include "VideoCommon/NativeVertexFormat.h" +#include "VideoCommon/VideoConfig.h" + +namespace Null +{ +// Init functions +NullGfx::NullGfx() +{ + UpdateActiveConfig(); +} + +NullGfx::~NullGfx() +{ + UpdateActiveConfig(); +} + +bool NullGfx::IsHeadless() const +{ + return true; +} + +bool NullGfx::SupportsUtilityDrawing() const +{ + return false; +} + +std::unique_ptr NullGfx::CreateTexture(const TextureConfig& config, + [[maybe_unused]] std::string_view name) +{ + return std::make_unique(config); +} + +std::unique_ptr NullGfx::CreateStagingTexture(StagingTextureType type, + const TextureConfig& config) +{ + return std::make_unique(type, config); +} + +class NullShader final : public AbstractShader +{ +public: + explicit NullShader(ShaderStage stage) : AbstractShader(stage) {} +}; + +std::unique_ptr +NullGfx::CreateShaderFromSource(ShaderStage stage, [[maybe_unused]] std::string_view source, + [[maybe_unused]] std::string_view name) +{ + return std::make_unique(stage); +} + +std::unique_ptr +NullGfx::CreateShaderFromBinary(ShaderStage stage, const void* data, size_t length, + [[maybe_unused]] std::string_view name) +{ + return std::make_unique(stage); +} + +class NullPipeline final : public AbstractPipeline +{ +}; + +std::unique_ptr NullGfx::CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data, + size_t cache_data_length) +{ + return std::make_unique(); +} + +std::unique_ptr NullGfx::CreateFramebuffer(AbstractTexture* color_attachment, + AbstractTexture* depth_attachment) +{ + return NullFramebuffer::Create(static_cast(color_attachment), + static_cast(depth_attachment)); +} + +std::unique_ptr +NullGfx::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) +{ + return std::make_unique(vtx_decl); +} + +NullRenderer::~NullRenderer() = default; + +} // namespace Null diff --git a/Source/Core/VideoBackends/Null/NullRender.h b/Source/Core/VideoBackends/Null/NullGfx.h similarity index 83% rename from Source/Core/VideoBackends/Null/NullRender.h rename to Source/Core/VideoBackends/Null/NullGfx.h index 061e78c61a..b1dff2bc2f 100644 --- a/Source/Core/VideoBackends/Null/NullRender.h +++ b/Source/Core/VideoBackends/Null/NullGfx.h @@ -3,19 +3,19 @@ #pragma once +#include "VideoCommon/AbstractGfx.h" #include "VideoCommon/RenderBase.h" -class BoundingBox; - namespace Null { -class Renderer final : public ::Renderer +class NullGfx final : public AbstractGfx { public: - Renderer(); - ~Renderer() override; + NullGfx(); + ~NullGfx() override; bool IsHeadless() const override; + virtual bool SupportsUtilityDrawing() const override; std::unique_ptr CreateTexture(const TextureConfig& config, std::string_view name) override; @@ -34,18 +34,18 @@ public: std::unique_ptr CreatePipeline(const AbstractPipelineConfig& config, const void* cache_data = nullptr, size_t cache_data_length = 0) override; +}; + +class NullRenderer final : public Renderer +{ +public: + NullRenderer() {} + ~NullRenderer() override; u32 AccessEFB(EFBAccessType type, u32 x, u32 y, u32 poke_data) override { return 0; } void PokeEFB(EFBAccessType type, const EfbPokeData* points, size_t num_points) override {} - void ClearScreen(const MathUtil::Rectangle& rc, bool colorEnable, bool alphaEnable, - bool zEnable, u32 color, u32 z) override - { - } - void ReinterpretPixelData(EFBReinterpretType convtype) override {} - -protected: - std::unique_ptr CreateBoundingBox() const override; }; + } // namespace Null diff --git a/Source/Core/VideoBackends/Null/NullRender.cpp b/Source/Core/VideoBackends/Null/NullRender.cpp deleted file mode 100644 index e7b7a7907c..0000000000 --- a/Source/Core/VideoBackends/Null/NullRender.cpp +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2015 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "VideoBackends/Null/NullRender.h" - -#include "VideoBackends/Null/NullBoundingBox.h" -#include "VideoBackends/Null/NullTexture.h" - -#include "VideoCommon/AbstractPipeline.h" -#include "VideoCommon/AbstractShader.h" -#include "VideoCommon/NativeVertexFormat.h" -#include "VideoCommon/VideoConfig.h" - -namespace Null -{ -// Init functions -Renderer::Renderer() : ::Renderer(1, 1, 1.0f, AbstractTextureFormat::RGBA8) -{ - UpdateActiveConfig(); -} - -Renderer::~Renderer() -{ - UpdateActiveConfig(); -} - -bool Renderer::IsHeadless() const -{ - return true; -} - -std::unique_ptr Renderer::CreateTexture(const TextureConfig& config, - [[maybe_unused]] std::string_view name) -{ - return std::make_unique(config); -} - -std::unique_ptr Renderer::CreateStagingTexture(StagingTextureType type, - const TextureConfig& config) -{ - return std::make_unique(type, config); -} - -class NullShader final : public AbstractShader -{ -public: - explicit NullShader(ShaderStage stage) : AbstractShader(stage) {} -}; - -std::unique_ptr -Renderer::CreateShaderFromSource(ShaderStage stage, [[maybe_unused]] std::string_view source, - [[maybe_unused]] std::string_view name) -{ - return std::make_unique(stage); -} - -std::unique_ptr -Renderer::CreateShaderFromBinary(ShaderStage stage, const void* data, size_t length, - [[maybe_unused]] std::string_view name) -{ - return std::make_unique(stage); -} - -class NullPipeline final : public AbstractPipeline -{ -}; - -std::unique_ptr Renderer::CreatePipeline(const AbstractPipelineConfig& config, - const void* cache_data, - size_t cache_data_length) -{ - return std::make_unique(); -} - -std::unique_ptr Renderer::CreateFramebuffer(AbstractTexture* color_attachment, - AbstractTexture* depth_attachment) -{ - return NullFramebuffer::Create(static_cast(color_attachment), - static_cast(depth_attachment)); -} - -std::unique_ptr -Renderer::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) -{ - return std::make_unique(vtx_decl); -} - -std::unique_ptr Renderer::CreateBoundingBox() const -{ - return std::make_unique(); -} -} // namespace Null diff --git a/Source/Core/VideoBackends/Null/TextureCache.h b/Source/Core/VideoBackends/Null/TextureCache.h index 2b95586f44..78e910b74b 100644 --- a/Source/Core/VideoBackends/Null/TextureCache.h +++ b/Source/Core/VideoBackends/Null/TextureCache.h @@ -18,7 +18,7 @@ protected: { } - void CopyEFBToCacheEntry(TCacheEntry* entry, bool is_depth_copy, + void CopyEFBToCacheEntry(RcTcacheEntry& entry, bool is_depth_copy, const MathUtil::Rectangle& src_rect, bool scale_by_half, bool linear_filter, EFBCopyFormat dst_format, bool is_intensity, float gamma, bool clamp_top, bool clamp_bottom, diff --git a/Source/Core/VideoBackends/OGL/CMakeLists.txt b/Source/Core/VideoBackends/OGL/CMakeLists.txt index 9a9d9caf2d..8fb8524054 100644 --- a/Source/Core/VideoBackends/OGL/CMakeLists.txt +++ b/Source/Core/VideoBackends/OGL/CMakeLists.txt @@ -2,14 +2,16 @@ add_library(videoogl GPUTimer.h OGLBoundingBox.cpp OGLBoundingBox.h + OGLConfig.cpp + OGLConfig.h + OGLGfx.cpp + OGLGfx.h OGLMain.cpp OGLNativeVertexFormat.cpp OGLPerfQuery.cpp OGLPerfQuery.h OGLPipeline.cpp OGLPipeline.h - OGLRender.cpp - OGLRender.h OGLShader.cpp OGLShader.h OGLStreamBuffer.cpp diff --git a/Source/Core/VideoBackends/OGL/OGLBoundingBox.cpp b/Source/Core/VideoBackends/OGL/OGLBoundingBox.cpp index b0f7139782..d21326c1f5 100644 --- a/Source/Core/VideoBackends/OGL/OGLBoundingBox.cpp +++ b/Source/Core/VideoBackends/OGL/OGLBoundingBox.cpp @@ -3,7 +3,7 @@ #include "VideoBackends/OGL/OGLBoundingBox.h" -#include "VideoBackends/OGL/OGLRender.h" +#include "VideoBackends/OGL/OGLGfx.h" #include "VideoCommon/DriverDetails.h" namespace OGL @@ -35,7 +35,7 @@ std::vector OGLBoundingBox::Read(u32 index, u32 length) // on nVidia drivers. This is more noticeable at higher internal resolutions. // Using glGetBufferSubData instead does not seem to exhibit this slowdown. if (!DriverDetails::HasBug(DriverDetails::BUG_SLOW_GETBUFFERSUBDATA) && - !static_cast(g_renderer.get())->IsGLES()) + !static_cast(g_gfx.get())->IsGLES()) { // We also need to ensure the the CPU does not receive stale values which have been updated by // the GPU. Apparently the buffer here is not coherent on NVIDIA drivers. Not sure if this is a diff --git a/Source/Core/VideoBackends/OGL/OGLRender.cpp b/Source/Core/VideoBackends/OGL/OGLConfig.cpp similarity index 52% rename from Source/Core/VideoBackends/OGL/OGLRender.cpp rename to Source/Core/VideoBackends/OGL/OGLConfig.cpp index 13549f3d72..bafc28841d 100644 --- a/Source/Core/VideoBackends/OGL/OGLRender.cpp +++ b/Source/Core/VideoBackends/OGL/OGLConfig.cpp @@ -1,135 +1,26 @@ -// Copyright 2008 Dolphin Emulator Project +// Copyright 2023 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "VideoBackends/OGL/OGLRender.h" +#include "VideoBackends/OGL/OGLConfig.h" -#include -#include -#include -#include -#include - -#include "Common/CommonTypes.h" #include "Common/GL/GLContext.h" -#include "Common/GL/GLUtil.h" +#include "Common/GL/GLExtensions/GLExtensions.h" #include "Common/Logging/LogManager.h" -#include "Common/MathUtil.h" #include "Common/MsgHandler.h" -#include "Common/StringUtil.h" #include "Core/Config/GraphicsSettings.h" -#include "VideoBackends/OGL/OGLBoundingBox.h" -#include "VideoBackends/OGL/OGLPipeline.h" -#include "VideoBackends/OGL/OGLShader.h" -#include "VideoBackends/OGL/OGLTexture.h" -#include "VideoBackends/OGL/OGLVertexManager.h" -#include "VideoBackends/OGL/ProgramShaderCache.h" -#include "VideoBackends/OGL/SamplerCache.h" - -#include "VideoCommon/BPFunctions.h" #include "VideoCommon/DriverDetails.h" -#include "VideoCommon/FramebufferManager.h" #include "VideoCommon/OnScreenDisplay.h" -#include "VideoCommon/PostProcessing.h" -#include "VideoCommon/RenderState.h" -#include "VideoCommon/VideoCommon.h" #include "VideoCommon/VideoConfig.h" +#include +#include +#include + namespace OGL { -VideoConfig g_ogl_config; - -static void APIENTRY ErrorCallback(GLenum source, GLenum type, GLuint id, GLenum severity, - GLsizei length, const char* message, const void* userParam) -{ - const char* s_source; - const char* s_type; - - // Performance - DualCore driver performance warning: - // DualCore application thread syncing with server thread - if (id == 0x200b0) - return; - - switch (source) - { - case GL_DEBUG_SOURCE_API_ARB: - s_source = "API"; - break; - case GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB: - s_source = "Window System"; - break; - case GL_DEBUG_SOURCE_SHADER_COMPILER_ARB: - s_source = "Shader Compiler"; - break; - case GL_DEBUG_SOURCE_THIRD_PARTY_ARB: - s_source = "Third Party"; - break; - case GL_DEBUG_SOURCE_APPLICATION_ARB: - s_source = "Application"; - break; - case GL_DEBUG_SOURCE_OTHER_ARB: - s_source = "Other"; - break; - default: - s_source = "Unknown"; - break; - } - switch (type) - { - case GL_DEBUG_TYPE_ERROR_ARB: - s_type = "Error"; - break; - case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB: - s_type = "Deprecated"; - break; - case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB: - s_type = "Undefined"; - break; - case GL_DEBUG_TYPE_PORTABILITY_ARB: - s_type = "Portability"; - break; - case GL_DEBUG_TYPE_PERFORMANCE_ARB: - s_type = "Performance"; - break; - case GL_DEBUG_TYPE_OTHER_ARB: - s_type = "Other"; - break; - default: - s_type = "Unknown"; - break; - } - switch (severity) - { - case GL_DEBUG_SEVERITY_HIGH_ARB: - ERROR_LOG_FMT(HOST_GPU, "id: {:x}, source: {}, type: {} - {}", id, s_source, s_type, message); - break; - case GL_DEBUG_SEVERITY_MEDIUM_ARB: - WARN_LOG_FMT(HOST_GPU, "id: {:x}, source: {}, type: {} - {}", id, s_source, s_type, message); - break; - case GL_DEBUG_SEVERITY_LOW_ARB: - DEBUG_LOG_FMT(HOST_GPU, "id: {:x}, source: {}, type: {} - {}", id, s_source, s_type, message); - break; - case GL_DEBUG_SEVERITY_NOTIFICATION: - DEBUG_LOG_FMT(HOST_GPU, "id: {:x}, source: {}, type: {} - {}", id, s_source, s_type, message); - break; - default: - ERROR_LOG_FMT(HOST_GPU, "id: {:x}, source: {}, type: {} - {}", id, s_source, s_type, message); - break; - } -} - -// Two small Fallbacks to avoid GL_ARB_ES2_compatibility -static void APIENTRY DepthRangef(GLfloat neardepth, GLfloat fardepth) -{ - glDepthRange(neardepth, fardepth); -} -static void APIENTRY ClearDepthf(GLfloat depthval) -{ - glClearDepth(depthval); -} - -static void InitDriverInfo() +void InitDriverInfo() { const std::string_view svendor(g_ogl_config.gl_vendor); const std::string_view srenderer(g_ogl_config.gl_renderer); @@ -319,26 +210,8 @@ static void InitDriverInfo() DriverDetails::Init(DriverDetails::API_OPENGL, vendor, driver, version, family); } -// Init functions -Renderer::Renderer(std::unique_ptr main_gl_context, float backbuffer_scale) - : ::Renderer(static_cast(std::max(main_gl_context->GetBackBufferWidth(), 1u)), - static_cast(std::max(main_gl_context->GetBackBufferHeight(), 1u)), - backbuffer_scale, AbstractTextureFormat::RGBA8), - m_main_gl_context(std::move(main_gl_context)), - m_current_rasterization_state(RenderState::GetInvalidRasterizationState()), - m_current_depth_state(RenderState::GetInvalidDepthState()), - m_current_blend_state(RenderState::GetInvalidBlendingState()) +bool PopulateConfig(GLContext* m_main_gl_context) { - // Create the window framebuffer. - if (!m_main_gl_context->IsHeadless()) - { - m_system_framebuffer = std::make_unique( - nullptr, nullptr, AbstractTextureFormat::RGBA8, AbstractTextureFormat::Undefined, - std::max(m_main_gl_context->GetBackBufferWidth(), 1u), - std::max(m_main_gl_context->GetBackBufferHeight(), 1u), 1, 1, 0); - m_current_framebuffer = m_system_framebuffer.get(); - } - bool bSuccess = true; bool supports_glsl_cache = false; @@ -346,8 +219,6 @@ Renderer::Renderer(std::unique_ptr main_gl_context, float backbuffer_ g_ogl_config.gl_renderer = (const char*)glGetString(GL_RENDERER); g_ogl_config.gl_version = (const char*)glGetString(GL_VERSION); - InitDriverInfo(); - if (!m_main_gl_context->IsGLES()) { if (!GLExtensions::Supports("GL_ARB_framebuffer_object")) @@ -401,15 +272,6 @@ Renderer::Renderer(std::unique_ptr main_gl_context, float backbuffer_ "GPU: Does your video card support OpenGL 3.3?"); bSuccess = false; } - - // OpenGL 3 doesn't provide GLES like float functions for depth. - // They are in core in OpenGL 4.1, so almost every driver should support them. - // But for the oldest ones, we provide fallbacks to the old double functions. - if (!GLExtensions::Supports("GL_ARB_ES2_compatibility")) - { - glDepthRangef = DepthRangef; - glClearDepthf = ClearDepthf; - } } // Copy the GPU name to g_Config, so Analytics can see it. @@ -693,29 +555,6 @@ Renderer::Renderer(std::unique_ptr main_gl_context, float backbuffer_ } g_Config.backend_info.bSupportsPipelineCacheData = supports_glsl_cache; - if (g_ogl_config.bSupportsDebug) - { - if (GLExtensions::Supports("GL_KHR_debug")) - { - glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, true); - glDebugMessageCallback(ErrorCallback, nullptr); - } - else - { - glDebugMessageControlARB(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, true); - glDebugMessageCallbackARB(ErrorCallback, nullptr); - } - if (Common::Log::LogManager::GetInstance()->IsEnabled(Common::Log::LogType::HOST_GPU, - Common::Log::LogLevel::LERROR)) - { - glEnable(GL_DEBUG_OUTPUT); - } - else - { - glDisable(GL_DEBUG_OUTPUT); - } - } - int samples; glGetIntegerv(GL_SAMPLES, &samples); if (samples > 1) @@ -732,11 +571,7 @@ Renderer::Renderer(std::unique_ptr main_gl_context, float backbuffer_ } if (!bSuccess) - { - // Not all needed extensions are supported, so we have to stop here. - // Else some of the next calls might crash. - return; - } + return false; g_Config.VerifyValidity(); UpdateActiveConfig(); @@ -771,565 +606,7 @@ Renderer::Renderer(std::unique_ptr main_gl_context, float backbuffer_ g_ogl_config.bSupportsCopySubImage ? "" : "CopyImageSubData ", g_ActiveConfig.backend_info.bSupportsDepthClamp ? "" : "DepthClamp "); - // Handle VSync on/off - if (!DriverDetails::HasBug(DriverDetails::BUG_BROKEN_VSYNC)) - m_main_gl_context->SwapInterval(g_ActiveConfig.bVSyncActive); - - if (g_ActiveConfig.backend_info.bSupportsClipControl) - glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE); - - if (g_ActiveConfig.backend_info.bSupportsDepthClamp) - { - glEnable(GL_CLIP_DISTANCE0); - glEnable(GL_CLIP_DISTANCE1); - glEnable(GL_DEPTH_CLAMP); - } - - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // 4-byte pixel alignment - - glGenFramebuffers(1, &m_shared_read_framebuffer); - glGenFramebuffers(1, &m_shared_draw_framebuffer); - - if (g_ActiveConfig.backend_info.bSupportsPrimitiveRestart) - GLUtil::EnablePrimitiveRestart(m_main_gl_context.get()); - - UpdateActiveConfig(); -} - -Renderer::~Renderer() = default; - -bool Renderer::IsHeadless() const -{ - return m_main_gl_context->IsHeadless(); -} - -bool Renderer::Initialize() -{ - if (!::Renderer::Initialize()) - return false; - return true; } -void Renderer::Shutdown() -{ - ::Renderer::Shutdown(); - - glDeleteFramebuffers(1, &m_shared_draw_framebuffer); - glDeleteFramebuffers(1, &m_shared_read_framebuffer); -} - -std::unique_ptr Renderer::CreateTexture(const TextureConfig& config, - std::string_view name) -{ - return std::make_unique(config, name); -} - -std::unique_ptr Renderer::CreateStagingTexture(StagingTextureType type, - const TextureConfig& config) -{ - return OGLStagingTexture::Create(type, config); -} - -std::unique_ptr Renderer::CreateFramebuffer(AbstractTexture* color_attachment, - AbstractTexture* depth_attachment) -{ - return OGLFramebuffer::Create(static_cast(color_attachment), - static_cast(depth_attachment)); -} - -std::unique_ptr -Renderer::CreateShaderFromSource(ShaderStage stage, std::string_view source, std::string_view name) -{ - return OGLShader::CreateFromSource(stage, source, name); -} - -std::unique_ptr -Renderer::CreateShaderFromBinary(ShaderStage stage, const void* data, size_t length, - [[maybe_unused]] std::string_view name) -{ - return nullptr; -} - -std::unique_ptr Renderer::CreatePipeline(const AbstractPipelineConfig& config, - const void* cache_data, - size_t cache_data_length) -{ - return OGLPipeline::Create(config, cache_data, cache_data_length); -} - -void Renderer::SetScissorRect(const MathUtil::Rectangle& rc) -{ - glScissor(rc.left, rc.top, rc.GetWidth(), rc.GetHeight()); -} - -std::unique_ptr<::BoundingBox> Renderer::CreateBoundingBox() const -{ - return std::make_unique(); -} - -void Renderer::SetViewport(float x, float y, float width, float height, float near_depth, - float far_depth) -{ - if (g_ogl_config.bSupportViewportFloat) - { - glViewportIndexedf(0, x, y, width, height); - } - else - { - auto iceilf = [](float f) { return static_cast(std::ceil(f)); }; - glViewport(iceilf(x), iceilf(y), iceilf(width), iceilf(height)); - } - - glDepthRangef(near_depth, far_depth); -} - -void Renderer::Draw(u32 base_vertex, u32 num_vertices) -{ - glDrawArrays(static_cast(m_current_pipeline)->GetGLPrimitive(), base_vertex, - num_vertices); -} - -void Renderer::DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) -{ - if (g_ogl_config.bSupportsGLBaseVertex) - { - glDrawElementsBaseVertex(static_cast(m_current_pipeline)->GetGLPrimitive(), - num_indices, GL_UNSIGNED_SHORT, - static_cast(nullptr) + base_index, base_vertex); - } - else - { - glDrawElements(static_cast(m_current_pipeline)->GetGLPrimitive(), - num_indices, GL_UNSIGNED_SHORT, static_cast(nullptr) + base_index); - } -} - -void Renderer::DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u32 groupsize_y, - u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z) -{ - glUseProgram(static_cast(shader)->GetGLComputeProgramID()); - glDispatchCompute(groups_x, groups_y, groups_z); - - // We messed up the program binding, so restore it. - ProgramShaderCache::InvalidateLastProgram(); - if (m_current_pipeline) - static_cast(m_current_pipeline)->GetProgram()->shader.Bind(); - - // Barrier to texture can be used for reads. - if (m_bound_image_texture) - glMemoryBarrier(GL_TEXTURE_UPDATE_BARRIER_BIT); -} - -void Renderer::ClearScreen(const MathUtil::Rectangle& rc, bool colorEnable, bool alphaEnable, - bool zEnable, u32 color, u32 z) -{ - g_framebuffer_manager->FlushEFBPokes(); - g_framebuffer_manager->FlagPeekCacheAsOutOfDate(); - - u32 clear_mask = 0; - if (colorEnable || alphaEnable) - { - glColorMask(colorEnable, colorEnable, colorEnable, alphaEnable); - glClearColor(float((color >> 16) & 0xFF) / 255.0f, float((color >> 8) & 0xFF) / 255.0f, - float((color >> 0) & 0xFF) / 255.0f, float((color >> 24) & 0xFF) / 255.0f); - clear_mask = GL_COLOR_BUFFER_BIT; - } - if (zEnable) - { - glDepthMask(zEnable ? GL_TRUE : GL_FALSE); - glClearDepthf(float(z & 0xFFFFFF) / 16777216.0f); - clear_mask |= GL_DEPTH_BUFFER_BIT; - } - - // Update rect for clearing the picture - // glColorMask/glDepthMask/glScissor affect glClear (glViewport does not) - const auto converted_target_rc = - ConvertFramebufferRectangle(ConvertEFBRectangle(rc), m_current_framebuffer); - SetScissorRect(converted_target_rc); - - glClear(clear_mask); - - // Restore color/depth mask. - if (colorEnable || alphaEnable) - { - glColorMask(m_current_blend_state.colorupdate, m_current_blend_state.colorupdate, - m_current_blend_state.colorupdate, m_current_blend_state.alphaupdate); - } - if (zEnable) - glDepthMask(m_current_depth_state.updateenable); - - // Scissor rect must be restored. - BPFunctions::SetScissorAndViewport(); -} - -void Renderer::RenderXFBToScreen(const MathUtil::Rectangle& target_rc, - const AbstractTexture* source_texture, - const MathUtil::Rectangle& source_rc) -{ - // Quad-buffered stereo is annoying on GL. - if (g_ActiveConfig.stereo_mode != StereoMode::QuadBuffer) - return ::Renderer::RenderXFBToScreen(target_rc, source_texture, source_rc); - - glDrawBuffer(GL_BACK_LEFT); - m_post_processor->BlitFromTexture(target_rc, source_rc, source_texture, 0); - - glDrawBuffer(GL_BACK_RIGHT); - m_post_processor->BlitFromTexture(target_rc, source_rc, source_texture, 1); - - glDrawBuffer(GL_BACK); -} - -void Renderer::SetFramebuffer(AbstractFramebuffer* framebuffer) -{ - if (m_current_framebuffer == framebuffer) - return; - - glBindFramebuffer(GL_FRAMEBUFFER, static_cast(framebuffer)->GetFBO()); - m_current_framebuffer = framebuffer; -} - -void Renderer::SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer) -{ - // EXT_discard_framebuffer could be used here to save bandwidth on tilers. - SetFramebuffer(framebuffer); -} - -void Renderer::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, - const ClearColor& color_value, float depth_value) -{ - SetFramebuffer(framebuffer); - - glDisable(GL_SCISSOR_TEST); - GLbitfield clear_mask = 0; - if (framebuffer->HasColorBuffer()) - { - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glClearColor(color_value[0], color_value[1], color_value[2], color_value[3]); - clear_mask |= GL_COLOR_BUFFER_BIT; - } - if (framebuffer->HasDepthBuffer()) - { - glDepthMask(GL_TRUE); - glClearDepthf(depth_value); - clear_mask |= GL_DEPTH_BUFFER_BIT; - } - glClear(clear_mask); - glEnable(GL_SCISSOR_TEST); - - // Restore color/depth mask. - if (framebuffer->HasColorBuffer()) - { - glColorMask(m_current_blend_state.colorupdate, m_current_blend_state.colorupdate, - m_current_blend_state.colorupdate, m_current_blend_state.alphaupdate); - } - if (framebuffer->HasDepthBuffer()) - glDepthMask(m_current_depth_state.updateenable); -} - -void Renderer::BindBackbuffer(const ClearColor& clear_color) -{ - CheckForSurfaceChange(); - CheckForSurfaceResize(); - SetAndClearFramebuffer(m_system_framebuffer.get(), clear_color); -} - -void Renderer::PresentBackbuffer() -{ - if (g_ogl_config.bSupportsDebug) - { - if (Common::Log::LogManager::GetInstance()->IsEnabled(Common::Log::LogType::HOST_GPU, - Common::Log::LogLevel::LERROR)) - { - glEnable(GL_DEBUG_OUTPUT); - } - else - { - glDisable(GL_DEBUG_OUTPUT); - } - } - - // Swap the back and front buffers, presenting the image. - m_main_gl_context->Swap(); -} - -void Renderer::OnConfigChanged(u32 bits) -{ - if (bits & CONFIG_CHANGE_BIT_VSYNC && !DriverDetails::HasBug(DriverDetails::BUG_BROKEN_VSYNC)) - m_main_gl_context->SwapInterval(g_ActiveConfig.bVSyncActive); - - if (bits & CONFIG_CHANGE_BIT_ANISOTROPY) - g_sampler_cache->Clear(); -} - -void Renderer::Flush() -{ - // ensure all commands are sent to the GPU. - // Otherwise the driver could batch several frames together. - glFlush(); -} - -void Renderer::WaitForGPUIdle() -{ - glFinish(); -} - -void Renderer::CheckForSurfaceChange() -{ - if (!m_surface_changed.TestAndClear()) - return; - - m_main_gl_context->UpdateSurface(m_new_surface_handle); - m_new_surface_handle = nullptr; - - // 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); -} - -void Renderer::CheckForSurfaceResize() -{ - if (!m_surface_resized.TestAndClear()) - 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); -} - -void Renderer::BeginUtilityDrawing() -{ - ::Renderer::BeginUtilityDrawing(); - if (g_ActiveConfig.backend_info.bSupportsDepthClamp) - { - glDisable(GL_CLIP_DISTANCE0); - glDisable(GL_CLIP_DISTANCE1); - } -} - -void Renderer::EndUtilityDrawing() -{ - ::Renderer::EndUtilityDrawing(); - if (g_ActiveConfig.backend_info.bSupportsDepthClamp) - { - glEnable(GL_CLIP_DISTANCE0); - glEnable(GL_CLIP_DISTANCE1); - } -} - -void Renderer::ApplyRasterizationState(const RasterizationState state) -{ - if (m_current_rasterization_state == state) - return; - - // none, ccw, cw, ccw - if (state.cullmode != CullMode::None) - { - // TODO: GX_CULL_ALL not supported, yet! - glEnable(GL_CULL_FACE); - glFrontFace(state.cullmode == CullMode::Front ? GL_CCW : GL_CW); - } - else - { - glDisable(GL_CULL_FACE); - } - - m_current_rasterization_state = state; -} - -void Renderer::ApplyDepthState(const DepthState state) -{ - if (m_current_depth_state == state) - return; - - const GLenum glCmpFuncs[8] = {GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, - GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, GL_ALWAYS}; - - if (state.testenable) - { - glEnable(GL_DEPTH_TEST); - glDepthMask(state.updateenable ? GL_TRUE : GL_FALSE); - glDepthFunc(glCmpFuncs[u32(state.func.Value())]); - } - else - { - // if the test is disabled write is disabled too - // TODO: When PE performance metrics are being emulated via occlusion queries, we should - // (probably?) enable depth test with depth function ALWAYS here - glDisable(GL_DEPTH_TEST); - glDepthMask(GL_FALSE); - } - - m_current_depth_state = state; -} - -void Renderer::ApplyBlendingState(const BlendingState state) -{ - if (m_current_blend_state == state) - return; - - bool useDualSource = state.usedualsrc; - - const GLenum src_factors[8] = {GL_ZERO, - GL_ONE, - GL_DST_COLOR, - GL_ONE_MINUS_DST_COLOR, - useDualSource ? GL_SRC1_ALPHA : (GLenum)GL_SRC_ALPHA, - useDualSource ? GL_ONE_MINUS_SRC1_ALPHA : - (GLenum)GL_ONE_MINUS_SRC_ALPHA, - GL_DST_ALPHA, - GL_ONE_MINUS_DST_ALPHA}; - const GLenum dst_factors[8] = {GL_ZERO, - GL_ONE, - GL_SRC_COLOR, - GL_ONE_MINUS_SRC_COLOR, - useDualSource ? GL_SRC1_ALPHA : (GLenum)GL_SRC_ALPHA, - useDualSource ? GL_ONE_MINUS_SRC1_ALPHA : - (GLenum)GL_ONE_MINUS_SRC_ALPHA, - GL_DST_ALPHA, - GL_ONE_MINUS_DST_ALPHA}; - - if (state.blendenable) - glEnable(GL_BLEND); - else - glDisable(GL_BLEND); - - // Always call glBlendEquationSeparate and glBlendFuncSeparate, even when - // GL_BLEND is disabled, as a workaround for some bugs (possibly graphics - // driver issues?). See https://bugs.dolphin-emu.org/issues/10120 : "Sonic - // Adventure 2 Battle: graphics crash when loading first Dark level" - GLenum equation = state.subtract ? GL_FUNC_REVERSE_SUBTRACT : GL_FUNC_ADD; - GLenum equationAlpha = state.subtractAlpha ? GL_FUNC_REVERSE_SUBTRACT : GL_FUNC_ADD; - glBlendEquationSeparate(equation, equationAlpha); - glBlendFuncSeparate(src_factors[u32(state.srcfactor.Value())], - dst_factors[u32(state.dstfactor.Value())], - src_factors[u32(state.srcfactoralpha.Value())], - dst_factors[u32(state.dstfactoralpha.Value())]); - - const GLenum logic_op_codes[16] = { - GL_CLEAR, GL_AND, GL_AND_REVERSE, GL_COPY, GL_AND_INVERTED, GL_NOOP, - GL_XOR, GL_OR, GL_NOR, GL_EQUIV, GL_INVERT, GL_OR_REVERSE, - GL_COPY_INVERTED, GL_OR_INVERTED, GL_NAND, GL_SET}; - - // Logic ops aren't available in GLES3 - if (!IsGLES()) - { - if (state.logicopenable) - { - glEnable(GL_COLOR_LOGIC_OP); - glLogicOp(logic_op_codes[u32(state.logicmode.Value())]); - } - else - { - glDisable(GL_COLOR_LOGIC_OP); - } - } - - glColorMask(state.colorupdate, state.colorupdate, state.colorupdate, state.alphaupdate); - m_current_blend_state = state; -} - -void Renderer::SetPipeline(const AbstractPipeline* pipeline) -{ - if (m_current_pipeline == pipeline) - return; - - if (pipeline) - { - ApplyRasterizationState(static_cast(pipeline)->GetRasterizationState()); - ApplyDepthState(static_cast(pipeline)->GetDepthState()); - ApplyBlendingState(static_cast(pipeline)->GetBlendingState()); - ProgramShaderCache::BindVertexFormat( - static_cast(pipeline)->GetVertexFormat()); - static_cast(pipeline)->GetProgram()->shader.Bind(); - } - else - { - ProgramShaderCache::InvalidateLastProgram(); - glUseProgram(0); - } - m_current_pipeline = pipeline; -} - -void Renderer::SetTexture(u32 index, const AbstractTexture* texture) -{ - const OGLTexture* gl_texture = static_cast(texture); - if (m_bound_textures[index] == gl_texture) - return; - - glActiveTexture(GL_TEXTURE0 + index); - if (gl_texture) - glBindTexture(gl_texture->GetGLTarget(), gl_texture->GetGLTextureId()); - else - glBindTexture(GL_TEXTURE_2D_ARRAY, 0); - m_bound_textures[index] = gl_texture; -} - -void Renderer::SetSamplerState(u32 index, const SamplerState& state) -{ - g_sampler_cache->SetSamplerState(index, state); -} - -void Renderer::SetComputeImageTexture(AbstractTexture* texture, bool read, bool write) -{ - if (m_bound_image_texture == texture) - return; - - if (texture) - { - const GLenum access = read ? (write ? GL_READ_WRITE : GL_READ_ONLY) : GL_WRITE_ONLY; - glBindImageTexture(0, static_cast(texture)->GetGLTextureId(), 0, GL_TRUE, 0, - access, static_cast(texture)->GetGLFormatForImageTexture()); - } - else - { - glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8); - } - - m_bound_image_texture = texture; -} - -void Renderer::UnbindTexture(const AbstractTexture* texture) -{ - for (size_t i = 0; i < m_bound_textures.size(); i++) - { - if (m_bound_textures[i] != texture) - continue; - - glActiveTexture(static_cast(GL_TEXTURE0 + i)); - glBindTexture(GL_TEXTURE_2D_ARRAY, 0); - m_bound_textures[i] = nullptr; - } - - if (m_bound_image_texture == texture) - { - glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8); - m_bound_image_texture = nullptr; - } -} - -std::unique_ptr Renderer::CreateAsyncShaderCompiler() -{ - return std::make_unique(); -} - -void Renderer::BindSharedReadFramebuffer() -{ - glBindFramebuffer(GL_READ_FRAMEBUFFER, m_shared_read_framebuffer); -} - -void Renderer::BindSharedDrawFramebuffer() -{ - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_shared_draw_framebuffer); -} - -void Renderer::RestoreFramebufferBinding() -{ - glBindFramebuffer( - GL_FRAMEBUFFER, - m_current_framebuffer ? static_cast(m_current_framebuffer)->GetFBO() : 0); -} - } // namespace OGL diff --git a/Source/Core/VideoBackends/OGL/OGLConfig.h b/Source/Core/VideoBackends/OGL/OGLConfig.h new file mode 100644 index 0000000000..2aece68896 --- /dev/null +++ b/Source/Core/VideoBackends/OGL/OGLConfig.h @@ -0,0 +1,78 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Common/CommonTypes.h" + +class GLContext; + +namespace OGL +{ +enum GlslVersion +{ + Glsl130, + Glsl140, + Glsl150, + Glsl330, + Glsl400, // and above + Glsl430, + GlslEs300, // GLES 3.0 + GlslEs310, // GLES 3.1 + GlslEs320, // GLES 3.2 +}; +enum class EsTexbufType +{ + TexbufNone, + TexbufCore, + TexbufOes, + TexbufExt +}; + +enum class EsFbFetchType +{ + FbFetchNone, + FbFetchExt, + FbFetchArm, +}; + +// ogl-only config, so not in VideoConfig.h +struct VideoConfig +{ + bool bIsES; + bool bSupportsGLPinnedMemory; + bool bSupportsGLSync; + bool bSupportsGLBaseVertex; + bool bSupportsGLBufferStorage; + bool bSupportsMSAA; + GlslVersion eSupportedGLSLVersion; + bool bSupportViewportFloat; + bool bSupportsAEP; + bool bSupportsDebug; + bool bSupportsCopySubImage; + u8 SupportedESPointSize; + EsTexbufType SupportedESTextureBuffer; + bool bSupportsTextureStorage; + bool bSupports2DTextureStorageMultisample; + bool bSupports3DTextureStorageMultisample; + bool bSupportsConservativeDepth; + bool bSupportsImageLoadStore; + bool bSupportsAniso; + bool bSupportsBitfield; + bool bSupportsTextureSubImage; + EsFbFetchType SupportedFramebufferFetch; + bool bSupportsShaderThreadShuffleNV; + + const char* gl_vendor; + const char* gl_renderer; + const char* gl_version; + + s32 max_samples; +}; + +void InitDriverInfo(); +bool PopulateConfig(GLContext* main_gl_context); + +extern VideoConfig g_ogl_config; + +} // namespace OGL diff --git a/Source/Core/VideoBackends/OGL/OGLGfx.cpp b/Source/Core/VideoBackends/OGL/OGLGfx.cpp new file mode 100644 index 0000000000..880b213fb8 --- /dev/null +++ b/Source/Core/VideoBackends/OGL/OGLGfx.cpp @@ -0,0 +1,731 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoBackends/OGL/OGLGfx.h" + +#include "Common/GL/GLContext.h" +#include "Common/GL/GLExtensions/GLExtensions.h" +#include "Common/Logging/LogManager.h" + +#include "Core/Config/GraphicsSettings.h" + +#include "VideoBackends/OGL/OGLConfig.h" +#include "VideoBackends/OGL/OGLPipeline.h" +#include "VideoBackends/OGL/OGLShader.h" +#include "VideoBackends/OGL/OGLTexture.h" +#include "VideoBackends/OGL/ProgramShaderCache.h" +#include "VideoBackends/OGL/SamplerCache.h" + +#include "VideoCommon/AsyncShaderCompiler.h" +#include "VideoCommon/DriverDetails.h" +#include "VideoCommon/OnScreenDisplay.h" +#include "VideoCommon/Present.h" +#include "VideoCommon/VideoConfig.h" + +#include + +namespace OGL +{ +VideoConfig g_ogl_config; + +static void APIENTRY ErrorCallback(GLenum source, GLenum type, GLuint id, GLenum severity, + GLsizei length, const char* message, const void* userParam) +{ + const char* s_source; + const char* s_type; + + // Performance - DualCore driver performance warning: + // DualCore application thread syncing with server thread + if (id == 0x200b0) + return; + + switch (source) + { + case GL_DEBUG_SOURCE_API_ARB: + s_source = "API"; + break; + case GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB: + s_source = "Window System"; + break; + case GL_DEBUG_SOURCE_SHADER_COMPILER_ARB: + s_source = "Shader Compiler"; + break; + case GL_DEBUG_SOURCE_THIRD_PARTY_ARB: + s_source = "Third Party"; + break; + case GL_DEBUG_SOURCE_APPLICATION_ARB: + s_source = "Application"; + break; + case GL_DEBUG_SOURCE_OTHER_ARB: + s_source = "Other"; + break; + default: + s_source = "Unknown"; + break; + } + switch (type) + { + case GL_DEBUG_TYPE_ERROR_ARB: + s_type = "Error"; + break; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB: + s_type = "Deprecated"; + break; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB: + s_type = "Undefined"; + break; + case GL_DEBUG_TYPE_PORTABILITY_ARB: + s_type = "Portability"; + break; + case GL_DEBUG_TYPE_PERFORMANCE_ARB: + s_type = "Performance"; + break; + case GL_DEBUG_TYPE_OTHER_ARB: + s_type = "Other"; + break; + default: + s_type = "Unknown"; + break; + } + switch (severity) + { + case GL_DEBUG_SEVERITY_HIGH_ARB: + ERROR_LOG_FMT(HOST_GPU, "id: {:x}, source: {}, type: {} - {}", id, s_source, s_type, message); + break; + case GL_DEBUG_SEVERITY_MEDIUM_ARB: + WARN_LOG_FMT(HOST_GPU, "id: {:x}, source: {}, type: {} - {}", id, s_source, s_type, message); + break; + case GL_DEBUG_SEVERITY_LOW_ARB: + DEBUG_LOG_FMT(HOST_GPU, "id: {:x}, source: {}, type: {} - {}", id, s_source, s_type, message); + break; + case GL_DEBUG_SEVERITY_NOTIFICATION: + DEBUG_LOG_FMT(HOST_GPU, "id: {:x}, source: {}, type: {} - {}", id, s_source, s_type, message); + break; + default: + ERROR_LOG_FMT(HOST_GPU, "id: {:x}, source: {}, type: {} - {}", id, s_source, s_type, message); + break; + } +} + +// Two small Fallbacks to avoid GL_ARB_ES2_compatibility +static void APIENTRY DepthRangef(GLfloat neardepth, GLfloat fardepth) +{ + glDepthRange(neardepth, fardepth); +} +static void APIENTRY ClearDepthf(GLfloat depthval) +{ + glClearDepth(depthval); +} + +OGLGfx::OGLGfx(std::unique_ptr main_gl_context, float backbuffer_scale) + : m_main_gl_context(std::move(main_gl_context)), + m_current_rasterization_state(RenderState::GetInvalidRasterizationState()), + m_current_depth_state(RenderState::GetInvalidDepthState()), + m_current_blend_state(RenderState::GetInvalidBlendingState()), + m_backbuffer_scale(backbuffer_scale) +{ + // Create the window framebuffer. + if (!m_main_gl_context->IsHeadless()) + { + m_system_framebuffer = std::make_unique( + nullptr, nullptr, AbstractTextureFormat::RGBA8, AbstractTextureFormat::Undefined, + std::max(m_main_gl_context->GetBackBufferWidth(), 1u), + std::max(m_main_gl_context->GetBackBufferHeight(), 1u), 1, 1, 0); + m_current_framebuffer = m_system_framebuffer.get(); + } + + if (m_main_gl_context->IsGLES()) + { + // OpenGL 3 doesn't provide GLES like float functions for depth. + // They are in core in OpenGL 4.1, so almost every driver should support them. + // But for the oldest ones, we provide fallbacks to the old double functions. + if (!GLExtensions::Supports("GL_ARB_ES2_compatibility")) + { + glDepthRangef = DepthRangef; + glClearDepthf = ClearDepthf; + } + } + + if (!PopulateConfig(m_main_gl_context.get())) + { + // Not all needed extensions are supported, so we have to stop here. + // Else some of the next calls might crash. + return; + } + InitDriverInfo(); + + // Setup Debug logging + if (g_ogl_config.bSupportsDebug) + { + if (GLExtensions::Supports("GL_KHR_debug")) + { + glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, true); + glDebugMessageCallback(ErrorCallback, nullptr); + } + else + { + glDebugMessageControlARB(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, true); + glDebugMessageCallbackARB(ErrorCallback, nullptr); + } + if (Common::Log::LogManager::GetInstance()->IsEnabled(Common::Log::LogType::HOST_GPU, + Common::Log::LogLevel::LERROR)) + { + glEnable(GL_DEBUG_OUTPUT); + } + else + { + glDisable(GL_DEBUG_OUTPUT); + } + } + + // Handle VSync on/off + if (!DriverDetails::HasBug(DriverDetails::BUG_BROKEN_VSYNC)) + m_main_gl_context->SwapInterval(g_ActiveConfig.bVSyncActive); + + if (g_ActiveConfig.backend_info.bSupportsClipControl) + glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE); + + if (g_ActiveConfig.backend_info.bSupportsDepthClamp) + { + glEnable(GL_CLIP_DISTANCE0); + glEnable(GL_CLIP_DISTANCE1); + glEnable(GL_DEPTH_CLAMP); + } + + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // 4-byte pixel alignment + + glGenFramebuffers(1, &m_shared_read_framebuffer); + glGenFramebuffers(1, &m_shared_draw_framebuffer); + + if (g_ActiveConfig.backend_info.bSupportsPrimitiveRestart) + GLUtil::EnablePrimitiveRestart(m_main_gl_context.get()); + + UpdateActiveConfig(); +} + +OGLGfx::~OGLGfx() +{ + glDeleteFramebuffers(1, &m_shared_draw_framebuffer); + glDeleteFramebuffers(1, &m_shared_read_framebuffer); +} + +bool OGLGfx::IsHeadless() const +{ + return m_main_gl_context->IsHeadless(); +} + +std::unique_ptr OGLGfx::CreateTexture(const TextureConfig& config, + std::string_view name) +{ + return std::make_unique(config, name); +} + +std::unique_ptr OGLGfx::CreateStagingTexture(StagingTextureType type, + const TextureConfig& config) +{ + return OGLStagingTexture::Create(type, config); +} + +std::unique_ptr OGLGfx::CreateFramebuffer(AbstractTexture* color_attachment, + AbstractTexture* depth_attachment) +{ + return OGLFramebuffer::Create(static_cast(color_attachment), + static_cast(depth_attachment)); +} + +std::unique_ptr +OGLGfx::CreateShaderFromSource(ShaderStage stage, std::string_view source, std::string_view name) +{ + return OGLShader::CreateFromSource(stage, source, name); +} + +std::unique_ptr +OGLGfx::CreateShaderFromBinary(ShaderStage stage, const void* data, size_t length, + [[maybe_unused]] std::string_view name) +{ + return nullptr; +} + +std::unique_ptr OGLGfx::CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data, + size_t cache_data_length) +{ + return OGLPipeline::Create(config, cache_data, cache_data_length); +} + +void OGLGfx::SetScissorRect(const MathUtil::Rectangle& rc) +{ + glScissor(rc.left, rc.top, rc.GetWidth(), rc.GetHeight()); +} + +void OGLGfx::SetViewport(float x, float y, float width, float height, float near_depth, + float far_depth) +{ + if (g_ogl_config.bSupportViewportFloat) + { + glViewportIndexedf(0, x, y, width, height); + } + else + { + auto iceilf = [](float f) { return static_cast(std::ceil(f)); }; + glViewport(iceilf(x), iceilf(y), iceilf(width), iceilf(height)); + } + + glDepthRangef(near_depth, far_depth); +} + +void OGLGfx::Draw(u32 base_vertex, u32 num_vertices) +{ + glDrawArrays(static_cast(m_current_pipeline)->GetGLPrimitive(), base_vertex, + num_vertices); +} + +void OGLGfx::DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) +{ + if (g_ogl_config.bSupportsGLBaseVertex) + { + glDrawElementsBaseVertex(static_cast(m_current_pipeline)->GetGLPrimitive(), + num_indices, GL_UNSIGNED_SHORT, + static_cast(nullptr) + base_index, base_vertex); + } + else + { + glDrawElements(static_cast(m_current_pipeline)->GetGLPrimitive(), + num_indices, GL_UNSIGNED_SHORT, static_cast(nullptr) + base_index); + } +} + +void OGLGfx::DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u32 groupsize_y, + u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z) +{ + glUseProgram(static_cast(shader)->GetGLComputeProgramID()); + glDispatchCompute(groups_x, groups_y, groups_z); + + // We messed up the program binding, so restore it. + ProgramShaderCache::InvalidateLastProgram(); + if (m_current_pipeline) + static_cast(m_current_pipeline)->GetProgram()->shader.Bind(); + + // Barrier to texture can be used for reads. + if (m_bound_image_texture) + glMemoryBarrier(GL_TEXTURE_UPDATE_BARRIER_BIT); +} + +void OGLGfx::SelectLeftBuffer() +{ + glDrawBuffer(GL_BACK_LEFT); +} + +void OGLGfx::SelectRightBuffer() +{ + glDrawBuffer(GL_BACK_RIGHT); +} + +void OGLGfx::SelectMainBuffer() +{ + glDrawBuffer(GL_BACK); +} + +void OGLGfx::SetFramebuffer(AbstractFramebuffer* framebuffer) +{ + if (m_current_framebuffer == framebuffer) + return; + + glBindFramebuffer(GL_FRAMEBUFFER, static_cast(framebuffer)->GetFBO()); + m_current_framebuffer = framebuffer; +} + +void OGLGfx::SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer) +{ + // EXT_discard_framebuffer could be used here to save bandwidth on tilers. + SetFramebuffer(framebuffer); +} + +void OGLGfx::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, const ClearColor& color_value, + float depth_value) +{ + SetFramebuffer(framebuffer); + + glDisable(GL_SCISSOR_TEST); + GLbitfield clear_mask = 0; + if (framebuffer->HasColorBuffer()) + { + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glClearColor(color_value[0], color_value[1], color_value[2], color_value[3]); + clear_mask |= GL_COLOR_BUFFER_BIT; + } + if (framebuffer->HasDepthBuffer()) + { + glDepthMask(GL_TRUE); + glClearDepthf(depth_value); + clear_mask |= GL_DEPTH_BUFFER_BIT; + } + glClear(clear_mask); + glEnable(GL_SCISSOR_TEST); + + // Restore color/depth mask. + if (framebuffer->HasColorBuffer()) + { + glColorMask(m_current_blend_state.colorupdate, m_current_blend_state.colorupdate, + m_current_blend_state.colorupdate, m_current_blend_state.alphaupdate); + } + if (framebuffer->HasDepthBuffer()) + glDepthMask(m_current_depth_state.updateenable); +} + +void OGLGfx::ClearRegion(const MathUtil::Rectangle& target_rc, bool colorEnable, + bool alphaEnable, bool zEnable, u32 color, u32 z) +{ + u32 clear_mask = 0; + if (colorEnable || alphaEnable) + { + glColorMask(colorEnable, colorEnable, colorEnable, alphaEnable); + glClearColor(float((color >> 16) & 0xFF) / 255.0f, float((color >> 8) & 0xFF) / 255.0f, + float((color >> 0) & 0xFF) / 255.0f, float((color >> 24) & 0xFF) / 255.0f); + clear_mask = GL_COLOR_BUFFER_BIT; + } + if (zEnable) + { + glDepthMask(zEnable ? GL_TRUE : GL_FALSE); + glClearDepthf(float(z & 0xFFFFFF) / 16777216.0f); + clear_mask |= GL_DEPTH_BUFFER_BIT; + } + + // Update rect for clearing the picture + // glColorMask/glDepthMask/glScissor affect glClear (glViewport does not) + g_gfx->SetScissorRect(target_rc); + + glClear(clear_mask); + + // Restore color/depth mask. + if (colorEnable || alphaEnable) + { + glColorMask(m_current_blend_state.colorupdate, m_current_blend_state.colorupdate, + m_current_blend_state.colorupdate, m_current_blend_state.alphaupdate); + } + if (zEnable) + glDepthMask(m_current_depth_state.updateenable); +} + +void OGLGfx::BindBackbuffer(const ClearColor& clear_color) +{ + CheckForSurfaceChange(); + CheckForSurfaceResize(); + SetAndClearFramebuffer(m_system_framebuffer.get(), clear_color); +} + +void OGLGfx::PresentBackbuffer() +{ + if (g_ogl_config.bSupportsDebug) + { + if (Common::Log::LogManager::GetInstance()->IsEnabled(Common::Log::LogType::HOST_GPU, + Common::Log::LogLevel::LERROR)) + { + glEnable(GL_DEBUG_OUTPUT); + } + else + { + glDisable(GL_DEBUG_OUTPUT); + } + } + + // Swap the back and front buffers, presenting the image. + m_main_gl_context->Swap(); +} + +void OGLGfx::OnConfigChanged(u32 bits) +{ + AbstractGfx::OnConfigChanged(bits); + + if (bits & CONFIG_CHANGE_BIT_VSYNC && !DriverDetails::HasBug(DriverDetails::BUG_BROKEN_VSYNC)) + m_main_gl_context->SwapInterval(g_ActiveConfig.bVSyncActive); + + if (bits & CONFIG_CHANGE_BIT_ANISOTROPY) + g_sampler_cache->Clear(); +} + +void OGLGfx::Flush() +{ + // ensure all commands are sent to the GPU. + // Otherwise the driver could batch several frames together. + glFlush(); +} + +void OGLGfx::WaitForGPUIdle() +{ + glFinish(); +} + +void OGLGfx::CheckForSurfaceChange() +{ + if (!g_presenter->SurfaceChangedTestAndClear()) + return; + + 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. + g_presenter->SetBackbuffer(width, height); + m_system_framebuffer->UpdateDimensions(width, height); +} + +void OGLGfx::CheckForSurfaceResize() +{ + if (!g_presenter->SurfaceResizedTestAndClear()) + return; + + m_main_gl_context->Update(); + 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 OGLGfx::BeginUtilityDrawing() +{ + AbstractGfx::BeginUtilityDrawing(); + if (g_ActiveConfig.backend_info.bSupportsDepthClamp) + { + glDisable(GL_CLIP_DISTANCE0); + glDisable(GL_CLIP_DISTANCE1); + } +} + +void OGLGfx::EndUtilityDrawing() +{ + AbstractGfx::EndUtilityDrawing(); + if (g_ActiveConfig.backend_info.bSupportsDepthClamp) + { + glEnable(GL_CLIP_DISTANCE0); + glEnable(GL_CLIP_DISTANCE1); + } +} + +void OGLGfx::ApplyRasterizationState(const RasterizationState state) +{ + if (m_current_rasterization_state == state) + return; + + // none, ccw, cw, ccw + if (state.cullmode != CullMode::None) + { + // TODO: GX_CULL_ALL not supported, yet! + glEnable(GL_CULL_FACE); + glFrontFace(state.cullmode == CullMode::Front ? GL_CCW : GL_CW); + } + else + { + glDisable(GL_CULL_FACE); + } + + m_current_rasterization_state = state; +} + +void OGLGfx::ApplyDepthState(const DepthState state) +{ + if (m_current_depth_state == state) + return; + + const GLenum glCmpFuncs[8] = {GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, + GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, GL_ALWAYS}; + + if (state.testenable) + { + glEnable(GL_DEPTH_TEST); + glDepthMask(state.updateenable ? GL_TRUE : GL_FALSE); + glDepthFunc(glCmpFuncs[u32(state.func.Value())]); + } + else + { + // if the test is disabled write is disabled too + // TODO: When PE performance metrics are being emulated via occlusion queries, we should + // (probably?) enable depth test with depth function ALWAYS here + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + } + + m_current_depth_state = state; +} + +void OGLGfx::ApplyBlendingState(const BlendingState state) +{ + if (m_current_blend_state == state) + return; + + bool useDualSource = state.usedualsrc; + + const GLenum src_factors[8] = {GL_ZERO, + GL_ONE, + GL_DST_COLOR, + GL_ONE_MINUS_DST_COLOR, + useDualSource ? GL_SRC1_ALPHA : (GLenum)GL_SRC_ALPHA, + useDualSource ? GL_ONE_MINUS_SRC1_ALPHA : + (GLenum)GL_ONE_MINUS_SRC_ALPHA, + GL_DST_ALPHA, + GL_ONE_MINUS_DST_ALPHA}; + const GLenum dst_factors[8] = {GL_ZERO, + GL_ONE, + GL_SRC_COLOR, + GL_ONE_MINUS_SRC_COLOR, + useDualSource ? GL_SRC1_ALPHA : (GLenum)GL_SRC_ALPHA, + useDualSource ? GL_ONE_MINUS_SRC1_ALPHA : + (GLenum)GL_ONE_MINUS_SRC_ALPHA, + GL_DST_ALPHA, + GL_ONE_MINUS_DST_ALPHA}; + + if (state.blendenable) + glEnable(GL_BLEND); + else + glDisable(GL_BLEND); + + // Always call glBlendEquationSeparate and glBlendFuncSeparate, even when + // GL_BLEND is disabled, as a workaround for some bugs (possibly graphics + // driver issues?). See https://bugs.dolphin-emu.org/issues/10120 : "Sonic + // Adventure 2 Battle: graphics crash when loading first Dark level" + GLenum equation = state.subtract ? GL_FUNC_REVERSE_SUBTRACT : GL_FUNC_ADD; + GLenum equationAlpha = state.subtractAlpha ? GL_FUNC_REVERSE_SUBTRACT : GL_FUNC_ADD; + glBlendEquationSeparate(equation, equationAlpha); + glBlendFuncSeparate(src_factors[u32(state.srcfactor.Value())], + dst_factors[u32(state.dstfactor.Value())], + src_factors[u32(state.srcfactoralpha.Value())], + dst_factors[u32(state.dstfactoralpha.Value())]); + + const GLenum logic_op_codes[16] = { + GL_CLEAR, GL_AND, GL_AND_REVERSE, GL_COPY, GL_AND_INVERTED, GL_NOOP, + GL_XOR, GL_OR, GL_NOR, GL_EQUIV, GL_INVERT, GL_OR_REVERSE, + GL_COPY_INVERTED, GL_OR_INVERTED, GL_NAND, GL_SET}; + + // Logic ops aren't available in GLES3 + if (!IsGLES()) + { + if (state.logicopenable) + { + glEnable(GL_COLOR_LOGIC_OP); + glLogicOp(logic_op_codes[u32(state.logicmode.Value())]); + } + else + { + glDisable(GL_COLOR_LOGIC_OP); + } + } + + glColorMask(state.colorupdate, state.colorupdate, state.colorupdate, state.alphaupdate); + m_current_blend_state = state; +} + +void OGLGfx::SetPipeline(const AbstractPipeline* pipeline) +{ + if (m_current_pipeline == pipeline) + return; + + if (pipeline) + { + ApplyRasterizationState(static_cast(pipeline)->GetRasterizationState()); + ApplyDepthState(static_cast(pipeline)->GetDepthState()); + ApplyBlendingState(static_cast(pipeline)->GetBlendingState()); + ProgramShaderCache::BindVertexFormat( + static_cast(pipeline)->GetVertexFormat()); + static_cast(pipeline)->GetProgram()->shader.Bind(); + } + else + { + ProgramShaderCache::InvalidateLastProgram(); + glUseProgram(0); + } + m_current_pipeline = pipeline; +} + +void OGLGfx::SetTexture(u32 index, const AbstractTexture* texture) +{ + const OGLTexture* gl_texture = static_cast(texture); + if (m_bound_textures[index] == gl_texture) + return; + + glActiveTexture(GL_TEXTURE0 + index); + if (gl_texture) + glBindTexture(gl_texture->GetGLTarget(), gl_texture->GetGLTextureId()); + else + glBindTexture(GL_TEXTURE_2D_ARRAY, 0); + m_bound_textures[index] = gl_texture; +} + +void OGLGfx::SetSamplerState(u32 index, const SamplerState& state) +{ + g_sampler_cache->SetSamplerState(index, state); +} + +void OGLGfx::SetComputeImageTexture(AbstractTexture* texture, bool read, bool write) +{ + if (m_bound_image_texture == texture) + return; + + if (texture) + { + const GLenum access = read ? (write ? GL_READ_WRITE : GL_READ_ONLY) : GL_WRITE_ONLY; + glBindImageTexture(0, static_cast(texture)->GetGLTextureId(), 0, GL_TRUE, 0, + access, static_cast(texture)->GetGLFormatForImageTexture()); + } + else + { + glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8); + } + + m_bound_image_texture = texture; +} + +void OGLGfx::UnbindTexture(const AbstractTexture* texture) +{ + for (size_t i = 0; i < m_bound_textures.size(); i++) + { + if (m_bound_textures[i] != texture) + continue; + + glActiveTexture(static_cast(GL_TEXTURE0 + i)); + glBindTexture(GL_TEXTURE_2D_ARRAY, 0); + m_bound_textures[i] = nullptr; + } + + if (m_bound_image_texture == texture) + { + glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8); + m_bound_image_texture = nullptr; + } +} + +std::unique_ptr OGLGfx::CreateAsyncShaderCompiler() +{ + return std::make_unique(); +} + +bool OGLGfx::IsGLES() const +{ + return m_main_gl_context->IsGLES(); +} + +void OGLGfx::BindSharedReadFramebuffer() +{ + glBindFramebuffer(GL_READ_FRAMEBUFFER, m_shared_read_framebuffer); +} + +void OGLGfx::BindSharedDrawFramebuffer() +{ + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_shared_draw_framebuffer); +} + +void OGLGfx::RestoreFramebufferBinding() +{ + glBindFramebuffer( + GL_FRAMEBUFFER, + m_current_framebuffer ? static_cast(m_current_framebuffer)->GetFBO() : 0); +} + +SurfaceInfo OGLGfx::GetSurfaceInfo() const +{ + return {std::max(m_main_gl_context->GetBackBufferWidth(), 1u), + std::max(m_main_gl_context->GetBackBufferHeight(), 1u), m_backbuffer_scale, + AbstractTextureFormat::RGBA8}; +} + +} // namespace OGL diff --git a/Source/Core/VideoBackends/OGL/OGLRender.h b/Source/Core/VideoBackends/OGL/OGLGfx.h similarity index 63% rename from Source/Core/VideoBackends/OGL/OGLRender.h rename to Source/Core/VideoBackends/OGL/OGLGfx.h index 72da03ef29..410b7d562e 100644 --- a/Source/Core/VideoBackends/OGL/OGLRender.h +++ b/Source/Core/VideoBackends/OGL/OGLGfx.h @@ -1,99 +1,25 @@ -// Copyright 2008 Dolphin Emulator Project +// Copyright 2023 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once -#include -#include -#include +#include "VideoCommon/AbstractGfx.h" -#include "Common/GL/GLContext.h" -#include "Common/GL/GLExtensions/GLExtensions.h" -#include "VideoCommon/RenderBase.h" - -class BoundingBox; +class GLContext; namespace OGL { class OGLFramebuffer; -class OGLPipeline; class OGLTexture; -enum GlslVersion -{ - Glsl130, - Glsl140, - Glsl150, - Glsl330, - Glsl400, // and above - Glsl430, - GlslEs300, // GLES 3.0 - GlslEs310, // GLES 3.1 - GlslEs320, // GLES 3.2 -}; -enum class EsTexbufType -{ - TexbufNone, - TexbufCore, - TexbufOes, - TexbufExt -}; - -enum class EsFbFetchType -{ - FbFetchNone, - FbFetchExt, - FbFetchArm, -}; - -// ogl-only config, so not in VideoConfig.h -struct VideoConfig -{ - bool bIsES; - bool bSupportsGLPinnedMemory; - bool bSupportsGLSync; - bool bSupportsGLBaseVertex; - bool bSupportsGLBufferStorage; - bool bSupportsMSAA; - GlslVersion eSupportedGLSLVersion; - bool bSupportViewportFloat; - bool bSupportsAEP; - bool bSupportsDebug; - bool bSupportsCopySubImage; - u8 SupportedESPointSize; - EsTexbufType SupportedESTextureBuffer; - bool bSupportsTextureStorage; - bool bSupports2DTextureStorageMultisample; - bool bSupports3DTextureStorageMultisample; - bool bSupportsConservativeDepth; - bool bSupportsImageLoadStore; - bool bSupportsAniso; - bool bSupportsBitfield; - bool bSupportsTextureSubImage; - EsFbFetchType SupportedFramebufferFetch; - bool bSupportsShaderThreadShuffleNV; - - const char* gl_vendor; - const char* gl_renderer; - const char* gl_version; - - s32 max_samples; -}; -extern VideoConfig g_ogl_config; - -class Renderer : public ::Renderer +class OGLGfx final : public AbstractGfx { public: - Renderer(std::unique_ptr main_gl_context, float backbuffer_scale); - ~Renderer() override; - - static Renderer* GetInstance() { return static_cast(g_renderer.get()); } + OGLGfx(std::unique_ptr main_gl_context, float backbuffer_scale); + ~OGLGfx(); bool IsHeadless() const override; - bool Initialize() override; - void Shutdown() override; - std::unique_ptr CreateTexture(const TextureConfig& config, std::string_view name) override; std::unique_ptr @@ -116,6 +42,8 @@ public: void SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer) override; void SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, const ClearColor& color_value = {}, float depth_value = 0.0f) override; + void ClearRegion(const MathUtil::Rectangle& target_rc, bool colorEnable, bool alphaEnable, + bool zEnable, u32 color, u32 z) override; void SetScissorRect(const MathUtil::Rectangle& rc) override; void SetTexture(u32 index, const AbstractTexture* texture) override; void SetSamplerState(u32 index, const SamplerState& state) override; @@ -135,35 +63,32 @@ public: void Flush() override; void WaitForGPUIdle() override; - void RenderXFBToScreen(const MathUtil::Rectangle& target_rc, - const AbstractTexture* source_texture, - const MathUtil::Rectangle& source_rc) override; void OnConfigChanged(u32 bits) override; - void ClearScreen(const MathUtil::Rectangle& rc, bool colorEnable, bool alphaEnable, - bool zEnable, u32 color, u32 z) override; + virtual void SelectLeftBuffer() override; + virtual void SelectRightBuffer() override; + virtual void SelectMainBuffer() override; std::unique_ptr CreateAsyncShaderCompiler() override; // Only call methods from this on the GPU thread. GLContext* GetMainGLContext() const { return m_main_gl_context.get(); } - bool IsGLES() const { return m_main_gl_context->IsGLES(); } + bool IsGLES() const; // Invalidates a cached texture binding. Required for texel buffers when they borrow the units. void InvalidateTextureBinding(u32 index) { m_bound_textures[index] = nullptr; } // The shared framebuffer exists for copying textures when extensions are not available. It is // slower, but the only way to do these things otherwise. - GLuint GetSharedReadFramebuffer() const { return m_shared_read_framebuffer; } - GLuint GetSharedDrawFramebuffer() const { return m_shared_draw_framebuffer; } + u32 GetSharedReadFramebuffer() const { return m_shared_read_framebuffer; } + u32 GetSharedDrawFramebuffer() const { return m_shared_draw_framebuffer; } void BindSharedReadFramebuffer(); void BindSharedDrawFramebuffer(); // Restores FBO binding after it's been changed. void RestoreFramebufferBinding(); -protected: - std::unique_ptr CreateBoundingBox() const override; + SurfaceInfo GetSurfaceInfo() const override; private: void CheckForSurfaceChange(); @@ -180,7 +105,14 @@ private: RasterizationState m_current_rasterization_state; DepthState m_current_depth_state; BlendingState m_current_blend_state; - GLuint m_shared_read_framebuffer = 0; - GLuint m_shared_draw_framebuffer = 0; + u32 m_shared_read_framebuffer = 0; + u32 m_shared_draw_framebuffer = 0; + float m_backbuffer_scale; }; + +inline OGLGfx* GetOGLGfx() +{ + return static_cast(g_gfx.get()); +} + } // namespace OGL diff --git a/Source/Core/VideoBackends/OGL/OGLMain.cpp b/Source/Core/VideoBackends/OGL/OGLMain.cpp index f6a84240e0..c47b94ceb8 100644 --- a/Source/Core/VideoBackends/OGL/OGLMain.cpp +++ b/Source/Core/VideoBackends/OGL/OGLMain.cpp @@ -46,8 +46,10 @@ Make AA apply instantly during gameplay if possible #include "Core/Config/GraphicsSettings.h" +#include "VideoBackends/OGL/OGLBoundingBox.h" +#include "VideoBackends/OGL/OGLConfig.h" +#include "VideoBackends/OGL/OGLGfx.h" #include "VideoBackends/OGL/OGLPerfQuery.h" -#include "VideoBackends/OGL/OGLRender.h" #include "VideoBackends/OGL/OGLVertexManager.h" #include "VideoBackends/OGL/ProgramShaderCache.h" #include "VideoBackends/OGL/SamplerCache.h" @@ -115,6 +117,8 @@ void VideoBackend::InitBackendInfo() g_Config.backend_info.bSupportsTextureQueryLevels = false; g_Config.backend_info.bSupportsSettingObjectNames = false; + g_Config.backend_info.bUsesExplictQuadBuffering = true; + g_Config.backend_info.Adapters.clear(); // aamodes - 1 is to stay consistent with D3D (means no AA) @@ -183,41 +187,23 @@ bool VideoBackend::Initialize(const WindowSystemInfo& wsi) if (!InitializeGLExtensions(main_gl_context.get()) || !FillBackendInfo()) return false; - InitializeShared(); - g_renderer = std::make_unique(std::move(main_gl_context), wsi.render_surface_scale); + auto gfx = std::make_unique(std::move(main_gl_context), wsi.render_surface_scale); ProgramShaderCache::Init(); - g_vertex_manager = std::make_unique(); - g_shader_cache = std::make_unique(); - g_framebuffer_manager = std::make_unique(); - g_perf_query = GetPerfQuery(); - g_texture_cache = std::make_unique(); g_sampler_cache = std::make_unique(); - if (!g_vertex_manager->Initialize() || !g_shader_cache->Initialize() || - !g_renderer->Initialize() || !g_framebuffer_manager->Initialize() || - !g_texture_cache->Initialize()) - { - PanicAlertFmtT("Failed to initialize renderer classes"); - Shutdown(); - return false; - } + auto vertex_manager = std::make_unique(); + auto perf_query = GetPerfQuery(gfx->IsGLES()); + auto bounding_box = std::make_unique(); - g_shader_cache->InitializeShaderCache(); - return true; + return InitializeShared(std::move(gfx), std::move(vertex_manager), std::move(perf_query), + std::move(bounding_box)); } void VideoBackend::Shutdown() { - g_shader_cache->Shutdown(); - g_renderer->Shutdown(); - g_sampler_cache.reset(); - g_texture_cache.reset(); - g_perf_query.reset(); - g_vertex_manager.reset(); - g_framebuffer_manager.reset(); - g_shader_cache.reset(); - ProgramShaderCache::Shutdown(); - g_renderer.reset(); ShutdownShared(); + + ProgramShaderCache::Shutdown(); + g_sampler_cache.reset(); } } // namespace OGL diff --git a/Source/Core/VideoBackends/OGL/OGLNativeVertexFormat.cpp b/Source/Core/VideoBackends/OGL/OGLNativeVertexFormat.cpp index b77755bb12..f1213fcde5 100644 --- a/Source/Core/VideoBackends/OGL/OGLNativeVertexFormat.cpp +++ b/Source/Core/VideoBackends/OGL/OGLNativeVertexFormat.cpp @@ -6,7 +6,7 @@ #include "Common/GL/GLUtil.h" #include "Common/MsgHandler.h" -#include "VideoBackends/OGL/OGLRender.h" +#include "VideoBackends/OGL/OGLGfx.h" #include "VideoBackends/OGL/OGLVertexManager.h" #include "VideoBackends/OGL/ProgramShaderCache.h" @@ -19,7 +19,7 @@ namespace OGL { std::unique_ptr -Renderer::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) +OGLGfx::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) { return std::make_unique(vtx_decl); } diff --git a/Source/Core/VideoBackends/OGL/OGLPerfQuery.cpp b/Source/Core/VideoBackends/OGL/OGLPerfQuery.cpp index 550b837de8..6303464251 100644 --- a/Source/Core/VideoBackends/OGL/OGLPerfQuery.cpp +++ b/Source/Core/VideoBackends/OGL/OGLPerfQuery.cpp @@ -8,15 +8,15 @@ #include "Common/CommonTypes.h" #include "Common/GL/GLExtensions/GLExtensions.h" -#include "VideoBackends/OGL/OGLRender.h" +#include "VideoBackends/OGL/OGLGfx.h" +#include "VideoCommon/FramebufferManager.h" #include "VideoCommon/VideoCommon.h" #include "VideoCommon/VideoConfig.h" namespace OGL { -std::unique_ptr GetPerfQuery() +std::unique_ptr GetPerfQuery(bool is_gles) { - const bool is_gles = static_cast(g_renderer.get())->IsGLES(); if (is_gles && GLExtensions::Supports("GL_NV_occlusion_query_samples")) return std::make_unique(); else if (is_gles) @@ -165,7 +165,7 @@ void PerfQueryGL::FlushOne() // TODO: Dropping the lower 2 bits from this count should be closer to actual // hardware behavior when drawing triangles. result = static_cast(result) * EFB_WIDTH * EFB_HEIGHT / - (g_renderer->GetTargetWidth() * g_renderer->GetTargetHeight()); + (g_framebuffer_manager->GetEFBWidth() * g_framebuffer_manager->GetEFBHeight()); // Adjust for multisampling if (g_ActiveConfig.iMultisamples > 1) @@ -264,8 +264,9 @@ void PerfQueryGLESNV::FlushOne() // NOTE: Reported pixel metrics should be referenced to native resolution // TODO: Dropping the lower 2 bits from this count should be closer to actual // hardware behavior when drawing triangles. - const u64 native_res_result = static_cast(result) * EFB_WIDTH * EFB_HEIGHT / - (g_renderer->GetTargetWidth() * g_renderer->GetTargetHeight()); + const u64 native_res_result = + static_cast(result) * EFB_WIDTH * EFB_HEIGHT / + (g_framebuffer_manager->GetEFBWidth() * g_framebuffer_manager->GetEFBHeight()); m_results[entry.query_group].fetch_add(static_cast(native_res_result), std::memory_order_relaxed); diff --git a/Source/Core/VideoBackends/OGL/OGLPerfQuery.h b/Source/Core/VideoBackends/OGL/OGLPerfQuery.h index 8ca2bd4334..f593ab5447 100644 --- a/Source/Core/VideoBackends/OGL/OGLPerfQuery.h +++ b/Source/Core/VideoBackends/OGL/OGLPerfQuery.h @@ -12,7 +12,7 @@ namespace OGL { -std::unique_ptr GetPerfQuery(); +std::unique_ptr GetPerfQuery(bool is_gles); class PerfQuery : public PerfQueryBase { diff --git a/Source/Core/VideoBackends/OGL/OGLPipeline.cpp b/Source/Core/VideoBackends/OGL/OGLPipeline.cpp index b4962cf270..3f9dea623f 100644 --- a/Source/Core/VideoBackends/OGL/OGLPipeline.cpp +++ b/Source/Core/VideoBackends/OGL/OGLPipeline.cpp @@ -5,7 +5,6 @@ #include "Common/Assert.h" -#include "VideoBackends/OGL/OGLRender.h" #include "VideoBackends/OGL/OGLShader.h" #include "VideoBackends/OGL/OGLVertexManager.h" #include "VideoBackends/OGL/ProgramShaderCache.h" diff --git a/Source/Core/VideoBackends/OGL/OGLStreamBuffer.cpp b/Source/Core/VideoBackends/OGL/OGLStreamBuffer.cpp index 762eaec1b0..b4f26db453 100644 --- a/Source/Core/VideoBackends/OGL/OGLStreamBuffer.cpp +++ b/Source/Core/VideoBackends/OGL/OGLStreamBuffer.cpp @@ -8,7 +8,7 @@ #include "Common/MathUtil.h" #include "Common/MemoryUtil.h" -#include "VideoBackends/OGL/OGLRender.h" +#include "VideoBackends/OGL/OGLConfig.h" #include "VideoCommon/DriverDetails.h" #include "VideoCommon/OnScreenDisplay.h" diff --git a/Source/Core/VideoBackends/OGL/OGLTexture.cpp b/Source/Core/VideoBackends/OGL/OGLTexture.cpp index 68d9009ac6..ad0e32bb39 100644 --- a/Source/Core/VideoBackends/OGL/OGLTexture.cpp +++ b/Source/Core/VideoBackends/OGL/OGLTexture.cpp @@ -7,6 +7,8 @@ #include "Common/CommonTypes.h" #include "Common/MsgHandler.h" +#include "VideoBackends/OGL/OGLConfig.h" +#include "VideoBackends/OGL/OGLGfx.h" #include "VideoBackends/OGL/SamplerCache.h" #include "VideoCommon/VideoConfig.h" @@ -160,7 +162,7 @@ OGLTexture::OGLTexture(const TextureConfig& tex_config, std::string_view name) OGLTexture::~OGLTexture() { - Renderer::GetInstance()->UnbindTexture(this); + GetOGLGfx()->UnbindTexture(this); glDeleteTextures(1, &m_texId); } @@ -190,10 +192,10 @@ void OGLTexture::BlitFramebuffer(OGLTexture* srcentry, const MathUtil::Rectangle const MathUtil::Rectangle& dst_rect, u32 dst_layer, u32 dst_level) { - Renderer::GetInstance()->BindSharedReadFramebuffer(); + GetOGLGfx()->BindSharedReadFramebuffer(); glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, srcentry->m_texId, src_level, src_layer); - Renderer::GetInstance()->BindSharedDrawFramebuffer(); + GetOGLGfx()->BindSharedDrawFramebuffer(); glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_texId, dst_level, dst_layer); @@ -206,7 +208,7 @@ void OGLTexture::BlitFramebuffer(OGLTexture* srcentry, const MathUtil::Rectangle // The default state for the scissor test is enabled. We don't need to do a full state // restore, as the framebuffer and scissor test are the only things we changed. glEnable(GL_SCISSOR_TEST); - Renderer::GetInstance()->RestoreFramebufferBinding(); + GetOGLGfx()->RestoreFramebufferBinding(); } void OGLTexture::ResolveFromTexture(const AbstractTexture* src, @@ -391,7 +393,7 @@ void OGLStagingTexture::CopyFromTexture(const AbstractTexture* src, else { // Mutate the shared framebuffer. - Renderer::GetInstance()->BindSharedReadFramebuffer(); + GetOGLGfx()->BindSharedReadFramebuffer(); if (AbstractTexture::IsDepthFormat(gltex->GetFormat())) { glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0, 0); @@ -407,7 +409,7 @@ void OGLStagingTexture::CopyFromTexture(const AbstractTexture* src, glReadPixels(src_rect.left, src_rect.top, src_rect.GetWidth(), src_rect.GetHeight(), GetGLFormatForTextureFormat(src->GetFormat()), GetGLTypeForTextureFormat(src->GetFormat()), reinterpret_cast(dst_offset)); - Renderer::GetInstance()->RestoreFramebufferBinding(); + GetOGLGfx()->RestoreFramebufferBinding(); } glPixelStorei(GL_PACK_ROW_LENGTH, 0); @@ -600,7 +602,7 @@ std::unique_ptr OGLFramebuffer::Create(OGLTexture* color_attachm } DEBUG_ASSERT(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); - Renderer::GetInstance()->RestoreFramebufferBinding(); + GetOGLGfx()->RestoreFramebufferBinding(); return std::make_unique(color_attachment, depth_attachment, color_format, depth_format, width, height, layers, samples, fbo); diff --git a/Source/Core/VideoBackends/OGL/OGLVertexManager.cpp b/Source/Core/VideoBackends/OGL/OGLVertexManager.cpp index e47e425a3b..03b2f71ac8 100644 --- a/Source/Core/VideoBackends/OGL/OGLVertexManager.cpp +++ b/Source/Core/VideoBackends/OGL/OGLVertexManager.cpp @@ -12,8 +12,8 @@ #include "Common/CommonTypes.h" #include "Common/GL/GLExtensions/GLExtensions.h" +#include "VideoBackends/OGL/OGLGfx.h" #include "VideoBackends/OGL/OGLPipeline.h" -#include "VideoBackends/OGL/OGLRender.h" #include "VideoBackends/OGL/OGLStreamBuffer.h" #include "VideoBackends/OGL/ProgramShaderCache.h" @@ -116,7 +116,7 @@ bool VertexManager::UploadTexelBuffer(const void* data, u32 data_size, TexelBuff // Bind the correct view to the texel buffer slot. glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_BUFFER, m_texel_buffer_views[static_cast(format)]); - Renderer::GetInstance()->InvalidateTextureBinding(0); + GetOGLGfx()->InvalidateTextureBinding(0); return true; } @@ -141,11 +141,11 @@ bool VertexManager::UploadTexelBuffer(const void* data, u32 data_size, TexelBuff glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_BUFFER, m_texel_buffer_views[static_cast(format)]); - Renderer::GetInstance()->InvalidateTextureBinding(0); + GetOGLGfx()->InvalidateTextureBinding(0); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_BUFFER, m_texel_buffer_views[static_cast(palette_format)]); - Renderer::GetInstance()->InvalidateTextureBinding(1); + GetOGLGfx()->InvalidateTextureBinding(1); return true; } diff --git a/Source/Core/VideoBackends/OGL/ProgramShaderCache.cpp b/Source/Core/VideoBackends/OGL/ProgramShaderCache.cpp index b4635c36ec..289e99cae0 100644 --- a/Source/Core/VideoBackends/OGL/ProgramShaderCache.cpp +++ b/Source/Core/VideoBackends/OGL/ProgramShaderCache.cpp @@ -22,7 +22,8 @@ #include "Core/ConfigManager.h" #include "Core/System.h" -#include "VideoBackends/OGL/OGLRender.h" +#include "VideoBackends/OGL/OGLConfig.h" +#include "VideoBackends/OGL/OGLGfx.h" #include "VideoBackends/OGL/OGLShader.h" #include "VideoBackends/OGL/OGLStreamBuffer.h" #include "VideoBackends/OGL/OGLVertexManager.h" @@ -863,8 +864,7 @@ u64 ProgramShaderCache::GenerateShaderID() bool SharedContextAsyncShaderCompiler::WorkerThreadInitMainThread(void** param) { - std::unique_ptr context = - static_cast(g_renderer.get())->GetMainGLContext()->CreateSharedContext(); + std::unique_ptr context = GetOGLGfx()->GetMainGLContext()->CreateSharedContext(); if (!context) { PanicAlertFmt("Failed to create shared context for shader compiling."); diff --git a/Source/Core/VideoBackends/OGL/SamplerCache.cpp b/Source/Core/VideoBackends/OGL/SamplerCache.cpp index c111caa546..2673fd2ddd 100644 --- a/Source/Core/VideoBackends/OGL/SamplerCache.cpp +++ b/Source/Core/VideoBackends/OGL/SamplerCache.cpp @@ -6,7 +6,7 @@ #include #include "Common/CommonTypes.h" -#include "VideoBackends/OGL/OGLRender.h" +#include "VideoBackends/OGL/OGLConfig.h" #include "VideoCommon/VideoConfig.h" namespace OGL diff --git a/Source/Core/VideoBackends/OGL/SamplerCache.h b/Source/Core/VideoBackends/OGL/SamplerCache.h index ad0a9bf3db..be8860c9f8 100644 --- a/Source/Core/VideoBackends/OGL/SamplerCache.h +++ b/Source/Core/VideoBackends/OGL/SamplerCache.h @@ -9,7 +9,7 @@ #include "Common/CommonTypes.h" #include "Common/GL/GLUtil.h" -#include "VideoBackends/OGL/OGLRender.h" +#include "VideoCommon/RenderState.h" namespace OGL { diff --git a/Source/Core/VideoBackends/Software/CMakeLists.txt b/Source/Core/VideoBackends/Software/CMakeLists.txt index f71421cb57..7fc904bbf2 100644 --- a/Source/Core/VideoBackends/Software/CMakeLists.txt +++ b/Source/Core/VideoBackends/Software/CMakeLists.txt @@ -14,6 +14,8 @@ add_library(videosoftware SWmain.cpp SWBoundingBox.cpp SWBoundingBox.h + SWGfx.cpp + SWGfx.h SWOGLWindow.cpp SWOGLWindow.h SWRenderer.cpp diff --git a/Source/Core/VideoBackends/Software/SWGfx.cpp b/Source/Core/VideoBackends/Software/SWGfx.cpp new file mode 100644 index 0000000000..4dfd5dc0f2 --- /dev/null +++ b/Source/Core/VideoBackends/Software/SWGfx.cpp @@ -0,0 +1,131 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoBackends/Software/SWGfx.h" + +#include "Common/GL/GLContext.h" + +#include "VideoBackends/Software/EfbCopy.h" +#include "VideoBackends/Software/Rasterizer.h" +#include "VideoBackends/Software/SWOGLWindow.h" +#include "VideoBackends/Software/SWTexture.h" + +#include "VideoCommon/AbstractPipeline.h" +#include "VideoCommon/AbstractShader.h" +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/NativeVertexFormat.h" +#include "VideoCommon/Present.h" + +namespace SW +{ +SWGfx::SWGfx(std::unique_ptr window) : m_window(std::move(window)) +{ +} + +bool SWGfx::IsHeadless() const +{ + return m_window->IsHeadless(); +} + +bool SWGfx::SupportsUtilityDrawing() const +{ + return false; +} + +std::unique_ptr SWGfx::CreateTexture(const TextureConfig& config, + [[maybe_unused]] std::string_view name) +{ + return std::make_unique(config); +} + +std::unique_ptr SWGfx::CreateStagingTexture(StagingTextureType type, + const TextureConfig& config) +{ + return std::make_unique(type, config); +} + +std::unique_ptr SWGfx::CreateFramebuffer(AbstractTexture* color_attachment, + AbstractTexture* depth_attachment) +{ + return SWFramebuffer::Create(static_cast(color_attachment), + static_cast(depth_attachment)); +} + +void SWGfx::BindBackbuffer(const ClearColor& clear_color) +{ + // Look for framebuffer resizes + if (!g_presenter->SurfaceResizedTestAndClear()) + return; + + GLContext* context = m_window->GetContext(); + context->Update(); + g_presenter->SetBackbuffer(context->GetBackBufferWidth(), context->GetBackBufferHeight()); +} + +class SWShader final : public AbstractShader +{ +public: + explicit SWShader(ShaderStage stage) : AbstractShader(stage) {} + ~SWShader() = default; + + BinaryData GetBinary() const override { return {}; } +}; + +std::unique_ptr +SWGfx::CreateShaderFromSource(ShaderStage stage, [[maybe_unused]] std::string_view source, + [[maybe_unused]] std::string_view name) +{ + return std::make_unique(stage); +} + +std::unique_ptr +SWGfx::CreateShaderFromBinary(ShaderStage stage, const void* data, size_t length, + [[maybe_unused]] std::string_view name) +{ + return std::make_unique(stage); +} + +class SWPipeline final : public AbstractPipeline +{ +public: + SWPipeline() = default; + ~SWPipeline() override = default; +}; + +std::unique_ptr SWGfx::CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data, + size_t cache_data_length) +{ + return std::make_unique(); +} + +// Called on the GPU thread +void SWGfx::ShowImage(const AbstractTexture* source_texture, + const MathUtil::Rectangle& source_rc) +{ + if (!IsHeadless()) + m_window->ShowImage(source_texture, source_rc); +} + +void SWGfx::ClearRegion(const MathUtil::Rectangle& target_rc, bool colorEnable, + bool alphaEnable, bool zEnable, u32 color, u32 z) +{ + EfbCopy::ClearEfb(); +} + +std::unique_ptr +SWGfx::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) +{ + return std::make_unique(vtx_decl); +} + +void SWGfx::SetScissorRect(const MathUtil::Rectangle& rc) +{ + // BPFunctions calls SetScissorRect with the "best" scissor rect whenever the viewport or scissor + // changes. However, the software renderer is actually able to use multiple scissor rects (which + // is necessary in a few renderering edge cases, such as with Major Minor's Majestic March). + // Thus, we use this as a signal to update the list of scissor rects, but ignore the parameter. + Rasterizer::ScissorChanged(); +} + +} // namespace SW diff --git a/Source/Core/VideoBackends/Software/SWGfx.h b/Source/Core/VideoBackends/Software/SWGfx.h new file mode 100644 index 0000000000..74ead3a323 --- /dev/null +++ b/Source/Core/VideoBackends/Software/SWGfx.h @@ -0,0 +1,56 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "VideoCommon/AbstractGfx.h" + +class SWOGLWindow; + +namespace SW +{ +class SWGfx final : public AbstractGfx +{ +public: + SWGfx(std::unique_ptr window); + + bool IsHeadless() const override; + virtual bool SupportsUtilityDrawing() const override; + + std::unique_ptr CreateTexture(const TextureConfig& config, + std::string_view name) override; + std::unique_ptr + CreateStagingTexture(StagingTextureType type, const TextureConfig& config) override; + std::unique_ptr + CreateFramebuffer(AbstractTexture* color_attachment, AbstractTexture* depth_attachment) override; + + void BindBackbuffer(const ClearColor& clear_color = {}) override; + + std::unique_ptr CreateShaderFromSource(ShaderStage stage, std::string_view source, + std::string_view name) override; + std::unique_ptr CreateShaderFromBinary(ShaderStage stage, const void* data, + size_t length, + std::string_view name) override; + std::unique_ptr + CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) override; + std::unique_ptr CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data = nullptr, + size_t cache_data_length = 0) override; + + void ShowImage(const AbstractTexture* source_texture, + const MathUtil::Rectangle& source_rc) override; + + void ScaleTexture(AbstractFramebuffer* dst_framebuffer, const MathUtil::Rectangle& dst_rect, + const AbstractTexture* src_texture, + const MathUtil::Rectangle& src_rect) override; + + void SetScissorRect(const MathUtil::Rectangle& rc) override; + + void ClearRegion(const MathUtil::Rectangle& target_rc, bool colorEnable, bool alphaEnable, + bool zEnable, u32 color, u32 z) override; + +private: + std::unique_ptr m_window; +}; + +} // namespace SW diff --git a/Source/Core/VideoBackends/Software/SWRenderer.cpp b/Source/Core/VideoBackends/Software/SWRenderer.cpp index 5e75b57701..d537820c95 100644 --- a/Source/Core/VideoBackends/Software/SWRenderer.cpp +++ b/Source/Core/VideoBackends/Software/SWRenderer.cpp @@ -6,118 +6,18 @@ #include #include "Common/CommonTypes.h" -#include "Common/GL/GLContext.h" +#include "Common/MsgHandler.h" #include "Core/HW/Memmap.h" #include "Core/System.h" -#include "VideoBackends/Software/EfbCopy.h" #include "VideoBackends/Software/EfbInterface.h" -#include "VideoBackends/Software/Rasterizer.h" -#include "VideoBackends/Software/SWBoundingBox.h" -#include "VideoBackends/Software/SWOGLWindow.h" -#include "VideoBackends/Software/SWTexture.h" -#include "VideoCommon/AbstractPipeline.h" -#include "VideoCommon/AbstractShader.h" -#include "VideoCommon/AbstractTexture.h" -#include "VideoCommon/NativeVertexFormat.h" #include "VideoCommon/PixelEngine.h" #include "VideoCommon/VideoBackendBase.h" -#include "VideoCommon/VideoCommon.h" namespace SW { -SWRenderer::SWRenderer(std::unique_ptr window) - : ::Renderer(static_cast(std::max(window->GetContext()->GetBackBufferWidth(), 1u)), - static_cast(std::max(window->GetContext()->GetBackBufferHeight(), 1u)), 1.0f, - AbstractTextureFormat::RGBA8), - m_window(std::move(window)) -{ -} - -bool SWRenderer::IsHeadless() const -{ - return m_window->IsHeadless(); -} - -std::unique_ptr SWRenderer::CreateTexture(const TextureConfig& config, - [[maybe_unused]] std::string_view name) -{ - return std::make_unique(config); -} - -std::unique_ptr -SWRenderer::CreateStagingTexture(StagingTextureType type, const TextureConfig& config) -{ - return std::make_unique(type, config); -} - -std::unique_ptr -SWRenderer::CreateFramebuffer(AbstractTexture* color_attachment, AbstractTexture* depth_attachment) -{ - return SWFramebuffer::Create(static_cast(color_attachment), - static_cast(depth_attachment)); -} - -void SWRenderer::BindBackbuffer(const ClearColor& clear_color) -{ - // Look for framebuffer resizes - if (!m_surface_resized.TestAndClear()) - return; - - GLContext* context = m_window->GetContext(); - context->Update(); - m_backbuffer_width = context->GetBackBufferWidth(); - m_backbuffer_height = context->GetBackBufferHeight(); -} - -class SWShader final : public AbstractShader -{ -public: - explicit SWShader(ShaderStage stage) : AbstractShader(stage) {} - ~SWShader() = default; - - BinaryData GetBinary() const override { return {}; } -}; - -std::unique_ptr -SWRenderer::CreateShaderFromSource(ShaderStage stage, [[maybe_unused]] std::string_view source, - [[maybe_unused]] std::string_view name) -{ - return std::make_unique(stage); -} - -std::unique_ptr -SWRenderer::CreateShaderFromBinary(ShaderStage stage, const void* data, size_t length, - [[maybe_unused]] std::string_view name) -{ - return std::make_unique(stage); -} - -class SWPipeline final : public AbstractPipeline -{ -public: - SWPipeline() = default; - ~SWPipeline() override = default; -}; - -std::unique_ptr SWRenderer::CreatePipeline(const AbstractPipelineConfig& config, - const void* cache_data, - size_t cache_data_length) -{ - return std::make_unique(); -} - -// Called on the GPU thread -void SWRenderer::RenderXFBToScreen(const MathUtil::Rectangle& target_rc, - const AbstractTexture* source_texture, - const MathUtil::Rectangle& source_rc) -{ - if (!IsHeadless()) - m_window->ShowImage(source_texture, source_rc); -} - u32 SWRenderer::AccessEFB(EFBAccessType type, u32 x, u32 y, u32 InputData) { u32 value = 0; @@ -166,29 +66,4 @@ u32 SWRenderer::AccessEFB(EFBAccessType type, u32 x, u32 y, u32 InputData) return value; } -std::unique_ptr SWRenderer::CreateBoundingBox() const -{ - return std::make_unique(); -} - -void SWRenderer::ClearScreen(const MathUtil::Rectangle& rc, bool colorEnable, bool alphaEnable, - bool zEnable, u32 color, u32 z) -{ - EfbCopy::ClearEfb(); -} - -std::unique_ptr -SWRenderer::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) -{ - return std::make_unique(vtx_decl); -} - -void SWRenderer::SetScissorRect(const MathUtil::Rectangle& rc) -{ - // BPFunctions calls SetScissorRect with the "best" scissor rect whenever the viewport or scissor - // changes. However, the software renderer is actually able to use multiple scissor rects (which - // is necessary in a few renderering edge cases, such as with Major Minor's Majestic March). - // Thus, we use this as a signal to update the list of scissor rects, but ignore the parameter. - Rasterizer::ScissorChanged(); -} } // namespace SW diff --git a/Source/Core/VideoBackends/Software/SWRenderer.h b/Source/Core/VideoBackends/Software/SWRenderer.h index 8aa9aa4af5..e34a016e15 100644 --- a/Source/Core/VideoBackends/Software/SWRenderer.h +++ b/Source/Core/VideoBackends/Software/SWRenderer.h @@ -10,60 +10,14 @@ #include "VideoCommon/RenderBase.h" -class BoundingBox; -class SWOGLWindow; - namespace SW { class SWRenderer final : public Renderer { public: - SWRenderer(std::unique_ptr window); - - bool IsHeadless() const override; - - std::unique_ptr CreateTexture(const TextureConfig& config, - std::string_view name) override; - std::unique_ptr - CreateStagingTexture(StagingTextureType type, const TextureConfig& config) override; - std::unique_ptr - CreateFramebuffer(AbstractTexture* color_attachment, AbstractTexture* depth_attachment) override; - - void BindBackbuffer(const ClearColor& clear_color = {}) override; - - std::unique_ptr CreateShaderFromSource(ShaderStage stage, std::string_view source, - std::string_view name) override; - std::unique_ptr CreateShaderFromBinary(ShaderStage stage, const void* data, - size_t length, - std::string_view name) override; - std::unique_ptr - CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) override; - std::unique_ptr CreatePipeline(const AbstractPipelineConfig& config, - const void* cache_data = nullptr, - size_t cache_data_length = 0) override; - u32 AccessEFB(EFBAccessType type, u32 x, u32 y, u32 poke_data) override; void PokeEFB(EFBAccessType type, const EfbPokeData* points, size_t num_points) override {} - void RenderXFBToScreen(const MathUtil::Rectangle& target_rc, - const AbstractTexture* source_texture, - const MathUtil::Rectangle& source_rc) override; - - void ClearScreen(const MathUtil::Rectangle& rc, bool colorEnable, bool alphaEnable, - bool zEnable, u32 color, u32 z) override; - void ReinterpretPixelData(EFBReinterpretType convtype) override {} - - void ScaleTexture(AbstractFramebuffer* dst_framebuffer, const MathUtil::Rectangle& dst_rect, - const AbstractTexture* src_texture, - const MathUtil::Rectangle& src_rect) override; - - void SetScissorRect(const MathUtil::Rectangle& rc) override; - -protected: - std::unique_ptr CreateBoundingBox() const override; - -private: - std::unique_ptr m_window; }; } // namespace SW diff --git a/Source/Core/VideoBackends/Software/SWTexture.cpp b/Source/Core/VideoBackends/Software/SWTexture.cpp index 4b55f1453c..0e3ebbde55 100644 --- a/Source/Core/VideoBackends/Software/SWTexture.cpp +++ b/Source/Core/VideoBackends/Software/SWTexture.cpp @@ -8,7 +8,7 @@ #include "Common/Assert.h" #include "VideoBackends/Software/CopyRegion.h" -#include "VideoBackends/Software/SWRenderer.h" +#include "VideoBackends/Software/SWGfx.h" namespace SW { @@ -48,10 +48,10 @@ void CopyTextureData(const TextureConfig& src_config, const u8* src_ptr, u32 src } } // namespace -void SWRenderer::ScaleTexture(AbstractFramebuffer* dst_framebuffer, - const MathUtil::Rectangle& dst_rect, - const AbstractTexture* src_texture, - const MathUtil::Rectangle& src_rect) +void SWGfx::ScaleTexture(AbstractFramebuffer* dst_framebuffer, + const MathUtil::Rectangle& dst_rect, + const AbstractTexture* src_texture, + const MathUtil::Rectangle& src_rect) { const SWTexture* software_source_texture = static_cast(src_texture); SWTexture* software_dest_texture = static_cast(dst_framebuffer->GetColorAttachment()); diff --git a/Source/Core/VideoBackends/Software/SWVertexLoader.cpp b/Source/Core/VideoBackends/Software/SWVertexLoader.cpp index 9fbf560ecf..903b430aff 100644 --- a/Source/Core/VideoBackends/Software/SWVertexLoader.cpp +++ b/Source/Core/VideoBackends/Software/SWVertexLoader.cpp @@ -18,6 +18,7 @@ #include "VideoBackends/Software/Tev.h" #include "VideoBackends/Software/TransformUnit.h" +#include "VideoCommon/BoundingBox.h" #include "VideoCommon/CPMemory.h" #include "VideoCommon/DataReader.h" #include "VideoCommon/IndexGenerator.h" @@ -62,8 +63,8 @@ void SWVertexLoader::DrawCurrentBatch(u32 base_index, u32 num_indices, u32 base_ } // Flush bounding box here because software overrides the base function - if (g_renderer->IsBBoxEnabled()) - g_renderer->BBoxFlush(); + if (g_bounding_box->IsEnabled()) + g_bounding_box->Flush(); m_setup_unit.Init(primitive_type); Rasterizer::SetTevKonstColors(); diff --git a/Source/Core/VideoBackends/Software/SWmain.cpp b/Source/Core/VideoBackends/Software/SWmain.cpp index b6d2005c26..8703ea4b4d 100644 --- a/Source/Core/VideoBackends/Software/SWmain.cpp +++ b/Source/Core/VideoBackends/Software/SWmain.cpp @@ -16,6 +16,8 @@ #include "VideoBackends/Software/Clipper.h" #include "VideoBackends/Software/EfbInterface.h" #include "VideoBackends/Software/Rasterizer.h" +#include "VideoBackends/Software/SWBoundingBox.h" +#include "VideoBackends/Software/SWGfx.h" #include "VideoBackends/Software/SWOGLWindow.h" #include "VideoBackends/Software/SWRenderer.h" #include "VideoBackends/Software/SWTexture.h" @@ -23,6 +25,7 @@ #include "VideoBackends/Software/TextureCache.h" #include "VideoCommon/FramebufferManager.h" +#include "VideoCommon/Present.h" #include "VideoCommon/TextureCacheBase.h" #include "VideoCommon/VideoCommon.h" #include "VideoCommon/VideoConfig.h" @@ -96,8 +99,6 @@ void VideoSoftware::InitBackendInfo() bool VideoSoftware::Initialize(const WindowSystemInfo& wsi) { - InitializeShared(); - std::unique_ptr window = SWOGLWindow::Create(wsi); if (!window) return false; @@ -105,40 +106,14 @@ bool VideoSoftware::Initialize(const WindowSystemInfo& wsi) Clipper::Init(); Rasterizer::Init(); - g_renderer = std::make_unique(std::move(window)); - g_vertex_manager = std::make_unique(); - g_shader_cache = std::make_unique(); - g_framebuffer_manager = std::make_unique(); - g_perf_query = std::make_unique(); - g_texture_cache = std::make_unique(); - - if (!g_vertex_manager->Initialize() || !g_shader_cache->Initialize() || - !g_renderer->Initialize() || !g_framebuffer_manager->Initialize() || - !g_texture_cache->Initialize()) - { - PanicAlertFmt("Failed to initialize renderer classes"); - Shutdown(); - return false; - } - - g_shader_cache->InitializeShaderCache(); - return true; + return InitializeShared(std::make_unique(std::move(window)), + std::make_unique(), std::make_unique(), + std::make_unique(), std::make_unique(), + std::make_unique()); } void VideoSoftware::Shutdown() { - if (g_shader_cache) - g_shader_cache->Shutdown(); - - if (g_renderer) - g_renderer->Shutdown(); - - g_texture_cache.reset(); - g_perf_query.reset(); - g_framebuffer_manager.reset(); - g_shader_cache.reset(); - g_vertex_manager.reset(); - g_renderer.reset(); ShutdownShared(); } } // namespace SW diff --git a/Source/Core/VideoBackends/Software/TextureCache.h b/Source/Core/VideoBackends/Software/TextureCache.h index a7d241197f..7fc38ed9e8 100644 --- a/Source/Core/VideoBackends/Software/TextureCache.h +++ b/Source/Core/VideoBackends/Software/TextureCache.h @@ -19,7 +19,7 @@ protected: TextureEncoder::Encode(dst, params, native_width, bytes_per_row, num_blocks_y, memory_stride, src_rect, scale_by_half, y_scale, gamma); } - void CopyEFBToCacheEntry(TCacheEntry* entry, bool is_depth_copy, + void CopyEFBToCacheEntry(RcTcacheEntry& entry, bool is_depth_copy, const MathUtil::Rectangle& src_rect, bool scale_by_half, bool linear_filter, EFBCopyFormat dst_format, bool is_intensity, float gamma, bool clamp_top, bool clamp_bottom, diff --git a/Source/Core/VideoBackends/Vulkan/CMakeLists.txt b/Source/Core/VideoBackends/Vulkan/CMakeLists.txt index 5fd6aa1ed7..7abf9a038b 100644 --- a/Source/Core/VideoBackends/Vulkan/CMakeLists.txt +++ b/Source/Core/VideoBackends/Vulkan/CMakeLists.txt @@ -17,8 +17,8 @@ add_library(videovulkan VKPerfQuery.h VKPipeline.cpp VKPipeline.h - VKRenderer.cpp - VKRenderer.h + VKGfx.cpp + VKGfx.h VKShader.cpp VKShader.h VKStreamBuffer.cpp diff --git a/Source/Core/VideoBackends/Vulkan/StateTracker.cpp b/Source/Core/VideoBackends/Vulkan/StateTracker.cpp index 8b01c66ca8..8cee69cfa9 100644 --- a/Source/Core/VideoBackends/Vulkan/StateTracker.cpp +++ b/Source/Core/VideoBackends/Vulkan/StateTracker.cpp @@ -7,8 +7,8 @@ #include "VideoBackends/Vulkan/CommandBufferManager.h" #include "VideoBackends/Vulkan/ObjectCache.h" +#include "VideoBackends/Vulkan/VKGfx.h" #include "VideoBackends/Vulkan/VKPipeline.h" -#include "VideoBackends/Vulkan/VKRenderer.h" #include "VideoBackends/Vulkan/VKShader.h" #include "VideoBackends/Vulkan/VKTexture.h" #include "VideoBackends/Vulkan/VKVertexFormat.h" diff --git a/Source/Core/VideoBackends/Vulkan/StateTracker.h b/Source/Core/VideoBackends/Vulkan/StateTracker.h index eb5e4676de..d90496631c 100644 --- a/Source/Core/VideoBackends/Vulkan/StateTracker.h +++ b/Source/Core/VideoBackends/Vulkan/StateTracker.h @@ -9,7 +9,6 @@ #include "Common/CommonTypes.h" #include "VideoBackends/Vulkan/Constants.h" -#include "VideoCommon/RenderBase.h" namespace Vulkan { diff --git a/Source/Core/VideoBackends/Vulkan/VKBoundingBox.cpp b/Source/Core/VideoBackends/Vulkan/VKBoundingBox.cpp index a182bd8483..7765e97a25 100644 --- a/Source/Core/VideoBackends/Vulkan/VKBoundingBox.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKBoundingBox.cpp @@ -11,7 +11,7 @@ #include "VideoBackends/Vulkan/ObjectCache.h" #include "VideoBackends/Vulkan/StagingBuffer.h" #include "VideoBackends/Vulkan/StateTracker.h" -#include "VideoBackends/Vulkan/VKRenderer.h" +#include "VideoBackends/Vulkan/VKGfx.h" #include "VideoBackends/Vulkan/VulkanContext.h" namespace Vulkan @@ -65,7 +65,7 @@ std::vector VKBoundingBox::Read(u32 index, u32 length) VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); // Wait until these commands complete. - Renderer::GetInstance()->ExecuteCommandBuffer(false, true); + VKGfx::GetInstance()->ExecuteCommandBuffer(false, true); // Cache is now valid. m_readback_buffer->InvalidateCPUCache(); diff --git a/Source/Core/VideoBackends/Vulkan/VKBoundingBox.h b/Source/Core/VideoBackends/Vulkan/VKBoundingBox.h index 15a5a172d9..107abc7551 100644 --- a/Source/Core/VideoBackends/Vulkan/VKBoundingBox.h +++ b/Source/Core/VideoBackends/Vulkan/VKBoundingBox.h @@ -8,6 +8,7 @@ #include #include "Common/CommonTypes.h" +#include "VideoBackends/Vulkan/StagingBuffer.h" #include "VideoBackends/Vulkan/VulkanLoader.h" #include "VideoCommon/BoundingBox.h" diff --git a/Source/Core/VideoBackends/Vulkan/VKRenderer.cpp b/Source/Core/VideoBackends/Vulkan/VKGfx.cpp similarity index 75% rename from Source/Core/VideoBackends/Vulkan/VKRenderer.cpp rename to Source/Core/VideoBackends/Vulkan/VKGfx.cpp index 5256d6ada3..569fbaa32b 100644 --- a/Source/Core/VideoBackends/Vulkan/VKRenderer.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKGfx.cpp @@ -1,7 +1,7 @@ // Copyright 2016 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "VideoBackends/Vulkan/VKRenderer.h" +#include "VideoBackends/Vulkan/VKGfx.h" #include #include @@ -15,152 +15,98 @@ #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" -#include "Core/Core.h" - #include "VideoBackends/Vulkan/CommandBufferManager.h" #include "VideoBackends/Vulkan/ObjectCache.h" -#include "VideoBackends/Vulkan/StagingBuffer.h" #include "VideoBackends/Vulkan/StateTracker.h" -#include "VideoBackends/Vulkan/VKBoundingBox.h" -#include "VideoBackends/Vulkan/VKPerfQuery.h" #include "VideoBackends/Vulkan/VKPipeline.h" #include "VideoBackends/Vulkan/VKShader.h" -#include "VideoBackends/Vulkan/VKStreamBuffer.h" #include "VideoBackends/Vulkan/VKSwapChain.h" #include "VideoBackends/Vulkan/VKTexture.h" #include "VideoBackends/Vulkan/VKVertexFormat.h" -#include "VideoBackends/Vulkan/VulkanContext.h" #include "VideoCommon/DriverDetails.h" #include "VideoCommon/FramebufferManager.h" +#include "VideoCommon/Present.h" #include "VideoCommon/RenderState.h" -#include "VideoCommon/VertexManagerBase.h" -#include "VideoCommon/VideoBackendBase.h" -#include "VideoCommon/VideoCommon.h" #include "VideoCommon/VideoConfig.h" -#include "VideoCommon/XFMemory.h" namespace Vulkan { -Renderer::Renderer(std::unique_ptr swap_chain, float backbuffer_scale) - : ::Renderer(swap_chain ? static_cast(swap_chain->GetWidth()) : 1, - swap_chain ? static_cast(swap_chain->GetHeight()) : 0, backbuffer_scale, - swap_chain ? swap_chain->GetTextureFormat() : AbstractTextureFormat::Undefined), - m_swap_chain(std::move(swap_chain)) +VKGfx::VKGfx(std::unique_ptr swap_chain, float backbuffer_scale) + : m_swap_chain(std::move(swap_chain)), m_backbuffer_scale(backbuffer_scale) { UpdateActiveConfig(); - for (SamplerState& m_sampler_state : m_sampler_states) - m_sampler_state = RenderState::GetPointSamplerState(); -} - -Renderer::~Renderer() = default; - -bool Renderer::IsHeadless() const -{ - return m_swap_chain == nullptr; -} - -bool Renderer::Initialize() -{ - if (!::Renderer::Initialize()) - return false; + for (SamplerState& sampler_state : m_sampler_states) + sampler_state = RenderState::GetPointSamplerState(); // Various initialization routines will have executed commands on the command buffer. // Execute what we have done before beginning the first frame. ExecuteCommandBuffer(true, false); - return true; } -void Renderer::Shutdown() +VKGfx::~VKGfx() = default; + +bool VKGfx::IsHeadless() const { - ::Renderer::Shutdown(); - m_swap_chain.reset(); + return m_swap_chain == nullptr; } -std::unique_ptr Renderer::CreateTexture(const TextureConfig& config, - std::string_view name) +std::unique_ptr VKGfx::CreateTexture(const TextureConfig& config, + std::string_view name) { return VKTexture::Create(config, name); } -std::unique_ptr Renderer::CreateStagingTexture(StagingTextureType type, - const TextureConfig& config) +std::unique_ptr VKGfx::CreateStagingTexture(StagingTextureType type, + const TextureConfig& config) { return VKStagingTexture::Create(type, config); } std::unique_ptr -Renderer::CreateShaderFromSource(ShaderStage stage, std::string_view source, std::string_view name) +VKGfx::CreateShaderFromSource(ShaderStage stage, std::string_view source, std::string_view name) { return VKShader::CreateFromSource(stage, source, name); } -std::unique_ptr Renderer::CreateShaderFromBinary(ShaderStage stage, - const void* data, size_t length, - std::string_view name) +std::unique_ptr VKGfx::CreateShaderFromBinary(ShaderStage stage, const void* data, + size_t length, std::string_view name) { return VKShader::CreateFromBinary(stage, data, length, name); } std::unique_ptr -Renderer::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) +VKGfx::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) { return std::make_unique(vtx_decl); } -std::unique_ptr Renderer::CreatePipeline(const AbstractPipelineConfig& config, - const void* cache_data, - size_t cache_data_length) +std::unique_ptr VKGfx::CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data, + size_t cache_data_length) { return VKPipeline::Create(config); } -std::unique_ptr Renderer::CreateFramebuffer(AbstractTexture* color_attachment, - AbstractTexture* depth_attachment) +std::unique_ptr VKGfx::CreateFramebuffer(AbstractTexture* color_attachment, + AbstractTexture* depth_attachment) { return VKFramebuffer::Create(static_cast(color_attachment), static_cast(depth_attachment)); } -void Renderer::SetPipeline(const AbstractPipeline* pipeline) +void VKGfx::SetPipeline(const AbstractPipeline* pipeline) { StateTracker::GetInstance()->SetPipeline(static_cast(pipeline)); } -std::unique_ptr Renderer::CreateBoundingBox() const +void VKGfx::ClearRegion(const MathUtil::Rectangle& target_rc, bool color_enable, + bool alpha_enable, bool z_enable, u32 color, u32 z) { - return std::make_unique(); -} - -void Renderer::ClearScreen(const MathUtil::Rectangle& rc, bool color_enable, bool alpha_enable, - bool z_enable, u32 color, u32 z) -{ - g_framebuffer_manager->FlushEFBPokes(); - g_framebuffer_manager->FlagPeekCacheAsOutOfDate(); - - // Native -> EFB coordinates - MathUtil::Rectangle target_rc = Renderer::ConvertEFBRectangle(rc); - - // Size we pass this size to vkBeginRenderPass, it has to be clamped to the framebuffer - // dimensions. The other backends just silently ignore this case. - target_rc.ClampUL(0, 0, m_target_width, m_target_height); - VkRect2D target_vk_rc = { {target_rc.left, target_rc.top}, {static_cast(target_rc.GetWidth()), static_cast(target_rc.GetHeight())}}; - // Determine whether the EFB has an alpha channel. If it doesn't, we can clear the alpha - // channel to 0xFF. This hopefully allows us to use the fast path in most cases. - if (bpmem.zcontrol.pixel_format == PixelFormat::RGB565_Z16 || - bpmem.zcontrol.pixel_format == PixelFormat::RGB8_Z24 || - bpmem.zcontrol.pixel_format == PixelFormat::Z24) - { - // Force alpha writes, and clear the alpha channel. This is different from the other backends, - // where the existing values of the alpha channel are preserved. - alpha_enable = true; - color &= 0x00FFFFFF; - } - // Convert RGBA8 -> floating-point values. VkClearValue clear_color_value = {}; VkClearValue clear_depth_value = {}; @@ -244,20 +190,20 @@ void Renderer::ClearScreen(const MathUtil::Rectangle& rc, bool color_enable if (!color_enable && !alpha_enable && !z_enable) return; - g_framebuffer_manager->ClearEFB(rc, color_enable, alpha_enable, z_enable, color, z); + AbstractGfx::ClearRegion(target_rc, color_enable, alpha_enable, z_enable, color, z); } -void Renderer::Flush() +void VKGfx::Flush() { ExecuteCommandBuffer(true, false); } -void Renderer::WaitForGPUIdle() +void VKGfx::WaitForGPUIdle() { ExecuteCommandBuffer(false, true); } -void Renderer::BindBackbuffer(const ClearColor& clear_color) +void VKGfx::BindBackbuffer(const ClearColor& clear_color) { StateTracker::GetInstance()->EndRenderPass(); @@ -334,7 +280,7 @@ void Renderer::BindBackbuffer(const ClearColor& clear_color) ClearColor{{0.0f, 0.0f, 0.0f, 1.0f}}); } -void Renderer::PresentBackbuffer() +void VKGfx::PresentBackbuffer() { // End drawing to backbuffer StateTracker::GetInstance()->EndRenderPass(); @@ -355,7 +301,7 @@ void Renderer::PresentBackbuffer() StateTracker::GetInstance()->InvalidateCachedState(); } -void Renderer::SetFullscreen(bool enable_fullscreen) +void VKGfx::SetFullscreen(bool enable_fullscreen) { if (!m_swap_chain->IsFullscreenSupported()) return; @@ -363,12 +309,12 @@ void Renderer::SetFullscreen(bool enable_fullscreen) m_swap_chain->SetNextFullscreenState(enable_fullscreen); } -bool Renderer::IsFullscreen() const +bool VKGfx::IsFullscreen() const { return m_swap_chain && m_swap_chain->GetCurrentFullscreenState(); } -void Renderer::ExecuteCommandBuffer(bool submit_off_thread, bool wait_for_completion) +void VKGfx::ExecuteCommandBuffer(bool submit_off_thread, bool wait_for_completion) { StateTracker::GetInstance()->EndRenderPass(); @@ -377,9 +323,9 @@ void Renderer::ExecuteCommandBuffer(bool submit_off_thread, bool wait_for_comple StateTracker::GetInstance()->InvalidateCachedState(); } -void Renderer::CheckForSurfaceChange() +void VKGfx::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,17 +335,16 @@ 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(); } -void Renderer::CheckForSurfaceResize() +void VKGfx::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? @@ -421,8 +366,10 @@ void Renderer::CheckForSurfaceResize() OnSwapChainResized(); } -void Renderer::OnConfigChanged(u32 bits) +void VKGfx::OnConfigChanged(u32 bits) { + AbstractGfx::OnConfigChanged(bits); + if (bits & CONFIG_CHANGE_BIT_HOST_CONFIG) g_object_cache->ReloadPipelineCache(); @@ -448,13 +395,12 @@ void Renderer::OnConfigChanged(u32 bits) } } -void Renderer::OnSwapChainResized() +void VKGfx::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) +void VKGfx::BindFramebuffer(VKFramebuffer* fb) { StateTracker::GetInstance()->EndRenderPass(); @@ -475,7 +421,7 @@ void Renderer::BindFramebuffer(VKFramebuffer* fb) m_current_framebuffer = fb; } -void Renderer::SetFramebuffer(AbstractFramebuffer* framebuffer) +void VKGfx::SetFramebuffer(AbstractFramebuffer* framebuffer) { if (m_current_framebuffer == framebuffer) return; @@ -484,7 +430,7 @@ void Renderer::SetFramebuffer(AbstractFramebuffer* framebuffer) BindFramebuffer(vkfb); } -void Renderer::SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer) +void VKGfx::SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer) { if (m_current_framebuffer == framebuffer) return; @@ -497,8 +443,8 @@ void Renderer::SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer) StateTracker::GetInstance()->BeginDiscardRenderPass(); } -void Renderer::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, - const ClearColor& color_value, float depth_value) +void VKGfx::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, const ClearColor& color_value, + float depth_value) { VKFramebuffer* vkfb = static_cast(framebuffer); BindFramebuffer(vkfb); @@ -521,7 +467,7 @@ void Renderer::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, num_clear_values); } -void Renderer::SetTexture(u32 index, const AbstractTexture* texture) +void VKGfx::SetTexture(u32 index, const AbstractTexture* texture) { // Texture should always be in SHADER_READ_ONLY layout prior to use. // This is so we don't need to transition during render passes. @@ -532,7 +478,7 @@ void Renderer::SetTexture(u32 index, const AbstractTexture* texture) { if (StateTracker::GetInstance()->InRenderPass()) { - WARN_LOG_FMT(VIDEO, "Transitioning image in render pass in Renderer::SetTexture()"); + WARN_LOG_FMT(VIDEO, "Transitioning image in render pass in VKGfx::SetTexture()"); StateTracker::GetInstance()->EndRenderPass(); } @@ -548,7 +494,7 @@ void Renderer::SetTexture(u32 index, const AbstractTexture* texture) } } -void Renderer::SetSamplerState(u32 index, const SamplerState& state) +void VKGfx::SetSamplerState(u32 index, const SamplerState& state) { // Skip lookup if the state hasn't changed. if (m_sampler_states[index] == state) @@ -566,7 +512,7 @@ void Renderer::SetSamplerState(u32 index, const SamplerState& state) m_sampler_states[index] = state; } -void Renderer::SetComputeImageTexture(AbstractTexture* texture, bool read, bool write) +void VKGfx::SetComputeImageTexture(AbstractTexture* texture, bool read, bool write) { VKTexture* vk_texture = static_cast(texture); if (vk_texture) @@ -584,12 +530,12 @@ void Renderer::SetComputeImageTexture(AbstractTexture* texture, bool read, bool } } -void Renderer::UnbindTexture(const AbstractTexture* texture) +void VKGfx::UnbindTexture(const AbstractTexture* texture) { StateTracker::GetInstance()->UnbindTexture(static_cast(texture)->GetView()); } -void Renderer::ResetSamplerStates() +void VKGfx::ResetSamplerStates() { // Invalidate all sampler states, next draw will re-initialize them. for (u32 i = 0; i < m_sampler_states.size(); i++) @@ -602,7 +548,7 @@ void Renderer::ResetSamplerStates() g_object_cache->ClearSamplerCache(); } -void Renderer::SetScissorRect(const MathUtil::Rectangle& rc) +void VKGfx::SetScissorRect(const MathUtil::Rectangle& rc) { VkRect2D scissor = {{rc.left, rc.top}, {static_cast(rc.GetWidth()), static_cast(rc.GetHeight())}}; @@ -622,14 +568,14 @@ void Renderer::SetScissorRect(const MathUtil::Rectangle& rc) StateTracker::GetInstance()->SetScissor(scissor); } -void Renderer::SetViewport(float x, float y, float width, float height, float near_depth, - float far_depth) +void VKGfx::SetViewport(float x, float y, float width, float height, float near_depth, + float far_depth) { VkViewport viewport = {x, y, width, height, near_depth, far_depth}; StateTracker::GetInstance()->SetViewport(viewport); } -void Renderer::Draw(u32 base_vertex, u32 num_vertices) +void VKGfx::Draw(u32 base_vertex, u32 num_vertices) { if (!StateTracker::GetInstance()->Bind()) return; @@ -637,7 +583,7 @@ void Renderer::Draw(u32 base_vertex, u32 num_vertices) vkCmdDraw(g_command_buffer_mgr->GetCurrentCommandBuffer(), num_vertices, 1, base_vertex, 0); } -void Renderer::DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) +void VKGfx::DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) { if (!StateTracker::GetInstance()->Bind()) return; @@ -646,12 +592,19 @@ void Renderer::DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) base_vertex, 0); } -void Renderer::DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u32 groupsize_y, - u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z) +void VKGfx::DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u32 groupsize_y, + u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z) { StateTracker::GetInstance()->SetComputeShader(static_cast(shader)); if (StateTracker::GetInstance()->BindCompute()) vkCmdDispatch(g_command_buffer_mgr->GetCurrentCommandBuffer(), groups_x, groups_y, groups_z); } +SurfaceInfo VKGfx::GetSurfaceInfo() const +{ + return {m_swap_chain ? m_swap_chain->GetWidth() : 1u, + m_swap_chain ? m_swap_chain->GetHeight() : 0u, m_backbuffer_scale, + m_swap_chain ? m_swap_chain->GetTextureFormat() : AbstractTextureFormat::Undefined}; +} + } // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKRenderer.h b/Source/Core/VideoBackends/Vulkan/VKGfx.h similarity index 87% rename from Source/Core/VideoBackends/Vulkan/VKRenderer.h rename to Source/Core/VideoBackends/Vulkan/VKGfx.h index 11fc7dd96e..bc254874fd 100644 --- a/Source/Core/VideoBackends/Vulkan/VKRenderer.h +++ b/Source/Core/VideoBackends/Vulkan/VKGfx.h @@ -4,16 +4,12 @@ #pragma once #include -#include #include #include #include "Common/CommonTypes.h" #include "VideoBackends/Vulkan/Constants.h" -#include "VideoCommon/RenderBase.h" - -class BoundingBox; -struct XFBSourceBase; +#include "VideoCommon/AbstractGfx.h" namespace Vulkan { @@ -23,19 +19,16 @@ class VKFramebuffer; class VKPipeline; class VKTexture; -class Renderer : public ::Renderer +class VKGfx final : public ::AbstractGfx { public: - Renderer(std::unique_ptr swap_chain, float backbuffer_scale); - ~Renderer() override; + VKGfx(std::unique_ptr swap_chain, float backbuffer_scale); + ~VKGfx() override; - static Renderer* GetInstance() { return static_cast(g_renderer.get()); } + static VKGfx* GetInstance() { return static_cast(g_gfx.get()); } bool IsHeadless() const override; - bool Initialize() override; - void Shutdown() override; - std::unique_ptr CreateTexture(const TextureConfig& config, std::string_view name) override; std::unique_ptr @@ -60,7 +53,7 @@ public: void WaitForGPUIdle() override; void OnConfigChanged(u32 bits) override; - void ClearScreen(const MathUtil::Rectangle& rc, bool color_enable, bool alpha_enable, + void ClearRegion(const MathUtil::Rectangle& target_rc, bool color_enable, bool alpha_enable, bool z_enable, u32 color, u32 z) override; void SetPipeline(const AbstractPipeline* pipeline) override; @@ -84,13 +77,12 @@ public: void SetFullscreen(bool enable_fullscreen) override; bool IsFullscreen() const override; + virtual SurfaceInfo GetSurfaceInfo() const override; + // Completes the current render pass, executes the command buffer, and restores state ready for // next render. Use when you want to kick the current buffer to make room for new data. void ExecuteCommandBuffer(bool execute_off_thread, bool wait_for_completion = false); -protected: - std::unique_ptr CreateBoundingBox() const override; - private: void CheckForSurfaceChange(); void CheckForSurfaceResize(); @@ -101,6 +93,7 @@ private: void BindFramebuffer(VKFramebuffer* fb); std::unique_ptr m_swap_chain; + float m_backbuffer_scale; // Keep a copy of sampler states to avoid cache lookups every draw std::array m_sampler_states = {}; diff --git a/Source/Core/VideoBackends/Vulkan/VKMain.cpp b/Source/Core/VideoBackends/Vulkan/VKMain.cpp index 46d535ce75..053b592c14 100644 --- a/Source/Core/VideoBackends/Vulkan/VKMain.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKMain.cpp @@ -12,8 +12,9 @@ #include "VideoBackends/Vulkan/Constants.h" #include "VideoBackends/Vulkan/ObjectCache.h" #include "VideoBackends/Vulkan/StateTracker.h" +#include "VideoBackends/Vulkan/VKBoundingBox.h" +#include "VideoBackends/Vulkan/VKGfx.h" #include "VideoBackends/Vulkan/VKPerfQuery.h" -#include "VideoBackends/Vulkan/VKRenderer.h" #include "VideoBackends/Vulkan/VKSwapChain.h" #include "VideoBackends/Vulkan/VKVertexManager.h" #include "VideoBackends/Vulkan/VulkanContext.h" @@ -193,8 +194,7 @@ bool VideoBackend::Initialize(const WindowSystemInfo& wsi) g_Config.backend_info.bSupportsExclusiveFullscreen = enable_surface && g_vulkan_context->SupportsExclusiveFullscreen(wsi, surface); - // With the backend information populated, we can now initialize videocommon. - InitializeShared(); + UpdateActiveConfig(); // Create command buffers. We do this separately because the other classes depend on it. g_command_buffer_mgr = std::make_unique(g_Config.bBackendMultithreading); @@ -234,25 +234,13 @@ bool VideoBackend::Initialize(const WindowSystemInfo& wsi) return false; } - // Create main wrapper instances. - g_renderer = std::make_unique(std::move(swap_chain), wsi.render_surface_scale); - g_vertex_manager = std::make_unique(); - g_shader_cache = std::make_unique(); - g_framebuffer_manager = std::make_unique(); - g_texture_cache = std::make_unique(); - g_perf_query = std::make_unique(); + auto gfx = std::make_unique(std::move(swap_chain), wsi.render_surface_scale); + auto vertex_manager = std::make_unique(); + auto perf_query = std::make_unique(); + auto bounding_box = std::make_unique(); - if (!g_vertex_manager->Initialize() || !g_shader_cache->Initialize() || - !g_renderer->Initialize() || !g_framebuffer_manager->Initialize() || - !g_texture_cache->Initialize() || !PerfQuery::GetInstance()->Initialize()) - { - PanicAlertFmt("Failed to initialize renderer classes"); - Shutdown(); - return false; - } - - g_shader_cache->InitializeShaderCache(); - return true; + return InitializeShared(std::move(gfx), std::move(vertex_manager), std::move(perf_query), + std::move(bounding_box)); } void VideoBackend::Shutdown() @@ -260,26 +248,15 @@ void VideoBackend::Shutdown() if (g_vulkan_context) vkDeviceWaitIdle(g_vulkan_context->GetDevice()); - if (g_shader_cache) - g_shader_cache->Shutdown(); - if (g_object_cache) g_object_cache->Shutdown(); - if (g_renderer) - g_renderer->Shutdown(); + ShutdownShared(); - g_perf_query.reset(); - g_texture_cache.reset(); - g_framebuffer_manager.reset(); - g_shader_cache.reset(); - g_vertex_manager.reset(); - g_renderer.reset(); g_object_cache.reset(); StateTracker::DestroyInstance(); g_command_buffer_mgr.reset(); g_vulkan_context.reset(); - ShutdownShared(); UnloadVulkanLibrary(); } diff --git a/Source/Core/VideoBackends/Vulkan/VKPerfQuery.cpp b/Source/Core/VideoBackends/Vulkan/VKPerfQuery.cpp index 9571c69677..d16c8c379b 100644 --- a/Source/Core/VideoBackends/Vulkan/VKPerfQuery.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKPerfQuery.cpp @@ -13,8 +13,9 @@ #include "VideoBackends/Vulkan/CommandBufferManager.h" #include "VideoBackends/Vulkan/StateTracker.h" -#include "VideoBackends/Vulkan/VKRenderer.h" +#include "VideoBackends/Vulkan/VKGfx.h" #include "VideoBackends/Vulkan/VulkanContext.h" +#include "VideoCommon/FramebufferManager.h" #include "VideoCommon/VideoCommon.h" namespace Vulkan @@ -218,8 +219,8 @@ void PerfQuery::ReadbackQueries(u32 query_count) // NOTE: Reported pixel metrics should be referenced to native resolution const u64 native_res_result = static_cast(m_query_result_buffer[i]) * EFB_WIDTH / - g_renderer->GetTargetWidth() * EFB_HEIGHT / - g_renderer->GetTargetHeight(); + g_framebuffer_manager->GetEFBWidth() * EFB_HEIGHT / + g_framebuffer_manager->GetEFBHeight(); m_results[entry.query_group].fetch_add(static_cast(native_res_result), std::memory_order_relaxed); } @@ -234,7 +235,7 @@ void PerfQuery::PartialFlush(bool blocking) if (blocking || m_query_buffer[m_query_readback_pos].fence_counter == g_command_buffer_mgr->GetCurrentFenceCounter()) { - Renderer::GetInstance()->ExecuteCommandBuffer(true, blocking); + VKGfx::GetInstance()->ExecuteCommandBuffer(true, blocking); } ReadbackQueries(); diff --git a/Source/Core/VideoBackends/Vulkan/VKPerfQuery.h b/Source/Core/VideoBackends/Vulkan/VKPerfQuery.h index ee4cabd7d6..a8d31f57e8 100644 --- a/Source/Core/VideoBackends/Vulkan/VKPerfQuery.h +++ b/Source/Core/VideoBackends/Vulkan/VKPerfQuery.h @@ -20,7 +20,7 @@ public: static PerfQuery* GetInstance() { return static_cast(g_perf_query.get()); } - bool Initialize(); + bool Initialize() override; void EnableQuery(PerfQueryGroup group) override; void DisableQuery(PerfQueryGroup group) override; diff --git a/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp b/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp index a7f5bb929d..71fb6bf9ba 100644 --- a/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp @@ -15,7 +15,7 @@ #include "VideoBackends/Vulkan/ObjectCache.h" #include "VideoBackends/Vulkan/VKTexture.h" #include "VideoBackends/Vulkan/VulkanContext.h" -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/Present.h" #if defined(VK_USE_PLATFORM_XLIB_KHR) #include @@ -265,8 +265,8 @@ bool SwapChain::CreateSwapChain() VkExtent2D size = surface_capabilities.currentExtent; if (size.width == UINT32_MAX) { - size.width = std::max(g_renderer->GetBackbufferWidth(), 1); - size.height = std::max(g_renderer->GetBackbufferHeight(), 1); + size.width = std::max(g_presenter->GetBackbufferWidth(), 1); + size.height = std::max(g_presenter->GetBackbufferHeight(), 1); } size.width = std::clamp(size.width, surface_capabilities.minImageExtent.width, surface_capabilities.maxImageExtent.width); diff --git a/Source/Core/VideoBackends/Vulkan/VKTexture.cpp b/Source/Core/VideoBackends/Vulkan/VKTexture.cpp index ec927bf45e..b1c3c122a8 100644 --- a/Source/Core/VideoBackends/Vulkan/VKTexture.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKTexture.cpp @@ -17,7 +17,7 @@ #include "VideoBackends/Vulkan/ObjectCache.h" #include "VideoBackends/Vulkan/StagingBuffer.h" #include "VideoBackends/Vulkan/StateTracker.h" -#include "VideoBackends/Vulkan/VKRenderer.h" +#include "VideoBackends/Vulkan/VKGfx.h" #include "VideoBackends/Vulkan/VKStreamBuffer.h" #include "VideoBackends/Vulkan/VulkanContext.h" @@ -367,7 +367,7 @@ void VKTexture::Load(u32 level, u32 width, u32 height, u32 row_length, const u8* // Execute the command buffer first. WARN_LOG_FMT(VIDEO, "Executing command list while waiting for space in texture upload buffer"); - Renderer::GetInstance()->ExecuteCommandBuffer(false); + VKGfx::GetInstance()->ExecuteCommandBuffer(false); // Try allocating again. This may cause a fence wait. if (!stream_buffer->ReserveMemory(upload_size, upload_alignment)) @@ -967,7 +967,7 @@ void VKStagingTexture::Flush() if (g_command_buffer_mgr->GetCurrentFenceCounter() == m_flush_fence_counter) { // Execute the command buffer and wait for it to finish. - Renderer::GetInstance()->ExecuteCommandBuffer(false, true); + VKGfx::GetInstance()->ExecuteCommandBuffer(false, true); } else { diff --git a/Source/Core/VideoBackends/Vulkan/VKVertexManager.cpp b/Source/Core/VideoBackends/Vulkan/VKVertexManager.cpp index 025fa2be29..5069d3e6d0 100644 --- a/Source/Core/VideoBackends/Vulkan/VKVertexManager.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKVertexManager.cpp @@ -14,7 +14,7 @@ #include "VideoBackends/Vulkan/CommandBufferManager.h" #include "VideoBackends/Vulkan/StateTracker.h" -#include "VideoBackends/Vulkan/VKRenderer.h" +#include "VideoBackends/Vulkan/VKGfx.h" #include "VideoBackends/Vulkan/VKStreamBuffer.h" #include "VideoBackends/Vulkan/VKVertexFormat.h" #include "VideoBackends/Vulkan/VulkanContext.h" @@ -152,7 +152,7 @@ void VertexManager::ResetBuffer(u32 vertex_stride) { // Flush any pending commands first, so that we can wait on the fences WARN_LOG_FMT(VIDEO, "Executing command list while waiting for space in vertex/index buffer"); - Renderer::GetInstance()->ExecuteCommandBuffer(false); + VKGfx::GetInstance()->ExecuteCommandBuffer(false); // Attempt to allocate again, this may cause a fence wait if (!has_vbuffer_allocation) @@ -266,7 +266,7 @@ bool VertexManager::ReserveConstantStorage() // The only places that call constant updates are safe to have state restored. WARN_LOG_FMT(VIDEO, "Executing command buffer while waiting for space in uniform buffer"); - Renderer::GetInstance()->ExecuteCommandBuffer(false); + VKGfx::GetInstance()->ExecuteCommandBuffer(false); // Since we are on a new command buffer, all constants have been invalidated, and we need // to reupload them. We may as well do this now, since we're issuing a draw anyway. @@ -337,7 +337,7 @@ void VertexManager::UploadUtilityUniforms(const void* data, u32 data_size) g_vulkan_context->GetUniformBufferAlignment())) { WARN_LOG_FMT(VIDEO, "Executing command buffer while waiting for ext space in uniform buffer"); - Renderer::GetInstance()->ExecuteCommandBuffer(false); + VKGfx::GetInstance()->ExecuteCommandBuffer(false); } StateTracker::GetInstance()->SetUtilityUniformBuffer( @@ -358,7 +358,7 @@ bool VertexManager::UploadTexelBuffer(const void* data, u32 data_size, TexelBuff { // Try submitting cmdbuffer. WARN_LOG_FMT(VIDEO, "Submitting command buffer while waiting for space in texel buffer"); - Renderer::GetInstance()->ExecuteCommandBuffer(false, false); + VKGfx::GetInstance()->ExecuteCommandBuffer(false, false); if (!m_texel_stream_buffer->ReserveMemory(data_size, elem_size)) { PanicAlertFmt("Failed to allocate {} bytes from texel buffer", data_size); @@ -388,7 +388,7 @@ bool VertexManager::UploadTexelBuffer(const void* data, u32 data_size, TexelBuff { // Try submitting cmdbuffer. WARN_LOG_FMT(VIDEO, "Submitting command buffer while waiting for space in texel buffer"); - Renderer::GetInstance()->ExecuteCommandBuffer(false, false); + VKGfx::GetInstance()->ExecuteCommandBuffer(false, false); if (!m_texel_stream_buffer->ReserveMemory(reserve_size, elem_size)) { PanicAlertFmt("Failed to allocate {} bytes from texel buffer", reserve_size); diff --git a/Source/Core/VideoCommon/AbstractGfx.cpp b/Source/Core/VideoCommon/AbstractGfx.cpp new file mode 100644 index 0000000000..6e1c56aa20 --- /dev/null +++ b/Source/Core/VideoCommon/AbstractGfx.cpp @@ -0,0 +1,180 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/AbstractGfx.h" + +#include "Common/Assert.h" + +#include "VideoCommon/AbstractFramebuffer.h" +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/BPFunctions.h" +#include "VideoCommon/FramebufferManager.h" +#include "VideoCommon/RenderBase.h" +#include "VideoCommon/ShaderCache.h" +#include "VideoCommon/VertexManagerBase.h" +#include "VideoCommon/VideoConfig.h" + +std::unique_ptr g_gfx; + +AbstractGfx::AbstractGfx() +{ + ConfigChangedEvent::Register([this](u32 bits) { OnConfigChanged(bits); }, "AbstractGfx"); +} + +bool AbstractGfx::IsHeadless() const +{ + return true; +} + +void AbstractGfx::BeginUtilityDrawing() +{ + g_vertex_manager->Flush(); +} + +void AbstractGfx::EndUtilityDrawing() +{ + // Reset framebuffer/scissor/viewport. Pipeline will be reset at next draw. + g_framebuffer_manager->BindEFBFramebuffer(); + BPFunctions::SetScissorAndViewport(); +} + +void AbstractGfx::SetFramebuffer(AbstractFramebuffer* framebuffer) +{ + m_current_framebuffer = framebuffer; +} + +void AbstractGfx::SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer) +{ + m_current_framebuffer = framebuffer; +} + +void AbstractGfx::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, + const ClearColor& color_value, float depth_value) +{ + m_current_framebuffer = framebuffer; +} + +void AbstractGfx::ClearRegion(const MathUtil::Rectangle& target_rc, bool colorEnable, + bool alphaEnable, bool zEnable, u32 color, u32 z) +{ + // This is a generic fallback for any ClearRegion operations that backends don't support. + // It simply draws a Quad. + + BeginUtilityDrawing(); + + // Set up uniforms. + struct Uniforms + { + float clear_color[4]; + float clear_depth; + float padding1, padding2, padding3; + }; + static_assert(std::is_standard_layout::value); + Uniforms uniforms = {{static_cast((color >> 16) & 0xFF) / 255.0f, + static_cast((color >> 8) & 0xFF) / 255.0f, + static_cast((color >> 0) & 0xFF) / 255.0f, + static_cast((color >> 24) & 0xFF) / 255.0f}, + static_cast(z & 0xFFFFFF) / 16777216.0f}; + if (!g_ActiveConfig.backend_info.bSupportsReversedDepthRange) + uniforms.clear_depth = 1.0f - uniforms.clear_depth; + g_vertex_manager->UploadUtilityUniforms(&uniforms, sizeof(uniforms)); + + g_gfx->SetPipeline(g_framebuffer_manager->GetClearPipeline(colorEnable, alphaEnable, zEnable)); + g_gfx->SetViewportAndScissor(target_rc); + g_gfx->Draw(0, 3); + EndUtilityDrawing(); +} + +void AbstractGfx::SetViewportAndScissor(const MathUtil::Rectangle& rect, float min_depth, + float max_depth) +{ + SetViewport(static_cast(rect.left), static_cast(rect.top), + static_cast(rect.GetWidth()), static_cast(rect.GetHeight()), min_depth, + max_depth); + SetScissorRect(rect); +} + +void AbstractGfx::ScaleTexture(AbstractFramebuffer* dst_framebuffer, + const MathUtil::Rectangle& dst_rect, + const AbstractTexture* src_texture, + const MathUtil::Rectangle& src_rect) +{ + ASSERT(dst_framebuffer->GetColorFormat() == AbstractTextureFormat::RGBA8); + + BeginUtilityDrawing(); + + // The shader needs to know the source rectangle. + const auto converted_src_rect = + ConvertFramebufferRectangle(src_rect, src_texture->GetWidth(), src_texture->GetHeight()); + const float rcp_src_width = 1.0f / src_texture->GetWidth(); + const float rcp_src_height = 1.0f / src_texture->GetHeight(); + const std::array uniforms = {{converted_src_rect.left * rcp_src_width, + converted_src_rect.top * rcp_src_height, + converted_src_rect.GetWidth() * rcp_src_width, + converted_src_rect.GetHeight() * rcp_src_height}}; + g_vertex_manager->UploadUtilityUniforms(&uniforms, sizeof(uniforms)); + + // Discard if we're overwriting the whole thing. + if (static_cast(dst_rect.GetWidth()) == dst_framebuffer->GetWidth() && + static_cast(dst_rect.GetHeight()) == dst_framebuffer->GetHeight()) + { + SetAndDiscardFramebuffer(dst_framebuffer); + } + else + { + SetFramebuffer(dst_framebuffer); + } + + SetViewportAndScissor(ConvertFramebufferRectangle(dst_rect, dst_framebuffer)); + SetPipeline(dst_framebuffer->GetLayers() > 1 ? g_shader_cache->GetRGBA8StereoCopyPipeline() : + g_shader_cache->GetRGBA8CopyPipeline()); + SetTexture(0, src_texture); + SetSamplerState(0, RenderState::GetLinearSamplerState()); + Draw(0, 3); + EndUtilityDrawing(); + if (dst_framebuffer->GetColorAttachment()) + dst_framebuffer->GetColorAttachment()->FinishedRendering(); +} + +MathUtil::Rectangle +AbstractGfx::ConvertFramebufferRectangle(const MathUtil::Rectangle& rect, + const AbstractFramebuffer* framebuffer) const +{ + return ConvertFramebufferRectangle(rect, framebuffer->GetWidth(), framebuffer->GetHeight()); +} + +MathUtil::Rectangle +AbstractGfx::ConvertFramebufferRectangle(const MathUtil::Rectangle& rect, u32 fb_width, + u32 fb_height) const +{ + MathUtil::Rectangle ret = rect; + if (g_ActiveConfig.backend_info.bUsesLowerLeftOrigin) + { + ret.top = fb_height - rect.bottom; + ret.bottom = fb_height - rect.top; + } + return ret; +} + +std::unique_ptr AbstractGfx::CreateAsyncShaderCompiler() +{ + return std::make_unique(); +} + +void AbstractGfx::OnConfigChanged(u32 changed_bits) +{ + // If there's any shader changes, wait for the GPU to finish before destroying anything. + if (changed_bits & (CONFIG_CHANGE_BIT_HOST_CONFIG | CONFIG_CHANGE_BIT_MULTISAMPLES)) + { + WaitForGPUIdle(); + SetPipeline(nullptr); + } +} + +bool AbstractGfx::UseGeometryShaderForUI() const +{ + // OpenGL doesn't render to a 2-layer backbuffer like D3D/Vulkan for quad-buffered stereo, + // instead drawing twice and the eye selected by glDrawBuffer() (see Presenter::RenderXFBToScreen) + return g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer && + g_ActiveConfig.backend_info.api_type != APIType::OpenGL; +} diff --git a/Source/Core/VideoCommon/AbstractGfx.h b/Source/Core/VideoCommon/AbstractGfx.h new file mode 100644 index 0000000000..916bcd4e11 --- /dev/null +++ b/Source/Core/VideoCommon/AbstractGfx.h @@ -0,0 +1,171 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Common/MathUtil.h" + +#include "VideoCommon/RenderState.h" + +#include +#include + +class AbstractFramebuffer; +class AbstractPipeline; +class AbstractShader; +class AbstractTexture; +class AbstractStagingTexture; +class NativeVertexFormat; +struct ComputePipelineConfig; +struct AbstractPipelineConfig; +struct PortableVertexDeclaration; +struct TextureConfig; +enum class AbstractTextureFormat : u32; +enum class ShaderStage; +enum class StagingTextureType; + +struct SurfaceInfo +{ + u32 width = 0; + u32 height = 0; + float scale = 0.0f; + AbstractTextureFormat format = {}; +}; + +namespace VideoCommon +{ +class AsyncShaderCompiler; +} + +using ClearColor = std::array; + +// AbstractGfx is the root of Dolphin's Graphics API abstraction layer. +// +// Abstract knows nothing about the internals of the GameCube/Wii, that is all handled elsewhere in +// VideoCommon. + +class AbstractGfx +{ +public: + AbstractGfx(); + virtual ~AbstractGfx() = default; + + virtual bool IsHeadless() const = 0; + + // Does the backend support drawing a UI or doing post-processing + virtual bool SupportsUtilityDrawing() const { return true; } + + virtual void SetPipeline(const AbstractPipeline* pipeline) {} + virtual void SetScissorRect(const MathUtil::Rectangle& rc) {} + virtual void SetTexture(u32 index, const AbstractTexture* texture) {} + virtual void SetSamplerState(u32 index, const SamplerState& state) {} + virtual void SetComputeImageTexture(AbstractTexture* texture, bool read, bool write) {} + virtual void UnbindTexture(const AbstractTexture* texture) {} + virtual void SetViewport(float x, float y, float width, float height, float near_depth, + float far_depth) + { + } + virtual void SetFullscreen(bool enable_fullscreen) {} + virtual bool IsFullscreen() const { return false; } + virtual void BeginUtilityDrawing(); + virtual void EndUtilityDrawing(); + virtual std::unique_ptr CreateTexture(const TextureConfig& config, + std::string_view name = "") = 0; + virtual std::unique_ptr + CreateStagingTexture(StagingTextureType type, const TextureConfig& config) = 0; + virtual std::unique_ptr + CreateFramebuffer(AbstractTexture* color_attachment, AbstractTexture* depth_attachment) = 0; + + // Framebuffer operations. + virtual void SetFramebuffer(AbstractFramebuffer* framebuffer); + virtual void SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer); + virtual void SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, + const ClearColor& color_value = {}, float depth_value = 0.0f); + + virtual void ClearRegion(const MathUtil::Rectangle& target_rc, bool colorEnable, + bool alphaEnable, bool zEnable, u32 color, u32 z); + + // Drawing with currently-bound pipeline state. + virtual void Draw(u32 base_vertex, u32 num_vertices) {} + virtual void DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) {} + + // Dispatching compute shaders with currently-bound state. + virtual void DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u32 groupsize_y, + u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z) + { + } + + // Binds the backbuffer for rendering. The buffer will be cleared immediately after binding. + // This is where any window size changes are detected, therefore m_backbuffer_width and/or + // m_backbuffer_height may change after this function returns. + virtual void BindBackbuffer(const ClearColor& clear_color = {}) {} + + // Presents the backbuffer to the window system, or "swaps buffers". + virtual void PresentBackbuffer() {} + + // Shader modules/objects. + virtual std::unique_ptr CreateShaderFromSource(ShaderStage stage, + std::string_view source, + std::string_view name = "") = 0; + virtual std::unique_ptr CreateShaderFromBinary(ShaderStage stage, + const void* data, size_t length, + std::string_view name = "") = 0; + virtual std::unique_ptr + CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) = 0; + virtual std::unique_ptr CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data = nullptr, + size_t cache_data_length = 0) = 0; + + AbstractFramebuffer* GetCurrentFramebuffer() const { return m_current_framebuffer; } + + // Sets viewport and scissor to the specified rectangle. rect is assumed to be in framebuffer + // coordinates, i.e. lower-left origin in OpenGL. + void SetViewportAndScissor(const MathUtil::Rectangle& rect, float min_depth = 0.0f, + float max_depth = 1.0f); + + // Scales a GPU texture using a copy shader. + virtual void ScaleTexture(AbstractFramebuffer* dst_framebuffer, + const MathUtil::Rectangle& dst_rect, + const AbstractTexture* src_texture, + const MathUtil::Rectangle& src_rect); + + // Converts an upper-left to lower-left if required by the backend, optionally + // clamping to the framebuffer size. + MathUtil::Rectangle ConvertFramebufferRectangle(const MathUtil::Rectangle& rect, + u32 fb_width, u32 fb_height) const; + MathUtil::Rectangle + ConvertFramebufferRectangle(const MathUtil::Rectangle& rect, + const AbstractFramebuffer* framebuffer) const; + + virtual void Flush() {} + virtual void WaitForGPUIdle() {} + + // For opengl's glDrawBuffer + virtual void SelectLeftBuffer() {} + virtual void SelectRightBuffer() {} + virtual void SelectMainBuffer() {} + + // A simple presentation fallback, only used by video software + virtual void ShowImage(const AbstractTexture* source_texture, + const MathUtil::Rectangle& source_rc) + { + } + + virtual std::unique_ptr CreateAsyncShaderCompiler(); + + // Called when the configuration changes, and backend structures need to be updated. + virtual void OnConfigChanged(u32 changed_bits); + + // Returns true if a layer-expanding geometry shader should be used when rendering the user + // interface and final XFB. + bool UseGeometryShaderForUI() const; + + // Returns info about the main surface (aka backbuffer) + virtual SurfaceInfo GetSurfaceInfo() const { return {}; } + +protected: + AbstractFramebuffer* m_current_framebuffer = nullptr; + const AbstractPipeline* m_current_pipeline = nullptr; +}; + +extern std::unique_ptr g_gfx; diff --git a/Source/Core/VideoCommon/AbstractTexture.cpp b/Source/Core/VideoCommon/AbstractTexture.cpp index fb34e2413e..03b83b21cc 100644 --- a/Source/Core/VideoCommon/AbstractTexture.cpp +++ b/Source/Core/VideoCommon/AbstractTexture.cpp @@ -8,8 +8,8 @@ #include "Common/Assert.h" #include "Common/Image.h" #include "Common/MsgHandler.h" +#include "VideoCommon/AbstractGfx.h" #include "VideoCommon/AbstractStagingTexture.h" -#include "VideoCommon/RenderBase.h" AbstractTexture::AbstractTexture(const TextureConfig& c) : m_config(c) { @@ -36,7 +36,7 @@ bool AbstractTexture::Save(const std::string& filename, unsigned int level) TextureConfig readback_texture_config(level_width, level_height, 1, 1, 1, AbstractTextureFormat::RGBA8, 0); auto readback_texture = - g_renderer->CreateStagingTexture(StagingTextureType::Readback, readback_texture_config); + g_gfx->CreateStagingTexture(StagingTextureType::Readback, readback_texture_config); if (!readback_texture) return false; diff --git a/Source/Core/VideoCommon/AsyncRequests.cpp b/Source/Core/VideoCommon/AsyncRequests.cpp index aca5be57a4..ad854a3dce 100644 --- a/Source/Core/VideoCommon/AsyncRequests.cpp +++ b/Source/Core/VideoCommon/AsyncRequests.cpp @@ -6,12 +6,16 @@ #include #include "Core/System.h" + +#include "VideoCommon/BoundingBox.h" #include "VideoCommon/Fifo.h" +#include "VideoCommon/Present.h" #include "VideoCommon/RenderBase.h" #include "VideoCommon/Statistics.h" #include "VideoCommon/VertexManagerBase.h" #include "VideoCommon/VideoBackendBase.h" #include "VideoCommon/VideoCommon.h" +#include "VideoCommon/VideoEvents.h" #include "VideoCommon/VideoState.h" AsyncRequests AsyncRequests::s_singleton; @@ -152,12 +156,12 @@ void AsyncRequests::HandleEvent(const AsyncRequests::Event& e) break; case Event::SWAP_EVENT: - g_renderer->Swap(e.swap_event.xfbAddr, e.swap_event.fbWidth, e.swap_event.fbStride, - e.swap_event.fbHeight, e.time); + g_presenter->ViSwap(e.swap_event.xfbAddr, e.swap_event.fbWidth, e.swap_event.fbStride, + e.swap_event.fbHeight, e.time); break; case Event::BBOX_READ: - *e.bbox.data = g_renderer->BBoxRead(e.bbox.index); + *e.bbox.data = g_bounding_box->Get(e.bbox.index); break; case Event::FIFO_RESET: diff --git a/Source/Core/VideoCommon/BPFunctions.cpp b/Source/Core/VideoCommon/BPFunctions.cpp index c065f1cc5f..ad9898b27e 100644 --- a/Source/Core/VideoCommon/BPFunctions.cpp +++ b/Source/Core/VideoCommon/BPFunctions.cpp @@ -12,11 +12,13 @@ #include "Common/Logging/Log.h" #include "VideoCommon/AbstractFramebuffer.h" +#include "VideoCommon/AbstractGfx.h" #include "VideoCommon/BPMemory.h" #include "VideoCommon/FramebufferManager.h" #include "VideoCommon/RenderBase.h" #include "VideoCommon/RenderState.h" #include "VideoCommon/VertexManagerBase.h" +#include "VideoCommon/VertexShaderManager.h" #include "VideoCommon/VideoCommon.h" #include "VideoCommon/VideoConfig.h" #include "VideoCommon/XFMemory.h" @@ -157,11 +159,11 @@ ScissorResult::ScissorResult(const BPMemory& bpmemory, std::pair v for (const auto& x_range : x_ranges) { DEBUG_ASSERT(x_range.start < x_range.end); - DEBUG_ASSERT(x_range.end <= EFB_WIDTH); + DEBUG_ASSERT(static_cast(x_range.end) <= EFB_WIDTH); for (const auto& y_range : y_ranges) { DEBUG_ASSERT(y_range.start < y_range.end); - DEBUG_ASSERT(y_range.end <= EFB_HEIGHT); + DEBUG_ASSERT(static_cast(y_range.end) <= EFB_HEIGHT); m_result.emplace_back(x_range, y_range); } } @@ -197,10 +199,9 @@ void SetScissorAndViewport() { auto native_rc = ComputeScissorRects().Best(); - auto target_rc = g_renderer->ConvertEFBRectangle(native_rc.rect); - auto converted_rc = - g_renderer->ConvertFramebufferRectangle(target_rc, g_renderer->GetCurrentFramebuffer()); - g_renderer->SetScissorRect(converted_rc); + auto target_rc = g_framebuffer_manager->ConvertEFBRectangle(native_rc.rect); + auto converted_rc = g_gfx->ConvertFramebufferRectangle(target_rc, g_gfx->GetCurrentFramebuffer()); + g_gfx->SetScissorRect(converted_rc); float raw_x = (xfmem.viewport.xOrig - native_rc.x_off) - xfmem.viewport.wd; float raw_y = (xfmem.viewport.yOrig - native_rc.y_off) + xfmem.viewport.ht; @@ -216,10 +217,10 @@ void SetScissorAndViewport() raw_height = std::round(raw_height); } - float x = g_renderer->EFBToScaledXf(raw_x); - float y = g_renderer->EFBToScaledYf(raw_y); - float width = g_renderer->EFBToScaledXf(raw_width); - float height = g_renderer->EFBToScaledYf(raw_height); + float x = g_framebuffer_manager->EFBToScaledXf(raw_x); + float y = g_framebuffer_manager->EFBToScaledYf(raw_y); + float width = g_framebuffer_manager->EFBToScaledXf(raw_width); + float height = g_framebuffer_manager->EFBToScaledYf(raw_height); float min_depth = (xfmem.viewport.farZ - xfmem.viewport.zRange) / 16777216.0f; float max_depth = xfmem.viewport.farZ / 16777216.0f; if (width < 0.f) @@ -246,7 +247,7 @@ void SetScissorAndViewport() max_depth = std::clamp(max_depth, 0.0f, GX_MAX_DEPTH); } - if (g_renderer->UseVertexDepthRange()) + if (VertexShaderManager::UseVertexDepthRange()) { // We need to ensure depth values are clamped the maximum value supported by the console GPU. // Taking into account whether the depth range is inverted or not. @@ -280,9 +281,9 @@ void SetScissorAndViewport() // Lower-left flip. if (g_ActiveConfig.backend_info.bUsesLowerLeftOrigin) - y = static_cast(g_renderer->GetCurrentFramebuffer()->GetHeight()) - y - height; + y = static_cast(g_gfx->GetCurrentFramebuffer()->GetHeight()) - y - height; - g_renderer->SetViewport(x, y, width, height, near_depth, far_depth); + g_gfx->SetViewport(x, y, width, height, near_depth, far_depth); } void SetDepthMode() @@ -342,7 +343,7 @@ void ClearScreen(const MathUtil::Rectangle& rc) color = RGBA8ToRGB565ToRGBA8(color); z = Z24ToZ16ToZ24(z); } - g_renderer->ClearScreen(rc, colorEnable, alphaEnable, zEnable, color, z); + g_framebuffer_manager->ClearEFB(rc, colorEnable, alphaEnable, zEnable, color, z); } } @@ -364,9 +365,9 @@ void OnPixelFormatChange() if (!g_ActiveConfig.bEFBEmulateFormatChanges) return; - const auto old_format = g_renderer->GetPrevPixelFormat(); + const auto old_format = g_framebuffer_manager->GetPrevPixelFormat(); const auto new_format = bpmem.zcontrol.pixel_format; - g_renderer->StorePixelFormat(new_format); + g_framebuffer_manager->StorePixelFormat(new_format); DEBUG_LOG_FMT(VIDEO, "pixelfmt: pixel={}, zc={}", new_format, bpmem.zcontrol.zformat); diff --git a/Source/Core/VideoCommon/BPStructs.cpp b/Source/Core/VideoCommon/BPStructs.cpp index 075e2243bf..8bb312f1ba 100644 --- a/Source/Core/VideoCommon/BPStructs.cpp +++ b/Source/Core/VideoCommon/BPStructs.cpp @@ -33,7 +33,7 @@ #include "VideoCommon/PerfQueryBase.h" #include "VideoCommon/PixelEngine.h" #include "VideoCommon/PixelShaderManager.h" -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/Present.h" #include "VideoCommon/Statistics.h" #include "VideoCommon/TMEM.h" #include "VideoCommon/TextureCacheBase.h" @@ -42,6 +42,7 @@ #include "VideoCommon/VideoBackendBase.h" #include "VideoCommon/VideoCommon.h" #include "VideoCommon/VideoConfig.h" +#include "VideoCommon/VideoEvents.h" using namespace BPFunctions; @@ -185,6 +186,7 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager, { INCSTAT(g_stats.this_frame.num_draw_done); g_texture_cache->FlushEFBCopies(); + g_texture_cache->FlushStaleBinds(); g_framebuffer_manager->InvalidatePeekCache(false); g_framebuffer_manager->RefreshPeekCache(); auto& system = Core::System::GetInstance(); @@ -203,6 +205,7 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager, { INCSTAT(g_stats.this_frame.num_token); g_texture_cache->FlushEFBCopies(); + g_texture_cache->FlushStaleBinds(); g_framebuffer_manager->InvalidatePeekCache(false); g_framebuffer_manager->RefreshPeekCache(); auto& system = Core::System::GetInstance(); @@ -218,6 +221,7 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager, { INCSTAT(g_stats.this_frame.num_token_int); g_texture_cache->FlushEFBCopies(); + g_texture_cache->FlushStaleBinds(); g_framebuffer_manager->InvalidatePeekCache(false); g_framebuffer_manager->RefreshPeekCache(); auto& system = Core::System::GetInstance(); @@ -282,7 +286,7 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager, if (PE_copy.copy_to_xfb == 1) { // Make sure we disable Bounding box to match the side effects of the non-failure path - g_renderer->BBoxDisable(pixel_shader_manager); + g_bounding_box->Disable(pixel_shader_manager); } return; @@ -313,7 +317,7 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager, // We should be able to get away with deactivating the current bbox tracking // here. Not sure if there's a better spot to put this. // the number of lines copied is determined by the y scale * source efb height - g_renderer->BBoxDisable(pixel_shader_manager); + g_bounding_box->Disable(pixel_shader_manager); float yScale; if (PE_copy.scale_invert) @@ -337,14 +341,26 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager, false, false, yScale, s_gammaLUT[PE_copy.gamma], bpmem.triggerEFBCopy.clamp_top, bpmem.triggerEFBCopy.clamp_bottom, bpmem.copyfilter.GetCoefficients()); - // This stays in to signal end of a "frame" - g_renderer->RenderToXFB(destAddr, srcRect, destStride, height, s_gammaLUT[PE_copy.gamma]); + // This is as closest as we have to an "end of the frame" + // It works 99% of the time. + // But sometimes games want to render an XFB larger than the EFB's 640x528 pixel resolution + // (especially when using the 3xMSAA mode, which cuts EFB resolution to 640x264). So they + // render multiple sub-frames and arrange the XFB copies in next to each-other in main memory + // so they form a single completed XFB. + // See https://dolphin-emu.org/blog/2017/11/19/hybridxfb/ for examples and more detail. + AfterFrameEvent::Trigger(); + + // Note: Theoretically, in the future we could track the VI configuration and try to detect + // when an XFB is the last XFB copy of a frame. Not only would we get a clean "end of + // the frame", but we would also be able to use ImmediateXFB even for these games. + // Might also clean up some issues with games doing XFB copies they don't intend to + // display. if (g_ActiveConfig.bImmediateXFB) { // below div two to convert from bytes to pixels - it expects width, not stride - g_renderer->Swap(destAddr, destStride / 2, destStride, height, - Core::System::GetInstance().GetCoreTiming().GetTicks()); + u64 ticks = Core::System::GetInstance().GetCoreTiming().GetTicks(); + g_presenter->ImmediateSwap(destAddr, destStride / 2, destStride, height, ticks); } else { @@ -481,10 +497,10 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager, case BPMEM_CLEARBBOX2: { const u8 offset = bp.address & 2; - g_renderer->BBoxEnable(pixel_shader_manager); + g_bounding_box->Enable(pixel_shader_manager); - g_renderer->BBoxWrite(offset, bp.newvalue & 0x3ff); - g_renderer->BBoxWrite(offset + 1, bp.newvalue >> 10); + g_bounding_box->Set(offset, bp.newvalue & 0x3ff); + g_bounding_box->Set(offset + 1, bp.newvalue >> 10); } return; case BPMEM_TEXINVALIDATE: diff --git a/Source/Core/VideoCommon/BoundingBox.cpp b/Source/Core/VideoCommon/BoundingBox.cpp index af0961aab6..ffc85d824c 100644 --- a/Source/Core/VideoCommon/BoundingBox.cpp +++ b/Source/Core/VideoCommon/BoundingBox.cpp @@ -13,6 +13,8 @@ #include +std::unique_ptr g_bounding_box; + void BoundingBox::Enable(PixelShaderManager& pixel_shader_manager) { m_is_active = true; @@ -27,7 +29,7 @@ void BoundingBox::Disable(PixelShaderManager& pixel_shader_manager) void BoundingBox::Flush() { - if (!g_ActiveConfig.backend_info.bSupportsBBox) + if (!g_ActiveConfig.bBBoxEnable || !g_ActiveConfig.backend_info.bSupportsBBox) return; m_is_valid = false; @@ -74,6 +76,9 @@ u16 BoundingBox::Get(u32 index) { ASSERT(index < NUM_BBOX_VALUES); + if (!g_ActiveConfig.bBBoxEnable || !g_ActiveConfig.backend_info.bSupportsBBox) + return m_bounding_box_fallback[index]; + if (!m_is_valid) Readback(); @@ -84,6 +89,12 @@ void BoundingBox::Set(u32 index, u16 value) { ASSERT(index < NUM_BBOX_VALUES); + if (!g_ActiveConfig.bBBoxEnable || !g_ActiveConfig.backend_info.bSupportsBBox) + { + m_bounding_box_fallback[index] = value; + return; + } + if (m_is_valid && m_values[index] == value) return; @@ -96,6 +107,7 @@ void BoundingBox::Set(u32 index, u16 value) // Nonetheless, it has been designed to be as safe as possible. void BoundingBox::DoState(PointerWrap& p) { + p.DoArray(m_bounding_box_fallback); p.Do(m_is_active); p.DoArray(m_values); p.DoArray(m_dirty); diff --git a/Source/Core/VideoCommon/BoundingBox.h b/Source/Core/VideoCommon/BoundingBox.h index bf8ceeb818..ed4861d6ad 100644 --- a/Source/Core/VideoCommon/BoundingBox.h +++ b/Source/Core/VideoCommon/BoundingBox.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include "Common/CommonTypes.h" @@ -48,4 +49,18 @@ private: std::array m_values = {}; std::array m_dirty = {}; bool m_is_valid = true; + + // Nintendo's SDK seems to write "default" bounding box values before every draw (1023 0 1023 0 + // are the only values encountered so far, which happen to be the extents allowed by the BP + // registers) to reset the registers for comparison in the pixel engine, and presumably to detect + // whether GX has updated the registers with real values. + // + // We can store these values when Bounding Box emulation is disabled and return them on read, + // which the game will interpret as "no pixels have been drawn" + // + // This produces much better results than just returning garbage, which can cause games like + // Ultimate Spider-Man to crash + std::array m_bounding_box_fallback = {}; }; + +extern std::unique_ptr g_bounding_box; diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index 4e8bff36fc..392e53df91 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -1,6 +1,8 @@ add_library(videocommon AbstractFramebuffer.cpp AbstractFramebuffer.h + AbstractGfx.cpp + AbstractGfx.h AbstractShader.h AbstractStagingTexture.cpp AbstractStagingTexture.h @@ -34,6 +36,9 @@ add_library(videocommon FramebufferManager.h FramebufferShaderGen.cpp FramebufferShaderGen.h + FrameDumper.cpp + FrameDumper.h + FrameDumpFFMpeg.h FreeLookCamera.cpp FreeLookCamera.h GeometryShaderGen.cpp @@ -82,6 +87,9 @@ add_library(videocommon NetPlayGolfUI.h OnScreenDisplay.cpp OnScreenDisplay.h + OnScreenUI.cpp + OnScreenUI.h + OnScreenUIKeyMap.h OpcodeDecoding.cpp OpcodeDecoding.h PerfQueryBase.cpp @@ -98,6 +106,8 @@ add_library(videocommon PixelShaderManager.h PostProcessing.cpp PostProcessing.h + Present.cpp + Present.h RenderBase.cpp RenderBase.h RenderState.cpp @@ -154,11 +164,14 @@ add_library(videocommon VertexShaderManager.h VideoBackendBase.cpp VideoBackendBase.h + VideoEvents.h VideoCommon.h VideoConfig.cpp VideoConfig.h VideoState.cpp VideoState.h + Widescreen.cpp + Widescreen.h XFMemory.cpp XFMemory.h XFStructs.cpp @@ -197,8 +210,7 @@ endif() if(FFmpeg_FOUND) target_sources(videocommon PRIVATE - FrameDump.cpp - FrameDump.h + FrameDumpFFMpeg.cpp ) target_link_libraries(videocommon PRIVATE FFmpeg::avcodec diff --git a/Source/Core/VideoCommon/FrameDump.cpp b/Source/Core/VideoCommon/FrameDumpFFMpeg.cpp similarity index 95% rename from Source/Core/VideoCommon/FrameDump.cpp rename to Source/Core/VideoCommon/FrameDumpFFMpeg.cpp index 55e0e3daa6..09fdf4dbac 100644 --- a/Source/Core/VideoCommon/FrameDump.cpp +++ b/Source/Core/VideoCommon/FrameDumpFFMpeg.cpp @@ -1,7 +1,7 @@ // Copyright 2009 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "VideoCommon/FrameDump.h" +#include "VideoCommon/FrameDumpFFMpeg.h" #if defined(__FreeBSD__) #define __STDC_CONSTANT_MACROS 1 @@ -37,6 +37,7 @@ extern "C" { #include "Core/HW/SystemTimers.h" #include "Core/HW/VideoInterface.h" +#include "VideoCommon/FrameDumper.h" #include "VideoCommon/OnScreenDisplay.h" #include "VideoCommon/VideoConfig.h" @@ -157,7 +158,7 @@ std::string AVErrorString(int error) } // namespace -bool FrameDump::Start(int w, int h, u64 start_ticks) +bool FFMpegFrameDump::Start(int w, int h, u64 start_ticks) { if (IsStarted()) return true; @@ -169,7 +170,7 @@ bool FrameDump::Start(int w, int h, u64 start_ticks) return PrepareEncoding(w, h, start_ticks, m_savestate_index); } -bool FrameDump::PrepareEncoding(int w, int h, u64 start_ticks, u32 savestate_index) +bool FFMpegFrameDump::PrepareEncoding(int w, int h, u64 start_ticks, u32 savestate_index) { m_context = std::make_unique(); @@ -189,7 +190,7 @@ bool FrameDump::PrepareEncoding(int w, int h, u64 start_ticks, u32 savestate_ind return success; } -bool FrameDump::CreateVideoFile() +bool FFMpegFrameDump::CreateVideoFile() { const std::string& format = g_Config.sDumpFormat; @@ -335,12 +336,12 @@ bool FrameDump::CreateVideoFile() return true; } -bool FrameDump::IsFirstFrameInCurrentFile() const +bool FFMpegFrameDump::IsFirstFrameInCurrentFile() const { return m_context->last_pts == AV_NOPTS_VALUE; } -void FrameDump::AddFrame(const FrameData& frame) +void FFMpegFrameDump::AddFrame(const FrameData& frame) { // Are we even dumping? if (!IsStarted()) @@ -402,7 +403,7 @@ void FrameDump::AddFrame(const FrameData& frame) ProcessPackets(); } -void FrameDump::ProcessPackets() +void FFMpegFrameDump::ProcessPackets() { auto pkt = std::unique_ptr>( av_packet_alloc(), [](AVPacket* packet) { av_packet_free(&packet); }); @@ -440,7 +441,7 @@ void FrameDump::ProcessPackets() } } -void FrameDump::Stop() +void FFMpegFrameDump::Stop() { if (!IsStarted()) return; @@ -457,12 +458,12 @@ void FrameDump::Stop() OSD::AddMessage("Stopped dumping frames"); } -bool FrameDump::IsStarted() const +bool FFMpegFrameDump::IsStarted() const { return m_context != nullptr; } -void FrameDump::CloseVideoFile() +void FFMpegFrameDump::CloseVideoFile() { av_frame_free(&m_context->src_frame); av_frame_free(&m_context->scaled_frame); @@ -480,13 +481,13 @@ void FrameDump::CloseVideoFile() m_context.reset(); } -void FrameDump::DoState(PointerWrap& p) +void FFMpegFrameDump::DoState(PointerWrap& p) { if (p.IsReadMode()) ++m_savestate_index; } -void FrameDump::CheckForConfigChange(const FrameData& frame) +void FFMpegFrameDump::CheckForConfigChange(const FrameData& frame) { bool restart_dump = false; @@ -524,7 +525,7 @@ void FrameDump::CheckForConfigChange(const FrameData& frame) } } -FrameDump::FrameState FrameDump::FetchState(u64 ticks, int frame_number) const +FrameState FFMpegFrameDump::FetchState(u64 ticks, int frame_number) const { FrameState state; state.ticks = ticks; @@ -537,9 +538,9 @@ FrameDump::FrameState FrameDump::FetchState(u64 ticks, int frame_number) const return state; } -FrameDump::FrameDump() = default; +FFMpegFrameDump::FFMpegFrameDump() = default; -FrameDump::~FrameDump() +FFMpegFrameDump::~FFMpegFrameDump() { Stop(); } diff --git a/Source/Core/VideoCommon/FrameDump.h b/Source/Core/VideoCommon/FrameDumpFFMpeg.h similarity index 60% rename from Source/Core/VideoCommon/FrameDump.h rename to Source/Core/VideoCommon/FrameDumpFFMpeg.h index 23e3bead20..8431e24274 100644 --- a/Source/Core/VideoCommon/FrameDump.h +++ b/Source/Core/VideoCommon/FrameDumpFFMpeg.h @@ -11,31 +11,31 @@ struct FrameDumpContext; class PointerWrap; -class 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; +}; + +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&); @@ -65,10 +65,10 @@ private: }; #if !defined(HAVE_FFMPEG) -inline FrameDump::FrameDump() = default; -inline FrameDump::~FrameDump() = default; +inline FFMpegFrameDump::FFMpegFrameDump() = default; +inline FFMpegFrameDump::~FFMpegFrameDump() = default; -inline FrameDump::FrameState FrameDump::FetchState(u64 ticks, int frame_number) const +inline FrameState FFMpegFrameDump::FetchState(u64 ticks, int frame_number) const { return {}; } diff --git a/Source/Core/VideoCommon/FrameDumper.cpp b/Source/Core/VideoCommon/FrameDumper.cpp new file mode 100644 index 0000000000..06bbe1b1c9 --- /dev/null +++ b/Source/Core/VideoCommon/FrameDumper.cpp @@ -0,0 +1,361 @@ +// 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/AbstractGfx.h" +#include "VideoCommon/AbstractStagingTexture.h" +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/OnScreenDisplay.h" +#include "VideoCommon/Present.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() +{ + m_frame_end_handle = AfterFrameEvent::Register([this] { FlushFrameDump(); }, "FrameDumper"); +} + +FrameDumper::~FrameDumper() +{ + ShutdownFrameDumping(); +} + +void FrameDumper::DumpCurrentFrame(const AbstractTexture* src_texture, + const MathUtil::Rectangle& src_rect, + const MathUtil::Rectangle& target_rect, u64 ticks, + int frame_number) +{ + int source_width = src_rect.GetWidth(); + int source_height = src_rect.GetHeight(); + int target_width = target_rect.GetWidth(); + int target_height = target_rect.GetHeight(); + + // We only need to render a copy if we need to stretch/scale the XFB copy. + MathUtil::Rectangle copy_rect = src_rect; + if (source_width != target_width || source_height != target_height) + { + if (!CheckFrameDumpRenderTexture(target_width, target_height)) + return; + + g_gfx->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_gfx->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_gfx->CreateFramebuffer(m_frame_dump_render_texture.get(), nullptr); + ASSERT(m_frame_dump_render_framebuffer); + return true; +} + +bool FrameDumper::CheckFrameDumpReadbackTexture(u32 target_width, u32 target_height) +{ + std::unique_ptr& rbtex = m_frame_dump_readback_texture; + if (rbtex && rbtex->GetWidth() == target_width && rbtex->GetHeight() == target_height) + return true; + + rbtex.reset(); + rbtex = g_gfx->CreateStagingTexture( + StagingTextureType::Readback, + TextureConfig(target_width, target_height, 1, 1, 1, AbstractTextureFormat::RGBA8, 0)); + if (!rbtex) + return false; + + return true; +} + +void FrameDumper::FlushFrameDump() +{ + if (!m_frame_dump_needs_flush) + return; + + // Ensure dumping thread is done with output texture before swapping. + FinishFrameData(); + + std::swap(m_frame_dump_output_texture, m_frame_dump_readback_texture); + + // Queue encoding of the last frame dumped. + auto& output = m_frame_dump_output_texture; + output->Flush(); + if (output->Map()) + { + DumpFrameData(reinterpret_cast(output->GetMappedPointer()), output->GetConfig().width, + output->GetConfig().height, static_cast(output->GetMappedStride())); + } + else + { + ERROR_LOG_FMT(VIDEO, "Failed to map texture for dumping."); + } + + m_frame_dump_needs_flush = false; + + // Shutdown frame dumping if it is no longer active. + if (!IsFrameDumping()) + ShutdownFrameDumping(); +} + +void FrameDumper::ShutdownFrameDumping() +{ + // Ensure the last queued readback has been sent to the encoder. + FlushFrameDump(); + + if (!m_frame_dump_thread_running.IsSet()) + return; + + // Ensure previous frame has been encoded. + FinishFrameData(); + + // Wake thread up, and wait for it to exit. + m_frame_dump_thread_running.Clear(); + m_frame_dump_start.Set(); + if (m_frame_dump_thread.joinable()) + m_frame_dump_thread.join(); + m_frame_dump_render_framebuffer.reset(); + m_frame_dump_render_texture.reset(); + + m_frame_dump_readback_texture.reset(); + m_frame_dump_output_texture.reset(); +} + +void FrameDumper::DumpFrameData(const u8* data, int w, int h, int stride) +{ + m_frame_dump_data = FrameData{data, w, h, stride, m_last_frame_state}; + + if (!m_frame_dump_thread_running.IsSet()) + { + if (m_frame_dump_thread.joinable()) + m_frame_dump_thread.join(); + m_frame_dump_thread_running.Set(); + m_frame_dump_thread = std::thread(&FrameDumper::FrameDumpThreadFunc, this); + } + + // Wake worker thread up. + m_frame_dump_start.Set(); + m_frame_dump_frame_running = true; +} + +void FrameDumper::FinishFrameData() +{ + if (!m_frame_dump_frame_running) + return; + + m_frame_dump_done.Wait(); + m_frame_dump_frame_running = false; + + m_frame_dump_output_texture->Unmap(); +} + +void FrameDumper::FrameDumpThreadFunc() +{ + Common::SetCurrentThreadName("FrameDumping"); + + bool dump_to_ffmpeg = !g_ActiveConfig.bDumpFramesAsImages; + bool frame_dump_started = false; + +// If Dolphin was compiled without ffmpeg, we only support dumping to images. +#if !defined(HAVE_FFMPEG) + if (dump_to_ffmpeg) + { + WARN_LOG_FMT(VIDEO, "FrameDump: Dolphin was not compiled with FFmpeg, using fallback option. " + "Frames will be saved as PNG images instead."); + dump_to_ffmpeg = false; + } +#endif + + while (true) + { + m_frame_dump_start.Wait(); + if (!m_frame_dump_thread_running.IsSet()) + break; + + auto frame = m_frame_dump_data; + + // Save screenshot + if (m_screenshot_request.TestAndClear()) + { + std::lock_guard lk(m_screenshot_lock); + + if (DumpFrameToPNG(frame, m_screenshot_name)) + OSD::AddMessage("Screenshot saved to " + m_screenshot_name); + + // Reset settings + m_screenshot_name.clear(); + m_screenshot_completed.Set(); + } + + if (Config::Get(Config::MAIN_MOVIE_DUMP_FRAMES)) + { + if (!frame_dump_started) + { + if (dump_to_ffmpeg) + frame_dump_started = StartFrameDumpToFFMPEG(frame); + else + frame_dump_started = StartFrameDumpToImage(frame); + + // Stop frame dumping if we fail to start. + if (!frame_dump_started) + Config::SetCurrent(Config::MAIN_MOVIE_DUMP_FRAMES, false); + } + + // If we failed to start frame dumping, don't write a frame. + if (frame_dump_started) + { + if (dump_to_ffmpeg) + DumpFrameToFFMPEG(frame); + else + DumpFrameToImage(frame); + } + } + + m_frame_dump_done.Set(); + } + + if (frame_dump_started) + { + // No additional cleanup is needed when dumping to images. + if (dump_to_ffmpeg) + StopFrameDumpToFFMPEG(); + } +} + +#if defined(HAVE_FFMPEG) + +bool FrameDumper::StartFrameDumpToFFMPEG(const FrameData& frame) +{ + // If dumping started at boot, the start time must be set to the boot time to maintain audio sync. + // TODO: Perhaps we should care about this when starting dumping in the middle of emulation too, + // but it's less important there since the first frame to dump usually gets delivered quickly. + const u64 start_ticks = frame.state.frame_number == 0 ? 0 : frame.state.ticks; + return m_ffmpeg_dump.Start(frame.width, frame.height, start_ticks); +} + +void FrameDumper::DumpFrameToFFMPEG(const FrameData& frame) +{ + m_ffmpeg_dump.AddFrame(frame); +} + +void FrameDumper::StopFrameDumpToFFMPEG() +{ + m_ffmpeg_dump.Stop(); +} + +#else + +bool FrameDumper::StartFrameDumpToFFMPEG(const FrameData&) +{ + return false; +} + +void FrameDumper::DumpFrameToFFMPEG(const FrameData&) +{ +} + +void FrameDumper::StopFrameDumpToFFMPEG() +{ +} + +#endif // defined(HAVE_FFMPEG) + +std::string FrameDumper::GetFrameDumpNextImageFileName() const +{ + return fmt::format("{}framedump_{}.png", File::GetUserPath(D_DUMPFRAMES_IDX), + m_frame_dump_image_counter); +} + +bool FrameDumper::StartFrameDumpToImage(const FrameData&) +{ + m_frame_dump_image_counter = 1; + if (!Config::Get(Config::MAIN_MOVIE_DUMP_FRAMES_SILENT)) + { + // Only check for the presence of the first image to confirm overwriting. + // A previous run will always have at least one image, and it's safe to assume that if the user + // has allowed the first image to be overwritten, this will apply any remaining images as well. + std::string filename = GetFrameDumpNextImageFileName(); + if (File::Exists(filename)) + { + if (!AskYesNoFmtT("Frame dump image(s) '{0}' already exists. Overwrite?", filename)) + return false; + } + } + + return true; +} + +void FrameDumper::DumpFrameToImage(const FrameData& frame) +{ + DumpFrameToPNG(frame, GetFrameDumpNextImageFileName()); + m_frame_dump_image_counter++; +} + +void FrameDumper::SaveScreenshot(std::string filename) +{ + std::lock_guard lk(m_screenshot_lock); + m_screenshot_name = std::move(filename); + m_screenshot_request.Set(); +} + +bool FrameDumper::IsFrameDumping() const +{ + if (m_screenshot_request.IsSet()) + return true; + + if (Config::Get(Config::MAIN_MOVIE_DUMP_FRAMES)) + return true; + + return false; +} + +void FrameDumper::DoState(PointerWrap& p) +{ +#ifdef HAVE_FFMPEG + m_ffmpeg_dump.DoState(p); +#endif +} +std::unique_ptr g_frame_dumper; diff --git a/Source/Core/VideoCommon/FrameDumper.h b/Source/Core/VideoCommon/FrameDumper.h new file mode 100644 index 0000000000..859a4140e3 --- /dev/null +++ b/Source/Core/VideoCommon/FrameDumper.h @@ -0,0 +1,104 @@ +// 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/FrameDumpFFMpeg.h" +#include "VideoCommon/VideoEvents.h" + +class AbstractStagingTexture; +class AbstractTexture; +class AbstractFramebuffer; + +class FrameDumper +{ +public: + FrameDumper(); + ~FrameDumper(); + + // Ensures all rendered frames are queued for encoding. + void FlushFrameDump(); + + // Fills the frame dump staging texture with the current XFB texture. + void DumpCurrentFrame(const AbstractTexture* src_texture, + const MathUtil::Rectangle& src_rect, + const MathUtil::Rectangle& target_rect, u64 ticks, int frame_number); + + void SaveScreenshot(std::string filename); + + bool IsFrameDumping() const; + + void DoState(PointerWrap& p); + +private: + // NOTE: The methods below are called on the framedumping thread. + void FrameDumpThreadFunc(); + bool StartFrameDumpToFFMPEG(const FrameData&); + void DumpFrameToFFMPEG(const FrameData&); + void StopFrameDumpToFFMPEG(); + std::string GetFrameDumpNextImageFileName() const; + bool StartFrameDumpToImage(const FrameData&); + void DumpFrameToImage(const FrameData&); + + void ShutdownFrameDumping(); + + // Checks that the frame dump render texture exists and is the correct size. + bool CheckFrameDumpRenderTexture(u32 target_width, u32 target_height); + + // Checks that the frame dump readback texture exists and is the correct size. + bool CheckFrameDumpReadbackTexture(u32 target_width, u32 target_height); + + // Asynchronously encodes the specified pointer of frame data to the frame dump. + void DumpFrameData(const u8* data, int w, int h, int stride); + + // Ensures all encoded frames have been written to the output file. + void FinishFrameData(); + + std::thread m_frame_dump_thread; + Common::Flag m_frame_dump_thread_running; + + // Used to kick frame dump thread. + Common::Event m_frame_dump_start; + + // Set by frame dump thread on frame completion. + Common::Event m_frame_dump_done; + + // Holds emulation state during the last swap when dumping. + FrameState m_last_frame_state; + + // Communication of frame between video and dump threads. + FrameData m_frame_dump_data; + + // Texture used for screenshot/frame dumping + std::unique_ptr m_frame_dump_render_texture; + std::unique_ptr m_frame_dump_render_framebuffer; + + // Double buffer: + std::unique_ptr m_frame_dump_readback_texture; + std::unique_ptr m_frame_dump_output_texture; + // Set when readback texture holds a frame that needs to be dumped. + bool m_frame_dump_needs_flush = false; + // Set when thread is processing output texture. + bool m_frame_dump_frame_running = false; + + // Used to generate screenshot names. + u32 m_frame_dump_image_counter = 0; + + FFMpegFrameDump m_ffmpeg_dump; + + // Screenshots + Common::Flag m_screenshot_request; + Common::Event m_screenshot_completed; + std::mutex m_screenshot_lock; + std::string m_screenshot_name; + + Common::EventHook m_frame_end_handle; +}; + +extern std::unique_ptr g_frame_dumper; diff --git a/Source/Core/VideoCommon/FramebufferManager.cpp b/Source/Core/VideoCommon/FramebufferManager.cpp index f025b2dfde..4bdc3616a9 100644 --- a/Source/Core/VideoCommon/FramebufferManager.cpp +++ b/Source/Core/VideoCommon/FramebufferManager.cpp @@ -10,14 +10,18 @@ #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" #include "Core/Config/GraphicsSettings.h" +#include "Core/System.h" #include "VideoCommon/AbstractFramebuffer.h" +#include "VideoCommon/AbstractGfx.h" #include "VideoCommon/AbstractPipeline.h" #include "VideoCommon/AbstractShader.h" #include "VideoCommon/AbstractStagingTexture.h" #include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/BPFunctions.h" #include "VideoCommon/DriverDetails.h" #include "VideoCommon/FramebufferShaderGen.h" -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/PixelShaderManager.h" +#include "VideoCommon/Present.h" #include "VideoCommon/VertexManagerBase.h" #include "VideoCommon/VideoCommon.h" #include "VideoCommon/VideoConfig.h" @@ -27,7 +31,9 @@ constexpr size_t MAX_POKE_VERTICES = 32768; std::unique_ptr g_framebuffer_manager; -FramebufferManager::FramebufferManager() = default; +FramebufferManager::FramebufferManager() : m_prev_efb_format(PixelFormat::INVALID_FMT) +{ +} FramebufferManager::~FramebufferManager() { @@ -78,6 +84,8 @@ bool FramebufferManager::Initialize() return false; } + m_end_of_frame_event = AfterFrameEvent::Register([this] { EndOfFrame(); }, "FramebufferManager"); + return true; } @@ -141,18 +149,16 @@ static u32 CalculateEFBLayers() return (g_ActiveConfig.stereo_mode != StereoMode::Off) ? 2 : 1; } -TextureConfig FramebufferManager::GetEFBColorTextureConfig() +TextureConfig FramebufferManager::GetEFBColorTextureConfig(u32 width, u32 height) { - return TextureConfig(g_renderer->GetTargetWidth(), g_renderer->GetTargetHeight(), 1, - CalculateEFBLayers(), g_ActiveConfig.iMultisamples, GetEFBColorFormat(), - AbstractTextureFlag_RenderTarget); + return TextureConfig(width, height, 1, CalculateEFBLayers(), g_ActiveConfig.iMultisamples, + GetEFBColorFormat(), AbstractTextureFlag_RenderTarget); } -TextureConfig FramebufferManager::GetEFBDepthTextureConfig() +TextureConfig FramebufferManager::GetEFBDepthTextureConfig(u32 width, u32 height) { - return TextureConfig(g_renderer->GetTargetWidth(), g_renderer->GetTargetHeight(), 1, - CalculateEFBLayers(), g_ActiveConfig.iMultisamples, GetEFBDepthFormat(), - AbstractTextureFlag_RenderTarget); + return TextureConfig(width, height, 1, CalculateEFBLayers(), g_ActiveConfig.iMultisamples, + GetEFBDepthFormat(), AbstractTextureFlag_RenderTarget); } FramebufferState FramebufferManager::GetEFBFramebufferState() const @@ -165,23 +171,78 @@ FramebufferState FramebufferManager::GetEFBFramebufferState() const return ret; } +MathUtil::Rectangle +FramebufferManager::ConvertEFBRectangle(const MathUtil::Rectangle& rc) const +{ + MathUtil::Rectangle result; + result.left = EFBToScaledX(rc.left); + result.top = EFBToScaledY(rc.top); + result.right = EFBToScaledX(rc.right); + result.bottom = EFBToScaledY(rc.bottom); + return result; +} + +unsigned int FramebufferManager::GetEFBScale() const +{ + return m_efb_scale; +} + +int FramebufferManager::EFBToScaledX(int x) const +{ + return x * static_cast(m_efb_scale); +} + +int FramebufferManager::EFBToScaledY(int y) const +{ + return y * static_cast(m_efb_scale); +} + +float FramebufferManager::EFBToScaledXf(float x) const +{ + return x * ((float)GetEFBWidth() / (float)EFB_WIDTH); +} + +float FramebufferManager::EFBToScaledYf(float y) const +{ + return y * ((float)GetEFBHeight() / (float)EFB_HEIGHT); +} + +std::tuple FramebufferManager::CalculateTargetSize() +{ + if (g_ActiveConfig.iEFBScale == EFB_SCALE_AUTO_INTEGRAL) + m_efb_scale = g_presenter->AutoIntegralScale(); + else + m_efb_scale = g_ActiveConfig.iEFBScale; + + const u32 max_size = g_ActiveConfig.backend_info.MaxTextureSize; + if (max_size < EFB_WIDTH * m_efb_scale) + m_efb_scale = max_size / EFB_WIDTH; + + u32 new_efb_width = std::max(EFB_WIDTH * static_cast(m_efb_scale), 1u); + u32 new_efb_height = std::max(EFB_HEIGHT * static_cast(m_efb_scale), 1u); + + return std::make_tuple(new_efb_width, new_efb_height); +} + bool FramebufferManager::CreateEFBFramebuffer() { - const TextureConfig efb_color_texture_config = GetEFBColorTextureConfig(); - const TextureConfig efb_depth_texture_config = GetEFBDepthTextureConfig(); + auto [width, height] = CalculateTargetSize(); + + const TextureConfig efb_color_texture_config = GetEFBColorTextureConfig(width, height); + const TextureConfig efb_depth_texture_config = GetEFBDepthTextureConfig(width, height); // We need a second texture to swap with for changing pixel formats - m_efb_color_texture = g_renderer->CreateTexture(efb_color_texture_config, "EFB color texture"); - m_efb_depth_texture = g_renderer->CreateTexture(efb_depth_texture_config, "EFB depth texture"); + m_efb_color_texture = g_gfx->CreateTexture(efb_color_texture_config, "EFB color texture"); + m_efb_depth_texture = g_gfx->CreateTexture(efb_depth_texture_config, "EFB depth texture"); m_efb_convert_color_texture = - g_renderer->CreateTexture(efb_color_texture_config, "EFB convert color texture"); + g_gfx->CreateTexture(efb_color_texture_config, "EFB convert color texture"); if (!m_efb_color_texture || !m_efb_depth_texture || !m_efb_convert_color_texture) return false; m_efb_framebuffer = - g_renderer->CreateFramebuffer(m_efb_color_texture.get(), m_efb_depth_texture.get()); + g_gfx->CreateFramebuffer(m_efb_color_texture.get(), m_efb_depth_texture.get()); m_efb_convert_framebuffer = - g_renderer->CreateFramebuffer(m_efb_convert_color_texture.get(), m_efb_depth_texture.get()); + g_gfx->CreateFramebuffer(m_efb_convert_color_texture.get(), m_efb_depth_texture.get()); if (!m_efb_framebuffer || !m_efb_convert_framebuffer) return false; @@ -191,7 +252,7 @@ bool FramebufferManager::CreateEFBFramebuffer() u32 flags = 0; if (!g_ActiveConfig.backend_info.bSupportsPartialMultisampleResolve) flags |= AbstractTextureFlag_RenderTarget; - m_efb_resolve_color_texture = g_renderer->CreateTexture( + m_efb_resolve_color_texture = g_gfx->CreateTexture( TextureConfig(efb_color_texture_config.width, efb_color_texture_config.height, 1, efb_color_texture_config.layers, 1, efb_color_texture_config.format, flags), "EFB color resolve texture"); @@ -201,7 +262,7 @@ bool FramebufferManager::CreateEFBFramebuffer() if (!g_ActiveConfig.backend_info.bSupportsPartialMultisampleResolve) { m_efb_color_resolve_framebuffer = - g_renderer->CreateFramebuffer(m_efb_resolve_color_texture.get(), nullptr); + g_gfx->CreateFramebuffer(m_efb_resolve_color_texture.get(), nullptr); if (!m_efb_color_resolve_framebuffer) return false; } @@ -210,7 +271,7 @@ bool FramebufferManager::CreateEFBFramebuffer() // We also need one to convert the D24S8 to R32F if that is being used (Adreno). if (g_ActiveConfig.MultisamplingEnabled() || GetEFBDepthFormat() != AbstractTextureFormat::R32F) { - m_efb_depth_resolve_texture = g_renderer->CreateTexture( + m_efb_depth_resolve_texture = g_gfx->CreateTexture( TextureConfig(efb_depth_texture_config.width, efb_depth_texture_config.height, 1, efb_depth_texture_config.layers, 1, GetEFBDepthCopyFormat(), AbstractTextureFlag_RenderTarget), @@ -219,15 +280,15 @@ bool FramebufferManager::CreateEFBFramebuffer() return false; m_efb_depth_resolve_framebuffer = - g_renderer->CreateFramebuffer(m_efb_depth_resolve_texture.get(), nullptr); + g_gfx->CreateFramebuffer(m_efb_depth_resolve_texture.get(), nullptr); if (!m_efb_depth_resolve_framebuffer) return false; } // Clear the renderable textures out. - g_renderer->SetAndClearFramebuffer( - m_efb_framebuffer.get(), {{0.0f, 0.0f, 0.0f, 0.0f}}, - g_ActiveConfig.backend_info.bSupportsReversedDepthRange ? 1.0f : 0.0f); + g_gfx->SetAndClearFramebuffer(m_efb_framebuffer.get(), {{0.0f, 0.0f, 0.0f, 0.0f}}, + g_ActiveConfig.backend_info.bSupportsReversedDepthRange ? 1.0f : + 0.0f); return true; } @@ -245,7 +306,7 @@ void FramebufferManager::DestroyEFBFramebuffer() void FramebufferManager::BindEFBFramebuffer() { - g_renderer->SetFramebuffer(m_efb_framebuffer.get()); + g_gfx->SetFramebuffer(m_efb_framebuffer.get()); } AbstractTexture* FramebufferManager::ResolveEFBColorTexture(const MathUtil::Rectangle& region) @@ -270,15 +331,15 @@ AbstractTexture* FramebufferManager::ResolveEFBColorTexture(const MathUtil::Rect else { m_efb_color_texture->FinishedRendering(); - g_renderer->BeginUtilityDrawing(); - g_renderer->SetAndDiscardFramebuffer(m_efb_color_resolve_framebuffer.get()); - g_renderer->SetPipeline(m_efb_color_resolve_pipeline.get()); - g_renderer->SetTexture(0, m_efb_color_texture.get()); - g_renderer->SetSamplerState(0, RenderState::GetPointSamplerState()); - g_renderer->SetViewportAndScissor(clamped_region); - g_renderer->Draw(0, 3); + g_gfx->BeginUtilityDrawing(); + g_gfx->SetAndDiscardFramebuffer(m_efb_color_resolve_framebuffer.get()); + g_gfx->SetPipeline(m_efb_color_resolve_pipeline.get()); + g_gfx->SetTexture(0, m_efb_color_texture.get()); + g_gfx->SetSamplerState(0, RenderState::GetPointSamplerState()); + g_gfx->SetViewportAndScissor(clamped_region); + g_gfx->Draw(0, 3); m_efb_resolve_color_texture->FinishedRendering(); - g_renderer->EndUtilityDrawing(); + g_gfx->EndUtilityDrawing(); } m_efb_resolve_color_texture->FinishedRendering(); return m_efb_resolve_color_texture.get(); @@ -298,16 +359,16 @@ AbstractTexture* FramebufferManager::ResolveEFBDepthTexture(const MathUtil::Rect clamped_region.ClampUL(0, 0, GetEFBWidth(), GetEFBHeight()); m_efb_depth_texture->FinishedRendering(); - g_renderer->BeginUtilityDrawing(); - g_renderer->SetAndDiscardFramebuffer(m_efb_depth_resolve_framebuffer.get()); - g_renderer->SetPipeline(IsEFBMultisampled() ? m_efb_depth_resolve_pipeline.get() : - m_efb_depth_cache.copy_pipeline.get()); - g_renderer->SetTexture(0, m_efb_depth_texture.get()); - g_renderer->SetSamplerState(0, RenderState::GetPointSamplerState()); - g_renderer->SetViewportAndScissor(clamped_region); - g_renderer->Draw(0, 3); + g_gfx->BeginUtilityDrawing(); + g_gfx->SetAndDiscardFramebuffer(m_efb_depth_resolve_framebuffer.get()); + g_gfx->SetPipeline(IsEFBMultisampled() ? m_efb_depth_resolve_pipeline.get() : + m_efb_depth_cache.copy_pipeline.get()); + g_gfx->SetTexture(0, m_efb_depth_texture.get()); + g_gfx->SetSamplerState(0, RenderState::GetPointSamplerState()); + g_gfx->SetViewportAndScissor(clamped_region); + g_gfx->Draw(0, 3); m_efb_depth_resolve_texture->FinishedRendering(); - g_renderer->EndUtilityDrawing(); + g_gfx->EndUtilityDrawing(); return m_efb_depth_resolve_texture.get(); } @@ -322,17 +383,17 @@ bool FramebufferManager::ReinterpretPixelData(EFBReinterpretType convtype) // buffer, which we want to preserve. If we find this to be hindering performance in the // future (e.g. on mobile/tilers), it may be worth discarding only the color buffer. m_efb_color_texture->FinishedRendering(); - g_renderer->BeginUtilityDrawing(); - g_renderer->SetFramebuffer(m_efb_convert_framebuffer.get()); - g_renderer->SetViewportAndScissor(m_efb_framebuffer->GetRect()); - g_renderer->SetPipeline(m_format_conversion_pipelines[static_cast(convtype)].get()); - g_renderer->SetTexture(0, m_efb_color_texture.get()); - g_renderer->Draw(0, 3); + g_gfx->BeginUtilityDrawing(); + g_gfx->SetFramebuffer(m_efb_convert_framebuffer.get()); + g_gfx->SetViewportAndScissor(m_efb_framebuffer->GetRect()); + g_gfx->SetPipeline(m_format_conversion_pipelines[static_cast(convtype)].get()); + g_gfx->SetTexture(0, m_efb_color_texture.get()); + g_gfx->Draw(0, 3); // And swap the framebuffers around, so we do new drawing to the converted framebuffer. std::swap(m_efb_color_texture, m_efb_convert_color_texture); std::swap(m_efb_framebuffer, m_efb_convert_framebuffer); - g_renderer->EndUtilityDrawing(); + g_gfx->EndUtilityDrawing(); InvalidatePeekCache(true); return true; } @@ -342,7 +403,7 @@ bool FramebufferManager::CompileConversionPipelines() for (u32 i = 0; i < NUM_EFB_REINTERPRET_TYPES; i++) { EFBReinterpretType convtype = static_cast(i); - std::unique_ptr pixel_shader = g_renderer->CreateShaderFromSource( + std::unique_ptr pixel_shader = g_gfx->CreateShaderFromSource( ShaderStage::Pixel, FramebufferShaderGen::GenerateFormatConversionShader(convtype, GetEFBSamples()), fmt::format("Framebuffer conversion pixel shader {}", convtype)); @@ -358,7 +419,7 @@ bool FramebufferManager::CompileConversionPipelines() config.blending_state = RenderState::GetNoBlendingBlendState(); config.framebuffer_state = GetEFBFramebufferState(); config.usage = AbstractPipelineUsage::Utility; - m_format_conversion_pipelines[i] = g_renderer->CreatePipeline(config); + m_format_conversion_pipelines[i] = g_gfx->CreatePipeline(config); if (!m_format_conversion_pipelines[i]) return false; } @@ -493,7 +554,7 @@ void FramebufferManager::RefreshPeekCache() if (flush_command_buffer) { - g_renderer->Flush(); + g_gfx->Flush(); } } @@ -562,33 +623,33 @@ bool FramebufferManager::CompileReadbackPipelines() config.blending_state = RenderState::GetNoBlendingBlendState(); config.framebuffer_state = RenderState::GetColorFramebufferState(GetEFBColorFormat()); config.usage = AbstractPipelineUsage::Utility; - m_efb_color_cache.copy_pipeline = g_renderer->CreatePipeline(config); + m_efb_color_cache.copy_pipeline = g_gfx->CreatePipeline(config); if (!m_efb_color_cache.copy_pipeline) return false; // same for depth, except different format config.framebuffer_state.color_texture_format = GetEFBDepthCopyFormat(); - m_efb_depth_cache.copy_pipeline = g_renderer->CreatePipeline(config); + m_efb_depth_cache.copy_pipeline = g_gfx->CreatePipeline(config); if (!m_efb_depth_cache.copy_pipeline) return false; if (IsEFBMultisampled()) { - auto depth_resolve_shader = g_renderer->CreateShaderFromSource( + auto depth_resolve_shader = g_gfx->CreateShaderFromSource( ShaderStage::Pixel, FramebufferShaderGen::GenerateResolveDepthPixelShader(GetEFBSamples()), "Depth resolve pixel shader"); if (!depth_resolve_shader) return false; config.pixel_shader = depth_resolve_shader.get(); - m_efb_depth_resolve_pipeline = g_renderer->CreatePipeline(config); + m_efb_depth_resolve_pipeline = g_gfx->CreatePipeline(config); if (!m_efb_depth_resolve_pipeline) return false; if (!g_ActiveConfig.backend_info.bSupportsPartialMultisampleResolve) { config.framebuffer_state.color_texture_format = GetEFBColorFormat(); - auto color_resolve_shader = g_renderer->CreateShaderFromSource( + auto color_resolve_shader = g_gfx->CreateShaderFromSource( ShaderStage::Pixel, FramebufferShaderGen::GenerateResolveColorPixelShader(GetEFBSamples()), "Color resolve pixel shader"); @@ -596,14 +657,14 @@ bool FramebufferManager::CompileReadbackPipelines() return false; config.pixel_shader = color_resolve_shader.get(); - m_efb_color_resolve_pipeline = g_renderer->CreatePipeline(config); + m_efb_color_resolve_pipeline = g_gfx->CreatePipeline(config); if (!m_efb_color_resolve_pipeline) return false; } } // EFB restore pipeline - auto restore_shader = g_renderer->CreateShaderFromSource( + auto restore_shader = g_gfx->CreateShaderFromSource( ShaderStage::Pixel, FramebufferShaderGen::GenerateEFBRestorePixelShader(), "EFB restore pixel shader"); if (!restore_shader) @@ -614,7 +675,7 @@ bool FramebufferManager::CompileReadbackPipelines() config.framebuffer_state.per_sample_shading = false; config.vertex_shader = g_shader_cache->GetScreenQuadVertexShader(); config.pixel_shader = restore_shader.get(); - m_efb_restore_pipeline = g_renderer->CreatePipeline(config); + m_efb_restore_pipeline = g_gfx->CreatePipeline(config); if (!m_efb_restore_pipeline) return false; @@ -630,17 +691,17 @@ void FramebufferManager::DestroyReadbackPipelines() bool FramebufferManager::CreateReadbackFramebuffer() { - if (g_renderer->GetEFBScale() != 1) + if (GetEFBScale() != 1) { const TextureConfig color_config(IsUsingTiledEFBCache() ? m_efb_cache_tile_size : EFB_WIDTH, IsUsingTiledEFBCache() ? m_efb_cache_tile_size : EFB_HEIGHT, 1, 1, 1, GetEFBColorFormat(), AbstractTextureFlag_RenderTarget); - m_efb_color_cache.texture = g_renderer->CreateTexture(color_config, "EFB color cache"); + m_efb_color_cache.texture = g_gfx->CreateTexture(color_config, "EFB color cache"); if (!m_efb_color_cache.texture) return false; m_efb_color_cache.framebuffer = - g_renderer->CreateFramebuffer(m_efb_color_cache.texture.get(), nullptr); + g_gfx->CreateFramebuffer(m_efb_color_cache.texture.get(), nullptr); if (!m_efb_color_cache.framebuffer) return false; } @@ -651,27 +712,27 @@ bool FramebufferManager::CreateReadbackFramebuffer() (IsUsingTiledEFBCache() && !g_ActiveConfig.backend_info.bSupportsPartialDepthCopies) || !AbstractTexture::IsCompatibleDepthAndColorFormats(m_efb_depth_texture->GetFormat(), GetEFBDepthCopyFormat()) || - g_renderer->GetEFBScale() != 1) + GetEFBScale() != 1) { const TextureConfig depth_config(IsUsingTiledEFBCache() ? m_efb_cache_tile_size : EFB_WIDTH, IsUsingTiledEFBCache() ? m_efb_cache_tile_size : EFB_HEIGHT, 1, 1, 1, GetEFBDepthCopyFormat(), AbstractTextureFlag_RenderTarget); - m_efb_depth_cache.texture = g_renderer->CreateTexture(depth_config, "EFB depth cache"); + m_efb_depth_cache.texture = g_gfx->CreateTexture(depth_config, "EFB depth cache"); if (!m_efb_depth_cache.texture) return false; m_efb_depth_cache.framebuffer = - g_renderer->CreateFramebuffer(m_efb_depth_cache.texture.get(), nullptr); + g_gfx->CreateFramebuffer(m_efb_depth_cache.texture.get(), nullptr); if (!m_efb_depth_cache.framebuffer) return false; } // Staging texture use the full EFB dimensions, as this is the buffer for the whole cache. - m_efb_color_cache.readback_texture = g_renderer->CreateStagingTexture( + m_efb_color_cache.readback_texture = g_gfx->CreateStagingTexture( StagingTextureType::Mutable, TextureConfig(EFB_WIDTH, EFB_HEIGHT, 1, 1, 1, GetEFBColorFormat(), 0)); - m_efb_depth_cache.readback_texture = g_renderer->CreateStagingTexture( + m_efb_depth_cache.readback_texture = g_gfx->CreateStagingTexture( StagingTextureType::Mutable, TextureConfig(EFB_WIDTH, EFB_HEIGHT, 1, 1, 1, GetEFBDepthCopyFormat(), 0)); if (!m_efb_color_cache.readback_texture || !m_efb_depth_cache.readback_texture) @@ -728,16 +789,16 @@ void FramebufferManager::PopulateEFBCache(bool depth, u32 tile_index, bool async // Issue a copy from framebuffer -> copy texture if we have >1xIR or MSAA on. EFBCacheData& data = depth ? m_efb_depth_cache : m_efb_color_cache; const MathUtil::Rectangle rect = GetEFBCacheTileRect(tile_index); - const MathUtil::Rectangle native_rect = g_renderer->ConvertEFBRectangle(rect); + const MathUtil::Rectangle native_rect = ConvertEFBRectangle(rect); AbstractTexture* src_texture = depth ? ResolveEFBDepthTexture(native_rect) : ResolveEFBColorTexture(native_rect); - if (g_renderer->GetEFBScale() != 1 || force_intermediate_copy) + if (GetEFBScale() != 1 || force_intermediate_copy) { // Downsample from internal resolution to 1x. // TODO: This won't produce correct results at IRs above 2x. More samples are required. // This is the same issue as with EFB copies. src_texture->FinishedRendering(); - g_renderer->BeginUtilityDrawing(); + g_gfx->BeginUtilityDrawing(); const float rcp_src_width = 1.0f / m_efb_framebuffer->GetWidth(); const float rcp_src_height = 1.0f / m_efb_framebuffer->GetHeight(); @@ -748,14 +809,13 @@ void FramebufferManager::PopulateEFBCache(bool depth, u32 tile_index, bool async // Viewport will not be TILE_SIZExTILE_SIZE for the last row of tiles, assuming a tile size of // 64, because 528 is not evenly divisible by 64. - g_renderer->SetAndDiscardFramebuffer(data.framebuffer.get()); - g_renderer->SetViewportAndScissor( - MathUtil::Rectangle(0, 0, rect.GetWidth(), rect.GetHeight())); - g_renderer->SetPipeline(data.copy_pipeline.get()); - g_renderer->SetTexture(0, src_texture); - g_renderer->SetSamplerState(0, depth ? RenderState::GetPointSamplerState() : - RenderState::GetLinearSamplerState()); - g_renderer->Draw(0, 3); + g_gfx->SetAndDiscardFramebuffer(data.framebuffer.get()); + g_gfx->SetViewportAndScissor(MathUtil::Rectangle(0, 0, rect.GetWidth(), rect.GetHeight())); + g_gfx->SetPipeline(data.copy_pipeline.get()); + g_gfx->SetTexture(0, src_texture); + g_gfx->SetSamplerState(0, depth ? RenderState::GetPointSamplerState() : + RenderState::GetLinearSamplerState()); + g_gfx->Draw(0, 3); // Copy from EFB or copy texture to staging texture. // No need to call FinishedRendering() here because CopyFromTexture() transitions. @@ -763,7 +823,7 @@ void FramebufferManager::PopulateEFBCache(bool depth, u32 tile_index, bool async data.texture.get(), MathUtil::Rectangle(0, 0, rect.GetWidth(), rect.GetHeight()), 0, 0, rect); - g_renderer->EndUtilityDrawing(); + g_gfx->EndUtilityDrawing(); } else { @@ -785,41 +845,39 @@ void FramebufferManager::PopulateEFBCache(bool depth, u32 tile_index, bool async data.tiles[tile_index].present = true; } -void FramebufferManager::ClearEFB(const MathUtil::Rectangle& rc, bool clear_color, - bool clear_alpha, bool clear_z, u32 color, u32 z) +void FramebufferManager::ClearEFB(const MathUtil::Rectangle& rc, bool color_enable, + bool alpha_enable, bool z_enable, u32 color, u32 z) { FlushEFBPokes(); FlagPeekCacheAsOutOfDate(); - g_renderer->BeginUtilityDrawing(); - // Set up uniforms. - struct Uniforms + // Native -> EFB coordinates + MathUtil::Rectangle target_rc = ConvertEFBRectangle(rc); + target_rc = g_gfx->ConvertFramebufferRectangle(target_rc, m_efb_framebuffer.get()); + target_rc.ClampUL(0, 0, m_efb_framebuffer->GetWidth(), m_efb_framebuffer->GetWidth()); + + // Determine whether the EFB has an alpha channel. If it doesn't, we can clear the alpha + // channel to 0xFF. + // On backends that don't allow masking Alpha clears, this allows us to use the fast path + // almost all the time + if (bpmem.zcontrol.pixel_format == PixelFormat::RGB565_Z16 || + bpmem.zcontrol.pixel_format == PixelFormat::RGB8_Z24 || + bpmem.zcontrol.pixel_format == PixelFormat::Z24) { - float clear_color[4]; - float clear_depth; - float padding1, padding2, padding3; - }; - static_assert(std::is_standard_layout::value); - Uniforms uniforms = {{static_cast((color >> 16) & 0xFF) / 255.0f, - static_cast((color >> 8) & 0xFF) / 255.0f, - static_cast((color >> 0) & 0xFF) / 255.0f, - static_cast((color >> 24) & 0xFF) / 255.0f}, - static_cast(z & 0xFFFFFF) / 16777216.0f}; - if (!g_ActiveConfig.backend_info.bSupportsReversedDepthRange) - uniforms.clear_depth = 1.0f - uniforms.clear_depth; - g_vertex_manager->UploadUtilityUniforms(&uniforms, sizeof(uniforms)); + // Force alpha writes, and clear the alpha channel. + alpha_enable = true; + color &= 0x00FFFFFF; + } - const auto target_rc = g_renderer->ConvertFramebufferRectangle( - g_renderer->ConvertEFBRectangle(rc), m_efb_framebuffer.get()); - g_renderer->SetPipeline(m_efb_clear_pipelines[clear_color][clear_alpha][clear_z].get()); - g_renderer->SetViewportAndScissor(target_rc); - g_renderer->Draw(0, 3); - g_renderer->EndUtilityDrawing(); + g_gfx->ClearRegion(target_rc, color_enable, alpha_enable, z_enable, color, z); + + // Scissor rect must be restored. + BPFunctions::SetScissorAndViewport(); } bool FramebufferManager::CompileClearPipelines() { - auto vertex_shader = g_renderer->CreateShaderFromSource( + auto vertex_shader = g_gfx->CreateShaderFromSource( ShaderStage::Vertex, FramebufferShaderGen::GenerateClearVertexShader(), "Clear vertex shader"); if (!vertex_shader) @@ -847,9 +905,8 @@ bool FramebufferManager::CompileClearPipelines() config.depth_state.testenable = depth_enable != 0; config.depth_state.updateenable = depth_enable != 0; - m_efb_clear_pipelines[color_enable][alpha_enable][depth_enable] = - g_renderer->CreatePipeline(config); - if (!m_efb_clear_pipelines[color_enable][alpha_enable][depth_enable]) + m_clear_pipelines[color_enable][alpha_enable][depth_enable] = g_gfx->CreatePipeline(config); + if (!m_clear_pipelines[color_enable][alpha_enable][depth_enable]) return false; } } @@ -866,12 +923,18 @@ void FramebufferManager::DestroyClearPipelines() { for (u32 depth_enable = 0; depth_enable < 2; depth_enable++) { - m_efb_clear_pipelines[color_enable][alpha_enable][depth_enable].reset(); + m_clear_pipelines[color_enable][alpha_enable][depth_enable].reset(); } } } } +AbstractPipeline* FramebufferManager::GetClearPipeline(bool colorEnable, bool alphaEnable, + bool zEnable) const +{ + return m_clear_pipelines[colorEnable][alphaEnable][zEnable].get(); +} + void FramebufferManager::PokeEFBColor(u32 x, u32 y, u32 color) { // Flush if we exceeded the number of vertices per batch. @@ -918,7 +981,7 @@ void FramebufferManager::CreatePokeVertices(std::vector* destinat // GPU will expand the point to a quad. const float cs_x = (static_cast(x) + 0.5f) * cs_pixel_width - 1.0f; const float cs_y = 1.0f - (static_cast(y) + 0.5f) * cs_pixel_height; - const float point_size = static_cast(g_renderer->GetEFBScale()); + const float point_size = static_cast(GetEFBScale()); destination_list->push_back({{cs_x, cs_y, z, point_size}, color}); return; } @@ -957,17 +1020,17 @@ void FramebufferManager::DrawPokeVertices(const EFBPokeVertex* vertices, u32 ver const AbstractPipeline* pipeline) { // Copy to vertex buffer. - g_renderer->BeginUtilityDrawing(); + g_gfx->BeginUtilityDrawing(); u32 base_vertex, base_index; g_vertex_manager->UploadUtilityVertices(vertices, sizeof(EFBPokeVertex), static_cast(vertex_count), nullptr, 0, &base_vertex, &base_index); // Now we can draw. - g_renderer->SetViewportAndScissor(m_efb_framebuffer->GetRect()); - g_renderer->SetPipeline(pipeline); - g_renderer->Draw(base_vertex, vertex_count); - g_renderer->EndUtilityDrawing(); + g_gfx->SetViewportAndScissor(m_efb_framebuffer->GetRect()); + g_gfx->SetPipeline(pipeline); + g_gfx->Draw(base_vertex, vertex_count); + g_gfx->EndUtilityDrawing(); } bool FramebufferManager::CompilePokePipelines() @@ -985,11 +1048,11 @@ bool FramebufferManager::CompilePokePipelines() vtx_decl.colors[0].offset = offsetof(EFBPokeVertex, color); vtx_decl.stride = sizeof(EFBPokeVertex); - m_poke_vertex_format = g_renderer->CreateNativeVertexFormat(vtx_decl); + m_poke_vertex_format = g_gfx->CreateNativeVertexFormat(vtx_decl); if (!m_poke_vertex_format) return false; - auto poke_vertex_shader = g_renderer->CreateShaderFromSource( + auto poke_vertex_shader = g_gfx->CreateShaderFromSource( ShaderStage::Vertex, FramebufferShaderGen::GenerateEFBPokeVertexShader(), "EFB poke vertex shader"); if (!poke_vertex_shader) @@ -1007,14 +1070,14 @@ bool FramebufferManager::CompilePokePipelines() config.blending_state = RenderState::GetNoBlendingBlendState(); config.framebuffer_state = GetEFBFramebufferState(); config.usage = AbstractPipelineUsage::Utility; - m_color_poke_pipeline = g_renderer->CreatePipeline(config); + m_color_poke_pipeline = g_gfx->CreatePipeline(config); if (!m_color_poke_pipeline) return false; // Turn off color writes, depth writes on for depth pokes. config.depth_state = RenderState::GetAlwaysWriteDepthState(); config.blending_state = RenderState::GetNoColorWriteBlendState(); - m_depth_poke_pipeline = g_renderer->CreatePipeline(config); + m_depth_poke_pipeline = g_gfx->CreatePipeline(config); if (!m_depth_poke_pipeline) return false; @@ -1031,6 +1094,7 @@ void FramebufferManager::DestroyPokePipelines() void FramebufferManager::DoState(PointerWrap& p) { FlushEFBPokes(); + p.Do(m_prev_efb_format); bool save_efb_state = Config::Get(Config::GFX_SAVE_TEXTURE_CACHE_TO_STATE); p.Do(save_efb_state); @@ -1077,9 +1141,9 @@ void FramebufferManager::DoLoadState(PointerWrap& p) color_tex->texture->GetLayers() != m_efb_color_texture->GetLayers()) { WARN_LOG_FMT(VIDEO, "Failed to deserialize EFB contents. Clearing instead."); - g_renderer->SetAndClearFramebuffer( - m_efb_framebuffer.get(), {{0.0f, 0.0f, 0.0f, 0.0f}}, - g_ActiveConfig.backend_info.bSupportsReversedDepthRange ? 1.0f : 0.0f); + g_gfx->SetAndClearFramebuffer(m_efb_framebuffer.get(), {{0.0f, 0.0f, 0.0f, 0.0f}}, + g_ActiveConfig.backend_info.bSupportsReversedDepthRange ? 1.0f : + 0.0f); return; } @@ -1089,15 +1153,15 @@ void FramebufferManager::DoLoadState(PointerWrap& p) color_tex->texture->GetHeight() != m_efb_color_texture->GetHeight(); // Draw the deserialized textures over the EFB. - g_renderer->BeginUtilityDrawing(); - g_renderer->SetAndDiscardFramebuffer(m_efb_framebuffer.get()); - g_renderer->SetViewportAndScissor(m_efb_framebuffer->GetRect()); - g_renderer->SetPipeline(m_efb_restore_pipeline.get()); - g_renderer->SetTexture(0, color_tex->texture.get()); - g_renderer->SetTexture(1, depth_tex->texture.get()); - g_renderer->SetSamplerState(0, rescale ? RenderState::GetLinearSamplerState() : - RenderState::GetPointSamplerState()); - g_renderer->SetSamplerState(1, RenderState::GetPointSamplerState()); - g_renderer->Draw(0, 3); - g_renderer->EndUtilityDrawing(); + g_gfx->BeginUtilityDrawing(); + g_gfx->SetAndDiscardFramebuffer(m_efb_framebuffer.get()); + g_gfx->SetViewportAndScissor(m_efb_framebuffer->GetRect()); + g_gfx->SetPipeline(m_efb_restore_pipeline.get()); + g_gfx->SetTexture(0, color_tex->texture.get()); + g_gfx->SetTexture(1, depth_tex->texture.get()); + g_gfx->SetSamplerState(0, rescale ? RenderState::GetLinearSamplerState() : + RenderState::GetPointSamplerState()); + g_gfx->SetSamplerState(1, RenderState::GetPointSamplerState()); + g_gfx->Draw(0, 3); + g_gfx->EndUtilityDrawing(); } diff --git a/Source/Core/VideoCommon/FramebufferManager.h b/Source/Core/VideoCommon/FramebufferManager.h index dd073dda12..7921a70d41 100644 --- a/Source/Core/VideoCommon/FramebufferManager.h +++ b/Source/Core/VideoCommon/FramebufferManager.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "Common/CommonTypes.h" #include "Common/EnumFormatter.h" @@ -16,6 +17,7 @@ #include "VideoCommon/AbstractTexture.h" #include "VideoCommon/RenderState.h" #include "VideoCommon/TextureConfig.h" +#include "VideoCommon/VideoEvents.h" class NativeVertexFormat; class PointerWrap; @@ -53,8 +55,8 @@ public: static AbstractTextureFormat GetEFBColorFormat(); static AbstractTextureFormat GetEFBDepthFormat(); static AbstractTextureFormat GetEFBDepthCopyFormat(); - static TextureConfig GetEFBColorTextureConfig(); - static TextureConfig GetEFBDepthTextureConfig(); + static TextureConfig GetEFBColorTextureConfig(u32 width, u32 height); + static TextureConfig GetEFBDepthTextureConfig(u32 width, u32 height); // Accessors. AbstractTexture* GetEFBColorTexture() const { return m_efb_color_texture.get(); } @@ -68,6 +70,20 @@ public: bool IsEFBStereo() const { return m_efb_color_texture->GetLayers() > 1; } FramebufferState GetEFBFramebufferState() const; + // EFB coordinate conversion functions + // Use this to convert a whole native EFB rect to backbuffer coordinates + MathUtil::Rectangle ConvertEFBRectangle(const MathUtil::Rectangle& rc) const; + + unsigned int GetEFBScale() const; + + // Use this to upscale native EFB coordinates to IDEAL internal resolution + int EFBToScaledX(int x) const; + int EFBToScaledY(int y) const; + + // Floating point versions of the above - only use them if really necessary + float EFBToScaledXf(float x) const; + float EFBToScaledYf(float y) const; + // First-time setup. bool Initialize(); @@ -89,11 +105,15 @@ public: // Assumes no render pass is currently in progress. // Swaps EFB framebuffers, so re-bind afterwards. bool ReinterpretPixelData(EFBReinterpretType convtype); + PixelFormat GetPrevPixelFormat() const { return m_prev_efb_format; } + void StorePixelFormat(PixelFormat new_format) { m_prev_efb_format = new_format; } // Clears the EFB using shaders. void ClearEFB(const MathUtil::Rectangle& rc, bool clear_color, bool clear_alpha, bool clear_z, u32 color, u32 z); + AbstractPipeline* GetClearPipeline(bool clear_color, bool clear_alpha, bool clear_z) const; + // Reads a framebuffer value back from the GPU. This may block if the cache is not current. u32 PeekEFBColor(u32 x, u32 y); float PeekEFBDepth(u32 x, u32 y); @@ -169,9 +189,14 @@ protected: void DrawPokeVertices(const EFBPokeVertex* vertices, u32 vertex_count, const AbstractPipeline* pipeline); + std::tuple CalculateTargetSize(); + void DoLoadState(PointerWrap& p); void DoSaveState(PointerWrap& p); + float m_efb_scale = 0.0f; + PixelFormat m_prev_efb_format; + std::unique_ptr m_efb_color_texture; std::unique_ptr m_efb_convert_color_texture; std::unique_ptr m_efb_depth_texture; @@ -204,8 +229,7 @@ protected: // EFB clear pipelines // Indexed by [color_write_enabled][alpha_write_enabled][depth_write_enabled] - std::array, 2>, 2>, 2> - m_efb_clear_pipelines; + std::array, 2>, 2>, 2> m_clear_pipelines; // EFB poke drawing setup std::unique_ptr m_poke_vertex_format; @@ -213,6 +237,8 @@ protected: std::unique_ptr m_depth_poke_pipeline; std::vector m_color_poke_vertices; std::vector m_depth_poke_vertices; + + Common::EventHook m_end_of_frame_event; }; extern std::unique_ptr g_framebuffer_manager; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp index 7441f210fa..368e7adba4 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp @@ -10,10 +10,15 @@ #include "Common/Logging/Log.h" #include "Common/VariantUtil.h" +#include "Core/ConfigManager.h" + #include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h" #include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h" #include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h" #include "VideoCommon/TextureInfo.h" +#include "VideoCommon/VideoConfig.h" + +std::unique_ptr g_graphics_mod_manager; class GraphicsModManager::DecoratedAction final : public GraphicsModAction { @@ -64,6 +69,29 @@ private: GraphicsModConfig m_mod; }; +bool GraphicsModManager::Initialize() +{ + if (g_ActiveConfig.bGraphicMods) + { + // If a config change occurred in a previous session, + // remember the old change count value. By setting + // our current change count to the old value, we + // avoid loading the stale data when we + // check for config changes. + const u32 old_game_mod_changes = g_ActiveConfig.graphics_mod_config ? + g_ActiveConfig.graphics_mod_config->GetChangeCount() : + 0; + g_ActiveConfig.graphics_mod_config = GraphicsModGroupConfig(SConfig::GetInstance().GetGameID()); + g_ActiveConfig.graphics_mod_config->Load(); + g_ActiveConfig.graphics_mod_config->SetChangeCount(old_game_mod_changes); + g_graphics_mod_manager->Load(*g_ActiveConfig.graphics_mod_config); + + m_end_of_frame_event = AfterFrameEvent::Register([this] { EndOfFrame(); }, "ModManager"); + } + + return true; +} + const std::vector& GraphicsModManager::GetProjectionActions(ProjectionType projection_type) const { diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h index 1775e3b0e3..ed0063444a 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h @@ -13,12 +13,15 @@ #include "VideoCommon/GraphicsModSystem/Runtime/FBInfo.h" #include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" #include "VideoCommon/TextureInfo.h" +#include "VideoCommon/VideoEvents.h" #include "VideoCommon/XFMemory.h" class GraphicsModGroupConfig; class GraphicsModManager { public: + bool Initialize(); + const std::vector& GetProjectionActions(ProjectionType projection_type) const; const std::vector& GetProjectionTextureActions(ProjectionType projection_type, @@ -32,9 +35,8 @@ public: void Load(const GraphicsModGroupConfig& config); - void EndOfFrame(); - private: + void EndOfFrame(); void Reset(); class DecoratedAction; @@ -51,4 +53,8 @@ private: std::unordered_map, FBInfoHasher> m_xfb_target_to_actions; std::unordered_set m_groups; + + Common::EventHook m_end_of_frame_event; }; + +extern std::unique_ptr g_graphics_mod_manager; diff --git a/Source/Core/VideoCommon/OnScreenUI.cpp b/Source/Core/VideoCommon/OnScreenUI.cpp new file mode 100644 index 0000000000..e2fa2593a7 --- /dev/null +++ b/Source/Core/VideoCommon/OnScreenUI.cpp @@ -0,0 +1,402 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/OnScreenUI.h" + +#include "Common/EnumMap.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/AbstractGfx.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/Statistics.h" +#include "VideoCommon/VertexManagerBase.h" +#include "VideoCommon/VideoConfig.h" + +#include +#include + +#include +#include + +namespace VideoCommon +{ +bool OnScreenUI::Initialize(u32 width, u32 height, float scale) +{ + std::unique_lock imgui_lock(m_imgui_mutex); + + if (!IMGUI_CHECKVERSION()) + { + PanicAlertFmt("ImGui version check failed"); + return false; + } + if (!ImGui::CreateContext()) + { + PanicAlertFmt("Creating ImGui context failed"); + return false; + } + if (!ImPlot::CreateContext()) + { + PanicAlertFmt("Creating ImPlot context failed"); + return false; + } + + // Don't create an ini file. TODO: Do we want this in the future? + ImGui::GetIO().IniFilename = nullptr; + SetScale(scale); + ImGui::GetStyle().WindowRounding = 7.0f; + + PortableVertexDeclaration vdecl = {}; + vdecl.position = {ComponentFormat::Float, 2, offsetof(ImDrawVert, pos), true, false}; + vdecl.texcoords[0] = {ComponentFormat::Float, 2, offsetof(ImDrawVert, uv), true, false}; + vdecl.colors[0] = {ComponentFormat::UByte, 4, offsetof(ImDrawVert, col), true, false}; + vdecl.stride = sizeof(ImDrawVert); + m_imgui_vertex_format = g_gfx->CreateNativeVertexFormat(vdecl); + if (!m_imgui_vertex_format) + { + PanicAlertFmt("Failed to create ImGui vertex format"); + return false; + } + + // Font texture(s). + { + ImGuiIO& io = ImGui::GetIO(); + u8* font_tex_pixels; + int font_tex_width, font_tex_height; + io.Fonts->GetTexDataAsRGBA32(&font_tex_pixels, &font_tex_width, &font_tex_height); + + TextureConfig font_tex_config(font_tex_width, font_tex_height, 1, 1, 1, + AbstractTextureFormat::RGBA8, 0); + std::unique_ptr font_tex = + g_gfx->CreateTexture(font_tex_config, "ImGui font texture"); + if (!font_tex) + { + PanicAlertFmt("Failed to create ImGui texture"); + return false; + } + font_tex->Load(0, font_tex_width, font_tex_height, font_tex_width, font_tex_pixels, + sizeof(u32) * font_tex_width * font_tex_height); + + io.Fonts->TexID = font_tex.get(); + + m_imgui_textures.push_back(std::move(font_tex)); + } + + if (!RecompileImGuiPipeline()) + return false; + + m_imgui_last_frame_time = Common::Timer::NowUs(); + m_ready = true; + BeginImGuiFrameUnlocked(width, height); // lock is already held + + return true; +} + +OnScreenUI::~OnScreenUI() +{ + std::unique_lock imgui_lock(m_imgui_mutex); + + ImGui::EndFrame(); + ImPlot::DestroyContext(); + ImGui::DestroyContext(); +} + +bool OnScreenUI::RecompileImGuiPipeline() +{ + if (g_presenter->GetBackbufferFormat() == AbstractTextureFormat::Undefined) + { + // No backbuffer (nogui) means no imgui rendering will happen + // Some backends don't like making pipelines with no render targets + return true; + } + + std::unique_ptr vertex_shader = g_gfx->CreateShaderFromSource( + ShaderStage::Vertex, FramebufferShaderGen::GenerateImGuiVertexShader(), + "ImGui vertex shader"); + std::unique_ptr pixel_shader = g_gfx->CreateShaderFromSource( + ShaderStage::Pixel, FramebufferShaderGen::GenerateImGuiPixelShader(), "ImGui pixel shader"); + if (!vertex_shader || !pixel_shader) + { + PanicAlertFmt("Failed to compile ImGui shaders"); + return false; + } + + // GS is used to render the UI to both eyes in stereo modes. + std::unique_ptr geometry_shader; + if (g_gfx->UseGeometryShaderForUI()) + { + geometry_shader = g_gfx->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_gfx->CreatePipeline(pconfig); + if (!m_imgui_pipeline) + { + PanicAlertFmt("Failed to create imgui pipeline"); + return false; + } + + return true; +} + +void OnScreenUI::BeginImGuiFrame(u32 width, u32 height) +{ + std::unique_lock imgui_lock(m_imgui_mutex); + BeginImGuiFrameUnlocked(width, height); +} + +void OnScreenUI::BeginImGuiFrameUnlocked(u32 width, u32 height) +{ + m_backbuffer_width = width; + m_backbuffer_height = height; + + const u64 current_time_us = Common::Timer::NowUs(); + const u64 time_diff_us = current_time_us - m_imgui_last_frame_time; + const float time_diff_secs = static_cast(time_diff_us / 1000000.0); + m_imgui_last_frame_time = current_time_us; + + // Update I/O with window dimensions. + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize = + ImVec2(static_cast(m_backbuffer_width), static_cast(m_backbuffer_height)); + io.DeltaTime = time_diff_secs; + + ImGui::NewFrame(); +} + +void OnScreenUI::DrawImGui() +{ + ImDrawData* draw_data = ImGui::GetDrawData(); + if (!draw_data) + return; + + g_gfx->SetViewport(0.0f, 0.0f, static_cast(m_backbuffer_width), + static_cast(m_backbuffer_height), 0.0f, 1.0f); + + // Uniform buffer for draws. + struct ImGuiUbo + { + float u_rcp_viewport_size_mul2[2]; + float padding[2]; + }; + ImGuiUbo ubo = {{1.0f / m_backbuffer_width * 2.0f, 1.0f / m_backbuffer_height * 2.0f}}; + + // Set up common state for drawing. + g_gfx->SetPipeline(m_imgui_pipeline.get()); + g_gfx->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_gfx->SetScissorRect(g_gfx->ConvertFramebufferRectangle( + MathUtil::Rectangle( + static_cast(cmd.ClipRect.x), static_cast(cmd.ClipRect.y), + static_cast(cmd.ClipRect.z), static_cast(cmd.ClipRect.w)), + g_gfx->GetCurrentFramebuffer())); + g_gfx->SetTexture(0, reinterpret_cast(cmd.TextureId)); + g_gfx->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_gfx->SetScissorRect(g_gfx->ConvertFramebufferRectangle( + MathUtil::Rectangle(0, 0, m_backbuffer_width, m_backbuffer_height), + g_gfx->GetCurrentFramebuffer())); +} + +// Create On-Screen-Messages +void OnScreenUI::DrawDebugText() +{ + const bool show_movie_window = + Config::Get(Config::MAIN_SHOW_FRAME_COUNT) || Config::Get(Config::MAIN_SHOW_LAG) || + Config::Get(Config::MAIN_MOVIE_SHOW_INPUT_DISPLAY) || + Config::Get(Config::MAIN_MOVIE_SHOW_RTC) || Config::Get(Config::MAIN_MOVIE_SHOW_RERECORD); + if (show_movie_window) + { + // Position under the FPS display. + ImGui::SetNextWindowPos( + ImVec2(ImGui::GetIO().DisplaySize.x - 10.f * m_backbuffer_scale, 80.f * m_backbuffer_scale), + ImGuiCond_FirstUseEver, ImVec2(1.0f, 0.0f)); + ImGui::SetNextWindowSizeConstraints( + ImVec2(150.0f * m_backbuffer_scale, 20.0f * m_backbuffer_scale), + ImGui::GetIO().DisplaySize); + if (ImGui::Begin("Movie", nullptr, ImGuiWindowFlags_NoFocusOnAppearing)) + { + if (Movie::IsPlayingInput()) + { + ImGui::Text("Frame: %" PRIu64 " / %" PRIu64, Movie::GetCurrentFrame(), + Movie::GetTotalFrames()); + ImGui::Text("Input: %" PRIu64 " / %" PRIu64, Movie::GetCurrentInputCount(), + Movie::GetTotalInputCount()); + } + else if (Config::Get(Config::MAIN_SHOW_FRAME_COUNT)) + { + ImGui::Text("Frame: %" PRIu64, Movie::GetCurrentFrame()); + ImGui::Text("Input: %" PRIu64, Movie::GetCurrentInputCount()); + } + if (Config::Get(Config::MAIN_SHOW_LAG)) + ImGui::Text("Lag: %" PRIu64 "\n", Movie::GetCurrentLagCount()); + if (Config::Get(Config::MAIN_MOVIE_SHOW_INPUT_DISPLAY)) + ImGui::TextUnformatted(Movie::GetInputDisplay().c_str()); + if (Config::Get(Config::MAIN_MOVIE_SHOW_RTC)) + ImGui::TextUnformatted(Movie::GetRTCDisplay().c_str()); + if (Config::Get(Config::MAIN_MOVIE_SHOW_RERECORD)) + ImGui::TextUnformatted(Movie::GetRerecords().c_str()); + } + ImGui::End(); + } + + if (g_ActiveConfig.bOverlayStats) + g_stats.Display(); + + if (g_ActiveConfig.bShowNetPlayMessages && g_netplay_chat_ui) + g_netplay_chat_ui->Display(); + + if (Config::Get(Config::NETPLAY_GOLF_MODE_OVERLAY) && g_netplay_golf_ui) + g_netplay_golf_ui->Display(); + + if (g_ActiveConfig.bOverlayProjStats) + g_stats.DisplayProj(); + + if (g_ActiveConfig.bOverlayScissorStats) + g_stats.DisplayScissor(); + + const std::string profile_output = Common::Profiler::ToString(); + if (!profile_output.empty()) + ImGui::TextUnformatted(profile_output.c_str()); +} + +void OnScreenUI::Finalize() +{ + auto lock = GetImGuiLock(); + + g_perf_metrics.DrawImGuiStats(m_backbuffer_scale); + DrawDebugText(); + OSD::DrawMessages(); + ImGui::Render(); +} + +std::unique_lock OnScreenUI::GetImGuiLock() +{ + return std::unique_lock(m_imgui_mutex); +} + +void OnScreenUI::SetScale(float backbuffer_scale) +{ + ImGui::GetIO().DisplayFramebufferScale.x = backbuffer_scale; + ImGui::GetIO().DisplayFramebufferScale.y = backbuffer_scale; + ImGui::GetIO().FontGlobalScale = backbuffer_scale; + ImGui::GetStyle().ScaleAllSizes(backbuffer_scale); + + m_backbuffer_scale = backbuffer_scale; +} +void OnScreenUI::SetKeyMap(const DolphinKeyMap& key_map) +{ + // Right now this is a 1:1 mapping. But might not be true later + static constexpr DolphinKeyMap dolphin_to_imgui_map = { + ImGuiKey_Tab, ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_UpArrow, + ImGuiKey_DownArrow, ImGuiKey_PageUp, ImGuiKey_PageDown, ImGuiKey_Home, + ImGuiKey_End, ImGuiKey_Insert, ImGuiKey_Delete, ImGuiKey_Backspace, + ImGuiKey_Space, ImGuiKey_Enter, ImGuiKey_Escape, ImGuiKey_KeyPadEnter, + ImGuiKey_A, ImGuiKey_C, ImGuiKey_V, ImGuiKey_X, + ImGuiKey_Y, ImGuiKey_Z, + }; + static_assert(dolphin_to_imgui_map.size() == ImGuiKey_COUNT); // Fail if ImGui adds keys + + auto lock = GetImGuiLock(); + + if (!ImGui::GetCurrentContext()) + return; + + for (int dolphin_key = 0; dolphin_key <= static_cast(DolphinKey::Z); dolphin_key++) + { + int imgui_key = dolphin_to_imgui_map[DolphinKey(dolphin_key)]; + if (imgui_key >= 0) + ImGui::GetIO().KeyMap[imgui_key] = (key_map[DolphinKey(dolphin_key)] & 0x1FF); + } +} + +void OnScreenUI::SetKey(u32 key, bool is_down, const char* chars) +{ + auto lock = GetImGuiLock(); + if (key < std::size(ImGui::GetIO().KeysDown)) + ImGui::GetIO().KeysDown[key] = is_down; + + if (chars) + ImGui::GetIO().AddInputCharactersUTF8(chars); +} + +void OnScreenUI::SetMousePos(float x, float y) +{ + auto lock = GetImGuiLock(); + + ImGui::GetIO().MousePos.x = x; + ImGui::GetIO().MousePos.y = y; +} + +void OnScreenUI::SetMousePress(u32 button_mask) +{ + auto lock = GetImGuiLock(); + + for (size_t i = 0; i < std::size(ImGui::GetIO().MouseDown); i++) + ImGui::GetIO().MouseDown[i] = (button_mask & (1u << i)) != 0; +} + +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/OnScreenUI.h b/Source/Core/VideoCommon/OnScreenUI.h new file mode 100644 index 0000000000..a0becbc126 --- /dev/null +++ b/Source/Core/VideoCommon/OnScreenUI.h @@ -0,0 +1,80 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/OnScreenUIKeyMap.h" + +class NativeVertexFormat; +class AbstractTexture; +class AbstractPipeline; + +namespace VideoCommon +{ +// OnScreenUI handles all the ImGui rendering. +class OnScreenUI +{ +public: + OnScreenUI() = default; + ~OnScreenUI(); + + // ImGui initialization depends on being able to create textures and pipelines, so do it last. + bool Initialize(u32 width, u32 height, float scale); + + // Returns a lock for the ImGui mutex, enabling data structures to be modified from outside. + // Use with care, only non-drawing functions should be called from outside the video thread, + // as the drawing is tied to a "frame". + std::unique_lock GetImGuiLock(); + + bool IsReady() { return m_ready; } + + // Sets up ImGui state for the next frame. + // This function itself acquires the ImGui lock, so it should not be held. + void BeginImGuiFrame(u32 width, u32 height); + + // Same as above but without locking the ImGui lock. + void BeginImGuiFrameUnlocked(u32 width, u32 height); + + // Renders ImGui windows to the currently-bound framebuffer. + // Should be called with the ImGui lock held. + void DrawImGui(); + + // Recompiles ImGui pipeline - call when stereo mode changes. + bool RecompileImGuiPipeline(); + + void SetScale(float backbuffer_scale); + + void Finalize(); + + // Receive keyboard and mouse from QT + void SetKeyMap(const DolphinKeyMap& key_map); + void SetKey(u32 key, bool is_down, const char* chars); + void SetMousePos(float x, float y); + void SetMousePress(u32 button_mask); + +private: + // Destroys all ImGui GPU resources, must do before shutdown. + void ShutdownImGui(); + + void DrawDebugText(); + + // ImGui resources. + std::unique_ptr m_imgui_vertex_format; + std::vector> m_imgui_textures; + std::unique_ptr m_imgui_pipeline; + std::mutex m_imgui_mutex; + u64 m_imgui_last_frame_time; + + u32 m_backbuffer_width = 1; + u32 m_backbuffer_height = 1; + float m_backbuffer_scale = 1.0; + + bool m_ready = false; +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/OnScreenUIKeyMap.h b/Source/Core/VideoCommon/OnScreenUIKeyMap.h new file mode 100644 index 0000000000..c637f3938b --- /dev/null +++ b/Source/Core/VideoCommon/OnScreenUIKeyMap.h @@ -0,0 +1,37 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Common/EnumMap.h" + +// The main point of this is to allow other parts of dolphin to set ImGui's key map without +// having to import ImGui headers. +// But the idea is that it can be expanded in the future with more keys to support more things. +enum class DolphinKey +{ + Tab, + LeftArrow, + RightArrow, + UpArrow, + DownArrow, + PageUp, + PageDown, + Home, + End, + Insert, + Delete, + Backspace, + Space, + Enter, + Escape, + KeyPadEnter, + A, // for text edit CTRL+A: select all + C, // for text edit CTRL+C: copy + V, // for text edit CTRL+V: paste + X, // for text edit CTRL+X: cut + Y, // for text edit CTRL+Y: redo + Z, // for text edit CTRL+Z: undo +}; + +using DolphinKeyMap = Common::EnumMap; diff --git a/Source/Core/VideoCommon/PerfQueryBase.h b/Source/Core/VideoCommon/PerfQueryBase.h index 9671c33ec3..183a27c80c 100644 --- a/Source/Core/VideoCommon/PerfQueryBase.h +++ b/Source/Core/VideoCommon/PerfQueryBase.h @@ -34,6 +34,8 @@ public: PerfQueryBase() : m_query_count(0) {} virtual ~PerfQueryBase() {} + virtual bool Initialize() { return true; } + // Checks if performance queries are enabled in the gameini configuration. // NOTE: Called from CPU+GPU thread static bool ShouldEmulate(); diff --git a/Source/Core/VideoCommon/PixelEngine.cpp b/Source/Core/VideoCommon/PixelEngine.cpp index 82464339e4..9817e38c07 100644 --- a/Source/Core/VideoCommon/PixelEngine.cpp +++ b/Source/Core/VideoCommon/PixelEngine.cpp @@ -20,7 +20,6 @@ #include "VideoCommon/BoundingBox.h" #include "VideoCommon/Fifo.h" #include "VideoCommon/PerfQueryBase.h" -#include "VideoCommon/RenderBase.h" #include "VideoCommon/VideoBackendBase.h" namespace PixelEngine @@ -148,7 +147,7 @@ void PixelEngineManager::RegisterMMIO(MMIO::Mapping* mmio, u32 base) { mmio->Register(base | (PE_BBOX_LEFT + 2 * i), MMIO::ComplexRead([i](Core::System& system, u32) { - g_renderer->BBoxDisable(system.GetPixelShaderManager()); + g_bounding_box->Disable(system.GetPixelShaderManager()); return g_video_backend->Video_GetBoundingBox(i); }), MMIO::InvalidWrite()); diff --git a/Source/Core/VideoCommon/PixelShaderGen.cpp b/Source/Core/VideoCommon/PixelShaderGen.cpp index 8a34f1debf..df6f5dfab8 100644 --- a/Source/Core/VideoCommon/PixelShaderGen.cpp +++ b/Source/Core/VideoCommon/PixelShaderGen.cpp @@ -16,7 +16,6 @@ #include "VideoCommon/DriverDetails.h" #include "VideoCommon/LightingShaderGen.h" #include "VideoCommon/NativeVertexFormat.h" -#include "VideoCommon/RenderBase.h" #include "VideoCommon/RenderState.h" #include "VideoCommon/VertexLoaderManager.h" #include "VideoCommon/VideoCommon.h" @@ -180,7 +179,7 @@ PixelShaderUid GetPixelShaderUid() uid_data->genMode_numindstages = bpmem.genMode.numindstages; uid_data->genMode_numtevstages = bpmem.genMode.numtevstages; uid_data->genMode_numtexgens = bpmem.genMode.numtexgens; - uid_data->bounding_box = g_ActiveConfig.bBBoxEnable && g_renderer->IsBBoxEnabled(); + uid_data->bounding_box = g_ActiveConfig.bBBoxEnable && g_bounding_box->IsEnabled(); uid_data->rgba6_format = bpmem.zcontrol.pixel_format == PixelFormat::RGBA6_Z24 && !g_ActiveConfig.bForceTrueColor; uid_data->dither = bpmem.blendmode.dither && uid_data->rgba6_format; @@ -323,7 +322,7 @@ void ClearUnusedPixelShaderUidBits(APIType api_type, const ShaderHostConfig& hos // If bounding box is enabled when a UID cache is created, then later disabled, we shouldn't // emit the bounding box portion of the shader. - uid_data->bounding_box &= host_config.bounding_box & host_config.backend_bbox; + uid_data->bounding_box &= host_config.bounding_box && host_config.backend_bbox; } void WritePixelShaderCommonHeader(ShaderCode& out, APIType api_type, diff --git a/Source/Core/VideoCommon/PixelShaderManager.cpp b/Source/Core/VideoCommon/PixelShaderManager.cpp index 35db2c6684..786c5d6177 100644 --- a/Source/Core/VideoCommon/PixelShaderManager.cpp +++ b/Source/Core/VideoCommon/PixelShaderManager.cpp @@ -7,7 +7,7 @@ #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/FramebufferManager.h" #include "VideoCommon/VideoCommon.h" #include "VideoCommon/VideoConfig.h" #include "VideoCommon/XFMemory.h" @@ -65,7 +65,7 @@ void PixelShaderManager::Init() } } - dirty = true; + Dirty(); } void PixelShaderManager::Dirty() @@ -74,7 +74,8 @@ void PixelShaderManager::Dirty() // Any constants that can changed based on settings should be re-calculated m_fog_range_adjusted_changed = true; - SetEfbScaleChanged(g_renderer->EFBToScaledXf(1), g_renderer->EFBToScaledYf(1)); + SetEfbScaleChanged(g_framebuffer_manager->EFBToScaledXf(1), + g_framebuffer_manager->EFBToScaledYf(1)); SetFogParamChanged(); dirty = true; @@ -102,8 +103,8 @@ void PixelShaderManager::SetConstants() // so to simplify I use the hi coefficient as K in the shader taking 256 as the scale // TODO: Shouldn't this be EFBToScaledXf? constants.fogf[2] = ScreenSpaceCenter; - constants.fogf[3] = - static_cast(g_renderer->EFBToScaledX(static_cast(2.0f * xfmem.viewport.wd))); + constants.fogf[3] = static_cast( + g_framebuffer_manager->EFBToScaledX(static_cast(2.0f * xfmem.viewport.wd))); for (size_t i = 0, vec_index = 0; i < std::size(bpmem.fogRange.K); i++) { diff --git a/Source/Core/VideoCommon/PostProcessing.cpp b/Source/Core/VideoCommon/PostProcessing.cpp index 0a1e995c15..7f4faed582 100644 --- a/Source/Core/VideoCommon/PostProcessing.cpp +++ b/Source/Core/VideoCommon/PostProcessing.cpp @@ -20,11 +20,12 @@ #include "Common/StringUtil.h" #include "VideoCommon/AbstractFramebuffer.h" +#include "VideoCommon/AbstractGfx.h" #include "VideoCommon/AbstractPipeline.h" #include "VideoCommon/AbstractShader.h" #include "VideoCommon/AbstractTexture.h" #include "VideoCommon/FramebufferManager.h" -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/Present.h" #include "VideoCommon/ShaderCache.h" #include "VideoCommon/VertexManagerBase.h" #include "VideoCommon/VideoCommon.h" @@ -416,9 +417,9 @@ void PostProcessing::BlitFromTexture(const MathUtil::Rectangle& dst, const MathUtil::Rectangle& src, const AbstractTexture* src_tex, int src_layer) { - if (g_renderer->GetCurrentFramebuffer()->GetColorFormat() != m_framebuffer_format) + if (g_gfx->GetCurrentFramebuffer()->GetColorFormat() != m_framebuffer_format) { - m_framebuffer_format = g_renderer->GetCurrentFramebuffer()->GetColorFormat(); + m_framebuffer_format = g_gfx->GetCurrentFramebuffer()->GetColorFormat(); RecompilePipeline(); } @@ -429,12 +430,12 @@ void PostProcessing::BlitFromTexture(const MathUtil::Rectangle& dst, g_vertex_manager->UploadUtilityUniforms(m_uniform_staging_buffer.data(), static_cast(m_uniform_staging_buffer.size())); - g_renderer->SetViewportAndScissor( - g_renderer->ConvertFramebufferRectangle(dst, g_renderer->GetCurrentFramebuffer())); - g_renderer->SetPipeline(m_pipeline.get()); - g_renderer->SetTexture(0, src_tex); - g_renderer->SetSamplerState(0, RenderState::GetLinearSamplerState()); - g_renderer->Draw(0, 3); + g_gfx->SetViewportAndScissor( + g_gfx->ConvertFramebufferRectangle(dst, g_gfx->GetCurrentFramebuffer())); + g_gfx->SetPipeline(m_pipeline.get()); + g_gfx->SetTexture(0, src_tex); + g_gfx->SetSamplerState(0, RenderState::GetLinearSamplerState()); + g_gfx->Draw(0, 3); } std::string PostProcessing::GetUniformBufferHeader() const @@ -597,8 +598,8 @@ bool PostProcessing::CompileVertexShader() ss << "}\n"; - m_vertex_shader = g_renderer->CreateShaderFromSource(ShaderStage::Vertex, ss.str(), - "Post-processing vertex shader"); + m_vertex_shader = + g_gfx->CreateShaderFromSource(ShaderStage::Vertex, ss.str(), "Post-processing vertex shader"); if (!m_vertex_shader) { PanicAlertFmt("Failed to compile post-processing vertex shader"); @@ -627,7 +628,7 @@ size_t PostProcessing::CalculateUniformsSize() const void PostProcessing::FillUniformBuffer(const MathUtil::Rectangle& src, const AbstractTexture* src_tex, int src_layer) { - const auto& window_rect = g_renderer->GetTargetRectangle(); + const auto& window_rect = g_presenter->GetTargetRectangle(); const float rcp_src_width = 1.0f / src_tex->GetWidth(); const float rcp_src_height = 1.0f / src_tex->GetHeight(); BuiltinUniforms builtin_uniforms = { @@ -687,7 +688,7 @@ bool PostProcessing::CompilePixelShader() // Generate GLSL and compile the new shader. m_config.LoadShader(g_ActiveConfig.sPostProcessingShader); - m_pixel_shader = g_renderer->CreateShaderFromSource( + m_pixel_shader = g_gfx->CreateShaderFromSource( ShaderStage::Pixel, GetHeader() + m_config.GetShaderCode() + GetFooter(), fmt::format("Post-processing pixel shader: {}", m_config.GetShader())); if (!m_pixel_shader) @@ -696,7 +697,7 @@ bool PostProcessing::CompilePixelShader() // Use default shader. m_config.LoadDefaultShader(); - m_pixel_shader = g_renderer->CreateShaderFromSource( + m_pixel_shader = g_gfx->CreateShaderFromSource( ShaderStage::Pixel, GetHeader() + m_config.GetShaderCode() + GetFooter(), "Default post-processing pixel shader"); if (!m_pixel_shader) @@ -715,14 +716,14 @@ bool PostProcessing::CompilePipeline() AbstractPipelineConfig config = {}; config.vertex_shader = m_vertex_shader.get(); config.geometry_shader = - g_renderer->UseGeometryShaderForUI() ? g_shader_cache->GetTexcoordGeometryShader() : nullptr; + g_gfx->UseGeometryShaderForUI() ? g_shader_cache->GetTexcoordGeometryShader() : nullptr; config.pixel_shader = m_pixel_shader.get(); config.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles); config.depth_state = RenderState::GetNoDepthTestingDepthState(); config.blending_state = RenderState::GetNoBlendingBlendState(); config.framebuffer_state = RenderState::GetColorFramebufferState(m_framebuffer_format); config.usage = AbstractPipelineUsage::Utility; - m_pipeline = g_renderer->CreatePipeline(config); + m_pipeline = g_gfx->CreatePipeline(config); if (!m_pipeline) return false; diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp new file mode 100644 index 0000000000..181dfea140 --- /dev/null +++ b/Source/Core/VideoCommon/Present.cpp @@ -0,0 +1,620 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/Present.h" + +#include "Common/ChunkFile.h" +#include "Core/HW/VideoInterface.h" +#include "Core/Host.h" + +#include "InputCommon/ControllerInterface/ControllerInterface.h" + +#include "Present.h" +#include "VideoCommon/AbstractGfx.h" +#include "VideoCommon/FrameDumper.h" +#include "VideoCommon/OnScreenUI.h" +#include "VideoCommon/PostProcessing.h" +#include "VideoCommon/Statistics.h" +#include "VideoCommon/VertexManagerBase.h" +#include "VideoCommon/VideoConfig.h" +#include "VideoCommon/VideoEvents.h" +#include "VideoCommon/Widescreen.h" + +std::unique_ptr g_presenter; + +namespace VideoCommon +{ +static float AspectToWidescreen(float aspect) +{ + return aspect * ((16.0f / 9.0f) / (4.0f / 3.0f)); +} + +Presenter::Presenter() +{ + m_config_changed = + ConfigChangedEvent::Register([this](u32 bits) { ConfigChanged(bits); }, "Presenter"); +} + +Presenter::~Presenter() +{ + // Disable ControllerInterface's aspect ratio adjustments so mapping dialog behaves normally. + g_controller_interface.SetAspectRatioAdjustment(1); +} + +bool Presenter::Initialize() +{ + UpdateDrawRectangle(); + + if (!g_gfx->IsHeadless()) + { + SetBackbuffer(g_gfx->GetSurfaceInfo()); + + m_post_processor = std::make_unique(); + if (!m_post_processor->Initialize(m_backbuffer_format)) + return false; + + m_onscreen_ui = std::make_unique(); + if (!m_onscreen_ui->Initialize(m_backbuffer_width, m_backbuffer_height, m_backbuffer_scale)) + return false; + + // Draw a blank frame (and complete OnScreenUI initialization) + g_gfx->BindBackbuffer({{0.0f, 0.0f, 0.0f, 1.0f}}); + g_gfx->PresentBackbuffer(); + } + + return true; +} + +bool Presenter::FetchXFB(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks) +{ + ReleaseXFBContentLock(); + u64 old_xfb_id = m_last_xfb_id; + + if (fb_width == 0 || fb_height == 0) + { + // Game is blanking the screen + m_xfb_entry.reset(); + m_last_xfb_id = std::numeric_limits::max(); + } + else + { + m_xfb_entry = + g_texture_cache->GetXFBTexture(xfb_addr, fb_width, fb_height, fb_stride, &m_xfb_rect); + m_last_xfb_id = m_xfb_entry->id; + + m_xfb_entry->AcquireContentLock(); + } + m_last_xfb_addr = xfb_addr; + m_last_xfb_ticks = ticks; + m_last_xfb_width = fb_width; + m_last_xfb_stride = fb_stride; + m_last_xfb_height = fb_height; + + return old_xfb_id == m_last_xfb_id; +} + +void Presenter::ViSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks) +{ + bool is_duplicate = FetchXFB(xfb_addr, fb_width, fb_stride, fb_height, ticks); + + PresentInfo present_info; + present_info.emulated_timestamp = ticks; + present_info.present_count = m_present_count++; + if (is_duplicate) + { + present_info.frame_count = m_frame_count - 1; // Previous frame + present_info.reason = PresentInfo::PresentReason::VideoInterfaceDuplicate; + } + else + { + present_info.frame_count = m_frame_count++; + present_info.reason = PresentInfo::PresentReason::VideoInterface; + } + + BeforePresentEvent::Trigger(present_info); + + if (!is_duplicate || !g_ActiveConfig.bSkipPresentingDuplicateXFBs) + { + Present(); + ProcessFrameDumping(ticks); + + AfterPresentEvent::Trigger(present_info); + } +} + +void Presenter::ImmediateSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks) +{ + FetchXFB(xfb_addr, fb_width, fb_stride, fb_height, ticks); + + PresentInfo present_info; + present_info.emulated_timestamp = ticks; // TODO: This should be the time of the next VI field + present_info.frame_count = m_frame_count++; + present_info.reason = PresentInfo::PresentReason::Immediate; + present_info.present_count = m_present_count++; + + BeforePresentEvent::Trigger(present_info); + + Present(); + ProcessFrameDumping(ticks); + + AfterPresentEvent::Trigger(present_info); +} + +void Presenter::ProcessFrameDumping(u64 ticks) const +{ + if (g_frame_dumper->IsFrameDumping() && m_xfb_entry) + { + MathUtil::Rectangle target_rect; + if (!g_ActiveConfig.bInternalResolutionFrameDumps && !g_gfx->IsHeadless()) + { + target_rect = GetTargetRectangle(); + } + else + { + int width, height; + std::tie(width, height) = + CalculateOutputDimensions(m_xfb_rect.GetWidth(), m_xfb_rect.GetHeight()); + target_rect = MathUtil::Rectangle(0, 0, width, height); + } + + g_frame_dumper->DumpCurrentFrame(m_xfb_entry->texture.get(), m_xfb_rect, target_rect, ticks, + m_frame_count); + } +} + +void Presenter::SetBackbuffer(int backbuffer_width, int backbuffer_height) +{ + m_backbuffer_width = backbuffer_width; + m_backbuffer_height = backbuffer_height; + UpdateDrawRectangle(); +} + +void Presenter::SetBackbuffer(SurfaceInfo info) +{ + m_backbuffer_width = info.width; + m_backbuffer_height = info.height; + m_backbuffer_scale = info.scale; + m_backbuffer_format = info.format; + UpdateDrawRectangle(); +} + +void Presenter::ConfigChanged(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 (changed_bits & ConfigChangeBits::CONFIG_CHANGE_BIT_POST_PROCESSING_SHADER && m_post_processor) + { + // The existing shader must not be in use when it's destroyed + g_gfx->WaitForGPUIdle(); + + m_post_processor->RecompileShader(); + } + + // Stereo mode change requires recompiling our post processing pipeline and imgui pipelines for + // rendering the UI. + if (changed_bits & ConfigChangeBits::CONFIG_CHANGE_BIT_STEREO_MODE) + { + if (m_onscreen_ui) + m_onscreen_ui->RecompileImGuiPipeline(); + if (m_post_processor) + m_post_processor->RecompilePipeline(); + } +} + +std::tuple, MathUtil::Rectangle> +Presenter::ConvertStereoRectangle(const MathUtil::Rectangle& rc) const +{ + // Resize target to half its original size + auto draw_rc = rc; + if (g_ActiveConfig.stereo_mode == StereoMode::TAB) + { + // The height may be negative due to flipped rectangles + int height = rc.bottom - rc.top; + draw_rc.top += height / 4; + draw_rc.bottom -= height / 4; + } + else + { + int width = rc.right - rc.left; + draw_rc.left += width / 4; + draw_rc.right -= width / 4; + } + + // Create two target rectangle offset to the sides of the backbuffer + auto left_rc = draw_rc; + auto right_rc = draw_rc; + if (g_ActiveConfig.stereo_mode == StereoMode::TAB) + { + left_rc.top -= m_backbuffer_height / 4; + left_rc.bottom -= m_backbuffer_height / 4; + right_rc.top += m_backbuffer_height / 4; + right_rc.bottom += m_backbuffer_height / 4; + } + else + { + left_rc.left -= m_backbuffer_width / 4; + left_rc.right -= m_backbuffer_width / 4; + right_rc.left += m_backbuffer_width / 4; + right_rc.right += m_backbuffer_width / 4; + } + + return std::make_tuple(left_rc, right_rc); +} + +float Presenter::CalculateDrawAspectRatio() const +{ + const auto aspect_mode = g_ActiveConfig.aspect_mode; + + // If stretch is enabled, we prefer the aspect ratio of the window. + if (aspect_mode == AspectMode::Stretch) + return (static_cast(m_backbuffer_width) / static_cast(m_backbuffer_height)); + + const float aspect_ratio = VideoInterface::GetAspectRatio(); + + if (aspect_mode == AspectMode::AnalogWide || + (aspect_mode == AspectMode::Auto && g_widescreen->IsGameWidescreen())) + { + return AspectToWidescreen(aspect_ratio); + } + + return aspect_ratio; +} + +void Presenter::AdjustRectanglesToFitBounds(MathUtil::Rectangle* target_rect, + MathUtil::Rectangle* source_rect, int fb_width, + int fb_height) +{ + const int orig_target_width = target_rect->GetWidth(); + const int orig_target_height = target_rect->GetHeight(); + const int orig_source_width = source_rect->GetWidth(); + const int orig_source_height = source_rect->GetHeight(); + if (target_rect->left < 0) + { + const int offset = -target_rect->left; + target_rect->left = 0; + source_rect->left += offset * orig_source_width / orig_target_width; + } + if (target_rect->right > fb_width) + { + const int offset = target_rect->right - fb_width; + target_rect->right -= offset; + source_rect->right -= offset * orig_source_width / orig_target_width; + } + if (target_rect->top < 0) + { + const int offset = -target_rect->top; + target_rect->top = 0; + source_rect->top += offset * orig_source_height / orig_target_height; + } + if (target_rect->bottom > fb_height) + { + const int offset = target_rect->bottom - fb_height; + target_rect->bottom -= offset; + source_rect->bottom -= offset * orig_source_height / orig_target_height; + } +} + +void Presenter::ReleaseXFBContentLock() +{ + if (m_xfb_entry) + m_xfb_entry->ReleaseContentLock(); +} + +void Presenter::ChangeSurface(void* new_surface_handle) +{ + std::lock_guard lock(m_swap_mutex); + m_new_surface_handle = new_surface_handle; + m_surface_changed.Set(); +} + +void Presenter::ResizeSurface() +{ + std::lock_guard lock(m_swap_mutex); + m_surface_resized.Set(); +} + +void* Presenter::GetNewSurfaceHandle() +{ + void* handle = m_new_surface_handle; + m_new_surface_handle = nullptr; + return handle; +} + +u32 Presenter::AutoIntegralScale() const +{ + // Calculate a scale based on the window size + u32 width = EFB_WIDTH * m_target_rectangle.GetWidth() / m_last_xfb_width; + u32 height = EFB_HEIGHT * m_target_rectangle.GetHeight() / m_last_xfb_height; + return std::max((width - 1) / EFB_WIDTH + 1, (height - 1) / EFB_HEIGHT + 1); +} +void Presenter::SetWindowSize(int width, int height) +{ + const auto [out_width, out_height] = g_presenter->CalculateOutputDimensions(width, height); + + // Track the last values of width/height to avoid sending a window resize event every frame. + if (out_width == m_last_window_request_width && out_height == m_last_window_request_height) + return; + + m_last_window_request_width = out_width; + m_last_window_request_height = out_height; + Host_RequestRenderWindowSize(out_width, out_height); +} + +// Crop to exactly 16:9 or 4:3 if enabled and not AspectMode::Stretch. +std::tuple Presenter::ApplyStandardAspectCrop(float width, float height) const +{ + const auto aspect_mode = g_ActiveConfig.aspect_mode; + + if (!g_ActiveConfig.bCrop || aspect_mode == AspectMode::Stretch) + return {width, height}; + + // Force 4:3 or 16:9 by cropping the image. + const float current_aspect = width / height; + const float expected_aspect = + (aspect_mode == AspectMode::AnalogWide || + (aspect_mode == AspectMode::Auto && g_widescreen->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_widescreen->IsGameWidescreen()) + source_aspect = AspectToWidescreen(source_aspect); + + const float adjust = source_aspect / draw_aspect_ratio; + if (adjust > 1) + { + // Vert+ + g_Config.fAspectRatioHackW = 1; + g_Config.fAspectRatioHackH = 1 / adjust; + } + else + { + // Hor+ + g_Config.fAspectRatioHackW = adjust; + g_Config.fAspectRatioHackH = 1; + } + } + else + { + // Hack is disabled. + g_Config.fAspectRatioHackW = 1; + g_Config.fAspectRatioHackH = 1; + } + + // The rendering window size + const float win_width = static_cast(m_backbuffer_width); + const float win_height = static_cast(m_backbuffer_height); + + // FIXME: this breaks at very low widget sizes + // Make ControllerInterface aware of the render window region actually being used + // to adjust mouse cursor inputs. + g_controller_interface.SetAspectRatioAdjustment(draw_aspect_ratio / (win_width / win_height)); + + float draw_width = draw_aspect_ratio; + float draw_height = 1; + + // Crop the picture to a standard aspect ratio. (if enabled) + auto [crop_width, crop_height] = ApplyStandardAspectCrop(draw_width, draw_height); + + // scale the picture to fit the rendering window + if (win_width / win_height >= crop_width / crop_height) + { + // the window is flatter than the picture + draw_width *= win_height / crop_height; + crop_width *= win_height / crop_height; + draw_height *= win_height / crop_height; + crop_height = win_height; + } + else + { + // the window is skinnier than the picture + draw_width *= win_width / crop_width; + draw_height *= win_width / crop_width; + crop_height *= win_width / crop_width; + crop_width = win_width; + } + + // ensure divisibility by 4 to make it compatible with all the video encoders + draw_width = std::ceil(draw_width) - static_cast(std::ceil(draw_width)) % 4; + draw_height = std::ceil(draw_height) - static_cast(std::ceil(draw_height)) % 4; + + m_target_rectangle.left = static_cast(std::round(win_width / 2.0 - draw_width / 2.0)); + m_target_rectangle.top = static_cast(std::round(win_height / 2.0 - draw_height / 2.0)); + m_target_rectangle.right = m_target_rectangle.left + static_cast(draw_width); + m_target_rectangle.bottom = m_target_rectangle.top + static_cast(draw_height); +} + +std::tuple Presenter::ScaleToDisplayAspectRatio(const int width, + const int height) const +{ + // Scale either the width or height depending the content aspect ratio. + // This way we preserve as much resolution as possible when scaling. + float scaled_width = static_cast(width); + float scaled_height = static_cast(height); + const float draw_aspect = CalculateDrawAspectRatio(); + if (scaled_width / scaled_height >= draw_aspect) + scaled_height = scaled_width / draw_aspect; + else + scaled_width = scaled_height * draw_aspect; + return std::make_tuple(scaled_width, scaled_height); +} + +std::tuple Presenter::CalculateOutputDimensions(int width, int height) const +{ + width = std::max(width, 1); + height = std::max(height, 1); + + auto [scaled_width, scaled_height] = ScaleToDisplayAspectRatio(width, height); + + // Apply crop if enabled. + std::tie(scaled_width, scaled_height) = ApplyStandardAspectCrop(scaled_width, scaled_height); + + width = static_cast(std::ceil(scaled_width)); + height = static_cast(std::ceil(scaled_height)); + + // UpdateDrawRectangle() makes sure that the rendered image is divisible by four for video + // encoders, so do that here too to match it + width -= width % 4; + height -= height % 4; + + return std::make_tuple(width, height); +} + +void Presenter::RenderXFBToScreen(const MathUtil::Rectangle& target_rc, + const AbstractTexture* source_texture, + const MathUtil::Rectangle& source_rc) +{ + if (g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer && + g_ActiveConfig.backend_info.bUsesExplictQuadBuffering) + { + // Quad-buffered stereo is annoying on GL. + g_gfx->SelectLeftBuffer(); + m_post_processor->BlitFromTexture(target_rc, source_rc, source_texture, 0); + + g_gfx->SelectRightBuffer(); + m_post_processor->BlitFromTexture(target_rc, source_rc, source_texture, 1); + + g_gfx->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); + } +} + +void Presenter::Present() +{ + m_present_count++; + + if (g_gfx->IsHeadless() || (!m_onscreen_ui && !m_xfb_entry)) + return; + + if (!g_gfx->SupportsUtilityDrawing()) + { + // Video Software doesn't support Drawing a UI or doing post-processing + // So just Show the XFB + g_gfx->ShowImage(m_xfb_entry->texture.get(), m_xfb_rect); + return; + } + + // 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(); + + UpdateDrawRectangle(); + + g_gfx->BeginUtilityDrawing(); + g_gfx->BindBackbuffer({{0.0f, 0.0f, 0.0f, 1.0f}}); + + // Render the XFB to the screen. + if (m_xfb_entry) + { + // 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); + } + + if (m_onscreen_ui) + { + m_onscreen_ui->Finalize(); + m_onscreen_ui->DrawImGui(); + } + + // Present to the window system. + { + std::lock_guard guard(m_swap_mutex); + g_gfx->PresentBackbuffer(); + } + + if (m_xfb_entry) + { + // 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()); + } + + if (m_onscreen_ui) + m_onscreen_ui->BeginImGuiFrame(m_backbuffer_width, m_backbuffer_height); + + g_gfx->EndUtilityDrawing(); +} + +void Presenter::SetKeyMap(const DolphinKeyMap& key_map) +{ + if (m_onscreen_ui) + m_onscreen_ui->SetKeyMap(key_map); +} + +void Presenter::SetKey(u32 key, bool is_down, const char* chars) +{ + if (m_onscreen_ui) + m_onscreen_ui->SetKey(key, is_down, chars); +} + +void Presenter::SetMousePos(float x, float y) +{ + if (m_onscreen_ui) + m_onscreen_ui->SetMousePos(x, y); +} + +void Presenter::SetMousePress(u32 button_mask) +{ + if (m_onscreen_ui) + m_onscreen_ui->SetMousePress(button_mask); +} + +void Presenter::DoState(PointerWrap& p) +{ + p.Do(m_frame_count); + p.Do(m_last_xfb_ticks); + p.Do(m_last_xfb_addr); + p.Do(m_last_xfb_width); + p.Do(m_last_xfb_stride); + p.Do(m_last_xfb_height); + + if (p.IsReadMode()) + { + // This technically counts as the end of the frame + AfterFrameEvent::Trigger(); + + // re-display the most recent XFB + ImmediateSwap(m_last_xfb_addr, m_last_xfb_width, m_last_xfb_stride, m_last_xfb_height, + m_last_xfb_ticks); + } +} + +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Present.h b/Source/Core/VideoCommon/Present.h new file mode 100644 index 0000000000..14d2104a65 --- /dev/null +++ b/Source/Core/VideoCommon/Present.h @@ -0,0 +1,156 @@ +// 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/OnScreenUIKeyMap.h" +#include "VideoCommon/TextureCacheBase.h" +#include "VideoCommon/TextureConfig.h" +#include "VideoCommon/VideoCommon.h" + +#include +#include +#include +#include +#include + +class AbstractTexture; +struct SurfaceInfo; +enum class DolphinKey; + +namespace VideoCommon +{ +class OnScreenUI; +class PostProcessing; + +// Presenter is a class that deals with putting the final XFB on the screen. +// It also handles the ImGui UI and post-processing. +class Presenter +{ +public: + using ClearColor = std::array; + + Presenter(); + virtual ~Presenter(); + + void ViSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks); + void ImmediateSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks); + + void Present(); + void ClearLastXfbId() { m_last_xfb_id = std::numeric_limits::max(); } + + bool Initialize(); + + void ConfigChanged(u32 changed_bits); + + // Display resolution + int GetBackbufferWidth() const { return m_backbuffer_width; } + int GetBackbufferHeight() const { return m_backbuffer_height; } + float GetBackbufferScale() const { return m_backbuffer_scale; } + u32 AutoIntegralScale() const; + AbstractTextureFormat GetBackbufferFormat() const { return m_backbuffer_format; } + void SetWindowSize(int width, int height); + void SetBackbuffer(int backbuffer_width, int backbuffer_height); + void SetBackbuffer(SurfaceInfo info); + + void UpdateDrawRectangle(); + + float CalculateDrawAspectRatio() const; + + // Crops the target rectangle to the framebuffer dimensions, reducing the size of the source + // rectangle if it is greater. Works even if the source and target rectangles don't have a + // 1:1 pixel mapping, scaling as appropriate. + void AdjustRectanglesToFitBounds(MathUtil::Rectangle* target_rect, + MathUtil::Rectangle* source_rect, int fb_width, + int fb_height); + + void ReleaseXFBContentLock(); + + // Draws the specified XFB buffer to the screen, performing any post-processing. + // Assumes that the backbuffer has already been bound and cleared. + virtual void RenderXFBToScreen(const MathUtil::Rectangle& target_rc, + const AbstractTexture* source_texture, + const MathUtil::Rectangle& source_rc); + + VideoCommon::PostProcessing* GetPostProcessor() const { return m_post_processor.get(); } + // Final surface changing + // This is called when the surface is resized (WX) or the window changes (Android). + void ChangeSurface(void* new_surface_handle); + void ResizeSurface(); + bool SurfaceResizedTestAndClear() { return m_surface_resized.TestAndClear(); } + bool SurfaceChangedTestAndClear() { return m_surface_changed.TestAndClear(); } + void* GetNewSurfaceHandle(); + + void SetKeyMap(const DolphinKeyMap& key_map); + + void SetKey(u32 key, bool is_down, const char* chars); + void SetMousePos(float x, float y); + void SetMousePress(u32 button_mask); + + int FrameCount() const { return m_frame_count; } + + void DoState(PointerWrap& p); + + const MathUtil::Rectangle& GetTargetRectangle() const { return m_target_rectangle; } + +private: + // Fetches the XFB texture from the texture cache. + // Returns true the contents have changed since last time + bool FetchXFB(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks); + + void ProcessFrameDumping(u64 ticks) const; + + std::tuple CalculateOutputDimensions(int width, int height) const; + std::tuple ApplyStandardAspectCrop(float width, float height) const; + std::tuple ScaleToDisplayAspectRatio(int width, int height) const; + + // Use this to convert a single target rectangle to two stereo rectangles + std::tuple, MathUtil::Rectangle> + ConvertStereoRectangle(const MathUtil::Rectangle& rc) const; + + std::mutex m_swap_mutex; + + // Backbuffer (window) size and render area + int m_backbuffer_width = 0; + int m_backbuffer_height = 0; + float m_backbuffer_scale = 1.0f; + AbstractTextureFormat m_backbuffer_format = AbstractTextureFormat::Undefined; + + void* m_new_surface_handle = nullptr; + Common::Flag m_surface_changed; + Common::Flag m_surface_resized; + + MathUtil::Rectangle m_target_rectangle = {}; + + RcTcacheEntry m_xfb_entry; + MathUtil::Rectangle m_xfb_rect; + + // Tracking of XFB textures so we don't render duplicate frames. + u64 m_last_xfb_id = std::numeric_limits::max(); + + // These will be set on the first call to SetWindowSize. + int m_last_window_request_width = 0; + int m_last_window_request_height = 0; + + std::unique_ptr m_post_processor; + std::unique_ptr m_onscreen_ui; + + u64 m_frame_count = 0; + u64 m_present_count = 0; + + // XFB tracking + u64 m_last_xfb_ticks = 0; + u32 m_last_xfb_addr = 0; + u32 m_last_xfb_width = MAX_XFB_WIDTH; + u32 m_last_xfb_stride = 0; + u32 m_last_xfb_height = MAX_XFB_HEIGHT; + + Common::EventHook m_config_changed; +}; + +} // namespace VideoCommon + +extern std::unique_ptr g_presenter; diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp index 209fa8293a..61820e0c82 100644 --- a/Source/Core/VideoCommon/RenderBase.cpp +++ b/Source/Core/VideoCommon/RenderBase.cpp @@ -14,247 +14,33 @@ #include "VideoCommon/RenderBase.h" #include -#include #include #include -#include -#include #include #include -#include -#include -#include "Common/Assert.h" -#include "Common/ChunkFile.h" -#include "Common/CommonTypes.h" -#include "Common/Config/Config.h" -#include "Common/FileUtil.h" -#include "Common/Flag.h" -#include "Common/Image.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" -#include "Common/Profiler.h" -#include "Common/StringUtil.h" -#include "Common/Thread.h" -#include "Common/Timer.h" -#include "Core/Config/GraphicsSettings.h" -#include "Core/Config/MainSettings.h" -#include "Core/Config/NetplaySettings.h" -#include "Core/Config/SYSCONFSettings.h" #include "Core/ConfigManager.h" -#include "Core/Core.h" -#include "Core/DolphinAnalytics.h" -#include "Core/FifoPlayer/FifoRecorder.h" -#include "Core/FreeLookConfig.h" -#include "Core/HW/SystemTimers.h" -#include "Core/HW/VideoInterface.h" -#include "Core/Host.h" -#include "Core/Movie.h" #include "Core/System.h" -#include "InputCommon/ControllerInterface/ControllerInterface.h" - -#include "VideoCommon/AbstractFramebuffer.h" -#include "VideoCommon/AbstractStagingTexture.h" -#include "VideoCommon/AbstractTexture.h" -#include "VideoCommon/BPFunctions.h" -#include "VideoCommon/BPMemory.h" -#include "VideoCommon/BoundingBox.h" -#include "VideoCommon/CPMemory.h" -#include "VideoCommon/CommandProcessor.h" -#include "VideoCommon/FrameDump.h" #include "VideoCommon/FramebufferManager.h" -#include "VideoCommon/FramebufferShaderGen.h" -#include "VideoCommon/FreeLookCamera.h" -#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h" -#include "VideoCommon/NetPlayChatUI.h" -#include "VideoCommon/NetPlayGolfUI.h" -#include "VideoCommon/OnScreenDisplay.h" -#include "VideoCommon/OpcodeDecoding.h" #include "VideoCommon/PixelEngine.h" -#include "VideoCommon/PixelShaderManager.h" -#include "VideoCommon/PostProcessing.h" -#include "VideoCommon/ShaderCache.h" -#include "VideoCommon/ShaderGenCommon.h" -#include "VideoCommon/Statistics.h" -#include "VideoCommon/TextureCacheBase.h" -#include "VideoCommon/TextureDecoder.h" -#include "VideoCommon/VertexLoaderManager.h" -#include "VideoCommon/VertexManagerBase.h" -#include "VideoCommon/VertexShaderManager.h" #include "VideoCommon/VideoBackendBase.h" #include "VideoCommon/VideoCommon.h" #include "VideoCommon/VideoConfig.h" -#include "VideoCommon/XFMemory.h" std::unique_ptr g_renderer; -static float AspectToWidescreen(float aspect) -{ - return aspect * ((16.0f / 9.0f) / (4.0f / 3.0f)); -} - -static bool DumpFrameToPNG(const FrameDump::FrameData& frame, const std::string& file_name) -{ - return Common::ConvertRGBAToRGBAndSavePNG(file_name, frame.data, frame.width, frame.height, - frame.stride, - Config::Get(Config::GFX_PNG_COMPRESSION_LEVEL)); -} - -Renderer::Renderer(int backbuffer_width, int backbuffer_height, float backbuffer_scale, - AbstractTextureFormat backbuffer_format) - : m_backbuffer_width(backbuffer_width), m_backbuffer_height(backbuffer_height), - m_backbuffer_scale(backbuffer_scale), - m_backbuffer_format(backbuffer_format), m_last_xfb_width{MAX_XFB_WIDTH}, m_last_xfb_height{ - MAX_XFB_HEIGHT} -{ - UpdateActiveConfig(); - FreeLook::UpdateActiveConfig(); - UpdateDrawRectangle(); - CalculateTargetSize(); - - m_is_game_widescreen = SConfig::GetInstance().bWii && Config::Get(Config::SYSCONF_WIDESCREEN); - g_freelook_camera.SetControlType(FreeLook::GetActiveConfig().camera_config.control_type); -} - Renderer::~Renderer() = default; -bool Renderer::Initialize() -{ - if (!InitializeImGui()) - return false; - - m_post_processor = std::make_unique(); - if (!m_post_processor->Initialize(m_backbuffer_format)) - return false; - - m_bounding_box = CreateBoundingBox(); - if (g_ActiveConfig.backend_info.bSupportsBBox && !m_bounding_box->Initialize()) - { - PanicAlertFmt("Failed to initialize bounding box."); - return false; - } - - if (g_ActiveConfig.bGraphicMods) - { - // If a config change occurred in a previous session, - // remember the old change count value. By setting - // our current change count to the old value, we - // avoid loading the stale data when we - // check for config changes. - const u32 old_game_mod_changes = g_ActiveConfig.graphics_mod_config ? - g_ActiveConfig.graphics_mod_config->GetChangeCount() : - 0; - g_ActiveConfig.graphics_mod_config = GraphicsModGroupConfig(SConfig::GetInstance().GetGameID()); - g_ActiveConfig.graphics_mod_config->Load(); - g_ActiveConfig.graphics_mod_config->SetChangeCount(old_game_mod_changes); - m_graphics_mod_manager.Load(*g_ActiveConfig.graphics_mod_config); - } - - return true; -} - -void Renderer::Shutdown() -{ - // Disable ControllerInterface's aspect ratio adjustments so mapping dialog behaves normally. - g_controller_interface.SetAspectRatioAdjustment(1); - - // First stop any framedumping, which might need to dump the last xfb frame. This process - // can require additional graphics sub-systems so it needs to be done first - ShutdownFrameDumping(); - ShutdownImGui(); - m_post_processor.reset(); - m_bounding_box.reset(); -} - -void Renderer::BeginUtilityDrawing() -{ - g_vertex_manager->Flush(); -} - -void Renderer::EndUtilityDrawing() -{ - // Reset framebuffer/scissor/viewport. Pipeline will be reset at next draw. - g_framebuffer_manager->BindEFBFramebuffer(); - BPFunctions::SetScissorAndViewport(); -} - -void Renderer::SetFramebuffer(AbstractFramebuffer* framebuffer) -{ - m_current_framebuffer = framebuffer; -} - -void Renderer::SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer) -{ - m_current_framebuffer = framebuffer; -} - -void Renderer::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, - const ClearColor& color_value, float depth_value) -{ - m_current_framebuffer = framebuffer; -} - -bool Renderer::EFBHasAlphaChannel() const -{ - return m_prev_efb_format == PixelFormat::RGBA6_Z24; -} - -void Renderer::ClearScreen(const MathUtil::Rectangle& rc, bool colorEnable, bool alphaEnable, - bool zEnable, u32 color, u32 z) -{ - g_framebuffer_manager->ClearEFB(rc, colorEnable, alphaEnable, zEnable, color, z); -} - void Renderer::ReinterpretPixelData(EFBReinterpretType convtype) { g_framebuffer_manager->ReinterpretPixelData(convtype); } -bool Renderer::IsBBoxEnabled() const -{ - return m_bounding_box->IsEnabled(); -} - -void Renderer::BBoxEnable(PixelShaderManager& pixel_shader_manager) -{ - m_bounding_box->Enable(pixel_shader_manager); -} - -void Renderer::BBoxDisable(PixelShaderManager& pixel_shader_manager) -{ - m_bounding_box->Disable(pixel_shader_manager); -} - -u16 Renderer::BBoxRead(u32 index) -{ - if (!g_ActiveConfig.bBBoxEnable || !g_ActiveConfig.backend_info.bSupportsBBox) - return m_bounding_box_fallback[index]; - - return m_bounding_box->Get(index); -} - -void Renderer::BBoxWrite(u32 index, u16 value) -{ - if (!g_ActiveConfig.bBBoxEnable || !g_ActiveConfig.backend_info.bSupportsBBox) - { - m_bounding_box_fallback[index] = value; - return; - } - - m_bounding_box->Set(index, value); -} - -void Renderer::BBoxFlush() -{ - if (!g_ActiveConfig.bBBoxEnable || !g_ActiveConfig.backend_info.bSupportsBBox) - return; - - m_bounding_box->Flush(); -} - u32 Renderer::AccessEFB(EFBAccessType type, u32 x, u32 y, u32 poke_data) { if (type == EFBAccessType::PeekColor) @@ -354,1530 +140,3 @@ void Renderer::PokeEFB(EFBAccessType type, const EfbPokeData* points, size_t num } } } - -void Renderer::RenderToXFB(u32 xfbAddr, const MathUtil::Rectangle& sourceRc, u32 fbStride, - u32 fbHeight, float Gamma) -{ - CheckFifoRecording(); - - if (!fbStride || !fbHeight) - return; -} - -unsigned int Renderer::GetEFBScale() const -{ - return m_efb_scale; -} - -int Renderer::EFBToScaledX(int x) const -{ - return x * static_cast(m_efb_scale); -} - -int Renderer::EFBToScaledY(int y) const -{ - return y * static_cast(m_efb_scale); -} - -float Renderer::EFBToScaledXf(float x) const -{ - return x * ((float)GetTargetWidth() / (float)EFB_WIDTH); -} - -float Renderer::EFBToScaledYf(float y) const -{ - return y * ((float)GetTargetHeight() / (float)EFB_HEIGHT); -} - -std::tuple Renderer::CalculateTargetScale(int x, int y) const -{ - return std::make_tuple(x * static_cast(m_efb_scale), y * static_cast(m_efb_scale)); -} - -// return true if target size changed -bool Renderer::CalculateTargetSize() -{ - if (g_ActiveConfig.iEFBScale == EFB_SCALE_AUTO_INTEGRAL) - { - // Set a scale based on the window size - int width = EFB_WIDTH * m_target_rectangle.GetWidth() / m_last_xfb_width; - int height = EFB_HEIGHT * m_target_rectangle.GetHeight() / m_last_xfb_height; - m_efb_scale = std::max((width - 1) / EFB_WIDTH + 1, (height - 1) / EFB_HEIGHT + 1); - } - else - { - m_efb_scale = g_ActiveConfig.iEFBScale; - } - - const u32 max_size = g_ActiveConfig.backend_info.MaxTextureSize; - if (max_size < EFB_WIDTH * m_efb_scale) - m_efb_scale = max_size / EFB_WIDTH; - - auto [new_efb_width, new_efb_height] = CalculateTargetScale(EFB_WIDTH, EFB_HEIGHT); - new_efb_width = std::max(new_efb_width, 1); - new_efb_height = std::max(new_efb_height, 1); - - if (new_efb_width != m_target_width || new_efb_height != m_target_height) - { - m_target_width = new_efb_width; - m_target_height = new_efb_height; - auto& system = Core::System::GetInstance(); - auto& pixel_shader_manager = system.GetPixelShaderManager(); - pixel_shader_manager.SetEfbScaleChanged(EFBToScaledXf(1), EFBToScaledYf(1)); - return true; - } - return false; -} - -std::tuple, MathUtil::Rectangle> -Renderer::ConvertStereoRectangle(const MathUtil::Rectangle& rc) const -{ - // Resize target to half its original size - auto draw_rc = rc; - if (g_ActiveConfig.stereo_mode == StereoMode::TAB) - { - // The height may be negative due to flipped rectangles - int height = rc.bottom - rc.top; - draw_rc.top += height / 4; - draw_rc.bottom -= height / 4; - } - else - { - int width = rc.right - rc.left; - draw_rc.left += width / 4; - draw_rc.right -= width / 4; - } - - // Create two target rectangle offset to the sides of the backbuffer - auto left_rc = draw_rc; - auto right_rc = draw_rc; - if (g_ActiveConfig.stereo_mode == StereoMode::TAB) - { - left_rc.top -= m_backbuffer_height / 4; - left_rc.bottom -= m_backbuffer_height / 4; - right_rc.top += m_backbuffer_height / 4; - right_rc.bottom += m_backbuffer_height / 4; - } - else - { - left_rc.left -= m_backbuffer_width / 4; - left_rc.right -= m_backbuffer_width / 4; - right_rc.left += m_backbuffer_width / 4; - right_rc.right += m_backbuffer_width / 4; - } - - return std::make_tuple(left_rc, right_rc); -} - -void Renderer::SaveScreenshot(std::string filename) -{ - std::lock_guard lk(m_screenshot_lock); - m_screenshot_name = std::move(filename); - m_screenshot_request.Set(); -} - -void Renderer::CheckForConfigChanges() -{ - const ShaderHostConfig old_shader_host_config = ShaderHostConfig::GetCurrent(); - const StereoMode old_stereo = g_ActiveConfig.stereo_mode; - const u32 old_multisamples = g_ActiveConfig.iMultisamples; - const int old_anisotropy = g_ActiveConfig.iMaxAnisotropy; - const int old_efb_access_tile_size = g_ActiveConfig.iEFBAccessTileSize; - const auto old_texture_filtering_mode = g_ActiveConfig.texture_filtering_mode; - const bool old_vsync = g_ActiveConfig.bVSyncActive; - const bool old_bbox = g_ActiveConfig.bBBoxEnable; - const u32 old_game_mod_changes = - g_ActiveConfig.graphics_mod_config ? g_ActiveConfig.graphics_mod_config->GetChangeCount() : 0; - const bool old_graphics_mods_enabled = g_ActiveConfig.bGraphicMods; - - UpdateActiveConfig(); - FreeLook::UpdateActiveConfig(); - g_vertex_manager->OnConfigChange(); - - g_freelook_camera.SetControlType(FreeLook::GetActiveConfig().camera_config.control_type); - - if (g_ActiveConfig.bGraphicMods && !old_graphics_mods_enabled) - { - g_ActiveConfig.graphics_mod_config = GraphicsModGroupConfig(SConfig::GetInstance().GetGameID()); - g_ActiveConfig.graphics_mod_config->Load(); - } - - if (g_ActiveConfig.graphics_mod_config && - (old_game_mod_changes != g_ActiveConfig.graphics_mod_config->GetChangeCount())) - { - m_graphics_mod_manager.Load(*g_ActiveConfig.graphics_mod_config); - } - - // Update texture cache settings with any changed options. - g_texture_cache->OnConfigChanged(g_ActiveConfig); - - // EFB tile cache doesn't need to notify the backend. - if (old_efb_access_tile_size != g_ActiveConfig.iEFBAccessTileSize) - g_framebuffer_manager->SetEFBCacheTileSize(std::max(g_ActiveConfig.iEFBAccessTileSize, 0)); - - // Check for post-processing shader changes. Done up here as it doesn't affect anything outside - // the post-processor. Note that options are applied every frame, so no need to check those. - if (m_post_processor->GetConfig()->GetShader() != g_ActiveConfig.sPostProcessingShader) - { - // The existing shader must not be in use when it's destroyed - WaitForGPUIdle(); - - m_post_processor->RecompileShader(); - } - - // Determine which (if any) settings have changed. - ShaderHostConfig new_host_config = ShaderHostConfig::GetCurrent(); - u32 changed_bits = 0; - if (old_shader_host_config.bits != new_host_config.bits) - changed_bits |= CONFIG_CHANGE_BIT_HOST_CONFIG; - if (old_stereo != g_ActiveConfig.stereo_mode) - changed_bits |= CONFIG_CHANGE_BIT_STEREO_MODE; - if (old_multisamples != g_ActiveConfig.iMultisamples) - changed_bits |= CONFIG_CHANGE_BIT_MULTISAMPLES; - if (old_anisotropy != g_ActiveConfig.iMaxAnisotropy) - changed_bits |= CONFIG_CHANGE_BIT_ANISOTROPY; - if (old_texture_filtering_mode != g_ActiveConfig.texture_filtering_mode) - changed_bits |= CONFIG_CHANGE_BIT_FORCE_TEXTURE_FILTERING; - if (old_vsync != g_ActiveConfig.bVSyncActive) - changed_bits |= CONFIG_CHANGE_BIT_VSYNC; - if (old_bbox != g_ActiveConfig.bBBoxEnable) - changed_bits |= CONFIG_CHANGE_BIT_BBOX; - if (CalculateTargetSize()) - changed_bits |= CONFIG_CHANGE_BIT_TARGET_SIZE; - - // No changes? - if (changed_bits == 0) - return; - - // Notify the backend of the changes, if any. - OnConfigChanged(changed_bits); - - // If there's any shader changes, wait for the GPU to finish before destroying anything. - if (changed_bits & (CONFIG_CHANGE_BIT_HOST_CONFIG | CONFIG_CHANGE_BIT_MULTISAMPLES)) - { - WaitForGPUIdle(); - SetPipeline(nullptr); - } - - // Framebuffer changed? - if (changed_bits & (CONFIG_CHANGE_BIT_MULTISAMPLES | CONFIG_CHANGE_BIT_STEREO_MODE | - CONFIG_CHANGE_BIT_TARGET_SIZE)) - { - g_framebuffer_manager->RecreateEFBFramebuffer(); - } - - // Reload shaders if host config has changed. - if (changed_bits & (CONFIG_CHANGE_BIT_HOST_CONFIG | CONFIG_CHANGE_BIT_MULTISAMPLES)) - { - OSD::AddMessage("Video config changed, reloading shaders.", OSD::Duration::NORMAL); - g_vertex_manager->InvalidatePipelineObject(); - g_shader_cache->SetHostConfig(new_host_config); - g_shader_cache->Reload(); - g_framebuffer_manager->RecompileShaders(); - } - - // Viewport and scissor rect have to be reset since they will be scaled differently. - if (changed_bits & CONFIG_CHANGE_BIT_TARGET_SIZE) - { - BPFunctions::SetScissorAndViewport(); - } - - // Stereo mode change requires recompiling our post processing pipeline and imgui pipelines for - // rendering the UI. - if (changed_bits & CONFIG_CHANGE_BIT_STEREO_MODE) - { - RecompileImGuiPipeline(); - m_post_processor->RecompilePipeline(); - } -} - -// Create On-Screen-Messages -void Renderer::DrawDebugText() -{ - const bool show_movie_window = - Config::Get(Config::MAIN_SHOW_FRAME_COUNT) || Config::Get(Config::MAIN_SHOW_LAG) || - Config::Get(Config::MAIN_MOVIE_SHOW_INPUT_DISPLAY) || - Config::Get(Config::MAIN_MOVIE_SHOW_RTC) || Config::Get(Config::MAIN_MOVIE_SHOW_RERECORD); - if (show_movie_window) - { - // Position under the FPS display. - ImGui::SetNextWindowPos( - ImVec2(ImGui::GetIO().DisplaySize.x - 10.f * m_backbuffer_scale, 80.f * m_backbuffer_scale), - ImGuiCond_FirstUseEver, ImVec2(1.0f, 0.0f)); - ImGui::SetNextWindowSizeConstraints( - ImVec2(150.0f * m_backbuffer_scale, 20.0f * m_backbuffer_scale), - ImGui::GetIO().DisplaySize); - if (ImGui::Begin("Movie", nullptr, ImGuiWindowFlags_NoFocusOnAppearing)) - { - if (Movie::IsPlayingInput()) - { - ImGui::Text("Frame: %" PRIu64 " / %" PRIu64, Movie::GetCurrentFrame(), - Movie::GetTotalFrames()); - ImGui::Text("Input: %" PRIu64 " / %" PRIu64, Movie::GetCurrentInputCount(), - Movie::GetTotalInputCount()); - } - else if (Config::Get(Config::MAIN_SHOW_FRAME_COUNT)) - { - ImGui::Text("Frame: %" PRIu64, Movie::GetCurrentFrame()); - ImGui::Text("Input: %" PRIu64, Movie::GetCurrentInputCount()); - } - if (Config::Get(Config::MAIN_SHOW_LAG)) - ImGui::Text("Lag: %" PRIu64 "\n", Movie::GetCurrentLagCount()); - if (Config::Get(Config::MAIN_MOVIE_SHOW_INPUT_DISPLAY)) - ImGui::TextUnformatted(Movie::GetInputDisplay().c_str()); - if (Config::Get(Config::MAIN_MOVIE_SHOW_RTC)) - ImGui::TextUnformatted(Movie::GetRTCDisplay().c_str()); - if (Config::Get(Config::MAIN_MOVIE_SHOW_RERECORD)) - ImGui::TextUnformatted(Movie::GetRerecords().c_str()); - } - ImGui::End(); - } - - if (g_ActiveConfig.bOverlayStats) - g_stats.Display(); - - if (g_ActiveConfig.bShowNetPlayMessages && g_netplay_chat_ui) - g_netplay_chat_ui->Display(); - - if (Config::Get(Config::NETPLAY_GOLF_MODE_OVERLAY) && g_netplay_golf_ui) - g_netplay_golf_ui->Display(); - - if (g_ActiveConfig.bOverlayProjStats) - g_stats.DisplayProj(); - - if (g_ActiveConfig.bOverlayScissorStats) - g_stats.DisplayScissor(); - - const std::string profile_output = Common::Profiler::ToString(); - if (!profile_output.empty()) - ImGui::TextUnformatted(profile_output.c_str()); -} - -float Renderer::CalculateDrawAspectRatio() const -{ - const auto aspect_mode = g_ActiveConfig.aspect_mode; - - // If stretch is enabled, we prefer the aspect ratio of the window. - if (aspect_mode == AspectMode::Stretch) - return (static_cast(m_backbuffer_width) / static_cast(m_backbuffer_height)); - - const float aspect_ratio = VideoInterface::GetAspectRatio(); - - if (aspect_mode == AspectMode::AnalogWide || - (aspect_mode == AspectMode::Auto && m_is_game_widescreen)) - { - return AspectToWidescreen(aspect_ratio); - } - - return aspect_ratio; -} - -void Renderer::AdjustRectanglesToFitBounds(MathUtil::Rectangle* target_rect, - MathUtil::Rectangle* source_rect, int fb_width, - int fb_height) -{ - const int orig_target_width = target_rect->GetWidth(); - const int orig_target_height = target_rect->GetHeight(); - const int orig_source_width = source_rect->GetWidth(); - const int orig_source_height = source_rect->GetHeight(); - if (target_rect->left < 0) - { - const int offset = -target_rect->left; - target_rect->left = 0; - source_rect->left += offset * orig_source_width / orig_target_width; - } - if (target_rect->right > fb_width) - { - const int offset = target_rect->right - fb_width; - target_rect->right -= offset; - source_rect->right -= offset * orig_source_width / orig_target_width; - } - if (target_rect->top < 0) - { - const int offset = -target_rect->top; - target_rect->top = 0; - source_rect->top += offset * orig_source_height / orig_target_height; - } - if (target_rect->bottom > fb_height) - { - const int offset = target_rect->bottom - fb_height; - target_rect->bottom -= offset; - source_rect->bottom -= offset * orig_source_height / orig_target_height; - } -} - -bool Renderer::IsHeadless() const -{ - return true; -} - -void Renderer::ChangeSurface(void* new_surface_handle) -{ - std::lock_guard lock(m_swap_mutex); - m_new_surface_handle = new_surface_handle; - m_surface_changed.Set(); -} - -void Renderer::ResizeSurface() -{ - std::lock_guard lock(m_swap_mutex); - m_surface_resized.Set(); -} - -void Renderer::SetViewportAndScissor(const MathUtil::Rectangle& rect, float min_depth, - float max_depth) -{ - SetViewport(static_cast(rect.left), static_cast(rect.top), - static_cast(rect.GetWidth()), static_cast(rect.GetHeight()), min_depth, - max_depth); - SetScissorRect(rect); -} - -void Renderer::ScaleTexture(AbstractFramebuffer* dst_framebuffer, - const MathUtil::Rectangle& dst_rect, - const AbstractTexture* src_texture, - const MathUtil::Rectangle& src_rect) -{ - ASSERT(dst_framebuffer->GetColorFormat() == AbstractTextureFormat::RGBA8); - - BeginUtilityDrawing(); - - // The shader needs to know the source rectangle. - const auto converted_src_rect = - ConvertFramebufferRectangle(src_rect, src_texture->GetWidth(), src_texture->GetHeight()); - const float rcp_src_width = 1.0f / src_texture->GetWidth(); - const float rcp_src_height = 1.0f / src_texture->GetHeight(); - const std::array uniforms = {{converted_src_rect.left * rcp_src_width, - converted_src_rect.top * rcp_src_height, - converted_src_rect.GetWidth() * rcp_src_width, - converted_src_rect.GetHeight() * rcp_src_height}}; - g_vertex_manager->UploadUtilityUniforms(&uniforms, sizeof(uniforms)); - - // Discard if we're overwriting the whole thing. - if (static_cast(dst_rect.GetWidth()) == dst_framebuffer->GetWidth() && - static_cast(dst_rect.GetHeight()) == dst_framebuffer->GetHeight()) - { - SetAndDiscardFramebuffer(dst_framebuffer); - } - else - { - SetFramebuffer(dst_framebuffer); - } - - SetViewportAndScissor(ConvertFramebufferRectangle(dst_rect, dst_framebuffer)); - SetPipeline(dst_framebuffer->GetLayers() > 1 ? g_shader_cache->GetRGBA8StereoCopyPipeline() : - g_shader_cache->GetRGBA8CopyPipeline()); - SetTexture(0, src_texture); - SetSamplerState(0, RenderState::GetLinearSamplerState()); - Draw(0, 3); - EndUtilityDrawing(); - if (dst_framebuffer->GetColorAttachment()) - dst_framebuffer->GetColorAttachment()->FinishedRendering(); -} - -MathUtil::Rectangle -Renderer::ConvertFramebufferRectangle(const MathUtil::Rectangle& rect, - const AbstractFramebuffer* framebuffer) const -{ - return ConvertFramebufferRectangle(rect, framebuffer->GetWidth(), framebuffer->GetHeight()); -} - -MathUtil::Rectangle Renderer::ConvertFramebufferRectangle(const MathUtil::Rectangle& rect, - u32 fb_width, u32 fb_height) const -{ - MathUtil::Rectangle ret = rect; - if (g_ActiveConfig.backend_info.bUsesLowerLeftOrigin) - { - ret.top = fb_height - rect.bottom; - ret.bottom = fb_height - rect.top; - } - return ret; -} - -MathUtil::Rectangle Renderer::ConvertEFBRectangle(const MathUtil::Rectangle& rc) const -{ - MathUtil::Rectangle result; - result.left = EFBToScaledX(rc.left); - result.top = EFBToScaledY(rc.top); - result.right = EFBToScaledX(rc.right); - result.bottom = EFBToScaledY(rc.bottom); - return result; -} - -std::tuple Renderer::ScaleToDisplayAspectRatio(const int width, - const int height) const -{ - // Scale either the width or height depending the content aspect ratio. - // This way we preserve as much resolution as possible when scaling. - float scaled_width = static_cast(width); - float scaled_height = static_cast(height); - const float draw_aspect = CalculateDrawAspectRatio(); - if (scaled_width / scaled_height >= draw_aspect) - scaled_height = scaled_width / draw_aspect; - else - scaled_width = scaled_height * draw_aspect; - return std::make_tuple(scaled_width, scaled_height); -} - -void Renderer::UpdateDrawRectangle() -{ - const float draw_aspect_ratio = CalculateDrawAspectRatio(); - - // Update aspect ratio hack values - // Won't take effect until next frame - // Don't know if there is a better place for this code so there isn't a 1 frame delay - if (g_ActiveConfig.bWidescreenHack) - { - float source_aspect = VideoInterface::GetAspectRatio(); - if (m_is_game_widescreen) - source_aspect = AspectToWidescreen(source_aspect); - - const float adjust = source_aspect / draw_aspect_ratio; - if (adjust > 1) - { - // Vert+ - g_Config.fAspectRatioHackW = 1; - g_Config.fAspectRatioHackH = 1 / adjust; - } - else - { - // Hor+ - g_Config.fAspectRatioHackW = adjust; - g_Config.fAspectRatioHackH = 1; - } - } - else - { - // Hack is disabled. - g_Config.fAspectRatioHackW = 1; - g_Config.fAspectRatioHackH = 1; - } - - // The rendering window size - const float win_width = static_cast(m_backbuffer_width); - const float win_height = static_cast(m_backbuffer_height); - - // FIXME: this breaks at very low widget sizes - // Make ControllerInterface aware of the render window region actually being used - // to adjust mouse cursor inputs. - g_controller_interface.SetAspectRatioAdjustment(draw_aspect_ratio / (win_width / win_height)); - - float draw_width = draw_aspect_ratio; - float draw_height = 1; - - // Crop the picture to a standard aspect ratio. (if enabled) - auto [crop_width, crop_height] = ApplyStandardAspectCrop(draw_width, draw_height); - - // scale the picture to fit the rendering window - if (win_width / win_height >= crop_width / crop_height) - { - // the window is flatter than the picture - draw_width *= win_height / crop_height; - crop_width *= win_height / crop_height; - draw_height *= win_height / crop_height; - crop_height = win_height; - } - else - { - // the window is skinnier than the picture - draw_width *= win_width / crop_width; - draw_height *= win_width / crop_width; - crop_height *= win_width / crop_width; - crop_width = win_width; - } - - // ensure divisibility by 4 to make it compatible with all the video encoders - draw_width = std::ceil(draw_width) - static_cast(std::ceil(draw_width)) % 4; - draw_height = std::ceil(draw_height) - static_cast(std::ceil(draw_height)) % 4; - - m_target_rectangle.left = static_cast(std::round(win_width / 2.0 - draw_width / 2.0)); - m_target_rectangle.top = static_cast(std::round(win_height / 2.0 - draw_height / 2.0)); - m_target_rectangle.right = m_target_rectangle.left + static_cast(draw_width); - m_target_rectangle.bottom = m_target_rectangle.top + static_cast(draw_height); -} - -void Renderer::SetWindowSize(int width, int height) -{ - const auto [out_width, out_height] = CalculateOutputDimensions(width, height); - - // Track the last values of width/height to avoid sending a window resize event every frame. - if (out_width == m_last_window_request_width && out_height == m_last_window_request_height) - return; - - m_last_window_request_width = out_width; - m_last_window_request_height = out_height; - Host_RequestRenderWindowSize(out_width, out_height); -} - -// Crop to exactly 16:9 or 4:3 if enabled and not AspectMode::Stretch. -std::tuple Renderer::ApplyStandardAspectCrop(float width, float height) const -{ - const auto aspect_mode = g_ActiveConfig.aspect_mode; - - if (!g_ActiveConfig.bCrop || aspect_mode == AspectMode::Stretch) - return {width, height}; - - // Force 4:3 or 16:9 by cropping the image. - const float current_aspect = width / height; - const float expected_aspect = (aspect_mode == AspectMode::AnalogWide || - (aspect_mode == AspectMode::Auto && m_is_game_widescreen)) ? - (16.0f / 9.0f) : - (4.0f / 3.0f); - if (current_aspect > expected_aspect) - { - // keep height, crop width - width = height * expected_aspect; - } - else - { - // keep width, crop height - height = width / expected_aspect; - } - - return {width, height}; -} - -std::tuple Renderer::CalculateOutputDimensions(int width, int height) const -{ - width = std::max(width, 1); - height = std::max(height, 1); - - auto [scaled_width, scaled_height] = ScaleToDisplayAspectRatio(width, height); - - // Apply crop if enabled. - std::tie(scaled_width, scaled_height) = ApplyStandardAspectCrop(scaled_width, scaled_height); - - width = static_cast(std::ceil(scaled_width)); - height = static_cast(std::ceil(scaled_height)); - - // UpdateDrawRectangle() makes sure that the rendered image is divisible by four for video - // encoders, so do that here too to match it - width -= width % 4; - height -= height % 4; - - return std::make_tuple(width, height); -} - -void Renderer::CheckFifoRecording() -{ - const bool was_recording = OpcodeDecoder::g_record_fifo_data; - OpcodeDecoder::g_record_fifo_data = FifoRecorder::GetInstance().IsRecording(); - - if (!OpcodeDecoder::g_record_fifo_data) - return; - - if (!was_recording) - { - RecordVideoMemory(); - } - - auto& system = Core::System::GetInstance(); - auto& command_processor = system.GetCommandProcessor(); - const auto& fifo = command_processor.GetFifo(); - FifoRecorder::GetInstance().EndFrame(fifo.CPBase.load(std::memory_order_relaxed), - fifo.CPEnd.load(std::memory_order_relaxed)); -} - -void Renderer::RecordVideoMemory() -{ - const u32* bpmem_ptr = reinterpret_cast(&bpmem); - u32 cpmem[256] = {}; - // The FIFO recording format splits XF memory into xfmem and xfregs; follow - // that split here. - const u32* xfmem_ptr = reinterpret_cast(&xfmem); - const u32* xfregs_ptr = reinterpret_cast(&xfmem) + FifoDataFile::XF_MEM_SIZE; - u32 xfregs_size = sizeof(XFMemory) / 4 - FifoDataFile::XF_MEM_SIZE; - - g_main_cp_state.FillCPMemoryArray(cpmem); - - FifoRecorder::GetInstance().SetVideoMemory(bpmem_ptr, cpmem, xfmem_ptr, xfregs_ptr, xfregs_size, - texMem); -} - -bool Renderer::InitializeImGui() -{ - std::unique_lock imgui_lock(m_imgui_mutex); - - if (!IMGUI_CHECKVERSION()) - { - PanicAlertFmt("ImGui version check failed"); - return false; - } - if (!ImGui::CreateContext()) - { - PanicAlertFmt("Creating ImGui context failed"); - return false; - } - if (!ImPlot::CreateContext()) - { - PanicAlertFmt("Creating ImPlot context failed"); - return false; - } - - // Don't create an ini file. TODO: Do we want this in the future? - ImGui::GetIO().IniFilename = nullptr; - ImGui::GetIO().DisplayFramebufferScale.x = m_backbuffer_scale; - ImGui::GetIO().DisplayFramebufferScale.y = m_backbuffer_scale; - ImGui::GetIO().FontGlobalScale = m_backbuffer_scale; - ImGui::GetStyle().ScaleAllSizes(m_backbuffer_scale); - ImGui::GetStyle().WindowRounding = 7.0f; - - PortableVertexDeclaration vdecl = {}; - vdecl.position = {ComponentFormat::Float, 2, offsetof(ImDrawVert, pos), true, false}; - vdecl.texcoords[0] = {ComponentFormat::Float, 2, offsetof(ImDrawVert, uv), true, false}; - vdecl.colors[0] = {ComponentFormat::UByte, 4, offsetof(ImDrawVert, col), true, false}; - vdecl.stride = sizeof(ImDrawVert); - m_imgui_vertex_format = CreateNativeVertexFormat(vdecl); - if (!m_imgui_vertex_format) - { - PanicAlertFmt("Failed to create ImGui vertex format"); - return false; - } - - // Font texture(s). - { - ImGuiIO& io = ImGui::GetIO(); - u8* font_tex_pixels; - int font_tex_width, font_tex_height; - io.Fonts->GetTexDataAsRGBA32(&font_tex_pixels, &font_tex_width, &font_tex_height); - - TextureConfig font_tex_config(font_tex_width, font_tex_height, 1, 1, 1, - AbstractTextureFormat::RGBA8, 0); - std::unique_ptr font_tex = - CreateTexture(font_tex_config, "ImGui font texture"); - if (!font_tex) - { - PanicAlertFmt("Failed to create ImGui texture"); - return false; - } - font_tex->Load(0, font_tex_width, font_tex_height, font_tex_width, font_tex_pixels, - sizeof(u32) * font_tex_width * font_tex_height); - - io.Fonts->TexID = font_tex.get(); - - m_imgui_textures.push_back(std::move(font_tex)); - } - - if (!RecompileImGuiPipeline()) - return false; - - m_imgui_last_frame_time = Common::Timer::NowUs(); - BeginImGuiFrameUnlocked(); // lock is already held - return true; -} - -bool Renderer::RecompileImGuiPipeline() -{ - if (m_backbuffer_format == AbstractTextureFormat::Undefined) - { - // No backbuffer (nogui) means no imgui rendering will happen - // Some backends don't like making pipelines with no render targets - return true; - } - - std::unique_ptr vertex_shader = - CreateShaderFromSource(ShaderStage::Vertex, FramebufferShaderGen::GenerateImGuiVertexShader(), - "ImGui vertex shader"); - std::unique_ptr pixel_shader = CreateShaderFromSource( - ShaderStage::Pixel, FramebufferShaderGen::GenerateImGuiPixelShader(), "ImGui pixel shader"); - if (!vertex_shader || !pixel_shader) - { - PanicAlertFmt("Failed to compile ImGui shaders"); - return false; - } - - // GS is used to render the UI to both eyes in stereo modes. - std::unique_ptr geometry_shader; - if (UseGeometryShaderForUI()) - { - geometry_shader = CreateShaderFromSource( - ShaderStage::Geometry, FramebufferShaderGen::GeneratePassthroughGeometryShader(1, 1), - "ImGui passthrough geometry shader"); - if (!geometry_shader) - { - PanicAlertFmt("Failed to compile ImGui geometry shader"); - return false; - } - } - - AbstractPipelineConfig pconfig = {}; - pconfig.vertex_format = m_imgui_vertex_format.get(); - pconfig.vertex_shader = vertex_shader.get(); - pconfig.geometry_shader = geometry_shader.get(); - pconfig.pixel_shader = pixel_shader.get(); - pconfig.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles); - pconfig.depth_state = RenderState::GetNoDepthTestingDepthState(); - pconfig.blending_state = RenderState::GetNoBlendingBlendState(); - pconfig.blending_state.blendenable = true; - pconfig.blending_state.srcfactor = SrcBlendFactor::SrcAlpha; - pconfig.blending_state.dstfactor = DstBlendFactor::InvSrcAlpha; - pconfig.blending_state.srcfactoralpha = SrcBlendFactor::Zero; - pconfig.blending_state.dstfactoralpha = DstBlendFactor::One; - pconfig.framebuffer_state.color_texture_format = m_backbuffer_format; - pconfig.framebuffer_state.depth_texture_format = AbstractTextureFormat::Undefined; - pconfig.framebuffer_state.samples = 1; - pconfig.framebuffer_state.per_sample_shading = false; - pconfig.usage = AbstractPipelineUsage::Utility; - m_imgui_pipeline = CreatePipeline(pconfig); - if (!m_imgui_pipeline) - { - PanicAlertFmt("Failed to create imgui pipeline"); - return false; - } - - return true; -} - -void Renderer::ShutdownImGui() -{ - std::unique_lock imgui_lock(m_imgui_mutex); - - ImGui::EndFrame(); - ImPlot::DestroyContext(); - ImGui::DestroyContext(); - m_imgui_pipeline.reset(); - m_imgui_vertex_format.reset(); - m_imgui_textures.clear(); -} - -void Renderer::BeginImGuiFrame() -{ - std::unique_lock imgui_lock(m_imgui_mutex); - BeginImGuiFrameUnlocked(); -} - -void Renderer::BeginImGuiFrameUnlocked() -{ - const u64 current_time_us = Common::Timer::NowUs(); - const u64 time_diff_us = current_time_us - m_imgui_last_frame_time; - const float time_diff_secs = static_cast(time_diff_us / 1000000.0); - m_imgui_last_frame_time = current_time_us; - - // Update I/O with window dimensions. - ImGuiIO& io = ImGui::GetIO(); - io.DisplaySize = - ImVec2(static_cast(m_backbuffer_width), static_cast(m_backbuffer_height)); - io.DeltaTime = time_diff_secs; - - ImGui::NewFrame(); -} - -void Renderer::DrawImGui() -{ - ImDrawData* draw_data = ImGui::GetDrawData(); - if (!draw_data) - return; - - SetViewport(0.0f, 0.0f, static_cast(m_backbuffer_width), - static_cast(m_backbuffer_height), 0.0f, 1.0f); - - // Uniform buffer for draws. - struct ImGuiUbo - { - float u_rcp_viewport_size_mul2[2]; - float padding[2]; - }; - ImGuiUbo ubo = {{1.0f / m_backbuffer_width * 2.0f, 1.0f / m_backbuffer_height * 2.0f}}; - - // Set up common state for drawing. - SetPipeline(m_imgui_pipeline.get()); - SetSamplerState(0, RenderState::GetPointSamplerState()); - g_vertex_manager->UploadUtilityUniforms(&ubo, sizeof(ubo)); - - for (int i = 0; i < draw_data->CmdListsCount; i++) - { - const ImDrawList* cmdlist = draw_data->CmdLists[i]; - if (cmdlist->VtxBuffer.empty() || cmdlist->IdxBuffer.empty()) - return; - - u32 base_vertex, base_index; - g_vertex_manager->UploadUtilityVertices(cmdlist->VtxBuffer.Data, sizeof(ImDrawVert), - cmdlist->VtxBuffer.Size, cmdlist->IdxBuffer.Data, - cmdlist->IdxBuffer.Size, &base_vertex, &base_index); - - for (const ImDrawCmd& cmd : cmdlist->CmdBuffer) - { - if (cmd.UserCallback) - { - cmd.UserCallback(cmdlist, &cmd); - continue; - } - - SetScissorRect(ConvertFramebufferRectangle( - MathUtil::Rectangle( - static_cast(cmd.ClipRect.x), static_cast(cmd.ClipRect.y), - static_cast(cmd.ClipRect.z), static_cast(cmd.ClipRect.w)), - m_current_framebuffer)); - SetTexture(0, reinterpret_cast(cmd.TextureId)); - DrawIndexed(base_index, cmd.ElemCount, base_vertex); - base_index += cmd.ElemCount; - } - } - - // Some capture software (such as OBS) hooks SwapBuffers and uses glBlitFramebuffer to copy our - // back buffer just before swap. Because glBlitFramebuffer honors the scissor test, the capture - // itself will be clipped to whatever bounds were last set by ImGui, resulting in a rather useless - // capture whenever any ImGui windows are open. We'll reset the scissor rectangle to the entire - // viewport here to avoid this problem. - SetScissorRect(ConvertFramebufferRectangle( - MathUtil::Rectangle(0, 0, m_backbuffer_width, m_backbuffer_height), - m_current_framebuffer)); -} - -bool Renderer::UseGeometryShaderForUI() const -{ - // OpenGL doesn't render to a 2-layer backbuffer like D3D/Vulkan for quad-buffered stereo, - // instead drawing twice and the eye selected by glDrawBuffer() (see - // OGL::Renderer::RenderXFBToScreen). - return g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer && - g_ActiveConfig.backend_info.api_type != APIType::OpenGL; -} - -std::unique_lock Renderer::GetImGuiLock() -{ - return std::unique_lock(m_imgui_mutex); -} - -void Renderer::BeginUIFrame() -{ - if (IsHeadless()) - return; - - BeginUtilityDrawing(); - BindBackbuffer({0.0f, 0.0f, 0.0f, 1.0f}); -} - -void Renderer::EndUIFrame() -{ - { - auto lock = GetImGuiLock(); - ImGui::Render(); - } - - if (!IsHeadless()) - { - DrawImGui(); - - std::lock_guard guard(m_swap_mutex); - PresentBackbuffer(); - EndUtilityDrawing(); - } - - BeginImGuiFrame(); -} - -void Renderer::ForceReloadTextures() -{ - m_force_reload_textures.Set(); -} - -// Heuristic to detect if a GameCube game is in 16:9 anamorphic widescreen mode. -void Renderer::UpdateWidescreenHeuristic() -{ - // VertexManager maintains no statistics in Wii mode. - if (SConfig::GetInstance().bWii) - return; - - const auto flush_statistics = g_vertex_manager->ResetFlushAspectRatioCount(); - - // If suggested_aspect_mode (GameINI) is configured don't use heuristic. - if (g_ActiveConfig.suggested_aspect_mode != AspectMode::Auto) - return; - - // If widescreen hack isn't active and aspect_mode (UI) is 4:3 or 16:9 don't use heuristic. - if (!g_ActiveConfig.bWidescreenHack && (g_ActiveConfig.aspect_mode == AspectMode::Analog || - g_ActiveConfig.aspect_mode == AspectMode::AnalogWide)) - return; - - // Modify the threshold based on which aspect ratio we're already using: - // If the game's in 4:3, it probably won't switch to anamorphic, and vice-versa. - static constexpr u32 TRANSITION_THRESHOLD = 3; - - const auto looks_normal = [](auto& counts) { - return counts.normal_vertex_count > counts.anamorphic_vertex_count * TRANSITION_THRESHOLD; - }; - const auto looks_anamorphic = [](auto& counts) { - return counts.anamorphic_vertex_count > counts.normal_vertex_count * TRANSITION_THRESHOLD; - }; - - const auto& persp = flush_statistics.perspective; - const auto& ortho = flush_statistics.orthographic; - - const auto ortho_looks_anamorphic = looks_anamorphic(ortho); - - if (looks_anamorphic(persp) || ortho_looks_anamorphic) - { - // If either perspective or orthographic projections look anamorphic, it's a safe bet. - m_is_game_widescreen = true; - } - else if (looks_normal(persp) || (m_was_orthographically_anamorphic && looks_normal(ortho))) - { - // Many widescreen games (or AR/GeckoCodes) use anamorphic perspective projections - // with NON-anamorphic orthographic projections. - // This can cause incorrect changes to 4:3 when perspective projections are temporarily not - // shown. e.g. Animal Crossing's inventory menu. - // Unless we were in a situation which was orthographically anamorphic - // we won't consider orthographic data for changes from 16:9 to 4:3. - m_is_game_widescreen = false; - } - - m_was_orthographically_anamorphic = ortho_looks_anamorphic; -} - -void Renderer::Swap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks) -{ - if (SConfig::GetInstance().bWii) - m_is_game_widescreen = Config::Get(Config::SYSCONF_WIDESCREEN); - - // suggested_aspect_mode overrides SYSCONF_WIDESCREEN - if (g_ActiveConfig.suggested_aspect_mode == AspectMode::Analog) - m_is_game_widescreen = false; - else if (g_ActiveConfig.suggested_aspect_mode == AspectMode::AnalogWide) - m_is_game_widescreen = true; - - // If widescreen hack is disabled override game's AR if UI is set to 4:3 or 16:9. - if (!g_ActiveConfig.bWidescreenHack) - { - const auto aspect_mode = g_ActiveConfig.aspect_mode; - if (aspect_mode == AspectMode::Analog) - m_is_game_widescreen = false; - else if (aspect_mode == AspectMode::AnalogWide) - m_is_game_widescreen = true; - } - - // Ensure the last frame was written to the dump. - // This is required even if frame dumping has stopped, since the frame dump is one frame - // behind the renderer. - FlushFrameDump(); - - if (g_ActiveConfig.bGraphicMods) - { - m_graphics_mod_manager.EndOfFrame(); - } - - g_framebuffer_manager->EndOfFrame(); - - if (xfb_addr && fb_width && fb_stride && fb_height) - { - // Get the current XFB from texture cache - MathUtil::Rectangle xfb_rect; - const auto* xfb_entry = - g_texture_cache->GetXFBTexture(xfb_addr, fb_width, fb_height, fb_stride, &xfb_rect); - const bool is_duplicate_frame = xfb_entry->id == m_last_xfb_id; - - if (xfb_entry && (!g_ActiveConfig.bSkipPresentingDuplicateXFBs || !is_duplicate_frame)) - { - m_last_xfb_id = xfb_entry->id; - - // Since we use the common pipelines here and draw vertices if a batch is currently being - // built by the vertex loader, we end up trampling over its pointer, as we share the buffer - // with the loader, and it has not been unmapped yet. Force a pipeline flush to avoid this. - g_vertex_manager->Flush(); - - // Render any UI elements to the draw list. - { - auto lock = GetImGuiLock(); - - g_perf_metrics.DrawImGuiStats(m_backbuffer_scale); - DrawDebugText(); - OSD::DrawMessages(); - ImGui::Render(); - } - - // Render the XFB to the screen. - BeginUtilityDrawing(); - if (!IsHeadless()) - { - BindBackbuffer({{0.0f, 0.0f, 0.0f, 1.0f}}); - - if (!is_duplicate_frame) - UpdateWidescreenHeuristic(); - - UpdateDrawRectangle(); - - // Adjust the source rectangle instead of using an oversized viewport to render the XFB. - auto render_target_rc = GetTargetRectangle(); - auto render_source_rc = xfb_rect; - AdjustRectanglesToFitBounds(&render_target_rc, &render_source_rc, m_backbuffer_width, - m_backbuffer_height); - RenderXFBToScreen(render_target_rc, xfb_entry->texture.get(), render_source_rc); - - DrawImGui(); - - // Present to the window system. - { - std::lock_guard guard(m_swap_mutex); - PresentBackbuffer(); - } - - // Update the window size based on the frame that was just rendered. - // Due to depending on guest state, we need to call this every frame. - SetWindowSize(xfb_rect.GetWidth(), xfb_rect.GetHeight()); - } - - if (!is_duplicate_frame) - { - DolphinAnalytics::PerformanceSample perf_sample; - perf_sample.speed_ratio = SystemTimers::GetEstimatedEmulationPerformance(); - perf_sample.num_prims = g_stats.this_frame.num_prims + g_stats.this_frame.num_dl_prims; - perf_sample.num_draw_calls = g_stats.this_frame.num_draw_calls; - DolphinAnalytics::Instance().ReportPerformanceInfo(std::move(perf_sample)); - - if (IsFrameDumping()) - DumpCurrentFrame(xfb_entry->texture.get(), xfb_rect, ticks, m_frame_count); - - // Begin new frame - m_frame_count++; - g_stats.ResetFrame(); - } - - g_shader_cache->RetrieveAsyncShaders(); - g_vertex_manager->OnEndFrame(); - BeginImGuiFrame(); - - // We invalidate the pipeline object at the start of the frame. - // This is for the rare case where only a single pipeline configuration is used, - // and hybrid ubershaders have compiled the specialized shader, but without any - // state changes the specialized shader will not take over. - g_vertex_manager->InvalidatePipelineObject(); - - if (m_force_reload_textures.TestAndClear()) - { - g_texture_cache->ForceReload(); - } - else - { - // Flush any outstanding EFB copies to RAM, in case the game is running at an uncapped frame - // rate and not waiting for vblank. Otherwise, we'd end up with a huge list of pending - // copies. - g_texture_cache->FlushEFBCopies(); - } - - if (!is_duplicate_frame) - { - // Remove stale EFB/XFB copies. - g_texture_cache->Cleanup(m_frame_count); - const double last_speed_denominator = g_perf_metrics.GetLastSpeedDenominator(); - // The denominator should always be > 0 but if it's not, just return 1 - const double last_speed = - last_speed_denominator > 0.0 ? (1.0 / last_speed_denominator) : 1.0; - Core::Callback_FramePresented(last_speed); - } - - // Handle any config changes, this gets propagated to the backend. - CheckForConfigChanges(); - g_Config.iSaveTargetId = 0; - - EndUtilityDrawing(); - } - else - { - Flush(); - } - - // Update our last xfb values - m_last_xfb_addr = xfb_addr; - m_last_xfb_ticks = ticks; - m_last_xfb_width = fb_width; - m_last_xfb_stride = fb_stride; - m_last_xfb_height = fb_height; - } - else - { - Flush(); - } -} - -void Renderer::RenderXFBToScreen(const MathUtil::Rectangle& target_rc, - const AbstractTexture* source_texture, - const MathUtil::Rectangle& source_rc) -{ - if (g_ActiveConfig.stereo_mode == StereoMode::SBS || - g_ActiveConfig.stereo_mode == StereoMode::TAB) - { - const auto [left_rc, right_rc] = ConvertStereoRectangle(target_rc); - - m_post_processor->BlitFromTexture(left_rc, source_rc, source_texture, 0); - m_post_processor->BlitFromTexture(right_rc, source_rc, source_texture, 1); - } - else - { - m_post_processor->BlitFromTexture(target_rc, source_rc, source_texture, 0); - } -} - -bool Renderer::IsFrameDumping() const -{ - if (m_screenshot_request.IsSet()) - return true; - - if (Config::Get(Config::MAIN_MOVIE_DUMP_FRAMES)) - return true; - - return false; -} - -void Renderer::DumpCurrentFrame(const AbstractTexture* src_texture, - const MathUtil::Rectangle& src_rect, u64 ticks, - int frame_number) -{ - int source_width = src_rect.GetWidth(); - int source_height = src_rect.GetHeight(); - int target_width, target_height; - if (!g_ActiveConfig.bInternalResolutionFrameDumps && !IsHeadless()) - { - auto target_rect = GetTargetRectangle(); - target_width = target_rect.GetWidth(); - target_height = target_rect.GetHeight(); - } - else - { - std::tie(target_width, target_height) = CalculateOutputDimensions(source_width, source_height); - } - - // We only need to render a copy if we need to stretch/scale the XFB copy. - MathUtil::Rectangle copy_rect = src_rect; - if (source_width != target_width || source_height != target_height) - { - if (!CheckFrameDumpRenderTexture(target_width, target_height)) - return; - - ScaleTexture(m_frame_dump_render_framebuffer.get(), m_frame_dump_render_framebuffer->GetRect(), - src_texture, src_rect); - src_texture = m_frame_dump_render_texture.get(); - copy_rect = src_texture->GetRect(); - } - - if (!CheckFrameDumpReadbackTexture(target_width, target_height)) - return; - - m_frame_dump_readback_texture->CopyFromTexture(src_texture, copy_rect, 0, 0, - m_frame_dump_readback_texture->GetRect()); - m_last_frame_state = m_frame_dump.FetchState(ticks, frame_number); - m_frame_dump_needs_flush = true; -} - -bool Renderer::CheckFrameDumpRenderTexture(u32 target_width, u32 target_height) -{ - // Ensure framebuffer exists (we lazily allocate it in case frame dumping isn't used). - // Or, resize texture if it isn't large enough to accommodate the current frame. - if (m_frame_dump_render_texture && m_frame_dump_render_texture->GetWidth() == target_width && - m_frame_dump_render_texture->GetHeight() == target_height) - { - return true; - } - - // Recreate texture, but release before creating so we don't temporarily use twice the RAM. - m_frame_dump_render_framebuffer.reset(); - m_frame_dump_render_texture.reset(); - m_frame_dump_render_texture = - CreateTexture(TextureConfig(target_width, target_height, 1, 1, 1, - AbstractTextureFormat::RGBA8, AbstractTextureFlag_RenderTarget), - "Frame dump render texture"); - if (!m_frame_dump_render_texture) - { - PanicAlertFmt("Failed to allocate frame dump render texture"); - return false; - } - m_frame_dump_render_framebuffer = CreateFramebuffer(m_frame_dump_render_texture.get(), nullptr); - ASSERT(m_frame_dump_render_framebuffer); - return true; -} - -bool Renderer::CheckFrameDumpReadbackTexture(u32 target_width, u32 target_height) -{ - std::unique_ptr& rbtex = m_frame_dump_readback_texture; - if (rbtex && rbtex->GetWidth() == target_width && rbtex->GetHeight() == target_height) - return true; - - rbtex.reset(); - rbtex = CreateStagingTexture( - StagingTextureType::Readback, - TextureConfig(target_width, target_height, 1, 1, 1, AbstractTextureFormat::RGBA8, 0)); - if (!rbtex) - return false; - - return true; -} - -void Renderer::FlushFrameDump() -{ - if (!m_frame_dump_needs_flush) - return; - - // Ensure dumping thread is done with output texture before swapping. - FinishFrameData(); - - std::swap(m_frame_dump_output_texture, m_frame_dump_readback_texture); - - // Queue encoding of the last frame dumped. - auto& output = m_frame_dump_output_texture; - output->Flush(); - if (output->Map()) - { - DumpFrameData(reinterpret_cast(output->GetMappedPointer()), output->GetConfig().width, - output->GetConfig().height, static_cast(output->GetMappedStride())); - } - else - { - ERROR_LOG_FMT(VIDEO, "Failed to map texture for dumping."); - } - - m_frame_dump_needs_flush = false; - - // Shutdown frame dumping if it is no longer active. - if (!IsFrameDumping()) - ShutdownFrameDumping(); -} - -void Renderer::ShutdownFrameDumping() -{ - // Ensure the last queued readback has been sent to the encoder. - FlushFrameDump(); - - if (!m_frame_dump_thread_running.IsSet()) - return; - - // Ensure previous frame has been encoded. - FinishFrameData(); - - // Wake thread up, and wait for it to exit. - m_frame_dump_thread_running.Clear(); - m_frame_dump_start.Set(); - if (m_frame_dump_thread.joinable()) - m_frame_dump_thread.join(); - m_frame_dump_render_framebuffer.reset(); - m_frame_dump_render_texture.reset(); - - m_frame_dump_readback_texture.reset(); - m_frame_dump_output_texture.reset(); -} - -void Renderer::DumpFrameData(const u8* data, int w, int h, int stride) -{ - m_frame_dump_data = FrameDump::FrameData{data, w, h, stride, m_last_frame_state}; - - if (!m_frame_dump_thread_running.IsSet()) - { - if (m_frame_dump_thread.joinable()) - m_frame_dump_thread.join(); - m_frame_dump_thread_running.Set(); - m_frame_dump_thread = std::thread(&Renderer::FrameDumpThreadFunc, this); - } - - // Wake worker thread up. - m_frame_dump_start.Set(); - m_frame_dump_frame_running = true; -} - -void Renderer::FinishFrameData() -{ - if (!m_frame_dump_frame_running) - return; - - m_frame_dump_done.Wait(); - m_frame_dump_frame_running = false; - - m_frame_dump_output_texture->Unmap(); -} - -void Renderer::FrameDumpThreadFunc() -{ - Common::SetCurrentThreadName("FrameDumping"); - - bool dump_to_ffmpeg = !g_ActiveConfig.bDumpFramesAsImages; - bool frame_dump_started = false; - -// If Dolphin was compiled without ffmpeg, we only support dumping to images. -#if !defined(HAVE_FFMPEG) - if (dump_to_ffmpeg) - { - WARN_LOG_FMT(VIDEO, "FrameDump: Dolphin was not compiled with FFmpeg, using fallback option. " - "Frames will be saved as PNG images instead."); - dump_to_ffmpeg = false; - } -#endif - - while (true) - { - m_frame_dump_start.Wait(); - if (!m_frame_dump_thread_running.IsSet()) - break; - - auto frame = m_frame_dump_data; - - // Save screenshot - if (m_screenshot_request.TestAndClear()) - { - std::lock_guard lk(m_screenshot_lock); - - if (DumpFrameToPNG(frame, m_screenshot_name)) - OSD::AddMessage("Screenshot saved to " + m_screenshot_name); - - // Reset settings - m_screenshot_name.clear(); - m_screenshot_completed.Set(); - } - - if (Config::Get(Config::MAIN_MOVIE_DUMP_FRAMES)) - { - if (!frame_dump_started) - { - if (dump_to_ffmpeg) - frame_dump_started = StartFrameDumpToFFMPEG(frame); - else - frame_dump_started = StartFrameDumpToImage(frame); - - // Stop frame dumping if we fail to start. - if (!frame_dump_started) - Config::SetCurrent(Config::MAIN_MOVIE_DUMP_FRAMES, false); - } - - // If we failed to start frame dumping, don't write a frame. - if (frame_dump_started) - { - if (dump_to_ffmpeg) - DumpFrameToFFMPEG(frame); - else - DumpFrameToImage(frame); - } - } - - m_frame_dump_done.Set(); - } - - if (frame_dump_started) - { - // No additional cleanup is needed when dumping to images. - if (dump_to_ffmpeg) - StopFrameDumpToFFMPEG(); - } -} - -#if defined(HAVE_FFMPEG) - -bool Renderer::StartFrameDumpToFFMPEG(const FrameDump::FrameData& frame) -{ - // If dumping started at boot, the start time must be set to the boot time to maintain audio sync. - // TODO: Perhaps we should care about this when starting dumping in the middle of emulation too, - // but it's less important there since the first frame to dump usually gets delivered quickly. - const u64 start_ticks = frame.state.frame_number == 0 ? 0 : frame.state.ticks; - return m_frame_dump.Start(frame.width, frame.height, start_ticks); -} - -void Renderer::DumpFrameToFFMPEG(const FrameDump::FrameData& frame) -{ - m_frame_dump.AddFrame(frame); -} - -void Renderer::StopFrameDumpToFFMPEG() -{ - m_frame_dump.Stop(); -} - -#else - -bool Renderer::StartFrameDumpToFFMPEG(const FrameDump::FrameData&) -{ - return false; -} - -void Renderer::DumpFrameToFFMPEG(const FrameDump::FrameData&) -{ -} - -void Renderer::StopFrameDumpToFFMPEG() -{ -} - -#endif // defined(HAVE_FFMPEG) - -std::string Renderer::GetFrameDumpNextImageFileName() const -{ - return fmt::format("{}framedump_{}.png", File::GetUserPath(D_DUMPFRAMES_IDX), - m_frame_dump_image_counter); -} - -bool Renderer::StartFrameDumpToImage(const FrameDump::FrameData&) -{ - m_frame_dump_image_counter = 1; - if (!Config::Get(Config::MAIN_MOVIE_DUMP_FRAMES_SILENT)) - { - // Only check for the presence of the first image to confirm overwriting. - // A previous run will always have at least one image, and it's safe to assume that if the user - // has allowed the first image to be overwritten, this will apply any remaining images as well. - std::string filename = GetFrameDumpNextImageFileName(); - if (File::Exists(filename)) - { - if (!AskYesNoFmtT("Frame dump image(s) '{0}' already exists. Overwrite?", filename)) - return false; - } - } - - return true; -} - -void Renderer::DumpFrameToImage(const FrameDump::FrameData& frame) -{ - DumpFrameToPNG(frame, GetFrameDumpNextImageFileName()); - m_frame_dump_image_counter++; -} - -bool Renderer::UseVertexDepthRange() const -{ - // We can't compute the depth range in the vertex shader if we don't support depth clamp. - if (!g_ActiveConfig.backend_info.bSupportsDepthClamp) - return false; - - // We need a full depth range if a ztexture is used. - if (bpmem.ztex2.op != ZTexOp::Disabled && !bpmem.zcontrol.early_ztest) - return true; - - // If an inverted depth range is unsupported, we also need to check if the range is inverted. - if (!g_ActiveConfig.backend_info.bSupportsReversedDepthRange && xfmem.viewport.zRange < 0.0f) - return true; - - // If an oversized depth range or a ztexture is used, we need to calculate the depth range - // in the vertex shader. - return fabs(xfmem.viewport.zRange) > 16777215.0f || fabs(xfmem.viewport.farZ) > 16777215.0f; -} - -void Renderer::DoState(PointerWrap& p) -{ - p.Do(m_is_game_widescreen); - p.Do(m_frame_count); - p.Do(m_prev_efb_format); - p.Do(m_last_xfb_ticks); - p.Do(m_last_xfb_addr); - p.Do(m_last_xfb_width); - p.Do(m_last_xfb_stride); - p.Do(m_last_xfb_height); - p.DoArray(m_bounding_box_fallback); - - m_bounding_box->DoState(p); - - if (p.IsReadMode()) - { - // Force the next xfb to be displayed. - m_last_xfb_id = std::numeric_limits::max(); - - m_was_orthographically_anamorphic = false; - - // And actually display it. - Swap(m_last_xfb_addr, m_last_xfb_width, m_last_xfb_stride, m_last_xfb_height, m_last_xfb_ticks); - } - -#if defined(HAVE_FFMPEG) - m_frame_dump.DoState(p); -#endif -} - -std::unique_ptr Renderer::CreateAsyncShaderCompiler() -{ - return std::make_unique(); -} - -const GraphicsModManager& Renderer::GetGraphicsModManager() const -{ - return m_graphics_mod_manager; -} diff --git a/Source/Core/VideoCommon/RenderBase.h b/Source/Core/VideoCommon/RenderBase.h index 853a840f39..dc48b1945d 100644 --- a/Source/Core/VideoCommon/RenderBase.h +++ b/Source/Core/VideoCommon/RenderBase.h @@ -1,63 +1,14 @@ // Copyright 2010 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -// --------------------------------------------------------------------------------------------- -// GC graphics pipeline -// --------------------------------------------------------------------------------------------- -// 3d commands are issued through the fifo. The GPU draws to the 2MB EFB. -// The efb can be copied back into ram in two forms: as textures or as XFB. -// The XFB is the region in RAM that the VI chip scans out to the television. -// So, after all rendering to EFB is done, the image is copied into one of two XFBs in RAM. -// Next frame, that one is scanned out and the other one gets the copy. = double buffering. -// --------------------------------------------------------------------------------------------- - #pragma once -#include #include -#include -#include -#include -#include -#include -#include #include "Common/CommonTypes.h" -#include "Common/Event.h" -#include "Common/Flag.h" -#include "Common/MathUtil.h" -#include "VideoCommon/AsyncShaderCompiler.h" -#include "VideoCommon/BPMemory.h" -#include "VideoCommon/FrameDump.h" -#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h" -#include "VideoCommon/PerformanceMetrics.h" -#include "VideoCommon/RenderState.h" -#include "VideoCommon/TextureConfig.h" -class AbstractFramebuffer; -class AbstractPipeline; -class AbstractShader; -class AbstractTexture; -class AbstractStagingTexture; -class BoundingBox; -class NativeVertexFormat; -class NetPlayChatUI; -class PixelShaderManager; -class PointerWrap; -struct TextureConfig; -struct ComputePipelineConfig; -struct AbstractPipelineConfig; -struct PortableVertexDeclaration; -enum class ShaderStage; enum class EFBAccessType; enum class EFBReinterpretType; -enum class StagingTextureType; -enum class AspectMode; - -namespace VideoCommon -{ -class PostProcessing; -} // namespace VideoCommon struct EfbPokeData { @@ -66,394 +17,18 @@ struct EfbPokeData }; // Renderer really isn't a very good name for this class - it's more like "Misc". -// The long term goal is to get rid of this class and replace it with others that make -// more sense. +// It used to be a massive mess, but almost everything has been refactored out. +// +// All that's left is a thin abstraction layer for VideoSoftware to intercept EFB accesses. class Renderer { public: - Renderer(int backbuffer_width, int backbuffer_height, float backbuffer_scale, - AbstractTextureFormat backbuffer_format); virtual ~Renderer(); - using ClearColor = std::array; - - virtual bool IsHeadless() const = 0; - - virtual bool Initialize(); - virtual void Shutdown(); - - virtual void SetPipeline(const AbstractPipeline* pipeline) {} - virtual void SetScissorRect(const MathUtil::Rectangle& rc) {} - virtual void SetTexture(u32 index, const AbstractTexture* texture) {} - virtual void SetSamplerState(u32 index, const SamplerState& state) {} - virtual void SetComputeImageTexture(AbstractTexture* texture, bool read, bool write) {} - virtual void UnbindTexture(const AbstractTexture* texture) {} - virtual void SetViewport(float x, float y, float width, float height, float near_depth, - float far_depth) - { - } - virtual void SetFullscreen(bool enable_fullscreen) {} - virtual bool IsFullscreen() const { return false; } - virtual void BeginUtilityDrawing(); - virtual void EndUtilityDrawing(); - virtual std::unique_ptr CreateTexture(const TextureConfig& config, - std::string_view name = "") = 0; - virtual std::unique_ptr - CreateStagingTexture(StagingTextureType type, const TextureConfig& config) = 0; - virtual std::unique_ptr - CreateFramebuffer(AbstractTexture* color_attachment, AbstractTexture* depth_attachment) = 0; - - // Framebuffer operations. - virtual void SetFramebuffer(AbstractFramebuffer* framebuffer); - virtual void SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer); - virtual void SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, - const ClearColor& color_value = {}, float depth_value = 0.0f); - - // Drawing with currently-bound pipeline state. - virtual void Draw(u32 base_vertex, u32 num_vertices) {} - virtual void DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) {} - - // Dispatching compute shaders with currently-bound state. - virtual void DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u32 groupsize_y, - u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z) - { - } - - // Binds the backbuffer for rendering. The buffer will be cleared immediately after binding. - // This is where any window size changes are detected, therefore m_backbuffer_width and/or - // m_backbuffer_height may change after this function returns. - virtual void BindBackbuffer(const ClearColor& clear_color = {}) {} - - // Presents the backbuffer to the window system, or "swaps buffers". - virtual void PresentBackbuffer() {} - - // Shader modules/objects. - virtual std::unique_ptr CreateShaderFromSource(ShaderStage stage, - std::string_view source, - std::string_view name = "") = 0; - virtual std::unique_ptr CreateShaderFromBinary(ShaderStage stage, - const void* data, size_t length, - std::string_view name = "") = 0; - virtual std::unique_ptr - CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) = 0; - virtual std::unique_ptr CreatePipeline(const AbstractPipelineConfig& config, - const void* cache_data = nullptr, - size_t cache_data_length = 0) = 0; - - AbstractFramebuffer* GetCurrentFramebuffer() const { return m_current_framebuffer; } - - // 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. - void SetViewportAndScissor(const MathUtil::Rectangle& rect, float min_depth = 0.0f, - float max_depth = 1.0f); - - // Scales a GPU texture using a copy shader. - virtual void ScaleTexture(AbstractFramebuffer* dst_framebuffer, - const MathUtil::Rectangle& dst_rect, - const AbstractTexture* src_texture, - const MathUtil::Rectangle& src_rect); - - // Converts an upper-left to lower-left if required by the backend, optionally - // clamping to the framebuffer size. - MathUtil::Rectangle ConvertFramebufferRectangle(const MathUtil::Rectangle& rect, - u32 fb_width, u32 fb_height) const; - MathUtil::Rectangle - ConvertFramebufferRectangle(const MathUtil::Rectangle& rect, - const AbstractFramebuffer* framebuffer) const; - - // EFB coordinate conversion functions - // Use this to convert a whole native EFB rect to backbuffer coordinates - MathUtil::Rectangle ConvertEFBRectangle(const MathUtil::Rectangle& rc) const; - - const MathUtil::Rectangle& GetTargetRectangle() const { return m_target_rectangle; } - float CalculateDrawAspectRatio() const; - - // Crops the target rectangle to the framebuffer dimensions, reducing the size of the source - // rectangle if it is greater. Works even if the source and target rectangles don't have a - // 1:1 pixel mapping, scaling as appropriate. - void AdjustRectanglesToFitBounds(MathUtil::Rectangle* target_rect, - MathUtil::Rectangle* source_rect, int fb_width, - int fb_height); - - std::tuple ScaleToDisplayAspectRatio(int width, int height) const; - void UpdateDrawRectangle(); - - std::tuple ApplyStandardAspectCrop(float width, float height) const; - - // Use this to convert a single target rectangle to two stereo rectangles - std::tuple, MathUtil::Rectangle> - ConvertStereoRectangle(const MathUtil::Rectangle& rc) const; - - unsigned int GetEFBScale() const; - - // Use this to upscale native EFB coordinates to IDEAL internal resolution - int EFBToScaledX(int x) const; - int EFBToScaledY(int y) const; - - // Floating point versions of the above - only use them if really necessary - float EFBToScaledXf(float x) const; - float EFBToScaledYf(float y) const; - - // Random utilities - void SaveScreenshot(std::string filename); - void DrawDebugText(); - - virtual void ClearScreen(const MathUtil::Rectangle& rc, bool colorEnable, bool alphaEnable, - bool zEnable, u32 color, u32 z); virtual void ReinterpretPixelData(EFBReinterpretType convtype); - void RenderToXFB(u32 xfbAddr, const MathUtil::Rectangle& sourceRc, u32 fbStride, - u32 fbHeight, float Gamma = 1.0f); virtual u32 AccessEFB(EFBAccessType type, u32 x, u32 y, u32 poke_data); virtual void PokeEFB(EFBAccessType type, const EfbPokeData* points, size_t num_points); - - bool IsBBoxEnabled() const; - void BBoxEnable(PixelShaderManager& pixel_shader_manager); - void BBoxDisable(PixelShaderManager& pixel_shader_manager); - u16 BBoxRead(u32 index); - void BBoxWrite(u32 index, u16 value); - void BBoxFlush(); - - virtual void Flush() {} - virtual void WaitForGPUIdle() {} - - // Finish up the current frame, print some stats - void Swap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks); - - void UpdateWidescreenHeuristic(); - - // Draws the specified XFB buffer to the screen, performing any post-processing. - // Assumes that the backbuffer has already been bound and cleared. - virtual void RenderXFBToScreen(const MathUtil::Rectangle& target_rc, - const AbstractTexture* source_texture, - const MathUtil::Rectangle& source_rc); - - // Called when the configuration changes, and backend structures need to be updated. - virtual void OnConfigChanged(u32 bits) {} - - 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); - - virtual std::unique_ptr CreateAsyncShaderCompiler(); - - // Returns true if a layer-expanding geometry shader should be used when rendering the user - // interface and final XFB. - bool UseGeometryShaderForUI() const; - - // Returns a lock for the ImGui mutex, enabling data structures to be modified from outside. - // Use with care, only non-drawing functions should be called from outside the video thread, - // as the drawing is tied to a "frame". - std::unique_lock GetImGuiLock(); - - // Begins/presents a "UI frame". UI frames do not draw any of the console XFB, but this could - // change in the future. - void BeginUIFrame(); - void EndUIFrame(); - - // Will forcibly reload all textures on the next swap - void ForceReloadTextures(); - - const GraphicsModManager& GetGraphicsModManager() const; - -protected: - // Bitmask containing information about which configuration has changed for the backend. - enum ConfigChangeBits : u32 - { - CONFIG_CHANGE_BIT_HOST_CONFIG = (1 << 0), - CONFIG_CHANGE_BIT_MULTISAMPLES = (1 << 1), - CONFIG_CHANGE_BIT_STEREO_MODE = (1 << 2), - CONFIG_CHANGE_BIT_TARGET_SIZE = (1 << 3), - CONFIG_CHANGE_BIT_ANISOTROPY = (1 << 4), - CONFIG_CHANGE_BIT_FORCE_TEXTURE_FILTERING = (1 << 5), - CONFIG_CHANGE_BIT_VSYNC = (1 << 6), - CONFIG_CHANGE_BIT_BBOX = (1 << 7) - }; - - std::tuple CalculateTargetScale(int x, int y) const; - bool CalculateTargetSize(); - - void CheckForConfigChanges(); - - void CheckFifoRecording(); - void RecordVideoMemory(); - - // ImGui initialization depends on being able to create textures and pipelines, so do it last. - bool InitializeImGui(); - - // Recompiles ImGui pipeline - call when stereo mode changes. - bool RecompileImGuiPipeline(); - - // Sets up ImGui state for the next frame. - // This function itself acquires the ImGui lock, so it should not be held. - void BeginImGuiFrame(); - - // Same as above but without locking the ImGui lock. - void BeginImGuiFrameUnlocked(); - - // Destroys all ImGui GPU resources, must do before shutdown. - void ShutdownImGui(); - - // Renders ImGui windows to the currently-bound framebuffer. - // Should be called with the ImGui lock held. - void DrawImGui(); - - virtual std::unique_ptr CreateBoundingBox() const = 0; - - AbstractFramebuffer* m_current_framebuffer = nullptr; - const AbstractPipeline* m_current_pipeline = nullptr; - - Common::Flag m_screenshot_request; - Common::Event m_screenshot_completed; - std::mutex m_screenshot_lock; - std::string m_screenshot_name; - - bool m_is_game_widescreen = false; - bool m_was_orthographically_anamorphic = false; - - // The framebuffer size - int m_target_width = 1; - int m_target_height = 1; - - // Backbuffer (window) size and render area - int m_backbuffer_width = 0; - int m_backbuffer_height = 0; - float m_backbuffer_scale = 1.0f; - AbstractTextureFormat m_backbuffer_format = AbstractTextureFormat::Undefined; - MathUtil::Rectangle m_target_rectangle = {}; - int m_frame_count = 0; - - std::unique_ptr m_post_processor; - - void* m_new_surface_handle = nullptr; - Common::Flag m_surface_changed; - Common::Flag m_surface_resized; - std::mutex m_swap_mutex; - - // ImGui resources. - std::unique_ptr m_imgui_vertex_format; - std::vector> m_imgui_textures; - std::unique_ptr m_imgui_pipeline; - std::mutex m_imgui_mutex; - u64 m_imgui_last_frame_time; - -private: - std::tuple CalculateOutputDimensions(int width, int height) const; - - PixelFormat m_prev_efb_format = PixelFormat::INVALID_FMT; - unsigned int m_efb_scale = 1; - - // These will be set on the first call to SetWindowSize. - int m_last_window_request_width = 0; - int m_last_window_request_height = 0; - - // frame dumping: - FrameDump m_frame_dump; - std::thread m_frame_dump_thread; - Common::Flag m_frame_dump_thread_running; - - // Used to kick frame dump thread. - Common::Event m_frame_dump_start; - - // Set by frame dump thread on frame completion. - Common::Event m_frame_dump_done; - - // Holds emulation state during the last swap when dumping. - FrameDump::FrameState m_last_frame_state; - - // Communication of frame between video and dump threads. - FrameDump::FrameData m_frame_dump_data; - - // Texture used for screenshot/frame dumping - std::unique_ptr m_frame_dump_render_texture; - std::unique_ptr m_frame_dump_render_framebuffer; - - // Double buffer: - std::unique_ptr m_frame_dump_readback_texture; - std::unique_ptr m_frame_dump_output_texture; - // Set when readback texture holds a frame that needs to be dumped. - bool m_frame_dump_needs_flush = false; - // Set when thread is processing output texture. - bool m_frame_dump_frame_running = false; - - // Used to generate screenshot names. - u32 m_frame_dump_image_counter = 0; - - // Tracking of XFB textures so we don't render duplicate frames. - u64 m_last_xfb_id = std::numeric_limits::max(); - u64 m_last_xfb_ticks = 0; - u32 m_last_xfb_addr = 0; - u32 m_last_xfb_width = 0; - u32 m_last_xfb_stride = 0; - u32 m_last_xfb_height = 0; - - std::unique_ptr m_bounding_box; - - // Nintendo's SDK seems to write "default" bounding box values before every draw (1023 0 1023 0 - // are the only values encountered so far, which happen to be the extents allowed by the BP - // registers) to reset the registers for comparison in the pixel engine, and presumably to detect - // whether GX has updated the registers with real values. - // - // We can store these values when Bounding Box emulation is disabled and return them on read, - // which the game will interpret as "no pixels have been drawn" - // - // This produces much better results than just returning garbage, which can cause games like - // Ultimate Spider-Man to crash - std::array m_bounding_box_fallback = {}; - - // NOTE: The methods below are called on the framedumping thread. - void FrameDumpThreadFunc(); - bool StartFrameDumpToFFMPEG(const FrameDump::FrameData&); - void DumpFrameToFFMPEG(const FrameDump::FrameData&); - void StopFrameDumpToFFMPEG(); - std::string GetFrameDumpNextImageFileName() const; - bool StartFrameDumpToImage(const FrameDump::FrameData&); - void DumpFrameToImage(const FrameDump::FrameData&); - - void ShutdownFrameDumping(); - - bool IsFrameDumping() const; - - // Checks that the frame dump render texture exists and is the correct size. - bool CheckFrameDumpRenderTexture(u32 target_width, u32 target_height); - - // Checks that the frame dump readback texture exists and is the correct size. - bool CheckFrameDumpReadbackTexture(u32 target_width, u32 target_height); - - // Fills the frame dump staging texture with the current XFB texture. - void DumpCurrentFrame(const AbstractTexture* src_texture, - const MathUtil::Rectangle& src_rect, u64 ticks, int frame_number); - - // Asynchronously encodes the specified pointer of frame data to the frame dump. - void DumpFrameData(const u8* data, int w, int h, int stride); - - // Ensures all rendered frames are queued for encoding. - void FlushFrameDump(); - - // Ensures all encoded frames have been written to the output file. - void FinishFrameData(); - - std::unique_ptr m_netplay_chat_ui; - - Common::Flag m_force_reload_textures; - - GraphicsModManager m_graphics_mod_manager; }; extern std::unique_ptr g_renderer; diff --git a/Source/Core/VideoCommon/RenderState.cpp b/Source/Core/VideoCommon/RenderState.cpp index d04733349f..93359e13df 100644 --- a/Source/Core/VideoCommon/RenderState.cpp +++ b/Source/Core/VideoCommon/RenderState.cpp @@ -6,6 +6,7 @@ #include #include +#include "VideoCommon/BPMemory.h" #include "VideoCommon/TextureConfig.h" void RasterizationState::Generate(const BPMemory& bp, PrimitiveType primitive_type) diff --git a/Source/Core/VideoCommon/RenderState.h b/Source/Core/VideoCommon/RenderState.h index 4dc305638e..c0fc695aae 100644 --- a/Source/Core/VideoCommon/RenderState.h +++ b/Source/Core/VideoCommon/RenderState.h @@ -4,12 +4,22 @@ #pragma once #include "Common/BitField.h" +#include "Common/CommonTypes.h" -#include "VideoCommon/BPMemory.h" -#include "VideoCommon/BPStructs.h" +struct BPMemory; enum class AbstractTextureFormat : u32; +enum class CompareMode : u32; +enum class CullMode : u32; +enum class DstBlendFactor : u32; +enum class FilterMode : u32; +enum class LODType : u32; +enum class LogicOp : u32; +enum class PixelFormat : u32; +enum class SrcBlendFactor : u32; +enum class WrapMode : u32; + enum class PrimitiveType : u32 { Points, diff --git a/Source/Core/VideoCommon/ShaderCache.cpp b/Source/Core/VideoCommon/ShaderCache.cpp index 9b6b1afa9b..98c915a7ee 100644 --- a/Source/Core/VideoCommon/ShaderCache.cpp +++ b/Source/Core/VideoCommon/ShaderCache.cpp @@ -10,11 +10,12 @@ #include "Common/MsgHandler.h" #include "Core/ConfigManager.h" +#include "VideoCommon/AbstractGfx.h" #include "VideoCommon/ConstantManager.h" #include "VideoCommon/DriverDetails.h" #include "VideoCommon/FramebufferManager.h" #include "VideoCommon/FramebufferShaderGen.h" -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/Present.h" #include "VideoCommon/Statistics.h" #include "VideoCommon/VertexLoaderManager.h" #include "VideoCommon/VertexManagerBase.h" @@ -44,7 +45,9 @@ bool ShaderCache::Initialize() if (!CompileSharedPipelines()) return false; - m_async_shader_compiler = g_renderer->CreateAsyncShaderCompiler(); + m_async_shader_compiler = g_gfx->CreateAsyncShaderCompiler(); + m_frame_end_handler = + AfterFrameEvent::Register([this] { RetrieveAsyncShaders(); }, "RetreiveAsyncShaders"); return true; } @@ -121,7 +124,7 @@ const AbstractPipeline* ShaderCache::GetPipelineForUid(const GXPipelineUid& uid) std::unique_ptr pipeline; std::optional pipeline_config = GetGXPipelineConfig(uid); if (pipeline_config) - pipeline = g_renderer->CreatePipeline(*pipeline_config); + pipeline = g_gfx->CreatePipeline(*pipeline_config); if (g_ActiveConfig.bShaderCache && !exists_in_cache) AppendGXPipelineUID(uid); return InsertGXPipeline(uid, std::move(pipeline)); @@ -153,7 +156,7 @@ const AbstractPipeline* ShaderCache::GetUberPipelineForUid(const GXUberPipelineU std::unique_ptr pipeline; std::optional pipeline_config = GetGXPipelineConfig(uid); if (pipeline_config) - pipeline = g_renderer->CreatePipeline(*pipeline_config); + pipeline = g_gfx->CreatePipeline(*pipeline_config); return InsertGXUberPipeline(uid, std::move(pipeline)); } @@ -162,8 +165,6 @@ void ShaderCache::WaitForAsyncCompiler() bool running = true; constexpr auto update_ui_progress = [](size_t completed, size_t total) { - g_renderer->BeginUIFrame(); - const float center_x = ImGui::GetIO().DisplaySize.x * 0.5f; const float center_y = ImGui::GetIO().DisplaySize.y * 0.5f; const float scale = ImGui::GetIO().DisplayFramebufferScale.x; @@ -183,7 +184,7 @@ void ShaderCache::WaitForAsyncCompiler() } ImGui::End(); - g_renderer->EndUIFrame(); + g_presenter->Present(); }; while (running && @@ -194,9 +195,8 @@ void ShaderCache::WaitForAsyncCompiler() m_async_shader_compiler->RetrieveWorkItems(); } - // Just render nothing to clear the screen - g_renderer->BeginUIFrame(); - g_renderer->EndUIFrame(); + // An extra Present to clear the screen + g_presenter->Present(); } template @@ -234,7 +234,7 @@ void ShaderCache::LoadShaderCache(T& cache, APIType api_type, const char* type, CacheReader(T& cache_) : cache(cache_) {} void Read(const K& key, const u8* value, u32 value_size) { - auto shader = g_renderer->CreateShaderFromBinary(stage, value, value_size); + auto shader = g_gfx->CreateShaderFromBinary(stage, value, value_size); if (shader) { auto& entry = cache.shader_map[key]; @@ -297,7 +297,7 @@ void ShaderCache::LoadPipelineCache(T& cache, LinearDiskCache& if (!config) return; - auto pipeline = g_renderer->CreatePipeline(*config, value, value_size); + auto pipeline = g_gfx->CreatePipeline(*config, value, value_size); if (!pipeline) { // If any of the pipelines fail to create, consider the cache stale. @@ -434,7 +434,7 @@ std::unique_ptr ShaderCache::CompileVertexShader(const VertexSha { const ShaderCode source_code = GenerateVertexShaderCode(m_api_type, m_host_config, uid.GetUidData()); - return g_renderer->CreateShaderFromSource(ShaderStage::Vertex, source_code.GetBuffer()); + return g_gfx->CreateShaderFromSource(ShaderStage::Vertex, source_code.GetBuffer()); } std::unique_ptr @@ -442,15 +442,15 @@ ShaderCache::CompileVertexUberShader(const UberShader::VertexShaderUid& uid) con { const ShaderCode source_code = UberShader::GenVertexShader(m_api_type, m_host_config, uid.GetUidData()); - return g_renderer->CreateShaderFromSource(ShaderStage::Vertex, source_code.GetBuffer(), - fmt::to_string(*uid.GetUidData())); + return g_gfx->CreateShaderFromSource(ShaderStage::Vertex, source_code.GetBuffer(), + fmt::to_string(*uid.GetUidData())); } std::unique_ptr ShaderCache::CompilePixelShader(const PixelShaderUid& uid) const { const ShaderCode source_code = GeneratePixelShaderCode(m_api_type, m_host_config, uid.GetUidData()); - return g_renderer->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer()); + return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer()); } std::unique_ptr @@ -458,8 +458,8 @@ ShaderCache::CompilePixelUberShader(const UberShader::PixelShaderUid& uid) const { const ShaderCode source_code = UberShader::GenPixelShader(m_api_type, m_host_config, uid.GetUidData()); - return g_renderer->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer(), - fmt::to_string(*uid.GetUidData())); + return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer(), + fmt::to_string(*uid.GetUidData())); } const AbstractShader* ShaderCache::InsertVertexShader(const VertexShaderUid& uid, @@ -555,8 +555,8 @@ const AbstractShader* ShaderCache::CreateGeometryShader(const GeometryShaderUid& const ShaderCode source_code = GenerateGeometryShaderCode(m_api_type, m_host_config, uid.GetUidData()); std::unique_ptr shader = - g_renderer->CreateShaderFromSource(ShaderStage::Geometry, source_code.GetBuffer(), - fmt::format("Geometry shader: {}", *uid.GetUidData())); + g_gfx->CreateShaderFromSource(ShaderStage::Geometry, source_code.GetBuffer(), + fmt::format("Geometry shader: {}", *uid.GetUidData())); auto& entry = m_gs_cache.shader_map[uid]; entry.pending = false; @@ -1176,7 +1176,7 @@ void ShaderCache::QueuePipelineCompile(const GXPipelineUid& uid, u32 priority) bool Compile() override { if (config) - pipeline = g_renderer->CreatePipeline(*config); + pipeline = g_gfx->CreatePipeline(*config); return true; } @@ -1251,7 +1251,7 @@ void ShaderCache::QueueUberPipelineCompile(const GXUberPipelineUid& uid, u32 pri bool Compile() override { if (config) - UberPipeline = g_renderer->CreatePipeline(*config); + UberPipeline = g_gfx->CreatePipeline(*config); return true; } @@ -1389,7 +1389,7 @@ ShaderCache::GetEFBCopyToVRAMPipeline(const TextureConversionShaderGen::TCShader return iter->second.get(); auto shader_code = TextureConversionShaderGen::GeneratePixelShader(m_api_type, uid.GetUidData()); - auto shader = g_renderer->CreateShaderFromSource( + auto shader = g_gfx->CreateShaderFromSource( ShaderStage::Pixel, shader_code.GetBuffer(), fmt::format("EFB copy to VRAM pixel shader: {}", *uid.GetUidData())); if (!shader) @@ -1409,7 +1409,7 @@ ShaderCache::GetEFBCopyToVRAMPipeline(const TextureConversionShaderGen::TCShader config.blending_state = RenderState::GetNoBlendingBlendState(); config.framebuffer_state = RenderState::GetRGBA8FramebufferState(); config.usage = AbstractPipelineUsage::Utility; - auto iiter = m_efb_copy_to_vram_pipelines.emplace(uid, g_renderer->CreatePipeline(config)); + auto iiter = m_efb_copy_to_vram_pipelines.emplace(uid, g_gfx->CreatePipeline(config)); return iiter.first->second.get(); } @@ -1421,7 +1421,7 @@ const AbstractPipeline* ShaderCache::GetEFBCopyToRAMPipeline(const EFBCopyParams const std::string shader_code = TextureConversionShaderTiled::GenerateEncodingShader(uid, m_api_type); - const auto shader = g_renderer->CreateShaderFromSource( + const auto shader = g_gfx->CreateShaderFromSource( ShaderStage::Pixel, shader_code, fmt::format("EFB copy to RAM pixel shader: {}", uid)); if (!shader) { @@ -1437,19 +1437,19 @@ const AbstractPipeline* ShaderCache::GetEFBCopyToRAMPipeline(const EFBCopyParams config.blending_state = RenderState::GetNoBlendingBlendState(); config.framebuffer_state = RenderState::GetColorFramebufferState(AbstractTextureFormat::BGRA8); config.usage = AbstractPipelineUsage::Utility; - auto iiter = m_efb_copy_to_ram_pipelines.emplace(uid, g_renderer->CreatePipeline(config)); + auto iiter = m_efb_copy_to_ram_pipelines.emplace(uid, g_gfx->CreatePipeline(config)); return iiter.first->second.get(); } bool ShaderCache::CompileSharedPipelines() { - m_screen_quad_vertex_shader = g_renderer->CreateShaderFromSource( + m_screen_quad_vertex_shader = g_gfx->CreateShaderFromSource( ShaderStage::Vertex, FramebufferShaderGen::GenerateScreenQuadVertexShader(), "Screen quad vertex shader"); - m_texture_copy_vertex_shader = g_renderer->CreateShaderFromSource( + m_texture_copy_vertex_shader = g_gfx->CreateShaderFromSource( ShaderStage::Vertex, FramebufferShaderGen::GenerateTextureCopyVertexShader(), "Texture copy vertex shader"); - m_efb_copy_vertex_shader = g_renderer->CreateShaderFromSource( + m_efb_copy_vertex_shader = g_gfx->CreateShaderFromSource( ShaderStage::Vertex, TextureConversionShaderGen::GenerateVertexShader(m_api_type).GetBuffer(), "EFB copy vertex shader"); if (!m_screen_quad_vertex_shader || !m_texture_copy_vertex_shader || !m_efb_copy_vertex_shader) @@ -1457,20 +1457,20 @@ bool ShaderCache::CompileSharedPipelines() if (UseGeometryShaderForEFBCopies()) { - m_texcoord_geometry_shader = g_renderer->CreateShaderFromSource( + m_texcoord_geometry_shader = g_gfx->CreateShaderFromSource( ShaderStage::Geometry, FramebufferShaderGen::GeneratePassthroughGeometryShader(1, 0), "Texcoord passthrough geometry shader"); - m_color_geometry_shader = g_renderer->CreateShaderFromSource( + m_color_geometry_shader = g_gfx->CreateShaderFromSource( ShaderStage::Geometry, FramebufferShaderGen::GeneratePassthroughGeometryShader(0, 1), "Color passthrough geometry shader"); if (!m_texcoord_geometry_shader || !m_color_geometry_shader) return false; } - m_texture_copy_pixel_shader = g_renderer->CreateShaderFromSource( + m_texture_copy_pixel_shader = g_gfx->CreateShaderFromSource( ShaderStage::Pixel, FramebufferShaderGen::GenerateTextureCopyPixelShader(), "Texture copy pixel shader"); - m_color_pixel_shader = g_renderer->CreateShaderFromSource( + m_color_pixel_shader = g_gfx->CreateShaderFromSource( ShaderStage::Pixel, FramebufferShaderGen::GenerateColorPixelShader(), "Color pixel shader"); if (!m_texture_copy_pixel_shader || !m_color_pixel_shader) return false; @@ -1485,14 +1485,14 @@ bool ShaderCache::CompileSharedPipelines() config.blending_state = RenderState::GetNoBlendingBlendState(); config.framebuffer_state = RenderState::GetRGBA8FramebufferState(); config.usage = AbstractPipelineUsage::Utility; - m_copy_rgba8_pipeline = g_renderer->CreatePipeline(config); + m_copy_rgba8_pipeline = g_gfx->CreatePipeline(config); if (!m_copy_rgba8_pipeline) return false; if (UseGeometryShaderForEFBCopies()) { config.geometry_shader = m_texcoord_geometry_shader.get(); - m_rgba8_stereo_copy_pipeline = g_renderer->CreatePipeline(config); + m_rgba8_stereo_copy_pipeline = g_gfx->CreatePipeline(config); if (!m_rgba8_stereo_copy_pipeline) return false; } @@ -1505,7 +1505,7 @@ bool ShaderCache::CompileSharedPipelines() for (size_t i = 0; i < NUM_PALETTE_CONVERSION_SHADERS; i++) { TLUTFormat format = static_cast(i); - auto shader = g_renderer->CreateShaderFromSource( + auto shader = g_gfx->CreateShaderFromSource( ShaderStage::Pixel, TextureConversionShaderTiled::GeneratePaletteConversionShader(format, m_api_type), fmt::format("Palette conversion pixel shader: {}", format)); @@ -1513,7 +1513,7 @@ bool ShaderCache::CompileSharedPipelines() return false; config.pixel_shader = shader.get(); - m_palette_conversion_pipelines[i] = g_renderer->CreatePipeline(config); + m_palette_conversion_pipelines[i] = g_gfx->CreatePipeline(config); if (!m_palette_conversion_pipelines[i]) return false; } @@ -1544,7 +1544,7 @@ const AbstractPipeline* ShaderCache::GetTextureReinterpretPipeline(TextureFormat return nullptr; } - std::unique_ptr shader = g_renderer->CreateShaderFromSource( + std::unique_ptr shader = g_gfx->CreateShaderFromSource( ShaderStage::Pixel, shader_source, fmt::format("Texture reinterpret pixel shader: {} to {}", from_format, to_format)); if (!shader) @@ -1563,7 +1563,7 @@ const AbstractPipeline* ShaderCache::GetTextureReinterpretPipeline(TextureFormat config.blending_state = RenderState::GetNoBlendingBlendState(); config.framebuffer_state = RenderState::GetRGBA8FramebufferState(); config.usage = AbstractPipelineUsage::Utility; - auto iiter = m_texture_reinterpret_pipelines.emplace(key, g_renderer->CreatePipeline(config)); + auto iiter = m_texture_reinterpret_pipelines.emplace(key, g_gfx->CreatePipeline(config)); return iiter.first->second.get(); } @@ -1591,7 +1591,7 @@ ShaderCache::GetTextureDecodingShader(TextureFormat format, fmt::format("Texture decoding compute shader: {}", format); std::unique_ptr shader = - g_renderer->CreateShaderFromSource(ShaderStage::Compute, shader_source, name); + g_gfx->CreateShaderFromSource(ShaderStage::Compute, shader_source, name); if (!shader) { m_texture_decoding_shaders.emplace(key, nullptr); diff --git a/Source/Core/VideoCommon/ShaderCache.h b/Source/Core/VideoCommon/ShaderCache.h index 2721cd7319..809ebb23eb 100644 --- a/Source/Core/VideoCommon/ShaderCache.h +++ b/Source/Core/VideoCommon/ShaderCache.h @@ -30,6 +30,7 @@ #include "VideoCommon/UberShaderPixel.h" #include "VideoCommon/UberShaderVertex.h" #include "VideoCommon/VertexShaderGen.h" +#include "VideoCommon/VideoEvents.h" class NativeVertexFormat; enum class AbstractTextureFormat : u32; @@ -250,6 +251,8 @@ private: // Texture decoding shaders std::map, std::unique_ptr> m_texture_decoding_shaders; + + Common::EventHook m_frame_end_handler; }; } // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Statistics.cpp b/Source/Core/VideoCommon/Statistics.cpp index a6b7eb8167..231bb470b4 100644 --- a/Source/Core/VideoCommon/Statistics.cpp +++ b/Source/Core/VideoCommon/Statistics.cpp @@ -8,11 +8,29 @@ #include +#include "Core/DolphinAnalytics.h" +#include "Core/HW/SystemTimers.h" + #include "VideoCommon/BPFunctions.h" #include "VideoCommon/VideoCommon.h" #include "VideoCommon/VideoConfig.h" +#include "VideoCommon/VideoEvents.h" Statistics g_stats; + +static Common::EventHook s_before_frame_event = + BeforeFrameEvent::Register([] { g_stats.ResetFrame(); }, "Statistics::ResetFrame"); + +static Common::EventHook s_after_frame_event = AfterFrameEvent::Register( + [] { + DolphinAnalytics::PerformanceSample perf_sample; + perf_sample.speed_ratio = SystemTimers::GetEstimatedEmulationPerformance(); + perf_sample.num_prims = g_stats.this_frame.num_prims + g_stats.this_frame.num_dl_prims; + perf_sample.num_draw_calls = g_stats.this_frame.num_draw_calls; + DolphinAnalytics::Instance().ReportPerformanceInfo(std::move(perf_sample)); + }, + "Statistics::PerformanceSample"); + static bool clear_scissors; void Statistics::ResetFrame() diff --git a/Source/Core/VideoCommon/TextureCacheBase.cpp b/Source/Core/VideoCommon/TextureCacheBase.cpp index 14cf207034..8b5e56af37 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.cpp +++ b/Source/Core/VideoCommon/TextureCacheBase.cpp @@ -34,15 +34,17 @@ #include "Core/System.h" #include "VideoCommon/AbstractFramebuffer.h" +#include "VideoCommon/AbstractGfx.h" #include "VideoCommon/AbstractStagingTexture.h" #include "VideoCommon/BPMemory.h" #include "VideoCommon/FramebufferManager.h" #include "VideoCommon/GraphicsModSystem/Runtime/FBInfo.h" #include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h" #include "VideoCommon/HiresTextures.h" #include "VideoCommon/OpcodeDecoding.h" #include "VideoCommon/PixelShaderManager.h" -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/Present.h" #include "VideoCommon/ShaderCache.h" #include "VideoCommon/Statistics.h" #include "VideoCommon/TMEM.h" @@ -62,16 +64,18 @@ static int xfb_count = 0; std::unique_ptr g_texture_cache; -TextureCacheBase::TCacheEntry::TCacheEntry(std::unique_ptr tex, - std::unique_ptr fb) +TCacheEntry::TCacheEntry(std::unique_ptr tex, + std::unique_ptr fb) : texture(std::move(tex)), framebuffer(std::move(fb)) { } -TextureCacheBase::TCacheEntry::~TCacheEntry() +TCacheEntry::~TCacheEntry() { for (auto& reference : references) reference->references.erase(this); + ASSERT_MSG(VIDEO, g_texture_cache, "Texture cache destroyed before TCacheEntry was destroyed"); + g_texture_cache->ReleaseToPool(this); } void TextureCacheBase::CheckTempSize(size_t required_size) @@ -99,13 +103,19 @@ TextureCacheBase::TextureCacheBase() TMEM::InvalidateAll(); } -TextureCacheBase::~TextureCacheBase() +void TextureCacheBase::Shutdown() { // Clear pending EFB copies first, so we don't try to flush them. m_pending_efb_copies.clear(); HiresTexture::Shutdown(); + + // For correctness, we need to invalidate textures before the gpu context starts shutting down. Invalidate(); +} + +TextureCacheBase::~TextureCacheBase() +{ Common::FreeAlignedMemory(temp); temp = nullptr; } @@ -126,13 +136,10 @@ void TextureCacheBase::Invalidate() FlushEFBCopies(); TMEM::InvalidateAll(); - bound_textures.fill(nullptr); - for (auto& tex : textures_by_address) - { - delete tex.second; - } - textures_by_address.clear(); + for (auto& bind : bound_textures) + bind.reset(); textures_by_hash.clear(); + textures_by_address.clear(); texture_pool.clear(); } @@ -183,11 +190,7 @@ void TextureCacheBase::Cleanup(int _frameCount) TexAddrCache::iterator tcend = textures_by_address.end(); while (iter != tcend) { - if (iter->second->tmem_only) - { - iter = InvalidateTexture(iter); - } - else if (iter->second->frameCount == FRAMECOUNT_INVALID) + if (iter->second->frameCount == FRAMECOUNT_INVALID) { iter->second->frameCount = _frameCount; ++iter; @@ -240,7 +243,7 @@ void TextureCacheBase::Cleanup(int _frameCount) } } -bool TextureCacheBase::TCacheEntry::OverlapsMemoryRange(u32 range_address, u32 range_size) const +bool TCacheEntry::OverlapsMemoryRange(u32 range_address, u32 range_size) const { if (addr + size_in_bytes <= range_address) return false; @@ -268,8 +271,8 @@ void TextureCacheBase::SetBackupConfig(const VideoConfig& config) config.graphics_mod_config ? config.graphics_mod_config->GetChangeCount() : 0; } -TextureCacheBase::TCacheEntry* -TextureCacheBase::ApplyPaletteToEntry(TCacheEntry* entry, const u8* palette, TLUTFormat tlutfmt) +RcTcacheEntry TextureCacheBase::ApplyPaletteToEntry(RcTcacheEntry& entry, const u8* palette, + TLUTFormat tlutfmt) { DEBUG_ASSERT(g_ActiveConfig.backend_info.bSupportsPaletteConversion); @@ -277,16 +280,16 @@ TextureCacheBase::ApplyPaletteToEntry(TCacheEntry* entry, const u8* palette, TLU if (!pipeline) { ERROR_LOG_FMT(VIDEO, "Failed to get conversion pipeline for format {}", tlutfmt); - return nullptr; + return {}; } TextureConfig new_config = entry->texture->GetConfig(); new_config.levels = 1; new_config.flags |= AbstractTextureFlag_RenderTarget; - TCacheEntry* decoded_entry = AllocateCacheEntry(new_config); + RcTcacheEntry decoded_entry = AllocateCacheEntry(new_config); if (!decoded_entry) - return nullptr; + return decoded_entry; decoded_entry->SetGeneralParameters(entry->addr, entry->size_in_bytes, entry->format, entry->should_force_safe_hashing); @@ -297,7 +300,7 @@ TextureCacheBase::ApplyPaletteToEntry(TCacheEntry* entry, const u8* palette, TLU decoded_entry->SetNotCopy(); decoded_entry->may_have_overlapping_textures = entry->may_have_overlapping_textures; - g_renderer->BeginUtilityDrawing(); + g_gfx->BeginUtilityDrawing(); const u32 palette_size = entry->format == TextureFormat::I4 ? 32 : 512; u32 texel_buffer_offset; @@ -317,19 +320,19 @@ TextureCacheBase::ApplyPaletteToEntry(TCacheEntry* entry, const u8* palette, TLU uniforms.texel_buffer_offset = texel_buffer_offset; g_vertex_manager->UploadUtilityUniforms(&uniforms, sizeof(uniforms)); - g_renderer->SetAndDiscardFramebuffer(decoded_entry->framebuffer.get()); - g_renderer->SetViewportAndScissor(decoded_entry->texture->GetRect()); - g_renderer->SetPipeline(pipeline); - g_renderer->SetTexture(1, entry->texture.get()); - g_renderer->SetSamplerState(1, RenderState::GetPointSamplerState()); - g_renderer->Draw(0, 3); - g_renderer->EndUtilityDrawing(); + g_gfx->SetAndDiscardFramebuffer(decoded_entry->framebuffer.get()); + g_gfx->SetViewportAndScissor(decoded_entry->texture->GetRect()); + g_gfx->SetPipeline(pipeline); + g_gfx->SetTexture(1, entry->texture.get()); + g_gfx->SetSamplerState(1, RenderState::GetPointSamplerState()); + g_gfx->Draw(0, 3); + g_gfx->EndUtilityDrawing(); decoded_entry->texture->FinishedRendering(); } else { ERROR_LOG_FMT(VIDEO, "Texel buffer upload of {} bytes failed", palette_size); - g_renderer->EndUtilityDrawing(); + g_gfx->EndUtilityDrawing(); } textures_by_address.emplace(decoded_entry->addr, decoded_entry); @@ -337,8 +340,8 @@ TextureCacheBase::ApplyPaletteToEntry(TCacheEntry* entry, const u8* palette, TLU return decoded_entry; } -TextureCacheBase::TCacheEntry* TextureCacheBase::ReinterpretEntry(const TCacheEntry* existing_entry, - TextureFormat new_format) +RcTcacheEntry TextureCacheBase::ReinterpretEntry(const RcTcacheEntry& existing_entry, + TextureFormat new_format) { const AbstractPipeline* pipeline = g_shader_cache->GetTextureReinterpretPipeline(existing_entry->format.texfmt, new_format); @@ -346,16 +349,16 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::ReinterpretEntry(const TCacheEn { ERROR_LOG_FMT(VIDEO, "Failed to obtain texture reinterpreting pipeline from format {} to {}", existing_entry->format.texfmt, new_format); - return nullptr; + return {}; } TextureConfig new_config = existing_entry->texture->GetConfig(); new_config.levels = 1; new_config.flags |= AbstractTextureFlag_RenderTarget; - TCacheEntry* reinterpreted_entry = AllocateCacheEntry(new_config); + RcTcacheEntry reinterpreted_entry = AllocateCacheEntry(new_config); if (!reinterpreted_entry) - return nullptr; + return {}; reinterpreted_entry->SetGeneralParameters(existing_entry->addr, existing_entry->size_in_bytes, new_format, existing_entry->should_force_safe_hashing); @@ -368,14 +371,14 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::ReinterpretEntry(const TCacheEn reinterpreted_entry->may_have_overlapping_textures = existing_entry->may_have_overlapping_textures; - g_renderer->BeginUtilityDrawing(); - g_renderer->SetAndDiscardFramebuffer(reinterpreted_entry->framebuffer.get()); - g_renderer->SetViewportAndScissor(reinterpreted_entry->texture->GetRect()); - g_renderer->SetPipeline(pipeline); - g_renderer->SetTexture(0, existing_entry->texture.get()); - g_renderer->SetSamplerState(1, RenderState::GetPointSamplerState()); - g_renderer->Draw(0, 3); - g_renderer->EndUtilityDrawing(); + g_gfx->BeginUtilityDrawing(); + g_gfx->SetAndDiscardFramebuffer(reinterpreted_entry->framebuffer.get()); + g_gfx->SetViewportAndScissor(reinterpreted_entry->texture->GetRect()); + g_gfx->SetPipeline(pipeline); + g_gfx->SetTexture(0, existing_entry->texture.get()); + g_gfx->SetSamplerState(1, RenderState::GetPointSamplerState()); + g_gfx->Draw(0, 3); + g_gfx->EndUtilityDrawing(); reinterpreted_entry->texture->FinishedRendering(); textures_by_address.emplace(reinterpreted_entry->addr, reinterpreted_entry); @@ -383,8 +386,7 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::ReinterpretEntry(const TCacheEn return reinterpreted_entry; } -void TextureCacheBase::ScaleTextureCacheEntryTo(TextureCacheBase::TCacheEntry* entry, u32 new_width, - u32 new_height) +void TextureCacheBase::ScaleTextureCacheEntryTo(RcTcacheEntry& entry, u32 new_width, u32 new_height) { if (entry->GetWidth() == new_width && entry->GetHeight() == new_height) { @@ -408,9 +410,8 @@ void TextureCacheBase::ScaleTextureCacheEntryTo(TextureCacheBase::TCacheEntry* e } // No need to convert the coordinates here since they'll be the same. - g_renderer->ScaleTexture(new_texture->framebuffer.get(), - new_texture->texture->GetConfig().GetRect(), entry->texture.get(), - entry->texture->GetConfig().GetRect()); + g_gfx->ScaleTexture(new_texture->framebuffer.get(), new_texture->texture->GetConfig().GetRect(), + entry->texture.get(), entry->texture->GetConfig().GetRect()); entry->texture.swap(new_texture->texture); entry->framebuffer.swap(new_texture->framebuffer); @@ -432,8 +433,7 @@ bool TextureCacheBase::CheckReadbackTexture(u32 width, u32 height, AbstractTextu TextureConfig staging_config(std::max(width, 128u), std::max(height, 128u), 1, 1, 1, format, 0); m_readback_texture.reset(); - m_readback_texture = - g_renderer->CreateStagingTexture(StagingTextureType::Readback, staging_config); + m_readback_texture = g_gfx->CreateStagingTexture(StagingTextureType::Readback, staging_config); return m_readback_texture != nullptr; } @@ -559,15 +559,19 @@ void TextureCacheBase::DoState(PointerWrap& p) void TextureCacheBase::DoSaveState(PointerWrap& p) { + // Flush all stale binds + FlushStaleBinds(); + std::map entry_map; std::vector entries_to_save; - auto ShouldSaveEntry = [](const TCacheEntry* entry) { + auto ShouldSaveEntry = [](const RcTcacheEntry& entry) { // We skip non-copies as they can be decoded from RAM when the state is loaded. // Storing them would duplicate data in the save state file, adding to decompression time. - return entry->IsCopy(); + // We also need to store invalidated entires, as they can't be restored from RAM. + return entry->IsCopy() || entry->invalidated; }; - auto AddCacheEntryToMap = [&entry_map, &entries_to_save](TCacheEntry* entry) -> u32 { - auto iter = entry_map.find(entry); + auto AddCacheEntryToMap = [&entry_map, &entries_to_save](const RcTcacheEntry& entry) -> u32 { + auto iter = entry_map.find(entry.get()); if (iter != entry_map.end()) return iter->second; @@ -575,8 +579,8 @@ void TextureCacheBase::DoSaveState(PointerWrap& p) // same order they were collected. This is because of iterating both the address and hash maps. // Therefore, the map is used for fast lookup, and the vector for ordering. u32 id = static_cast(entry_map.size()); - entry_map.emplace(entry, id); - entries_to_save.push_back(entry); + entry_map.emplace(entry.get(), id); + entries_to_save.push_back(entry.get()); return id; }; auto GetCacheEntryId = [&entry_map](const TCacheEntry* entry) -> std::optional { @@ -588,6 +592,7 @@ void TextureCacheBase::DoSaveState(PointerWrap& p) // of address/hash to entry ID. std::vector> textures_by_address_list; std::vector> textures_by_hash_list; + std::vector> bound_textures_list; if (Config::Get(Config::GFX_SAVE_TEXTURE_CACHE_TO_STATE)) { for (const auto& it : textures_by_address) @@ -606,6 +611,15 @@ void TextureCacheBase::DoSaveState(PointerWrap& p) textures_by_hash_list.emplace_back(it.first, id); } } + for (u32 i = 0; i < bound_textures.size(); i++) + { + const auto& tentry = bound_textures[i]; + if (bound_textures[i] && ShouldSaveEntry(tentry)) + { + const u32 id = AddCacheEntryToMap(tentry); + bound_textures_list.emplace_back(i, id); + } + } } // Save the texture cache entries out in the order the were referenced. @@ -641,29 +655,20 @@ void TextureCacheBase::DoSaveState(PointerWrap& p) } } - size = static_cast(reference_pairs.size()); - p.Do(size); - for (const auto& it : reference_pairs) - { - p.Do(it.first); - p.Do(it.second); - } + auto doList = [&p](auto list) { + u32 size = static_cast(list.size()); + p.Do(size); + for (const auto& it : list) + { + p.Do(it.first); + p.Do(it.second); + } + }; - size = static_cast(textures_by_address_list.size()); - p.Do(size); - for (const auto& it : textures_by_address_list) - { - p.Do(it.first); - p.Do(it.second); - } - - size = static_cast(textures_by_hash_list.size()); - p.Do(size); - for (const auto& it : textures_by_hash_list) - { - p.Do(it.first); - p.Do(it.second); - } + doList(reference_pairs); + doList(textures_by_address_list); + doList(textures_by_hash_list); + doList(bound_textures_list); // Free the readback texture to potentially save host-mapped GPU memory, depending on where // the driver mapped the staging buffer. @@ -673,10 +678,11 @@ void TextureCacheBase::DoSaveState(PointerWrap& p) void TextureCacheBase::DoLoadState(PointerWrap& p) { // Helper for getting a cache entry from an ID. - std::map id_map; - auto GetEntry = [&id_map](u32 id) { + std::map id_map; + RcTcacheEntry null_entry; + auto GetEntry = [&id_map, &null_entry](u32 id) -> RcTcacheEntry& { auto iter = id_map.find(id); - return iter == id_map.end() ? nullptr : iter->second; + return iter == id_map.end() ? null_entry : iter->second; }; // Only clear out state when actually restoring/loading. @@ -694,13 +700,12 @@ void TextureCacheBase::DoLoadState(PointerWrap& p) // Even if the texture isn't valid, we still need to create the cache entry object // to update the point in the state state. We'll just throw it away if it's invalid. auto tex = DeserializeTexture(p); - TCacheEntry* entry = new TCacheEntry(std::move(tex->texture), std::move(tex->framebuffer)); + auto entry = + std::make_shared(std::move(tex->texture), std::move(tex->framebuffer)); entry->textures_by_hash_iter = textures_by_hash.end(); entry->DoState(p); if (entry->texture && commit_state) id_map.emplace(i, entry); - else - delete entry; } p.DoMarker("TextureCacheEntries"); @@ -711,10 +716,10 @@ void TextureCacheBase::DoLoadState(PointerWrap& p) u32 id1 = 0, id2 = 0; p.Do(id1); p.Do(id2); - TCacheEntry* e1 = GetEntry(id1); - TCacheEntry* e2 = GetEntry(id2); + auto e1 = GetEntry(id1); + auto e2 = GetEntry(id2); if (e1 && e2) - e1->CreateReference(e2); + e1->CreateReference(e2.get()); } // Fill in address map. @@ -726,7 +731,7 @@ void TextureCacheBase::DoLoadState(PointerWrap& p) p.Do(addr); p.Do(id); - TCacheEntry* entry = GetEntry(id); + auto& entry = GetEntry(id); if (entry) textures_by_address.emplace(addr, entry); } @@ -740,13 +745,48 @@ void TextureCacheBase::DoLoadState(PointerWrap& p) p.Do(hash); p.Do(id); - TCacheEntry* entry = GetEntry(id); + auto& entry = GetEntry(id); if (entry) entry->textures_by_hash_iter = textures_by_hash.emplace(hash, entry); } + + // Clear bound textures + for (u32 i = 0; i < bound_textures.size(); i++) + bound_textures[i].reset(); + + // Fill in bound textures + p.Do(size); + for (u32 i = 0; i < size; i++) + { + u32 index = 0; + u32 id = 0; + p.Do(index); + p.Do(id); + + auto& entry = GetEntry(id); + if (entry) + bound_textures[index] = entry; + } } -void TextureCacheBase::TCacheEntry::DoState(PointerWrap& p) +void TextureCacheBase::OnFrameEnd() +{ + if (m_force_reload_textures.TestAndClear()) + { + ForceReload(); + } + else + { + // Flush any outstanding EFB copies to RAM, in case the game is running at an uncapped frame + // rate and not waiting for vblank. Otherwise, we'd end up with a huge list of pending + // copies. + FlushEFBCopies(); + } + + Cleanup(g_presenter->FrameCount()); +} + +void TCacheEntry::DoState(PointerWrap& p) { p.Do(addr); p.Do(size_in_bytes); @@ -757,7 +797,7 @@ void TextureCacheBase::TCacheEntry::DoState(PointerWrap& p) p.Do(is_efb_copy); p.Do(is_custom_tex); p.Do(may_have_overlapping_textures); - p.Do(tmem_only); + p.Do(invalidated); p.Do(has_arbitrary_mips); p.Do(should_force_safe_hashing); p.Do(is_xfb_copy); @@ -770,9 +810,8 @@ void TextureCacheBase::TCacheEntry::DoState(PointerWrap& p) p.Do(frameCount); } -TextureCacheBase::TCacheEntry* -TextureCacheBase::DoPartialTextureUpdates(TCacheEntry* entry_to_update, const u8* palette, - TLUTFormat tlutfmt) +RcTcacheEntry TextureCacheBase::DoPartialTextureUpdates(RcTcacheEntry& entry_to_update, + const u8* palette, TLUTFormat tlutfmt) { // If the flag may_have_overlapping_textures is cleared, there are no overlapping EFB copies, // which aren't applied already. It is set for new textures, and for the affected range @@ -788,6 +827,13 @@ TextureCacheBase::DoPartialTextureUpdates(TCacheEntry* entry_to_update, const u8 if (entry_to_update->IsCopy()) return entry_to_update; + if (entry_to_update->IsLocked()) + { + // TODO: Shouldn't be too hard, just need to clone the texture entry + texture contents. + PanicAlertFmt("TextureCache: PartialTextureUpdates of locked textures is not implemented"); + return {}; + } + u32 block_width = TexDecoder_GetBlockWidthInTexels(entry_to_update->format.texfmt); u32 block_height = TexDecoder_GetBlockHeightInTexels(entry_to_update->format.texfmt); u32 block_size = block_width * block_height * @@ -798,9 +844,9 @@ TextureCacheBase::DoPartialTextureUpdates(TCacheEntry* entry_to_update, const u8 auto iter = FindOverlappingTextures(entry_to_update->addr, entry_to_update->size_in_bytes); while (iter.first != iter.second) { - TCacheEntry* entry = iter.first->second; - if (entry != entry_to_update && entry->IsCopy() && !entry->tmem_only && - entry->references.count(entry_to_update) == 0 && + auto& entry = iter.first->second; + if (entry != entry_to_update && entry->IsCopy() && + entry->references.count(entry_to_update.get()) == 0 && entry->OverlapsMemoryRange(entry_to_update->addr, entry_to_update->size_in_bytes) && entry->memory_stride == numBlocksX * block_size) { @@ -815,20 +861,19 @@ TextureCacheBase::DoPartialTextureUpdates(TCacheEntry* entry_to_update, const u8 continue; } - TCacheEntry* reinterpreted_entry = - ReinterpretEntry(entry, entry_to_update->format.texfmt); + auto reinterpreted_entry = ReinterpretEntry(entry, entry_to_update->format.texfmt); if (reinterpreted_entry) entry = reinterpreted_entry; } if (isPaletteTexture) { - TCacheEntry* decoded_entry = ApplyPaletteToEntry(entry, palette, tlutfmt); + auto decoded_entry = ApplyPaletteToEntry(entry, palette, tlutfmt); if (decoded_entry) { // Link the efb copy with the partially updated texture, so we won't apply this partial // update again - entry->CreateReference(entry_to_update); + entry->CreateReference(entry_to_update.get()); // Mark the texture update as used, as if it was loaded directly entry->frameCount = FRAMECOUNT_INVALID; entry = decoded_entry; @@ -875,18 +920,18 @@ TextureCacheBase::DoPartialTextureUpdates(TCacheEntry* entry_to_update, const u8 entry_to_update->native_height != entry_to_update->GetHeight() || entry->native_width != entry->GetWidth() || entry->native_height != entry->GetHeight()) { - ScaleTextureCacheEntryTo(entry_to_update, - g_renderer->EFBToScaledX(entry_to_update->native_width), - g_renderer->EFBToScaledY(entry_to_update->native_height)); - ScaleTextureCacheEntryTo(entry, g_renderer->EFBToScaledX(entry->native_width), - g_renderer->EFBToScaledY(entry->native_height)); + ScaleTextureCacheEntryTo( + entry_to_update, g_framebuffer_manager->EFBToScaledX(entry_to_update->native_width), + g_framebuffer_manager->EFBToScaledY(entry_to_update->native_height)); + ScaleTextureCacheEntryTo(entry, g_framebuffer_manager->EFBToScaledX(entry->native_width), + g_framebuffer_manager->EFBToScaledY(entry->native_height)); - src_x = g_renderer->EFBToScaledX(src_x); - src_y = g_renderer->EFBToScaledY(src_y); - dst_x = g_renderer->EFBToScaledX(dst_x); - dst_y = g_renderer->EFBToScaledY(dst_y); - copy_width = g_renderer->EFBToScaledX(copy_width); - copy_height = g_renderer->EFBToScaledY(copy_height); + src_x = g_framebuffer_manager->EFBToScaledX(src_x); + src_y = g_framebuffer_manager->EFBToScaledY(src_y); + dst_x = g_framebuffer_manager->EFBToScaledX(dst_x); + dst_y = g_framebuffer_manager->EFBToScaledY(dst_y); + copy_width = g_framebuffer_manager->EFBToScaledX(copy_width); + copy_height = g_framebuffer_manager->EFBToScaledY(copy_height); } // If the source rectangle is outside of what we actually have in VRAM, skip the copy. @@ -929,7 +974,7 @@ TextureCacheBase::DoPartialTextureUpdates(TCacheEntry* entry_to_update, const u8 else { // Link the two textures together, so we won't apply this partial update again - entry->CreateReference(entry_to_update); + entry->CreateReference(entry_to_update.get()); // Mark the texture update as used, as if it was loaded directly entry->frameCount = FRAMECOUNT_INVALID; } @@ -947,7 +992,7 @@ TextureCacheBase::DoPartialTextureUpdates(TCacheEntry* entry_to_update, const u8 return entry_to_update; } -void TextureCacheBase::DumpTexture(TCacheEntry* entry, std::string basename, unsigned int level, +void TextureCacheBase::DumpTexture(RcTcacheEntry& entry, std::string basename, unsigned int level, bool is_arbitrary) { std::string szDir = File::GetUserPath(D_DUMPTEXTURES_IDX) + SConfig::GetInstance().GetGameID(); @@ -1046,14 +1091,14 @@ static void SetSamplerState(u32 index, float custom_tex_scale, bool custom_tex, // that have arbitrary contents, eg. are used for fog effects where the // distance they kick in at is important to preserve at any resolution. // Correct this with the upscaling factor of custom textures. - s32 lod_offset = std::log2(g_renderer->GetEFBScale() / custom_tex_scale) * 256.f; + s32 lod_offset = std::log2(g_framebuffer_manager->GetEFBScale() / custom_tex_scale) * 256.f; state.tm0.lod_bias = std::clamp(state.tm0.lod_bias + lod_offset, -32768, 32767); // Anisotropic also pushes mips farther away so it cannot be used either state.tm0.anisotropic_filtering = false; } - g_renderer->SetSamplerState(index, state); + g_gfx->SetSamplerState(index, state); auto& system = Core::System::GetInstance(); auto& pixel_shader_manager = system.GetPixelShaderManager(); pixel_shader_manager.SetSamplerState(index, state.tm0.hex, state.tm1.hex); @@ -1065,10 +1110,10 @@ void TextureCacheBase::BindTextures(BitSet32 used_textures) auto& pixel_shader_manager = system.GetPixelShaderManager(); for (u32 i = 0; i < bound_textures.size(); i++) { - const TCacheEntry* tentry = bound_textures[i]; + const RcTcacheEntry& tentry = bound_textures[i]; if (used_textures[i] && tentry) { - g_renderer->SetTexture(i, tentry->texture.get()); + g_gfx->SetTexture(i, tentry->texture.get()); pixel_shader_manager.SetTexDims(i, tentry->native_width, tentry->native_height); const float custom_tex_scale = tentry->GetWidth() / float(tentry->native_width); @@ -1224,14 +1269,21 @@ private: std::vector levels; }; -TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const TextureInfo& texture_info) +TCacheEntry* TextureCacheBase::Load(const TextureInfo& texture_info) { // if this stage was not invalidated by changes to texture registers, keep the current texture if (TMEM::IsValid(texture_info.GetStage()) && bound_textures[texture_info.GetStage()]) { - TCacheEntry* entry = bound_textures[texture_info.GetStage()]; + TCacheEntry* entry = bound_textures[texture_info.GetStage()].get(); // If the TMEM configuration is such that this texture is more or less guaranteed to still // be in TMEM, then we know we can reuse the old entry without even hashing the memory + // + // It's possible this texture has already been overwritten in emulated memory and therfore + // invalidated from our texture cache, but we want to use it anyway to approximate the + // result of the game using an overwritten texture cached in TMEM. + // + // Spyro: A Hero's Tail is known for (deliberately?) using such overwritten textures + // in it's bloom effect, which breaks without giving it the invalidated texture. if (TMEM::IsCached(texture_info.GetStage())) { return entry; @@ -1239,7 +1291,7 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const TextureInfo& texture // Otherwise, hash the backing memory and check it's unchanged. // FIXME: this doesn't correctly handle textures from tmem. - if (!entry->tmem_only && entry->base_hash == entry->CalculateHash()) + if (!entry->invalidated && entry->base_hash == entry->CalculateHash()) { return entry; } @@ -1257,7 +1309,7 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const TextureInfo& texture GraphicsModActionData::TextureLoad texture_load{entry->texture_info_name}; for (const auto action : - g_renderer->GetGraphicsModManager().GetTextureLoadActions(entry->texture_info_name)) + g_graphics_mod_manager->GetTextureLoadActions(entry->texture_info_name)) { action->OnTextureLoad(&texture_load); } @@ -1269,12 +1321,11 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const TextureInfo& texture TMEM::Bind(texture_info.GetStage(), entry->NumBlocksX(), entry->NumBlocksY(), entry->GetNumLevels() > 1, entry->format == TextureFormat::RGBA8); - return entry; + return entry.get(); } -TextureCacheBase::TCacheEntry* -TextureCacheBase::GetTexture(const int textureCacheSafetyColorSampleSize, - const TextureInfo& texture_info) +RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSampleSize, + const TextureInfo& texture_info) { u32 expanded_width = texture_info.GetExpandedWidth(); u32 expanded_height = texture_info.GetExpandedHeight(); @@ -1291,7 +1342,7 @@ TextureCacheBase::GetTexture(const int textureCacheSafetyColorSampleSize, // Reject invalid tlut format. if (texture_info.GetPaletteSize() && !IsValidTLUTFormat(texture_info.GetTlutFormat())) - return nullptr; + return {}; u32 bytes_per_block = (texture_info.GetBlockWidth() * texture_info.GetBlockHeight() * TexDecoder_GetTexelSizeInNibbles(texture_info.GetTextureFormat())) / @@ -1304,7 +1355,7 @@ TextureCacheBase::GetTexture(const int textureCacheSafetyColorSampleSize, { ERROR_LOG_FMT(VIDEO, "Trying to use an invalid texture address {:#010x}", texture_info.GetRawAddress()); - return nullptr; + return {}; } // If we are recording a FifoLog, keep track of what memory we read. FifoRecorder does @@ -1368,14 +1419,7 @@ TextureCacheBase::GetTexture(const int textureCacheSafetyColorSampleSize, while (iter != iter_range.second) { - TCacheEntry* entry = iter->second; - - // Skip entries that are only left in our texture cache for the tmem cache emulation - if (entry->tmem_only) - { - ++iter; - continue; - } + RcTcacheEntry& entry = iter->second; // TODO: Some games (Rogue Squadron 3, Twin Snakes) seem to load a previously made XFB // copy as a regular texture. You can see this particularly well in RS3 whenever the @@ -1455,8 +1499,11 @@ TextureCacheBase::GetTexture(const int textureCacheSafetyColorSampleSize, { entry = DoPartialTextureUpdates(iter->second, texture_info.GetTlutAddress(), texture_info.GetTlutFormat()); - entry->texture->FinishedRendering(); - return entry; + if (entry) + { + entry->texture->FinishedRendering(); + return entry; + } } } @@ -1478,7 +1525,7 @@ TextureCacheBase::GetTexture(const int textureCacheSafetyColorSampleSize, if (unreinterpreted_copy != textures_by_address.end()) { - TCacheEntry* decoded_entry = + auto decoded_entry = ReinterpretEntry(unreinterpreted_copy->second, texture_info.GetTextureFormat()); // It's possible to combine reinterpreted textures + palettes. @@ -1492,7 +1539,7 @@ TextureCacheBase::GetTexture(const int textureCacheSafetyColorSampleSize, if (unconverted_copy != textures_by_address.end()) { - TCacheEntry* decoded_entry = ApplyPaletteToEntry( + auto decoded_entry = ApplyPaletteToEntry( unconverted_copy->second, texture_info.GetTlutAddress(), texture_info.GetTlutFormat()); if (decoded_entry) @@ -1515,7 +1562,7 @@ TextureCacheBase::GetTexture(const int textureCacheSafetyColorSampleSize, TexHashCache::iterator hash_iter = hash_range.first; while (hash_iter != hash_range.second) { - TCacheEntry* entry = hash_iter->second; + RcTcacheEntry& entry = hash_iter->second; // All parameters, except the address, need to match here if (entry->format == full_format && entry->native_levels >= texture_info.GetLevelCount() && entry->native_width == texture_info.GetRawWidth() && @@ -1523,8 +1570,11 @@ TextureCacheBase::GetTexture(const int textureCacheSafetyColorSampleSize, { entry = DoPartialTextureUpdates(hash_iter->second, texture_info.GetTlutAddress(), texture_info.GetTlutFormat()); - entry->texture->FinishedRendering(); - return entry; + if (entry) + { + entry->texture->FinishedRendering(); + return entry; + } } ++hash_iter; } @@ -1577,9 +1627,9 @@ TextureCacheBase::GetTexture(const int textureCacheSafetyColorSampleSize, // create the entry/texture const TextureConfig config(width, height, texLevels, 1, 1, hires_tex ? hires_tex->GetFormat() : AbstractTextureFormat::RGBA8, 0); - TCacheEntry* entry = AllocateCacheEntry(config); + RcTcacheEntry entry = AllocateCacheEntry(config); if (!entry) - return nullptr; + return entry; ArbitraryMipmapDetector arbitrary_mip_detector; if (hires_tex) @@ -1729,7 +1779,7 @@ TextureCacheBase::GetTexture(const int textureCacheSafetyColorSampleSize, return entry; } -static void GetDisplayRectForXFBEntry(TextureCacheBase::TCacheEntry* entry, u32 width, u32 height, +static void GetDisplayRectForXFBEntry(TCacheEntry* entry, u32 width, u32 height, MathUtil::Rectangle* display_rect) { // Scale the sub-rectangle to the full resolution of the texture. @@ -1739,9 +1789,8 @@ static void GetDisplayRectForXFBEntry(TextureCacheBase::TCacheEntry* entry, u32 display_rect->bottom = static_cast(height * entry->GetHeight() / entry->native_height); } -TextureCacheBase::TCacheEntry* -TextureCacheBase::GetXFBTexture(u32 address, u32 width, u32 height, u32 stride, - MathUtil::Rectangle* display_rect) +RcTcacheEntry TextureCacheBase::GetXFBTexture(u32 address, u32 width, u32 height, u32 stride, + MathUtil::Rectangle* display_rect) { auto& system = Core::System::GetInstance(); auto& memory = system.GetMemory(); @@ -1749,12 +1798,12 @@ TextureCacheBase::GetXFBTexture(u32 address, u32 width, u32 height, u32 stride, if (!src_data) { ERROR_LOG_FMT(VIDEO, "Trying to load XFB texture from invalid address {:#010x}", address); - return nullptr; + return {}; } - // Do we currently have a version of this XFB copy in VRAM? - TCacheEntry* entry = GetXFBFromCache(address, width, height, stride); - if (entry) + // Do we currently have a mutable version of this XFB copy in VRAM? + RcTcacheEntry entry = GetXFBFromCache(address, width, height, stride); + if (entry && !entry->IsLocked()) { if (entry->is_xfb_container) { @@ -1762,7 +1811,7 @@ TextureCacheBase::GetXFBTexture(u32 address, u32 width, u32 height, u32 stride, entry->texture->FinishedRendering(); } - GetDisplayRectForXFBEntry(entry, width, height, display_rect); + GetDisplayRectForXFBEntry(entry.get(), width, height, display_rect); return entry; } @@ -1818,19 +1867,18 @@ TextureCacheBase::GetXFBTexture(u32 address, u32 width, u32 height, u32 stride, } } - GetDisplayRectForXFBEntry(entry, width, height, display_rect); + GetDisplayRectForXFBEntry(entry.get(), width, height, display_rect); return entry; } -TextureCacheBase::TCacheEntry* TextureCacheBase::GetXFBFromCache(u32 address, u32 width, u32 height, - u32 stride) +RcTcacheEntry TextureCacheBase::GetXFBFromCache(u32 address, u32 width, u32 height, u32 stride) { auto iter_range = textures_by_address.equal_range(address); TexAddrCache::iterator iter = iter_range.first; while (iter != iter_range.second) { - TCacheEntry* entry = iter->second; + auto& entry = iter->second; // The only thing which has to match exactly is the stride. We can use a partial rectangle if // the VI width/height differs from that of the XFB copy. @@ -1854,10 +1902,10 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::GetXFBFromCache(u32 address, u3 ++iter; } - return nullptr; + return {}; } -void TextureCacheBase::StitchXFBCopy(TCacheEntry* stitched_entry) +void TextureCacheBase::StitchXFBCopy(RcTcacheEntry& stitched_entry) { // It is possible that some of the overlapping textures overlap each other. This behavior has been // seen with XFB copies in Rogue Leader. To get the correct result, we apply the texture updates @@ -1876,8 +1924,8 @@ void TextureCacheBase::StitchXFBCopy(TCacheEntry* stitched_entry) // our force progressive hack means that an XFB copy should always have a matching stride. If // the hack is disabled, XFB2RAM should also be enabled. Should we wish to implement interlaced // stitching in the future, this would require a shader which grabs every second line. - TCacheEntry* entry = iter.first->second; - if (entry != stitched_entry && entry->IsCopy() && !entry->tmem_only && + auto& entry = iter.first->second; + if (entry != stitched_entry && entry->IsCopy() && entry->OverlapsMemoryRange(stitched_entry->addr, stitched_entry->size_in_bytes) && entry->memory_stride == stitched_entry->memory_stride) { @@ -1887,7 +1935,7 @@ void TextureCacheBase::StitchXFBCopy(TCacheEntry* stitched_entry) if (entry->native_width != entry->GetWidth()) create_upscaled_copy = true; - candidates.emplace_back(entry); + candidates.emplace_back(entry.get()); } else { @@ -1909,8 +1957,9 @@ void TextureCacheBase::StitchXFBCopy(TCacheEntry* stitched_entry) // copies to be stitched together. if (create_upscaled_copy) { - ScaleTextureCacheEntryTo(stitched_entry, g_renderer->EFBToScaledX(stitched_entry->native_width), - g_renderer->EFBToScaledY(stitched_entry->native_height)); + ScaleTextureCacheEntryTo(stitched_entry, + g_framebuffer_manager->EFBToScaledX(stitched_entry->native_width), + g_framebuffer_manager->EFBToScaledY(stitched_entry->native_height)); } for (TCacheEntry* entry : candidates) @@ -1945,17 +1994,17 @@ void TextureCacheBase::StitchXFBCopy(TCacheEntry* stitched_entry) // Scale to internal resolution. if (entry->native_width != entry->GetWidth()) { - src_x = g_renderer->EFBToScaledX(src_x); - src_y = g_renderer->EFBToScaledY(src_y); - src_width = g_renderer->EFBToScaledX(src_width); - src_height = g_renderer->EFBToScaledY(src_height); + src_x = g_framebuffer_manager->EFBToScaledX(src_x); + src_y = g_framebuffer_manager->EFBToScaledY(src_y); + src_width = g_framebuffer_manager->EFBToScaledX(src_width); + src_height = g_framebuffer_manager->EFBToScaledY(src_height); } if (create_upscaled_copy) { - dst_x = g_renderer->EFBToScaledX(dst_x); - dst_y = g_renderer->EFBToScaledY(dst_y); - dst_width = g_renderer->EFBToScaledX(dst_width); - dst_height = g_renderer->EFBToScaledY(dst_height); + dst_x = g_framebuffer_manager->EFBToScaledX(dst_x); + dst_y = g_framebuffer_manager->EFBToScaledY(dst_y); + dst_width = g_framebuffer_manager->EFBToScaledX(dst_width); + dst_height = g_framebuffer_manager->EFBToScaledY(dst_height); } // If the source rectangle is outside of what we actually have in VRAM, skip the copy. @@ -1982,8 +2031,8 @@ void TextureCacheBase::StitchXFBCopy(TCacheEntry* stitched_entry) // We may have to scale if one of the copies is not internal resolution. if (srcrect.GetWidth() != dstrect.GetWidth() || srcrect.GetHeight() != dstrect.GetHeight()) { - g_renderer->ScaleTexture(stitched_entry->framebuffer.get(), dstrect, entry->texture.get(), - srcrect); + g_gfx->ScaleTexture(stitched_entry->framebuffer.get(), dstrect, entry->texture.get(), + srcrect); } else { @@ -1997,7 +2046,7 @@ void TextureCacheBase::StitchXFBCopy(TCacheEntry* stitched_entry) } // Link the two textures together, so we won't apply this partial update again - entry->CreateReference(stitched_entry); + entry->CreateReference(stitched_entry.get()); // Mark the texture update as used, as if it was loaded directly entry->frameCount = FRAMECOUNT_INVALID; @@ -2135,8 +2184,8 @@ void TextureCacheBase::CopyRenderTargetToTexture( // For the latter, we keep the EFB resolution for the virtual XFB blit. u32 tex_w = width; u32 tex_h = height; - u32 scaled_tex_w = g_renderer->EFBToScaledX(width); - u32 scaled_tex_h = g_renderer->EFBToScaledY(height); + u32 scaled_tex_w = g_framebuffer_manager->EFBToScaledX(width); + u32 scaled_tex_h = g_framebuffer_manager->EFBToScaledY(height); if (scaleByHalf) { @@ -2180,7 +2229,7 @@ void TextureCacheBase::CopyRenderTargetToTexture( info.m_texture_format = baseFormat; if (is_xfb_copy) { - for (const auto action : g_renderer->GetGraphicsModManager().GetXFBActions(info)) + for (const auto action : g_graphics_mod_manager->GetXFBActions(info)) { action->OnXFB(); } @@ -2189,7 +2238,7 @@ void TextureCacheBase::CopyRenderTargetToTexture( { bool skip = false; GraphicsModActionData::EFB efb{tex_w, tex_h, &skip, &scaled_tex_w, &scaled_tex_h}; - for (const auto action : g_renderer->GetGraphicsModManager().GetEFBActions(info)) + for (const auto action : g_graphics_mod_manager->GetEFBActions(info)) { action->OnEFB(&efb); } @@ -2221,9 +2270,10 @@ void TextureCacheBase::CopyRenderTargetToTexture( // TODO: This only produces perfect downsampling for 2x IR, other resolutions will need more // complex down filtering to average all pixels and produce the correct result. const bool linear_filter = - !is_depth_copy && (scaleByHalf || g_renderer->GetEFBScale() != 1 || y_scale > 1.0f); + !is_depth_copy && + (scaleByHalf || g_framebuffer_manager->GetEFBScale() != 1 || y_scale > 1.0f); - TCacheEntry* entry = nullptr; + RcTcacheEntry entry; if (copy_to_vram) { // create the texture @@ -2314,7 +2364,6 @@ void TextureCacheBase::CopyRenderTargetToTexture( entry->pending_efb_copy = std::move(staging_texture); entry->pending_efb_copy_width = bytes_per_row / sizeof(u32); entry->pending_efb_copy_height = num_blocks_y; - entry->pending_efb_copy_invalidated = false; m_pending_efb_copies.push_back(entry); } } @@ -2339,7 +2388,7 @@ void TextureCacheBase::CopyRenderTargetToTexture( auto iter = FindOverlappingTextures(dstAddr, covered_range); while (iter.first != iter.second) { - TCacheEntry* overlapping_entry = iter.first->second; + RcTcacheEntry& overlapping_entry = iter.first->second; if (overlapping_entry->addr == dstAddr && overlapping_entry->is_xfb_copy) { @@ -2412,7 +2461,7 @@ void TextureCacheBase::CopyRenderTargetToTexture( { const u64 hash = entry->CalculateHash(); entry->SetHashes(hash, hash); - textures_by_address.emplace(dstAddr, entry); + textures_by_address.emplace(dstAddr, std::move(entry)); } } @@ -2421,11 +2470,20 @@ void TextureCacheBase::FlushEFBCopies() if (m_pending_efb_copies.empty()) return; - for (TCacheEntry* entry : m_pending_efb_copies) - FlushEFBCopy(entry); + for (auto& entry : m_pending_efb_copies) + FlushEFBCopy(entry.get()); m_pending_efb_copies.clear(); } +void TextureCacheBase::FlushStaleBinds() +{ + for (u32 i = 0; i < bound_textures.size(); i++) + { + if (!TMEM::IsCached(i)) + bound_textures[i].reset(); + } +} + void TextureCacheBase::WriteEFBCopyToRAM(u8* dst_ptr, u32 width, u32 height, u32 stride, std::unique_ptr staging_texture) { @@ -2443,14 +2501,10 @@ void TextureCacheBase::FlushEFBCopy(TCacheEntry* entry) WriteEFBCopyToRAM(dst, entry->pending_efb_copy_width, entry->pending_efb_copy_height, entry->memory_stride, std::move(entry->pending_efb_copy)); - // If the EFB copy was invalidated (e.g. the bloom case mentioned in InvalidateTexture), now is - // the time to clean up the TCacheEntry. In which case, we don't need to compute the new hash of - // the RAM copy. But we need to clean up the TCacheEntry, as InvalidateTexture doesn't free it. - if (entry->pending_efb_copy_invalidated) - { - delete entry; + // If the EFB copy was invalidated (e.g. the bloom case mentioned in InvalidateTexture), we don't + // need to do anything more. The entry will be automatically deleted by smart pointers + if (entry->invalidated) return; - } // Re-hash the texture now that the guest memory is populated. // This should be safe because we'll catch any writes before the game can modify it. @@ -2465,7 +2519,7 @@ void TextureCacheBase::FlushEFBCopy(TCacheEntry* entry) auto range = FindOverlappingTextures(entry->addr, covered_range); for (auto iter = range.first; iter != range.second; ++iter) { - TCacheEntry* overlapping_entry = iter->second; + auto& overlapping_entry = iter->second; if (overlapping_entry->may_have_overlapping_textures && overlapping_entry->is_xfb_copy && overlapping_entry->OverlapsMemoryRange(entry->addr, covered_range)) { @@ -2486,7 +2540,7 @@ std::unique_ptr TextureCacheBase::GetEFBCopyStagingTextu return ptr; } - std::unique_ptr tex = g_renderer->CreateStagingTexture( + std::unique_ptr tex = g_gfx->CreateStagingTexture( StagingTextureType::Readback, m_efb_encoding_texture->GetConfig()); if (!tex) WARN_LOG_FMT(VIDEO, "Failed to create EFB copy staging texture"); @@ -2555,14 +2609,14 @@ void TextureCacheBase::UninitializeXFBMemory(u8* dst, u32 stride, u32 bytes_per_ } } -TextureCacheBase::TCacheEntry* TextureCacheBase::AllocateCacheEntry(const TextureConfig& config) +RcTcacheEntry TextureCacheBase::AllocateCacheEntry(const TextureConfig& config) { std::optional alloc = AllocateTexture(config); if (!alloc) - return nullptr; + return {}; - TCacheEntry* cacheEntry = - new TCacheEntry(std::move(alloc->texture), std::move(alloc->framebuffer)); + auto cacheEntry = + std::make_shared(std::move(alloc->texture), std::move(alloc->framebuffer)); cacheEntry->textures_by_hash_iter = textures_by_hash.end(); cacheEntry->id = last_entry_id++; return cacheEntry; @@ -2579,7 +2633,7 @@ TextureCacheBase::AllocateTexture(const TextureConfig& config) return std::move(entry); } - std::unique_ptr texture = g_renderer->CreateTexture(config); + std::unique_ptr texture = g_gfx->CreateTexture(config); if (!texture) { WARN_LOG_FMT(VIDEO, "Failed to allocate a {}x{}x{} texture", config.width, config.height, @@ -2590,7 +2644,7 @@ TextureCacheBase::AllocateTexture(const TextureConfig& config) std::unique_ptr framebuffer; if (config.IsRenderTarget()) { - framebuffer = g_renderer->CreateFramebuffer(texture.get(), nullptr); + framebuffer = g_gfx->CreateFramebuffer(texture.get(), nullptr); if (!framebuffer) { WARN_LOG_FMT(VIDEO, "Failed to allocate a {}x{}x{} framebuffer", config.width, config.height, @@ -2618,14 +2672,13 @@ TextureCacheBase::FindMatchingTextureFromPool(const TextureConfig& config) return matching_iter != range.second ? matching_iter : texture_pool.end(); } -TextureCacheBase::TexAddrCache::iterator -TextureCacheBase::GetTexCacheIter(TextureCacheBase::TCacheEntry* entry) +TextureCacheBase::TexAddrCache::iterator TextureCacheBase::GetTexCacheIter(TCacheEntry* entry) { auto iter_range = textures_by_address.equal_range(entry->addr); TexAddrCache::iterator iter = iter_range.first; while (iter != iter_range.second) { - if (iter->second == entry) + if (iter->second.get() == entry) { return iter; } @@ -2657,7 +2710,7 @@ TextureCacheBase::InvalidateTexture(TexAddrCache::iterator iter, bool discard_pe if (iter == textures_by_address.end()) return textures_by_address.end(); - TCacheEntry* entry = iter->second; + RcTcacheEntry& entry = iter->second; if (entry->textures_by_hash_iter != textures_by_hash.end()) { @@ -2665,26 +2718,6 @@ TextureCacheBase::InvalidateTexture(TexAddrCache::iterator iter, bool discard_pe entry->textures_by_hash_iter = textures_by_hash.end(); } - for (size_t i = 0; i < bound_textures.size(); ++i) - { - if (bound_textures[i] == entry) - { - if (TMEM::IsCached(static_cast(i))) - { - // If the entry is currently bound and tmem has it recorded as cached, keep it, but mark it - // as invalidated. This way it can still be used via tmem cache emulation, but nothing else. - // Spyro: A Hero's Tail is known for using such overwritten textures. - bound_textures[i]->tmem_only = true; - return ++iter; - } - else - { - // Otherwise, delete the reference to it from bound_textures - bound_textures[i] = nullptr; - } - } - } - // If this is a pending EFB copy, we don't want to flush it here. // Why? Because let's say a game is rendering a bloom-type effect, using EFB copies to essentially // downscale the framebuffer. Copy from EFB->Texture, draw texture to EFB, copy EFB->Texture, @@ -2707,31 +2740,35 @@ TextureCacheBase::InvalidateTexture(TexAddrCache::iterator iter, bool discard_pe } else { - entry->pending_efb_copy_invalidated = true; + // The texture data has already been copied into the staging texture, so it's valid to + // optimistically release the texture data. Will slightly lower VRAM usage. + if (!entry->IsLocked()) + ReleaseToPool(entry.get()); } } + entry->invalidated = true; + return textures_by_address.erase(iter); +} + +void TextureCacheBase::ReleaseToPool(TCacheEntry* entry) +{ + if (!entry->texture) + return; auto config = entry->texture->GetConfig(); texture_pool.emplace(config, TexPoolEntry(std::move(entry->texture), std::move(entry->framebuffer))); - - // Don't delete if there's a pending EFB copy, as we need the TCacheEntry alive. - if (!entry->pending_efb_copy) - delete entry; - - return textures_by_address.erase(iter); } bool TextureCacheBase::CreateUtilityTextures() { constexpr TextureConfig encoding_texture_config( EFB_WIDTH * 4, 1024, 1, 1, 1, AbstractTextureFormat::BGRA8, AbstractTextureFlag_RenderTarget); - m_efb_encoding_texture = - g_renderer->CreateTexture(encoding_texture_config, "EFB encoding texture"); + m_efb_encoding_texture = g_gfx->CreateTexture(encoding_texture_config, "EFB encoding texture"); if (!m_efb_encoding_texture) return false; - m_efb_encoding_framebuffer = g_renderer->CreateFramebuffer(m_efb_encoding_texture.get(), nullptr); + m_efb_encoding_framebuffer = g_gfx->CreateFramebuffer(m_efb_encoding_texture.get(), nullptr); if (!m_efb_encoding_framebuffer) return false; @@ -2740,7 +2777,7 @@ bool TextureCacheBase::CreateUtilityTextures() constexpr TextureConfig decoding_texture_config( 1024, 1024, 1, 1, 1, AbstractTextureFormat::RGBA8, AbstractTextureFlag_ComputeImage); m_decoding_texture = - g_renderer->CreateTexture(decoding_texture_config, "GPU texture decoding texture"); + g_gfx->CreateTexture(decoding_texture_config, "GPU texture decoding texture"); if (!m_decoding_texture) return false; } @@ -2748,7 +2785,7 @@ bool TextureCacheBase::CreateUtilityTextures() return true; } -void TextureCacheBase::CopyEFBToCacheEntry(TCacheEntry* entry, bool is_depth_copy, +void TextureCacheBase::CopyEFBToCacheEntry(RcTcacheEntry& entry, bool is_depth_copy, const MathUtil::Rectangle& src_rect, bool scale_by_half, bool linear_filter, EFBCopyFormat dst_format, bool is_intensity, float gamma, @@ -2768,15 +2805,15 @@ void TextureCacheBase::CopyEFBToCacheEntry(TCacheEntry* entry, bool is_depth_cop return; } - const auto scaled_src_rect = g_renderer->ConvertEFBRectangle(src_rect); - const auto framebuffer_rect = g_renderer->ConvertFramebufferRectangle( + const auto scaled_src_rect = g_framebuffer_manager->ConvertEFBRectangle(src_rect); + const auto framebuffer_rect = g_gfx->ConvertFramebufferRectangle( scaled_src_rect, g_framebuffer_manager->GetEFBFramebuffer()); AbstractTexture* src_texture = is_depth_copy ? g_framebuffer_manager->ResolveEFBDepthTexture(framebuffer_rect) : g_framebuffer_manager->ResolveEFBColorTexture(framebuffer_rect); src_texture->FinishedRendering(); - g_renderer->BeginUtilityDrawing(); + g_gfx->BeginUtilityDrawing(); // Fill uniform buffer. struct Uniforms @@ -2813,14 +2850,14 @@ void TextureCacheBase::CopyEFBToCacheEntry(TCacheEntry* entry, bool is_depth_cop g_vertex_manager->UploadUtilityUniforms(&uniforms, sizeof(uniforms)); // Use the copy pipeline to render the VRAM copy. - g_renderer->SetAndDiscardFramebuffer(entry->framebuffer.get()); - g_renderer->SetViewportAndScissor(entry->framebuffer->GetRect()); - g_renderer->SetPipeline(copy_pipeline); - g_renderer->SetTexture(0, src_texture); - g_renderer->SetSamplerState(0, linear_filter ? RenderState::GetLinearSamplerState() : - RenderState::GetPointSamplerState()); - g_renderer->Draw(0, 3); - g_renderer->EndUtilityDrawing(); + g_gfx->SetAndDiscardFramebuffer(entry->framebuffer.get()); + g_gfx->SetViewportAndScissor(entry->framebuffer->GetRect()); + g_gfx->SetPipeline(copy_pipeline); + g_gfx->SetTexture(0, src_texture); + g_gfx->SetSamplerState(0, linear_filter ? RenderState::GetLinearSamplerState() : + RenderState::GetPointSamplerState()); + g_gfx->Draw(0, 3); + g_gfx->EndUtilityDrawing(); entry->texture->FinishedRendering(); } @@ -2842,15 +2879,15 @@ void TextureCacheBase::CopyEFB(AbstractStagingTexture* dst, const EFBCopyParams& return; } - const auto scaled_src_rect = g_renderer->ConvertEFBRectangle(src_rect); - const auto framebuffer_rect = g_renderer->ConvertFramebufferRectangle( + const auto scaled_src_rect = g_framebuffer_manager->ConvertEFBRectangle(src_rect); + const auto framebuffer_rect = g_gfx->ConvertFramebufferRectangle( scaled_src_rect, g_framebuffer_manager->GetEFBFramebuffer()); AbstractTexture* src_texture = params.depth ? g_framebuffer_manager->ResolveEFBDepthTexture(framebuffer_rect) : g_framebuffer_manager->ResolveEFBColorTexture(framebuffer_rect); src_texture->FinishedRendering(); - g_renderer->BeginUtilityDrawing(); + g_gfx->BeginUtilityDrawing(); // Fill uniform buffer. struct Uniforms @@ -2890,21 +2927,21 @@ void TextureCacheBase::CopyEFB(AbstractStagingTexture* dst, const EFBCopyParams& const auto encode_rect = MathUtil::Rectangle(0, 0, render_width, render_height); // Render to GPU texture, and then copy to CPU-accessible texture. - g_renderer->SetAndDiscardFramebuffer(m_efb_encoding_framebuffer.get()); - g_renderer->SetViewportAndScissor(encode_rect); - g_renderer->SetPipeline(copy_pipeline); - g_renderer->SetTexture(0, src_texture); - g_renderer->SetSamplerState(0, linear_filter ? RenderState::GetLinearSamplerState() : - RenderState::GetPointSamplerState()); - g_renderer->Draw(0, 3); + g_gfx->SetAndDiscardFramebuffer(m_efb_encoding_framebuffer.get()); + g_gfx->SetViewportAndScissor(encode_rect); + g_gfx->SetPipeline(copy_pipeline); + g_gfx->SetTexture(0, src_texture); + g_gfx->SetSamplerState(0, linear_filter ? RenderState::GetLinearSamplerState() : + RenderState::GetPointSamplerState()); + g_gfx->Draw(0, 3); dst->CopyFromTexture(m_efb_encoding_texture.get(), encode_rect, 0, 0, encode_rect); - g_renderer->EndUtilityDrawing(); + g_gfx->EndUtilityDrawing(); // Flush if there's sufficient draws between this copy and the last. g_vertex_manager->OnEFBCopyToRAM(); } -bool TextureCacheBase::DecodeTextureOnGPU(TCacheEntry* entry, u32 dst_level, const u8* data, +bool TextureCacheBase::DecodeTextureOnGPU(RcTcacheEntry& entry, u32 dst_level, const u8* data, u32 data_size, TextureFormat format, u32 width, u32 height, u32 aligned_width, u32 aligned_height, u32 row_stride, const u8* palette, @@ -2951,12 +2988,12 @@ bool TextureCacheBase::DecodeTextureOnGPU(TCacheEntry* entry, u32 dst_level, con aligned_height, src_offset, row_stride / bytes_per_buffer_elem, palette_offset}; g_vertex_manager->UploadUtilityUniforms(&uniforms, sizeof(uniforms)); - g_renderer->SetComputeImageTexture(m_decoding_texture.get(), false, true); + g_gfx->SetComputeImageTexture(m_decoding_texture.get(), false, true); auto dispatch_groups = TextureConversionShaderTiled::GetDispatchCount(info, aligned_width, aligned_height); - g_renderer->DispatchComputeShader(shader, info->group_size_x, info->group_size_y, 1, - dispatch_groups.first, dispatch_groups.second, 1); + g_gfx->DispatchComputeShader(shader, info->group_size_x, info->group_size_y, 1, + dispatch_groups.first, dispatch_groups.second, 1); // Copy from decoding texture -> final texture // This is because we don't want to have to create compute view for every layer @@ -2967,7 +3004,7 @@ bool TextureCacheBase::DecodeTextureOnGPU(TCacheEntry* entry, u32 dst_level, con return true; } -u32 TextureCacheBase::TCacheEntry::BytesPerRow() const +u32 TCacheEntry::BytesPerRow() const { // RGBA takes two cache lines per block; all others take one const u32 bytes_per_block = format == TextureFormat::RGBA8 ? 64 : 32; @@ -2975,7 +3012,7 @@ u32 TextureCacheBase::TCacheEntry::BytesPerRow() const return NumBlocksX() * bytes_per_block; } -u32 TextureCacheBase::TCacheEntry::NumBlocksX() const +u32 TCacheEntry::NumBlocksX() const { const u32 blockW = TexDecoder_GetBlockWidthInTexels(format.texfmt); @@ -2985,7 +3022,7 @@ u32 TextureCacheBase::TCacheEntry::NumBlocksX() const return actualWidth / blockW; } -u32 TextureCacheBase::TCacheEntry::NumBlocksY() const +u32 TCacheEntry::NumBlocksY() const { u32 blockH = TexDecoder_GetBlockHeightInTexels(format.texfmt); // Round up source height to multiple of block size @@ -2994,7 +3031,7 @@ u32 TextureCacheBase::TCacheEntry::NumBlocksY() const return actualHeight / blockH; } -void TextureCacheBase::TCacheEntry::SetXfbCopy(u32 stride) +void TCacheEntry::SetXfbCopy(u32 stride) { is_efb_copy = false; is_xfb_copy = true; @@ -3006,7 +3043,7 @@ void TextureCacheBase::TCacheEntry::SetXfbCopy(u32 stride) size_in_bytes = memory_stride * NumBlocksY(); } -void TextureCacheBase::TCacheEntry::SetEfbCopy(u32 stride) +void TCacheEntry::SetEfbCopy(u32 stride) { is_efb_copy = true; is_xfb_copy = false; @@ -3018,14 +3055,14 @@ void TextureCacheBase::TCacheEntry::SetEfbCopy(u32 stride) size_in_bytes = memory_stride * NumBlocksY(); } -void TextureCacheBase::TCacheEntry::SetNotCopy() +void TCacheEntry::SetNotCopy() { is_efb_copy = false; is_xfb_copy = false; is_xfb_container = false; } -int TextureCacheBase::TCacheEntry::HashSampleSize() const +int TCacheEntry::HashSampleSize() const { if (should_force_safe_hashing) { @@ -3035,7 +3072,7 @@ int TextureCacheBase::TCacheEntry::HashSampleSize() const return g_ActiveConfig.iSafeTextureCache_ColorSamples; } -u64 TextureCacheBase::TCacheEntry::CalculateHash() const +u64 TCacheEntry::CalculateHash() const { const u32 bytes_per_row = BytesPerRow(); const u32 hash_sample_size = HashSampleSize(); diff --git a/Source/Core/VideoCommon/TextureCacheBase.h b/Source/Core/VideoCommon/TextureCacheBase.h index 4fe11a64f4..958d7704fe 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.h +++ b/Source/Core/VideoCommon/TextureCacheBase.h @@ -18,12 +18,15 @@ #include "Common/BitSet.h" #include "Common/CommonTypes.h" +#include "Common/Flag.h" #include "Common/MathUtil.h" + #include "VideoCommon/AbstractTexture.h" #include "VideoCommon/BPMemory.h" #include "VideoCommon/TextureConfig.h" #include "VideoCommon/TextureDecoder.h" #include "VideoCommon/TextureInfo.h" +#include "VideoCommon/VideoEvents.h" class AbstractFramebuffer; class AbstractStagingTexture; @@ -33,6 +36,8 @@ struct VideoConfig; constexpr std::string_view EFB_DUMP_PREFIX = "efb1"; constexpr std::string_view XFB_DUMP_PREFIX = "xfb1"; +static constexpr int FRAMECOUNT_INVALID = 0; + struct TextureAndTLUTFormat { TextureAndTLUTFormat(TextureFormat texfmt_ = TextureFormat::I4, @@ -86,6 +91,7 @@ struct EFBCopyParams template <> struct fmt::formatter { + std::shared_ptr state; constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); } template auto format(const EFBCopyParams& uid, FormatContext& ctx) const @@ -103,121 +109,130 @@ struct fmt::formatter } }; +struct TCacheEntry +{ + // common members + std::unique_ptr texture; + std::unique_ptr framebuffer; + u32 addr = 0; + u32 size_in_bytes = 0; + u64 base_hash = 0; + u64 hash = 0; // for paletted textures, hash = base_hash ^ palette_hash + TextureAndTLUTFormat format; + u32 memory_stride = 0; + bool is_efb_copy = false; + bool is_custom_tex = false; + bool may_have_overlapping_textures = true; + // indicates that the mips in this texture are arbitrary content, aren't just downscaled + bool has_arbitrary_mips = false; + bool should_force_safe_hashing = false; // for XFB + bool is_xfb_copy = false; + bool is_xfb_container = false; + u64 id = 0; + u32 content_semaphore = 0; // Counts up + + // Indicates that this TCacheEntry has been invalided from textures_by_address + bool invalidated = false; + + bool reference_changed = false; // used by xfb to determine when a reference xfb changed + + // Texture dimensions from the GameCube's point of view + u32 native_width = 0; + u32 native_height = 0; + u32 native_levels = 0; + + // used to delete textures which haven't been used for TEXTURE_KILL_THRESHOLD frames + int frameCount = FRAMECOUNT_INVALID; + + // Keep an iterator to the entry in textures_by_hash, so it does not need to be searched when + // removing the cache entry + std::multimap>::iterator textures_by_hash_iter; + + // This is used to keep track of both: + // * efb copies used by this partially updated texture + // * partially updated textures which refer to this efb copy + std::unordered_set references; + + // Pending EFB copy + std::unique_ptr pending_efb_copy; + u32 pending_efb_copy_width = 0; + u32 pending_efb_copy_height = 0; + + std::string texture_info_name = ""; + + explicit TCacheEntry(std::unique_ptr tex, + std::unique_ptr fb); + + ~TCacheEntry(); + + void SetGeneralParameters(u32 _addr, u32 _size, TextureAndTLUTFormat _format, + bool force_safe_hashing) + { + addr = _addr; + size_in_bytes = _size; + format = _format; + should_force_safe_hashing = force_safe_hashing; + } + + void SetDimensions(unsigned int _native_width, unsigned int _native_height, + unsigned int _native_levels) + { + native_width = _native_width; + native_height = _native_height; + native_levels = _native_levels; + memory_stride = _native_width; + } + + void SetHashes(u64 _base_hash, u64 _hash) + { + base_hash = _base_hash; + hash = _hash; + } + + // This texture entry is used by the other entry as a sub-texture + void CreateReference(TCacheEntry* other_entry) + { + // References are two-way, so they can easily be destroyed later + this->references.emplace(other_entry); + other_entry->references.emplace(this); + } + + // Acquiring a content lock will lock the current contents and prevent texture cache from + // reusing the same entry for a newer version of the texture. + void AcquireContentLock() { content_semaphore++; } + void ReleaseContentLock() { content_semaphore--; } + + // Can this be mutated? + bool IsLocked() const { return content_semaphore > 0; } + + void SetXfbCopy(u32 stride); + void SetEfbCopy(u32 stride); + void SetNotCopy(); + + bool OverlapsMemoryRange(u32 range_address, u32 range_size) const; + + bool IsEfbCopy() const { return is_efb_copy; } + bool IsCopy() const { return is_xfb_copy || is_efb_copy; } + u32 NumBlocksX() const; + u32 NumBlocksY() const; + u32 BytesPerRow() const; + + u64 CalculateHash() const; + + int HashSampleSize() const; + u32 GetWidth() const { return texture->GetConfig().width; } + u32 GetHeight() const { return texture->GetConfig().height; } + u32 GetNumLevels() const { return texture->GetConfig().levels; } + u32 GetNumLayers() const { return texture->GetConfig().layers; } + AbstractTextureFormat GetFormat() const { return texture->GetConfig().format; } + void DoState(PointerWrap& p); +}; + +using RcTcacheEntry = std::shared_ptr; + class TextureCacheBase { -private: - static const int FRAMECOUNT_INVALID = 0; - public: - struct TCacheEntry - { - // common members - std::unique_ptr texture; - std::unique_ptr framebuffer; - u32 addr = 0; - u32 size_in_bytes = 0; - u64 base_hash = 0; - u64 hash = 0; // for paletted textures, hash = base_hash ^ palette_hash - TextureAndTLUTFormat format; - u32 memory_stride = 0; - bool is_efb_copy = false; - bool is_custom_tex = false; - bool may_have_overlapping_textures = true; - bool tmem_only = false; // indicates that this texture only exists in the tmem cache - bool has_arbitrary_mips = false; // indicates that the mips in this texture are arbitrary - // content, aren't just downscaled - bool should_force_safe_hashing = false; // for XFB - bool is_xfb_copy = false; - bool is_xfb_container = false; - u64 id = 0; - - bool reference_changed = false; // used by xfb to determine when a reference xfb changed - - // Texture dimensions from the GameCube's point of view - u32 native_width = 0; - u32 native_height = 0; - u32 native_levels = 0; - - // used to delete textures which haven't been used for TEXTURE_KILL_THRESHOLD frames - int frameCount = FRAMECOUNT_INVALID; - - // Keep an iterator to the entry in textures_by_hash, so it does not need to be searched when - // removing the cache entry - std::multimap::iterator textures_by_hash_iter; - - // This is used to keep track of both: - // * efb copies used by this partially updated texture - // * partially updated textures which refer to this efb copy - std::unordered_set references; - - // Pending EFB copy - std::unique_ptr pending_efb_copy; - u32 pending_efb_copy_width = 0; - u32 pending_efb_copy_height = 0; - bool pending_efb_copy_invalidated = false; - - std::string texture_info_name = ""; - - explicit TCacheEntry(std::unique_ptr tex, - std::unique_ptr fb); - - ~TCacheEntry(); - - void SetGeneralParameters(u32 _addr, u32 _size, TextureAndTLUTFormat _format, - bool force_safe_hashing) - { - addr = _addr; - size_in_bytes = _size; - format = _format; - should_force_safe_hashing = force_safe_hashing; - } - - void SetDimensions(unsigned int _native_width, unsigned int _native_height, - unsigned int _native_levels) - { - native_width = _native_width; - native_height = _native_height; - native_levels = _native_levels; - memory_stride = _native_width; - } - - void SetHashes(u64 _base_hash, u64 _hash) - { - base_hash = _base_hash; - hash = _hash; - } - - // This texture entry is used by the other entry as a sub-texture - void CreateReference(TCacheEntry* other_entry) - { - // References are two-way, so they can easily be destroyed later - this->references.emplace(other_entry); - other_entry->references.emplace(this); - } - - void SetXfbCopy(u32 stride); - void SetEfbCopy(u32 stride); - void SetNotCopy(); - - bool OverlapsMemoryRange(u32 range_address, u32 range_size) const; - - bool IsEfbCopy() const { return is_efb_copy; } - bool IsCopy() const { return is_xfb_copy || is_efb_copy; } - u32 NumBlocksX() const; - u32 NumBlocksY() const; - u32 BytesPerRow() const; - - u64 CalculateHash() const; - - int HashSampleSize() const; - u32 GetWidth() const { return texture->GetConfig().width; } - u32 GetHeight() const { return texture->GetConfig().height; } - u32 GetNumLevels() const { return texture->GetConfig().levels; } - u32 GetNumLayers() const { return texture->GetConfig().layers; } - AbstractTextureFormat GetFormat() const { return texture->GetConfig().format; } - void DoState(PointerWrap& p); - }; - // Minimal version of TCacheEntry just for TexPool struct TexPoolEntry { @@ -232,6 +247,7 @@ public: virtual ~TextureCacheBase(); bool Initialize(); + void Shutdown(); void OnConfigChanged(const VideoConfig& config); void ForceReload(); @@ -241,12 +257,13 @@ public: void Cleanup(int _frameCount); void Invalidate(); + void ReleaseToPool(TCacheEntry* entry); TCacheEntry* Load(const TextureInfo& texture_info); - TCacheEntry* GetTexture(const int textureCacheSafetyColorSampleSize, - const TextureInfo& texture_info); - TCacheEntry* GetXFBTexture(u32 address, u32 width, u32 height, u32 stride, - MathUtil::Rectangle* display_rect); + RcTcacheEntry GetTexture(const int textureCacheSafetyColorSampleSize, + const TextureInfo& texture_info); + RcTcacheEntry GetXFBTexture(u32 address, u32 width, u32 height, u32 stride, + MathUtil::Rectangle* display_rect); virtual void BindTextures(BitSet32 used_textures); void CopyRenderTargetToTexture(u32 dstAddr, EFBCopyFormat dstFormat, u32 width, u32 height, @@ -256,11 +273,14 @@ public: bool clamp_bottom, const CopyFilterCoefficients::Values& filter_coefficients); - void ScaleTextureCacheEntryTo(TCacheEntry* entry, u32 new_width, u32 new_height); + void ScaleTextureCacheEntryTo(RcTcacheEntry& entry, u32 new_width, u32 new_height); // Flushes all pending EFB copies to emulated RAM. void FlushEFBCopies(); + // Flush any Bound textures that can't be reused + void FlushStaleBinds(); + // Texture Serialization void SerializeTexture(AbstractTexture* tex, const TextureConfig& config, PointerWrap& p); std::optional DeserializeTexture(PointerWrap& p); @@ -271,13 +291,16 @@ public: static bool AllCopyFilterCoefsNeeded(const std::array& coefficients); static bool CopyFilterCanOverflow(const std::array& coefficients); + // Will forcibly reload all textures when the frame next ends + void ForceReloadTextures() { m_force_reload_textures.Set(); } + protected: // Decodes the specified data to the GPU texture specified by entry. // Returns false if the configuration is not supported. // width, height are the size of the image in pixels. // aligned_width, aligned_height are the size of the image in pixels, aligned to the block size. // row_stride is the number of bytes for a row of blocks, not pixels. - bool DecodeTextureOnGPU(TCacheEntry* entry, u32 dst_level, const u8* data, u32 data_size, + bool DecodeTextureOnGPU(RcTcacheEntry& entry, u32 dst_level, const u8* data, u32 data_size, TextureFormat format, u32 width, u32 height, u32 aligned_width, u32 aligned_height, u32 row_stride, const u8* palette, TLUTFormat palette_format); @@ -287,7 +310,7 @@ protected: const MathUtil::Rectangle& src_rect, bool scale_by_half, bool linear_filter, float y_scale, float gamma, bool clamp_top, bool clamp_bottom, const std::array& filter_coefficients); - virtual void CopyEFBToCacheEntry(TCacheEntry* entry, bool is_depth_copy, + virtual void CopyEFBToCacheEntry(RcTcacheEntry& entry, bool is_depth_copy, const MathUtil::Rectangle& src_rect, bool scale_by_half, bool linear_filter, EFBCopyFormat dst_format, bool is_intensity, float gamma, bool clamp_top, bool clamp_bottom, @@ -296,32 +319,31 @@ protected: alignas(16) u8* temp = nullptr; size_t temp_size = 0; - std::array bound_textures{}; - static std::bitset<8> valid_bind_points; - private: - using TexAddrCache = std::multimap; - using TexHashCache = std::multimap; + using TexAddrCache = std::multimap; + using TexHashCache = std::multimap; + using TexPool = std::unordered_multimap; bool CreateUtilityTextures(); void SetBackupConfig(const VideoConfig& config); - TCacheEntry* GetXFBFromCache(u32 address, u32 width, u32 height, u32 stride); + RcTcacheEntry GetXFBFromCache(u32 address, u32 width, u32 height, u32 stride); - TCacheEntry* ApplyPaletteToEntry(TCacheEntry* entry, const u8* palette, TLUTFormat tlutfmt); + RcTcacheEntry ApplyPaletteToEntry(RcTcacheEntry& entry, const u8* palette, TLUTFormat tlutfmt); - TCacheEntry* ReinterpretEntry(const TCacheEntry* existing_entry, TextureFormat new_format); + RcTcacheEntry ReinterpretEntry(const RcTcacheEntry& existing_entry, TextureFormat new_format); - TCacheEntry* DoPartialTextureUpdates(TCacheEntry* entry_to_update, const u8* palette, - TLUTFormat tlutfmt); - void StitchXFBCopy(TCacheEntry* entry_to_update); + RcTcacheEntry DoPartialTextureUpdates(RcTcacheEntry& entry_to_update, const u8* palette, + TLUTFormat tlutfmt); + void StitchXFBCopy(RcTcacheEntry& entry_to_update); - void DumpTexture(TCacheEntry* entry, std::string basename, unsigned int level, bool is_arbitrary); + void DumpTexture(RcTcacheEntry& entry, std::string basename, unsigned int level, + bool is_arbitrary); void CheckTempSize(size_t required_size); - TCacheEntry* AllocateCacheEntry(const TextureConfig& config); + RcTcacheEntry AllocateCacheEntry(const TextureConfig& config); std::optional AllocateTexture(const TextureConfig& config); TexPool::iterator FindMatchingTextureFromPool(const TextureConfig& config); TexAddrCache::iterator GetTexCacheIter(TCacheEntry* entry); @@ -359,8 +381,18 @@ private: void DoSaveState(PointerWrap& p); void DoLoadState(PointerWrap& p); + // textures_by_address is the authoritive version of what's actually "in" the texture cache + // but it's possible for invalidated TCache entries to live on elsewhere TexAddrCache textures_by_address; + + // textures_by_hash is an alternative view of the texture cache + // All textures in here will also be in textures_by_address TexHashCache textures_by_hash; + + // bound_textures are actually active in the current draw + // It's valid for textures to be in here after they've been invalidated + std::array bound_textures{}; + TexPool texture_pool; u64 last_entry_id = 0; @@ -395,12 +427,19 @@ private: // List of pending EFB copies. It is important that the order is preserved for these, // so that overlapping textures are written to guest RAM in the order they are issued. - std::vector m_pending_efb_copies; + // It's valid for textures to live be in here after they've been invalidated + std::vector m_pending_efb_copies; // Staging texture used for readbacks. // We store this in the class so that the same staging texture can be used for multiple // readbacks, saving the overhead of allocating a new buffer every time. std::unique_ptr m_readback_texture; + + void OnFrameEnd(); + + Common::Flag m_force_reload_textures; + Common::EventHook m_frame_event = + AfterFrameEvent::Register([this] { OnFrameEnd(); }, "TextureCache"); }; extern std::unique_ptr g_texture_cache; diff --git a/Source/Core/VideoCommon/VertexLoaderManager.cpp b/Source/Core/VideoCommon/VertexLoaderManager.cpp index 9bee673db4..f3d580b801 100644 --- a/Source/Core/VideoCommon/VertexLoaderManager.cpp +++ b/Source/Core/VideoCommon/VertexLoaderManager.cpp @@ -20,12 +20,12 @@ #include "Core/HW/Memmap.h" #include "Core/System.h" +#include "VideoCommon/AbstractGfx.h" #include "VideoCommon/BPMemory.h" #include "VideoCommon/CPMemory.h" #include "VideoCommon/DataReader.h" #include "VideoCommon/IndexGenerator.h" #include "VideoCommon/NativeVertexFormat.h" -#include "VideoCommon/RenderBase.h" #include "VideoCommon/Statistics.h" #include "VideoCommon/VertexLoaderBase.h" #include "VideoCommon/VertexManagerBase.h" @@ -140,7 +140,7 @@ NativeVertexFormat* GetOrCreateMatchingFormat(const PortableVertexDeclaration& d auto iter = s_native_vertex_map.find(decl); if (iter == s_native_vertex_map.end()) { - std::unique_ptr fmt = g_renderer->CreateNativeVertexFormat(decl); + std::unique_ptr fmt = g_gfx->CreateNativeVertexFormat(decl); auto ipair = s_native_vertex_map.emplace(decl, std::move(fmt)); iter = ipair.first; } diff --git a/Source/Core/VideoCommon/VertexManagerBase.cpp b/Source/Core/VideoCommon/VertexManagerBase.cpp index 1ccfebad0c..9686809c88 100644 --- a/Source/Core/VideoCommon/VertexManagerBase.cpp +++ b/Source/Core/VideoCommon/VertexManagerBase.cpp @@ -17,18 +17,19 @@ #include "Core/DolphinAnalytics.h" #include "Core/System.h" +#include "VideoCommon/AbstractGfx.h" #include "VideoCommon/BPMemory.h" #include "VideoCommon/BoundingBox.h" #include "VideoCommon/DataReader.h" #include "VideoCommon/FramebufferManager.h" #include "VideoCommon/GeometryShaderManager.h" #include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h" #include "VideoCommon/IndexGenerator.h" #include "VideoCommon/NativeVertexFormat.h" #include "VideoCommon/OpcodeDecoding.h" #include "VideoCommon/PerfQueryBase.h" #include "VideoCommon/PixelShaderManager.h" -#include "VideoCommon/RenderBase.h" #include "VideoCommon/Statistics.h" #include "VideoCommon/TextureCacheBase.h" #include "VideoCommon/TextureInfo.h" @@ -103,6 +104,7 @@ VertexManagerBase::~VertexManagerBase() = default; bool VertexManagerBase::Initialize() { + m_frame_end_event = AfterFrameEvent::Register([this] { OnEndFrame(); }, "VertexManagerBase"); m_index_generator.Init(); m_cpu_cull.Init(); return true; @@ -323,13 +325,13 @@ void VertexManagerBase::CommitBuffer(u32 num_vertices, u32 vertex_stride, u32 nu void VertexManagerBase::DrawCurrentBatch(u32 base_index, u32 num_indices, u32 base_vertex) { // If bounding box is enabled, we need to flush any changes first, then invalidate what we have. - if (g_renderer->IsBBoxEnabled() && g_ActiveConfig.bBBoxEnable && + if (g_bounding_box->IsEnabled() && g_ActiveConfig.bBBoxEnable && g_ActiveConfig.backend_info.bSupportsBBox) { - g_renderer->BBoxFlush(); + g_bounding_box->Flush(); } - g_renderer->DrawIndexed(base_index, num_indices, base_vertex); + g_gfx->DrawIndexed(base_index, num_indices, base_vertex); } void VertexManagerBase::UploadUniforms() @@ -415,6 +417,12 @@ void VertexManagerBase::Flush() m_is_flushed = true; + if (m_draw_counter == 0) + { + // This is more or less the start of the Frame + BeforeFrameEvent::Trigger(); + } + if (xfmem.numTexGen.numTexGens != bpmem.genMode.numtexgens || xfmem.numChan.numColorChans != bpmem.genMode.numcolchans) { @@ -554,8 +562,7 @@ void VertexManagerBase::Flush() { bool skip = false; GraphicsModActionData::DrawStarted draw_started{&skip}; - for (const auto action : - g_renderer->GetGraphicsModManager().GetDrawStartedActions(texture_name)) + for (const auto action : g_graphics_mod_manager->GetDrawStartedActions(texture_name)) { action->OnDrawStarted(&draw_started); } @@ -599,7 +606,7 @@ void VertexManagerBase::Flush() UpdatePipelineObject(); if (m_current_pipeline_object) { - g_renderer->SetPipeline(m_current_pipeline_object); + g_gfx->SetPipeline(m_current_pipeline_object); if (PerfQueryBase::ShouldEmulate()) g_perf_query->EnableQuery(bpmem.zcontrol.early_ztest ? PQG_ZCOMP_ZCOMPLOC : PQG_ZCOMP); @@ -877,7 +884,7 @@ void VertexManagerBase::OnDraw() u32 diff = m_draw_counter - m_last_efb_copy_draw_counter; if (m_unflushed_efb_copy && diff > MINIMUM_DRAW_CALLS_PER_COMMAND_BUFFER_FOR_READBACK) { - g_renderer->Flush(); + g_gfx->Flush(); m_unflushed_efb_copy = false; m_last_efb_copy_draw_counter = m_draw_counter; } @@ -892,7 +899,7 @@ void VertexManagerBase::OnDraw() m_scheduled_command_buffer_kicks.end(), m_draw_counter)) { // Kick a command buffer on the background thread. - g_renderer->Flush(); + g_gfx->Flush(); m_unflushed_efb_copy = false; m_last_efb_copy_draw_counter = m_draw_counter; } @@ -927,7 +934,7 @@ void VertexManagerBase::OnEFBCopyToRAM() } m_unflushed_efb_copy = false; - g_renderer->Flush(); + g_gfx->Flush(); } void VertexManagerBase::OnEndFrame() @@ -988,4 +995,10 @@ void VertexManagerBase::OnEndFrame() #endif m_cpu_accesses_this_frame.clear(); + + // We invalidate the pipeline object at the start of the frame. + // This is for the rare case where only a single pipeline configuration is used, + // and hybrid ubershaders have compiled the specialized shader, but without any + // state changes the specialized shader will not take over. + InvalidatePipelineObject(); } diff --git a/Source/Core/VideoCommon/VertexManagerBase.h b/Source/Core/VideoCommon/VertexManagerBase.h index 3b8180c5d2..775d8e2787 100644 --- a/Source/Core/VideoCommon/VertexManagerBase.h +++ b/Source/Core/VideoCommon/VertexManagerBase.h @@ -13,6 +13,7 @@ #include "VideoCommon/IndexGenerator.h" #include "VideoCommon/RenderState.h" #include "VideoCommon/ShaderCache.h" +#include "VideoCommon/VideoEvents.h" class DataReader; class NativeVertexFormat; @@ -228,6 +229,8 @@ private: std::vector m_cpu_accesses_this_frame; std::vector m_scheduled_command_buffer_kicks; bool m_allow_background_execution = true; + + Common::EventHook m_frame_end_event; }; extern std::unique_ptr g_vertex_manager; diff --git a/Source/Core/VideoCommon/VertexShaderManager.cpp b/Source/Core/VideoCommon/VertexShaderManager.cpp index deda07fd65..2408281da6 100644 --- a/Source/Core/VideoCommon/VertexShaderManager.cpp +++ b/Source/Core/VideoCommon/VertexShaderManager.cpp @@ -20,9 +20,10 @@ #include "VideoCommon/BPFunctions.h" #include "VideoCommon/BPMemory.h" #include "VideoCommon/CPMemory.h" +#include "VideoCommon/FramebufferManager.h" #include "VideoCommon/FreeLookCamera.h" #include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h" -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h" #include "VideoCommon/Statistics.h" #include "VideoCommon/VertexLoaderManager.h" #include "VideoCommon/VertexManagerBase.h" @@ -156,6 +157,25 @@ void VertexShaderManager::SetProjectionMatrix() } } +bool VertexShaderManager::UseVertexDepthRange() +{ + // We can't compute the depth range in the vertex shader if we don't support depth clamp. + if (!g_ActiveConfig.backend_info.bSupportsDepthClamp) + return false; + + // We need a full depth range if a ztexture is used. + if (bpmem.ztex2.op != ZTexOp::Disabled && !bpmem.zcontrol.early_ztest) + return true; + + // If an inverted depth range is unsupported, we also need to check if the range is inverted. + if (!g_ActiveConfig.backend_info.bSupportsReversedDepthRange && xfmem.viewport.zRange < 0.0f) + return true; + + // If an oversized depth range or a ztexture is used, we need to calculate the depth range + // in the vertex shader. + return fabs(xfmem.viewport.zRange) > 16777215.0f || fabs(xfmem.viewport.farZ) > 16777215.0f; +} + // Syncs the shader constant buffers with xfmem // TODO: A cleaner way to control the matrices without making a mess in the parameters field void VertexShaderManager::SetConstants(const std::vector& textures) @@ -338,10 +358,10 @@ void VertexShaderManager::SetConstants(const std::vector& textures) const bool bUseVertexRounding = g_ActiveConfig.UseVertexRounding(); const float viewport_width = bUseVertexRounding ? (2.f * xfmem.viewport.wd) : - g_renderer->EFBToScaledXf(2.f * xfmem.viewport.wd); + g_framebuffer_manager->EFBToScaledXf(2.f * xfmem.viewport.wd); const float viewport_height = bUseVertexRounding ? (2.f * xfmem.viewport.ht) : - g_renderer->EFBToScaledXf(2.f * xfmem.viewport.ht); + g_framebuffer_manager->EFBToScaledXf(2.f * xfmem.viewport.ht); const float pixel_size_x = 2.f / viewport_width; const float pixel_size_y = 2.f / viewport_height; constants.pixelcentercorrection[0] = pixel_center_correction * pixel_size_x; @@ -354,7 +374,7 @@ void VertexShaderManager::SetConstants(const std::vector& textures) constants.viewport[0] = (2.f * xfmem.viewport.wd); constants.viewport[1] = (2.f * xfmem.viewport.ht); - if (g_renderer->UseVertexDepthRange()) + if (UseVertexDepthRange()) { // Oversized depth ranges are handled in the vertex shader. We need to reverse // the far value to use the reversed-Z trick. @@ -386,16 +406,15 @@ void VertexShaderManager::SetConstants(const std::vector& textures) std::vector projection_actions; if (g_ActiveConfig.bGraphicMods) { - for (const auto action : - g_renderer->GetGraphicsModManager().GetProjectionActions(xfmem.projection.type)) + for (const auto action : g_graphics_mod_manager->GetProjectionActions(xfmem.projection.type)) { projection_actions.push_back(action); } for (const auto& texture : textures) { - for (const auto action : g_renderer->GetGraphicsModManager().GetProjectionTextureActions( - xfmem.projection.type, texture)) + for (const auto action : + g_graphics_mod_manager->GetProjectionTextureActions(xfmem.projection.type, texture)) { projection_actions.push_back(action); } diff --git a/Source/Core/VideoCommon/VertexShaderManager.h b/Source/Core/VideoCommon/VertexShaderManager.h index 9a150980da..320e04985d 100644 --- a/Source/Core/VideoCommon/VertexShaderManager.h +++ b/Source/Core/VideoCommon/VertexShaderManager.h @@ -44,6 +44,8 @@ public: // (i.e. VertexShaderManager::SetConstants needs to be called before using this!) void TransformToClipSpace(const float* data, float* out, u32 mtxIdx); + static bool UseVertexDepthRange(); + VertexShaderConstants constants{}; bool dirty = false; diff --git a/Source/Core/VideoCommon/VideoBackendBase.cpp b/Source/Core/VideoCommon/VideoBackendBase.cpp index 8baa6f0be7..7788fe0dfb 100644 --- a/Source/Core/VideoCommon/VideoBackendBase.cpp +++ b/Source/Core/VideoCommon/VideoBackendBase.cpp @@ -39,16 +39,22 @@ #include "VideoBackends/Metal/VideoBackend.h" #endif +#include "VideoCommon/AbstractGfx.h" #include "VideoCommon/AsyncRequests.h" #include "VideoCommon/BPStructs.h" +#include "VideoCommon/BoundingBox.h" #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/GraphicsModSystem/Runtime/GraphicsModManager.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" @@ -58,6 +64,7 @@ #include "VideoCommon/VideoCommon.h" #include "VideoCommon/VideoConfig.h" #include "VideoCommon/VideoState.h" +#include "VideoCommon/Widescreen.h" VideoBackendBase* g_video_backend = nullptr; @@ -91,7 +98,7 @@ void VideoBackendBase::Video_ExitLoop() void VideoBackendBase::Video_OutputXFB(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks) { - if (m_initialized && g_renderer && !g_ActiveConfig.bImmediateXFB) + if (m_initialized && g_presenter && !g_ActiveConfig.bImmediateXFB) { auto& system = Core::System::GetInstance(); system.GetFifo().SyncGPU(Fifo::SyncGPUReason::Swap); @@ -312,7 +319,25 @@ void VideoBackendBase::DoState(PointerWrap& p) system.GetFifo().GpuMaySleep(); } -void VideoBackendBase::InitializeShared() +bool VideoBackendBase::InitializeShared(std::unique_ptr gfx, + std::unique_ptr vertex_manager, + std::unique_ptr perf_query, + std::unique_ptr bounding_box) +{ + // All hardware backends use the default RendererBase and TextureCacheBase. + // Only Null and Software backends override them + + return InitializeShared(std::move(gfx), std::move(vertex_manager), std::move(perf_query), + std::move(bounding_box), std::make_unique(), + std::make_unique()); +} + +bool VideoBackendBase::InitializeShared(std::unique_ptr gfx, + std::unique_ptr vertex_manager, + std::unique_ptr perf_query, + std::unique_ptr bounding_box, + std::unique_ptr renderer, + std::unique_ptr texture_cache) { memset(reinterpret_cast(&g_main_cp_state), 0, sizeof(g_main_cp_state)); memset(reinterpret_cast(&g_preprocess_cp_state), 0, sizeof(g_preprocess_cp_state)); @@ -321,6 +346,32 @@ void VideoBackendBase::InitializeShared() // do not initialize again for the config window m_initialized = true; + g_gfx = std::move(gfx); + g_vertex_manager = std::move(vertex_manager); + g_perf_query = std::move(perf_query); + g_bounding_box = std::move(bounding_box); + + // Null and Software Backends supply their own derived Renderer and Texture Cache + g_texture_cache = std::move(texture_cache); + g_renderer = std::move(renderer); + + g_presenter = std::make_unique(); + g_frame_dumper = std::make_unique(); + g_framebuffer_manager = std::make_unique(); + g_shader_cache = std::make_unique(); + g_graphics_mod_manager = std::make_unique(); + g_widescreen = std::make_unique(); + + if (!g_vertex_manager->Initialize() || !g_shader_cache->Initialize() || + !g_perf_query->Initialize() || !g_presenter->Initialize() || + !g_framebuffer_manager->Initialize() || !g_texture_cache->Initialize() || + !g_bounding_box->Initialize() || !g_graphics_mod_manager->Initialize()) + { + PanicAlertFmtT("Failed to initialize renderer classes"); + Shutdown(); + return false; + } + auto& system = Core::System::GetInstance(); auto& command_processor = system.GetCommandProcessor(); command_processor.Init(system); @@ -335,10 +386,33 @@ void VideoBackendBase::InitializeShared() g_Config.VerifyValidity(); UpdateActiveConfig(); + + g_shader_cache->InitializeShaderCache(); + + return true; } void VideoBackendBase::ShutdownShared() { + g_frame_dumper.reset(); + g_presenter.reset(); + + if (g_shader_cache) + g_shader_cache->Shutdown(); + if (g_texture_cache) + g_texture_cache->Shutdown(); + + g_bounding_box.reset(); + g_perf_query.reset(); + g_texture_cache.reset(); + g_framebuffer_manager.reset(); + g_shader_cache.reset(); + g_vertex_manager.reset(); + g_renderer.reset(); + g_widescreen.reset(); + g_presenter.reset(); + g_gfx.reset(); + m_initialized = false; auto& system = Core::System::GetInstance(); diff --git a/Source/Core/VideoCommon/VideoBackendBase.h b/Source/Core/VideoCommon/VideoBackendBase.h index 8238bc2067..2ce235f8f8 100644 --- a/Source/Core/VideoCommon/VideoBackendBase.h +++ b/Source/Core/VideoCommon/VideoBackendBase.h @@ -18,6 +18,12 @@ class Mapping; } class PointerWrap; +class AbstractGfx; +class BoundingBox; +class Renderer; +class TextureCacheBase; +class VertexManagerBase; + enum class FieldType { Odd, @@ -71,7 +77,19 @@ public: void DoState(PointerWrap& p); protected: - void InitializeShared(); + // For hardware backends + bool InitializeShared(std::unique_ptr gfx, + std::unique_ptr vertex_manager, + std::unique_ptr perf_query, + std::unique_ptr bounding_box); + + // For software and null backends. Allows overriding the default Renderer and Texture Cache + bool InitializeShared(std::unique_ptr gfx, + std::unique_ptr vertex_manager, + std::unique_ptr perf_query, + std::unique_ptr bounding_box, + std::unique_ptr renderer, + std::unique_ptr texture_cache); void ShutdownShared(); bool m_initialized = false; diff --git a/Source/Core/VideoCommon/VideoCommon.h b/Source/Core/VideoCommon/VideoCommon.h index 2e5aa46936..517e8b3e9b 100644 --- a/Source/Core/VideoCommon/VideoCommon.h +++ b/Source/Core/VideoCommon/VideoCommon.h @@ -12,18 +12,18 @@ #include "VideoCommon/BPMemory.h" // These are accurate (disregarding AA modes). -constexpr u32 EFB_WIDTH = 640; -constexpr u32 EFB_HEIGHT = 528; +constexpr u32 EFB_WIDTH = 640u; +constexpr u32 EFB_HEIGHT = 528u; // Max XFB width is 720. You can only copy out 640 wide areas of efb to XFB // so you need multiple copies to do the full width. // The VI can do horizontal scaling (TODO: emulate). -constexpr u32 MAX_XFB_WIDTH = 720; +constexpr u32 MAX_XFB_WIDTH = 720u; // Although EFB height is 528, 576-line XFB's can be created either with // vertical scaling by the EFB copy operation or copying to multiple XFB's // that are next to each other in memory (TODO: handle that situation). -constexpr u32 MAX_XFB_HEIGHT = 576; +constexpr u32 MAX_XFB_HEIGHT = 576u; #define PRIM_LOG(t, ...) DEBUG_LOG_FMT(VIDEO, t __VA_OPT__(, ) __VA_ARGS__) diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp index f37d8e9595..38ee680bf3 100644 --- a/Source/Core/VideoCommon/VideoConfig.cpp +++ b/Source/Core/VideoCommon/VideoConfig.cpp @@ -8,12 +8,28 @@ #include "Common/CPUDetect.h" #include "Common/CommonTypes.h" #include "Common/StringUtil.h" + #include "Core/Config/GraphicsSettings.h" #include "Core/Config/MainSettings.h" +#include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/Movie.h" +#include "Core/System.h" + +#include "VideoCommon/AbstractGfx.h" +#include "VideoCommon/BPFunctions.h" #include "VideoCommon/DriverDetails.h" +#include "VideoCommon/FramebufferManager.h" +#include "VideoCommon/FreeLookCamera.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h" #include "VideoCommon/OnScreenDisplay.h" +#include "VideoCommon/PixelShaderManager.h" +#include "VideoCommon/Present.h" +#include "VideoCommon/ShaderGenCommon.h" +#include "VideoCommon/TextureCacheBase.h" +#include "VideoCommon/VertexManagerBase.h" + #include "VideoCommon/VideoCommon.h" VideoConfig g_Config; @@ -227,3 +243,117 @@ u32 VideoConfig::GetShaderPrecompilerThreads() const else return 1; } + +void CheckForConfigChanges() +{ + const ShaderHostConfig old_shader_host_config = ShaderHostConfig::GetCurrent(); + const StereoMode old_stereo = g_ActiveConfig.stereo_mode; + const u32 old_multisamples = g_ActiveConfig.iMultisamples; + const int old_anisotropy = g_ActiveConfig.iMaxAnisotropy; + const int old_efb_access_tile_size = g_ActiveConfig.iEFBAccessTileSize; + const auto old_texture_filtering_mode = g_ActiveConfig.texture_filtering_mode; + const bool old_vsync = g_ActiveConfig.bVSyncActive; + const bool old_bbox = g_ActiveConfig.bBBoxEnable; + const int old_efb_scale = g_ActiveConfig.iEFBScale; + const u32 old_game_mod_changes = + g_ActiveConfig.graphics_mod_config ? g_ActiveConfig.graphics_mod_config->GetChangeCount() : 0; + const bool old_graphics_mods_enabled = g_ActiveConfig.bGraphicMods; + const AspectMode old_suggested_aspect_mode = g_ActiveConfig.suggested_aspect_mode; + const bool old_widescreen_hack = g_ActiveConfig.bWidescreenHack; + const auto old_post_processing_shader = g_ActiveConfig.sPostProcessingShader; + + UpdateActiveConfig(); + FreeLook::UpdateActiveConfig(); + g_vertex_manager->OnConfigChange(); + + g_freelook_camera.SetControlType(FreeLook::GetActiveConfig().camera_config.control_type); + + if (g_ActiveConfig.bGraphicMods && !old_graphics_mods_enabled) + { + g_ActiveConfig.graphics_mod_config = GraphicsModGroupConfig(SConfig::GetInstance().GetGameID()); + g_ActiveConfig.graphics_mod_config->Load(); + } + + if (g_ActiveConfig.graphics_mod_config && + (old_game_mod_changes != g_ActiveConfig.graphics_mod_config->GetChangeCount())) + { + g_graphics_mod_manager->Load(*g_ActiveConfig.graphics_mod_config); + } + + // Update texture cache settings with any changed options. + g_texture_cache->OnConfigChanged(g_ActiveConfig); + + // EFB tile cache doesn't need to notify the backend. + if (old_efb_access_tile_size != g_ActiveConfig.iEFBAccessTileSize) + g_framebuffer_manager->SetEFBCacheTileSize(std::max(g_ActiveConfig.iEFBAccessTileSize, 0)); + + // Determine which (if any) settings have changed. + ShaderHostConfig new_host_config = ShaderHostConfig::GetCurrent(); + u32 changed_bits = 0; + if (old_shader_host_config.bits != new_host_config.bits) + changed_bits |= CONFIG_CHANGE_BIT_HOST_CONFIG; + if (old_stereo != g_ActiveConfig.stereo_mode) + changed_bits |= CONFIG_CHANGE_BIT_STEREO_MODE; + if (old_multisamples != g_ActiveConfig.iMultisamples) + changed_bits |= CONFIG_CHANGE_BIT_MULTISAMPLES; + if (old_anisotropy != g_ActiveConfig.iMaxAnisotropy) + changed_bits |= CONFIG_CHANGE_BIT_ANISOTROPY; + if (old_texture_filtering_mode != g_ActiveConfig.texture_filtering_mode) + changed_bits |= CONFIG_CHANGE_BIT_FORCE_TEXTURE_FILTERING; + if (old_vsync != g_ActiveConfig.bVSyncActive) + changed_bits |= CONFIG_CHANGE_BIT_VSYNC; + if (old_bbox != g_ActiveConfig.bBBoxEnable) + changed_bits |= CONFIG_CHANGE_BIT_BBOX; + if (old_efb_scale != g_ActiveConfig.iEFBScale) + changed_bits |= CONFIG_CHANGE_BIT_TARGET_SIZE; + if (old_suggested_aspect_mode != g_ActiveConfig.suggested_aspect_mode) + changed_bits |= CONFIG_CHANGE_BIT_ASPECT_RATIO; + if (old_widescreen_hack != g_ActiveConfig.bWidescreenHack) + changed_bits |= CONFIG_CHANGE_BIT_ASPECT_RATIO; + if (old_post_processing_shader != g_ActiveConfig.sPostProcessingShader) + changed_bits |= CONFIG_CHANGE_BIT_POST_PROCESSING_SHADER; + + // No changes? + if (changed_bits == 0) + return; + + float old_scale = g_framebuffer_manager->GetEFBScale(); + + // Framebuffer changed? + if (changed_bits & (CONFIG_CHANGE_BIT_MULTISAMPLES | CONFIG_CHANGE_BIT_STEREO_MODE | + CONFIG_CHANGE_BIT_TARGET_SIZE)) + { + g_framebuffer_manager->RecreateEFBFramebuffer(); + } + + if (old_scale != g_framebuffer_manager->GetEFBScale()) + { + auto& system = Core::System::GetInstance(); + auto& pixel_shader_manager = system.GetPixelShaderManager(); + pixel_shader_manager.Dirty(); + } + + // Reload shaders if host config has changed. + if (changed_bits & (CONFIG_CHANGE_BIT_HOST_CONFIG | CONFIG_CHANGE_BIT_MULTISAMPLES)) + { + OSD::AddMessage("Video config changed, reloading shaders.", OSD::Duration::NORMAL); + g_vertex_manager->InvalidatePipelineObject(); + g_shader_cache->SetHostConfig(new_host_config); + g_shader_cache->Reload(); + g_framebuffer_manager->RecompileShaders(); + } + + // Viewport and scissor rect have to be reset since they will be scaled differently. + if (changed_bits & CONFIG_CHANGE_BIT_TARGET_SIZE) + { + BPFunctions::SetScissorAndViewport(); + } + + // Notify all listeners + ConfigChangedEvent::Trigger(changed_bits); + + // TODO: Move everything else to the ConfigChanged event +} + +static Common::EventHook s_check_config_event = + AfterFrameEvent::Register([] { CheckForConfigChanges(); }, "CheckForConfigChanges"); diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h index df6f608760..1e5e2f683e 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -59,6 +59,21 @@ enum class TriState : int Auto }; +// Bitmask containing information about which configuration has changed for the backend. +enum ConfigChangeBits : u32 +{ + CONFIG_CHANGE_BIT_HOST_CONFIG = (1 << 0), + CONFIG_CHANGE_BIT_MULTISAMPLES = (1 << 1), + CONFIG_CHANGE_BIT_STEREO_MODE = (1 << 2), + CONFIG_CHANGE_BIT_TARGET_SIZE = (1 << 3), + CONFIG_CHANGE_BIT_ANISOTROPY = (1 << 4), + CONFIG_CHANGE_BIT_FORCE_TEXTURE_FILTERING = (1 << 5), + CONFIG_CHANGE_BIT_VSYNC = (1 << 6), + CONFIG_CHANGE_BIT_BBOX = (1 << 7), + CONFIG_CHANGE_BIT_ASPECT_RATIO = (1 << 8), + CONFIG_CHANGE_BIT_POST_PROCESSING_SHADER = (1 << 9), +}; + // NEVER inherit from this class. struct VideoConfig final { @@ -214,6 +229,7 @@ struct VideoConfig final u32 MaxTextureSize = 16384; bool bUsesLowerLeftOrigin = false; + bool bUsesExplictQuadBuffering = false; bool bSupportsExclusiveFullscreen = false; bool bSupportsDualSourceBlend = false; @@ -306,3 +322,4 @@ extern VideoConfig g_ActiveConfig; // Called every frame. void UpdateActiveConfig(); +void CheckForConfigChanges(); diff --git a/Source/Core/VideoCommon/VideoEvents.h b/Source/Core/VideoCommon/VideoEvents.h new file mode 100644 index 0000000000..a54d4d9ce7 --- /dev/null +++ b/Source/Core/VideoCommon/VideoEvents.h @@ -0,0 +1,83 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Common/CommonTypes.h" +#include "Common/HookableEvent.h" + +// Called when certain video config setting are changed +using ConfigChangedEvent = Common::HookableEvent<"ConfigChanged", u32>; + +// An event called just before the first draw call of a frame +using BeforeFrameEvent = Common::HookableEvent<"BeforeFrame">; + +// An event called after the frame XFB copy begins processing on the host GPU. +// Useful for "once per frame" usecases. +// Note: In a few rare cases, games do multiple XFB copies per frame and join them while presenting. +// If this matters to your usecase, you should use BeforePresent instead. +using AfterFrameEvent = Common::HookableEvent<"AfterFrame">; + +struct PresentInfo +{ + enum class PresentReason + { + Immediate, // FIFO is Presenting the XFB immediately, straight after the XFB copy + VideoInterface, // VideoInterface has triggered a present with a new frame + VideoInterfaceDuplicate, // VideoInterface has triggered a present with a duplicate frame + }; + + // The number of (unique) frames since the emulated console booted + u64 frame_count; + + // The number of presents since the video backend was initialized. + // never goes backwards. + u64 present_count; + + // The frame is identical to the previous frame + PresentReason reason; + + // The exact emulated time of the when real hardware would have presented this frame + // FIXME: Immediate should predict the timestamp of this present + u64 emulated_timestamp; + + // TODO: + // u64 intended_present_time; + + // AfterPresent only: The actual time the frame was presented + u64 actual_present_time = 0; + + enum class PresentTimeAccuracy + { + // The Driver/OS has given us an exact timestamp of when the first line of the frame started + // scanning out to the monitor + PresentOnScreenExact, + + // An approximate timestamp of scanout. + PresentOnScreen, + + // Dolphin doesn't have visibility of the present time. But the present operation has + // been queued with the GPU driver and will happen in the near future. + PresentInProgress, + + // Not implemented + Unimplemented, + }; + + // Accuracy of actual_present_time + PresentTimeAccuracy present_time_accuracy = PresentTimeAccuracy::Unimplemented; +}; + +// An event called just as a frame is queued for presentation. +// The exact timing of this event depends on the "Immediately Present XFB" option. +// +// If enabled, this event will trigger immediately after AfterFrame +// If disabled, this event won't trigger until the emulated interface starts drawing out a new +// frame. +// +// frame_count: The number of frames +using BeforePresentEvent = Common::HookableEvent<"BeforePresent", PresentInfo&>; + +// An event that is triggered after a frame is presented. +// The exact timing of this event depends on backend/driver support. +using AfterPresentEvent = Common::HookableEvent<"AfterPresent", PresentInfo&>; diff --git a/Source/Core/VideoCommon/VideoState.cpp b/Source/Core/VideoCommon/VideoState.cpp index 34b4d6332d..7f005e1474 100644 --- a/Source/Core/VideoCommon/VideoState.cpp +++ b/Source/Core/VideoCommon/VideoState.cpp @@ -8,20 +8,24 @@ #include "Common/ChunkFile.h" #include "Core/System.h" #include "VideoCommon/BPMemory.h" +#include "VideoCommon/BPStructs.h" +#include "VideoCommon/BoundingBox.h" #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/PixelEngine.h" #include "VideoCommon/PixelShaderManager.h" -#include "VideoCommon/RenderBase.h" +#include "VideoCommon/Present.h" #include "VideoCommon/TMEM.h" #include "VideoCommon/TextureCacheBase.h" #include "VideoCommon/TextureDecoder.h" #include "VideoCommon/VertexLoaderManager.h" #include "VideoCommon/VertexManagerBase.h" #include "VideoCommon/VertexShaderManager.h" +#include "VideoCommon/Widescreen.h" #include "VideoCommon/XFMemory.h" void VideoCommon_DoState(PointerWrap& p) @@ -91,8 +95,15 @@ void VideoCommon_DoState(PointerWrap& p) g_texture_cache->DoState(p); p.DoMarker("TextureCache"); - g_renderer->DoState(p); - p.DoMarker("Renderer"); + g_presenter->DoState(p); + g_frame_dumper->DoState(p); + p.DoMarker("Presenter"); + + g_bounding_box->DoState(p); + p.DoMarker("Bounding Box"); + + g_widescreen->DoState(p); + p.DoMarker("Widescreen"); // Refresh state. if (p.IsReadMode()) diff --git a/Source/Core/VideoCommon/Widescreen.cpp b/Source/Core/VideoCommon/Widescreen.cpp new file mode 100644 index 0000000000..dd1393d309 --- /dev/null +++ b/Source/Core/VideoCommon/Widescreen.cpp @@ -0,0 +1,114 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/Widescreen.h" + +#include "Common/ChunkFile.h" +#include "Core/Config/SYSCONFSettings.h" +#include "Core/ConfigManager.h" + +#include "VideoCommon/VertexManagerBase.h" + +std::unique_ptr g_widescreen; + +WidescreenManager::WidescreenManager() +{ + Update(); + + m_config_changed = ConfigChangedEvent::Register( + [this](u32 bits) { + if (bits & (CONFIG_CHANGE_BIT_ASPECT_RATIO)) + Update(); + }, + "Widescreen"); + + // VertexManager doesn't maintain statistics in Wii mode. + if (!SConfig::GetInstance().bWii) + { + m_update_widescreen = + AfterFrameEvent::Register([this] { UpdateWidescreenHeuristic(); }, "WideScreen Heuristic"); + } +} + +void WidescreenManager::Update() +{ + if (SConfig::GetInstance().bWii) + m_is_game_widescreen = Config::Get(Config::SYSCONF_WIDESCREEN); + + // suggested_aspect_mode overrides SYSCONF_WIDESCREEN + if (g_ActiveConfig.suggested_aspect_mode == AspectMode::Analog) + m_is_game_widescreen = false; + else if (g_ActiveConfig.suggested_aspect_mode == AspectMode::AnalogWide) + m_is_game_widescreen = true; + + // If widescreen hack is disabled override game's AR if UI is set to 4:3 or 16:9. + if (!g_ActiveConfig.bWidescreenHack) + { + const auto aspect_mode = g_ActiveConfig.aspect_mode; + if (aspect_mode == AspectMode::Analog) + m_is_game_widescreen = false; + else if (aspect_mode == AspectMode::AnalogWide) + m_is_game_widescreen = true; + } +} + +// Heuristic to detect if a GameCube game is in 16:9 anamorphic widescreen mode. +void WidescreenManager::UpdateWidescreenHeuristic() +{ + const auto flush_statistics = g_vertex_manager->ResetFlushAspectRatioCount(); + + // If suggested_aspect_mode (GameINI) is configured don't use heuristic. + if (g_ActiveConfig.suggested_aspect_mode != AspectMode::Auto) + return; + + Update(); + + // If widescreen hack isn't active and aspect_mode (UI) is 4:3 or 16:9 don't use heuristic. + if (!g_ActiveConfig.bWidescreenHack && (g_ActiveConfig.aspect_mode == AspectMode::Analog || + g_ActiveConfig.aspect_mode == AspectMode::AnalogWide)) + return; + + // Modify the threshold based on which aspect ratio we're already using: + // If the game's in 4:3, it probably won't switch to anamorphic, and vice-versa. + static constexpr u32 TRANSITION_THRESHOLD = 3; + + const auto looks_normal = [](auto& counts) { + return counts.normal_vertex_count > counts.anamorphic_vertex_count * TRANSITION_THRESHOLD; + }; + const auto looks_anamorphic = [](auto& counts) { + return counts.anamorphic_vertex_count > counts.normal_vertex_count * TRANSITION_THRESHOLD; + }; + + const auto& persp = flush_statistics.perspective; + const auto& ortho = flush_statistics.orthographic; + + const auto ortho_looks_anamorphic = looks_anamorphic(ortho); + + if (looks_anamorphic(persp) || ortho_looks_anamorphic) + { + // If either perspective or orthographic projections look anamorphic, it's a safe bet. + m_is_game_widescreen = true; + } + else if (looks_normal(persp) || (m_was_orthographically_anamorphic && looks_normal(ortho))) + { + // Many widescreen games (or AR/GeckoCodes) use anamorphic perspective projections + // with NON-anamorphic orthographic projections. + // This can cause incorrect changes to 4:3 when perspective projections are temporarily not + // shown. e.g. Animal Crossing's inventory menu. + // Unless we were in a situation which was orthographically anamorphic + // we won't consider orthographic data for changes from 16:9 to 4:3. + m_is_game_widescreen = false; + } + + m_was_orthographically_anamorphic = ortho_looks_anamorphic; +} + +void WidescreenManager::DoState(PointerWrap& p) +{ + p.Do(m_is_game_widescreen); + + if (p.IsReadMode()) + { + m_was_orthographically_anamorphic = false; + } +} diff --git a/Source/Core/VideoCommon/Widescreen.h b/Source/Core/VideoCommon/Widescreen.h new file mode 100644 index 0000000000..c8dc5847ec --- /dev/null +++ b/Source/Core/VideoCommon/Widescreen.h @@ -0,0 +1,34 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "VideoCommon/VideoConfig.h" +#include "VideoCommon/VideoEvents.h" + +class PointerWrap; + +// This class is responsible for tracking the game's aspect ratio. +class WidescreenManager +{ +public: + WidescreenManager(); + + bool IsGameWidescreen() const { return m_is_game_widescreen; } + + void DoState(PointerWrap& p); + +private: + void Update(); + void UpdateWidescreenHeuristic(); + + bool m_is_game_widescreen = false; + bool m_was_orthographically_anamorphic = false; + + Common::EventHook m_update_widescreen; + Common::EventHook m_config_changed; +}; + +extern std::unique_ptr g_widescreen;