From b29a0820ffabdfd724c3620d88c4f97376e86b62 Mon Sep 17 00:00:00 2001 From: scawful Date: Wed, 8 Oct 2025 21:51:31 -0400 Subject: [PATCH] feat: Enhance gRPC configuration and add dungeon test harness - Updated gRPC configuration to use version 1.67.1, improving compatibility with modern compilers and fixing MSVC template issues. - Enhanced warning messages for missing vcpkg gRPC installation, providing clearer instructions for faster builds. - Introduced a new dungeon test harness tool to capture and dump the state of WRAM and CPU/PPU registers, aiding in emulator testing and development. - Organized source groups in CMake for better structure and clarity in the input and UI systems. --- cmake/grpc.cmake | 29 ++++- cmake/grpc_windows.cmake | 22 +++- src/CMakeLists.txt | 19 +++ tools/test_helpers/CMakeLists.txt | 13 +- tools/test_helpers/dungeon_test_harness.cc | 138 +++++++++++++++++++++ 5 files changed, 212 insertions(+), 9 deletions(-) create mode 100644 tools/test_helpers/dungeon_test_harness.cc diff --git a/cmake/grpc.cmake b/cmake/grpc.cmake index e5b1538b..4f48e723 100644 --- a/cmake/grpc.cmake +++ b/cmake/grpc.cmake @@ -27,11 +27,25 @@ set(CMAKE_DISABLE_FIND_PACKAGE_gRPC TRUE) # Also prevent pkg-config from finding system packages set(PKG_CONFIG_USE_CMAKE_PREFIX_PATH FALSE) -# Add compiler flags for Clang 15+ compatibility -# gRPC v1.62.0 requires C++17 (std::result_of removed in C++20) +# Add compiler flags for modern compiler compatibility +# These flags are scoped to gRPC and its dependencies only if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - add_compile_options(-Wno-error=missing-template-arg-list-after-template-kw) + # Clang 15+ compatibility for gRPC + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=missing-template-arg-list-after-template-kw") add_compile_definitions(_LIBCPP_ENABLE_CXX20_REMOVED_TYPE_TRAITS) +elseif(MSVC) + # MSVC/Visual Studio compatibility for gRPC templates + # v1.67.1 fixes most issues, but these flags help with large template instantiations + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj") # Large object files + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /permissive-") # Standards conformance + + # Suppress common gRPC warnings on MSVC (don't use add_compile_options to avoid affecting user code) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4267 /wd4244") + + # Increase template instantiation depth for complex promise chains (MSVC 2019+) + if(MSVC_VERSION GREATER_EQUAL 1920) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /constexpr:depth2048") + endif() endif() # Save YAZE's C++ standard and temporarily set to C++17 for gRPC @@ -77,12 +91,15 @@ set(ABSL_PROPAGATE_CXX_STD ON CACHE BOOL "" FORCE) set(ABSL_ENABLE_INSTALL ON CACHE BOOL "" FORCE) set(ABSL_BUILD_TESTING OFF CACHE BOOL "" FORCE) -# Declare gRPC - use v1.62.0 which fixes health_check_client incomplete type bug -# and is compatible with Clang 18 +# Declare gRPC - use v1.67.1 which fixes MSVC template issues and is compatible with modern compilers +# v1.67.1 includes: +# - MSVC/Visual Studio compatibility fixes (template instantiation errors) +# - Clang 18+ compatibility +# - Abseil compatibility updates FetchContent_Declare( grpc GIT_REPOSITORY https://github.com/grpc/grpc.git - GIT_TAG v1.62.0 + GIT_TAG v1.67.1 GIT_PROGRESS TRUE GIT_SHALLOW TRUE USES_TERMINAL_DOWNLOAD TRUE diff --git a/cmake/grpc_windows.cmake b/cmake/grpc_windows.cmake index 9ae4655e..4370b266 100644 --- a/cmake/grpc_windows.cmake +++ b/cmake/grpc_windows.cmake @@ -1,5 +1,13 @@ # Windows-optimized gRPC configuration using vcpkg # This file provides fast gRPC builds on Windows using pre-compiled packages +# +# Benefits: +# - vcpkg build: ~5 minutes (pre-compiled) +# - FetchContent build: ~45 minutes (compile from source) +# +# To use vcpkg (recommended): +# vcpkg install grpc:x64-windows +# cmake -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake .. cmake_minimum_required(VERSION 3.16) @@ -53,8 +61,18 @@ if(WIN32 AND YAZE_USE_VCPKG_GRPC) set(YAZE_GRPC_CONFIGURED TRUE PARENT_SCOPE) return() else() - message(WARNING "vcpkg gRPC not found. Install with: vcpkg install grpc:x64-windows") - message(STATUS "Falling back to FetchContent build (this will be slow on first build)") + message(WARNING "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + message(WARNING " vcpkg gRPC not found") + message(WARNING " For faster builds (5 min vs 45 min), install:") + message(WARNING " ") + message(WARNING " vcpkg install grpc:x64-windows") + message(WARNING " ") + message(WARNING " Then configure with:") + message(WARNING " cmake -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake ..") + message(WARNING " ") + message(WARNING " Falling back to FetchContent (slow but works)") + message(WARNING " Using gRPC v1.67.1 (MSVC-compatible)") + message(WARNING "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") endif() endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a0852df9..420cfd55 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,6 +12,9 @@ set( app/emu/debug/disassembly_viewer.cc app/emu/debug/breakpoint_manager.cc app/emu/debug/watchpoint_manager.cc + app/emu/input/input_backend.cc + app/emu/input/input_manager.cc + app/emu/ui/input_handler.cc app/emu/cpu/cpu.cc app/emu/video/ppu.cc app/emu/memory/dma.cc @@ -924,6 +927,22 @@ source_group("Application\\Emulator\\Video" FILES app/emu/video/ppu_registers.h ) +# Input System (abstracted for SDL2/SDL3) +source_group("Application\\Emulator\\Input" FILES + app/emu/input/input_backend.cc + app/emu/input/input_backend.h + app/emu/input/input_manager.cc + app/emu/input/input_manager.h +) + +# Emulator UI System +source_group("Application\\Emulator\\UI" FILES + app/emu/ui/input_handler.cc + app/emu/ui/input_handler.h + app/emu/ui/emulator_ui.h + app/emu/ui/debugger_ui.h +) + # Debug System source_group("Application\\Emulator\\Debug" FILES app/emu/debug/apu_debugger.cc diff --git a/tools/test_helpers/CMakeLists.txt b/tools/test_helpers/CMakeLists.txt index 07f5595b..6c40884b 100644 --- a/tools/test_helpers/CMakeLists.txt +++ b/tools/test_helpers/CMakeLists.txt @@ -28,11 +28,22 @@ target_link_libraries(rom_patch_utility ${CMAKE_THREAD_LIBS_INIT} ) +# Add dungeon_test_harness tool +add_executable(dungeon_test_harness + dungeon_test_harness.cc +) + +target_link_libraries(dungeon_test_harness + yaze_core + ${CMAKE_THREAD_LIBS_INIT} +) + # Windows stack size configuration for helper tools set(HELPER_TOOLS overworld_golden_data_extractor extract_vanilla_values rom_patch_utility + dungeon_test_harness ) foreach(TOOL ${HELPER_TOOLS}) @@ -48,6 +59,6 @@ foreach(TOOL ${HELPER_TOOLS}) endforeach() # Install tools to bin directory -install(TARGETS overworld_golden_data_extractor extract_vanilla_values rom_patch_utility +install(TARGETS overworld_golden_data_extractor extract_vanilla_values rom_patch_utility dungeon_test_harness DESTINATION bin ) diff --git a/tools/test_helpers/dungeon_test_harness.cc b/tools/test_helpers/dungeon_test_harness.cc new file mode 100644 index 00000000..940022c9 --- /dev/null +++ b/tools/test_helpers/dungeon_test_harness.cc @@ -0,0 +1,138 @@ +#include +#include +#include +#include + +#include "app/emu/snes.h" +#include "app/rom.h" + +using namespace yaze; + +/** + * @brief Dumps the state of WRAM and CPU/PPU registers after initialization. + * + * This tool runs the SNES emulator from the Reset vector until it hits the + * main game loop. It then dumps the entire state of WRAM and relevant registers + * to a header file. This captured state can be used to initialize the emulator + * for test harnesses, providing a realistic environment for executing native + * game code. + */ +class DungeonTestHarness { + public: + explicit DungeonTestHarness(const std::string& rom_path) + : rom_path_(rom_path) {} + + absl::Status GenerateHarnessState(const std::string& output_path) { + // Load ROM + Rom rom; + RETURN_IF_ERROR(rom.LoadFromFile(rom_path_)); + auto rom_data = rom.vector(); + + // Initialize SNES + emu::Snes snes; + snes.Init(rom_data); + snes.Reset(false); + + auto& cpu = snes.cpu(); + auto& ppu = snes.ppu(); + + // Run emulator until the main game loop is reached + int max_cycles = 5000000; // 5 million cycles should be plenty + int cycles = 0; + while (cycles < max_cycles) { + snes.RunCycle(); + cycles++; + if (cpu.PB == 0x00 && cpu.PC == 0x8034) { + break; // Reached MainGameLoop + } + } + + if (cycles >= max_cycles) { + return absl::InternalError("Emulator timed out; did not reach main game loop."); + } + + std::ofstream out_file(output_path); + if (!out_file.is_open()) { + return absl::InternalError("Failed to open output file: " + output_path); + } + + // Write header + out_file << "// =============================================================================" << std::endl; + out_file << "// YAZE Dungeon Test Harness State - Generated from: " << rom_path_ << std::endl; + out_file << "// Generated on: " << __DATE__ << " " << __TIME__ << std::endl; + out_file << "// =============================================================================" << std::endl; + out_file << std::endl; + out_file << "#pragma once" << std::endl; + out_file << std::endl; + out_file << "#include " << std::endl; + out_file << "#include " << std::endl; + out_file << std::endl; + out_file << "namespace yaze {" << std::endl; + out_file << "namespace emu {" << std::endl; + out_file << std::endl; + + // Write WRAM state + out_file << "constexpr std::array kInitialWRAMState = {{" << std::endl; + for (int i = 0; i < 0x20000; ++i) { + if (i % 16 == 0) out_file << " "; + out_file << "0x" << std::hex << std::setw(2) << std::setfill('0') + << static_cast(snes.Read(0x7E0000 + i)); + if (i < 0x1FFFF) out_file << ", "; + if (i % 16 == 15) out_file << std::endl; + } + out_file << "}};" << std::endl << std::endl; + + // Write CPU/PPU register state + out_file << "// =============================================================================" << std::endl; + out_file << "// Initial Register States" << std::endl; + out_file << "// =============================================================================" << std::endl; + out_file << std::endl; + + out_file << "struct InitialPpuState {" << std::endl; + out_file << " uint8_t inidisp = 0x" << std::hex << ppu.Read(0x2100, false) << ";" << std::endl; + out_file << " uint8_t objsel = 0x" << std::hex << ppu.Read(0x2101, false) << ";" << std::endl; + out_file << " uint8_t bgmode = 0x" << std::hex << ppu.Read(0x2105, false) << ";" << std::endl; + out_file << " uint8_t mosaic = 0x" << std::hex << ppu.Read(0x2106, false) << ";" << std::endl; + out_file << " uint8_t tm = 0x" << std::hex << ppu.Read(0x212C, false) << ";" << std::endl; + out_file << " uint8_t ts = 0x" << std::hex << ppu.Read(0x212D, false) << ";" << std::endl; + out_file << " uint8_t cgwsel = 0x" << std::hex << ppu.Read(0x2130, false) << ";" << std::endl; + out_file << " uint8_t cgadsub = 0x" << std::hex << ppu.Read(0x2131, false) << ";" << std::endl; + out_file << " uint8_t setini = 0x" << std::hex << ppu.Read(0x2133, false) << ";" << std::endl; + out_file << "};" << std::endl << std::endl; + + out_file << "} // namespace emu" << std::endl; + out_file << "} // namespace yaze" << std::endl; + + return absl::OkStatus(); + } + + private: + std::string rom_path_; +}; + +int main(int argc, char* argv[]) { + if (argc != 3) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return 1; + } + + std::string rom_path = argv[1]; + std::string output_path = argv[2]; + + if (!std::filesystem::exists(rom_path)) { + std::cerr << "Error: ROM file not found: " << rom_path << std::endl; + return 1; + } + + DungeonTestHarness harness(rom_path); + auto status = harness.GenerateHarnessState(output_path); + + if (status.ok()) { + std::cout << "Successfully generated dungeon harness state from " << rom_path + << " to " << output_path << std::endl; + return 0; + } else { + std::cerr << "Error generating harness state: " << status.message() << std::endl; + return 1; + } +}