first work on remappable inputs
This commit is contained in:
+3
-2
@@ -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)
|
||||||
|
|||||||
Vendored
+1
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
@@ -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
@@ -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
@@ -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 = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -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());
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
};
|
};
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -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; }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user