This commit is contained in:
2026-06-10 10:53:49 +02:00
parent e72abc2407
commit ac4af8106c
11 changed files with 139 additions and 70 deletions
+14
View File
@@ -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
+1 -7
View File
@@ -194,8 +194,7 @@ void ParallelRDP::UpdateScreen(Util::IntrusivePtr<Image> image) const {
wsi->end_frame();
}
template <>
void ParallelRDP::UpdateScreen<true>() 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<true>() const {
command_processor->begin_frame_context();
}
template <>
void ParallelRDP::UpdateScreen<false>() const {
UpdateScreen(static_cast<Util::IntrusivePtr<Image>>(nullptr));
}
void ParallelRDP::EnqueueCommand(const int command_length, const u32 *buffer) const {
command_processor->enqueue_command(command_length, buffer);
}
-1
View File
@@ -18,7 +18,6 @@ class ParallelRDP {
void Init(const std::shared_ptr<Vulkan::WSIPlatform> &, const std::shared_ptr<WindowInfo> &,
Vulkan::InstanceFactory *, const u8 *);
template <bool>
void UpdateScreen() const;
void EnqueueCommand(int, const u32 *) const;
void OnFullSync() const;
+1 -10
View File
@@ -4,16 +4,7 @@
#include <Options.hpp>
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;
+3 -3
View File
@@ -4,12 +4,10 @@
#include <string>
#include <set>
#include <Registers.hpp>
#include <Options.hpp>
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<s64> breakpoints{};
std::unique_ptr<Mem> mem = std::make_unique<Mem>();
+92 -31
View File
@@ -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<false>();
if (!core.romLoaded || core.pause)
continue;
}
if (!core.pause) {
auto timeStart = SDL_GetTicks();
core.Run();
core.parallel.UpdateScreen<true>();
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);
}
}
+15 -8
View File
@@ -7,28 +7,33 @@
#include <QDropEvent>
#include <QDragEnterEvent>
#include <Core.hpp>
#include <bitset>
#include <tuple>
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<Qt::Key, 18> 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,
};
};
+4 -4
View File
@@ -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();
});
+1 -2
View File
@@ -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);
+1 -1
View File
@@ -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<n64::CPUType>();
idleSkip = settings.value("cpu/idle_skip", false).toBool();
savesPath = settings.value("general/saves_path", "saves").toString().toStdString();
}
+7 -3
View File
@@ -2,6 +2,10 @@
#include <common.hpp>
#include <string>
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;
};