Easier solution: only run the actual emulation logic in the child thread; call Parallel-RDP in main thread. Still needs sync with RDRAM buffer (the GPU crashes now LMAO)

This commit is contained in:
SimoZ64
2025-05-22 23:18:39 +02:00
parent 326b4b43cd
commit ca21e57835
7 changed files with 95 additions and 77 deletions

View File

@@ -4,11 +4,10 @@
EmuThread::EmuThread(const std::shared_ptr<n64::Core> &core, double &fps, RenderWidget &renderWidget, EmuThread::EmuThread(const std::shared_ptr<n64::Core> &core, double &fps, RenderWidget &renderWidget,
SettingsWindow &settings) noexcept : 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([&]() { thread = std::thread([&]() {
core->parallel.Init(renderWidget.wsiPlatform, renderWidget.windowInfo, core->cpu->GetMem().GetRDRAMPtr());
parallelRDPInitialized = true;
isRunning = true; isRunning = true;
auto lastSample = std::chrono::high_resolution_clock::now(); auto lastSample = std::chrono::high_resolution_clock::now();
@@ -19,24 +18,11 @@ EmuThread::EmuThread(const std::shared_ptr<n64::Core> &core, double &fps, Render
fps = 1000.0 / avgFps; fps = 1000.0 / avgFps;
while (!interruptionRequested) { while (!interruptionRequested) {
if(!started) {
core->parallel.UpdateScreen(core->cpu->GetMem().mmio.vi, false);
fps = -1.0;
continue;
}
if(started) {
started = false;
const auto startFrameTime = std::chrono::high_resolution_clock::now(); const auto startFrameTime = std::chrono::high_resolution_clock::now();
if (!core->pause) { if (!core->pause) {
core->Run(settings.getVolumeL(), settings.getVolumeR()); 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(); const auto endFrameTime = std::chrono::high_resolution_clock::now();
using namespace std::chrono_literals; using namespace std::chrono_literals;
const auto frameTimeMs = std::chrono::duration<double>(endFrameTime - startFrameTime) / 1ms; const auto frameTimeMs = std::chrono::duration<double>(endFrameTime - startFrameTime) / 1ms;
@@ -56,15 +42,12 @@ EmuThread::EmuThread(const std::shared_ptr<n64::Core> &core, double &fps, Render
fps = 1000.0 / avgFps; fps = 1000.0 / avgFps;
} }
} }
}
SetRender(false); SetRender(false);
Stop(); Stop();
isRunning = false; isRunning = false;
}); });
} }
void EmuThread::start() noexcept { started = true; }
void EmuThread::TogglePause() const noexcept { void EmuThread::TogglePause() const noexcept {
core->TogglePause(); core->TogglePause();
Util::RPC::GetInstance().Update(core->pause ? Util::RPC::Paused : Util::RPC::GetInstance().GetState(), Util::RPC::GetInstance().Update(core->pause ? Util::RPC::Paused : Util::RPC::GetInstance().GetState(),

View File

@@ -133,12 +133,14 @@ namespace gui {
init_info.ImageCount = 2; init_info.ImageCount = 2;
init_info.CheckVkResultFn = CheckVkResult; init_info.CheckVkResultFn = CheckVkResult;
init_info.RenderPass = renderPass; 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) { ImGui_ImplVulkan_LoadFunctions(VK_API_VERSION_1_3, [](const char *function_name, void *vulkan_instance) {
return vkGetInstanceProcAddr((reinterpret_cast<VkInstance>(vulkan_instance)), function_name); return vkGetInstanceProcAddr((reinterpret_cast<VkInstance>(vulkan_instance)), function_name);
}, g_Instance); }, g_Instance);
ImGui_ImplVulkan_Init(&init_info); if(!ImGui_ImplVulkan_Init(&init_info))
Util::panic("Failed to initialize ImGui!");
} }
inline void StartFrame() { inline void StartFrame() {

View File

@@ -1,7 +1,47 @@
#pragma once #pragma once
#include <imgui.h> #include <imgui.h>
#include <imgui_internal.h>
#include <functional> #include <functional>
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 { namespace gui {
struct StatusBar { struct StatusBar {
StatusBar(std::function<void()>&& func = nullptr, bool enabled = true) : exec(func), enabled(enabled) {} StatusBar(std::function<void()>&& func = nullptr, bool enabled = true) : exec(func), enabled(enabled) {}
@@ -9,23 +49,15 @@ struct StatusBar {
void setFunc(std::function<void()>&& func) { exec = func; } void setFunc(std::function<void()>&& func) { exec = func; }
bool render() { bool render() {
float statusWindowHeight = ImGui::GetFrameHeight() * 1.4f; if(ImGui::BeginMainStatusBar()) {
ImGuiViewport* viewport = ImGui::GetMainViewport();
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) if(exec)
exec(); exec();
ImGui::End(); ImGui::EndMainStatusBar();
return true; return true;
} }
return false;
}
private: private:
std::function<void()> exec = nullptr; std::function<void()> exec = nullptr;
bool enabled = true; bool enabled = true;

View File

@@ -5,15 +5,9 @@
#include <ImGuiImpl/GUI.hpp> #include <ImGuiImpl/GUI.hpp>
KaizenGui::KaizenGui() noexcept : window("Kaizen", 1280, 720), core(std::make_shared<n64::Core>()), vulkanWidget(core, window.getHandle()), emuThread(core, fpsCounter, vulkanWidget, settingsWindow) { KaizenGui::KaizenGui() noexcept : window("Kaizen", 1280, 720), core(std::make_shared<n64::Core>()), vulkanWidget(core, window.getHandle()), emuThread(core, fpsCounter, vulkanWidget, settingsWindow) {
while(!emuThread.parallelRDPInitialized);
gui::Initialize(core->parallel.wsi, window.getHandle()); gui::Initialize(core->parallel.wsi, window.getHandle());
emuExitFunc = [&]() { emuExitFunc = [&]() {
quit = true;
if (emuThread.isRunning) {
emuThread.requestInterruption();
while (emuThread.isRunning) {}
}
}; };
about.setFunc([&]() { about.setFunc([&]() {
@@ -28,7 +22,7 @@ KaizenGui::KaizenGui() noexcept : window("Kaizen", 1280, 720), core(std::make_sh
statusBar.setFunc([&]() { statusBar.setFunc([&]() {
ImGui::Text("GUI FPS: %.2f, Emulation FPS: %s", ImGui::GetIO().Framerate, 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", menuBar.addMenu({"File",
@@ -68,14 +62,16 @@ KaizenGui::KaizenGui() noexcept : window("Kaizen", 1280, 720), core(std::make_sh
void KaizenGui::RenderUI() { void KaizenGui::RenderUI() {
gui::StartFrame(); gui::StartFrame();
menuBar.render(); menuBar.render();
about.render(); about.render();
statusBar.render(); statusBar.render();
gui::EndFrame(); 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 { void KaizenGui::LoadROM(const std::string &path) noexcept {
@@ -91,7 +87,6 @@ void KaizenGui::LoadROM(const std::string &path) noexcept {
void KaizenGui::handleEvents() { void KaizenGui::handleEvents() {
SDL_Event e; SDL_Event e;
while(SDL_PollEvent(&e)) { while(SDL_PollEvent(&e)) {
ImGui_ImplSDL3_ProcessEvent(&e);
switch(e.type) { switch(e.type) {
case SDL_EVENT_QUIT: case SDL_EVENT_QUIT:
emuExitFunc(); emuExitFunc();
@@ -102,7 +97,13 @@ void KaizenGui::handleEvents() {
int KaizenGui::run() { int KaizenGui::run() {
while(!quit) { while(!quit) {
handleEvents(); if(vulkanWidget.wsiPlatform->quitRequested) {
quit = true;
if (emuThread.isRunning) {
emuThread.requestInterruption();
while (emuThread.isRunning) {}
}
}
RenderUI(); RenderUI();
} }

View File

@@ -11,7 +11,7 @@ class KaizenGui final {
gui::NativeWindow window; gui::NativeWindow window;
public: public:
explicit KaizenGui() noexcept; explicit KaizenGui() noexcept;
double fpsCounter; double fpsCounter = -1.0;
gui::MenuBar<true> menuBar; gui::MenuBar<true> menuBar;
gui::MenuItem actionPause{"Pause"}, actionStop{"Stop"}, actionReset{"Reset"}; gui::MenuItem actionPause{"Pause"}, actionStop{"Stop"}, actionReset{"Reset"};
SettingsWindow settingsWindow; SettingsWindow settingsWindow;

View File

@@ -3,19 +3,22 @@
#include <RenderWidget.hpp> #include <RenderWidget.hpp>
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <InputSettings.hpp> #include <InputSettings.hpp>
#include <imgui_impl_sdl3.h>
RenderWidget::RenderWidget(const std::shared_ptr<n64::Core> &core, SDL_Window* window) { RenderWidget::RenderWidget(const std::shared_ptr<n64::Core> &core, SDL_Window* window) {
wsiPlatform = std::make_shared<SDLWSIPlatform>(core, window); wsiPlatform = std::make_shared<SDLWSIPlatform>(core, window);
windowInfo = std::make_shared<SDLParallelRdpWindowInfo>(window); windowInfo = std::make_shared<SDLParallelRdpWindowInfo>(window);
core->parallel.Init(wsiPlatform, windowInfo, core->cpu->GetMem().GetRDRAMPtr());
} }
void SDLWSIPlatform::poll_input() { void SDLWSIPlatform::poll_input() {
if (!canPollEvents)
return;
SDL_Event e; SDL_Event e;
while (SDL_PollEvent(&e)) { while (SDL_PollEvent(&e)) {
ImGui_ImplSDL3_ProcessEvent(&e);
switch (e.type) { switch (e.type) {
case SDL_EVENT_QUIT:
quitRequested = true;
break;
case SDL_EVENT_GAMEPAD_ADDED: case SDL_EVENT_GAMEPAD_ADDED:
{ {
const auto index = e.gdevice.which; const auto index = e.gdevice.which;

View File

@@ -57,20 +57,17 @@ public:
void event_frame_tick(double frame, double elapsed) override {} 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; } const VkApplicationInfo *get_application_info() override { return &appInfo; }
VkApplicationInfo appInfo{.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .apiVersion = VK_API_VERSION_1_3}; VkApplicationInfo appInfo{.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .apiVersion = VK_API_VERSION_1_3};
SDL_Window* window{}; SDL_Window* window{};
VkSurfaceKHR surface; VkSurfaceKHR surface;
bool quitRequested = false;
private: private:
std::shared_ptr<n64::Core> core; std::shared_ptr<n64::Core> core;
SDL_Gamepad *gamepad{}; SDL_Gamepad *gamepad{};
bool gamepadConnected = false; bool gamepadConnected = false;
bool canPollEvents = true;
}; };
class RenderWidget final { class RenderWidget final {
@@ -78,5 +75,5 @@ public:
explicit RenderWidget(const std::shared_ptr<n64::Core> &, SDL_Window*); explicit RenderWidget(const std::shared_ptr<n64::Core> &, SDL_Window*);
std::shared_ptr<ParallelRDP::WindowInfo> windowInfo; std::shared_ptr<ParallelRDP::WindowInfo> windowInfo;
std::shared_ptr<Vulkan::WSIPlatform> wsiPlatform; std::shared_ptr<SDLWSIPlatform> wsiPlatform;
}; };