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:
@@ -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,42 +18,28 @@ 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) {
|
const auto startFrameTime = std::chrono::high_resolution_clock::now();
|
||||||
core->parallel.UpdateScreen(core->cpu->GetMem().mmio.vi, false);
|
if (!core->pause) {
|
||||||
fps = -1.0;
|
core->Run(settings.getVolumeL(), settings.getVolumeR());
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(started) {
|
const auto endFrameTime = std::chrono::high_resolution_clock::now();
|
||||||
started = false;
|
using namespace std::chrono_literals;
|
||||||
const auto startFrameTime = std::chrono::high_resolution_clock::now();
|
const auto frameTimeMs = std::chrono::duration<double>(endFrameTime - startFrameTime) / 1ms;
|
||||||
if (!core->pause) {
|
avgFps += frameTimeMs;
|
||||||
core->Run(settings.getVolumeL(), settings.getVolumeR());
|
|
||||||
}
|
sampledFps++;
|
||||||
|
|
||||||
if (core->render) {
|
if (const auto elapsedSinceLastSample = std::chrono::duration<double>(endFrameTime - lastSample) / 1s;
|
||||||
core->parallel.UpdateScreen(core->cpu->GetMem().mmio.vi);
|
elapsedSinceLastSample >= 1.0) {
|
||||||
}
|
if (!oneSecondPassed) {
|
||||||
|
oneSecondPassed = true;
|
||||||
const auto endFrameTime = std::chrono::high_resolution_clock::now();
|
continue;
|
||||||
using namespace std::chrono_literals;
|
|
||||||
const auto frameTimeMs = std::chrono::duration<double>(endFrameTime - startFrameTime) / 1ms;
|
|
||||||
avgFps += frameTimeMs;
|
|
||||||
|
|
||||||
sampledFps++;
|
|
||||||
|
|
||||||
if (const auto elapsedSinceLastSample = std::chrono::duration<double>(endFrameTime - lastSample) / 1s;
|
|
||||||
elapsedSinceLastSample >= 1.0) {
|
|
||||||
if (!oneSecondPassed) {
|
|
||||||
oneSecondPassed = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
lastSample = endFrameTime;
|
|
||||||
avgFps /= sampledFps;
|
|
||||||
sampledFps = 0;
|
|
||||||
fps = 1000.0 / avgFps;
|
|
||||||
}
|
}
|
||||||
|
lastSample = endFrameTime;
|
||||||
|
avgFps /= sampledFps;
|
||||||
|
sampledFps = 0;
|
||||||
|
fps = 1000.0 / avgFps;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SetRender(false);
|
SetRender(false);
|
||||||
@@ -63,8 +48,6 @@ EmuThread::EmuThread(const std::shared_ptr<n64::Core> &core, double &fps, Render
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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(),
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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,22 +49,14 @@ 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();
|
if(exec)
|
||||||
|
exec();
|
||||||
|
ImGui::EndMainStatusBar();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
ImVec2 statusBarSize, statusBarPos;
|
return false;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
std::function<void()> exec = nullptr;
|
std::function<void()> exec = nullptr;
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user