Rewrite host GPU abstraction

- Don't have to repeat the same thing for 4 renderers.
 - Add native Metal renderer.
This commit is contained in:
Stenzek
2023-08-13 13:42:02 +10:00
parent bfa792ddbf
commit e3d9ba4c99
249 changed files with 28851 additions and 32222 deletions

View File

@ -27,7 +27,6 @@
#include "common/log.h"
#include "common/path.h"
#include "common/string_util.h"
#include "common/window_info.h"
#include "util/audio_stream.h"
#include "util/imgui_manager.h"
@ -107,7 +106,9 @@ static bool s_start_fullscreen_ui_fullscreen = false;
EmuThread* g_emu_thread;
GDBServer* g_gdb_server;
EmuThread::EmuThread(QThread* ui_thread) : QThread(), m_ui_thread(ui_thread) {}
EmuThread::EmuThread(QThread* ui_thread) : QThread(), m_ui_thread(ui_thread)
{
}
EmuThread::~EmuThread() = default;
@ -336,24 +337,31 @@ void EmuThread::setInitialState(std::optional<bool> override_fullscreen)
m_is_surfaceless = false;
}
void EmuThread::checkForSettingsChanges(const Settings& old_settings)
{
if (g_main_window)
{
QMetaObject::invokeMethod(g_main_window, &MainWindow::checkForSettingChanges, Qt::QueuedConnection);
updatePerformanceCounters();
}
if (g_gpu_device)
{
const bool render_to_main = shouldRenderToMain();
if (m_is_rendering_to_main != render_to_main)
{
m_is_rendering_to_main = render_to_main;
g_gpu_device->UpdateWindow();
}
}
}
void Host::CheckForSettingsChanges(const Settings& old_settings)
{
CommonHost::CheckForSettingsChanges(old_settings);
g_emu_thread->checkForSettingsChanges(old_settings);
}
void EmuThread::checkForSettingsChanges(const Settings& old_settings)
{
const bool render_to_main = shouldRenderToMain();
if (m_is_rendering_to_main != render_to_main)
{
m_is_rendering_to_main = render_to_main;
updateDisplayState();
}
QMetaObject::invokeMethod(g_main_window, &MainWindow::checkForSettingChanges, Qt::QueuedConnection);
}
void EmuThread::setDefaultSettings(bool system /* = true */, bool controller /* = true */)
{
if (isOnThread())
@ -399,7 +407,7 @@ void Host::RequestResizeHostDisplay(s32 new_window_width, s32 new_window_height)
if (g_emu_thread->isFullscreen())
return;
emit g_emu_thread->displaySizeRequested(new_window_width, new_window_height);
emit g_emu_thread->onResizeRenderWindowRequested(new_window_width, new_window_height);
}
void EmuThread::applySettings(bool display_osd_messages /* = false */)
@ -456,8 +464,10 @@ void EmuThread::startFullscreenUI()
setInitialState(s_start_fullscreen_ui_fullscreen ? std::optional<bool>(true) : std::optional<bool>());
m_run_fullscreen_ui = true;
if (!acquireHostDisplay(Settings::GetRenderAPIForRenderer(g_settings.gpu_renderer)))
if (!Host::CreateGPUDevice(Settings::GetRenderAPIForRenderer(g_settings.gpu_renderer)) || !FullscreenUI::Initialize())
{
Host::ReleaseGPUDevice();
Host::ReleaseRenderWindow();
m_run_fullscreen_ui = false;
return;
}
@ -475,7 +485,7 @@ void EmuThread::stopFullscreenUI()
QMetaObject::invokeMethod(this, &EmuThread::stopFullscreenUI, Qt::QueuedConnection);
// wait until the host display is gone
while (g_host_display)
while (g_gpu_device)
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1);
return;
@ -484,11 +494,12 @@ void EmuThread::stopFullscreenUI()
if (System::IsValid())
shutdownSystem();
if (!g_host_display)
if (!g_gpu_device)
return;
m_run_fullscreen_ui = false;
releaseHostDisplay();
Host::ReleaseGPUDevice();
Host::ReleaseRenderWindow();
}
void EmuThread::bootSystem(std::shared_ptr<SystemBootParameters> params)
@ -506,7 +517,7 @@ void EmuThread::bootSystem(std::shared_ptr<SystemBootParameters> params)
return;
// force a frame to be drawn to repaint the window
renderDisplay(false);
Host::InvalidateDisplay();
}
void EmuThread::bootOrLoadState(std::string path)
@ -568,8 +579,8 @@ void EmuThread::onDisplayWindowMouseMoveEvent(bool relative, float x, float y)
DebugAssert(isOnThread());
if (!relative)
{
if (g_host_display)
g_host_display->SetMousePosition(static_cast<s32>(x), static_cast<s32>(y));
if (g_gpu_device)
g_gpu_device->SetMousePosition(static_cast<s32>(x), static_cast<s32>(y));
InputManager::UpdatePointerAbsolutePosition(0, x, y);
ImGuiManager::UpdateMousePosition(x, y);
@ -581,11 +592,11 @@ void EmuThread::onDisplayWindowMouseMoveEvent(bool relative, float x, float y)
if (y != 0.0f)
InputManager::UpdatePointerRelativeDelta(0, InputPointerAxis::Y, y);
if (g_host_display)
if (g_gpu_device)
{
const float abs_x = static_cast<float>(g_host_display->GetMousePositionX()) + x;
const float abs_y = static_cast<float>(g_host_display->GetMousePositionY()) + y;
g_host_display->SetMousePosition(static_cast<s32>(abs_x), static_cast<s32>(abs_y));
const float abs_x = static_cast<float>(g_gpu_device->GetMousePositionX()) + x;
const float abs_y = static_cast<float>(g_gpu_device->GetMousePositionY()) + y;
g_gpu_device->SetMousePosition(static_cast<s32>(abs_x), static_cast<s32>(abs_y));
ImGuiManager::UpdateMousePosition(abs_x, abs_y);
}
}
@ -612,33 +623,9 @@ void EmuThread::onDisplayWindowMouseWheelEvent(const QPoint& delta_angle)
InputManager::UpdatePointerRelativeDelta(0, InputPointerAxis::WheelY, dy);
}
void EmuThread::onDisplayWindowResized(int width, int height)
void EmuThread::onDisplayWindowResized(int width, int height, float scale)
{
// this can be null if it was destroyed and the main thread is late catching up
if (!g_host_display)
return;
Log_DevPrintf("Display window resized to %dx%d", width, height);
g_host_display->ResizeWindow(width, height);
ImGuiManager::WindowResized();
System::HostDisplayResized();
// re-render the display, since otherwise it will be out of date and stretched if paused
if (System::IsValid())
{
if (m_is_exclusive_fullscreen && !g_host_display->IsFullscreen())
{
// we lost exclusive fullscreen, switch to borderless
Host::AddOSDMessage(TRANSLATE_STR("OSDMessage", "Lost exclusive fullscreen."), 10.0f);
m_is_exclusive_fullscreen = false;
m_is_fullscreen = false;
m_lost_exclusive_fullscreen = true;
}
// force redraw if we're paused
if (!System::IsRunning() && !FullscreenUI::HasActiveWindow())
renderDisplay(false);
}
Host::ResizeDisplayWindow(width, height, scale);
}
void EmuThread::redrawDisplayWindow()
@ -649,10 +636,10 @@ void EmuThread::redrawDisplayWindow()
return;
}
if (!g_host_display || System::IsShutdown())
if (!g_gpu_device || System::IsShutdown())
return;
renderDisplay(false);
Host::RenderDisplay(false);
}
void EmuThread::toggleFullscreen()
@ -663,22 +650,24 @@ void EmuThread::toggleFullscreen()
return;
}
setFullscreen(!m_is_fullscreen);
setFullscreen(!m_is_fullscreen, true);
}
void EmuThread::setFullscreen(bool fullscreen)
void EmuThread::setFullscreen(bool fullscreen, bool allow_render_to_main)
{
if (!isOnThread())
{
QMetaObject::invokeMethod(this, "setFullscreen", Qt::QueuedConnection, Q_ARG(bool, fullscreen));
QMetaObject::invokeMethod(this, "setFullscreen", Qt::QueuedConnection, Q_ARG(bool, fullscreen),
Q_ARG(bool, allow_render_to_main));
return;
}
if (!g_host_display || m_is_fullscreen == fullscreen)
if (!g_gpu_device || m_is_fullscreen == fullscreen)
return;
m_is_fullscreen = fullscreen;
updateDisplayState();
m_is_rendering_to_main = allow_render_to_main && shouldRenderToMain();
Host::UpdateDisplayWindow();
}
bool Host::IsFullscreen()
@ -688,7 +677,7 @@ bool Host::IsFullscreen()
void Host::SetFullscreen(bool enabled)
{
g_emu_thread->setFullscreen(enabled);
g_emu_thread->setFullscreen(enabled, true);
}
void EmuThread::setSurfaceless(bool surfaceless)
@ -699,11 +688,11 @@ void EmuThread::setSurfaceless(bool surfaceless)
return;
}
if (!g_host_display || m_is_surfaceless == surfaceless)
if (!g_gpu_device || m_is_surfaceless == surfaceless)
return;
m_is_surfaceless = surfaceless;
updateDisplayState();
Host::UpdateDisplayWindow();
}
void EmuThread::requestDisplaySize(float scale)
@ -720,52 +709,25 @@ void EmuThread::requestDisplaySize(float scale)
System::RequestDisplaySize(scale);
}
bool EmuThread::acquireHostDisplay(RenderAPI api)
std::optional<WindowInfo> EmuThread::acquireRenderWindow(bool recreate_window)
{
if (g_host_display)
{
if (g_host_display->GetRenderAPI() == api)
{
// current is fine
return true;
}
DebugAssert(g_gpu_device);
u32 fs_width, fs_height;
float fs_refresh_rate;
m_is_exclusive_fullscreen = (m_is_fullscreen && g_gpu_device->SupportsExclusiveFullscreen() &&
GPUDevice::GetRequestedExclusiveFullscreenMode(&fs_width, &fs_height, &fs_refresh_rate));
// otherwise we need to switch
releaseHostDisplay();
}
const bool window_fullscreen = m_is_fullscreen && !m_is_exclusive_fullscreen;
const bool render_to_main = !m_is_exclusive_fullscreen && !window_fullscreen && m_is_rendering_to_main;
const bool use_main_window_pos = m_is_exclusive_fullscreen && shouldRenderToMain();
g_host_display = Host::CreateDisplayForAPI(api);
if (!g_host_display)
return false;
return emit onAcquireRenderWindowRequested(recreate_window, window_fullscreen, render_to_main, m_is_surfaceless,
use_main_window_pos);
}
if (!createDisplayRequested(m_is_fullscreen, m_is_rendering_to_main))
{
emit destroyDisplayRequested();
g_host_display.reset();
return false;
}
if (!g_host_display->MakeCurrent() || !g_host_display->SetupDevice() || !ImGuiManager::Initialize() ||
!CommonHost::CreateHostDisplayResources())
{
ImGuiManager::Shutdown();
CommonHost::ReleaseHostDisplayResources();
g_host_display.reset();
emit destroyDisplayRequested();
return false;
}
m_is_exclusive_fullscreen = g_host_display->IsFullscreen();
if (m_run_fullscreen_ui && !FullscreenUI::Initialize())
{
Log_ErrorPrint("Failed to initialize fullscreen UI");
releaseHostDisplay();
m_run_fullscreen_ui = false;
return false;
}
return true;
void EmuThread::releaseRenderWindow()
{
emit onReleaseRenderWindowRequested();
}
void EmuThread::connectDisplaySignals(DisplayWidget* widget)
@ -781,46 +743,6 @@ void EmuThread::connectDisplaySignals(DisplayWidget* widget)
connect(widget, &DisplayWidget::windowMouseWheelEvent, this, &EmuThread::onDisplayWindowMouseWheelEvent);
}
void EmuThread::updateDisplayState()
{
if (!g_host_display)
return;
// this expects the context to get moved back to us afterwards
g_host_display->DoneCurrent();
updateDisplayRequested(m_is_fullscreen, m_is_rendering_to_main && !m_is_fullscreen, m_is_surfaceless);
if (!g_host_display->MakeCurrent())
Panic("Failed to make device context current after updating");
m_is_exclusive_fullscreen = g_host_display->IsFullscreen();
ImGuiManager::WindowResized();
System::HostDisplayResized();
if (!System::IsShutdown())
{
System::UpdateSoftwareCursor();
if (!FullscreenUI::IsInitialized() || System::IsPaused())
redrawDisplayWindow();
}
System::UpdateSpeedLimiterState();
}
void EmuThread::releaseHostDisplay()
{
if (!g_host_display)
return;
CommonHost::ReleaseHostDisplayResources();
FullscreenUI::Shutdown();
ImGuiManager::Shutdown();
g_host_display.reset();
emit destroyDisplayRequested();
m_is_fullscreen = false;
}
void Host::OnSystemStarting()
{
CommonHost::OnSystemStarting();
@ -844,7 +766,7 @@ void Host::OnSystemPaused()
emit g_emu_thread->systemPaused();
g_emu_thread->startBackgroundControllerPollTimer();
g_emu_thread->renderDisplay(false);
Host::InvalidateDisplay();
}
void Host::OnSystemResumed()
@ -1246,7 +1168,7 @@ void EmuThread::singleStepCPU()
return;
System::SingleStepCPU();
renderDisplay(false);
Host::InvalidateDisplay();
}
void EmuThread::dumpRAM(const QString& filename)
@ -1449,11 +1371,11 @@ void EmuThread::run()
m_event_loop->processEvents(QEventLoop::AllEvents);
CommonHost::PumpMessagesOnCPUThread();
if (g_host_display)
if (g_gpu_device)
{
renderDisplay(false);
if (!g_host_display->IsVsyncEnabled())
g_host_display->ThrottlePresentation();
Host::RenderDisplay(false);
if (!g_gpu_device->IsVsyncEnabled())
g_gpu_device->ThrottlePresentation();
}
}
}
@ -1468,35 +1390,8 @@ void EmuThread::run()
moveToThread(m_ui_thread);
}
void EmuThread::renderDisplay(bool skip_present)
void Host::BeginPresentFrame()
{
// acquire for IO.MousePos.
std::atomic_thread_fence(std::memory_order_acquire);
if (!skip_present)
{
FullscreenUI::Render();
ImGuiManager::RenderTextOverlays();
ImGuiManager::RenderOSDMessages();
}
// Debug windows are always rendered, otherwise mouse input breaks on skip.
ImGuiManager::RenderOverlayWindows();
ImGuiManager::RenderDebugWindows();
g_host_display->Render(skip_present);
ImGuiManager::NewFrame();
}
void Host::InvalidateDisplay()
{
g_emu_thread->renderDisplay(false);
}
void Host::RenderDisplay(bool skip_present)
{
g_emu_thread->renderDisplay(skip_present);
}
void EmuThread::wakeThread()
@ -1605,39 +1500,34 @@ void Host::CommitBaseSettingChanges()
QtHost::QueueSettingsSave();
}
bool Host::AcquireHostDisplay(RenderAPI api)
std::optional<WindowInfo> Host::AcquireRenderWindow(bool recreate_window)
{
return g_emu_thread->acquireHostDisplay(api);
return g_emu_thread->acquireRenderWindow(recreate_window);
}
void Host::ReleaseHostDisplay()
void Host::ReleaseRenderWindow()
{
if (g_emu_thread->isRunningFullscreenUI())
{
// keep display alive when running fsui
return;
}
g_emu_thread->releaseHostDisplay();
g_emu_thread->releaseRenderWindow();
}
void EmuThread::updatePerformanceCounters()
{
GPURenderer renderer = GPURenderer::Count;
const RenderAPI render_api = g_gpu_device ? g_gpu_device->GetRenderAPI() : RenderAPI::None;
const bool hardware_renderer = g_gpu && g_gpu->IsHardwareRenderer();
u32 render_width = 0;
u32 render_height = 0;
if (g_gpu)
{
renderer = g_gpu->GetRendererType();
std::tie(render_width, render_height) = g_gpu->GetEffectiveDisplayResolution();
}
if (renderer != m_last_renderer)
if (render_api != m_last_render_api || hardware_renderer != m_last_hardware_renderer)
{
const QString renderer_str = hardware_renderer ? QString::fromUtf8(GPUDevice::RenderAPIToString(render_api)) :
qApp->translate("GPURenderer", "Software");
QMetaObject::invokeMethod(g_main_window->getStatusRendererWidget(), "setText", Qt::QueuedConnection,
Q_ARG(const QString&, QString::fromUtf8(Settings::GetRendererName(renderer))));
m_last_renderer = renderer;
Q_ARG(const QString&, renderer_str));
m_last_render_api = render_api;
m_last_hardware_renderer = hardware_renderer;
}
if (render_width != m_last_render_width || render_height != m_last_render_height)
{
@ -1674,7 +1564,8 @@ void EmuThread::resetPerformanceCounters()
m_last_video_fps = std::numeric_limits<float>::infinity();
m_last_render_width = std::numeric_limits<u32>::max();
m_last_render_height = std::numeric_limits<u32>::max();
m_last_renderer = GPURenderer::Count;
m_last_render_api = RenderAPI::None;
m_last_hardware_renderer = false;
QString blank;
QMetaObject::invokeMethod(g_main_window->getStatusRendererWidget(), "setText", Qt::QueuedConnection,