first work on remappable inputs

This commit is contained in:
2026-05-08 14:27:37 +02:00
parent 609fa2fb08
commit ba39535acf
26 changed files with 1224 additions and 1038 deletions
+3 -2
View File
@@ -171,11 +171,12 @@ add_executable(kaizen
src/frontend/Settings/CPUSettings.cpp src/frontend/Settings/CPUSettings.cpp
src/frontend/Settings/AudioSettings.hpp src/frontend/Settings/AudioSettings.hpp
src/frontend/Settings/AudioSettings.cpp src/frontend/Settings/AudioSettings.cpp
src/frontend/NativeWindow.hpp src/frontend/Window.hpp
src/utils/Options.cpp src/utils/Options.cpp
src/utils/File.cpp src/utils/File.cpp
src/frontend/Debugger.hpp src/frontend/Debugger.hpp
src/frontend/Debugger.cpp) src/frontend/Debugger.cpp
src/frontend/Gamepad.hpp)
if (WIN32) if (WIN32)
+1
View File
@@ -13,6 +13,7 @@
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#pragma once #pragma once
#define IMGUI_IMPL_VULKAN_USE_VOLK
//---- Define assertion handler. Defaults to calling assert(). //---- Define assertion handler. Defaults to calling assert().
// - If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement. // - If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement.
+229 -184
View File
@@ -14,220 +14,265 @@ namespace n64 {
enum AccessoryType : u8 { ACCESSORY_NONE, ACCESSORY_MEMPACK, ACCESSORY_RUMBLE_PACK }; enum AccessoryType : u8 { ACCESSORY_NONE, ACCESSORY_MEMPACK, ACCESSORY_RUMBLE_PACK };
struct Controller { struct Controller {
union { union {
struct {
union {
u8 byte1;
struct { struct {
bool dpRight : 1; union {
bool dpLeft : 1; u8 byte1;
bool dpDown : 1; struct {
bool dpUp : 1; bool dpRight : 1;
bool start : 1; bool dpLeft : 1;
bool z : 1; bool dpDown : 1;
bool b : 1; bool dpUp : 1;
bool a : 1; bool start : 1;
}; bool z : 1;
}; bool b : 1;
union { bool a : 1;
u8 byte2; };
struct { };
bool cRight : 1; union {
bool cLeft : 1; u8 byte2;
bool cDown : 1; struct {
bool cUp : 1; bool cRight : 1;
bool r : 1; bool cLeft : 1;
bool l : 1; bool cDown : 1;
bool zero : 1; bool cUp : 1;
bool joyReset : 1; bool r : 1;
}; bool l : 1;
}; bool zero : 1;
bool joyReset : 1;
};
};
s8 joyX; s8 joyX;
s8 joyY; s8 joyY;
};
u32 raw;
}; };
Controller &operator=(const Controller &other) {
byte1 = other.byte1;
byte2 = other.byte2;
joyX = other.joyX;
joyY = other.joyY;
u32 raw; return *this;
};
Controller &operator=(const Controller &other) {
byte1 = other.byte1;
byte2 = other.byte2;
joyX = other.joyX;
joyY = other.joyY;
return *this;
}
enum Key { A, B, Z, Start, DUp, DDown, DLeft, DRight, CUp, CDown, CLeft, CRight, LT, RT };
enum Axis { X, Y };
Controller() = default;
void UpdateButton(Key k, bool state) {
switch (k) {
case A:
a = state;
break;
case B:
b = state;
break;
case Z:
z = state;
break;
case Start:
start = state;
break;
case DUp:
dpUp = state;
break;
case DDown:
dpDown = state;
break;
case DLeft:
dpLeft = state;
break;
case DRight:
dpRight = state;
break;
case CUp:
cUp = state;
break;
case CDown:
cDown = state;
break;
case CLeft:
cLeft = state;
break;
case CRight:
cRight = state;
break;
case LT:
l = state;
break;
case RT:
r = state;
break;
} }
}
void UpdateAxis(Axis a, s8 state) { enum Key { A, B, Z, Start, DUp, DDown, DLeft, DRight, CUp, CDown, CLeft, CRight, LT, RT };
switch (a) {
case X: enum Axis { X, Y };
joyX = state;
break; Controller() = default;
case Y: void UpdateButton(Key k, bool state) {
joyY = state; switch (k) {
break; case A:
a = state;
break;
case B:
b = state;
break;
case Z:
z = state;
break;
case Start:
start = state;
break;
case DUp:
dpUp = state;
break;
case DDown:
dpDown = state;
break;
case DLeft:
dpLeft = state;
break;
case DRight:
dpRight = state;
break;
case CUp:
cUp = state;
break;
case CDown:
cDown = state;
break;
case CLeft:
cLeft = state;
break;
case CRight:
cRight = state;
break;
case LT:
l = state;
break;
case RT:
r = state;
break;
}
} }
}
Controller &operator=(u32 v) { void UpdateAxis(Axis a, s8 state) {
joyY = v & 0xff; switch (a) {
joyX = v >> 8; case X:
byte2 = v >> 16; joyX = state;
byte1 = v >> 24; break;
case Y:
joyY = state;
break;
}
}
return *this; Controller &operator=(u32 v) {
} joyY = v & 0xff;
joyX = v >> 8;
byte2 = v >> 16;
byte1 = v >> 24;
return *this;
}
static const char *as_c_str(Axis a) {
switch (a) {
case X:
return "X-Axis";
case Y:
return "Y-Axis";
break;
}
}
static const char *as_c_str(Key k) {
switch (k) {
case A:
return "A";
case B:
return "B";
case Z:
return "Z";
case Start:
return "Start";
case DUp:
return "Up";
case DDown:
return "Down";
case DLeft:
return "Left";
case DRight:
return "Right";
case CUp:
return "C-Up";
case CDown:
return "C-Down";
case CLeft:
return "C-Left";
case CRight:
return "C-Right";
case LT:
return "Left Trigger";
case RT:
return "Right Trigger";
break;
}
}
}; };
static_assert(sizeof(Controller) == 4); static_assert(sizeof(Controller) == 4);
enum JoybusType : u8 { enum JoybusType : u8 {
JOYBUS_NONE, JOYBUS_NONE,
JOYBUS_CONTROLLER, JOYBUS_CONTROLLER,
JOYBUS_DANCEPAD, JOYBUS_DANCEPAD,
JOYBUS_VRU, JOYBUS_VRU,
JOYBUS_MOUSE, JOYBUS_MOUSE,
JOYBUS_RANDNET_KEYBOARD, JOYBUS_RANDNET_KEYBOARD,
JOYBUS_DENSHA_DE_GO, JOYBUS_DENSHA_DE_GO,
JOYBUS_4KB_EEPROM, JOYBUS_4KB_EEPROM,
JOYBUS_16KB_EEPROM JOYBUS_16KB_EEPROM
}; };
struct JoybusDevice { struct JoybusDevice {
JoybusType type{}; JoybusType type{};
AccessoryType accessoryType{}; AccessoryType accessoryType{};
Controller controller{}; Controller controller{};
JoybusDevice() = default; JoybusDevice() = default;
}; };
// https://github.com/ares-emulator/ares/blob/master/ares/n64/cic/cic.cpp // https://github.com/ares-emulator/ares/blob/master/ares/n64/cic/cic.cpp
// https://github.com/ares-emulator/ares/blob/master/LICENSE // https://github.com/ares-emulator/ares/blob/master/LICENSE
constexpr u32 cicSeeds[] = { constexpr u32 cicSeeds[] = {
0x0, 0x0,
0x00043F3F, // CIC_NUS_6101 0x00043F3F, // CIC_NUS_6101
0x00043F3F, // CIC_NUS_7102 0x00043F3F, // CIC_NUS_7102
0x00043F3F, // CIC_NUS_6102_7101 0x00043F3F, // CIC_NUS_6102_7101
0x00047878, // CIC_NUS_6103_7103 0x00047878, // CIC_NUS_6103_7103
0x00049191, // CIC_NUS_6105_7105 0x00049191, // CIC_NUS_6105_7105
0x00048585, // CIC_NUS_6106_7106 0x00048585, // CIC_NUS_6106_7106
}; };
enum CICType { enum CICType {
UNKNOWN_CIC_TYPE, UNKNOWN_CIC_TYPE,
CIC_NUS_6101, CIC_NUS_6101,
CIC_NUS_7102, CIC_NUS_7102,
CIC_NUS_6102_7101, CIC_NUS_6102_7101,
CIC_NUS_6103_7103, CIC_NUS_6103_7103,
CIC_NUS_6105_7105, CIC_NUS_6105_7105,
CIC_NUS_6106_7106 CIC_NUS_6106_7106
}; };
struct PIF { struct PIF {
void Reset(); void Reset();
void MaybeLoadMempak(); void MaybeLoadMempak();
void LoadEeprom(SaveType, const std::string &); void LoadEeprom(SaveType, const std::string &);
void ProcessCommands(); void ProcessCommands();
void InitDevices(SaveType); void InitDevices(SaveType);
void CICChallenge(); void CICChallenge();
void Execute() const; void Execute() const;
void HLE(bool pal, CICType cicType) const; void HLE(bool pal, CICType cicType) const;
bool ReadButtons(u8 *); bool ReadButtons(u8 *);
void ControllerID(u8 *) const; void ControllerID(u8 *) const;
void MempakRead(const u8 *, u8 *); void MempakRead(const u8 *, u8 *);
void MempakWrite(u8 *, u8 *); void MempakWrite(u8 *, u8 *);
void EepromRead(const u8 *, u8 *) const; void EepromRead(const u8 *, u8 *) const;
void EepromWrite(const u8 *, u8 *); void EepromWrite(const u8 *, u8 *);
void UpdateButton(int index, Controller::Key k, bool state) { void UpdateButton(int index, Controller::Key k, bool state) {
joybusDevices[index].controller.UpdateButton(k, state); joybusDevices[index].controller.UpdateButton(k, state);
}
void UpdateAxis(int index, Controller::Axis a, s8 state) { joybusDevices[index].controller.UpdateAxis(a, state); }
bool mempakOpen = false;
std::array<u8, PIF_BOOTROM_SIZE> bootrom{};
std::array<u8, PIF_RAM_SIZE> ram{};
int channel = 0;
std::array<JoybusDevice, 6> joybusDevices{};
mio::mmap_sink mempak, eeprom;
std::string mempakPath{}, eepromPath{};
size_t eepromSize{};
MupenMovie movie;
[[nodiscard]] FORCE_INLINE u8 Read(u32 addr) const {
addr &= 0x7FF;
if (addr < 0x7c0)
return bootrom[addr];
return ram[addr & PIF_RAM_DSIZE];
}
FORCE_INLINE void Write(u32 addr, const u8 val) {
addr &= 0x7FF;
if (addr < 0x7c0)
return;
ram[addr & PIF_RAM_DSIZE] = val;
}
[[nodiscard]] FORCE_INLINE AccessoryType GetAccessoryType() const {
if (channel >= 4 || joybusDevices[channel].type != JOYBUS_CONTROLLER) {
return ACCESSORY_NONE;
} }
return joybusDevices[channel].accessoryType; void UpdateAxis(int index, Controller::Axis a, s8 state) { joybusDevices[index].controller.UpdateAxis(a, state); }
}
private: bool mempakOpen = false;
void ConfigureJoyBusFrame(); std::array<u8, PIF_BOOTROM_SIZE> bootrom{};
std::array<u8, PIF_RAM_SIZE> ram{};
int channel = 0;
std::array<JoybusDevice, 6> joybusDevices{};
mio::mmap_sink mempak, eeprom;
std::string mempakPath{}, eepromPath{};
size_t eepromSize{};
MupenMovie movie;
[[nodiscard]] FORCE_INLINE u8 Read(u32 addr) const {
addr &= 0x7FF;
if (addr < 0x7c0)
return bootrom[addr];
return ram[addr & PIF_RAM_DSIZE];
}
FORCE_INLINE void Write(u32 addr, const u8 val) {
addr &= 0x7FF;
if (addr < 0x7c0)
return;
ram[addr & PIF_RAM_DSIZE] = val;
}
[[nodiscard]] FORCE_INLINE AccessoryType GetAccessoryType() const {
if (channel >= 4 || joybusDevices[channel].type != JOYBUS_CONTROLLER) {
return ACCESSORY_NONE;
}
return joybusDevices[channel].accessoryType;
}
private:
void ConfigureJoyBusFrame();
}; };
} // namespace n64 } // namespace n64
+34 -37
View File
@@ -5,49 +5,46 @@
EmuThread::EmuThread(double &fps, SettingsWindow &settings) noexcept : settings(settings), fps(fps) {} EmuThread::EmuThread(double &fps, SettingsWindow &settings) noexcept : settings(settings), fps(fps) {}
void EmuThread::run() const noexcept { void EmuThread::run() const noexcept {
n64::Core& core = n64::Core::GetInstance(); n64::Core &core = n64::Core::GetInstance();
if(!core.romLoaded) return; if (!core.romLoaded)
return;
auto lastSample = std::chrono::high_resolution_clock::now(); auto lastSample = std::chrono::high_resolution_clock::now();
auto avgFps = 16.667; auto avgFps = 16.667;
auto sampledFps = 0; auto sampledFps = 0;
static bool oneSecondPassed = false; static bool oneSecondPassed = false;
fps = 1000.0 / avgFps;
const auto startFrameTime = std::chrono::high_resolution_clock::now();
if (!core.pause) {
core.Run(settings.getVolumeL(), settings.getVolumeR());
}
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;
return;
}
avgFps /= sampledFps;
fps = 1000.0 / avgFps; fps = 1000.0 / avgFps;
}
const auto startFrameTime = std::chrono::high_resolution_clock::now();
if (!core.pause) {
core.Run(settings.getVolumeL(), settings.getVolumeR());
}
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;
return;
}
avgFps /= sampledFps;
fps = 1000.0 / avgFps;
}
} }
void EmuThread::TogglePause() const noexcept { void EmuThread::TogglePause() const noexcept { n64::Core::GetInstance().TogglePause(); }
n64::Core::GetInstance().TogglePause();
}
void EmuThread::Reset() const noexcept { void EmuThread::Reset() const noexcept { n64::Core::GetInstance().Reset(); }
n64::Core::GetInstance().Reset();
}
void EmuThread::Stop() const noexcept { void EmuThread::Stop() const noexcept {
n64::Core& core = n64::Core::GetInstance(); n64::Core &core = n64::Core::GetInstance();
core.Stop(); core.Stop();
core.rom = {}; core.rom = {};
} }
+12 -12
View File
@@ -1,23 +1,23 @@
#pragma once #pragma once
#include <RenderWidget.hpp> #include <RenderWidget.hpp>
#include <SettingsWindow.hpp> #include <SettingsWindow.hpp>
#include <memory>
namespace n64 { namespace n64 {
struct Core; struct Core;
} }
class EmuThread final { class EmuThread final {
bool started = false; bool started = false;
public:
explicit EmuThread(double &, SettingsWindow &) noexcept;
~EmuThread() = default;
void run() const noexcept;
void TogglePause() const noexcept;
void Reset() const noexcept;
void Stop() const noexcept;
bool interruptionRequested = false, parallelRDPInitialized = false; public:
SettingsWindow &settings; explicit EmuThread(double &, SettingsWindow &) noexcept;
double& fps; ~EmuThread() = default;
void run() const noexcept;
void TogglePause() const noexcept;
void Reset() const noexcept;
void Stop() const noexcept;
bool interruptionRequested = false, parallelRDPInitialized = false;
SettingsWindow &settings;
double &fps;
}; };
+80
View File
@@ -0,0 +1,80 @@
#pragma once
#include <variant>
#include <PIF.hpp>
#include <Options.hpp>
#include <SDL3/SDL_gamepad.h>
struct Gamepad {
private:
struct MapEntry {
bool isAxis;
int sdlVal;
std::variant<n64::Controller::Key, n64::Controller::Axis> emuVal;
const char *as_c_str() {
if (std::holds_alternative<n64::Controller::Key>(emuVal)) {
switch (std::get<n64::Controller::Key>(emuVal)) {
case n64::Controller::A:
return "A";
case n64::Controller::B:
return "B";
case n64::Controller::Z:
return "Z";
case n64::Controller::Start:
return "Start";
case n64::Controller::DUp:
return "DUp";
case n64::Controller::DDown:
return "DDown";
case n64::Controller::DLeft:
return "DLeft";
case n64::Controller::DRight:
return "DRight";
case n64::Controller::CUp:
return "CUp";
case n64::Controller::CDown:
return "CDown";
case n64::Controller::CLeft:
return "CLeft";
case n64::Controller::CRight:
return "CRight";
case n64::Controller::LT:
return "LT";
case n64::Controller::RT:
return "RT";
}
}
switch (std::get<n64::Controller::Axis>(emuVal)) {
case n64::Controller::X:
return "X";
case n64::Controller::Y:
return "Y";
}
}
} entries[14] = {
{false, SDL_GAMEPAD_BUTTON_SOUTH, n64::Controller::A},
{false, SDL_GAMEPAD_BUTTON_WEST, n64::Controller::B},
{false, SDL_GAMEPAD_BUTTON_START, n64::Controller::Start},
{false, SDL_GAMEPAD_BUTTON_DPAD_UP, n64::Controller::DUp},
{false, SDL_GAMEPAD_BUTTON_DPAD_DOWN, n64::Controller::DDown},
{false, SDL_GAMEPAD_BUTTON_DPAD_LEFT, n64::Controller::DLeft},
{false, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, n64::Controller::DRight},
{false, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, n64::Controller::LT},
{false, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, n64::Controller::RT},
{false, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, n64::Controller::Z},
{true, SDL_GAMEPAD_AXIS_RIGHTX, n64::Controller::X},
{true, SDL_GAMEPAD_AXIS_RIGHTY, n64::Controller::Y},
{true, SDL_GAMEPAD_AXIS_LEFTX, n64::Controller::X},
{true, SDL_GAMEPAD_AXIS_LEFTY, n64::Controller::Y},
};
public:
void SetValue(const MapEntry &);
void Serialize() {
auto &options = Options::GetInstance();
for(const auto& entry : entries) {
options.SetValue<int>(, const std::string &field, const T &value)
}
}
};
+115 -117
View File
@@ -1,10 +1,8 @@
#pragma once #pragma once
#define IMGUI_IMPL_VULKAN_NO_PROTOTYPES
#include <imgui.h> #include <imgui.h>
#include <imgui_impl_sdl3.h> #include <imgui_impl_sdl3.h>
#include <imgui_impl_vulkan.h> #include <imgui_impl_vulkan.h>
#include <utils/log.hpp> #include <utils/log.hpp>
#include <memory>
namespace gui { namespace gui {
static VkAllocationCallbacks *g_Allocator = NULL; static VkAllocationCallbacks *g_Allocator = NULL;
@@ -20,138 +18,138 @@ static ImGui_ImplVulkanH_Window g_MainWindowData;
static uint32_t g_MinImageCount = 2; static uint32_t g_MinImageCount = 2;
static void CheckVkResult(VkResult err) { static void CheckVkResult(VkResult err) {
if (err == VK_SUCCESS) if (err == VK_SUCCESS)
return; return;
if (err < VK_SUCCESS) if (err < VK_SUCCESS)
panic("[vulkan] VkResult = {}", (int)err); panic("[vulkan] VkResult = {}", (int)err);
warn("[vulkan] VkResult = {}", (int)err); warn("[vulkan] VkResult = {}", (int)err);
} }
inline void Initialize(const std::shared_ptr<Vulkan::WSI> &wsi, SDL_Window *nativeWindow) { inline void Initialize(const std::shared_ptr<Vulkan::WSI> &wsi, SDL_Window *nativeWindow) {
VkResult err; VkResult err;
// Setup Dear ImGui context // Setup Dear ImGui context
IMGUI_CHECKVERSION(); IMGUI_CHECKVERSION();
ImGui::CreateContext(); ImGui::CreateContext();
ImGuiIO &io = ImGui::GetIO(); ImGuiIO &io = ImGui::GetIO();
(void)io; (void)io;
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
// io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls // io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
// io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls // io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
// Setup Dear ImGui style // Setup Dear ImGui style
ImGui::StyleColorsDark(); ImGui::StyleColorsDark();
// ImGui::StyleColorsClassic(); // ImGui::StyleColorsClassic();
g_Instance = wsi->get_context().get_instance(); g_Instance = wsi->get_context().get_instance();
g_PhysicalDevice = wsi->get_device().get_physical_device(); g_PhysicalDevice = wsi->get_device().get_physical_device();
g_Device = wsi->get_device().get_device(); g_Device = wsi->get_device().get_device();
g_QueueFamily = wsi->get_context().get_queue_info().family_indices[Vulkan::QUEUE_INDEX_GRAPHICS]; g_QueueFamily = wsi->get_context().get_queue_info().family_indices[Vulkan::QUEUE_INDEX_GRAPHICS];
g_Queue = wsi->get_context().get_queue_info().queues[Vulkan::QUEUE_INDEX_GRAPHICS]; g_Queue = wsi->get_context().get_queue_info().queues[Vulkan::QUEUE_INDEX_GRAPHICS];
g_PipelineCache = nullptr; g_PipelineCache = nullptr;
g_DescriptorPool = nullptr; g_DescriptorPool = nullptr;
g_Allocator = nullptr; g_Allocator = nullptr;
g_MinImageCount = 2; g_MinImageCount = 2;
// Create Descriptor Pool // Create Descriptor Pool
{ {
VkDescriptorPoolSize pool_sizes[] = {{VK_DESCRIPTOR_TYPE_SAMPLER, 1000}, VkDescriptorPoolSize pool_sizes[] = {{VK_DESCRIPTOR_TYPE_SAMPLER, 1000},
{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000}, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000},
{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000}, {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000},
{VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000}, {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000},
{VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000}, {VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000},
{VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000}, {VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000},
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000}, {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000},
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000}, {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000},
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000}, {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000},
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000}, {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000},
{VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000}}; {VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000}};
VkDescriptorPoolCreateInfo pool_info = {}; VkDescriptorPoolCreateInfo pool_info = {};
pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
pool_info.maxSets = 1000 * IM_ARRAYSIZE(pool_sizes); pool_info.maxSets = 1000 * IM_ARRAYSIZE(pool_sizes);
pool_info.poolSizeCount = (uint32_t)IM_ARRAYSIZE(pool_sizes); pool_info.poolSizeCount = (uint32_t)IM_ARRAYSIZE(pool_sizes);
pool_info.pPoolSizes = pool_sizes; pool_info.pPoolSizes = pool_sizes;
err = vkCreateDescriptorPool(g_Device, &pool_info, g_Allocator, &g_DescriptorPool); err = vkCreateDescriptorPool(g_Device, &pool_info, g_Allocator, &g_DescriptorPool);
CheckVkResult(err); CheckVkResult(err);
} }
// Create the Render Pass // Create the Render Pass
VkRenderPass renderPass; VkRenderPass renderPass;
{ {
VkAttachmentDescription attachment = {}; VkAttachmentDescription attachment = {};
attachment.format = wsi->get_device().get_swapchain_view().get_format(); attachment.format = wsi->get_device().get_swapchain_view().get_format();
attachment.samples = VK_SAMPLE_COUNT_1_BIT; attachment.samples = VK_SAMPLE_COUNT_1_BIT;
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
VkAttachmentReference color_attachment = {}; VkAttachmentReference color_attachment = {};
color_attachment.attachment = 0; color_attachment.attachment = 0;
color_attachment.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; color_attachment.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpass = {}; VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1; subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &color_attachment; subpass.pColorAttachments = &color_attachment;
VkSubpassDependency dependency = {}; VkSubpassDependency dependency = {};
dependency.srcSubpass = VK_SUBPASS_EXTERNAL; dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
dependency.dstSubpass = 0; dependency.dstSubpass = 0;
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.srcAccessMask = 0; dependency.srcAccessMask = 0;
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
VkRenderPassCreateInfo info = {}; VkRenderPassCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
info.attachmentCount = 1; info.attachmentCount = 1;
info.pAttachments = &attachment; info.pAttachments = &attachment;
info.subpassCount = 1; info.subpassCount = 1;
info.pSubpasses = &subpass; info.pSubpasses = &subpass;
info.dependencyCount = 1; info.dependencyCount = 1;
info.pDependencies = &dependency; info.pDependencies = &dependency;
err = vkCreateRenderPass(g_Device, &info, g_Allocator, &renderPass); err = vkCreateRenderPass(g_Device, &info, g_Allocator, &renderPass);
CheckVkResult(err); CheckVkResult(err);
} }
// Setup Platform/Renderer backends // Setup Platform/Renderer backends
ImGui_ImplSDL3_InitForVulkan(nativeWindow); ImGui_ImplSDL3_InitForVulkan(nativeWindow);
ImGui_ImplVulkan_InitInfo init_info = {}; ImGui_ImplVulkan_InitInfo init_info = {};
init_info.Instance = g_Instance; init_info.Instance = g_Instance;
init_info.PhysicalDevice = g_PhysicalDevice; init_info.PhysicalDevice = g_PhysicalDevice;
init_info.Device = g_Device; init_info.Device = g_Device;
init_info.QueueFamily = g_QueueFamily; init_info.QueueFamily = g_QueueFamily;
init_info.Queue = g_Queue; init_info.Queue = g_Queue;
init_info.PipelineCache = g_PipelineCache; init_info.PipelineCache = g_PipelineCache;
init_info.DescriptorPool = g_DescriptorPool; init_info.DescriptorPool = g_DescriptorPool;
init_info.Allocator = g_Allocator; init_info.Allocator = g_Allocator;
init_info.MinImageCount = g_MinImageCount; init_info.MinImageCount = g_MinImageCount;
init_info.ImageCount = 2; init_info.ImageCount = 2;
init_info.CheckVkResultFn = CheckVkResult; init_info.CheckVkResultFn = CheckVkResult;
init_info.PipelineInfoMain.RenderPass = renderPass; init_info.PipelineInfoMain.RenderPass = renderPass;
init_info.ApiVersion = VK_API_VERSION_1_3; init_info.ApiVersion = VK_API_VERSION_1_3;
ImGui_ImplVulkan_LoadFunctions( ImGui_ImplVulkan_LoadFunctions(
VK_API_VERSION_1_3, VK_API_VERSION_1_3,
[](const char *function_name, void *vulkan_instance) { [](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);
if (!ImGui_ImplVulkan_Init(&init_info)) if (!ImGui_ImplVulkan_Init(&init_info))
panic("Failed to initialize ImGui!"); panic("Failed to initialize ImGui!");
} }
inline void StartFrame() { inline void StartFrame() {
ImGui_ImplVulkan_NewFrame(); ImGui_ImplVulkan_NewFrame();
ImGui_ImplSDL3_NewFrame(); ImGui_ImplSDL3_NewFrame();
ImGui::NewFrame(); ImGui::NewFrame();
} }
inline void Cleanup() { inline void Cleanup() {
ImGui_ImplVulkan_Shutdown(); ImGui_ImplVulkan_Shutdown();
ImGui_ImplSDL3_Shutdown(); ImGui_ImplSDL3_Shutdown();
ImGui::DestroyContext(); ImGui::DestroyContext();
} }
} // namespace gui } // namespace gui
+29 -28
View File
@@ -1,43 +1,44 @@
#pragma once #pragma once
#include <imgui.h> #include <imgui.h>
#include <imgui_internal.h> #include <imgui_internal.h>
#include <cstdlib>
namespace ImGui { namespace ImGui {
inline bool Spinner(const char* label, const float radius, const int thickness, const ImU32& color) { inline bool Spinner(const char *label, const float radius, const int thickness, const ImU32 &color) {
ImGuiWindow* window = GetCurrentWindow(); ImGuiWindow *window = GetCurrentWindow();
if (window->SkipItems) if (window->SkipItems)
return false; return false;
const ImGuiContext & g = *GImGui; const ImGuiContext &g = *GImGui;
const ImGuiStyle& style = g.Style; const ImGuiStyle &style = g.Style;
const ImGuiID id = window->GetID(label); const ImGuiID id = window->GetID(label);
const ImVec2 pos = window->DC.CursorPos; const ImVec2 pos = window->DC.CursorPos;
const ImVec2 size(radius*2, (radius + style.FramePadding.y)*2); const ImVec2 size(radius * 2, (radius + style.FramePadding.y) * 2);
const ImRect bb(pos, ImVec2(pos.x + size.x, pos.y + size.y)); const ImRect bb(pos, ImVec2(pos.x + size.x, pos.y + size.y));
ItemSize(bb, style.FramePadding.y); ItemSize(bb, style.FramePadding.y);
if (!ItemAdd(bb, id)) if (!ItemAdd(bb, id))
return false; return false;
// Render // Render
window->DrawList->PathClear(); window->DrawList->PathClear();
constexpr int num_segments = 30; constexpr int num_segments = 30;
const int start = abs(ImSin(g.Time*1.8f)*(num_segments-5)); const int start = std::abs(ImSin(g.Time * 1.8f) * (num_segments - 5));
const float a_min = IM_PI * 2.0f * static_cast<float>(start) / static_cast<float>(num_segments); const float a_min = IM_PI * 2.0f * static_cast<float>(start) / static_cast<float>(num_segments);
constexpr float a_max = IM_PI*2.0f * (static_cast<float>(num_segments) -3) / static_cast<float>(num_segments); constexpr float a_max = IM_PI * 2.0f * (static_cast<float>(num_segments) - 3) / static_cast<float>(num_segments);
const auto centre = ImVec2(pos.x+radius, pos.y+radius+style.FramePadding.y); const auto centre = ImVec2(pos.x + radius, pos.y + radius + style.FramePadding.y);
for (int i = 0; i < num_segments; i++) { for (int i = 0; i < num_segments; i++) {
const float a = a_min + static_cast<float>(i) / static_cast<float>(num_segments) * (a_max - a_min); const float a = a_min + static_cast<float>(i) / static_cast<float>(num_segments) * (a_max - a_min);
window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a+g.Time*8) * radius, window->DrawList->PathLineTo(
centre.y + ImSin(a+g.Time*8) * radius)); ImVec2(centre.x + ImCos(a + g.Time * 8) * radius, centre.y + ImSin(a + g.Time * 8) * radius));
} }
window->DrawList->PathStroke(color, false, thickness); window->DrawList->PathStroke(color, false, thickness);
return true; return true;
}
} }
} // namespace ImGui
+3 -3
View File
@@ -1,8 +1,8 @@
#pragma once #pragma once
struct SettingsTab { struct SettingsTab {
virtual ~SettingsTab() = default; virtual ~SettingsTab() = default;
virtual void render() = 0; virtual void render() = 0;
bool modified = false; bool modified = false;
}; };
+19 -14
View File
@@ -3,19 +3,22 @@
#include <imgui_internal.h> #include <imgui_internal.h>
namespace ImGui { namespace ImGui {
inline bool BeginMainStatusBar() inline bool BeginMainStatusBar() {
{ ImGuiContext &g = *GetCurrentContext();
ImGuiContext& g = *GetCurrentContext(); ImGuiViewportP *viewport = (ImGuiViewportP *)(void *)GetMainViewport();
ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport();
// Notify of viewport change so GetFrameHeight() can be accurate in case of DPI change // Notify of viewport change so GetFrameHeight() can be accurate in case of DPI change
SetCurrentViewport(NULL, viewport); 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. // 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: 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. // FIXME: Consider removing support for safe area down the line... it's messy. Nowadays consoles have support for TV
g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f)); // calibration in OS settings.
ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar; 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(); float height = GetFrameHeight();
bool is_open = BeginViewportSideBar("##MainStatusBar", viewport, ImGuiDir_Down, height, window_flags); bool is_open = BeginViewportSideBar("##MainStatusBar", viewport, ImGuiDir_Down, height, window_flags);
g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f); g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
@@ -27,16 +30,18 @@ inline bool BeginMainStatusBar()
return is_open; return is_open;
} }
inline void EndMainStatusBar() inline void EndMainStatusBar() {
{
EndMenuBar(); EndMenuBar();
// When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window // 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. // FIXME: With this strategy we won't be able to restore a NULL focus.
ImGuiContext& g = *GImGui; ImGuiContext &g = *GImGui;
if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest) if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest)
FocusTopMostWindowUnderOne(g.NavWindow, NULL, NULL, ImGuiFocusRequestFlags_UnlessBelowModal | ImGuiFocusRequestFlags_RestoreFocusedChild); FocusTopMostWindowUnderOne(g.NavWindow, NULL, NULL,
ImGuiFocusRequestFlags_UnlessBelowModal |
ImGuiFocusRequestFlags_RestoreFocusedChild);
End(); End();
} }
} } // namespace ImGui
+412 -383
View File
@@ -5,456 +5,485 @@
#include <ImGuiImpl/StatusBar.hpp> #include <ImGuiImpl/StatusBar.hpp>
#include <resources/gamecontrollerdb.h> #include <resources/gamecontrollerdb.h>
KaizenGui::KaizenGui() noexcept : window("Kaizen " KAIZEN_VERSION_STR, 1280, 720), settingsWindow(window), vulkanWidget(window.getHandle()), emuThread(fpsCounter, settingsWindow) { KaizenGui::KaizenGui() noexcept :
gui::Initialize(n64::Core::GetInstance().parallel.wsi, window.getHandle()); window("Kaizen " KAIZEN_VERSION_STR, 1280, 720), settingsWindow(window), vulkanWidget(window.getHandle()),
SDL_InitSubSystem(SDL_INIT_GAMEPAD); emuThread(fpsCounter, settingsWindow) {
gui::Initialize(n64::Core::GetInstance().parallel.wsi, window.getHandle());
SDL_InitSubSystem(SDL_INIT_GAMEPAD);
SDL_AddGamepadMapping(gamecontrollerdb_str); SDL_AddGamepadMapping(gamecontrollerdb_str);
} }
KaizenGui::~KaizenGui() { KaizenGui::~KaizenGui() {
gui::Cleanup(); gui::Cleanup();
SDL_Quit(); SDL_Quit();
} }
void KaizenGui::QueryDevices(const SDL_Event &event) { void KaizenGui::QueryDevices(const SDL_Event &event) {
switch (event.type) { switch (event.type) {
case SDL_EVENT_GAMEPAD_ADDED: case SDL_EVENT_GAMEPAD_ADDED:
if (!gamepad) { if (!gamepad) {
const auto index = event.gdevice.which; const auto index = event.gdevice.which;
gamepad = SDL_OpenGamepad(index); gamepad = SDL_OpenGamepad(index);
info("Found controller!"); info("Found controller!");
info("Name: {}", SDL_GetGamepadName(gamepad)); info("Name: {}", SDL_GetGamepadName(gamepad));
info("Vendor: {}", SDL_GetGamepadVendor(gamepad)); info("Vendor: {}", SDL_GetGamepadVendor(gamepad));
}
break;
case SDL_EVENT_GAMEPAD_REMOVED:
if (gamepad)
SDL_CloseGamepad(gamepad);
break;
default:
break;
} }
break;
case SDL_EVENT_GAMEPAD_REMOVED:
if (gamepad)
SDL_CloseGamepad(gamepad);
break;
default: break;
}
} }
void KaizenGui::HandleInput(const SDL_Event &event) { void KaizenGui::HandleInput(const SDL_Event &event) {
const n64::Core& core = n64::Core::GetInstance(); const n64::Core &core = n64::Core::GetInstance();
n64::PIF &pif = n64::Core::GetMem().mmio.si.pif; n64::PIF &pif = n64::Core::GetMem().mmio.si.pif;
switch(event.type) { switch (event.type) {
case SDL_EVENT_GAMEPAD_AXIS_MOTION: case SDL_EVENT_GAMEPAD_AXIS_MOTION:
if(!gamepad) if (!gamepad)
break;
{
pif.UpdateButton(0, n64::Controller::Key::Z,
SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER) == SDL_JOYSTICK_AXIS_MAX ||
SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) == SDL_JOYSTICK_AXIS_MAX);
pif.UpdateButton(0, n64::Controller::Key::CUp,
SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY) <= -127);
pif.UpdateButton(0, n64::Controller::Key::CDown,
SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY) >= 127);
pif.UpdateButton(0, n64::Controller::Key::CLeft,
SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX) <= -127);
pif.UpdateButton(0, n64::Controller::Key::CRight,
SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX) >= 127);
float xclamped = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX);
if (xclamped < 0) {
xclamped /= static_cast<float>(std::abs(SDL_JOYSTICK_AXIS_MAX));
} else {
xclamped /= SDL_JOYSTICK_AXIS_MAX;
}
xclamped *= 86;
float yclamped = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY);
if (yclamped < 0) {
yclamped /= static_cast<float>(std::abs(SDL_JOYSTICK_AXIS_MIN));
} else {
yclamped /= SDL_JOYSTICK_AXIS_MAX;
}
yclamped *= 86;
pif.UpdateAxis(0, n64::Controller::Axis::Y, static_cast<s8>(-yclamped));
pif.UpdateAxis(0, n64::Controller::Axis::X, static_cast<s8>(xclamped));
}
break; break;
{
pif.UpdateButton(0, n64::Controller::Key::Z, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER) == SDL_JOYSTICK_AXIS_MAX);
pif.UpdateButton(0, n64::Controller::Key::CUp, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY) <= -127);
pif.UpdateButton(0, n64::Controller::Key::CDown, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY) >= 127);
pif.UpdateButton(0, n64::Controller::Key::CLeft, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX) <= -127);
pif.UpdateButton(0, n64::Controller::Key::CRight, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX) >= 127);
float xclamped = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX);
if (xclamped < 0) {
xclamped /= static_cast<float>(std::abs(SDL_JOYSTICK_AXIS_MAX));
} else {
xclamped /= SDL_JOYSTICK_AXIS_MAX;
}
xclamped *= 86;
float yclamped = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY);
if (yclamped < 0) {
yclamped /= static_cast<float>(std::abs(SDL_JOYSTICK_AXIS_MIN));
} else {
yclamped /= SDL_JOYSTICK_AXIS_MAX;
}
yclamped *= 86;
pif.UpdateAxis(0, n64::Controller::Axis::Y, static_cast<s8>(-yclamped));
pif.UpdateAxis(0, n64::Controller::Axis::X, static_cast<s8>( xclamped));
}
break;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN: case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP: case SDL_EVENT_GAMEPAD_BUTTON_UP:
if(!gamepad) if (!gamepad)
break; break;
pif.UpdateButton(0, n64::Controller::Key::A, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH)); pif.UpdateButton(0, n64::Controller::Key::A, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH));
pif.UpdateButton(0, n64::Controller::Key::B, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_WEST)); pif.UpdateButton(0, n64::Controller::Key::B, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_WEST));
pif.UpdateButton(0, n64::Controller::Key::Start, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_START)); pif.UpdateButton(0, n64::Controller::Key::Start, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_START));
pif.UpdateButton(0, n64::Controller::Key::DUp, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP)); pif.UpdateButton(0, n64::Controller::Key::DUp, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP));
pif.UpdateButton(0, n64::Controller::Key::DDown, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN)); pif.UpdateButton(0, n64::Controller::Key::DDown, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN));
pif.UpdateButton(0, n64::Controller::Key::DLeft, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_LEFT)); pif.UpdateButton(0, n64::Controller::Key::DLeft, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_LEFT));
pif.UpdateButton(0, n64::Controller::Key::DRight, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT)); pif.UpdateButton(0, n64::Controller::Key::DRight, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT));
pif.UpdateButton(0, n64::Controller::Key::LT, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)); pif.UpdateButton(0, n64::Controller::Key::LT, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER));
pif.UpdateButton(0, n64::Controller::Key::RT, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)); pif.UpdateButton(0, n64::Controller::Key::RT, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER));
break; break;
case SDL_EVENT_KEY_DOWN: case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP: case SDL_EVENT_KEY_UP:
{ {
const auto keys = SDL_GetKeyboardState(nullptr); const auto keys = SDL_GetKeyboardState(nullptr);
if((keys[SDL_SCANCODE_LCTRL] || keys[SDL_SCANCODE_RCTRL]) && keys[SDL_SCANCODE_O]) { if ((keys[SDL_SCANCODE_LCTRL] || keys[SDL_SCANCODE_RCTRL]) && keys[SDL_SCANCODE_O]) {
fileDialogOpen = true; fileDialogOpen = true;
}
fastForward = keys[SDL_SCANCODE_SPACE];
if (!unlockFramerate)
core.parallel.SetFramerateUnlocked(fastForward);
if (core.romLoaded) {
if (keys[SDL_SCANCODE_P]) {
emuThread.TogglePause();
}
if (keys[SDL_SCANCODE_R]) {
emuThread.Reset();
}
if (keys[SDL_SCANCODE_Q]) {
emuThread.Stop();
}
}
if (gamepad)
break;
pif.UpdateButton(0, n64::Controller::Key::Z, keys[SDL_SCANCODE_Z]);
pif.UpdateButton(0, n64::Controller::Key::CUp, keys[SDL_SCANCODE_HOME]);
pif.UpdateButton(0, n64::Controller::Key::CDown, keys[SDL_SCANCODE_END]);
pif.UpdateButton(0, n64::Controller::Key::CLeft, keys[SDL_SCANCODE_DELETE]);
pif.UpdateButton(0, n64::Controller::Key::CRight, keys[SDL_SCANCODE_PAGEDOWN]);
pif.UpdateButton(0, n64::Controller::Key::A, keys[SDL_SCANCODE_X]);
pif.UpdateButton(0, n64::Controller::Key::B, keys[SDL_SCANCODE_C]);
pif.UpdateButton(0, n64::Controller::Key::Start, keys[SDL_SCANCODE_RETURN]);
pif.UpdateButton(0, n64::Controller::Key::DUp, keys[SDL_SCANCODE_I]);
pif.UpdateButton(0, n64::Controller::Key::DDown, keys[SDL_SCANCODE_K]);
pif.UpdateButton(0, n64::Controller::Key::DLeft, keys[SDL_SCANCODE_J]);
pif.UpdateButton(0, n64::Controller::Key::DRight, keys[SDL_SCANCODE_L]);
pif.UpdateButton(0, n64::Controller::Key::LT, keys[SDL_SCANCODE_A]);
pif.UpdateButton(0, n64::Controller::Key::RT, keys[SDL_SCANCODE_S]);
float x = 0, y = 0;
if (keys[SDL_SCANCODE_UP])
y = 86;
if (keys[SDL_SCANCODE_DOWN])
y = -86;
if (keys[SDL_SCANCODE_LEFT])
x = -86;
if (keys[SDL_SCANCODE_RIGHT])
x = 86;
pif.UpdateAxis(0, n64::Controller::Axis::X, x);
pif.UpdateAxis(0, n64::Controller::Axis::Y, y);
} }
break;
fastForward = keys[SDL_SCANCODE_SPACE]; default:
if(!unlockFramerate) break;
core.parallel.SetFramerateUnlocked(fastForward); }
if(core.romLoaded) {
if(keys[SDL_SCANCODE_P]) {
emuThread.TogglePause();
}
if(keys[SDL_SCANCODE_R]) {
emuThread.Reset();
}
if(keys[SDL_SCANCODE_Q]) {
emuThread.Stop();
}
}
if(gamepad)
break;
pif.UpdateButton(0, n64::Controller::Key::Z, keys[SDL_SCANCODE_Z]);
pif.UpdateButton(0, n64::Controller::Key::CUp, keys[SDL_SCANCODE_HOME]);
pif.UpdateButton(0, n64::Controller::Key::CDown, keys[SDL_SCANCODE_END]);
pif.UpdateButton(0, n64::Controller::Key::CLeft, keys[SDL_SCANCODE_DELETE]);
pif.UpdateButton(0, n64::Controller::Key::CRight, keys[SDL_SCANCODE_PAGEDOWN]);
pif.UpdateButton(0, n64::Controller::Key::A, keys[SDL_SCANCODE_X]);
pif.UpdateButton(0, n64::Controller::Key::B, keys[SDL_SCANCODE_C]);
pif.UpdateButton(0, n64::Controller::Key::Start, keys[SDL_SCANCODE_RETURN]);
pif.UpdateButton(0, n64::Controller::Key::DUp, keys[SDL_SCANCODE_I]);
pif.UpdateButton(0, n64::Controller::Key::DDown, keys[SDL_SCANCODE_K]);
pif.UpdateButton(0, n64::Controller::Key::DLeft, keys[SDL_SCANCODE_J]);
pif.UpdateButton(0, n64::Controller::Key::DRight, keys[SDL_SCANCODE_L]);
pif.UpdateButton(0, n64::Controller::Key::LT, keys[SDL_SCANCODE_A]);
pif.UpdateButton(0, n64::Controller::Key::RT, keys[SDL_SCANCODE_S]);
float x = 0, y = 0;
if (keys[SDL_SCANCODE_UP]) y = 86;
if (keys[SDL_SCANCODE_DOWN]) y = -86;
if (keys[SDL_SCANCODE_LEFT]) x = -86;
if (keys[SDL_SCANCODE_RIGHT]) x = 86;
pif.UpdateAxis(0, n64::Controller::Axis::X, x);
pif.UpdateAxis(0, n64::Controller::Axis::Y, y);
}
break;
default: break;
}
} }
std::pair<std::optional<s64>, std::optional<Util::Error::MemoryAccess>> RenderErrorMessageDetails() { std::pair<std::optional<s64>, std::optional<Util::Error::MemoryAccess>> RenderErrorMessageDetails() {
auto lastPC = Util::Error::GetLastPC(); auto lastPC = Util::Error::GetLastPC();
if(lastPC.has_value()) { if (lastPC.has_value()) {
ImGui::Text("%s", std::format("Occurred @ PC = {:016X}", Util::Error::GetLastPC().value()).c_str()); ImGui::Text("%s", std::format("Occurred @ PC = {:016X}", Util::Error::GetLastPC().value()).c_str());
} }
auto memoryAccess = Util::Error::GetMemoryAccess(); auto memoryAccess = Util::Error::GetMemoryAccess();
if(memoryAccess.has_value()) { if (memoryAccess.has_value()) {
const auto [is_write, size, address, written_val] = memoryAccess.value(); const auto [is_write, size, address, written_val] = memoryAccess.value();
ImGui::Text("%s", std::format("{} {}-bit value @ {:08X}{}", is_write ? "Writing" : "Reading", ImGui::Text("%s",
static_cast<u8>(size), address, std::format("{} {}-bit value @ {:08X}{}", is_write ? "Writing" : "Reading", static_cast<u8>(size),
is_write ? std::format(" (value = 0x{:X})", written_val) : "") address, is_write ? std::format(" (value = 0x{:X})", written_val) : "")
.c_str()); .c_str());
} }
return {lastPC, memoryAccess}; return {lastPC, memoryAccess};
} }
void KaizenGui::RenderUI() { void KaizenGui::RenderUI() {
n64::Core& core = n64::Core::GetInstance(); n64::Core &core = n64::Core::GetInstance();
gui::StartFrame(); gui::StartFrame();
if(ImGui::BeginMainMenuBar()) { if (ImGui::BeginMainMenuBar()) {
if(ImGui::BeginMenu("File")) { if (ImGui::BeginMenu("File")) {
if(ImGui::MenuItem("Open", "Ctrl-O")) { if (ImGui::MenuItem("Open", "Ctrl-O")) {
fileDialogOpen = true; fileDialogOpen = true;
} }
if(ImGui::MenuItem("Exit")) { if (ImGui::MenuItem("Exit")) {
quit = true; quit = true;
emuThread.Stop(); emuThread.Stop();
} }
ImGui::EndMenu(); ImGui::EndMenu();
} }
if(ImGui::BeginMenu("Emulation")) { if (ImGui::BeginMenu("Emulation")) {
ImGui::BeginDisabled(!core.romLoaded); ImGui::BeginDisabled(!core.romLoaded);
if(ImGui::MenuItem(core.pause ? "Resume" : "Pause", "P")) { if (ImGui::MenuItem(core.pause ? "Resume" : "Pause", "P")) {
emuThread.TogglePause(); emuThread.TogglePause();
} }
if(ImGui::MenuItem("Reset", "R")) { if (ImGui::MenuItem("Reset", "R")) {
emuThread.Reset(); emuThread.Reset();
} }
if(ImGui::MenuItem("Stop", "Q")) { if (ImGui::MenuItem("Stop", "Q")) {
emuThread.Stop(); emuThread.Stop();
core.romLoaded = false; core.romLoaded = false;
} }
if(ImGui::Checkbox("Unlock framerate", &unlockFramerate)) { if (ImGui::Checkbox("Unlock framerate", &unlockFramerate)) {
core.parallel.SetFramerateUnlocked(unlockFramerate); core.parallel.SetFramerateUnlocked(unlockFramerate);
} }
if(ImGui::MenuItem("Open Debugger")) { if (ImGui::MenuItem("Open Debugger")) {
debugger.Open(); debugger.Open();
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
if(ImGui::MenuItem("Options")) { if (ImGui::MenuItem("Options")) {
settingsWindow.isOpen = true; settingsWindow.isOpen = true;
} }
ImGui::EndMenu(); ImGui::EndMenu();
} }
if(ImGui::BeginMenu("Help")) { if (ImGui::BeginMenu("Help")) {
if(ImGui::MenuItem("About")) { if (ImGui::MenuItem("About")) {
aboutOpen = true; aboutOpen = true;
} }
ImGui::EndMenu(); ImGui::EndMenu();
} }
ImGui::EndMainMenuBar(); ImGui::EndMainMenuBar();
}
if(!Util::Error::IsHandled()) {
ImGui::OpenPopup(Util::Error::GetSeverity().as_c_str());
}
if(settingsWindow.isOpen) {
ImGui::OpenPopup("Settings", ImGuiPopupFlags_None);
}
if(aboutOpen) {
ImGui::OpenPopup("About Kaizen");
}
settingsWindow.render();
debugger.render();
const ImVec2 center = ImGui::GetMainViewport()->GetCenter();
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
if (ImGui::BeginPopupModal("About Kaizen", &aboutOpen, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Kaizen is a Nintendo 64 emulator that strives");
ImGui::Text("to offer a friendly user experience and compatibility.");
ImGui::Text("Kaizen is licensed under the BSD 3-clause license.");
ImGui::Text("Nintendo 64 is a registered trademark of Nintendo Co., Ltd.");
ImGui::Separator();
ImGui::Text("Kaizen %s%s", KAIZEN_USE_HASH ? "dev build " : "", KAIZEN_VERSION_STR);
ImGui::Separator();
if(ImGui::Button("OK")) {
aboutOpen = false;
ImGui::CloseCurrentPopup();
} }
ImGui::EndPopup(); if (!Util::Error::IsHandled()) {
} ImGui::OpenPopup(Util::Error::GetSeverity().as_c_str());
}
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); if (settingsWindow.isOpen) {
ImGui::OpenPopup("Settings", ImGuiPopupFlags_None);
}
if (ImGui::BeginPopupModal(Util::Error::GetSeverity().as_c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { if (aboutOpen) {
emuThread.TogglePause(); ImGui::OpenPopup("About Kaizen");
switch(Util::Error::GetSeverity().as_enum) { }
case Util::Error::Severity::WARN: {
ImGui::PushStyleColor(ImGuiCol_TitleBg, 0x8054eae5);
ImGui::PushStyleColor(ImGuiCol_Text, 0xff7be4e1);
ImGui::Text("Warning of type: %s", Util::Error::GetType().as_c_str());
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::Text(R"(Warning message: "%s")", Util::Error::GetError().c_str());
RenderErrorMessageDetails();
if(n64::Core::GetInstance().romLoaded && !n64::Core::GetInstance().pause) { settingsWindow.render();
const bool ignore = ImGui::Button("Try continuing"); ImGui::SameLine(); debugger.render();
const bool stop = ImGui::Button("Stop emulation"); ImGui::SameLine();
const bool chooseAnother = ImGui::Button("Choose another ROM"); const ImVec2 center = ImGui::GetMainViewport()->GetCenter();
if(ignore || stop || chooseAnother) { ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
Util::Error::SetHandled();
if (ImGui::BeginPopupModal("About Kaizen", &aboutOpen, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Kaizen is a Nintendo 64 emulator that strives");
ImGui::Text("to offer a friendly user experience and compatibility.");
ImGui::Text("Kaizen is licensed under the BSD 3-clause license.");
ImGui::Text("Nintendo 64 is a registered trademark of Nintendo Co., Ltd.");
ImGui::Separator();
ImGui::Text("Kaizen %s%s", KAIZEN_USE_HASH ? "dev build " : "", KAIZEN_VERSION_STR);
ImGui::Separator();
if (ImGui::Button("OK")) {
aboutOpen = false;
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
}
if(ignore) {
emuThread.TogglePause();
}
if(stop || chooseAnother) {
emuThread.Stop();
}
if(chooseAnother) {
fileDialogOpen = true;
}
break;
} }
if(ImGui::Button("OK")) ImGui::EndPopup();
ImGui::CloseCurrentPopup();
} break;
case Util::Error::Severity::UNRECOVERABLE: {
emuThread.Stop();
ImGui::PushStyleColor(ImGuiCol_TitleBg, 0x800000ff);
ImGui::PushStyleColor(ImGuiCol_Text, 0xff3b3bbf);
ImGui::Text("An unrecoverable error has occurred! Emulation has been stopped...");
ImGui::Text("Error of type: %s", Util::Error::GetType().as_c_str());
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::Text(R"(Error message: "%s")", Util::Error::GetError().c_str());
RenderErrorMessageDetails();
if(ImGui::Button("OK"))
ImGui::CloseCurrentPopup();
} break;
case Util::Error::Severity::NON_FATAL: {
ImGui::PushStyleColor(ImGuiCol_TitleBg, 0x800000ff);
ImGui::PushStyleColor(ImGuiCol_Text, 0xff3b3bbf);
ImGui::Text("An error has occurred!");
ImGui::Text("Error of type: %s", Util::Error::GetType().as_c_str());
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::Text(R"(Error message: "%s")", Util::Error::GetError().c_str());
auto [lastPC, memoryAccess] = RenderErrorMessageDetails();
const bool ignore = ImGui::Button("Try continuing"); ImGui::SameLine();
const bool stop = ImGui::Button("Stop emulation"); ImGui::SameLine();
const bool chooseAnother = ImGui::Button("Choose another ROM");
const bool openInDebugger = lastPC.has_value() ? ImGui::Button("Add breakpoint at this PC and open the debugger") : false;
if(ignore || stop || chooseAnother || openInDebugger) {
Util::Error::SetHandled();
ImGui::CloseCurrentPopup();
}
if(ignore) {
emuThread.TogglePause();
}
if(stop || chooseAnother) {
emuThread.Stop();
}
if(chooseAnother) {
fileDialogOpen = true;
}
if(openInDebugger) {
if(!n64::Core::GetInstance().breakpoints.contains(lastPC.value()))
n64::Core::GetInstance().ToggleBreakpoint(lastPC.value());
debugger.Open();
emuThread.Reset();
}
} break;
default: break;
} }
ImGui::EndPopup(); ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
}
if(ImGui::BeginMainStatusBar()) { if (ImGui::BeginPopupModal(Util::Error::GetSeverity().as_c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("FPS: %.2f", ImGui::GetIO().Framerate); emuThread.TogglePause();
ImGui::EndMainStatusBar(); switch (Util::Error::GetSeverity().as_enum) {
} case Util::Error::Severity::WARN:
{
ImGui::PushStyleColor(ImGuiCol_TitleBg, 0x8054eae5);
ImGui::PushStyleColor(ImGuiCol_Text, 0xff7be4e1);
ImGui::Text("Warning of type: %s", Util::Error::GetType().as_c_str());
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::Text(R"(Warning message: "%s")", Util::Error::GetError().c_str());
RenderErrorMessageDetails();
if (shouldDisplaySpinner) { if (n64::Core::GetInstance().romLoaded && !n64::Core::GetInstance().pause) {
ImGui::SetNextWindowPos({static_cast<float>(width) * 0.5f, static_cast<float>(height) * 0.5f}, 0, ImVec2(0.5f, 0.5f)); const bool ignore = ImGui::Button("Try continuing");
ImGui::PushStyleColor(ImGuiCol_WindowBg, IM_COL32_BLACK_TRANS); ImGui::SameLine();
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); const bool stop = ImGui::Button("Stop emulation");
ImGui::SameLine();
const bool chooseAnother = ImGui::Button("Choose another ROM");
if (ignore || stop || chooseAnother) {
Util::Error::SetHandled();
ImGui::CloseCurrentPopup();
}
ImGui::Begin("##spinnerContainer", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration); if (ignore) {
emuThread.TogglePause();
}
ImGui::Spinner("##spinner", 10.f, 4.f, ImGui::GetColorU32(ImGui::GetStyle().Colors[ImGuiCol_TitleBgActive])); if (stop || chooseAnother) {
ImGui::SameLine(); emuThread.Stop();
}
ImGui::PushFont(nullptr, ImGui::GetStyle().FontSizeBase * 2.f); if (chooseAnother) {
ImGui::Text("Loading \"%s\"...", fs::path(fileToLoad).filename().string().c_str()); fileDialogOpen = true;
ImGui::PopFont(); }
break;
}
ImGui::End(); if (ImGui::Button("OK"))
ImGui::CloseCurrentPopup();
}
break;
case Util::Error::Severity::UNRECOVERABLE:
{
emuThread.Stop();
ImGui::PushStyleColor(ImGuiCol_TitleBg, 0x800000ff);
ImGui::PushStyleColor(ImGuiCol_Text, 0xff3b3bbf);
ImGui::Text("An unrecoverable error has occurred! Emulation has been stopped...");
ImGui::Text("Error of type: %s", Util::Error::GetType().as_c_str());
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::Text(R"(Error message: "%s")", Util::Error::GetError().c_str());
RenderErrorMessageDetails();
if (ImGui::Button("OK"))
ImGui::CloseCurrentPopup();
}
break;
case Util::Error::Severity::NON_FATAL:
{
ImGui::PushStyleColor(ImGuiCol_TitleBg, 0x800000ff);
ImGui::PushStyleColor(ImGuiCol_Text, 0xff3b3bbf);
ImGui::Text("An error has occurred!");
ImGui::Text("Error of type: %s", Util::Error::GetType().as_c_str());
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::Text(R"(Error message: "%s")", Util::Error::GetError().c_str());
auto [lastPC, memoryAccess] = RenderErrorMessageDetails();
ImGui::PopStyleVar(); const bool ignore = ImGui::Button("Try continuing");
ImGui::PopStyleColor(); ImGui::SameLine();
} const bool stop = ImGui::Button("Stop emulation");
ImGui::SameLine();
const bool chooseAnother = ImGui::Button("Choose another ROM");
const bool openInDebugger =
lastPC.has_value() ? ImGui::Button("Add breakpoint at this PC and open the debugger") : false;
if (ignore || stop || chooseAnother || openInDebugger) {
Util::Error::SetHandled();
ImGui::CloseCurrentPopup();
}
ImGui::Render(); if (ignore) {
if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { emuThread.TogglePause();
ImGui::UpdatePlatformWindows(); }
ImGui::RenderPlatformWindowsDefault();
}
if(fileDialogOpen) { if (stop || chooseAnother) {
fileDialogOpen = false; emuThread.Stop();
constexpr SDL_DialogFileFilter filters[] = {{"All files", "*"}, {"Nintendo 64 executable", "n64;z64;v64"}, {"Nintendo 64 executable archive", "rar;tar;zip;7z"}}; }
SDL_ShowOpenFileDialog([](void *userdata, const char * const *filelist, int) {
auto kaizen = static_cast<KaizenGui*>(userdata);
if (!filelist) { if (chooseAnother) {
panic("An error occured: {}", SDL_GetError()); fileDialogOpen = true;
} }
if (!*filelist) { if (openInDebugger) {
warn("The user did not select any file."); if (!n64::Core::GetInstance().breakpoints.contains(lastPC.value()))
warn("Most likely, the dialog was canceled."); n64::Core::GetInstance().ToggleBreakpoint(lastPC.value());
debugger.Open();
emuThread.Reset();
}
}
break;
default:
break;
}
ImGui::EndPopup();
}
if (ImGui::BeginMainStatusBar()) {
ImGui::Text("FPS: %.2f", ImGui::GetIO().Framerate);
ImGui::EndMainStatusBar();
}
if (shouldDisplaySpinner) {
ImGui::SetNextWindowPos({static_cast<float>(width) * 0.5f, static_cast<float>(height) * 0.5f}, 0,
ImVec2(0.5f, 0.5f));
ImGui::PushStyleColor(ImGuiCol_WindowBg, IM_COL32_BLACK_TRANS);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::Begin("##spinnerContainer", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration);
ImGui::Spinner("##spinner", 10.f, 4.f, ImGui::GetColorU32(ImGui::GetStyle().Colors[ImGuiCol_TitleBgActive]));
ImGui::SameLine();
ImGui::PushFont(nullptr, ImGui::GetStyle().FontSizeBase * 2.f);
ImGui::Text("Loading \"%s\"...", fs::path(fileToLoad).filename().string().c_str());
ImGui::PopFont();
ImGui::End();
ImGui::PopStyleVar();
ImGui::PopStyleColor();
}
ImGui::Render();
if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
}
if (fileDialogOpen) {
fileDialogOpen = false;
constexpr SDL_DialogFileFilter filters[] = {{"All files", "*"},
{"Nintendo 64 executable", "n64;z64;v64"},
{"Nintendo 64 executable archive", "rar;tar;zip;7z"}};
SDL_ShowOpenFileDialog(
[](void *userdata, const char *const *filelist, int) {
auto kaizen = static_cast<KaizenGui *>(userdata);
if (!filelist) {
panic("An error occured: {}", SDL_GetError());
}
if (!*filelist) {
warn("The user did not select any file.");
warn("Most likely, the dialog was canceled.");
return;
}
kaizen->fileToLoad = *filelist;
kaizen->shouldDisplaySpinner = true;
std::thread fileWorker(&KaizenGui::FileWorker, kaizen);
fileWorker.detach();
},
this, window.getHandle(), filters, 3, nullptr, false);
}
if (minimized)
return; return;
}
kaizen->fileToLoad = *filelist; if (core.romLoaded) {
kaizen->shouldDisplaySpinner = true; core.parallel.UpdateScreen<true>();
return;
}
std::thread fileWorker(&KaizenGui::FileWorker, kaizen); core.parallel.UpdateScreen<false>();
fileWorker.detach();
}, this, window.getHandle(), filters, 3, nullptr, false);
}
if(minimized)
return;
if(core.romLoaded) {
core.parallel.UpdateScreen<true>();
return;
}
core.parallel.UpdateScreen<false>();
} }
void KaizenGui::LoadROM(const std::string &path) noexcept { void KaizenGui::LoadROM(const std::string &path) noexcept {
n64::Core& core = n64::Core::GetInstance(); n64::Core &core = n64::Core::GetInstance();
core.LoadROM(path); core.LoadROM(path);
const auto gameNameDB = n64::Core::GetMem().rom.gameNameDB; const auto gameNameDB = n64::Core::GetMem().rom.gameNameDB;
SDL_SetWindowTitle(window.getHandle(), ("Kaizen " KAIZEN_VERSION_STR " - " + gameNameDB).c_str()); SDL_SetWindowTitle(window.getHandle(), ("Kaizen " KAIZEN_VERSION_STR " - " + gameNameDB).c_str());
} }
void KaizenGui::run() { void KaizenGui::run() {
while(!quit) { while (!quit) {
SDL_Event e; SDL_Event e;
while (SDL_PollEvent(&e)) { while (SDL_PollEvent(&e)) {
ImGui_ImplSDL3_ProcessEvent(&e); ImGui_ImplSDL3_ProcessEvent(&e);
switch(e.type) { switch (e.type) {
case SDL_EVENT_QUIT: case SDL_EVENT_QUIT:
quit = true; quit = true;
emuThread.Stop(); emuThread.Stop();
break; break;
case SDL_EVENT_WINDOW_MINIMIZED: case SDL_EVENT_WINDOW_MINIMIZED:
minimized = true; minimized = true;
break; break;
case SDL_EVENT_WINDOW_RESTORED: case SDL_EVENT_WINDOW_RESTORED:
minimized = false; minimized = false;
break; break;
default: default:
} }
QueryDevices(e); QueryDevices(e);
HandleInput(e); HandleInput(e);
}
SDL_GetWindowSize(window.getHandle(), &width, &height);
emuThread.run();
RenderUI();
} }
SDL_GetWindowSize(window.getHandle(), &width, &height);
emuThread.run();
RenderUI();
}
} }
void KaizenGui::LoadTAS(const std::string &path) noexcept { void KaizenGui::LoadTAS(const std::string &path) noexcept { n64::Core::GetInstance().LoadTAS(fs::path(path)); }
n64::Core::GetInstance().LoadTAS(fs::path(path));
}
+38 -35
View File
@@ -1,49 +1,52 @@
#pragma once #pragma once
#include <RenderWidget.hpp> #include <RenderWidget.hpp>
#include <NativeWindow.hpp> #include <Window.hpp>
#include <Debugger.hpp> #include <Debugger.hpp>
#include <EmuThread.hpp> #include <EmuThread.hpp>
#include <SDL3/SDL_gamepad.h> #include <SDL3/SDL_gamepad.h>
#include <Gamepad.hpp>
class KaizenGui final { class KaizenGui final {
gui::NativeWindow window; gui::Window window;
public:
explicit KaizenGui() noexcept;
~KaizenGui();
double fpsCounter = -1.0; public:
bool fastForward = false; explicit KaizenGui() noexcept;
bool unlockFramerate = false; ~KaizenGui();
bool minimized = false;
SettingsWindow settingsWindow; double fpsCounter = -1.0;
RenderWidget vulkanWidget; bool fastForward = false;
EmuThread emuThread; bool unlockFramerate = false;
Debugger debugger; bool minimized = false;
SDL_Gamepad* gamepad = nullptr; SettingsWindow settingsWindow;
RenderWidget vulkanWidget;
EmuThread emuThread;
Debugger debugger;
void run(); SDL_Gamepad *gamepad = nullptr;
static void LoadTAS(const std::string &path) noexcept;
void LoadROM(const std::string &path) noexcept;
private:
int width{}, height{};
bool aboutOpen = false;
bool fileDialogOpen = false;
bool quit = false;
bool shouldDisplaySpinner = false;
std::string fileToLoad = "";
void RenderUI();
void HandleInput(const SDL_Event &event);
void QueryDevices(const SDL_Event &event);
[[noreturn]] void FileWorker() { void run();
while (true) { static void LoadTAS(const std::string &path) noexcept;
if (!fileToLoad.empty()) { void LoadROM(const std::string &path) noexcept;
LoadROM(fileToLoad);
shouldDisplaySpinner = false; private:
fileToLoad = ""; int width{}, height{};
} bool aboutOpen = false;
bool fileDialogOpen = false;
bool quit = false;
bool shouldDisplaySpinner = false;
std::string fileToLoad = "";
void RenderUI();
void HandleInput(const SDL_Event &event);
void QueryDevices(const SDL_Event &event);
[[noreturn]] void FileWorker() {
while (true) {
if (!fileToLoad.empty()) {
LoadROM(fileToLoad);
shouldDisplaySpinner = false;
fileToLoad = "";
}
}
} }
}
}; };
-28
View File
@@ -1,28 +0,0 @@
#pragma once
#include <SDL3/SDL.h>
#include <string>
#include <memory>
#include <volk.h>
#include <utils/log.hpp>
namespace gui {
struct NativeWindow {
NativeWindow(const std::string& title, int w, int h, int posX = SDL_WINDOWPOS_CENTERED, int posY = SDL_WINDOWPOS_CENTERED) {
SDL_Init(SDL_INIT_VIDEO);
float scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
window = SDL_CreateWindow(title.c_str(), w * scale, h * scale, SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);
if(volkInitialize() != VK_SUCCESS) {
panic("Failed to initialize Volk!");
}
}
~NativeWindow() {
SDL_DestroyWindow(window);
}
SDL_Window* getHandle() { return window; }
private:
SDL_Window* window;
};
}
+5 -5
View File
@@ -4,9 +4,9 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <imgui_impl_sdl3.h> #include <imgui_impl_sdl3.h>
RenderWidget::RenderWidget(SDL_Window* window) { RenderWidget::RenderWidget(SDL_Window *window) {
wsiPlatform = std::make_shared<SDLWSIPlatform>(window); wsiPlatform = std::make_shared<SDLWSIPlatform>(window);
windowInfo = std::make_shared<SDLParallelRdpWindowInfo>(window); windowInfo = std::make_shared<SDLParallelRdpWindowInfo>(window);
n64::Core& core = n64::Core::GetInstance(); n64::Core &core = n64::Core::GetInstance();
core.parallel.Init(wsiPlatform, windowInfo, core.GetMem().GetRDRAMPtr()); core.parallel.Init(wsiPlatform, windowInfo, core.GetMem().GetRDRAMPtr());
} }
+45 -44
View File
@@ -10,70 +10,71 @@ struct Core;
} }
class SDLParallelRdpWindowInfo final : public ParallelRDP::WindowInfo { class SDLParallelRdpWindowInfo final : public ParallelRDP::WindowInfo {
public: public:
explicit SDLParallelRdpWindowInfo(SDL_Window* window) : window(window) {} explicit SDLParallelRdpWindowInfo(SDL_Window *window) : window(window) {}
CoordinatePair get_window_size() override { CoordinatePair get_window_size() override {
int w,h; int w, h;
SDL_GetWindowSizeInPixels(window, &w, &h); SDL_GetWindowSizeInPixels(window, &w, &h);
return CoordinatePair{static_cast<float>(w), static_cast<float>(h)}; return CoordinatePair{static_cast<float>(w), static_cast<float>(h)};
} }
private: private:
SDL_Window* window{}; SDL_Window *window{};
}; };
class SDLWSIPlatform final : public Vulkan::WSIPlatform { class SDLWSIPlatform final : public Vulkan::WSIPlatform {
public: public:
explicit SDLWSIPlatform(SDL_Window* window) : window(window) {} explicit SDLWSIPlatform(SDL_Window *window) : window(window) {}
~SDLWSIPlatform() = default; ~SDLWSIPlatform() = default;
std::vector<const char *> get_instance_extensions() override { std::vector<const char *> get_instance_extensions() override {
auto vec = std::vector<const char *>(); auto vec = std::vector<const char *>();
u32 extCount; u32 extCount;
const auto &extensions = SDL_Vulkan_GetInstanceExtensions(&extCount); const auto &extensions = SDL_Vulkan_GetInstanceExtensions(&extCount);
vec.resize(extCount); vec.resize(extCount);
for (u32 i = 0; i < extCount; i++) { for (u32 i = 0; i < extCount; i++) {
vec[i] = extensions[i]; vec[i] = extensions[i];
}
return vec;
} }
return vec; VkSurfaceKHR create_surface(VkInstance instance, VkPhysicalDevice pDevice) override {
} SDL_Vulkan_CreateSurface(window, instance, nullptr, &surface);
return surface;
}
VkSurfaceKHR create_surface(VkInstance instance, VkPhysicalDevice pDevice) override { void destroy_surface(VkInstance instance, VkSurfaceKHR surface) override {
SDL_Vulkan_CreateSurface(window, instance, nullptr, &surface); SDL_Vulkan_DestroySurface(instance, surface, nullptr);
return surface; }
}
void destroy_surface(VkInstance instance, VkSurfaceKHR surface) override { uint32_t get_surface_width() override { return 640; }
SDL_Vulkan_DestroySurface(instance, surface, nullptr);
}
uint32_t get_surface_width() override { return 640; } uint32_t get_surface_height() override { return 480; }
uint32_t get_surface_height() override { return 480; } bool alive(Vulkan::WSI &) override { return true; }
bool alive(Vulkan::WSI &) override { return true; } void poll_input() override {}
void poll_input_async(Granite::InputTrackerHandler *handler) override {}
void poll_input() override {} void event_frame_tick(double frame, double elapsed) override {}
void poll_input_async(Granite::InputTrackerHandler *handler) override {}
void event_frame_tick(double frame, double elapsed) override {} 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{};
VkSurfaceKHR surface;
SDL_Window* window{}; private:
VkSurfaceKHR surface; bool gamepadConnected = false;
private:
bool gamepadConnected = false;
}; };
class RenderWidget final { class RenderWidget final {
public: public:
explicit RenderWidget(SDL_Window*); explicit RenderWidget(SDL_Window *);
std::shared_ptr<ParallelRDP::WindowInfo> windowInfo; std::shared_ptr<ParallelRDP::WindowInfo> windowInfo;
std::shared_ptr<SDLWSIPlatform> wsiPlatform; std::shared_ptr<SDLWSIPlatform> wsiPlatform;
}; };
+24 -24
View File
@@ -3,36 +3,36 @@
#include <Options.hpp> #include <Options.hpp>
AudioSettings::AudioSettings() { AudioSettings::AudioSettings() {
lockChannels = Options::GetInstance().GetValue<bool>("audio", "lock"); lockChannels = Options::GetInstance().GetValue<bool>("audio", "lock");
volumeL = Options::GetInstance().GetValue<float>("audio", "volumeL") * 100; volumeL = Options::GetInstance().GetValue<float>("audio", "volumeL") * 100;
volumeR = Options::GetInstance().GetValue<float>("audio", "volumeR") * 100; volumeR = Options::GetInstance().GetValue<float>("audio", "volumeR") * 100;
} }
void AudioSettings::render() { void AudioSettings::render() {
if(ImGui::Checkbox("Lock channels:", &lockChannels)) { if (ImGui::Checkbox("Lock channels:", &lockChannels)) {
Options::GetInstance().SetValue("audio", "lock", lockChannels); Options::GetInstance().SetValue("audio", "lock", lockChannels);
if(lockChannels) { if (lockChannels) {
volumeR = volumeL; volumeR = volumeL;
Options::GetInstance().SetValue("audio", "volumeR", volumeR / 100.f); Options::GetInstance().SetValue("audio", "volumeR", volumeR / 100.f);
}
modified = true;
} }
modified = true; if (ImGui::SliderFloat("Volume L", &volumeL, 0.f, 100.f, "%.2f")) {
} Options::GetInstance().SetValue("audio", "volumeL", volumeL / 100.f);
if (lockChannels) {
volumeR = volumeL;
Options::GetInstance().SetValue("audio", "volumeR", volumeR / 100.f);
}
if(ImGui::SliderFloat("Volume L", &volumeL, 0.f, 100.f, "%.2f")) { modified = true;
Options::GetInstance().SetValue("audio", "volumeL", volumeL / 100.f);
if (lockChannels) {
volumeR = volumeL;
Options::GetInstance().SetValue("audio", "volumeR", volumeR / 100.f);
} }
modified = true; ImGui::BeginDisabled(lockChannels);
} if (ImGui::SliderFloat("Volume R", &volumeR, 0.f, 100.f, "%.2f")) {
Options::GetInstance().SetValue("audio", "volumeR", volumeR / 100.f);
ImGui::BeginDisabled(lockChannels); modified = true;
if(ImGui::SliderFloat("Volume R", &volumeR, 0.f, 100.f, "%.2f")) { }
Options::GetInstance().SetValue("audio", "volumeR", volumeR / 100.f); ImGui::EndDisabled();
modified = true;
}
ImGui::EndDisabled();
} }
+5 -5
View File
@@ -2,10 +2,10 @@
#include <SettingsTab.hpp> #include <SettingsTab.hpp>
struct AudioSettings final : SettingsTab { struct AudioSettings final : SettingsTab {
bool lockChannels = false; bool lockChannels = false;
float volumeL{}; float volumeL{};
float volumeR{}; float volumeR{};
explicit AudioSettings(); explicit AudioSettings();
void render() override; void render() override;
}; };
+3 -3
View File
@@ -2,7 +2,7 @@
#include <SettingsTab.hpp> #include <SettingsTab.hpp>
struct CPUSettings final : SettingsTab { struct CPUSettings final : SettingsTab {
int selectedCpuTypeIndex = 0; int selectedCpuTypeIndex = 0;
void render() override; void render() override;
explicit CPUSettings(); explicit CPUSettings();
}; };
+25 -23
View File
@@ -3,33 +3,35 @@
#include <imgui.h> #include <imgui.h>
#include <log.hpp> #include <log.hpp>
GeneralSettings::GeneralSettings(gui::NativeWindow& window) : window(window) { GeneralSettings::GeneralSettings(gui::Window &window) : window(window) {
savesPath = Options::GetInstance().GetValue<std::string>("general", "savePath"); savesPath = Options::GetInstance().GetValue<std::string>("general", "savePath");
} }
void GeneralSettings::render() { void GeneralSettings::render() {
if(ImGui::Button("Pick...")) { if (ImGui::Button("Pick...")) {
SDL_ShowOpenFolderDialog([](void *userdata, const char * const *filelist, int _) { SDL_ShowOpenFolderDialog(
auto* general = static_cast<GeneralSettings*>(userdata); [](void *userdata, const char *const *filelist, int _) {
auto *general = static_cast<GeneralSettings *>(userdata);
if (!filelist) { if (!filelist) {
panic("An error occurred: {}", SDL_GetError()); panic("An error occurred: {}", SDL_GetError());
} }
if (!*filelist) { if (!*filelist) {
warn("The user did not select any file."); warn("The user did not select any file.");
warn("Most likely, the dialog was canceled."); warn("Most likely, the dialog was canceled.");
general->modified = false; general->modified = false;
return; return;
} }
general->savesPath = fs::absolute(*filelist).string(); general->savesPath = fs::absolute(*filelist).string();
Options::GetInstance().SetValue<std::string>("general", "savePath", general->savesPath); Options::GetInstance().SetValue<std::string>("general", "savePath", general->savesPath);
general->modified = true; general->modified = true;
}, this, window.getHandle(), nullptr, false); },
} this, window.getHandle(), nullptr, false);
ImGui::SameLine(); }
ImGui::BeginDisabled(); ImGui::SameLine();
ImGui::InputText("Save Path", const_cast<char*>(savesPath.c_str()), savesPath.length()); ImGui::BeginDisabled();
ImGui::EndDisabled(); ImGui::InputText("Save Path", const_cast<char *>(savesPath.c_str()), savesPath.length());
ImGui::EndDisabled();
} }
+5 -4
View File
@@ -1,11 +1,12 @@
#pragma once #pragma once
#include <SettingsTab.hpp> #include <SettingsTab.hpp>
#include <NativeWindow.hpp> #include <Window.hpp>
struct GeneralSettings final : SettingsTab { struct GeneralSettings final : SettingsTab {
void render() override; void render() override;
explicit GeneralSettings(gui::NativeWindow&); explicit GeneralSettings(gui::Window &);
private:
gui::NativeWindow& window; private:
gui::Window &window;
std::string savesPath; std::string savesPath;
}; };
+29 -29
View File
@@ -4,45 +4,45 @@
#include <ranges> #include <ranges>
bool SettingsWindow::render() { bool SettingsWindow::render() {
const ImVec2 center = ImGui::GetMainViewport()->GetCenter(); const ImVec2 center = ImGui::GetMainViewport()->GetCenter();
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
if(!ImGui::BeginPopupModal("Settings", &isOpen, ImGuiWindowFlags_AlwaysAutoResize)) if (!ImGui::BeginPopupModal("Settings", &isOpen, ImGuiWindowFlags_AlwaysAutoResize))
return false; return false;
if(!ImGui::BeginTabBar("SettingsTabBar")) if (!ImGui::BeginTabBar("SettingsTabBar"))
return false; return false;
for (auto& [name, tab] : tabs) { for (auto &[name, tab] : tabs) {
if (ImGui::BeginTabItem(name.c_str())) { if (ImGui::BeginTabItem(name.c_str())) {
tab->render(); tab->render();
if (tab->modified && !applyEnabled) if (tab->modified && !applyEnabled)
applyEnabled = true; applyEnabled = true;
ImGui::EndTabItem(); ImGui::EndTabItem();
}
} }
}
ImGui::EndTabBar(); ImGui::EndTabBar();
ImGui::BeginDisabled(!applyEnabled); ImGui::BeginDisabled(!applyEnabled);
if(ImGui::Button("Apply")) { if (ImGui::Button("Apply")) {
applyEnabled = false; applyEnabled = false;
Options::GetInstance().Apply(); Options::GetInstance().Apply();
for (const auto &tab : tabs | std::views::values) { for (const auto &tab : tabs | std::views::values) {
tab->modified = false; tab->modified = false;
}
} }
} ImGui::EndDisabled();
ImGui::EndDisabled();
ImGui::SameLine(); ImGui::SameLine();
if(ImGui::Button("Cancel")) { if (ImGui::Button("Cancel")) {
isOpen = false; isOpen = false;
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
} }
ImGui::EndPopup(); ImGui::EndPopup();
return true; return true;
} }
+17 -17
View File
@@ -2,25 +2,25 @@
#include <AudioSettings.hpp> #include <AudioSettings.hpp>
#include <CPUSettings.hpp> #include <CPUSettings.hpp>
#include <GeneralSettings.hpp> #include <GeneralSettings.hpp>
#include <NativeWindow.hpp> #include <Window.hpp>
#include <vector> #include <vector>
class SettingsWindow final { class SettingsWindow final {
gui::NativeWindow& window; GeneralSettings generalSettings;
GeneralSettings generalSettings; CPUSettings cpuSettings;
CPUSettings cpuSettings; AudioSettings audioSettings;
AudioSettings audioSettings; bool applyEnabled = false;
bool applyEnabled = false;
std::vector<std::pair<std::string, SettingsTab*>> tabs = { std::vector<std::pair<std::string, SettingsTab *>> tabs = {
{ "General", &generalSettings }, {"General", &generalSettings},
{ "CPU", &cpuSettings }, {"CPU", &cpuSettings},
{ "Audio", &audioSettings }, {"Audio", &audioSettings},
}; };
public:
bool isOpen = false; public:
bool render(); bool isOpen = false;
explicit SettingsWindow(gui::NativeWindow& window) : window(window), generalSettings(window) {} bool render();
[[nodiscard]] float getVolumeL() const { return audioSettings.volumeL / 100.f; } explicit SettingsWindow(gui::Window &window) : generalSettings(window) {}
[[nodiscard]] float getVolumeR() const { return audioSettings.volumeR / 100.f; } [[nodiscard]] float getVolumeL() const { return audioSettings.volumeL / 100.f; }
[[nodiscard]] float getVolumeR() const { return audioSettings.volumeR / 100.f; }
}; };
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <SDL3/SDL.h>
#include <string>
#include <utils/log.hpp>
namespace gui {
struct Window {
Window(const std::string &title, int w, int h, int posX = SDL_WINDOWPOS_CENTERED,
int posY = SDL_WINDOWPOS_CENTERED) {
SDL_Init(SDL_INIT_VIDEO);
float scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
window = SDL_CreateWindow(title.c_str(), w * scale, h * scale,
SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);
}
~Window() { SDL_DestroyWindow(window); }
SDL_Window *getHandle() { return window; }
private:
SDL_Window *window;
};
} // namespace gui
+11 -9
View File
@@ -2,16 +2,18 @@
#include <cflags.hpp> #include <cflags.hpp>
int main(const int argc, char **argv) { int main(const int argc, char **argv) {
KaizenGui kaizenGui; KaizenGui kaizenGui;
cflags::cflags flags; cflags::cflags flags;
flags.add_string_callback('\0', "rom", [&kaizenGui](const std::string& v) { kaizenGui.LoadROM(v); }, "Rom to launch from command-line"); flags.add_string_callback(
flags.add_string_callback('\0', "movie", [](const std::string& v) { KaizenGui::LoadTAS(v); }, "Mupen Movie to replay"); '\0', "rom", [&kaizenGui](const std::string &v) { kaizenGui.LoadROM(v); }, "Rom to launch from command-line");
flags.add_string_callback(
'\0', "movie", [](const std::string &v) { KaizenGui::LoadTAS(v); }, "Mupen Movie to replay");
if(!flags.parse(argc, argv)) { if (!flags.parse(argc, argv)) {
return -1; return -1;
} }
kaizenGui.run(); kaizenGui.run();
return 0; return 0;
} }
+13 -3
View File
@@ -10,22 +10,32 @@ void Options::SetValue<float>(const std::string &key, const std::string &field,
structure[key][field] = std::format("{:.2f}", value); structure[key][field] = std::format("{:.2f}", value);
} }
template <>
void Options::SetValue<int>(const std::string &key, const std::string &field, const int &value) {
structure[key][field] = std::format("{}", value);
}
template <> template <>
void Options::SetValue<bool>(const std::string &key, const std::string &field, const bool &value) { void Options::SetValue<bool>(const std::string &key, const std::string &field, const bool &value) {
structure[key][field] = value ? "true" : "false"; structure[key][field] = value ? "true" : "false";
} }
template<> template <>
std::string Options::GetValue<std::string>(const std::string &key, const std::string &field) { std::string Options::GetValue<std::string>(const std::string &key, const std::string &field) {
return structure[key][field]; return structure[key][field];
} }
template<> template <>
float Options::GetValue<float>(const std::string &key, const std::string &field) { float Options::GetValue<float>(const std::string &key, const std::string &field) {
return std::stof(structure[key][field]); return std::stof(structure[key][field]);
} }
template<> template <>
int Options::GetValue<int>(const std::string &key, const std::string &field) {
return std::stoi(structure[key][field]);
}
template <>
bool Options::GetValue<bool>(const std::string &key, const std::string &field) { bool Options::GetValue<bool>(const std::string &key, const std::string &field) {
return structure[key][field] == "true" ? true : false; return structure[key][field] == "true" ? true : false;
} }
+23 -8
View File
@@ -10,19 +10,33 @@ namespace fs = std::filesystem;
struct Options { struct Options {
Options() : file{"resources/options.ini"} { Options() : file{"resources/options.ini"} {
auto fileExists = fs::exists("resources/options.ini"); auto fileExists = fs::exists("resources/options.ini");
if(fileExists) { if (fileExists) {
file.read(structure); file.read(structure);
return; return;
} }
structure["general"]["savePath"] = "saves"; if (!fs::exists("saves"))
fs::create_directory("saves"); fs::create_directory("saves");
structure["cpu"]["type"] = "interpreter";
structure["audio"]["lock"] = "true";
structure["audio"]["volumeL"] = "0.5"; structure["audio"]["volumeL"] = "0.5";
structure["audio"]["volumeR"] = "0.5"; structure["audio"]["volumeR"] = "0.5";
structure["audio"]["lock"] = "true"; structure["general"]["savePath"] = "saves";
structure["cpu"]["type"] = "interpreter"; structure["gamepad"]["A"] = "SDL_GAMEPAD_BUTTON_SOUTH";
structure["gamepad"]["B"] = "SDL_GAMEPAD_BUTTON_WEST";
structure["gamepad"]["Start"] = "SDL_GAMEPAD_BUTTON_START";
structure["gamepad"]["DUp"] = "SDL_GAMEPAD_BUTTON_DPAD_UP";
structure["gamepad"]["DDown"] = "SDL_GAMEPAD_BUTTON_DPAD_DOWN";
structure["gamepad"]["DLeft"] = "SDL_GAMEPAD_BUTTON_DPAD_LEFT";
structure["gamepad"]["DRight"] = "SDL_GAMEPAD_BUTTON_DPAD_RIGHT";
structure["gamepad"]["LT"] = "SDL_GAMEPAD_BUTTON_LEFT_SHOULDER";
structure["gamepad"]["RT"] = "SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER";
structure["gamepad"]["Z"] = "SDL_GAMEPAD_AXIS_LEFT_TRIGGER";
structure["gamepad"]["X"] = "SDL_GAMEPAD_AXIS_RIGHTX";
structure["gamepad"]["Y"] = "SDL_GAMEPAD_AXIS_RIGHTY";
if(!file.generate(structure)) if (!file.generate(structure))
panic("Couldn't generate settings' INI!"); panic("Couldn't generate settings' INI!");
} }
@@ -37,10 +51,11 @@ struct Options {
T GetValue(const std::string &key, const std::string &field); T GetValue(const std::string &key, const std::string &field);
void Apply() { void Apply() {
if(!file.write(structure)) if (!file.write(structure))
panic("Could not modify options on disk!"); panic("Could not modify options on disk!");
} }
private:
private:
mINI::INIFile file; mINI::INIFile file;
mINI::INIStructure structure; mINI::INIStructure structure;
}; };