Window shows up!
This commit is contained in:
@@ -6,6 +6,7 @@ saves/
|
|||||||
.vscode/
|
.vscode/
|
||||||
.zed/
|
.zed/
|
||||||
out/
|
out/
|
||||||
|
.qtcreator/
|
||||||
*.toml
|
*.toml
|
||||||
*.ini
|
*.ini
|
||||||
*.z64
|
*.z64
|
||||||
|
|||||||
+1
-7
@@ -49,7 +49,6 @@ else()
|
|||||||
message(FATAL_ERROR "Git not found, please define KAIZEN_GIT_COMMIT_HASH manually.")
|
message(FATAL_ERROR "Git not found, please define KAIZEN_GIT_COMMIT_HASH manually.")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
configure_file(${CMAKE_CURRENT_LIST_DIR}/cmake/version.hpp.in ${CMAKE_CURRENT_LIST_DIR}/resources/version.hpp)
|
configure_file(${CMAKE_CURRENT_LIST_DIR}/cmake/version.hpp.in ${CMAKE_CURRENT_LIST_DIR}/resources/version.hpp)
|
||||||
|
|
||||||
include_directories(
|
include_directories(
|
||||||
@@ -152,7 +151,7 @@ set(CAPSTONE_MIPS_SUPPORT ON)
|
|||||||
set(CAPSTONE_X86_SUPPORT ON)
|
set(CAPSTONE_X86_SUPPORT ON)
|
||||||
add_subdirectory(external/capstone)
|
add_subdirectory(external/capstone)
|
||||||
|
|
||||||
qt_add_executable(kaizen
|
qt_add_executable(kaizen
|
||||||
src/frontend/main.cpp
|
src/frontend/main.cpp
|
||||||
src/frontend/KaizenGui.hpp
|
src/frontend/KaizenGui.hpp
|
||||||
src/frontend/KaizenGui.cpp
|
src/frontend/KaizenGui.cpp
|
||||||
@@ -171,7 +170,6 @@ qt_add_executable(kaizen
|
|||||||
src/utils/Options.cpp
|
src/utils/Options.cpp
|
||||||
src/utils/File.cpp)
|
src/utils/File.cpp)
|
||||||
|
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
set(MIO_LIB mio::mio_full_winapi)
|
set(MIO_LIB mio::mio_full_winapi)
|
||||||
else()
|
else()
|
||||||
@@ -179,10 +177,6 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_link_libraries(kaizen PUBLIC Qt6::Core Qt6::Gui Qt6::Widgets SDL3::SDL3 SDL3::SDL3-static cflags::cflags ${MIO_LIB} parallel-rdp capstone backend)
|
target_link_libraries(kaizen PUBLIC Qt6::Core Qt6::Gui Qt6::Widgets SDL3::SDL3 SDL3::SDL3-static cflags::cflags ${MIO_LIB} parallel-rdp capstone backend)
|
||||||
target_compile_definitions(kaizen PUBLIC SDL_MAIN_HANDLED)
|
|
||||||
set_target_properties(kaizen PROPERTIES
|
|
||||||
WIN32_EXECUTABLE ON
|
|
||||||
MACOSX_BUNDLE ON)
|
|
||||||
|
|
||||||
if (SANITIZERS)
|
if (SANITIZERS)
|
||||||
message("UBSAN AND ASAN: ON")
|
message("UBSAN AND ASAN: ON")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Copyright 2023 Simone Coco
|
Copyright 2026 Iris Coco
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
|||||||
+11
-6
@@ -4,7 +4,6 @@
|
|||||||
#include <rdp_device.hpp>
|
#include <rdp_device.hpp>
|
||||||
#include <resources/vert.spv.h>
|
#include <resources/vert.spv.h>
|
||||||
#include <resources/frag.spv.h>
|
#include <resources/frag.spv.h>
|
||||||
#include <KaizenGui.hpp>
|
|
||||||
|
|
||||||
using namespace Vulkan;
|
using namespace Vulkan;
|
||||||
using namespace RDP;
|
using namespace RDP;
|
||||||
@@ -23,7 +22,8 @@ Program *fullscreen_quad_program;
|
|||||||
|
|
||||||
// Copied and modified from WSI::init_context_from_platform
|
// Copied and modified from WSI::init_context_from_platform
|
||||||
Util::IntrusivePtr<Context> InitVulkanContext(WSIPlatform *platform, const unsigned num_thread_indices,
|
Util::IntrusivePtr<Context> InitVulkanContext(WSIPlatform *platform, const unsigned num_thread_indices,
|
||||||
const Context::SystemHandles &system_handles) {
|
const Context::SystemHandles &system_handles,
|
||||||
|
Vulkan::InstanceFactory *instanceFactory) {
|
||||||
VK_ASSERT(platform);
|
VK_ASSERT(platform);
|
||||||
const auto instance_ext = platform->get_instance_extensions();
|
const auto instance_ext = platform->get_instance_extensions();
|
||||||
const auto device_ext = platform->get_device_extensions();
|
const auto device_ext = platform->get_device_extensions();
|
||||||
@@ -32,6 +32,9 @@ Util::IntrusivePtr<Context> InitVulkanContext(WSIPlatform *platform, const unsig
|
|||||||
new_context->set_application_info(platform->get_application_info());
|
new_context->set_application_info(platform->get_application_info());
|
||||||
new_context->set_num_thread_indices(num_thread_indices);
|
new_context->set_num_thread_indices(num_thread_indices);
|
||||||
new_context->set_system_handles(system_handles);
|
new_context->set_system_handles(system_handles);
|
||||||
|
if (instanceFactory) {
|
||||||
|
new_context->set_instance_factory(instanceFactory);
|
||||||
|
}
|
||||||
|
|
||||||
if (!new_context->init_instance(instance_ext.data(), instance_ext.size(),
|
if (!new_context->init_instance(instance_ext.data(), instance_ext.size(),
|
||||||
CONTEXT_CREATION_ENABLE_ADVANCED_WSI_BIT)) {
|
CONTEXT_CREATION_ENABLE_ADVANCED_WSI_BIT)) {
|
||||||
@@ -55,14 +58,15 @@ Util::IntrusivePtr<Context> InitVulkanContext(WSIPlatform *platform, const unsig
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ParallelRDP::LoadWSIPlatform(const std::shared_ptr<WSIPlatform> &wsi_platform,
|
void ParallelRDP::LoadWSIPlatform(const std::shared_ptr<WSIPlatform> &wsi_platform,
|
||||||
const std::shared_ptr<WindowInfo> &newWindowInfo) {
|
const std::shared_ptr<WindowInfo> &newWindowInfo,
|
||||||
|
Vulkan::InstanceFactory *instanceFactory) {
|
||||||
wsi = std::make_shared<WSI>();
|
wsi = std::make_shared<WSI>();
|
||||||
wsi->set_backbuffer_srgb(false);
|
wsi->set_backbuffer_srgb(false);
|
||||||
wsi->set_platform(wsi_platform.get());
|
wsi->set_platform(wsi_platform.get());
|
||||||
wsi->set_present_mode(PresentMode::SyncToVBlank);
|
wsi->set_present_mode(PresentMode::SyncToVBlank);
|
||||||
|
|
||||||
if (constexpr Context::SystemHandles handles;
|
if (constexpr Context::SystemHandles handles;
|
||||||
!wsi->init_from_existing_context(InitVulkanContext(wsi_platform.get(), 1, handles))) {
|
!wsi->init_from_existing_context(InitVulkanContext(wsi_platform.get(), 1, handles, instanceFactory))) {
|
||||||
panic("Failed to initialize WSI: init_from_existing_context() failed");
|
panic("Failed to initialize WSI: init_from_existing_context() failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,8 +82,9 @@ void ParallelRDP::LoadWSIPlatform(const std::shared_ptr<WSIPlatform> &wsi_platfo
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ParallelRDP::Init(const std::shared_ptr<WSIPlatform> &wsiPlatform,
|
void ParallelRDP::Init(const std::shared_ptr<WSIPlatform> &wsiPlatform,
|
||||||
const std::shared_ptr<WindowInfo> &newWindowInfo, const u8 *rdram) {
|
const std::shared_ptr<WindowInfo> &newWindowInfo, Vulkan::InstanceFactory *instanceFactory,
|
||||||
LoadWSIPlatform(wsiPlatform, newWindowInfo);
|
const u8 *rdram) {
|
||||||
|
LoadWSIPlatform(wsiPlatform, newWindowInfo, instanceFactory);
|
||||||
|
|
||||||
ResourceLayout vertLayout;
|
ResourceLayout vertLayout;
|
||||||
ResourceLayout fragLayout;
|
ResourceLayout fragLayout;
|
||||||
|
|||||||
+4
-2
@@ -15,7 +15,8 @@ class ParallelRDP {
|
|||||||
virtual ~WindowInfo() = default;
|
virtual ~WindowInfo() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
void Init(const std::shared_ptr<Vulkan::WSIPlatform> &, const std::shared_ptr<WindowInfo> &, const u8 *);
|
void Init(const std::shared_ptr<Vulkan::WSIPlatform> &, const std::shared_ptr<WindowInfo> &,
|
||||||
|
Vulkan::InstanceFactory *, const u8 *);
|
||||||
|
|
||||||
template <bool>
|
template <bool>
|
||||||
void UpdateScreen() const;
|
void UpdateScreen() const;
|
||||||
@@ -29,7 +30,8 @@ class ParallelRDP {
|
|||||||
std::shared_ptr<WindowInfo> windowInfo;
|
std::shared_ptr<WindowInfo> windowInfo;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void LoadWSIPlatform(const std::shared_ptr<Vulkan::WSIPlatform> &, const std::shared_ptr<WindowInfo> &);
|
void LoadWSIPlatform(const std::shared_ptr<Vulkan::WSIPlatform> &, const std::shared_ptr<WindowInfo> &,
|
||||||
|
Vulkan::InstanceFactory *);
|
||||||
void DrawFullscreenTexturedQuad(Util::IntrusivePtr<Vulkan::Image>, Util::IntrusivePtr<Vulkan::CommandBuffer>) const;
|
void DrawFullscreenTexturedQuad(Util::IntrusivePtr<Vulkan::Image>, Util::IntrusivePtr<Vulkan::CommandBuffer>) const;
|
||||||
void UpdateScreen(Util::IntrusivePtr<Vulkan::Image>) const;
|
void UpdateScreen(Util::IntrusivePtr<Vulkan::Image>) const;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
namespace n64 {
|
namespace n64 {
|
||||||
Core::Core() : interpreter(*mem, regs) {
|
Core::Core() : interpreter(*mem, regs) {
|
||||||
const auto selectedCpu = Options::GetInstance().GetValue<std::string>("cpu", "type");
|
const auto selectedCpu = Options::GetCpuType();
|
||||||
if (selectedCpu == "interpreter") {
|
if (selectedCpu == "interpreter") {
|
||||||
cpuType = PlainInterpreter;
|
cpuType = PlainInterpreter;
|
||||||
} else if (selectedCpu == "cached_interpreter") {
|
} else if (selectedCpu == "cached_interpreter") {
|
||||||
@@ -88,7 +88,7 @@ void Core::StepRSP(const u32 cpuCycles) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::Run(const float volumeL, const float volumeR) {
|
void Core::Run() {
|
||||||
MMIO &mmio = mem->mmio;
|
MMIO &mmio = mem->mmio;
|
||||||
|
|
||||||
bool broken = false;
|
bool broken = false;
|
||||||
@@ -116,7 +116,7 @@ void Core::Run(const float volumeL, const float volumeR) {
|
|||||||
mmio.mi.InterruptRaise(MI::Interrupt::VI);
|
mmio.mi.InterruptRaise(MI::Interrupt::VI);
|
||||||
}
|
}
|
||||||
|
|
||||||
mmio.ai.Step(frameCycles, volumeL, volumeR);
|
mmio.ai.Step(frameCycles);
|
||||||
Scheduler::GetInstance().Tick(frameCycles);
|
Scheduler::GetInstance().Tick(frameCycles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ struct Core {
|
|||||||
void Reset();
|
void Reset();
|
||||||
void LoadROM(const std::string &);
|
void LoadROM(const std::string &);
|
||||||
void LoadTAS(const fs::path &) const;
|
void LoadTAS(const fs::path &) const;
|
||||||
void Run(float volumeL, float volumeR);
|
void Run();
|
||||||
void TogglePause() { pause = !pause; }
|
void TogglePause() { pause = !pause; }
|
||||||
inline void ToggleBreakpoint(s64 addr) {
|
inline void ToggleBreakpoint(s64 addr) {
|
||||||
if (breakpoints.contains(addr)) {
|
if (breakpoints.contains(addr)) {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ void Mem::Reset() {
|
|||||||
void Mem::LoadSRAM(SaveType save_type, fs::path path) {
|
void Mem::LoadSRAM(SaveType save_type, fs::path path) {
|
||||||
if (save_type == SAVE_SRAM_256k) {
|
if (save_type == SAVE_SRAM_256k) {
|
||||||
std::error_code err;
|
std::error_code err;
|
||||||
std::string savePath = Options::GetInstance().GetValue<std::string>("general", "savePath");
|
std::string savePath = Options::GetSavesPath();
|
||||||
if (!savePath.empty()) {
|
if (!savePath.empty()) {
|
||||||
path = savePath / path.filename();
|
path = savePath / path.filename();
|
||||||
}
|
}
|
||||||
|
|||||||
+138
-138
@@ -8,190 +8,190 @@ constexpr auto FLASH_SIZE = 1_mb;
|
|||||||
Flash::Flash(mio::mmap_sink &saveData) : saveData(saveData) {}
|
Flash::Flash(mio::mmap_sink &saveData) : saveData(saveData) {}
|
||||||
|
|
||||||
void Flash::Reset() {
|
void Flash::Reset() {
|
||||||
state = FlashState::Idle;
|
state = FlashState::Idle;
|
||||||
writeOffs = {};
|
writeOffs = {};
|
||||||
state = {};
|
state = {};
|
||||||
status = {};
|
status = {};
|
||||||
eraseOffs = {};
|
eraseOffs = {};
|
||||||
writeBuf = {};
|
writeBuf = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void Flash::Load(SaveType saveType, const std::string &path) {
|
void Flash::Load(SaveType saveType, const std::string &path) {
|
||||||
if (saveType == SAVE_FLASH_1m) {
|
if (saveType == SAVE_FLASH_1m) {
|
||||||
fs::path flashPath_ = path;
|
fs::path flashPath_ = path;
|
||||||
std::string savePath = Options::GetInstance().GetValue<std::string>("general", "savePath");
|
std::string savePath = Options::GetSavesPath();
|
||||||
if (!savePath.empty()) {
|
if (!savePath.empty()) {
|
||||||
flashPath_ = savePath / flashPath_.filename();
|
flashPath_ = savePath / flashPath_.filename();
|
||||||
}
|
}
|
||||||
flashPath = flashPath_.replace_extension(".flash").string();
|
flashPath = flashPath_.replace_extension(".flash").string();
|
||||||
std::error_code error;
|
std::error_code error;
|
||||||
if (saveData.is_mapped()) {
|
if (saveData.is_mapped()) {
|
||||||
saveData.sync(error);
|
saveData.sync(error);
|
||||||
if (error) {
|
if (error) {
|
||||||
panic("Could not sync {}", flashPath);
|
panic("Could not sync {}", flashPath);
|
||||||
}
|
}
|
||||||
saveData.unmap();
|
saveData.unmap();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto flashVec = ircolib::ReadFileBinary(flashPath);
|
auto flashVec = ircolib::ReadFileBinary(flashPath);
|
||||||
if (flashVec.empty()) {
|
if (flashVec.empty()) {
|
||||||
std::vector<u8> dummy{};
|
std::vector<u8> dummy{};
|
||||||
dummy.resize(FLASH_SIZE);
|
dummy.resize(FLASH_SIZE);
|
||||||
ircolib::WriteFileBinary(dummy, flashPath);
|
ircolib::WriteFileBinary(dummy, flashPath);
|
||||||
flashVec = ircolib::ReadFileBinary(flashPath);
|
flashVec = ircolib::ReadFileBinary(flashPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flashVec.size() != FLASH_SIZE) {
|
if (flashVec.size() != FLASH_SIZE) {
|
||||||
panic("Corrupt SRAM!");
|
panic("Corrupt SRAM!");
|
||||||
}
|
}
|
||||||
|
|
||||||
saveData = mio::make_mmap_sink(flashPath, error);
|
saveData = mio::make_mmap_sink(flashPath, error);
|
||||||
if (error) {
|
if (error) {
|
||||||
panic("Could not make mmap {}", flashPath);
|
panic("Could not make mmap {}", flashPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Flash::CommandExecute() const {
|
void Flash::CommandExecute() const {
|
||||||
trace("Flash::CommandExecute");
|
trace("Flash::CommandExecute");
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case FlashState::Idle:
|
case FlashState::Idle:
|
||||||
break;
|
break;
|
||||||
case FlashState::Erase:
|
case FlashState::Erase:
|
||||||
if (saveData.is_mapped()) {
|
if (saveData.is_mapped()) {
|
||||||
for (int i = 0; i < 128; i++) {
|
for (int i = 0; i < 128; i++) {
|
||||||
saveData[eraseOffs + i] = 0xFF;
|
saveData[eraseOffs + i] = 0xFF;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
panic("Accessing flash when not mapped!");
|
panic("Accessing flash when not mapped!");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FlashState::Write:
|
||||||
|
if (saveData.is_mapped()) {
|
||||||
|
for (int i = 0; i < 128; i++) {
|
||||||
|
saveData[writeOffs + i] = writeBuf[i];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic("Accessing flash when not mapped!");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FlashState::Read:
|
||||||
|
panic("Execute command when flash in read state");
|
||||||
|
break;
|
||||||
|
case FlashState::Status:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case FlashState::Write:
|
|
||||||
if (saveData.is_mapped()) {
|
|
||||||
for (int i = 0; i < 128; i++) {
|
|
||||||
saveData[writeOffs + i] = writeBuf[i];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic("Accessing flash when not mapped!");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case FlashState::Read:
|
|
||||||
panic("Execute command when flash in read state");
|
|
||||||
break;
|
|
||||||
case FlashState::Status:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Flash::CommandStatus() {
|
void Flash::CommandStatus() {
|
||||||
state = FlashState::Status;
|
state = FlashState::Status;
|
||||||
status = 0x1111800100C20000;
|
status = 0x1111800100C20000;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Flash::CommandSetEraseOffs(u32 val) { eraseOffs = (val & 0xffff) << 7; }
|
void Flash::CommandSetEraseOffs(u32 val) { eraseOffs = (val & 0xffff) << 7; }
|
||||||
|
|
||||||
void Flash::CommandErase() {
|
void Flash::CommandErase() {
|
||||||
state = FlashState::Erase;
|
state = FlashState::Erase;
|
||||||
status = 0x1111800800C20000LL;
|
status = 0x1111800800C20000LL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Flash::CommandSetWriteOffs(u32 val) {
|
void Flash::CommandSetWriteOffs(u32 val) {
|
||||||
writeOffs = (val & 0xffff) << 7;
|
writeOffs = (val & 0xffff) << 7;
|
||||||
status = 0x1111800400C20000LL;
|
status = 0x1111800400C20000LL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Flash::CommandWrite() { state = FlashState::Write; }
|
void Flash::CommandWrite() { state = FlashState::Write; }
|
||||||
|
|
||||||
void Flash::CommandRead() {
|
void Flash::CommandRead() {
|
||||||
state = FlashState::Read;
|
state = FlashState::Read;
|
||||||
status = 0x11118004F0000000;
|
status = 0x11118004F0000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
void Flash::Write<u32>(u32 index, u32 val) {
|
void Flash::Write<u32>(u32 index, u32 val) {
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
u8 cmd = val >> 24;
|
u8 cmd = val >> 24;
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case FLASH_COMMAND_EXECUTE:
|
case FLASH_COMMAND_EXECUTE:
|
||||||
CommandExecute();
|
CommandExecute();
|
||||||
break;
|
break;
|
||||||
case FLASH_COMMAND_STATUS:
|
case FLASH_COMMAND_STATUS:
|
||||||
CommandStatus();
|
CommandStatus();
|
||||||
break;
|
break;
|
||||||
case FLASH_COMMAND_SET_ERASE_OFFSET:
|
case FLASH_COMMAND_SET_ERASE_OFFSET:
|
||||||
CommandSetEraseOffs(val);
|
CommandSetEraseOffs(val);
|
||||||
break;
|
break;
|
||||||
case FLASH_COMMAND_ERASE:
|
case FLASH_COMMAND_ERASE:
|
||||||
CommandErase();
|
CommandErase();
|
||||||
break;
|
break;
|
||||||
case FLASH_COMMAND_SET_WRITE_OFFSET:
|
case FLASH_COMMAND_SET_WRITE_OFFSET:
|
||||||
CommandSetWriteOffs(val);
|
CommandSetWriteOffs(val);
|
||||||
break;
|
break;
|
||||||
case FLASH_COMMAND_WRITE:
|
case FLASH_COMMAND_WRITE:
|
||||||
CommandWrite();
|
CommandWrite();
|
||||||
break;
|
break;
|
||||||
case FLASH_COMMAND_READ:
|
case FLASH_COMMAND_READ:
|
||||||
CommandRead();
|
CommandRead();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
warn("Invalid flash command: {:02X}", cmd);
|
warn("Invalid flash command: {:02X}", cmd);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn("Flash Write of {:08X} @ {:08X}", val, index);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
warn("Flash Write of {:08X} @ {:08X}", val, index);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
void Flash::Write<u8>(u32 index, u8 val) {
|
void Flash::Write<u8>(u32 index, u8 val) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case FlashState::Idle:
|
case FlashState::Idle:
|
||||||
panic("Invalid FlashState::Idle with Write<u8>");
|
panic("Invalid FlashState::Idle with Write<u8>");
|
||||||
case FlashState::Status:
|
case FlashState::Status:
|
||||||
panic("Invalid FlashState::Status with Write<u8>");
|
panic("Invalid FlashState::Status with Write<u8>");
|
||||||
case FlashState::Erase:
|
case FlashState::Erase:
|
||||||
panic("Invalid FlashState::Erase with Write<u8>");
|
panic("Invalid FlashState::Erase with Write<u8>");
|
||||||
case FlashState::Read:
|
case FlashState::Read:
|
||||||
panic("Invalid FlashState::Read with Write<u8>");
|
panic("Invalid FlashState::Read with Write<u8>");
|
||||||
case FlashState::Write:
|
case FlashState::Write:
|
||||||
assert(index <= 0x7F && "Out of range flash Write8");
|
assert(index <= 0x7F && "Out of range flash Write8");
|
||||||
writeBuf[index] = val;
|
writeBuf[index] = val;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
warn("Invalid flash state on Write<u8>: {:02X}", static_cast<u8>(state));
|
warn("Invalid flash state on Write<u8>: {:02X}", static_cast<u8>(state));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
u8 Flash::Read<u8>(const u32 index) const {
|
u8 Flash::Read<u8>(const u32 index) const {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case FlashState::Idle:
|
case FlashState::Idle:
|
||||||
panic("Flash read byte while in state FLASH_STATE_IDLE");
|
panic("Flash read byte while in state FLASH_STATE_IDLE");
|
||||||
case FlashState::Write:
|
case FlashState::Write:
|
||||||
panic("Flash read byte while in state FLASH_STATE_WRITE");
|
panic("Flash read byte while in state FLASH_STATE_WRITE");
|
||||||
case FlashState::Read:
|
case FlashState::Read:
|
||||||
if (saveData.is_mapped()) {
|
if (saveData.is_mapped()) {
|
||||||
const u8 value = saveData[index];
|
const u8 value = saveData[index];
|
||||||
trace("Flash read byte in state read: index {:08X} = {:02X}", index, value);
|
trace("Flash read byte in state read: index {:08X} = {:02X}", index, value);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
panic("Accessing flash when not mapped!");
|
panic("Accessing flash when not mapped!");
|
||||||
|
|
||||||
case FlashState::Status:
|
case FlashState::Status:
|
||||||
{
|
{
|
||||||
const u32 offset = (7 - (index % 8)) * 8;
|
const u32 offset = (7 - (index % 8)) * 8;
|
||||||
const u8 value = (status >> offset) & 0xFF;
|
const u8 value = (status >> offset) & 0xFF;
|
||||||
trace("Flash read byte in state status: index {:08X} = {:02X}", index, value);
|
trace("Flash read byte in state status: index {:08X} = {:02X}", index, value);
|
||||||
return value;
|
return value;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("Flash read byte while in unknown state");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
panic("Flash read byte while in unknown state");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
u32 Flash::Read<u32>(u32) const {
|
u32 Flash::Read<u32>(u32) const {
|
||||||
return status >> 32;
|
return status >> 32;
|
||||||
}
|
}
|
||||||
} // namespace n64
|
} // namespace n64
|
||||||
|
|||||||
@@ -1,119 +1,122 @@
|
|||||||
#include <Core.hpp>
|
#include <Core.hpp>
|
||||||
#include <log.hpp>
|
#include <log.hpp>
|
||||||
|
#include <Options.hpp>
|
||||||
|
|
||||||
namespace n64 {
|
namespace n64 {
|
||||||
AI::AI() { Reset(); }
|
AI::AI() { Reset(); }
|
||||||
|
|
||||||
void AI::Reset() {
|
void AI::Reset() {
|
||||||
dmaEnable = false;
|
dmaEnable = false;
|
||||||
dacRate = 0;
|
dacRate = 0;
|
||||||
bitrate = 0;
|
bitrate = 0;
|
||||||
dmaCount = 0;
|
dmaCount = 0;
|
||||||
dmaAddrCarry = false;
|
dmaAddrCarry = false;
|
||||||
cycles = 0;
|
cycles = 0;
|
||||||
dmaLen = {};
|
dmaLen = {};
|
||||||
dmaAddr = {};
|
dmaAddr = {};
|
||||||
dac = {44100, N64_CPU_FREQ / dac.freq, 16};
|
dac = {44100, N64_CPU_FREQ / dac.freq, 16};
|
||||||
device.Reset();
|
device.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/ares-emulator/ares/blob/master/ares/n64/ai/io.cpp
|
// https://github.com/ares-emulator/ares/blob/master/ares/n64/ai/io.cpp
|
||||||
// https://github.com/ares-emulator/ares/blob/master/LICENSE
|
// https://github.com/ares-emulator/ares/blob/master/LICENSE
|
||||||
auto AI::Read(const u32 addr) const -> u32 {
|
auto AI::Read(const u32 addr) const -> u32 {
|
||||||
if (addr == 0x0450000C) {
|
if (addr == 0x0450000C) {
|
||||||
u32 val = 0;
|
u32 val = 0;
|
||||||
val |= (dmaCount > 1);
|
val |= (dmaCount > 1);
|
||||||
val |= 1 << 20;
|
val |= 1 << 20;
|
||||||
val |= 1 << 24;
|
val |= 1 << 24;
|
||||||
val |= (dmaEnable << 25);
|
val |= (dmaEnable << 25);
|
||||||
val |= (dmaCount > 0) << 30;
|
val |= (dmaCount > 0) << 30;
|
||||||
val |= (dmaCount > 1) << 31;
|
val |= (dmaCount > 1) << 31;
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
return dmaLen[0];
|
return dmaLen[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
void AI::Write(const u32 addr, const u32 val) {
|
void AI::Write(const u32 addr, const u32 val) {
|
||||||
n64::Mem& mem = n64::Core::GetMem();
|
n64::Mem &mem = n64::Core::GetMem();
|
||||||
switch (addr) {
|
switch (addr) {
|
||||||
case 0x04500000:
|
case 0x04500000:
|
||||||
if (dmaCount < 2) {
|
if (dmaCount < 2) {
|
||||||
dmaAddr[dmaCount] = val & 0xFFFFFF & ~7;
|
dmaAddr[dmaCount] = val & 0xFFFFFF & ~7;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x04500004:
|
||||||
|
{
|
||||||
|
const u32 len = (val & 0x3FFFF) & ~7;
|
||||||
|
if (dmaCount < 2) {
|
||||||
|
if (dmaCount == 0)
|
||||||
|
mem.mmio.mi.InterruptRaise(MI::Interrupt::AI);
|
||||||
|
dmaLen[dmaCount] = len;
|
||||||
|
dmaCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x04500008:
|
||||||
|
dmaEnable = val & 1;
|
||||||
|
break;
|
||||||
|
case 0x0450000C:
|
||||||
|
mem.mmio.mi.InterruptLower(MI::Interrupt::AI);
|
||||||
|
break;
|
||||||
|
case 0x04500010:
|
||||||
|
{
|
||||||
|
const u32 oldDacFreq = dac.freq;
|
||||||
|
dacRate = val & 0x3FFF;
|
||||||
|
dac.freq = std::max(1.f, (float)GetVideoFrequency(mem.IsROMPAL()) / (dacRate + 1)) * 1.037;
|
||||||
|
dac.period = GetVideoFrequency(mem.IsROMPAL()) / dac.freq;
|
||||||
|
if (oldDacFreq != dac.freq) {
|
||||||
|
device.AdjustSampleRate(dac.freq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x04500014:
|
||||||
|
bitrate = val & 0xF;
|
||||||
|
dac.precision = bitrate + 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
panic("Unhandled AI write at addr {:08X} with val {:08X}", addr, val);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case 0x04500004:
|
|
||||||
{
|
|
||||||
const u32 len = (val & 0x3FFFF) & ~7;
|
|
||||||
if (dmaCount < 2) {
|
|
||||||
if (dmaCount == 0)
|
|
||||||
mem.mmio.mi.InterruptRaise(MI::Interrupt::AI);
|
|
||||||
dmaLen[dmaCount] = len;
|
|
||||||
dmaCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x04500008:
|
|
||||||
dmaEnable = val & 1;
|
|
||||||
break;
|
|
||||||
case 0x0450000C:
|
|
||||||
mem.mmio.mi.InterruptLower(MI::Interrupt::AI);
|
|
||||||
break;
|
|
||||||
case 0x04500010:
|
|
||||||
{
|
|
||||||
const u32 oldDacFreq = dac.freq;
|
|
||||||
dacRate = val & 0x3FFF;
|
|
||||||
dac.freq = std::max(1.f, (float)GetVideoFrequency(mem.IsROMPAL()) / (dacRate + 1)) * 1.037;
|
|
||||||
dac.period = GetVideoFrequency(mem.IsROMPAL()) / dac.freq;
|
|
||||||
if (oldDacFreq != dac.freq) {
|
|
||||||
device.AdjustSampleRate(dac.freq);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x04500014:
|
|
||||||
bitrate = val & 0xF;
|
|
||||||
dac.precision = bitrate + 1;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
panic("Unhandled AI write at addr {:08X} with val {:08X}", addr, val);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AI::Step(const u32 cpuCycles, const float volumeL, const float volumeR) {
|
void AI::Step(const u32 cpuCycles) {
|
||||||
n64::Mem& mem = n64::Core::GetMem();
|
n64::Mem &mem = n64::Core::GetMem();
|
||||||
cycles += cpuCycles;
|
cycles += cpuCycles;
|
||||||
while (cycles > dac.period) {
|
while (cycles > dac.period) {
|
||||||
if (dmaCount == 0) {
|
if (dmaCount == 0) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dmaLen[0] && dmaEnable) {
|
||||||
|
const u32 addrHi = (dmaAddr[0] >> 13) + dmaAddrCarry & 0x7FF;
|
||||||
|
dmaAddr[0] = addrHi << 13 | dmaAddr[0] & 0x1FFF;
|
||||||
|
const u32 data = mem.mmio.rdp.ReadRDRAM<u32>(dmaAddr[0]);
|
||||||
|
const s16 l = s16(data >> 16);
|
||||||
|
const s16 r = s16(data);
|
||||||
|
|
||||||
|
auto volume = Options::GetVolume();
|
||||||
|
if (volume > 0) {
|
||||||
|
device.PushSample((float)l / std::numeric_limits<s16>::max(), volume,
|
||||||
|
(float)r / std::numeric_limits<s16>::max(), volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 addrLo = dmaAddr[0] + 4 & 0x1FFF;
|
||||||
|
dmaAddr[0] = dmaAddr[0] & ~0x1FFF | addrLo;
|
||||||
|
dmaAddrCarry = addrLo == 0;
|
||||||
|
dmaLen[0] -= 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dmaLen[0]) {
|
||||||
|
if (--dmaCount > 0) {
|
||||||
|
mem.mmio.mi.InterruptRaise(MI::Interrupt::AI);
|
||||||
|
dmaAddr[0] = dmaAddr[1];
|
||||||
|
dmaLen[0] = dmaLen[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cycles -= dac.period;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dmaLen[0] && dmaEnable) {
|
|
||||||
const u32 addrHi = (dmaAddr[0] >> 13) + dmaAddrCarry & 0x7FF;
|
|
||||||
dmaAddr[0] = addrHi << 13 | dmaAddr[0] & 0x1FFF;
|
|
||||||
const u32 data = mem.mmio.rdp.ReadRDRAM<u32>(dmaAddr[0]);
|
|
||||||
const s16 l = s16(data >> 16);
|
|
||||||
const s16 r = s16(data);
|
|
||||||
|
|
||||||
if (volumeR > 0 && volumeL > 0) {
|
|
||||||
device.PushSample((float)l / std::numeric_limits<s16>::max(), volumeL, (float)r / std::numeric_limits<s16>::max(), volumeR);
|
|
||||||
}
|
|
||||||
|
|
||||||
const u32 addrLo = dmaAddr[0] + 4 & 0x1FFF;
|
|
||||||
dmaAddr[0] = dmaAddr[0] & ~0x1FFF | addrLo;
|
|
||||||
dmaAddrCarry = addrLo == 0;
|
|
||||||
dmaLen[0] -= 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dmaLen[0]) {
|
|
||||||
if (--dmaCount > 0) {
|
|
||||||
mem.mmio.mi.InterruptRaise(MI::Interrupt::AI);
|
|
||||||
dmaAddr[0] = dmaAddr[1];
|
|
||||||
dmaLen[0] = dmaLen[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cycles -= dac.period;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace n64
|
} // namespace n64
|
||||||
|
|||||||
@@ -1,29 +1,30 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <common.hpp>
|
#include <common.hpp>
|
||||||
#include <core/mmio/Audio.hpp>
|
#include <core/mmio/Audio.hpp>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
namespace n64 {
|
namespace n64 {
|
||||||
struct AI {
|
struct AI {
|
||||||
AI();
|
AI();
|
||||||
void Reset();
|
void Reset();
|
||||||
auto Read(u32) const -> u32;
|
auto Read(u32) const -> u32;
|
||||||
void Write(u32, u32);
|
void Write(u32, u32);
|
||||||
void Step(u32, float, float);
|
void Step(u32);
|
||||||
bool dmaEnable{};
|
bool dmaEnable{};
|
||||||
bool dmaAddrCarry{};
|
bool dmaAddrCarry{};
|
||||||
u8 bitrate{};
|
u8 bitrate{};
|
||||||
u16 dacRate{};
|
u16 dacRate{};
|
||||||
int dmaCount{};
|
int dmaCount{};
|
||||||
u32 cycles{};
|
u32 cycles{};
|
||||||
std::array<u32, 2> dmaLen{};
|
std::array<u32, 2> dmaLen{};
|
||||||
std::array<u32, 2> dmaAddr{};
|
std::array<u32, 2> dmaAddr{};
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
u32 freq{44100};
|
u32 freq{44100};
|
||||||
u32 period{N64_CPU_FREQ / freq};
|
u32 period{N64_CPU_FREQ / freq};
|
||||||
u32 precision{16};
|
u32 precision{16};
|
||||||
} dac;
|
} dac;
|
||||||
|
|
||||||
AudioDevice device;
|
AudioDevice device;
|
||||||
};
|
};
|
||||||
} // namespace n64
|
} // namespace n64
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ void PIF::Reset() {
|
|||||||
void PIF::MaybeLoadMempak() {
|
void PIF::MaybeLoadMempak() {
|
||||||
if (!mempakOpen) {
|
if (!mempakOpen) {
|
||||||
fs::path mempakPath_ = mempakPath;
|
fs::path mempakPath_ = mempakPath;
|
||||||
fs::path savePath = Options::GetInstance().GetValue<std::string>("general", "savePath");
|
fs::path savePath = Options::GetSavesPath();
|
||||||
if (!savePath.empty()) {
|
if (!savePath.empty()) {
|
||||||
if (!fs::exists(savePath))
|
if (!fs::exists(savePath))
|
||||||
fs::create_directory(savePath);
|
fs::create_directory(savePath);
|
||||||
@@ -92,7 +92,7 @@ FORCE_INLINE size_t GetSaveSize(SaveType saveType) {
|
|||||||
void PIF::LoadEeprom(const SaveType saveType, const std::string &path) {
|
void PIF::LoadEeprom(const SaveType saveType, const std::string &path) {
|
||||||
if (saveType == SAVE_EEPROM_16k || saveType == SAVE_EEPROM_4k) {
|
if (saveType == SAVE_EEPROM_16k || saveType == SAVE_EEPROM_4k) {
|
||||||
fs::path eepromPath_ = path;
|
fs::path eepromPath_ = path;
|
||||||
std::string savePath = Options::GetInstance().GetValue<std::string>("general", "savePath");
|
std::string savePath = Options::GetSavesPath();
|
||||||
if (!savePath.empty()) {
|
if (!savePath.empty()) {
|
||||||
eepromPath_ = savePath / eepromPath_.filename();
|
eepromPath_ = savePath / eepromPath_.filename();
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-41
@@ -1,53 +1,25 @@
|
|||||||
#include <Core.hpp>
|
#include <EmuThread.hpp>
|
||||||
#include <EmuThread.hpp>
|
|
||||||
#include <KaizenGui.hpp>
|
#include <KaizenGui.hpp>
|
||||||
|
#include <Core.hpp>
|
||||||
|
|
||||||
EmuThread::EmuThread(double &fps, SettingsWindow &settings) noexcept : settings(settings), fps(fps) {}
|
EmuThread::EmuThread() noexcept {}
|
||||||
|
|
||||||
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 avgFps = 16.667;
|
|
||||||
auto sampledFps = 0;
|
|
||||||
static bool oneSecondPassed = false;
|
|
||||||
|
|
||||||
fps = 1000.0 / avgFps;
|
if (!core.pause) {
|
||||||
|
core.Run();
|
||||||
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 = {};
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-338
@@ -2,360 +2,53 @@
|
|||||||
#include <backend/Core.hpp>
|
#include <backend/Core.hpp>
|
||||||
#include <QMenuBar>
|
#include <QMenuBar>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
|
#include <QMessageBox>
|
||||||
#include <resources/gamecontrollerdb.h>
|
#include <resources/gamecontrollerdb.h>
|
||||||
|
|
||||||
KaizenGui::KaizenGui() noexcept : vulkanWidget(windowHandle()) {
|
KaizenGui::KaizenGui() noexcept {
|
||||||
SDL_InitSubSystem(SDL_INIT_GAMEPAD);
|
SDL_InitSubSystem(SDL_INIT_GAMEPAD);
|
||||||
|
|
||||||
|
hide();
|
||||||
|
vulkanWidget = new RenderWidget();
|
||||||
|
|
||||||
|
setWindowTitle("Kaizen " KAIZEN_VERSION_STR);
|
||||||
|
setMinimumSize(640, 480);
|
||||||
|
setCentralWidget(vulkanWidget);
|
||||||
|
|
||||||
auto fileMenu = menuBar()->addMenu("File");
|
auto fileMenu = menuBar()->addMenu("File");
|
||||||
auto open = fileMenu->addMenu("Open");
|
auto open = fileMenu->addAction("Open");
|
||||||
auto exit = fileMenu->addMenu("Exit");
|
auto exit = fileMenu->addAction("Exit");
|
||||||
auto emulationMenu = menuBar()->addMenu("Emulation");
|
auto emulationMenu = menuBar()->addMenu("Emulation");
|
||||||
auto settingsMenu = emulationMenu->addMenu("Settings");
|
auto settingsMenu = emulationMenu->addAction("Settings");
|
||||||
connect(settingsMenu, &QMenu::triggered, this, [&] { settingsWindow.show(); });
|
settingsWindow = new SettingsWindow();
|
||||||
|
connect(settingsMenu, &QAction::triggered, settingsWindow, &SettingsWindow::show);
|
||||||
|
|
||||||
emulationMenu->addSeparator();
|
emulationMenu->addSeparator();
|
||||||
auto pause = emulationMenu->addMenu("Pause");
|
auto pause = emulationMenu->addAction("Pause");
|
||||||
auto reset = emulationMenu->addMenu("Reset");
|
auto reset = emulationMenu->addAction("Reset");
|
||||||
auto stop = emulationMenu->addMenu("Stop");
|
auto stop = emulationMenu->addAction("Stop");
|
||||||
auto helpMenu = menuBar()->addMenu("Help");
|
auto helpMenu = menuBar()->addMenu("Help");
|
||||||
|
auto about = helpMenu->addAction("About");
|
||||||
|
connect(about, &QAction::triggered, this, [&] {
|
||||||
|
auto text = std::format("<p>Kaizen is a Nintendo 64 emulator that strives<br>"
|
||||||
|
"to offer a friendly user experience and compatibility.<br>"
|
||||||
|
"Kaizen is licensed under the BSD 3-clause license.<br>"
|
||||||
|
"Nintendo 64 is a registered trademark of Nintendo Co., Ltd.</p><hr>"
|
||||||
|
"Kaizen {}{}",
|
||||||
|
KAIZEN_USE_HASH ? "dev build " : "", KAIZEN_VERSION_STR);
|
||||||
|
|
||||||
|
QMessageBox::about(this, "About", text.c_str());
|
||||||
|
});
|
||||||
|
show();
|
||||||
}
|
}
|
||||||
|
|
||||||
KaizenGui::~KaizenGui() { SDL_Quit(); }
|
KaizenGui::~KaizenGui() { SDL_Quit(); }
|
||||||
|
|
||||||
void KaizenGui::QueryDevices(const SDL_Event &event) {
|
|
||||||
switch (event.type) {
|
|
||||||
case SDL_EVENT_GAMEPAD_ADDED:
|
|
||||||
if (!gamepad) {
|
|
||||||
const auto index = event.gdevice.which;
|
|
||||||
|
|
||||||
gamepad = SDL_OpenGamepad(index);
|
|
||||||
info("Found controller!");
|
|
||||||
info("Name: {}", SDL_GetGamepadName(gamepad));
|
|
||||||
info("Vendor: {}", SDL_GetGamepadVendor(gamepad));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SDL_EVENT_GAMEPAD_REMOVED:
|
|
||||||
if (gamepad)
|
|
||||||
SDL_CloseGamepad(gamepad);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void KaizenGui::HandleInput(const SDL_Event &event) {
|
|
||||||
const n64::Core &core = n64::Core::GetInstance();
|
|
||||||
n64::PIF &pif = n64::Core::GetMem().mmio.si.pif;
|
|
||||||
switch (event.type) {
|
|
||||||
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
|
|
||||||
if (!gamepad)
|
|
||||||
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_UP:
|
|
||||||
if (!gamepad)
|
|
||||||
break;
|
|
||||||
|
|
||||||
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::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::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::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::RT, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER));
|
|
||||||
break;
|
|
||||||
case SDL_EVENT_KEY_DOWN:
|
|
||||||
case SDL_EVENT_KEY_UP:
|
|
||||||
{
|
|
||||||
const auto keys = SDL_GetKeyboardState(nullptr);
|
|
||||||
if ((keys[SDL_SCANCODE_LCTRL] || keys[SDL_SCANCODE_RCTRL]) && keys[SDL_SCANCODE_O]) {
|
|
||||||
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;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void KaizenGui::RenderUI() {
|
|
||||||
n64::Core &core = n64::Core::GetInstance();
|
|
||||||
gui::StartFrame();
|
|
||||||
|
|
||||||
if (ImGui::BeginMainMenuBar()) {
|
|
||||||
if (ImGui::BeginMenu("File")) {
|
|
||||||
if (ImGui::MenuItem("Open", "Ctrl-O")) {
|
|
||||||
fileDialogOpen = true;
|
|
||||||
}
|
|
||||||
if (ImGui::MenuItem("Exit")) {
|
|
||||||
quit = true;
|
|
||||||
emuThread.Stop();
|
|
||||||
}
|
|
||||||
ImGui::EndMenu();
|
|
||||||
}
|
|
||||||
if (ImGui::BeginMenu("Emulation")) {
|
|
||||||
ImGui::BeginDisabled(!core.romLoaded);
|
|
||||||
|
|
||||||
if (ImGui::MenuItem(core.pause ? "Resume" : "Pause", "P")) {
|
|
||||||
emuThread.TogglePause();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::MenuItem("Reset", "R")) {
|
|
||||||
emuThread.Reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::MenuItem("Stop", "Q")) {
|
|
||||||
emuThread.Stop();
|
|
||||||
core.romLoaded = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::Checkbox("Unlock framerate", &unlockFramerate)) {
|
|
||||||
core.parallel.SetFramerateUnlocked(unlockFramerate);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::MenuItem("Open Debugger")) {
|
|
||||||
debugger.Open();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndDisabled();
|
|
||||||
|
|
||||||
if (ImGui::MenuItem("Options")) {
|
|
||||||
settingsWindow.isOpen = true;
|
|
||||||
}
|
|
||||||
ImGui::EndMenu();
|
|
||||||
}
|
|
||||||
if (ImGui::BeginMenu("Help")) {
|
|
||||||
if (ImGui::MenuItem("About")) {
|
|
||||||
aboutOpen = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndMenu();
|
|
||||||
}
|
|
||||||
ImGui::EndMainMenuBar();
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
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());
|
setWindowTitle(("Kaizen " KAIZEN_VERSION_STR " - " + gameNameDB).c_str());
|
||||||
}
|
|
||||||
|
|
||||||
void KaizenGui::run() {
|
|
||||||
while (!quit) {
|
|
||||||
SDL_Event e;
|
|
||||||
while (SDL_PollEvent(&e)) {
|
|
||||||
ImGui_ImplSDL3_ProcessEvent(&e);
|
|
||||||
switch (e.type) {
|
|
||||||
case SDL_EVENT_QUIT:
|
|
||||||
quit = true;
|
|
||||||
emuThread.Stop();
|
|
||||||
break;
|
|
||||||
case SDL_EVENT_WINDOW_MINIMIZED:
|
|
||||||
minimized = true;
|
|
||||||
break;
|
|
||||||
case SDL_EVENT_WINDOW_RESTORED:
|
|
||||||
minimized = false;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
QueryDevices(e);
|
|
||||||
HandleInput(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_GetWindowSize(window.getHandle(), &width, &height);
|
|
||||||
|
|
||||||
emuThread.run();
|
|
||||||
RenderUI();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void KaizenGui::LoadTAS(const std::string &path) noexcept { n64::Core::GetInstance().LoadTAS(fs::path(path)); }
|
void KaizenGui::LoadTAS(const std::string &path) noexcept { n64::Core::GetInstance().LoadTAS(fs::path(path)); }
|
||||||
|
|||||||
@@ -1,26 +1,24 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QMainWindow>
|
|
||||||
#include <RenderWidget.hpp>
|
#include <RenderWidget.hpp>
|
||||||
#include <EmuThread.hpp>
|
#include <EmuThread.hpp>
|
||||||
#include <SDL3/SDL_gamepad.h>
|
#include <SDL3/SDL_gamepad.h>
|
||||||
|
#include <QMainWindow>
|
||||||
|
|
||||||
class KaizenGui final : QMainWindow {
|
class KaizenGui final : QMainWindow {
|
||||||
public:
|
public:
|
||||||
explicit KaizenGui() noexcept;
|
explicit KaizenGui() noexcept;
|
||||||
~KaizenGui();
|
~KaizenGui();
|
||||||
|
|
||||||
double fpsCounter = -1.0;
|
|
||||||
bool fastForward = false;
|
bool fastForward = false;
|
||||||
bool unlockFramerate = false;
|
bool unlockFramerate = false;
|
||||||
bool minimized = false;
|
bool minimized = false;
|
||||||
|
|
||||||
SettingsWindow settingsWindow;
|
SettingsWindow *settingsWindow;
|
||||||
RenderWidget vulkanWidget;
|
RenderWidget *vulkanWidget;
|
||||||
EmuThread emuThread;
|
EmuThread emuThread;
|
||||||
|
|
||||||
SDL_Gamepad *gamepad = nullptr;
|
SDL_Gamepad *gamepad = nullptr;
|
||||||
|
|
||||||
void run();
|
|
||||||
static void LoadTAS(const std::string &path) noexcept;
|
static void LoadTAS(const std::string &path) noexcept;
|
||||||
void LoadROM(const std::string &path) noexcept;
|
void LoadROM(const std::string &path) noexcept;
|
||||||
|
|
||||||
@@ -31,9 +29,6 @@ class KaizenGui final : QMainWindow {
|
|||||||
bool quit = false;
|
bool quit = false;
|
||||||
bool shouldDisplaySpinner = false;
|
bool shouldDisplaySpinner = false;
|
||||||
std::string fileToLoad = "";
|
std::string fileToLoad = "";
|
||||||
void RenderUI();
|
|
||||||
void HandleInput(const SDL_Event &event);
|
|
||||||
void QueryDevices(const SDL_Event &event);
|
|
||||||
|
|
||||||
void FileWorker() {
|
void FileWorker() {
|
||||||
if (fileToLoad.empty())
|
if (fileToLoad.empty())
|
||||||
|
|||||||
@@ -1,10 +1,49 @@
|
|||||||
#include <Core.hpp>
|
|
||||||
#include <KaizenGui.hpp>
|
#include <KaizenGui.hpp>
|
||||||
#include <RenderWidget.hpp>
|
#include <RenderWidget.hpp>
|
||||||
|
#include <Core.hpp>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
|
||||||
RenderWidget::RenderWidget(QWindow *window) {
|
enum class CompositorCategory { Windows, MacOS, XCB, Wayland };
|
||||||
wsiPlatform = std::make_shared<QtWSIPlatform>(window);
|
|
||||||
windowInfo = std::make_shared<QtPrdpWindowInfo>(window);
|
static CompositorCategory GetOSCompositorCategory() {
|
||||||
|
const QString platform_name = QGuiApplication::platformName();
|
||||||
|
if (platform_name == QStringLiteral("windows"))
|
||||||
|
return CompositorCategory::Windows;
|
||||||
|
if (platform_name == QStringLiteral("xcb"))
|
||||||
|
return CompositorCategory::XCB;
|
||||||
|
if (platform_name == QStringLiteral("wayland") || platform_name == QStringLiteral("wayland-egl"))
|
||||||
|
return CompositorCategory::Wayland;
|
||||||
|
if (platform_name == QStringLiteral("cocoa") || platform_name == QStringLiteral("ios"))
|
||||||
|
return CompositorCategory::MacOS;
|
||||||
|
|
||||||
|
warn("Unknown Qt platform!");
|
||||||
|
return CompositorCategory::Windows;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderWidget::RenderWidget() {
|
||||||
|
setAttribute(Qt::WA_NativeWindow);
|
||||||
|
setAttribute(Qt::WA_PaintOnScreen);
|
||||||
|
if (GetOSCompositorCategory() == CompositorCategory::Wayland) {
|
||||||
|
setAttribute(Qt::WA_DontCreateNativeAncestors);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetOSCompositorCategory() == CompositorCategory::MacOS) {
|
||||||
|
windowHandle()->setSurfaceType(QWindow::MetalSurface);
|
||||||
|
} else {
|
||||||
|
windowHandle()->setSurfaceType(QWindow::VulkanSurface);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Vulkan::Context::init_loader(nullptr)) {
|
||||||
|
panic("Could not initialize Vulkan ICD");
|
||||||
|
}
|
||||||
|
|
||||||
|
qtVkInstanceFactory = std::make_unique<QtInstanceFactory>();
|
||||||
|
|
||||||
|
windowHandle()->setVulkanInstance(&qtVkInstanceFactory->handle);
|
||||||
|
windowHandle()->create();
|
||||||
|
|
||||||
|
wsiPlatform = std::make_shared<QtWSIPlatform>(windowHandle());
|
||||||
|
windowInfo = std::make_shared<QtPrdpWindowInfo>(windowHandle());
|
||||||
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, qtVkInstanceFactory.get(), core.GetMem().GetRDRAMPtr());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#undef signals
|
||||||
#include <ParallelRDPWrapper.hpp>
|
#include <ParallelRDPWrapper.hpp>
|
||||||
#include <QVulkanWindow>
|
#include <QVulkanWindow>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
@@ -8,12 +9,47 @@ struct InputSettings;
|
|||||||
namespace n64 {
|
namespace n64 {
|
||||||
struct Core;
|
struct Core;
|
||||||
}
|
}
|
||||||
|
class QtPrdpWindowInfo;
|
||||||
|
class QtWSIPlatform;
|
||||||
|
|
||||||
|
struct QtInstanceFactory : Vulkan::InstanceFactory {
|
||||||
|
VkInstance create_instance(const VkInstanceCreateInfo *info) override {
|
||||||
|
handle.setApiVersion({1, 3, 0});
|
||||||
|
QByteArrayList exts;
|
||||||
|
for (int i = 0; i < info->enabledExtensionCount; i++) {
|
||||||
|
exts.push_back(QByteArray::fromStdString(info->ppEnabledExtensionNames[i]));
|
||||||
|
}
|
||||||
|
QByteArrayList layers;
|
||||||
|
for (int i = 0; i < info->enabledLayerCount; i++) {
|
||||||
|
layers.push_back(QByteArray::fromStdString(info->ppEnabledLayerNames[i]));
|
||||||
|
}
|
||||||
|
handle.setExtensions(exts);
|
||||||
|
handle.setLayers(layers);
|
||||||
|
handle.create();
|
||||||
|
|
||||||
|
return handle.vkInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVulkanInstance handle;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RenderWidget final : public QWidget {
|
||||||
|
QVulkanInstance inst;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit RenderWidget();
|
||||||
|
|
||||||
|
std::unique_ptr<QtInstanceFactory> qtVkInstanceFactory;
|
||||||
|
std::shared_ptr<ParallelRDP::WindowInfo> windowInfo;
|
||||||
|
std::shared_ptr<QtWSIPlatform> wsiPlatform;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
class QtPrdpWindowInfo final : public ParallelRDP::WindowInfo {
|
class QtPrdpWindowInfo final : public ParallelRDP::WindowInfo {
|
||||||
public:
|
public:
|
||||||
explicit QtPrdpWindowInfo(QWindow *window) : window(window) {}
|
explicit QtPrdpWindowInfo(QWindow *window) : window(window) {}
|
||||||
CoordinatePair get_window_size() override {
|
CoordinatePair get_window_size() override {
|
||||||
return CoordinatePair{static_cast<float>(window->size().width()), static_cast<float>(window->size().height())};
|
return CoordinatePair{static_cast<float>(window->width()), static_cast<float>(window->height())};
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -27,12 +63,11 @@ class QtWSIPlatform final : public Vulkan::WSIPlatform {
|
|||||||
|
|
||||||
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;
|
|
||||||
auto extensions = window->vulkanInstance()->extensions();
|
auto extensions = window->vulkanInstance()->extensions();
|
||||||
|
|
||||||
vec.resize(extensions.size());
|
vec.resize(extensions.size());
|
||||||
|
|
||||||
for (u32 i = 0; i < extCount; i++) {
|
for (u32 i = 0; i < extensions.size(); i++) {
|
||||||
vec[i] = extensions[i];
|
vec[i] = extensions[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +75,7 @@ class QtWSIPlatform final : public Vulkan::WSIPlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
VkSurfaceKHR create_surface(VkInstance instance, VkPhysicalDevice pDevice) override {
|
VkSurfaceKHR create_surface(VkInstance instance, VkPhysicalDevice pDevice) override {
|
||||||
return (surface = QVulkanInstance::surfaceForWindow(window));
|
return QVulkanInstance::surfaceForWindow(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
void destroy_surface(VkInstance instance, VkSurfaceKHR surface) override {}
|
void destroy_surface(VkInstance instance, VkSurfaceKHR surface) override {}
|
||||||
@@ -61,16 +96,7 @@ class QtWSIPlatform final : public Vulkan::WSIPlatform {
|
|||||||
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};
|
||||||
|
|
||||||
QWindow *window{};
|
QWindow *window{};
|
||||||
VkSurfaceKHR surface;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool gamepadConnected = false;
|
bool gamepadConnected = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
class RenderWidget final : QVulkanWindow {
|
|
||||||
public:
|
|
||||||
explicit RenderWidget(QWindow *);
|
|
||||||
|
|
||||||
std::shared_ptr<ParallelRDP::WindowInfo> windowInfo;
|
|
||||||
std::shared_ptr<QtWSIPlatform> wsiPlatform;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
#include <GeneralSettings.hpp>
|
#include <GeneralSettings.hpp>
|
||||||
#include <Options.hpp>
|
#include <Options.hpp>
|
||||||
#include <log.hpp>
|
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
|
#include <log.hpp>
|
||||||
|
|
||||||
GeneralSettings::GeneralSettings() {
|
GeneralSettings::GeneralSettings() {
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
@@ -18,10 +18,10 @@ GeneralSettings::GeneralSettings() {
|
|||||||
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
|
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
|
||||||
selectedFolderLabel->setText(dir);
|
selectedFolderLabel->setText(dir);
|
||||||
Options::SetSavesPath(dir.toStdString());
|
Options::SetSavesPath(dir.toStdString());
|
||||||
settings.setValue("general/saves_path", dir);
|
settings.setValue("general/savesPath", dir);
|
||||||
});
|
});
|
||||||
|
|
||||||
h = new QHBoxLayout(this);
|
h = new QHBoxLayout();
|
||||||
|
|
||||||
h->addWidget(description);
|
h->addWidget(description);
|
||||||
h->addWidget(selectedFolderLabel);
|
h->addWidget(selectedFolderLabel);
|
||||||
|
|||||||
@@ -4,11 +4,14 @@
|
|||||||
|
|
||||||
SettingsWindow::SettingsWindow() {
|
SettingsWindow::SettingsWindow() {
|
||||||
hide();
|
hide();
|
||||||
QSettings settings;
|
|
||||||
|
general = new GeneralSettings();
|
||||||
|
cpu = new CPUSettings();
|
||||||
|
audio = new AudioSettings();
|
||||||
categories = new QTabWidget(this);
|
categories = new QTabWidget(this);
|
||||||
categories->addTab(&general, "General");
|
categories->addTab(general, "General");
|
||||||
categories->addTab(&cpu, "MIPS VR4300i");
|
categories->addTab(cpu, "MIPS VR4300i");
|
||||||
categories->addTab(&audio, "Audio");
|
categories->addTab(audio, "Audio");
|
||||||
|
|
||||||
v = new QVBoxLayout(this);
|
v = new QVBoxLayout(this);
|
||||||
v->addWidget(categories);
|
v->addWidget(categories);
|
||||||
|
|||||||
@@ -7,14 +7,13 @@
|
|||||||
#include <AudioSettings.hpp>
|
#include <AudioSettings.hpp>
|
||||||
#include <CPUSettings.hpp>
|
#include <CPUSettings.hpp>
|
||||||
|
|
||||||
class SettingsWindow final : QWidget {
|
class SettingsWindow final : public QWidget {
|
||||||
QTabWidget *categories;
|
QTabWidget *categories;
|
||||||
GeneralSettings general;
|
GeneralSettings *general;
|
||||||
AudioSettings audio;
|
AudioSettings *audio;
|
||||||
CPUSettings cpu;
|
CPUSettings *cpu;
|
||||||
QVBoxLayout *v;
|
QVBoxLayout *v;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SettingsWindow();
|
SettingsWindow();
|
||||||
void show() { QWidget::show(); }
|
|
||||||
};
|
};
|
||||||
|
|||||||
+5
-14
@@ -1,23 +1,14 @@
|
|||||||
#include <QCoreApplication>
|
|
||||||
#include <KaizenGui.hpp>
|
#include <KaizenGui.hpp>
|
||||||
#include <cflags.hpp>
|
#include <cflags.hpp>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QApplication>
|
||||||
|
|
||||||
int main(const int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
QCoreApplication::setOrganizationName("kaizen");
|
QCoreApplication::setOrganizationName("kaizen");
|
||||||
QCoreApplication::setOrganizationDomain("irco.sh");
|
QCoreApplication::setOrganizationDomain("irco.sh");
|
||||||
QCoreApplication::setApplicationName("Kaizen");
|
QCoreApplication::setApplicationName("Kaizen");
|
||||||
|
QApplication app(argc, argv);
|
||||||
|
|
||||||
KaizenGui kaizenGui;
|
KaizenGui kaizenGui;
|
||||||
cflags::cflags flags;
|
return app.exec();
|
||||||
flags.add_string_callback(
|
|
||||||
'\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))
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
kaizenGui.run();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user