Duckstation/src/duckstation-sdl/sdl_host_interface.cpp

1310 lines
36 KiB
C++
Raw Normal View History

#include "sdl_host_interface.h"
2020-01-10 03:31:12 +00:00
#include "common/assert.h"
#include "common/byte_stream.h"
#include "common/log.h"
#include "common/string_util.h"
#include "core/controller.h"
#include "core/gpu.h"
#include "core/host_display.h"
2019-10-04 03:54:09 +00:00
#include "core/system.h"
#include "frontend-common/icon.h"
#include "frontend-common/imgui_styles.h"
#include "frontend-common/sdl_audio_stream.h"
#include "frontend-common/sdl_controller_interface.h"
#include "imgui_impl_sdl.h"
#include "opengl_host_display.h"
#include "sdl_settings_interface.h"
2019-09-09 07:01:26 +00:00
#include <cinttypes>
#include <cmath>
2019-10-04 04:24:52 +00:00
#include <imgui.h>
2019-11-07 13:52:19 +00:00
#include <imgui_stdlib.h>
2019-10-20 10:47:27 +00:00
#include <nfd.h>
Log_SetChannel(SDLHostInterface);
2019-09-09 07:01:26 +00:00
2020-01-10 03:31:12 +00:00
#ifdef WIN32
#include "d3d11_host_display.h"
#endif
SDLHostInterface::SDLHostInterface()
{
2020-02-15 15:33:43 +00:00
m_run_later_event_id = SDL_RegisterEvents(1);
}
2019-09-09 07:01:26 +00:00
SDLHostInterface::~SDLHostInterface()
2019-09-09 07:01:26 +00:00
{
g_sdl_controller_interface.Shutdown();
if (m_display)
{
DestroyDisplay();
ImGui::DestroyContext();
}
2019-09-12 14:18:13 +00:00
if (m_window)
DestroySDLWindow();
2019-09-09 07:01:26 +00:00
}
bool SDLHostInterface::CreateSDLWindow()
2019-09-09 07:01:26 +00:00
{
constexpr u32 DEFAULT_WINDOW_WIDTH = 900;
constexpr u32 DEFAULT_WINDOW_HEIGHT = 700;
// Create window.
const u32 window_flags =
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | (UseOpenGLRenderer() ? SDL_WINDOW_OPENGL : 0);
2019-09-12 14:18:13 +00:00
2019-10-04 04:24:52 +00:00
m_window = SDL_CreateWindow("DuckStation", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, DEFAULT_WINDOW_WIDTH,
DEFAULT_WINDOW_HEIGHT, window_flags);
2019-09-12 14:18:13 +00:00
if (!m_window)
return false;
2019-09-09 07:01:26 +00:00
2019-10-04 04:24:52 +00:00
// Set window icon.
SDL_Surface* icon_surface =
SDL_CreateRGBSurfaceFrom(const_cast<unsigned int*>(WINDOW_ICON_DATA), WINDOW_ICON_WIDTH, WINDOW_ICON_HEIGHT, 32,
WINDOW_ICON_WIDTH * sizeof(u32), UINT32_C(0x000000FF), UINT32_C(0x0000FF00),
UINT32_C(0x00FF0000), UINT32_C(0xFF000000));
2019-10-04 04:24:52 +00:00
if (icon_surface)
{
SDL_SetWindowIcon(m_window, icon_surface);
SDL_FreeSurface(icon_surface);
}
2019-09-12 14:18:13 +00:00
return true;
}
2019-09-09 07:01:26 +00:00
void SDLHostInterface::DestroySDLWindow()
{
SDL_DestroyWindow(m_window);
m_window = nullptr;
}
bool SDLHostInterface::CreateDisplay()
2019-09-12 14:18:13 +00:00
{
const bool debug_device = m_settings.gpu_use_debug_device;
std::unique_ptr<HostDisplay> display;
#ifdef WIN32
display = UseOpenGLRenderer() ? OpenGLHostDisplay::Create(m_window, debug_device) :
D3D11HostDisplay::Create(m_window, debug_device);
#else
display = OpenGLHostDisplay::Create(m_window, debug_device);
#endif
2019-09-09 07:01:26 +00:00
if (!display)
2019-09-12 14:18:13 +00:00
return false;
2019-09-09 07:01:26 +00:00
m_app_icon_texture =
display->CreateTexture(APP_ICON_WIDTH, APP_ICON_HEIGHT, APP_ICON_DATA, APP_ICON_WIDTH * sizeof(u32));
if (!display)
return false;
2019-09-09 07:01:26 +00:00
m_display = display.release();
2019-09-12 14:18:13 +00:00
return true;
}
void SDLHostInterface::DestroyDisplay()
{
m_app_icon_texture.reset();
delete m_display;
m_display = nullptr;
}
2019-11-07 15:07:39 +00:00
void SDLHostInterface::CreateImGuiContext()
2019-09-12 14:18:13 +00:00
{
2019-09-09 07:01:26 +00:00
ImGui::CreateContext();
ImGui::GetIO().IniFilename = nullptr;
ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
2019-11-07 13:59:04 +00:00
ImGui::StyleColorsDarker();
2019-11-07 14:22:10 +00:00
ImGui::AddRobotoRegularFont();
2019-09-09 07:01:26 +00:00
}
bool SDLHostInterface::AcquireHostDisplay()
2019-10-10 16:20:21 +00:00
{
// Handle renderer switch if required on Windows.
#ifdef WIN32
const HostDisplay::RenderAPI render_api = m_display->GetRenderAPI();
const bool render_api_is_gl =
render_api == HostDisplay::RenderAPI::OpenGL || render_api == HostDisplay::RenderAPI::OpenGLES;
const bool render_api_wants_gl = UseOpenGLRenderer();
if (render_api_is_gl != render_api_wants_gl)
{
ImGui::EndFrame();
DestroyDisplay();
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();
}
#endif
return true;
2019-11-07 15:07:39 +00:00
}
void SDLHostInterface::ReleaseHostDisplay()
{
// restore vsync, since we don't want to burn cycles at the menu
m_display->SetVSync(true);
}
std::unique_ptr<AudioStream> SDLHostInterface::CreateAudioStream(AudioBackend backend)
{
switch (backend)
{
case AudioBackend::Null:
return AudioStream::CreateNullAudioStream();
case AudioBackend::Cubeb:
return AudioStream::CreateCubebAudioStream();
case AudioBackend::SDL:
return SDLAudioStream::Create();
default:
return nullptr;
}
}
void SDLHostInterface::OnSystemCreated()
{
HostInterface::OnSystemCreated();
UpdateKeyboardControllerMapping();
g_sdl_controller_interface.SetDefaultBindings();
ClearImGuiFocus();
}
void SDLHostInterface::OnSystemPaused(bool paused)
{
HostInterface::OnSystemPaused(paused);
if (!paused)
ClearImGuiFocus();
}
2019-11-07 13:52:19 +00:00
void SDLHostInterface::OnSystemDestroyed()
{
HostInterface::OnSystemDestroyed();
}
void SDLHostInterface::OnControllerTypeChanged(u32 slot)
{
HostInterface::OnControllerTypeChanged(slot);
UpdateKeyboardControllerMapping();
g_sdl_controller_interface.SetDefaultBindings();
}
2020-02-15 15:33:43 +00:00
void SDLHostInterface::RunLater(std::function<void()> callback)
{
SDL_Event ev = {};
ev.type = SDL_USEREVENT;
2020-02-15 15:33:43 +00:00
ev.user.code = m_run_later_event_id;
ev.user.data1 = new std::function<void()>(std::move(callback));
SDL_PushEvent(&ev);
}
2020-02-15 15:33:43 +00:00
void SDLHostInterface::SaveAndUpdateSettings()
{
SDLSettingsInterface si(GetSettingsFileName().c_str());
m_settings.Save(si);
m_settings.Load(si);
}
2019-11-15 04:57:27 +00:00
void SDLHostInterface::UpdateFullscreen()
{
SDL_SetWindowFullscreen(m_window, m_settings.display_fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
// We set the margin only in windowed mode, the menu bar is drawn on top in fullscreen.
m_display->SetDisplayTopMargin(
m_settings.display_fullscreen ? 0 : static_cast<int>(20.0f * ImGui::GetIO().DisplayFramebufferScale.x));
}
std::unique_ptr<SDLHostInterface> SDLHostInterface::Create()
2019-09-09 07:01:26 +00:00
{
std::unique_ptr<SDLHostInterface> intf = std::make_unique<SDLHostInterface>();
2019-11-07 15:07:39 +00:00
// Settings need to be loaded prior to creating the window for OpenGL bits.
SDLSettingsInterface si(intf->GetSettingsFileName().c_str());
intf->m_settings.Load(si);
2019-11-07 15:07:39 +00:00
if (!intf->CreateSDLWindow())
{
Log_ErrorPrintf("Failed to create SDL window");
2019-09-12 14:18:13 +00:00
return nullptr;
2019-11-07 15:07:39 +00:00
}
if (!g_sdl_controller_interface.Initialize(intf.get()))
{
Log_ErrorPrintf("Failed to initialize controller interface.");
return nullptr;
}
2019-11-07 15:07:39 +00:00
intf->CreateImGuiContext();
if (!intf->CreateDisplay())
{
Log_ErrorPrintf("Failed to create host display");
return nullptr;
}
ImGui::NewFrame();
2019-09-12 14:18:13 +00:00
2019-11-15 04:57:27 +00:00
intf->UpdateFullscreen();
2019-09-12 14:18:13 +00:00
return intf;
2019-09-09 07:01:26 +00:00
}
void SDLHostInterface::ReportError(const char* message)
{
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "DuckStation Error", message, m_window);
}
void SDLHostInterface::ReportMessage(const char* message)
2019-09-09 07:01:26 +00:00
{
AddOSDMessage(message, 2.0f);
2019-09-09 07:01:26 +00:00
}
void SDLHostInterface::HandleSDLEvent(const SDL_Event* event)
2019-10-23 11:39:48 +00:00
{
ImGui_ImplSDL2_ProcessEvent(event);
g_sdl_controller_interface.ProcessSDLEvent(event);
2019-10-23 11:39:48 +00:00
switch (event->type)
{
2020-02-03 03:53:31 +00:00
case SDL_WINDOWEVENT:
{
2019-10-23 11:39:48 +00:00
if (event->window.event == SDL_WINDOWEVENT_RESIZED)
m_display->WindowResized();
2019-10-23 11:39:48 +00:00
}
break;
case SDL_QUIT:
2019-10-27 11:22:33 +00:00
m_quit_request = true;
2019-10-23 11:39:48 +00:00
break;
case SDL_KEYDOWN:
2020-02-03 03:53:31 +00:00
case SDL_KEYUP:
{
if (!ImGui::GetIO().WantCaptureKeyboard)
HandleSDLKeyEvent(event);
}
break;
2019-10-23 11:39:48 +00:00
2020-02-03 03:53:31 +00:00
case SDL_CONTROLLERDEVICEADDED:
case SDL_CONTROLLERDEVICEREMOVED:
g_sdl_controller_interface.SetDefaultBindings();
break;
2019-10-23 11:39:48 +00:00
case SDL_CONTROLLERBUTTONDOWN:
2020-02-03 03:53:31 +00:00
case SDL_CONTROLLERBUTTONUP:
{
if (event->type == SDL_CONTROLLERBUTTONDOWN && event->cbutton.button == SDL_CONTROLLER_BUTTON_RIGHTSTICK)
{
// focus the menu bar
m_focus_main_menu_bar = true;
}
2019-10-23 11:39:48 +00:00
}
break;
2020-02-03 03:53:31 +00:00
case SDL_USEREVENT:
{
2020-02-15 15:33:43 +00:00
if (static_cast<u32>(event->user.code) == m_run_later_event_id)
{
2020-02-15 15:33:43 +00:00
std::function<void()>* callback = static_cast<std::function<void()>*>(event->user.data1);
Assert(callback);
(*callback)();
delete callback;
}
}
break;
2019-10-23 11:39:48 +00:00
}
}
void SDLHostInterface::HandleSDLKeyEvent(const SDL_Event* event)
2019-10-23 11:39:48 +00:00
{
const bool repeat = event->key.repeat != 0;
if (!repeat && HandleSDLKeyEventForController(event))
2019-10-23 11:39:48 +00:00
return;
const bool pressed = (event->type == SDL_KEYDOWN);
switch (event->key.keysym.scancode)
{
case SDL_SCANCODE_F1:
case SDL_SCANCODE_F2:
case SDL_SCANCODE_F3:
case SDL_SCANCODE_F4:
case SDL_SCANCODE_F5:
case SDL_SCANCODE_F6:
case SDL_SCANCODE_F7:
2020-02-03 03:53:31 +00:00
case SDL_SCANCODE_F8:
{
if (!pressed)
{
const u32 index = event->key.keysym.scancode - SDL_SCANCODE_F1 + 1;
if (event->key.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT))
SaveState(true, index);
else
LoadState(true, index);
}
}
break;
2020-02-03 03:53:31 +00:00
case SDL_SCANCODE_F11:
{
2019-11-15 04:57:27 +00:00
if (!pressed)
DoToggleFullscreen();
}
break;
2020-02-03 03:53:31 +00:00
case SDL_SCANCODE_TAB:
{
if (!repeat)
{
m_speed_limiter_temp_disabled = pressed;
UpdateSpeedLimiterState();
}
}
break;
2019-10-27 11:22:33 +00:00
2020-02-03 03:53:31 +00:00
case SDL_SCANCODE_PAUSE:
{
2019-10-27 11:22:33 +00:00
if (pressed)
PauseSystem(!m_paused);
2019-10-27 11:22:33 +00:00
}
break;
2020-02-03 03:53:31 +00:00
case SDL_SCANCODE_SPACE:
{
if (pressed)
DoFrameStep();
}
break;
2020-02-03 03:53:31 +00:00
case SDL_SCANCODE_HOME:
{
if (pressed && !repeat && m_system)
{
2019-11-07 15:07:39 +00:00
m_settings.speed_limiter_enabled = !m_settings.speed_limiter_enabled;
UpdateSpeedLimiterState();
2019-11-07 13:52:19 +00:00
AddOSDMessage(m_system->GetSettings().speed_limiter_enabled ? "Speed limiter enabled." :
"Speed limiter disabled.");
}
}
break;
2020-02-03 03:53:31 +00:00
case SDL_SCANCODE_END:
{
if (pressed)
ToggleSoftwareRendering();
}
break;
case SDL_SCANCODE_PAGEUP:
2020-02-03 03:53:31 +00:00
case SDL_SCANCODE_PAGEDOWN:
{
if (pressed)
ModifyResolutionScale(event->key.keysym.scancode == SDL_SCANCODE_PAGEUP ? 1 : -1);
}
break;
2019-09-09 07:01:26 +00:00
}
}
void SDLHostInterface::UpdateKeyboardControllerMapping()
{
m_keyboard_button_mapping.fill(-1);
const Controller* controller = m_system ? m_system->GetController(0) : nullptr;
if (controller)
{
#define SET_BUTTON_MAP(action, name) \
m_keyboard_button_mapping[static_cast<int>(action)] = controller->GetButtonCodeByName(name).value_or(-1)
SET_BUTTON_MAP(KeyboardControllerAction::Up, "Up");
SET_BUTTON_MAP(KeyboardControllerAction::Down, "Down");
SET_BUTTON_MAP(KeyboardControllerAction::Left, "Left");
SET_BUTTON_MAP(KeyboardControllerAction::Right, "Right");
SET_BUTTON_MAP(KeyboardControllerAction::Triangle, "Triangle");
SET_BUTTON_MAP(KeyboardControllerAction::Cross, "Cross");
SET_BUTTON_MAP(KeyboardControllerAction::Square, "Square");
SET_BUTTON_MAP(KeyboardControllerAction::Circle, "Circle");
SET_BUTTON_MAP(KeyboardControllerAction::L1, "L1");
SET_BUTTON_MAP(KeyboardControllerAction::R1, "R1");
SET_BUTTON_MAP(KeyboardControllerAction::L2, "L2");
SET_BUTTON_MAP(KeyboardControllerAction::R2, "R2");
SET_BUTTON_MAP(KeyboardControllerAction::Start, "Start");
SET_BUTTON_MAP(KeyboardControllerAction::Select, "Select");
#undef SET_BUTTON_MAP
}
}
bool SDLHostInterface::HandleSDLKeyEventForController(const SDL_Event* event)
{
const bool pressed = (event->type == SDL_KEYDOWN);
Controller* controller;
#define DO_ACTION(action) \
if ((controller = m_system ? m_system->GetController(0) : nullptr) != nullptr && \
m_keyboard_button_mapping[static_cast<int>(action)]) \
{ \
controller->SetButtonState(m_keyboard_button_mapping[static_cast<int>(action)], pressed); \
}
switch (event->key.keysym.scancode)
{
case SDL_SCANCODE_KP_8:
case SDL_SCANCODE_I:
DO_ACTION(KeyboardControllerAction::Triangle);
return true;
case SDL_SCANCODE_KP_2:
case SDL_SCANCODE_K:
DO_ACTION(KeyboardControllerAction::Cross);
return true;
case SDL_SCANCODE_KP_4:
case SDL_SCANCODE_J:
DO_ACTION(KeyboardControllerAction::Square);
return true;
case SDL_SCANCODE_KP_6:
case SDL_SCANCODE_L:
DO_ACTION(KeyboardControllerAction::Circle);
return true;
case SDL_SCANCODE_W:
case SDL_SCANCODE_UP:
DO_ACTION(KeyboardControllerAction::Up);
return true;
case SDL_SCANCODE_S:
case SDL_SCANCODE_DOWN:
DO_ACTION(KeyboardControllerAction::Down);
return true;
case SDL_SCANCODE_A:
case SDL_SCANCODE_LEFT:
DO_ACTION(KeyboardControllerAction::Left);
return true;
case SDL_SCANCODE_D:
case SDL_SCANCODE_RIGHT:
DO_ACTION(KeyboardControllerAction::Right);
return true;
case SDL_SCANCODE_Q:
DO_ACTION(KeyboardControllerAction::L1);
return true;
case SDL_SCANCODE_E:
DO_ACTION(KeyboardControllerAction::R1);
return true;
case SDL_SCANCODE_1:
DO_ACTION(KeyboardControllerAction::L2);
return true;
case SDL_SCANCODE_3:
DO_ACTION(KeyboardControllerAction::R2);
return true;
case SDL_SCANCODE_RETURN:
DO_ACTION(KeyboardControllerAction::Start);
return true;
case SDL_SCANCODE_BACKSPACE:
DO_ACTION(KeyboardControllerAction::Select);
return true;
default:
break;
}
#undef DO_ACTION
return false;
}
void SDLHostInterface::DrawImGui()
2019-09-09 07:01:26 +00:00
{
DrawMainMenuBar();
2019-09-09 07:01:26 +00:00
2019-10-20 11:18:11 +00:00
if (m_system)
DrawDebugWindows();
2019-10-20 11:18:11 +00:00
else
DrawPoweredOffWindow();
2019-11-07 13:52:19 +00:00
if (m_settings_window_open)
DrawSettingsWindow();
2019-10-20 11:18:11 +00:00
if (m_about_window_open)
DrawAboutWindow();
2019-10-10 16:20:21 +00:00
DrawOSDMessages();
2019-09-26 11:44:02 +00:00
2019-09-09 07:01:26 +00:00
ImGui::Render();
}
void SDLHostInterface::DrawMainMenuBar()
2019-09-09 07:01:26 +00:00
{
2019-11-15 04:57:27 +00:00
// We skip drawing the menu bar if we're in fullscreen and the mouse pointer isn't in range.
const float SHOW_THRESHOLD = 20.0f;
if (m_settings.display_fullscreen &&
ImGui::GetIO().MousePos.y >= (SHOW_THRESHOLD * ImGui::GetIO().DisplayFramebufferScale.x) &&
!ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow))
{
return;
}
2019-09-09 07:01:26 +00:00
if (!ImGui::BeginMainMenuBar())
return;
const bool system_enabled = static_cast<bool>(m_system);
if (m_focus_main_menu_bar)
{
ImGui::OpenPopup("System");
m_focus_main_menu_bar = false;
}
2019-09-09 07:01:26 +00:00
if (ImGui::BeginMenu("System"))
{
if (ImGui::MenuItem("Start Disc", nullptr, false, !system_enabled))
{
DoStartDisc();
ClearImGuiFocus();
}
if (ImGui::MenuItem("Start BIOS", nullptr, false, !system_enabled))
{
BootSystemFromBIOS();
ClearImGuiFocus();
}
ImGui::Separator();
2019-10-27 11:22:33 +00:00
if (ImGui::MenuItem("Power Off", nullptr, false, system_enabled))
{
DestroySystem();
ClearImGuiFocus();
}
2019-10-27 11:22:33 +00:00
2019-10-20 11:18:11 +00:00
if (ImGui::MenuItem("Reset", nullptr, false, system_enabled))
{
ResetSystem();
ClearImGuiFocus();
}
2019-09-09 07:01:26 +00:00
2019-10-27 11:22:33 +00:00
if (ImGui::MenuItem("Pause", nullptr, m_paused, system_enabled))
{
PauseSystem(!m_paused);
ClearImGuiFocus();
}
2019-10-20 11:18:11 +00:00
2019-09-09 07:01:26 +00:00
ImGui::Separator();
if (ImGui::MenuItem("Change Disc", nullptr, false, system_enabled))
{
DoChangeDisc();
ClearImGuiFocus();
}
if (ImGui::MenuItem("Frame Step", nullptr, false, system_enabled))
{
DoFrameStep();
ClearImGuiFocus();
}
2019-10-20 11:18:11 +00:00
ImGui::Separator();
2019-09-09 07:01:26 +00:00
if (ImGui::BeginMenu("Load State"))
{
for (u32 i = 1; i <= GLOBAL_SAVE_STATE_SLOTS; i++)
2019-09-09 07:01:26 +00:00
{
2020-01-10 03:31:12 +00:00
char buf[16];
std::snprintf(buf, sizeof(buf), "State %u", i);
if (ImGui::MenuItem(buf))
{
LoadState(true, i);
ClearImGuiFocus();
}
2019-09-09 07:01:26 +00:00
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Save State", system_enabled))
2019-09-09 07:01:26 +00:00
{
for (u32 i = 1; i <= GLOBAL_SAVE_STATE_SLOTS; i++)
2019-09-09 07:01:26 +00:00
{
2020-01-10 03:31:12 +00:00
char buf[16];
std::snprintf(buf, sizeof(buf), "State %u", i);
if (ImGui::MenuItem(buf))
{
SaveState(true, i);
ClearImGuiFocus();
}
2019-09-09 07:01:26 +00:00
}
ImGui::EndMenu();
}
2019-10-20 11:18:11 +00:00
ImGui::Separator();
2019-09-09 07:01:26 +00:00
if (ImGui::MenuItem("Exit"))
2019-10-27 11:22:33 +00:00
m_quit_request = true;
2019-09-09 07:01:26 +00:00
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Settings"))
2019-09-09 07:01:26 +00:00
{
2019-11-07 15:07:39 +00:00
if (ImGui::MenuItem("Change Settings..."))
m_settings_window_open = true;
2019-09-09 07:01:26 +00:00
ImGui::Separator();
2019-09-09 07:01:26 +00:00
2019-11-07 15:07:39 +00:00
DrawQuickSettingsMenu();
ImGui::EndMenu();
}
2019-11-07 15:07:39 +00:00
if (ImGui::BeginMenu("Debug", system_enabled))
{
DrawDebugMenu();
2019-11-07 15:07:39 +00:00
ImGui::EndMenu();
}
2019-10-20 11:18:11 +00:00
if (ImGui::BeginMenu("Help"))
{
if (ImGui::MenuItem("GitHub Repository"))
{
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Add URL Opener", "https://github.com/stenzek/duckstation",
m_window);
}
2019-10-04 10:48:29 +00:00
2019-10-20 11:18:11 +00:00
ImGui::Separator();
2019-10-04 10:48:29 +00:00
2019-10-20 11:18:11 +00:00
if (ImGui::MenuItem("About"))
m_about_window_open = true;
2019-10-20 11:18:11 +00:00
ImGui::EndMenu();
}
if (m_system)
{
2019-10-27 11:22:33 +00:00
if (!m_paused)
{
ImGui::SetCursorPosX(ImGui::GetIO().DisplaySize.x - 210.0f);
const float speed = m_system->GetEmulationSpeed();
const u32 rounded_speed = static_cast<u32>(std::round(speed));
if (speed < 90.0f)
2019-10-27 11:22:33 +00:00
ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "%u%%", rounded_speed);
else if (speed < 110.0f)
2019-10-27 11:22:33 +00:00
ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), "%u%%", rounded_speed);
else
ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "%u%%", rounded_speed);
2019-10-27 11:22:33 +00:00
ImGui::SetCursorPosX(ImGui::GetIO().DisplaySize.x - 165.0f);
ImGui::Text("FPS: %.2f", m_system->GetFPS());
2019-10-27 11:22:33 +00:00
ImGui::SetCursorPosX(ImGui::GetIO().DisplaySize.x - 80.0f);
ImGui::Text("VPS: %.2f", m_system->GetVPS());
2019-10-27 11:22:33 +00:00
}
else
{
ImGui::SetCursorPosX(ImGui::GetIO().DisplaySize.x - 50.0f);
ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Paused");
}
}
2019-09-09 07:01:26 +00:00
ImGui::EndMainMenuBar();
}
2019-11-07 15:07:39 +00:00
void SDLHostInterface::DrawQuickSettingsMenu()
{
bool settings_changed = false;
settings_changed |= ImGui::MenuItem("Enable Speed Limiter", nullptr, &m_settings.speed_limiter_enabled);
2019-11-07 15:07:39 +00:00
ImGui::Separator();
2019-11-23 10:22:09 +00:00
if (ImGui::BeginMenu("CPU Execution Mode"))
{
const CPUExecutionMode current = m_settings.cpu_execution_mode;
for (u32 i = 0; i < static_cast<u32>(CPUExecutionMode::Count); i++)
{
if (ImGui::MenuItem(Settings::GetCPUExecutionModeDisplayName(static_cast<CPUExecutionMode>(i)), nullptr,
i == static_cast<u32>(current)))
{
m_settings.cpu_execution_mode = static_cast<CPUExecutionMode>(i);
settings_changed = true;
}
}
ImGui::EndMenu();
}
ImGui::Separator();
2019-11-07 15:07:39 +00:00
if (ImGui::BeginMenu("Renderer"))
{
const GPURenderer current = m_settings.gpu_renderer;
for (u32 i = 0; i < static_cast<u32>(GPURenderer::Count); i++)
2019-11-07 15:07:39 +00:00
{
if (ImGui::MenuItem(Settings::GetRendererDisplayName(static_cast<GPURenderer>(i)), nullptr,
2019-11-07 15:07:39 +00:00
i == static_cast<u32>(current)))
{
m_settings.gpu_renderer = static_cast<GPURenderer>(i);
2019-11-07 15:07:39 +00:00
settings_changed = true;
}
}
ImGui::EndMenu();
}
2019-11-15 04:57:27 +00:00
if (ImGui::MenuItem("Fullscreen", nullptr, &m_settings.display_fullscreen))
{
settings_changed = true;
2019-11-15 04:57:27 +00:00
UpdateFullscreen();
}
2019-11-07 15:07:39 +00:00
settings_changed |= ImGui::MenuItem("VSync", nullptr, &m_settings.video_sync_enabled);
2019-11-07 15:07:39 +00:00
ImGui::Separator();
if (ImGui::BeginMenu("Resolution Scale"))
{
const u32 current_internal_resolution = m_settings.gpu_resolution_scale;
for (u32 scale = 1; scale <= m_settings.max_gpu_resolution_scale; scale++)
{
2020-01-10 03:31:12 +00:00
char buf[32];
std::snprintf(buf, sizeof(buf), "%ux (%ux%u)", scale, scale * GPU::VRAM_WIDTH, scale * GPU::VRAM_HEIGHT);
if (ImGui::MenuItem(buf, nullptr, current_internal_resolution == scale))
2019-11-07 15:07:39 +00:00
{
m_settings.gpu_resolution_scale = scale;
settings_changed = true;
2019-11-07 15:07:39 +00:00
}
}
ImGui::EndMenu();
}
settings_changed |= ImGui::MenuItem("True (24-Bit) Color", nullptr, &m_settings.gpu_true_color);
settings_changed |= ImGui::MenuItem("Texture Filtering", nullptr, &m_settings.gpu_texture_filtering);
settings_changed |= ImGui::MenuItem("Display Linear Filtering", nullptr, &m_settings.display_linear_filtering);
2019-11-07 15:07:39 +00:00
if (settings_changed)
2020-02-15 15:33:43 +00:00
RunLater(std::bind(&SDLHostInterface::SaveAndUpdateSettings, this));
2019-11-07 15:07:39 +00:00
}
void SDLHostInterface::DrawDebugMenu()
{
Settings::DebugSettings& debug_settings = m_settings.debugging;
ImGui::MenuItem("Show System State");
ImGui::Separator();
ImGui::MenuItem("Show GPU State", nullptr, &debug_settings.show_gpu_state);
ImGui::MenuItem("Show VRAM", nullptr, &debug_settings.show_vram);
ImGui::MenuItem("Dump CPU to VRAM Copies", nullptr, &debug_settings.dump_cpu_to_vram_copies);
ImGui::MenuItem("Dump VRAM to CPU Copies", nullptr, &debug_settings.dump_vram_to_cpu_copies);
ImGui::Separator();
ImGui::MenuItem("Show CDROM State", nullptr, &debug_settings.show_cdrom_state);
ImGui::Separator();
ImGui::MenuItem("Show SPU State", nullptr, &debug_settings.show_spu_state);
ImGui::Separator();
ImGui::MenuItem("Show Timers State", nullptr, &debug_settings.show_timers_state);
ImGui::Separator();
ImGui::MenuItem("Show MDEC State", nullptr, &debug_settings.show_mdec_state);
ImGui::Separator();
}
void SDLHostInterface::DrawPoweredOffWindow()
{
constexpr int WINDOW_WIDTH = 400;
constexpr int WINDOW_HEIGHT = 650;
constexpr int BUTTON_WIDTH = 200;
constexpr int BUTTON_HEIGHT = 40;
ImGui::SetNextWindowSize(ImVec2(WINDOW_WIDTH, WINDOW_HEIGHT));
2019-10-27 10:41:16 +00:00
ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x * 0.5f, ImGui::GetIO().DisplaySize.y * 0.5f),
ImGuiCond_Always, ImVec2(0.5f, 0.5f));
if (!ImGui::Begin("Powered Off", nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoBringToFrontOnFocus))
{
ImGui::End();
}
ImGui::SetCursorPosX((WINDOW_WIDTH - APP_ICON_WIDTH) / 2);
ImGui::Image(m_app_icon_texture->GetHandle(), ImVec2(APP_ICON_WIDTH, APP_ICON_HEIGHT));
ImGui::SetCursorPosY(APP_ICON_HEIGHT + 32);
2019-10-22 13:07:51 +00:00
static const ImVec2 button_size(static_cast<float>(BUTTON_WIDTH), static_cast<float>(BUTTON_HEIGHT));
constexpr float button_left = static_cast<float>((WINDOW_WIDTH - BUTTON_WIDTH) / 2);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
ImGui::PushStyleColor(ImGuiCol_Button, 0xFF202020);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, 0xFF808080);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, 0xFF575757);
ImGui::SetCursorPosX(button_left);
if (ImGui::Button("Resume", button_size))
{
2020-02-15 15:33:43 +00:00
RunLater([this]() { ResumeSystemFromMostRecentState(); });
ClearImGuiFocus();
}
ImGui::NewLine();
ImGui::SetCursorPosX(button_left);
2019-10-20 10:47:27 +00:00
if (ImGui::Button("Start Disc", button_size))
{
2020-02-15 15:33:43 +00:00
RunLater([this]() { DoStartDisc(); });
ClearImGuiFocus();
}
ImGui::NewLine();
ImGui::SetCursorPosX(button_left);
if (ImGui::Button("Start BIOS", button_size))
{
2020-02-15 15:33:43 +00:00
RunLater([this]() { BootSystemFromFile(nullptr); });
ClearImGuiFocus();
}
ImGui::NewLine();
ImGui::SetCursorPosX(button_left);
if (ImGui::Button("Load State", button_size))
ImGui::OpenPopup("PowerOffWindow_LoadStateMenu");
if (ImGui::BeginPopup("PowerOffWindow_LoadStateMenu"))
{
for (u32 i = 1; i <= GLOBAL_SAVE_STATE_SLOTS; i++)
{
2020-01-10 03:31:12 +00:00
char buf[16];
std::snprintf(buf, sizeof(buf), "State %u", i);
if (ImGui::MenuItem(buf))
{
2020-02-15 15:33:43 +00:00
RunLater([this, i]() { LoadState(true, i); });
ClearImGuiFocus();
}
}
ImGui::EndPopup();
}
ImGui::NewLine();
ImGui::SetCursorPosX(button_left);
2019-11-07 13:52:19 +00:00
if (ImGui::Button("Settings", button_size))
m_settings_window_open = true;
ImGui::NewLine();
ImGui::SetCursorPosX(button_left);
if (ImGui::Button("Exit", button_size))
2019-10-27 11:22:33 +00:00
m_quit_request = true;
ImGui::NewLine();
ImGui::PopStyleColor(3);
ImGui::PopStyleVar(2);
ImGui::End();
}
2019-11-07 13:52:19 +00:00
static bool DrawSettingsSectionHeader(const char* title)
{
return ImGui::CollapsingHeader(title, ImGuiTreeNodeFlags_DefaultOpen /* | ImGuiTreeNodeFlags_Leaf*/);
}
void SDLHostInterface::DrawSettingsWindow()
{
ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x * 0.5f, ImGui::GetIO().DisplaySize.y * 0.5f),
ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f));
ImGui::SetNextWindowSize(ImVec2(500, 400), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("Settings", &m_settings_window_open, ImGuiWindowFlags_NoResize))
{
ImGui::End();
return;
}
bool settings_changed = false;
if (ImGui::BeginTabBar("SettingsTabBar", 0))
{
const float indent = 150.0f;
if (ImGui::BeginTabItem("General"))
{
if (DrawSettingsSectionHeader("Console"))
{
ImGui::Text("Region:");
ImGui::SameLine(indent);
2019-11-16 10:27:30 +00:00
int region = static_cast<int>(m_settings.region);
if (ImGui::Combo(
"##region", &region,
[](void*, int index, const char** out_text) {
*out_text = Settings::GetConsoleRegionDisplayName(static_cast<ConsoleRegion>(index));
return true;
},
nullptr, static_cast<int>(ConsoleRegion::Count)))
{
m_settings.region = static_cast<ConsoleRegion>(region);
settings_changed = true;
}
2019-11-23 10:22:09 +00:00
ImGui::Text("BIOS Path:");
ImGui::SameLine(indent);
settings_changed |= DrawFileChooser("##bios_path", &m_settings.bios_path);
settings_changed |= ImGui::Checkbox("Enable TTY Output", &m_settings.bios_patch_tty_enable);
settings_changed |= ImGui::Checkbox("Fast Boot", &m_settings.bios_patch_fast_boot);
}
2019-11-07 13:52:19 +00:00
ImGui::NewLine();
if (DrawSettingsSectionHeader("Behavior"))
{
2020-02-15 15:13:48 +00:00
ImGui::Text("Emulation Speed:");
ImGui::SameLine(indent);
settings_changed |= ImGui::SliderFloat("##speed", &m_settings.emulation_speed, 0.25f, 5.0f);
settings_changed |= ImGui::Checkbox("Enable Speed Limiter", &m_settings.speed_limiter_enabled);
settings_changed |= ImGui::Checkbox("Increase Timer Resolution", &m_settings.increase_timer_resolution);
settings_changed |= ImGui::Checkbox("Pause On Start", &m_settings.start_paused);
settings_changed |= ImGui::Checkbox("Save State On Exit", &m_settings.save_state_on_exit);
}
ImGui::NewLine();
if (DrawSettingsSectionHeader("Audio"))
{
ImGui::Text("Backend:");
ImGui::SameLine(indent);
int backend = static_cast<int>(m_settings.audio_backend);
if (ImGui::Combo(
"##backend", &backend,
[](void*, int index, const char** out_text) {
*out_text = Settings::GetAudioBackendDisplayName(static_cast<AudioBackend>(index));
return true;
},
nullptr, static_cast<int>(AudioBackend::Count)))
{
m_settings.audio_backend = static_cast<AudioBackend>(backend);
settings_changed = true;
}
settings_changed |= ImGui::Checkbox("Output Sync", &m_settings.audio_sync_enabled);
}
2019-11-07 13:52:19 +00:00
ImGui::EndTabItem();
}
2019-12-15 12:24:27 +00:00
if (ImGui::BeginTabItem("Ports"))
2019-11-07 13:52:19 +00:00
{
for (int i = 0; i < 2; i++)
{
2020-01-10 03:31:12 +00:00
char buf[32];
std::snprintf(buf, sizeof(buf), "Front Port %d", 1 + i);
if (DrawSettingsSectionHeader(buf))
2019-12-15 12:24:27 +00:00
{
ImGui::Text("Controller:");
ImGui::SameLine(indent);
int controller_type = static_cast<int>(m_settings.controller_types[i]);
2019-12-15 12:24:27 +00:00
if (ImGui::Combo(
"##controller_type", &controller_type,
[](void*, int index, const char** out_text) {
*out_text = Settings::GetControllerTypeDisplayName(static_cast<ControllerType>(index));
return true;
},
nullptr, static_cast<int>(ControllerType::Count)))
{
m_settings.controller_types[i] = static_cast<ControllerType>(controller_type);
2019-12-15 12:24:27 +00:00
settings_changed = true;
}
}
2019-11-07 13:52:19 +00:00
2019-12-15 12:24:27 +00:00
ImGui::Text("Memory Card Path:");
2019-11-07 13:52:19 +00:00
ImGui::SameLine(indent);
std::string* path_ptr = &m_settings.memory_card_paths[i];
2020-01-10 03:31:12 +00:00
std::snprintf(buf, sizeof(buf), "##memcard_%c_path", 'a' + i);
settings_changed |= DrawFileChooser(buf, path_ptr);
2019-11-07 13:52:19 +00:00
2019-12-15 12:24:27 +00:00
if (ImGui::Button("Eject Memory Card"))
2019-11-07 13:52:19 +00:00
{
path_ptr->clear();
settings_changed = true;
}
ImGui::NewLine();
}
ImGui::EndTabItem();
}
2019-11-23 10:22:09 +00:00
if (ImGui::BeginTabItem("CPU"))
{
ImGui::Text("Execution Mode:");
ImGui::SameLine(indent);
int execution_mode = static_cast<int>(m_settings.cpu_execution_mode);
if (ImGui::Combo(
"##execution_mode", &execution_mode,
[](void*, int index, const char** out_text) {
*out_text = Settings::GetCPUExecutionModeDisplayName(static_cast<CPUExecutionMode>(index));
return true;
},
nullptr, static_cast<int>(CPUExecutionMode::Count)))
{
m_settings.cpu_execution_mode = static_cast<CPUExecutionMode>(execution_mode);
settings_changed = true;
}
ImGui::EndTabItem();
}
2019-11-07 13:52:19 +00:00
if (ImGui::BeginTabItem("GPU"))
{
if (DrawSettingsSectionHeader("Basic"))
{
ImGui::Text("Renderer:");
ImGui::SameLine(indent);
2019-11-07 15:07:39 +00:00
int gpu_renderer = static_cast<int>(m_settings.gpu_renderer);
2019-11-07 13:52:19 +00:00
if (ImGui::Combo(
"##gpu_renderer", &gpu_renderer,
[](void*, int index, const char** out_text) {
*out_text = Settings::GetRendererDisplayName(static_cast<GPURenderer>(index));
2019-11-07 13:52:19 +00:00
return true;
},
nullptr, static_cast<int>(GPURenderer::Count)))
2019-11-07 13:52:19 +00:00
{
m_settings.gpu_renderer = static_cast<GPURenderer>(gpu_renderer);
2019-11-16 10:27:30 +00:00
settings_changed = true;
2019-11-07 13:52:19 +00:00
}
}
ImGui::NewLine();
if (DrawSettingsSectionHeader("Display Output"))
{
2019-11-15 04:57:27 +00:00
if (ImGui::Checkbox("Fullscreen", &m_settings.display_fullscreen))
{
2019-11-15 04:57:27 +00:00
UpdateFullscreen();
settings_changed = true;
}
2019-11-15 04:57:27 +00:00
settings_changed |= ImGui::Checkbox("Linear Filtering", &m_settings.display_linear_filtering);
settings_changed |= ImGui::Checkbox("VSync", &m_settings.video_sync_enabled);
2019-11-07 13:52:19 +00:00
}
ImGui::NewLine();
if (DrawSettingsSectionHeader("Enhancements"))
{
ImGui::Text("Resolution Scale:");
ImGui::SameLine(indent);
static constexpr std::array<const char*, 16> resolutions = {{
"1x (1024x512)",
"2x (2048x1024)",
"3x (3072x1536)",
"4x (4096x2048)",
"5x (5120x2560)",
"6x (6144x3072)",
"7x (7168x3584)",
"8x (8192x4096)",
"9x (9216x4608)",
"10x (10240x5120)",
"11x (11264x5632)",
"12x (12288x6144)",
"13x (13312x6656)",
"14x (14336x7168)",
"15x (15360x7680)",
"16x (16384x8192)",
}};
2019-11-07 15:07:39 +00:00
int current_resolution_index = static_cast<int>(m_settings.gpu_resolution_scale) - 1;
2019-11-07 13:52:19 +00:00
if (ImGui::Combo("##gpu_resolution_scale", &current_resolution_index, resolutions.data(),
static_cast<int>(resolutions.size())))
{
2019-11-07 15:07:39 +00:00
m_settings.gpu_resolution_scale = static_cast<u32>(current_resolution_index + 1);
settings_changed = true;
2019-11-07 13:52:19 +00:00
}
settings_changed |= ImGui::Checkbox("True 24-bit Color (disables dithering)", &m_settings.gpu_true_color);
settings_changed |= ImGui::Checkbox("Texture Filtering", &m_settings.gpu_texture_filtering);
settings_changed |= ImGui::Checkbox("Force Progressive Scan", &m_settings.gpu_force_progressive_scan);
2019-11-07 13:52:19 +00:00
}
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
const auto window_size = ImGui::GetWindowSize();
ImGui::SetCursorPosX(window_size.x - 50.0f);
ImGui::SetCursorPosY(window_size.y - 30.0f);
if (ImGui::Button("Close"))
m_settings_window_open = false;
ImGui::End();
if (settings_changed)
2020-02-15 15:33:43 +00:00
RunLater(std::bind(&SDLHostInterface::SaveAndUpdateSettings, this));
2019-11-07 13:52:19 +00:00
}
void SDLHostInterface::DrawAboutWindow()
2019-10-20 11:18:11 +00:00
{
2019-10-27 10:41:16 +00:00
ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x * 0.5f, ImGui::GetIO().DisplaySize.y * 0.5f),
2019-11-07 13:52:19 +00:00
ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
2019-11-07 14:08:27 +00:00
ImGui::OpenPopup("About DuckStation");
if (!ImGui::BeginPopupModal("About DuckStation", &m_about_window_open, ImGuiWindowFlags_NoResize))
2019-10-20 11:18:11 +00:00
return;
ImGui::Text("DuckStation");
ImGui::NewLine();
ImGui::Text("Authors:");
ImGui::Text(" Connor McLaughlin <stenzek@gmail.com>");
ImGui::NewLine();
ImGui::Text("Uses Dear ImGui (https://github.com/ocornut/imgui)");
ImGui::Text("Uses libcue (https://github.com/lipnitsk/libcue)");
ImGui::Text("Uses stb_image_write (https://github.com/nothings/stb)");
ImGui::Text("Uses simpleini (https://github.com/brofield/simpleini)");
2019-10-20 11:18:11 +00:00
ImGui::NewLine();
ImGui::Text("Duck icon by icons8 (https://icons8.com/icon/74847/platforms.undefined.short-title)");
ImGui::NewLine();
ImGui::SetCursorPosX((ImGui::GetWindowSize().x - 60.0f) / 2.0f);
if (ImGui::Button("Close", ImVec2(60.0f, 20.0f)))
m_about_window_open = false;
2019-11-07 14:08:27 +00:00
ImGui::EndPopup();
2019-10-20 11:18:11 +00:00
}
2019-11-07 13:52:19 +00:00
bool SDLHostInterface::DrawFileChooser(const char* label, std::string* path, const char* filter /* = nullptr */)
{
ImGui::SetNextItemWidth(ImGui::CalcItemWidth() - 50.0f);
bool result = ImGui::InputText(label, path);
ImGui::SameLine();
ImGui::SetNextItemWidth(50.0f);
if (ImGui::Button("..."))
{
nfdchar_t* out_path = nullptr;
if (NFD_OpenDialog(filter, path->c_str(), &out_path) == NFD_OKAY)
{
path->assign(out_path);
result = true;
}
}
return result;
}
void SDLHostInterface::ClearImGuiFocus()
2019-10-20 11:18:11 +00:00
{
ImGui::SetWindowFocus(nullptr);
}
void SDLHostInterface::DoStartDisc()
2019-10-20 10:47:27 +00:00
{
Assert(!m_system);
nfdchar_t* path = nullptr;
if (!NFD_OpenDialog("bin,img,cue,chd,exe,psexe", nullptr, &path) || !path || std::strlen(path) == 0)
2019-10-20 10:47:27 +00:00
return;
2020-01-10 03:31:12 +00:00
AddFormattedOSDMessage(2.0f, "Starting disc from '%s'...", path);
BootSystemFromFile(path);
}
void SDLHostInterface::DoChangeDisc()
{
Assert(m_system);
nfdchar_t* path = nullptr;
if (!NFD_OpenDialog("bin,img,cue,chd,exe,psexe", nullptr, &path) || !path || std::strlen(path) == 0)
return;
if (m_system->InsertMedia(path))
2020-01-10 03:31:12 +00:00
AddFormattedOSDMessage(2.0f, "Switched CD to '%s'", path);
else
AddOSDMessage("Failed to switch CD. The log may contain further information.");
m_system->ResetPerformanceCounters();
2019-10-27 11:22:33 +00:00
}
void SDLHostInterface::DoFrameStep()
{
if (!m_system)
return;
m_frame_step_request = true;
m_paused = false;
}
2019-11-15 04:57:27 +00:00
void SDLHostInterface::DoToggleFullscreen()
{
m_settings.display_fullscreen = !m_settings.display_fullscreen;
UpdateFullscreen();
}
void SDLHostInterface::Run()
{
2019-10-27 11:22:33 +00:00
while (!m_quit_request)
2019-09-09 07:01:26 +00:00
{
for (;;)
{
SDL_Event ev;
if (SDL_PollEvent(&ev))
HandleSDLEvent(&ev);
else
break;
}
2019-10-27 11:22:33 +00:00
if (m_system && !m_paused)
{
m_system->RunFrame();
if (m_frame_step_request)
{
m_frame_step_request = false;
m_paused = true;
}
}
2019-09-26 11:44:02 +00:00
g_sdl_controller_interface.UpdateControllerRumble();
2019-12-15 13:24:34 +00:00
// rendering
{
DrawImGui();
if (m_system)
m_system->GetGPU()->ResetGraphicsAPIState();
ImGui::Render();
m_display->Render();
ImGui::NewFrame();
if (m_system)
{
m_system->GetGPU()->RestoreGraphicsAPIState();
if (m_speed_limiter_enabled)
m_system->Throttle();
}
}
2019-09-09 07:01:26 +00:00
}
// Save state on exit so it can be resumed
if (m_system)
{
if (m_settings.save_state_on_exit)
SaveResumeSaveState();
DestroySystem();
}
}