diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 1f21057..70d18a4 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -15,7 +15,7 @@ jobs: # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix runs-on: ubuntu-latest container: - image: mattgomes28/jammy-cpp:0.0.0 + image: mattgomes28/jammy-cpp:0.0.1 options: --user 1001 steps: diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e3929e..8f50164 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,7 @@ option(BUILD_PROFILER "Build profiling for the instruction handlers") add_subdirectory(emulator) add_subdirectory(emulator_app) +add_subdirectory(ui) if (BUILD_PROFILER) add_subdirectory(profiler) diff --git a/CMakePresets.json b/CMakePresets.json index a9544ef..7d70259 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -36,11 +36,11 @@ "hidden": true, "generator": "Ninja", "cacheVariables": { - "CMAKE_C_COMPILER": "clang-17", - "CMAKE_CXX_COMPILER": "clang++-17", + "CMAKE_C_COMPILER": "clang-19", + "CMAKE_CXX_COMPILER": "clang++-19", "CMAKE_CXX_FLAGS_INIT": "$env{CMAKE_CXX_FLAGS} -Werror -Wall -Wextra -pedantic", "CMAKE_CXX_SCAN_FOR_MODULES": true, - "CMAKE_CXX_COMPILER_CLANG_SCAN_DEPS": "/usr/bin/clang-scan-deps-17" + "CMAKE_CXX_COMPILER_CLANG_SCAN_DEPS": "/usr/bin/clang-scan-deps-19" } }, { @@ -115,7 +115,7 @@ "binaryDir": "${sourceDir}/build/unix-deb-ninja", "inherits": ["unix-ninja", "deb"], "cacheVariables": { - "CMAKE_CXX_FLAGS": "-O0 --coverage -g -fsanitize=address", + "CMAKE_CXX_FLAGS": "-O0 -g -fsanitize=address", "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/unix-deb-ninja/install", "CLOCK_SPEED_MHZ": "3.58", "BUILD_PROFILER": true diff --git a/dependencies/CMakeLists.txt b/dependencies/CMakeLists.txt index 6f19f99..797a973 100644 --- a/dependencies/CMakeLists.txt +++ b/dependencies/CMakeLists.txt @@ -1,9 +1,9 @@ file( DOWNLOAD - https://github.com/cpm-cmake/CPM.cmake/releases/download/v0.40.2/CPM.cmake + https://github.com/cpm-cmake/CPM.cmake/releases/download/v0.40.5/CPM.cmake ${CMAKE_CURRENT_BINARY_DIR}/cmake/CPM.cmake - EXPECTED_HASH SHA256=c8cdc32c03816538ce22781ed72964dc864b2a34a310d3b7104812a5ca2d835d + EXPECTED_HASH SHA256=c46b876ae3b9f994b4f05a4c15553e0485636862064f1fcc9d8b4f832086bc5d ) include(${CMAKE_CURRENT_BINARY_DIR}/cmake/CPM.cmake) @@ -14,6 +14,55 @@ CPMAddPackage( VERSION 5.0 ) +CPMAddPackage( + NAME raylib_imgui + GITHUB_REPOSITORY raylib-extras/rlImGui + GIT_TAG 583d4fea67e67d431319974f0625f680d3840dfb + VERSION 583d4fea67e67d431319974f0625f680d3840dfb +) + +add_library(raylib_imgui) +add_library(raylib::imgui ALIAS raylib_imgui) +target_sources(raylib_imgui PRIVATE + ${raylib_imgui_SOURCE_DIR}/rlImGui.cpp + ${raylib_imgui_SOURCE_DIR}/rlImGui.h + ${raylib_imgui_SOURCE_DIR}/rlImGuiColors.h) +target_include_directories(raylib_imgui PUBLIC ${raylib_imgui_SOURCE_DIR}) +target_link_libraries(raylib_imgui PUBLIC raylib::raylib imgui::imgui) + +CPMAddPackage( + NAME imgui + VERSION 1.91.1 + GITHUB_REPOSITORY ocornut/imgui + DOWNLOAD_ONLY TRUE +) + +# CMakeLists.txt from https://gist.githubusercontent.com/rokups/f771217b2d530d170db5cb1e08e9a8f4 +file( + DOWNLOAD + "https://gist.githubusercontent.com/rokups/f771217b2d530d170db5cb1e08e9a8f4/raw/4c2c14374ab878ca2f45daabfed4c156468e4e27/CMakeLists.txt" + "${imgui_SOURCE_DIR}/CMakeLists.txt" + EXPECTED_HASH SHA256=fd62f69364ce13a4f7633a9b50ae6672c466bcc44be60c69c45c0c6e225bb086 +) +# Options +set(IMGUI_EXAMPLES FALSE) +set(IMGUI_DEMO FALSE) +set(IMGUI_ENABLE_STDLIB_SUPPORT TRUE) +# FreeType (https://github.com/cpm-cmake/CPM.cmake/wiki/More-Snippets#freetype) +set(FREETYPE_FOUND TRUE) +set(FREETYPE_INCLUDE_DIRS "") +set(FREETYPE_LIBRARIES Freetype::Freetype) + +file(GLOB IMGUI_SRC + ${imgui_SOURCE_DIR}/*.c + ${imgui_SOURCE_DIR}/*.cpp +) + +add_library(imgui) +add_library(imgui::imgui ALIAS imgui) +target_sources(imgui PRIVATE ${IMGUI_SRC}) +target_include_directories(imgui PUBLIC ${imgui_SOURCE_DIR}) + CPMAddPackage( NAME googletest GITHUB_REPOSITORY google/googletest @@ -32,6 +81,7 @@ CPMAddPackage( GITHUB_REPOSITORY fmtlib/fmt ) + # Aliases add_library(gtest::gtest ALIAS gtest) add_library(gtest::gtest_main ALIAS gtest_main) diff --git a/docker/Dockerfile b/docker/Dockerfile index 711b512..f79b605 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -32,7 +32,7 @@ RUN apt-get update \ && apt-get autoremove --purge -y \ && rm -rf /var/lib/apt/lists/* -# Install the LLVM 17 tools +# Install the LLVM 19 tools RUN apt-get update \ && apt-get install -y --no-install-recommends --no-install-suggests \ gnupg \ @@ -41,17 +41,17 @@ RUN apt-get update \ wget \ && wget https://apt.llvm.org/llvm.sh \ && chmod +x llvm.sh \ - && ./llvm.sh 17 \ + && ./llvm.sh 19 \ && apt-get install -y --no-install-recommends --no-install-suggests \ - clang-format-17 \ - clang-tidy-17 \ - clang-tools-17 \ - libclang-rt-17-dev \ - && update-alternatives --install /usr/bin/clang clang /usr/bin/clang-17 10 \ - && update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-17 10 \ - && update-alternatives --install /usr/bin/clang-scan-deps clang-scan-deps /usr/bin/clang-scan-deps-17 10 \ - && update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-17 10 \ - && update-alternatives --install /usr/bin/run-clang-tidy run-clang-tidy /usr/bin/run-clang-tidy-17 10 \ + clang-format-19 \ + clang-tidy-19 \ + clang-tools-19 \ + libclang-rt-19-dev \ + && update-alternatives --install /usr/bin/clang clang /usr/bin/clang-19 10 \ + && update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-19 10 \ + && update-alternatives --install /usr/bin/clang-scan-deps clang-scan-deps /usr/bin/clang-scan-deps-19 10 \ + && update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-19 10 \ + && update-alternatives --install /usr/bin/run-clang-tidy run-clang-tidy /usr/bin/run-clang-tidy-19 10 \ && rm llvm.sh \ && apt-get autoremove -y \ gnupg \ diff --git a/emulator/CMakeLists.txt b/emulator/CMakeLists.txt index 1106f67..72c1dcc 100644 --- a/emulator/CMakeLists.txt +++ b/emulator/CMakeLists.txt @@ -20,3 +20,4 @@ else() endif() target_link_libraries(emulator PRIVATE fmt::fmt) +add_library(emulator::emulator ALIAS emulator) diff --git a/emulator/emulator.cpp b/emulator/emulator.cpp index 6637cd3..624006a 100644 --- a/emulator/emulator.cpp +++ b/emulator/emulator.cpp @@ -1,8 +1,6 @@ module; -// #ifdef BUILD_PROFILER -import profiler; -// #endif // BUILD_PROFILER +#include #include #include @@ -20,16 +18,17 @@ import profiler; #include #include -#include - #ifndef CLOCK_SPEED_MHZ #define CLOCK_SPEED_MHZ 1.79 #endif // CLOCK_SPEED_MHZ -#include export module emulator; +#ifdef BUILD_PROFILER +import profiler; +#endif // BUILD_PROFILER + // Define a macro for the profiler #ifdef BUILD_PROFILER struct ProfileBook @@ -62,9 +61,9 @@ export namespace emulator class OpcodeNotSupported : public std::exception { public: - OpcodeNotSupported(std::string opcode) : _error_msg{"opcode not supported: " + opcode} {} + OpcodeNotSupported(std::string const& opcode) : _error_msg{"opcode not supported: " + opcode} {} - const char* what() const throw() override + auto what() const noexcept -> const char* override { return _error_msg.c_str(); } @@ -93,7 +92,7 @@ export namespace emulator std::uint8_t sp{0xff}; }; - bool operator==(Flags const& lhs, Flags const& rhs) + auto operator==(Flags const& lhs, Flags const& rhs) -> bool { return lhs.n == rhs.n && lhs.v == rhs.v && lhs.b == rhs.b && lhs.d == rhs.d && lhs.i == rhs.i && lhs.z == rhs.z && lhs.c == rhs.c; @@ -114,7 +113,7 @@ export namespace emulator // Clock speed for this particular CPU double clock_speed = CLOCK_SPEED_MHZ; - std::uint8_t sr() const + auto sr() const -> std::uint8_t { // clang-format off std::uint8_t const sp_val = @@ -174,7 +173,7 @@ using Instruction = std::function(emulator::Cpu /// @param cpu is the cpu object to operate on /// @param value is the zeropage address to add the index to /// @param index is the register to use as the index add value -inline std::uint16_t zeropage_indexed(emulator::Cpu& cpu, std::uint8_t value, std::uint8_t emulator::Registers::*index) +inline std::uint16_t zeropage_indexed(emulator::Cpu& cpu, std::uint8_t value, std::uint8_t emulator::Registers::* index) { auto const masked = ((cpu.reg).*index + value) & 0xff; return static_cast(masked); @@ -188,7 +187,7 @@ inline std::uint16_t zeropage_indexed(emulator::Cpu& cpu, std::uint8_t value, st /// @param index is the pointer to the register to use as the index add. /// @return the resolved target address. inline std::uint16_t absolute_indexed( - emulator::Cpu& cpu, std::uint8_t lsb, std::uint8_t hsb, std::uint8_t emulator::Registers::*index) + emulator::Cpu& cpu, std::uint8_t lsb, std::uint8_t hsb, std::uint8_t emulator::Registers::* index) { // Do we want to put these numbers as std::uint16_t? auto const address = (hsb << 8) | lsb; @@ -659,7 +658,7 @@ std::optional asl_absolute_indexed(emulator::Cpu& cpu, std::s /* End bit shift/rotation functions */ /* Flag setting opcodes */ -Instruction set_flag(bool emulator::Flags::*f) +Instruction set_flag(bool emulator::Flags::* f) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { @@ -671,7 +670,7 @@ Instruction set_flag(bool emulator::Flags::*f) /* End of flag setting opcodes */ /* Flag clearning operation */ -Instruction clear_flag(bool emulator::Flags::*f) +Instruction clear_flag(bool emulator::Flags::* f) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { @@ -682,7 +681,7 @@ Instruction clear_flag(bool emulator::Flags::*f) } /* End of flag clearning operations */ -Instruction ld_immediate(std::uint8_t emulator::Registers::*reg) +Instruction ld_immediate(std::uint8_t emulator::Registers::* reg) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { @@ -701,7 +700,7 @@ Instruction ld_immediate(std::uint8_t emulator::Registers::*reg) }; } -Instruction ld_zeropage(std::uint8_t emulator::Registers::*reg) +Instruction ld_zeropage(std::uint8_t emulator::Registers::* reg) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { @@ -730,7 +729,7 @@ Instruction ld_zeropage(std::uint8_t emulator::Registers::*reg) /// @param add the register to use as the index add. /// @return Instruction containing the number of bytes consumed from the /// program and the cycles taken. -Instruction ld_zeropage_indexed(std::uint8_t emulator::Registers::*to, std::uint8_t emulator::Registers::*add) +Instruction ld_zeropage_indexed(std::uint8_t emulator::Registers::* to, std::uint8_t emulator::Registers::* add) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { @@ -752,7 +751,7 @@ Instruction ld_zeropage_indexed(std::uint8_t emulator::Registers::*to, std::uint }; } -Instruction ld_absolute(std::uint8_t emulator::Registers::*to) +Instruction ld_absolute(std::uint8_t emulator::Registers::* to) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { @@ -775,7 +774,7 @@ Instruction ld_absolute(std::uint8_t emulator::Registers::*to) }; } -Instruction ld_absolute_plus_reg(std::uint8_t emulator::Registers::*to, std::uint8_t emulator::Registers::*add) +Instruction ld_absolute_plus_reg(std::uint8_t emulator::Registers::* to, std::uint8_t emulator::Registers::* add) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { @@ -798,7 +797,7 @@ Instruction ld_absolute_plus_reg(std::uint8_t emulator::Registers::*to, std::uin // This is basically zeropage + x, but an extra indirection with // the value ad zeropage + x as a position -Instruction ld_index_indirect(std::uint8_t emulator::Registers::*to, std::uint8_t emulator::Registers::*add) +Instruction ld_index_indirect(std::uint8_t emulator::Registers::* to, std::uint8_t emulator::Registers::* add) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { @@ -824,7 +823,7 @@ Instruction ld_index_indirect(std::uint8_t emulator::Registers::*to, std::uint8_ }; } -Instruction ld_indirect_index(std::uint8_t emulator::Registers::*to) +Instruction ld_indirect_index(std::uint8_t emulator::Registers::* to) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { @@ -978,7 +977,7 @@ std::optional dec_abs_indexed(emulator::Cpu& cpu, std::span /* program */) { @@ -990,7 +989,7 @@ Instruction inc_reg(std::uint8_t emulator::Registers::*reg) }; } -Instruction dec_reg(std::uint8_t emulator::Registers::*reg) +Instruction dec_reg(std::uint8_t emulator::Registers::* reg) { return [=](emulator::Cpu& cpu, std::span /* program */) { @@ -1002,7 +1001,7 @@ Instruction dec_reg(std::uint8_t emulator::Registers::*reg) }; } -Instruction transfer_regs(std::uint8_t emulator::Registers::*from, std::uint8_t emulator::Registers::*to) +Instruction transfer_regs(std::uint8_t emulator::Registers::* from, std::uint8_t emulator::Registers::* to) { return [=](emulator::Cpu& cpu, std::span /* program */) { @@ -1023,7 +1022,7 @@ std::optional txa(emulator::Cpu& cpu, std::span(1); } -Instruction st_indirect(std::uint8_t emulator::Registers::*from, std::uint8_t emulator::Registers::*add) +Instruction st_indirect(std::uint8_t emulator::Registers::* from, std::uint8_t emulator::Registers::* add) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { @@ -1047,7 +1046,7 @@ Instruction st_indirect(std::uint8_t emulator::Registers::*from, std::uint8_t em }; } -Instruction st_zeropage(std::uint8_t emulator::Registers::*from) +Instruction st_zeropage(std::uint8_t emulator::Registers::* from) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { @@ -1070,7 +1069,7 @@ Instruction st_zeropage(std::uint8_t emulator::Registers::*from) /// arguments. /// @param from is the register containing the value to be stored in memory. /// @param index is the register used as the index (i.e. X or Y mostly). -Instruction st_zeropage_indexed(std::uint8_t emulator::Registers::*from, std::uint8_t emulator::Registers::*index) +Instruction st_zeropage_indexed(std::uint8_t emulator::Registers::* from, std::uint8_t emulator::Registers::* index) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { @@ -1088,7 +1087,7 @@ Instruction st_zeropage_indexed(std::uint8_t emulator::Registers::*from, std::ui }; } -Instruction sta_absolute_indexed(std::uint8_t emulator::Registers::*index) +Instruction sta_absolute_indexed(std::uint8_t emulator::Registers::* index) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { @@ -1107,7 +1106,7 @@ Instruction sta_absolute_indexed(std::uint8_t emulator::Registers::*index) }; } -Instruction st_absolute(std::uint8_t emulator::Registers::*from) +Instruction st_absolute(std::uint8_t emulator::Registers::* from) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { @@ -1140,7 +1139,7 @@ std::optional sta_index_indirect(emulator::Cpu& cpu, std::spa } // Compare instructions here -void cmp_operation(emulator::Cpu& cpu, std::uint8_t emulator::Registers::*reg, std::uint8_t val) +void cmp_operation(emulator::Cpu& cpu, std::uint8_t emulator::Registers::* reg, std::uint8_t val) { auto const comparison = (cpu.reg).*reg - val; cpu.flags.n = comparison & 0b1000'0000; // Negative flag @@ -1150,7 +1149,7 @@ void cmp_operation(emulator::Cpu& cpu, std::uint8_t emulator::Registers::*reg, s /// Compares whichever register was given to the immediate /// value in the next address in the program array -Instruction cmp_immediate_reg(std::uint8_t emulator::Registers::*reg) +Instruction cmp_immediate_reg(std::uint8_t emulator::Registers::* reg) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { @@ -1165,7 +1164,7 @@ Instruction cmp_immediate_reg(std::uint8_t emulator::Registers::*reg) }; } -Instruction cmp_zeropage_reg(std::uint8_t emulator::Registers::*reg) +Instruction cmp_zeropage_reg(std::uint8_t emulator::Registers::* reg) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { @@ -1195,7 +1194,7 @@ std::optional cmp_zp_indexed(emulator::Cpu& cpu, std::span(2, 4); } -Instruction cmp_absolute(std::uint8_t emulator::Registers::*reg) +Instruction cmp_absolute(std::uint8_t emulator::Registers::* reg) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { @@ -1215,7 +1214,7 @@ Instruction cmp_absolute(std::uint8_t emulator::Registers::*reg) }; } -Instruction cmp_abs_indexed(std::uint8_t emulator::Registers::*index) +Instruction cmp_abs_indexed(std::uint8_t emulator::Registers::* index) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { @@ -1317,7 +1316,7 @@ std::optional bne(emulator::Cpu& cpu, std::span -Instruction branch_flag_value(bool emulator::Flags::*flag) +Instruction branch_flag_value(bool emulator::Flags::* flag) { // TODO : Do we need a return here? return [=](emulator::Cpu& cpu, std::span program) -> std::optional @@ -1413,7 +1412,7 @@ std::optional eor_acc_absolute(emulator::Cpu& cpu, std::span(cpu, cpu.mem[addr]); } -Instruction eor_acc_absolute_plus_reg(std::uint8_t emulator::Registers::*reg) +Instruction eor_acc_absolute_plus_reg(std::uint8_t emulator::Registers::* reg) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { @@ -1504,7 +1503,7 @@ std::optional and_acc_absolute(emulator::Cpu& cpu, std::span(cpu, cpu.mem[addr]); } -Instruction and_acc_absolute_plus_reg(std::uint8_t emulator::Registers::*reg) +Instruction and_acc_absolute_plus_reg(std::uint8_t emulator::Registers::* reg) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { @@ -1595,7 +1594,7 @@ std::optional or_acc_absolute(emulator::Cpu& cpu, std::span(cpu, cpu.mem[addr]); } -Instruction or_acc_absolute_plus_reg(std::uint8_t emulator::Registers::*reg) +Instruction or_acc_absolute_plus_reg(std::uint8_t emulator::Registers::* reg) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { diff --git a/emulator_app/CMakeLists.txt b/emulator_app/CMakeLists.txt index 31506d0..697893b 100644 --- a/emulator_app/CMakeLists.txt +++ b/emulator_app/CMakeLists.txt @@ -2,6 +2,7 @@ add_executable(emulator_app main.cpp) target_link_libraries(emulator_app PRIVATE - emulator + emulator::emulator + emulator::ui fmt::fmt - raylib) + raylib::imgui) diff --git a/emulator_app/main.cpp b/emulator_app/main.cpp index 5bd1abf..d994867 100644 --- a/emulator_app/main.cpp +++ b/emulator_app/main.cpp @@ -1,5 +1,4 @@ -import emulator; - +#include #include #include #include @@ -9,40 +8,271 @@ import emulator; #include #include +#include #include +#include + +import emulator; +import register_table_ui; // make these names better +import flag_table_ui; +import common_ui; namespace { - static constexpr std::array colour_table{{ - {0, 0, 0, 255}, // Black - {255, 255, 255, 255}, // White - {136, 0, 0, 255}, // Dark Red - {170, 255, 238, 255}, // Light Cyan - {204, 68, 204, 255}, // Violet - {0, 204, 85, 255}, // Light Green - {0, 0, 170, 255}, // Blue - {255, 241, 224, 255}, {221, 136, 85, 255}, // Light Brown - {102, 68, 0, 255}, // Dark Brown - {255, 119, 119, 255}, // Light Red - {51, 51, 51, 255}, // Dark Grey - {119, 119, 119, 255}, // Light Grey - {170, 255, 102, 255}, // Light Lime - {0, 136, 255, 255}, // Light Blue - {187, 187, 187, 255} // Grey + constexpr std::array colour_table{{ + {.r = 0, .g = 0, .b = 0, .a = 255}, // Black + {.r = 255, .g = 255, .b = 255, .a = 255}, // White + {.r = 136, .g = 0, .b = 0, .a = 255}, // Dark Red + {.r = 170, .g = 255, .b = 238, .a = 255}, // Light Cyan + {.r = 204, .g = 68, .b = 204, .a = 255}, // Violet + {.r = 0, .g = 204, .b = 85, .a = 255}, // Light Green + {.r = 0, .g = 0, .b = 170, .a = 255}, // Blue + {.r = 255, .g = 241, .b = 224, .a = 255}, // Something close to white + {.r = 221, .g = 136, .b = 85, .a = 255}, // Light Brown + {.r = 102, .g = 68, .b = 0, .a = 255}, // Dark Brown + {.r = 255, .g = 119, .b = 119, .a = 255}, // Light Red + {.r = 51, .g = 51, .b = 51, .a = 255}, // Dark Grey + {.r = 119, .g = 119, .b = 119, .a = 255}, // Light Grey + {.r = 170, .g = 255, .b = 102, .a = 255}, // Light Lime + {.r = 0, .g = 136, .b = 255, .a = 255}, // Light Blue + {.r = 187, .g = 187, .b = 187, .a = 255} // Grey }}; -} + + enum class ScrollDriver : std::uint8_t + { + Left, + Right + }; + + + void draw_memory_view(emulator::Cpu& cpu) + { + static constexpr float offset_view_width = 75.0f; + static constexpr float offset_view_height = 200.0f; + int constexpr num_columns = 16; + int constexpr num_rows = 16; + + // Keep the previous value of the scrollbar + static float scroll_value = 0.0f; + static auto scroll_driver = ScrollDriver::Left; + + /**************************************************** + Memory Offset View + ***************************************************/ + ImGui::BeginChild("Offset View", ImVec2(offset_view_width, offset_view_height), ImGuiChildFlags_Borders, + ImGuiWindowFlags_MenuBar); + if (ImGui::BeginMenuBar()) + { + ImGui::TextUnformatted("Offset"); + ImGui::EndMenuBar(); + } + + if (ImGui::BeginTable("MemoryOffsetTable", 1, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) + { + // Fill the table with data + for (int row = 0; row < num_rows; row++) + { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + std::string const offset_str = fmt::format("0x{:02x}", row * 16); + ImGui::Text("%s", offset_str.c_str()); + } + + ImGui::EndTable(); + } + + + if (ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_NoPopupHierarchy)) + { + scroll_driver = ScrollDriver::Left; + } + + + if (scroll_driver == ScrollDriver::Left) + { + auto const left_scroll_value = ImGui::GetScrollY(); + if (scroll_value != left_scroll_value) + { + scroll_value = left_scroll_value; + } + } + else + { + ImGui::SetScrollY(scroll_value); + } + + // override scroll progress if not in focus + // if (left_scroll_value != scroll_value) { + // ImGui::SetScrollY(scroll_value); + // } + + ImGui::EndChild(); + ImGui::SameLine(); + + /**************************************************** + Memory Contents View + ***************************************************/ + auto const available_size = ImGui::GetContentRegionAvail(); + ImGui::BeginChild("ScrollingRegion", ImVec2(available_size.x, offset_view_height), true, + ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_MenuBar); + + // Menu top bar + if (ImGui::BeginMenuBar()) + { + ImGui::TextUnformatted("Memory Contents"); + ImGui::EndMenuBar(); + } + + if (ImGui::BeginTable("TableWithoutHeader", num_columns, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) + { + // Fill the table with data + for (int row = 0; row < num_rows; row++) + { + ImGui::TableNextRow(); + for (int col = 0; col < num_columns; col++) + { + ImGui::TableSetColumnIndex(col); + auto const data = fmt::format("0x{:02x}", cpu.mem[(row * num_columns) + col]); + ImGui::Text("%s", data.c_str()); + } + } + + ImGui::EndTable(); + } + + ImGui::IsItemActive(); + if (ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_NoPopupHierarchy)) + { + scroll_driver = ScrollDriver::Right; + } + + if (scroll_driver == ScrollDriver::Right) + { + auto const right_scroll_value = ImGui::GetScrollY(); + if (right_scroll_value != scroll_value) + { + scroll_value = right_scroll_value; + } + } + else + { + ImGui::SetScrollY(scroll_value); + } + + + ImGui::EndChild(); + } + + + void draw_control_buttons(emulator::Cpu& cpu) + { + auto const button_box_height = 100.0f; + + ImGui::BeginChild("ControlButtonView", ImVec2(0, button_box_height), true, ImGuiWindowFlags_NoDecoration); + + // We want to set the cursor position to roughly + // where we would end up centering the buttons + auto const current_pos = ImGui::GetCursorPosX(); + auto const available_size = ImGui::GetContentRegionAvail(); + ImGui::SetCursorPosX(current_pos + ((available_size.x - 230) * 0.5)); + + auto const button_size = ImVec2(70, 20); + if (ImGui::Button("Pause", button_size)) + { + // TODO : pause the running emulation + } + ImGui::SameLine(); + if (ImGui::Button("Step", button_size)) + { + // TODO : step through an instruction + } + ImGui::SameLine(); + if (ImGui::Button("Continue", button_size)) + { + // TODO : continue running the program + } + + ImGui::EndChild(); + } +} // namespace // TODO mutex to protect memory -bool draw(emulator::Cpu& cpu) +auto draw(emulator::Cpu& cpu) -> bool { + // before your game loop InitWindow(512, 512, "6502 Graphics"); + SetTargetFPS(30); + rlImGuiSetup(true); // sets up ImGui with ether a dark or light default theme + + bool window_open = true; while (!WindowShouldClose()) { BeginDrawing(); ClearBackground(BLACK); + rlImGuiBegin(); + + ImGui::ShowDemoWindow(); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(745, 345)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGui::Begin("Emulator", &window_open); + + + /*************************************************** + * Drawing the top tables for registers and flags * + ***************************************************/ + float const margin_x = 50.0f; + float const table_width = (ImGui::GetContentRegionAvail().x - margin_x) * 0.5f; + float const table_height = (ImGui::GetContentRegionAvail().y * 0.25f); // Adjust table height as needed + + ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(margin_x, 0)); + + { // table one + ImGui::BeginChild("LeftTable", ImVec2(table_width, table_height), true); + emulator::ui::draw_flag_table(cpu); + ImGui::EndChild(); + } + + ImGui::SameLine(); + + // Pop item spacing only + ImGui::PopStyleVar(1); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + + { // table two + ImGui::BeginChild("RightTable", ImVec2(table_width, table_height), true); + emulator::ui::draw_register_table(cpu); + ImGui::EndChild(); + } + ImGui::PopStyleVar(6); + + + /*************************************************** + * Drawing the scrollable memory view * + ***************************************************/ + draw_memory_view(cpu); + + /*************************************************** + * Drawing the control buttons at the bottom * + ***************************************************/ + draw_control_buttons(cpu); + + ImGui::BeginChild("LeftTable", ImVec2(table_width, table_height), true); + ImGui::BeginGroup(); + { // left panel with the offsets + } + ImGui::EndGroup(); + ImGui::EndChild(); + + ImGui::End(); + + // 0200 - 05FF : for (int i = 0x0200; i < 0x0600; ++i) { @@ -55,16 +285,18 @@ bool draw(emulator::Cpu& cpu) auto const col = rel_pos % 32; DrawRectangle(col * 16, row * 16, 16, 16, colour); } + rlImGuiEnd(); EndDrawing(); // Make sure we only draw at most 30 times per second std::this_thread::sleep_for(std::chrono::milliseconds(1000 / 30)); } - + rlImGuiShutdown(); + CloseWindow(); return true; } -int main(int argc, char** argv) +auto main(int argc, char** argv) -> int { if (argc < 2) { @@ -76,8 +308,8 @@ int main(int argc, char** argv) std::cout << fmt::format("The clock speed was set to {}\n", easy65k.clock_speed); - auto const filename = argv[1]; - auto file = std::ifstream{filename}; + auto* const filename = argv[1]; + auto file = std::ifstream{filename}; std::vector program_contents{(std::istreambuf_iterator(file)), std::istreambuf_iterator()}; diff --git a/profiler/profiler.cpp b/profiler/profiler.cpp index 653be59..c1ec139 100644 --- a/profiler/profiler.cpp +++ b/profiler/profiler.cpp @@ -17,9 +17,7 @@ export namespace profiler template concept Bookeper = std::convertible_to && std::convertible_to && requires(T v, FuncName func_name, Measure measure) { - { - v.update(func_name, measure) - } -> std::same_as; + { v.update(func_name, measure) } -> std::same_as; }; template diff --git a/tests/plp_tests.cpp b/tests/plp_tests.cpp index cf77039..07fa66d 100644 --- a/tests/plp_tests.cpp +++ b/tests/plp_tests.cpp @@ -10,7 +10,7 @@ import emulator; namespace { - void test_flag(bool emulator::Flags::*flag, std::size_t pos) + void test_flag(bool emulator::Flags::* flag, std::size_t pos) { emulator::Cpu cpu; diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt new file mode 100644 index 0000000..2f0d3cf --- /dev/null +++ b/ui/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(emulator_ui) +target_sources(emulator_ui + PUBLIC + FILE_SET CXX_MODULES FILES + common.cpp + flags_table.cpp + register_table.cpp +) + +target_link_libraries(emulator_ui + PUBLIC + emulator + imgui::imgui + fmt::fmt) + +add_library(emulator::ui ALIAS emulator_ui) diff --git a/ui/common.cpp b/ui/common.cpp new file mode 100644 index 0000000..f3a7ee5 --- /dev/null +++ b/ui/common.cpp @@ -0,0 +1,47 @@ +module; + +#include +#include + +#include + +export module common_ui; + +export namespace emulator::ui +{ + + enum class TextAlign : std::uint8_t + { + Left, + Right, + Center, + }; + + void align_text(std::string const& text, TextAlign align) + { + ImVec2 textSize = ImGui::CalcTextSize(text.c_str()); + float cellWidth = ImGui::GetColumnWidth(); + + float padding = 0; + switch (align) + { + case TextAlign::Left: + padding = 0; + break; + case TextAlign::Right: + padding = cellWidth - textSize.x; + break; + case TextAlign::Center: + padding = (cellWidth - textSize.x) * 0.5f; + break; + } + + // Ensure text stays centered + if (padding > 0) + { + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + padding); + } + + ImGui::TextUnformatted(text.c_str()); + } +} // namespace emulator::ui \ No newline at end of file diff --git a/ui/flags_table.cpp b/ui/flags_table.cpp new file mode 100644 index 0000000..c660410 --- /dev/null +++ b/ui/flags_table.cpp @@ -0,0 +1,76 @@ +module; + +#include +#include +#include +#include + +export module flag_table_ui; + +import common_ui; +import emulator; + +namespace +{ + /// number of columns in the registers table + constexpr int columns_count = 8; + + /// Header text for each of the registers cells + constexpr std::array headers = {"N", "V", "-", "B", "D", "I", "Z", "C"}; + + /// Array of boolens where each index represents whether the + /// header is selected (true) or not (false) + std::array column_selected{}; + + auto draw_register_cell(bool val, int col_index) -> void + { + ImGui::TableSetColumnIndex(col_index); + std::string const reg_str = fmt::format("{}", val ? "1" : "0"); + // ImGui::Selectable(reg_str.c_str(), column_selected[col_index]); + emulator::ui::align_text(reg_str, emulator::ui::TextAlign::Center); + } +} // namespace + +export namespace emulator::ui +{ + auto draw_flag_table(emulator::Cpu& cpu) + { + if (ImGui::BeginTable("FlagsTable", columns_count, ImGuiTableFlags_Borders | ImGuiTableFlags_Reorderable)) + { + for (auto const& header : headers) + { + ImGui::TableSetupColumn(header); + } + + // This is submitting a different style and checkboxes to + // the header cells + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + for (int column = 0; column < columns_count; column++) + { + ImGui::TableSetColumnIndex(column); + const char* column_name = + ImGui::TableGetColumnName(column); // Retrieve name passed to TableSetupColumn() + ImGui::PushID(column); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::Checkbox("##checkall", &column_selected[column]); + ImGui::PopStyleVar(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::TableHeader(column_name); + ImGui::PopID(); + } + + // Submit table contents + ImGui::TableNextRow(); + draw_register_cell(cpu.flags.n, 0); + draw_register_cell(cpu.flags.v, 1); + draw_register_cell(cpu.flags.b, 3); + draw_register_cell(cpu.flags.d, 4); + draw_register_cell(cpu.flags.i, 5); + draw_register_cell(cpu.flags.z, 6); + draw_register_cell(cpu.flags.c, 7); + ImGui::EndTable(); + } + } +} // namespace emulator::ui + +module :private; diff --git a/ui/register_table.cpp b/ui/register_table.cpp new file mode 100644 index 0000000..afe8f9c --- /dev/null +++ b/ui/register_table.cpp @@ -0,0 +1,75 @@ +module; + +#include +#include + +#include +#include + +export module register_table_ui; + +import emulator; +import common_ui; + +namespace +{ + /// number of columns in the registers table + constexpr int columns_count = 7; + + /// Header text for each of the registers cells + constexpr std::array headers = {"PC1", "PC2", "AC", "X", "Y", "SR", "SP"}; + + /// Array of boolens where each index represents whether the + /// header is selected (true) or not (false) + std::array column_selected{}; + + auto draw_register_cell(std::uint8_t val, int col_index) -> void + { + ImGui::TableSetColumnIndex(col_index); + std::string const reg_str = fmt::format("0x{:02x}", val); + // ImGui::Selectable(reg_str.c_str(), column_selected[col_index]); + emulator::ui::align_text(reg_str, emulator::ui::TextAlign::Right); + } +} // namespace + +export namespace emulator::ui +{ + auto draw_register_table(emulator::Cpu& cpu) + { + if (ImGui::BeginTable("RegistersTable", columns_count, ImGuiTableFlags_Borders | ImGuiTableFlags_Reorderable)) + { + for (auto const& header : headers) + { + ImGui::TableSetupColumn(header); + } + + // This is submitting a different style and checkboxes to + // the header cells + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + for (int column = 0; column < columns_count; column++) + { + ImGui::TableSetColumnIndex(column); + const char* column_name = + ImGui::TableGetColumnName(column); // Retrieve name passed to TableSetupColumn() + ImGui::PushID(column); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::Checkbox("##checkall", &column_selected[column]); + ImGui::PopStyleVar(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::TableHeader(column_name); + ImGui::PopID(); + } + + // Submit table contents + ImGui::TableNextRow(); + draw_register_cell(static_cast(cpu.reg.pc & 0xff), 0); + draw_register_cell(static_cast((cpu.reg.pc >> 8) & 0xff), 1); + draw_register_cell(cpu.reg.a, 2); + draw_register_cell(cpu.reg.x, 3); + draw_register_cell(cpu.reg.y, 4); + draw_register_cell(cpu.sr(), 5); + draw_register_cell(cpu.reg.sp, 6); + ImGui::EndTable(); + } + } +} // namespace emulator::ui