NoGUI: Merge in old branch and use base NoGUI class for SDL

This commit is contained in:
Connor McLaughlin
2021-01-31 02:28:14 +10:00
parent b09da307b5
commit 98bad30af8
17 changed files with 1610 additions and 698 deletions

View File

@ -1,41 +1,61 @@
#include "sdl_host_interface.h"
#include "common/assert.h"
#include "common/byte_stream.h"
#include "common/file_system.h"
#include "common/image.h"
#include "common/log.h"
#include "common/string_util.h"
#include "core/gpu.h"
#include "core/host_display.h"
#include "core/system.h"
#include "frontend-common/fullscreen_ui.h"
#include "frontend-common/controller_interface.h"
#include "frontend-common/icon.h"
#include "frontend-common/imgui_fullscreen.h"
#include "frontend-common/imgui_styles.h"
#include "frontend-common/ini_settings_interface.h"
#include "frontend-common/opengl_host_display.h"
#include "frontend-common/sdl_audio_stream.h"
#include "frontend-common/sdl_controller_interface.h"
#include "frontend-common/vulkan_host_display.h"
#include "frontend-common/sdl_initializer.h"
#include "imgui.h"
#include "imgui_impl_sdl.h"
#include "imgui_stdlib.h"
#include "scmversion/scmversion.h"
#include "sdl_key_names.h"
#include "sdl_util.h"
#include <SDL_syswm.h>
#include <cinttypes>
#include <cmath>
Log_SetChannel(SDLHostInterface);
#ifdef WIN32
#include "frontend-common/d3d11_host_display.h"
#ifdef __APPLE__
#include <objc/message.h>
struct NSView;
static NSView* GetContentViewFromWindow(NSWindow* window)
{
// window.contentView
return reinterpret_cast<NSView* (*)(id, SEL)>(objc_msgSend)(reinterpret_cast<id>(window), sel_getUid("contentView"));
}
#endif
SDLHostInterface::SDLHostInterface()
static float GetDPIScaleFactor(SDL_Window* window)
{
m_run_later_event_id = SDL_RegisterEvents(1);
#ifdef __APPLE__
static constexpr float DEFAULT_DPI = 72.0f;
#else
static constexpr float DEFAULT_DPI = 96.0f;
#endif
if (!window)
{
SDL_Window* dummy_window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1, 1,
SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN);
if (!dummy_window)
return 1.0f;
const float scale = GetDPIScaleFactor(dummy_window);
SDL_DestroyWindow(dummy_window);
return scale;
}
int display_index = SDL_GetWindowDisplayIndex(window);
float display_dpi = DEFAULT_DPI;
if (SDL_GetDisplayDPI(display_index, &display_dpi, nullptr, nullptr) != 0)
return 1.0f;
return display_dpi / DEFAULT_DPI;
}
SDLHostInterface::SDLHostInterface() = default;
SDLHostInterface::~SDLHostInterface() = default;
const char* SDLHostInterface::GetFrontendName() const
@ -43,16 +63,76 @@ const char* SDLHostInterface::GetFrontendName() const
return "DuckStation NoGUI Frontend";
}
std::unique_ptr<SDLHostInterface> SDLHostInterface::Create()
{
return std::make_unique<SDLHostInterface>();
}
bool SDLHostInterface::Initialize()
{
FrontendCommon::EnsureSDLInitialized();
if (!NoGUIHostInterface::Initialize())
return false;
return true;
}
void SDLHostInterface::Shutdown()
{
NoGUIHostInterface::Shutdown();
}
bool SDLHostInterface::IsFullscreen() const
{
return m_fullscreen;
}
bool SDLHostInterface::SetFullscreen(bool enabled)
{
if (m_fullscreen == enabled)
return true;
SDL_SetWindowFullscreen(m_window, enabled ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
int window_width, window_height;
SDL_GetWindowSize(m_window, &window_width, &window_height);
m_display->ResizeRenderWindow(window_width, window_height);
OnPlatformWindowResized(window_width, window_height, GetDPIScaleFactor(m_window));
m_fullscreen = enabled;
return true;
}
bool SDLHostInterface::RequestRenderWindowSize(s32 new_window_width, s32 new_window_height)
{
if (new_window_width <= 0 || new_window_height <= 0 || m_fullscreen)
return false;
// use imgui scale as the dpr
const float dpi_scale = ImGui::GetIO().DisplayFramebufferScale.x;
const s32 scaled_width =
std::max<s32>(static_cast<s32>(std::ceil(static_cast<float>(new_window_width) * dpi_scale)), 1);
const s32 scaled_height = std::max<s32>(
static_cast<s32>(std::ceil(static_cast<float>(new_window_height) * dpi_scale)) + m_display->GetDisplayTopMargin(),
1);
SDL_SetWindowSize(m_window, scaled_width, scaled_height);
s32 window_width, window_height;
SDL_GetWindowSize(m_window, &window_width, &window_height);
m_display->ResizeRenderWindow(window_width, window_height);
return true;
}
ALWAYS_INLINE static TinyString GetWindowTitle()
{
return TinyString::FromFormat("DuckStation %s (%s)", g_scm_tag_str, g_scm_branch_str);
}
bool SDLHostInterface::CreateSDLWindow()
bool SDLHostInterface::CreatePlatformWindow()
{
static constexpr u32 DEFAULT_WINDOW_WIDTH = 1280;
static constexpr u32 DEFAULT_WINDOW_HEIGHT = 720;
// Create window.
const u32 window_flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
@ -63,7 +143,7 @@ bool SDLHostInterface::CreateSDLWindow()
#ifndef __APPLE__
{
// scale by default monitor's DPI
float scale = SDLUtil::GetDPIScaleFactor(nullptr);
float scale = GetDPIScaleFactor(nullptr);
window_width = static_cast<u32>(std::round(static_cast<float>(window_width) * scale));
window_height = static_cast<u32>(std::round(static_cast<float>(window_height) * scale));
}
@ -88,179 +168,77 @@ bool SDLHostInterface::CreateSDLWindow()
if (m_fullscreen)
SDL_SetWindowFullscreen(m_window, SDL_WINDOW_FULLSCREEN_DESKTOP);
ImGui_ImplSDL2_Init(m_window);
// Process events so that we have everything sorted out before creating a child window for the GL context (X11).
SDL_PumpEvents();
return true;
}
void SDLHostInterface::DestroySDLWindow()
void SDLHostInterface::DestroyPlatformWindow()
{
ImGui_ImplSDL2_Shutdown();
SDL_DestroyWindow(m_window);
m_window = nullptr;
}
bool SDLHostInterface::CreateDisplay()
std::optional<WindowInfo> SDLHostInterface::GetPlatformWindowInfo()
{
std::optional<WindowInfo> wi = SDLUtil::GetWindowInfoForSDLWindow(m_window);
if (!wi.has_value())
SDL_SysWMinfo syswm = {};
SDL_VERSION(&syswm.version);
if (!SDL_GetWindowWMInfo(m_window, &syswm))
{
ReportError("Failed to get window info from SDL window");
return false;
Log_ErrorPrintf("SDL_GetWindowWMInfo failed");
return std::nullopt;
}
switch (g_settings.gpu_renderer)
int window_width, window_height;
SDL_GetWindowSize(m_window, &window_width, &window_height);
WindowInfo wi;
wi.surface_width = static_cast<u32>(window_width);
wi.surface_height = static_cast<u32>(window_height);
wi.surface_scale = GetDPIScaleFactor(m_window);
wi.surface_format = WindowInfo::SurfaceFormat::RGB8;
switch (syswm.subsystem)
{
case GPURenderer::HardwareVulkan:
m_display = std::make_unique<FrontendCommon::VulkanHostDisplay>();
break;
case GPURenderer::HardwareOpenGL:
#ifndef WIN32
default:
#endif
m_display = std::make_unique<FrontendCommon::OpenGLHostDisplay>();
break;
#ifdef WIN32
case GPURenderer::HardwareD3D11:
default:
m_display = std::make_unique<FrontendCommon::D3D11HostDisplay>();
break;
#endif
}
Assert(m_display);
if (!m_display->CreateRenderDevice(wi.value(), g_settings.gpu_adapter, g_settings.gpu_use_debug_device,
g_settings.gpu_threaded_presentation) ||
!m_display->InitializeRenderDevice(GetShaderCacheBasePath(), g_settings.gpu_use_debug_device,
g_settings.gpu_threaded_presentation))
{
ReportError("Failed to create/initialize display render device");
m_display.reset();
return false;
}
if (!ImGui_ImplSDL2_Init(m_window) || !m_display->CreateImGuiContext())
{
ReportError("Failed to initialize ImGui SDL2 wrapper");
ImGui_ImplSDL2_Shutdown();
m_display->DestroyRenderDevice();
m_display.reset();
return false;
}
if (!FullscreenUI::Initialize(this, m_settings_interface.get()) || !m_display->UpdateImGuiFontTexture())
{
ReportError("Failed to initialize fonts/fullscreen UI");
FullscreenUI::Shutdown();
m_display->DestroyImGuiContext();
ImGui_ImplSDL2_Shutdown();
m_display->DestroyRenderDevice();
m_display.reset();
return false;
}
m_fullscreen_ui_enabled = true;
return true;
}
void SDLHostInterface::DestroyDisplay()
{
FullscreenUI::Shutdown();
m_display->DestroyImGuiContext();
m_display->DestroyRenderDevice();
m_display.reset();
}
void SDLHostInterface::CreateImGuiContext()
{
const float framebuffer_scale = SDLUtil::GetDPIScaleFactor(m_window);
ImGui::CreateContext();
ImGui::GetIO().IniFilename = nullptr;
ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad;
ImGui::GetIO().DisplayFramebufferScale.x = framebuffer_scale;
ImGui::GetIO().DisplayFramebufferScale.y = framebuffer_scale;
ImGui::GetStyle().ScaleAllSizes(framebuffer_scale);
ImGui::StyleColorsDarker();
}
void SDLHostInterface::UpdateFramebufferScale()
{
ImGuiIO& io = ImGui::GetIO();
const float framebuffer_scale = SDLUtil::GetDPIScaleFactor(m_window);
if (framebuffer_scale != io.DisplayFramebufferScale.x)
{
io.DisplayFramebufferScale = ImVec2(framebuffer_scale, framebuffer_scale);
ImGui::GetStyle().ScaleAllSizes(framebuffer_scale);
}
if (ImGuiFullscreen::UpdateLayoutScale())
{
if (ImGuiFullscreen::UpdateFonts())
{
if (!m_display->UpdateImGuiFontTexture())
Panic("Failed to update font texture");
}
}
}
bool SDLHostInterface::AcquireHostDisplay()
{
// Handle renderer switch if required.
const HostDisplay::RenderAPI render_api = m_display->GetRenderAPI();
bool needs_switch = false;
switch (g_settings.gpu_renderer)
{
#ifdef WIN32
case GPURenderer::HardwareD3D11:
needs_switch = (render_api != HostDisplay::RenderAPI::D3D11);
#ifdef SDL_VIDEO_DRIVER_WINDOWS
case SDL_SYSWM_WINDOWS:
wi.type = WindowInfo::Type::Win32;
wi.window_handle = syswm.info.win.window;
break;
#endif
case GPURenderer::HardwareVulkan:
needs_switch = (render_api != HostDisplay::RenderAPI::Vulkan);
#ifdef SDL_VIDEO_DRIVER_COCOA
case SDL_SYSWM_COCOA:
wi.type = WindowInfo::Type::MacOS;
wi.window_handle = GetContentViewFromWindow(syswm.info.cocoa.window);
break;
#endif
case GPURenderer::HardwareOpenGL:
needs_switch = (render_api != HostDisplay::RenderAPI::OpenGL && render_api != HostDisplay::RenderAPI::OpenGLES);
#ifdef SDL_VIDEO_DRIVER_X11
case SDL_SYSWM_X11:
wi.type = WindowInfo::Type::X11;
wi.window_handle = reinterpret_cast<void*>(static_cast<uintptr_t>(syswm.info.x11.window));
wi.display_connection = syswm.info.x11.display;
break;
#endif
#ifdef SDL_VIDEO_DRIVER_WAYLAND
case SDL_SYSWM_WAYLAND:
wi.type = WindowInfo::Type::Wayland;
wi.window_handle = syswm.info.wl.surface;
wi.display_connection = syswm.info.wl.display;
break;
#endif
case GPURenderer::Software:
default:
needs_switch = false;
break;
Log_ErrorPrintf("Unhandled syswm subsystem %u", static_cast<u32>(syswm.subsystem));
return std::nullopt;
}
if (needs_switch)
{
ImGui::EndFrame();
DestroyDisplay();
// We need to recreate the window, otherwise bad things happen...
DestroySDLWindow();
if (!CreateSDLWindow())
Panic("Failed to recreate SDL window on GPU renderer switch");
if (!CreateDisplay())
Panic("Failed to recreate display on GPU renderer switch");
ImGui::NewFrame();
}
if (!CreateHostDisplayResources())
return false;
return true;
}
void SDLHostInterface::ReleaseHostDisplay()
{
ReleaseHostDisplayResources();
// restore vsync, since we don't want to burn cycles at the menu
m_display->SetVSync(true);
return wi;
}
std::optional<CommonHostInterface::HostKeyCode> SDLHostInterface::GetHostKeyCode(const std::string_view key_code) const
@ -272,197 +250,6 @@ std::optional<CommonHostInterface::HostKeyCode> SDLHostInterface::GetHostKeyCode
return static_cast<HostKeyCode>(*code);
}
void SDLHostInterface::UpdateInputMap()
{
CommonHostInterface::UpdateInputMap(*m_settings_interface.get());
}
void SDLHostInterface::OnSystemCreated()
{
CommonHostInterface::OnSystemCreated();
FullscreenUI::SystemCreated();
}
void SDLHostInterface::OnSystemPaused(bool paused)
{
CommonHostInterface::OnSystemPaused(paused);
FullscreenUI::SystemPaused(paused);
}
void SDLHostInterface::OnSystemDestroyed()
{
CommonHostInterface::OnSystemDestroyed();
ReportFormattedMessage("System shut down.");
FullscreenUI::SystemDestroyed();
}
void SDLHostInterface::OnRunningGameChanged()
{
CommonHostInterface::OnRunningGameChanged();
Settings old_settings(std::move(g_settings));
CommonHostInterface::LoadSettings(*m_settings_interface.get());
CommonHostInterface::ApplyGameSettings(true);
CommonHostInterface::FixIncompatibleSettings(true);
CheckForSettingsChanges(old_settings);
if (!System::GetRunningTitle().empty())
SDL_SetWindowTitle(m_window, System::GetRunningTitle().c_str());
else
SDL_SetWindowTitle(m_window, GetWindowTitle());
}
void SDLHostInterface::RequestExit()
{
m_quit_request = true;
}
void SDLHostInterface::RunLater(std::function<void()> callback)
{
SDL_Event ev = {};
ev.type = SDL_USEREVENT;
ev.user.code = m_run_later_event_id;
ev.user.data1 = new std::function<void()>(std::move(callback));
SDL_PushEvent(&ev);
}
void SDLHostInterface::ApplySettings(bool display_osd_messages)
{
Settings old_settings(std::move(g_settings));
CommonHostInterface::LoadSettings(*m_settings_interface.get());
CommonHostInterface::ApplyGameSettings(display_osd_messages);
CommonHostInterface::FixIncompatibleSettings(display_osd_messages);
CheckForSettingsChanges(old_settings);
}
bool SDLHostInterface::IsFullscreen() const
{
return m_fullscreen;
}
bool SDLHostInterface::SetFullscreen(bool enabled)
{
if (m_fullscreen == enabled)
return true;
SDL_SetWindowFullscreen(m_window, enabled ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
int window_width, window_height;
SDL_GetWindowSize(m_window, &window_width, &window_height);
m_display->ResizeRenderWindow(window_width, window_height);
if (!System::IsShutdown())
g_gpu->UpdateResolutionScale();
m_fullscreen = enabled;
return true;
}
std::unique_ptr<SDLHostInterface> SDLHostInterface::Create()
{
return std::make_unique<SDLHostInterface>();
}
bool SDLHostInterface::Initialize()
{
if (!CommonHostInterface::Initialize())
return false;
// Change to the user directory so that all default/relative paths in the config are after this.
if (!FileSystem::SetWorkingDirectory(m_user_directory.c_str()))
Log_ErrorPrintf("Failed to set working directory to '%s'", m_user_directory.c_str());
if (!CreateSDLWindow())
{
Log_ErrorPrintf("Failed to create SDL window");
return false;
}
CreateImGuiContext();
if (!CreateDisplay())
{
Log_ErrorPrintf("Failed to create host display");
return false;
}
// process events to pick up controllers before updating input map
ProcessEvents();
UpdateInputMap();
return true;
}
void SDLHostInterface::Shutdown()
{
DestroySystem();
CommonHostInterface::Shutdown();
if (m_display)
{
DestroyDisplay();
ImGui::DestroyContext();
}
if (m_window)
DestroySDLWindow();
}
std::string SDLHostInterface::GetStringSettingValue(const char* section, const char* key,
const char* default_value /*= ""*/)
{
return m_settings_interface->GetStringValue(section, key, default_value);
}
bool SDLHostInterface::GetBoolSettingValue(const char* section, const char* key, bool default_value /* = false */)
{
return m_settings_interface->GetBoolValue(section, key, default_value);
}
int SDLHostInterface::GetIntSettingValue(const char* section, const char* key, int default_value /* = 0 */)
{
return m_settings_interface->GetIntValue(section, key, default_value);
}
float SDLHostInterface::GetFloatSettingValue(const char* section, const char* key, float default_value /* = 0.0f */)
{
return m_settings_interface->GetFloatValue(section, key, default_value);
}
bool SDLHostInterface::RequestRenderWindowSize(s32 new_window_width, s32 new_window_height)
{
if (new_window_width <= 0 || new_window_height <= 0 || m_fullscreen)
return false;
// use imgui scale as the dpr
const float dpi_scale = ImGui::GetIO().DisplayFramebufferScale.x;
const s32 scaled_width =
std::max<s32>(static_cast<s32>(std::ceil(static_cast<float>(new_window_width) * dpi_scale)), 1);
const s32 scaled_height = std::max<s32>(
static_cast<s32>(std::ceil(static_cast<float>(new_window_height) * dpi_scale)) + m_display->GetDisplayTopMargin(),
1);
SDL_SetWindowSize(m_window, scaled_width, scaled_height);
s32 window_width, window_height;
SDL_GetWindowSize(m_window, &window_width, &window_height);
m_display->ResizeRenderWindow(window_width, window_height);
UpdateFramebufferScale();
if (!System::IsShutdown())
g_gpu->UpdateResolutionScale();
return true;
}
void SDLHostInterface::LoadSettings()
{
// Settings need to be loaded prior to creating the window for OpenGL bits.
m_settings_interface = std::make_unique<INISettingsInterface>(GetSettingsFileName());
CommonHostInterface::LoadSettings(*m_settings_interface.get());
CommonHostInterface::FixIncompatibleSettings(false);
}
void SDLHostInterface::ReportError(const char* message)
{
const bool was_fullscreen = IsFullscreen();
@ -514,16 +301,35 @@ bool SDLHostInterface::ConfirmMessage(const char* message)
return result;
}
void SDLHostInterface::PollAndUpdate()
{
// Process SDL events before the controller interface can steal them.
const bool is_sdl_controller_interface =
(m_controller_interface && m_controller_interface->GetBackend() == ControllerInterface::Backend::SDL);
for (;;)
{
SDL_Event ev;
if (!SDL_PollEvent(&ev))
break;
if (is_sdl_controller_interface &&
static_cast<SDLControllerInterface*>(m_controller_interface.get())->ProcessSDLEvent(&ev))
{
continue;
}
HandleSDLEvent(&ev);
}
ImGui_ImplSDL2_NewFrame();
CommonHostInterface::PollAndUpdate();
}
void SDLHostInterface::HandleSDLEvent(const SDL_Event* event)
{
ImGui_ImplSDL2_ProcessEvent(event);
if (m_controller_interface &&
static_cast<SDLControllerInterface*>(m_controller_interface.get())->ProcessSDLEvent(event))
{
return;
}
switch (event->type)
{
case SDL_WINDOWEVENT:
@ -531,14 +337,11 @@ void SDLHostInterface::HandleSDLEvent(const SDL_Event* event)
if (event->window.event == SDL_WINDOWEVENT_RESIZED)
{
m_display->ResizeRenderWindow(event->window.data1, event->window.data2);
UpdateFramebufferScale();
if (!System::IsShutdown())
g_gpu->UpdateResolutionScale();
OnPlatformWindowResized(event->window.data1, event->window.data2, GetDPIScaleFactor(m_window));
}
else if (event->window.event == SDL_WINDOWEVENT_MOVED)
{
UpdateFramebufferScale();
// TODO: Do we want to update DPI scale here?
}
}
break;
@ -576,86 +379,5 @@ void SDLHostInterface::HandleSDLEvent(const SDL_Event* event)
}
}
break;
case SDL_USEREVENT:
{
if (static_cast<u32>(event->user.code) == m_run_later_event_id)
{
std::function<void()>* callback = static_cast<std::function<void()>*>(event->user.data1);
Assert(callback);
(*callback)();
delete callback;
}
}
break;
}
}
void SDLHostInterface::PollAndUpdate()
{
ProcessEvents();
CommonHostInterface::PollAndUpdate();
}
void SDLHostInterface::ProcessEvents()
{
for (;;)
{
SDL_Event ev;
if (SDL_PollEvent(&ev))
HandleSDLEvent(&ev);
else
break;
}
}
void SDLHostInterface::Run()
{
while (!m_quit_request)
{
PollAndUpdate();
if (System::IsRunning())
{
if (m_display_all_frames)
System::RunFrame();
else
System::RunFrames();
UpdateControllerRumble();
if (m_frame_step_request)
{
m_frame_step_request = false;
PauseSystem(true);
}
}
// rendering
{
ImGui_ImplSDL2_NewFrame();
FullscreenUI::SetImGuiNavInputs();
ImGui::NewFrame();
DrawImGuiWindows();
ImGui::Render();
ImGui::EndFrame();
m_display->Render();
if (System::IsRunning())
{
System::UpdatePerformanceCounters();
if (m_throttler_enabled)
System::Throttle();
}
}
}
// Save state on exit so it can be resumed
if (!System::IsShutdown())
{
if (g_settings.save_state_on_exit)
SaveResumeSaveState();
DestroySystem();
}
}