input
This commit is contained in:
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user