diff --git a/src/frontend/EmuThread.cpp b/src/frontend/EmuThread.cpp index 7375d0ec..e0b4b2dd 100644 --- a/src/frontend/EmuThread.cpp +++ b/src/frontend/EmuThread.cpp @@ -4,11 +4,10 @@ EmuThread::EmuThread(const std::shared_ptr &core, double &fps, RenderWidget &renderWidget, SettingsWindow &settings) noexcept : - renderWidget(renderWidget), core(core), settings(settings), fps(fps) { + renderWidget(renderWidget), core(core), settings(settings), fps(fps) {} +void EmuThread::start() noexcept { thread = std::thread([&]() { - core->parallel.Init(renderWidget.wsiPlatform, renderWidget.windowInfo, core->cpu->GetMem().GetRDRAMPtr()); - parallelRDPInitialized = true; isRunning = true; auto lastSample = std::chrono::high_resolution_clock::now(); @@ -19,42 +18,28 @@ EmuThread::EmuThread(const std::shared_ptr &core, double &fps, Render fps = 1000.0 / avgFps; while (!interruptionRequested) { - if(!started) { - core->parallel.UpdateScreen(core->cpu->GetMem().mmio.vi, false); - fps = -1.0; - - continue; + const auto startFrameTime = std::chrono::high_resolution_clock::now(); + if (!core->pause) { + core->Run(settings.getVolumeL(), settings.getVolumeR()); } - - if(started) { - started = false; - const auto startFrameTime = std::chrono::high_resolution_clock::now(); - if (!core->pause) { - core->Run(settings.getVolumeL(), settings.getVolumeR()); - } - - if (core->render) { - core->parallel.UpdateScreen(core->cpu->GetMem().mmio.vi); - } - - const auto endFrameTime = std::chrono::high_resolution_clock::now(); - using namespace std::chrono_literals; - const auto frameTimeMs = std::chrono::duration(endFrameTime - startFrameTime) / 1ms; - avgFps += frameTimeMs; - - sampledFps++; - - if (const auto elapsedSinceLastSample = std::chrono::duration(endFrameTime - lastSample) / 1s; - elapsedSinceLastSample >= 1.0) { - if (!oneSecondPassed) { - oneSecondPassed = true; - continue; - } - lastSample = endFrameTime; - avgFps /= sampledFps; - sampledFps = 0; - fps = 1000.0 / avgFps; + + const auto endFrameTime = std::chrono::high_resolution_clock::now(); + using namespace std::chrono_literals; + const auto frameTimeMs = std::chrono::duration(endFrameTime - startFrameTime) / 1ms; + avgFps += frameTimeMs; + + sampledFps++; + + if (const auto elapsedSinceLastSample = std::chrono::duration(endFrameTime - lastSample) / 1s; + elapsedSinceLastSample >= 1.0) { + if (!oneSecondPassed) { + oneSecondPassed = true; + continue; } + lastSample = endFrameTime; + avgFps /= sampledFps; + sampledFps = 0; + fps = 1000.0 / avgFps; } } SetRender(false); @@ -63,8 +48,6 @@ EmuThread::EmuThread(const std::shared_ptr &core, double &fps, Render }); } -void EmuThread::start() noexcept { started = true; } - void EmuThread::TogglePause() const noexcept { core->TogglePause(); Util::RPC::GetInstance().Update(core->pause ? Util::RPC::Paused : Util::RPC::GetInstance().GetState(), diff --git a/src/frontend/ImGuiImpl/GUI.hpp b/src/frontend/ImGuiImpl/GUI.hpp index 6cdd77d3..a72d944b 100644 --- a/src/frontend/ImGuiImpl/GUI.hpp +++ b/src/frontend/ImGuiImpl/GUI.hpp @@ -133,12 +133,14 @@ namespace gui { init_info.ImageCount = 2; init_info.CheckVkResultFn = CheckVkResult; init_info.RenderPass = renderPass; + init_info.ApiVersion = VK_API_VERSION_1_3; ImGui_ImplVulkan_LoadFunctions(VK_API_VERSION_1_3, [](const char *function_name, void *vulkan_instance) { return vkGetInstanceProcAddr((reinterpret_cast(vulkan_instance)), function_name); }, g_Instance); - ImGui_ImplVulkan_Init(&init_info); + if(!ImGui_ImplVulkan_Init(&init_info)) + Util::panic("Failed to initialize ImGui!"); } inline void StartFrame() { diff --git a/src/frontend/ImGuiImpl/StatusBar.hpp b/src/frontend/ImGuiImpl/StatusBar.hpp index 20ef8af2..7d9182aa 100644 --- a/src/frontend/ImGuiImpl/StatusBar.hpp +++ b/src/frontend/ImGuiImpl/StatusBar.hpp @@ -1,7 +1,47 @@ #pragma once #include +#include #include +namespace ImGui { +inline bool BeginMainStatusBar() +{ + ImGuiContext& g = *GetCurrentContext(); + ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport(); + + // Notify of viewport change so GetFrameHeight() can be accurate in case of DPI change + SetCurrentViewport(NULL, viewport); + + // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set. + // FIXME: This could be generalized as an opt-in way to clamp window->DC.CursorStartPos to avoid SafeArea? + // FIXME: Consider removing support for safe area down the line... it's messy. Nowadays consoles have support for TV calibration in OS settings. + g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f)); + ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar; + float height = GetFrameHeight(); + bool is_open = BeginViewportSideBar("##MainStatusBar", viewport, ImGuiDir_Down, height, window_flags); + g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f); + + if (is_open) + BeginMenuBar(); + else + End(); + return is_open; +} + +inline void EndMainStatusBar() +{ + EndMenuBar(); + + // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window + // FIXME: With this strategy we won't be able to restore a NULL focus. + ImGuiContext& g = *GImGui; + if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest) + FocusTopMostWindowUnderOne(g.NavWindow, NULL, NULL, ImGuiFocusRequestFlags_UnlessBelowModal | ImGuiFocusRequestFlags_RestoreFocusedChild); + + End(); +} +} + namespace gui { struct StatusBar { StatusBar(std::function&& func = nullptr, bool enabled = true) : exec(func), enabled(enabled) {} @@ -9,22 +49,14 @@ struct StatusBar { void setFunc(std::function&& func) { exec = func; } bool render() { - float statusWindowHeight = ImGui::GetFrameHeight() * 1.4f; - ImGuiViewport* viewport = ImGui::GetMainViewport(); + if(ImGui::BeginMainStatusBar()) { + if(exec) + exec(); + ImGui::EndMainStatusBar(); + return true; + } - ImVec2 statusBarSize, statusBarPos; - - ImGui::SetNextWindowPos(statusBarPos); - ImGui::SetNextWindowSize(statusBarSize); - ImGui::SetNextWindowViewport(viewport->ID); - - ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoDocking; - ImGui::Begin("StatusBar", nullptr, windowFlags); - if(exec) - exec(); - ImGui::End(); - - return true; + return false; } private: std::function exec = nullptr; diff --git a/src/frontend/KaizenGui.cpp b/src/frontend/KaizenGui.cpp index 9d511157..2dce11d0 100644 --- a/src/frontend/KaizenGui.cpp +++ b/src/frontend/KaizenGui.cpp @@ -5,15 +5,9 @@ #include KaizenGui::KaizenGui() noexcept : window("Kaizen", 1280, 720), core(std::make_shared()), vulkanWidget(core, window.getHandle()), emuThread(core, fpsCounter, vulkanWidget, settingsWindow) { - while(!emuThread.parallelRDPInitialized); gui::Initialize(core->parallel.wsi, window.getHandle()); emuExitFunc = [&]() { - quit = true; - if (emuThread.isRunning) { - emuThread.requestInterruption(); - while (emuThread.isRunning) {} - } }; about.setFunc([&]() { @@ -28,7 +22,7 @@ KaizenGui::KaizenGui() noexcept : window("Kaizen", 1280, 720), core(std::make_sh statusBar.setFunc([&]() { ImGui::Text("GUI FPS: %.2f, Emulation FPS: %s", ImGui::GetIO().Framerate, - fmt::format(fpsCounter > 0 ? fmt::runtime("{:.2f{0}}") : fmt::runtime("{1}"), fpsCounter, "Not playing").c_str()); + fmt::format(fpsCounter > 0 ? fmt::runtime("{0:.2f}") : fmt::runtime("{1}"), fpsCounter, "Not playing").c_str()); }); menuBar.addMenu({"File", @@ -68,14 +62,16 @@ KaizenGui::KaizenGui() noexcept : window("Kaizen", 1280, 720), core(std::make_sh void KaizenGui::RenderUI() { gui::StartFrame(); - menuBar.render(); - about.render(); - statusBar.render(); - gui::EndFrame(); + + if (core->render) { + core->parallel.UpdateScreen(core->cpu->GetMem().mmio.vi); + } else { + core->parallel.UpdateScreen(core->cpu->GetMem().mmio.vi, false); + } } void KaizenGui::LoadROM(const std::string &path) noexcept { @@ -91,7 +87,6 @@ void KaizenGui::LoadROM(const std::string &path) noexcept { void KaizenGui::handleEvents() { SDL_Event e; while(SDL_PollEvent(&e)) { - ImGui_ImplSDL3_ProcessEvent(&e); switch(e.type) { case SDL_EVENT_QUIT: emuExitFunc(); @@ -102,7 +97,13 @@ void KaizenGui::handleEvents() { int KaizenGui::run() { while(!quit) { - handleEvents(); + if(vulkanWidget.wsiPlatform->quitRequested) { + quit = true; + if (emuThread.isRunning) { + emuThread.requestInterruption(); + while (emuThread.isRunning) {} + } + } RenderUI(); } diff --git a/src/frontend/KaizenGui.hpp b/src/frontend/KaizenGui.hpp index b74b9523..1a8d4a1a 100644 --- a/src/frontend/KaizenGui.hpp +++ b/src/frontend/KaizenGui.hpp @@ -11,7 +11,7 @@ class KaizenGui final { gui::NativeWindow window; public: explicit KaizenGui() noexcept; - double fpsCounter; + double fpsCounter = -1.0; gui::MenuBar menuBar; gui::MenuItem actionPause{"Pause"}, actionStop{"Stop"}, actionReset{"Reset"}; SettingsWindow settingsWindow; diff --git a/src/frontend/RenderWidget.cpp b/src/frontend/RenderWidget.cpp index a5455172..fda8f44b 100644 --- a/src/frontend/RenderWidget.cpp +++ b/src/frontend/RenderWidget.cpp @@ -3,19 +3,22 @@ #include #include #include +#include RenderWidget::RenderWidget(const std::shared_ptr &core, SDL_Window* window) { wsiPlatform = std::make_shared(core, window); windowInfo = std::make_shared(window); + core->parallel.Init(wsiPlatform, windowInfo, core->cpu->GetMem().GetRDRAMPtr()); } void SDLWSIPlatform::poll_input() { - if (!canPollEvents) - return; - SDL_Event e; while (SDL_PollEvent(&e)) { + ImGui_ImplSDL3_ProcessEvent(&e); switch (e.type) { + case SDL_EVENT_QUIT: + quitRequested = true; + break; case SDL_EVENT_GAMEPAD_ADDED: { const auto index = e.gdevice.which; diff --git a/src/frontend/RenderWidget.hpp b/src/frontend/RenderWidget.hpp index dc37c821..6f4b8c7a 100644 --- a/src/frontend/RenderWidget.hpp +++ b/src/frontend/RenderWidget.hpp @@ -57,20 +57,17 @@ public: void event_frame_tick(double frame, double elapsed) override {} - void EnableEventPolling() { canPollEvents = true; } - void DisableEventPolling() { canPollEvents = false; } - const VkApplicationInfo *get_application_info() override { return &appInfo; } VkApplicationInfo appInfo{.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .apiVersion = VK_API_VERSION_1_3}; SDL_Window* window{}; VkSurfaceKHR surface; + bool quitRequested = false; private: std::shared_ptr core; SDL_Gamepad *gamepad{}; bool gamepadConnected = false; - bool canPollEvents = true; }; class RenderWidget final { @@ -78,5 +75,5 @@ public: explicit RenderWidget(const std::shared_ptr &, SDL_Window*); std::shared_ptr windowInfo; - std::shared_ptr wsiPlatform; + std::shared_ptr wsiPlatform; };