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,
|
||||
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<n64::Core> &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<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;
|
||||
const auto endFrameTime = std::chrono::high_resolution_clock::now();
|
||||
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;
|
||||
}
|
||||
}
|
||||
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 {
|
||||
core->TogglePause();
|
||||
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.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<VkInstance>(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() {
|
||||
|
||||
@@ -1,7 +1,47 @@
|
||||
#pragma once
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#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 {
|
||||
struct StatusBar {
|
||||
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; }
|
||||
|
||||
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<void()> exec = nullptr;
|
||||
|
||||
@@ -5,15 +5,9 @@
|
||||
#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) {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ class KaizenGui final {
|
||||
gui::NativeWindow window;
|
||||
public:
|
||||
explicit KaizenGui() noexcept;
|
||||
double fpsCounter;
|
||||
double fpsCounter = -1.0;
|
||||
gui::MenuBar<true> menuBar;
|
||||
gui::MenuItem actionPause{"Pause"}, actionStop{"Stop"}, actionReset{"Reset"};
|
||||
SettingsWindow settingsWindow;
|
||||
|
||||
@@ -3,19 +3,22 @@
|
||||
#include <RenderWidget.hpp>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <InputSettings.hpp>
|
||||
#include <imgui_impl_sdl3.h>
|
||||
|
||||
RenderWidget::RenderWidget(const std::shared_ptr<n64::Core> &core, SDL_Window* window) {
|
||||
wsiPlatform = std::make_shared<SDLWSIPlatform>(core, window);
|
||||
windowInfo = std::make_shared<SDLParallelRdpWindowInfo>(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;
|
||||
|
||||
@@ -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<n64::Core> core;
|
||||
SDL_Gamepad *gamepad{};
|
||||
bool gamepadConnected = false;
|
||||
bool canPollEvents = true;
|
||||
};
|
||||
|
||||
class RenderWidget final {
|
||||
@@ -78,5 +75,5 @@ public:
|
||||
explicit RenderWidget(const std::shared_ptr<n64::Core> &, SDL_Window*);
|
||||
|
||||
std::shared_ptr<ParallelRDP::WindowInfo> windowInfo;
|
||||
std::shared_ptr<Vulkan::WSIPlatform> wsiPlatform;
|
||||
std::shared_ptr<SDLWSIPlatform> wsiPlatform;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user