diff --git a/CMakeLists.txt b/CMakeLists.txt index b8ec4bb..9e7adf4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -139,6 +139,7 @@ if (${CMAKE_BUILD_TYPE} MATCHES Debug AND VULKAN_VALIDATION) endif () find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) +qt_standard_project_setup() add_subdirectory(external/mio) add_subdirectory(src/backend) @@ -184,6 +185,19 @@ if (SANITIZERS) target_link_options(kaizen PUBLIC -fsanitize=undefined -fsanitize=address) endif () +install(TARGETS kaizen + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +qt_generate_deploy_app_script( + TARGET kaizen + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR +) + +install(SCRIPT ${deploy_script}) + file(COPY resources/ DESTINATION ${PROJECT_BINARY_DIR}/resources/) file(REMOVE ${PROJECT_BINARY_DIR}/resources/mario.png diff --git a/external/parallel-rdp/ParallelRDPWrapper.cpp b/external/parallel-rdp/ParallelRDPWrapper.cpp index 46aab61..5632709 100644 --- a/external/parallel-rdp/ParallelRDPWrapper.cpp +++ b/external/parallel-rdp/ParallelRDPWrapper.cpp @@ -194,8 +194,7 @@ void ParallelRDP::UpdateScreen(Util::IntrusivePtr image) const { wsi->end_frame(); } -template <> -void ParallelRDP::UpdateScreen() const { +void ParallelRDP::UpdateScreen() const { const n64::VI &vi = n64::Core::GetMem().mmio.vi; command_processor->set_vi_register(VIRegister::Control, vi.status.raw); command_processor->set_vi_register(VIRegister::Origin, vi.origin); @@ -225,11 +224,6 @@ void ParallelRDP::UpdateScreen() const { command_processor->begin_frame_context(); } -template <> -void ParallelRDP::UpdateScreen() const { - UpdateScreen(static_cast>(nullptr)); -} - void ParallelRDP::EnqueueCommand(const int command_length, const u32 *buffer) const { command_processor->enqueue_command(command_length, buffer); } diff --git a/external/parallel-rdp/ParallelRDPWrapper.hpp b/external/parallel-rdp/ParallelRDPWrapper.hpp index 6389aab..cd01be2 100644 --- a/external/parallel-rdp/ParallelRDPWrapper.hpp +++ b/external/parallel-rdp/ParallelRDPWrapper.hpp @@ -18,7 +18,6 @@ class ParallelRDP { void Init(const std::shared_ptr &, const std::shared_ptr &, Vulkan::InstanceFactory *, const u8 *); - template void UpdateScreen() const; void EnqueueCommand(int, const u32 *) const; void OnFullSync() const; diff --git a/src/backend/Core.cpp b/src/backend/Core.cpp index 9607528..c67ba61 100644 --- a/src/backend/Core.cpp +++ b/src/backend/Core.cpp @@ -4,16 +4,7 @@ #include namespace n64 { -Core::Core() : interpreter(*mem, regs) { - const auto selectedCpu = Options::GetCpuType(); - if (selectedCpu == 0) { - cpuType = PlainInterpreter; - } else if (selectedCpu == 1) { - cpuType = CachedInterpreter; - } else { - panic("Unimplemented CPU type"); - } -} +Core::Core() : interpreter(*mem, regs) { cpuType = Options::GetCpuType(); } void Core::Stop() { pause = true; diff --git a/src/backend/Core.hpp b/src/backend/Core.hpp index 7e67360..0025106 100644 --- a/src/backend/Core.hpp +++ b/src/backend/Core.hpp @@ -4,12 +4,10 @@ #include #include #include +#include namespace n64 { struct Core { - enum CPUType { PlainInterpreter, CachedInterpreter } cpuType = CachedInterpreter; - - bool idleSkip = true; explicit Core(); static Core &GetInstance() { @@ -43,10 +41,12 @@ struct Core { breakpoints.insert(addr); } + bool idleSkip = true; bool pause = true; bool romLoaded = false; int slot = 0; size_t memSize{}, cpuSize{}, verSize{}; + CPUType cpuType; std::string rom; std::set breakpoints{}; std::unique_ptr mem = std::make_unique(); diff --git a/src/frontend/KaizenGui.cpp b/src/frontend/KaizenGui.cpp index bd31f02..79fcacb 100644 --- a/src/frontend/KaizenGui.cpp +++ b/src/frontend/KaizenGui.cpp @@ -22,11 +22,11 @@ KaizenGui::KaizenGui() noexcept : QMainWindow(nullptr), settings(QSettings::User vulkanWidget->hide(); cpuTypeLabel = new QLabel("Interpreter"); - if (Options::GetCpuType() == 1) + if (Options::GetCpuType() == n64::CachedInterpreter) cpuTypeLabel->setText("Cached Interpreter"); idleSkipLabel = new QLabel("Idle skipping"); - if (!Options::GetIdleSkip()) + if (!Options::GetIdleSkip() || Options::GetCpuType() == n64::PlainInterpreter) idleSkipLabel->hide(); fpsLabel = new QLabel("Not running"); @@ -42,7 +42,7 @@ KaizenGui::KaizenGui() noexcept : QMainWindow(nullptr), settings(QSettings::User setAcceptDrops(true); statusBarTimer = new QTimer(); - statusBarTimer->setInterval(1000); + statusBarTimer->setInterval(500); connect(statusBarTimer, &QTimer::timeout, this, [&] { pause->setText("Pause"); @@ -53,6 +53,7 @@ KaizenGui::KaizenGui() noexcept : QMainWindow(nullptr), settings(QSettings::User } if (!core.romLoaded) { + pause->setText("Pause"); pause->setDisabled(true); reset->setDisabled(true); stop->setDisabled(true); @@ -64,11 +65,11 @@ KaizenGui::KaizenGui() noexcept : QMainWindow(nullptr), settings(QSettings::User auto fileMenu = menuBar()->addMenu("File"); auto open = fileMenu->addAction("Open"); + open->setShortcut(QKeyCombination(Qt::CTRL, Qt::Key_O)); connect(open, &QAction::triggered, this, [&] { - auto fileToLoad = - QFileDialog::getOpenFileName(this, "Select a Nintendo 64 ROM", QDir::currentPath(), - "N64 ROM (*.z64 *.n64 *.v64)", nullptr, QFileDialog::DontUseNativeDialog) - .toStdString(); + auto fileToLoad = QFileDialog::getOpenFileName(this, "Select a Nintendo 64 ROM", QDir::currentPath(), + "N64 ROM (*.z64 *.n64 *.v64)") + .toStdString(); if (!fileToLoad.empty()) LoadROM(fileToLoad); }); @@ -82,20 +83,28 @@ KaizenGui::KaizenGui() noexcept : QMainWindow(nullptr), settings(QSettings::User connect(settingsMenu, &QAction::triggered, settingsWindow, &SettingsWindow::show); connect(settingsWindow->cpu, &CPUSettings::cpuTypeChanged, this, [&] { + core.cpuType = Options::GetCpuType(); cpuTypeLabel->setText("Cached Interpreter"); - if (Options::GetCpuType() == 0) + idleSkipLabel->setVisible(Options::GetCpuType() == n64::CachedInterpreter); + if (Options::GetCpuType() == n64::PlainInterpreter) cpuTypeLabel->setText("Interpreter"); }); connect(settingsWindow->cpu, &CPUSettings::idleSkipChanged, this, [&] { - idleSkipLabel->show(); - if (!Options::GetIdleSkip()) - idleSkipLabel->hide(); + core.idleSkip = Options::GetIdleSkip(); + idleSkipLabel->setVisible(Options::GetIdleSkip()); }); emulationMenu->addSeparator(); + auto unlockFramerate = emulationMenu->addAction("Unlock framerate"); + unlockFramerate->setCheckable(true); + + connect(unlockFramerate, &QAction::triggered, this, + [&](bool checked) { core.parallel.SetFramerateUnlocked(checked); }); + pause->setDisabled(true); + pause->setShortcut(QKeyCombination(Qt::CTRL, Qt::Key_P)); emulationMenu->addAction(pause); connect(pause, &QAction::triggered, this, [&] { if (!core.pause) @@ -104,11 +113,14 @@ KaizenGui::KaizenGui() noexcept : QMainWindow(nullptr), settings(QSettings::User core.TogglePause(); }); + reset->setDisabled(true); + reset->setShortcut(QKeyCombination(Qt::CTRL, Qt::Key_R)); emulationMenu->addAction(reset); connect(reset, &QAction::triggered, this, [&] { Scheduler::GetInstance().EnqueueRelative(0, RESET); }); stop->setDisabled(true); + stop->setShortcut(QKeyCombination(Qt::CTRL, Qt::Key_X)); emulationMenu->addAction(stop); connect(stop, &QAction::triggered, this, [&] { Scheduler::GetInstance().EnqueueRelative(0, STOP); @@ -134,47 +146,82 @@ KaizenGui::KaizenGui() noexcept : QMainWindow(nullptr), settings(QSettings::User core.parallel.Init(vulkanWidget->wsiPlatform, vulkanWidget->windowInfo, vulkanWidget->qtVkInstanceFactory.get(), core.GetMem().GetRDRAMPtr()); while (!emuThread->isInterruptionRequested()) { - if (!core.romLoaded) { - core.parallel.UpdateScreen(); + if (!core.romLoaded || core.pause) continue; - } - if (!core.pause) { - auto timeStart = SDL_GetTicks(); - core.Run(); - core.parallel.UpdateScreen(); - elapsed = SDL_GetTicks() - timeStart; - continue; - } + updateKeys(); + updateAxis(); + + auto timeStart = SDL_GetTicks(); + core.Run(); + core.parallel.UpdateScreen(); + elapsed = SDL_GetTicks() - timeStart; } }); emuThread->start(); } -KaizenGui::~KaizenGui() { - SDL_Quit(); - emuThread->requestInterruption(); - emuThread->quit(); +KaizenGui::~KaizenGui() { cleanup(); } + +void KaizenGui::updateKeys() { + auto &pif = core.mem->mmio.si.pif; + pif.UpdateButton(0, n64::Controller::Z, pressedKeys.test(0)); + pif.UpdateButton(0, n64::Controller::A, pressedKeys.test(1)); + pif.UpdateButton(0, n64::Controller::B, pressedKeys.test(2)); + pif.UpdateButton(0, n64::Controller::LT, pressedKeys.test(3)); + pif.UpdateButton(0, n64::Controller::RT, pressedKeys.test(4)); + pif.UpdateButton(0, n64::Controller::Start, pressedKeys.test(5)); + pif.UpdateButton(0, n64::Controller::DUp, pressedKeys.test(6)); + pif.UpdateButton(0, n64::Controller::DDown, pressedKeys.test(7)); + pif.UpdateButton(0, n64::Controller::DLeft, pressedKeys.test(8)); + pif.UpdateButton(0, n64::Controller::DRight, pressedKeys.test(9)); + pif.UpdateButton(0, n64::Controller::CUp, pressedKeys.test(10)); + pif.UpdateButton(0, n64::Controller::CDown, pressedKeys.test(11)); + pif.UpdateButton(0, n64::Controller::CLeft, pressedKeys.test(12)); + pif.UpdateButton(0, n64::Controller::CRight, pressedKeys.test(13)); +} + +void KaizenGui::updateAxis() { + s16 x = 0, y = 0; + auto &pif = core.mem->mmio.si.pif; + if (pressedKeys.test(14)) // up + y += 86; + + if (pressedKeys.test(15)) // down + y -= 86; + + if (pressedKeys.test(16)) // left + x -= 86; + + if (pressedKeys.test(17)) // right + x += 86; + + core.mem->mmio.si.pif.UpdateAxis(0, n64::Controller::X, x); + core.mem->mmio.si.pif.UpdateAxis(0, n64::Controller::Y, y); } void KaizenGui::LoadROM(const std::string &path) noexcept { - n64::Core &core = n64::Core::GetInstance(); core.LoadROM(path); pause->setEnabled(true); reset->setEnabled(true); stop->setEnabled(true); vulkanWidget->show(); - setWindowTitle(("Kaizen " KAIZEN_VERSION_STR " - " + n64::Core::GetMem().rom.gameNameDB).c_str()); + setWindowTitle(("Kaizen " KAIZEN_VERSION_STR " - " + core.mem->rom.gameNameDB).c_str()); } -void KaizenGui::LoadTAS(const std::string &path) noexcept { n64::Core::GetInstance().LoadTAS(fs::path(path)); } +void KaizenGui::LoadTAS(const std::string &path) noexcept { core.LoadTAS(fs::path(path)); } -void KaizenGui::closeEvent(QCloseEvent *event) { - Scheduler::GetInstance().EnqueueRelative(0, STOP); +void KaizenGui::cleanup() { + SDL_Quit(); + core.Stop(); + emuThread->requestInterruption(); + emuThread->quit(); + delete emuThread; + delete vulkanWidget; + delete settingsWindow; settings.setValue("geometry", saveGeometry()); settings.setValue("windowState", saveState()); - QMainWindow::closeEvent(event); } void KaizenGui::dropEvent(QDropEvent *event) { @@ -189,3 +236,17 @@ void KaizenGui::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasUrls()) event->acceptProposedAction(); } + +void KaizenGui::keyPressEvent(QKeyEvent *event) { + for (int i = 0; i < mapping.size(); i++) { + if (mapping[i] == event->key()) + pressedKeys.set(i); + } +} + +void KaizenGui::keyReleaseEvent(QKeyEvent *event) { + for (int i = 0; i < mapping.size(); i++) { + if (mapping[i] == event->key()) + pressedKeys.reset(i); + } +} diff --git a/src/frontend/KaizenGui.hpp b/src/frontend/KaizenGui.hpp index 6c6e987..d581495 100644 --- a/src/frontend/KaizenGui.hpp +++ b/src/frontend/KaizenGui.hpp @@ -7,28 +7,33 @@ #include #include #include +#include +#include class EmuThread; class KaizenGui final : QMainWindow { Q_OBJECT + void updateKeys(); + void updateAxis(); + public: explicit KaizenGui() noexcept; ~KaizenGui(); - void closeEvent(QCloseEvent *) override; + void cleanup(); void dropEvent(QDropEvent *) override; void dragEnterEvent(QDragEnterEvent *) override; + void keyPressEvent(QKeyEvent *) override; + void keyReleaseEvent(QKeyEvent *) override; bool fastForward = false; - bool unlockFramerate = false; bool minimized = false; SettingsWindow *settingsWindow; RenderWidget *vulkanWidget; QSettings settings; QThread *emuThread; - QThread *fileWorker; QTimer *statusBarTimer; QLabel *fpsLabel; QLabel *cpuTypeLabel; @@ -39,16 +44,18 @@ class KaizenGui final : QMainWindow { SDL_Gamepad *gamepad = nullptr; - static void LoadTAS(const std::string &path) noexcept; + void LoadTAS(const std::string &path) noexcept; void LoadROM(const std::string &path) noexcept; signals: void paused(); private: float elapsed = 0.f; - int width{}, height{}; - bool aboutOpen = false; - bool fileDialogOpen = false; - bool quit = false; n64::Core &core = n64::Core::GetInstance(); + std::bitset<18> pressedKeys{}; + std::array mapping = { + Qt::Key_Z, Qt::Key_X, Qt::Key_C, Qt::Key_A, Qt::Key_S, Qt::Key_Return, + Qt::Key_I, Qt::Key_K, Qt::Key_J, Qt::Key_L, Qt::Key_T, Qt::Key_G, + Qt::Key_F, Qt::Key_H, Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right, + }; }; diff --git a/src/frontend/Settings/CPUSettings.cpp b/src/frontend/Settings/CPUSettings.cpp index 5d71e1d..18bc4e1 100644 --- a/src/frontend/Settings/CPUSettings.cpp +++ b/src/frontend/Settings/CPUSettings.cpp @@ -21,12 +21,12 @@ CPUSettings::CPUSettings() : settings(QSettings::UserScope) { h = new QHBoxLayout(); types->addItems({"Interpreter", "Cached Interpreter"}); - if (Options::GetCpuType() == 0) { + if (Options::GetCpuType() == n64::PlainInterpreter) { idleSkip->hide(); } else { idleSkip->show(); } - types->setCurrentIndex(Options::GetCpuType()); + types->setCurrentIndex((int)Options::GetCpuType()); connect(types, &QComboBox::currentIndexChanged, this, [&] { int index = types->currentIndex(); @@ -35,8 +35,8 @@ CPUSettings::CPUSettings() : settings(QSettings::UserScope) { else idleSkip->show(); - Options::SetCpuType(index); - settings.setValue("cpu/type", index); + Options::SetCpuType((n64::CPUType)index); + settings.setValue("cpu/type", (n64::CPUType)index); settings.sync(); emit cpuTypeChanged(); }); diff --git a/src/frontend/Settings/GeneralSettings.cpp b/src/frontend/Settings/GeneralSettings.cpp index 920ddd5..046c9c3 100644 --- a/src/frontend/Settings/GeneralSettings.cpp +++ b/src/frontend/Settings/GeneralSettings.cpp @@ -13,8 +13,7 @@ GeneralSettings::GeneralSettings() : settings(QSettings::UserScope) { connect(folderSelectButton, &QPushButton::clicked, this, [&] { auto dir = QFileDialog::getExistingDirectory(this, tr("Open Directory"), QCoreApplication::applicationDirPath(), - QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks | - QFileDialog::DontUseNativeDialog); + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); selectedFolderLabel->setText(dir); Options::SetSavesPath(dir.toStdString()); settings.setValue("general/saves_path", dir); diff --git a/src/utils/Options.cpp b/src/utils/Options.cpp index e24fd36..555da60 100644 --- a/src/utils/Options.cpp +++ b/src/utils/Options.cpp @@ -4,7 +4,7 @@ Options::Options() { QSettings settings(QSettings::UserScope); volume = settings.value("audio/volume", 0.5f).toFloat(); - cpuType = settings.value("cpu/type", 0).toUInt(); + cpuType = settings.value("cpu/type", n64::PlainInterpreter).value(); idleSkip = settings.value("cpu/idle_skip", false).toBool(); savesPath = settings.value("general/saves_path", "saves").toString().toStdString(); } diff --git a/src/utils/Options.hpp b/src/utils/Options.hpp index d7615bb..274d432 100644 --- a/src/utils/Options.hpp +++ b/src/utils/Options.hpp @@ -2,6 +2,10 @@ #include #include +namespace n64 { +enum CPUType { PlainInterpreter, CachedInterpreter }; +} + struct Options { static Options &GetInstance() { static Options instance; @@ -12,16 +16,16 @@ struct Options { static bool GetIdleSkip() { return GetInstance().idleSkip; } static float GetVolume() { return GetInstance().volume; } - static uint8_t GetCpuType() { return GetInstance().cpuType; } + static n64::CPUType GetCpuType() { return GetInstance().cpuType; } static std::string GetSavesPath() { return GetInstance().savesPath; } static void SetIdleSkip(bool v) { GetInstance().idleSkip = v; } static void SetVolume(float v) { GetInstance().volume = v; } - static void SetCpuType(uint8_t v) { GetInstance().cpuType = v; } + static void SetCpuType(n64::CPUType v) { GetInstance().cpuType = v; } static void SetSavesPath(const std::string &v) { GetInstance().savesPath = v; } private: bool idleSkip = false; float volume = 0.5; std::string savesPath = "saves"; - uint8_t cpuType = 0; + n64::CPUType cpuType = n64::PlainInterpreter; };