From b0d8e6135dd96e0c8bdead5e742f59bf9b876646 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 23 Jun 2025 01:52:28 -0400 Subject: [PATCH] Reorganizando codigo, otra vez :p --- .idea/misc.xml | 3 + src/CMakeLists.txt | 20 +- src/Graphics/Chip8Display.cpp | 80 +++++ src/Graphics/Chip8Display.h | 35 ++ src/Graphics/Graphics.cpp | 97 ++++++ src/Graphics/Graphics.h | 35 ++ src/Interpreter/Instruction.h | 19 ++ src/Interpreter/Interpreter.cpp | 458 +++++++++++++++++++++++++++ src/Interpreter/Interpreter.h | 70 ++++ src/{ => Interpreter}/MachineState.h | 0 src/Interpreter/OpCode.h | 43 +++ src/Machine.cpp | 48 +++ src/Machine.h | 27 ++ src/main.cpp | 39 +-- src/{ => old}/Chip8.cpp | 4 +- src/{ => old}/Chip8.h | 2 +- src/{ => old}/Chip8ControlPanel.cpp | 2 +- src/{ => old}/Chip8ControlPanel.h | 0 src/{ => old}/Graphics.cpp | 2 +- src/{ => old}/Graphics.h | 0 src/{ => old}/Interpreter.cpp | 2 +- src/{ => old}/Interpreter.h | 2 +- src/{ => old}/bitops.h | 0 23 files changed, 942 insertions(+), 46 deletions(-) create mode 100644 src/Graphics/Chip8Display.cpp create mode 100644 src/Graphics/Chip8Display.h create mode 100644 src/Graphics/Graphics.cpp create mode 100644 src/Graphics/Graphics.h create mode 100644 src/Interpreter/Instruction.h create mode 100644 src/Interpreter/Interpreter.cpp create mode 100644 src/Interpreter/Interpreter.h rename src/{ => Interpreter}/MachineState.h (100%) create mode 100644 src/Interpreter/OpCode.h create mode 100644 src/Machine.cpp create mode 100644 src/Machine.h rename src/{ => old}/Chip8.cpp (99%) rename src/{ => old}/Chip8.h (97%) rename src/{ => old}/Chip8ControlPanel.cpp (96%) rename src/{ => old}/Chip8ControlPanel.h (100%) rename src/{ => old}/Graphics.cpp (99%) rename src/{ => old}/Graphics.h (100%) rename src/{ => old}/Interpreter.cpp (99%) rename src/{ => old}/Interpreter.h (99%) rename src/{ => old}/bitops.h (100%) diff --git a/.idea/misc.xml b/.idea/misc.xml index 1b3a82a..fe44e63 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -9,5 +9,8 @@ + + + \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 46a169d..b9c6e02 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,13 +1,13 @@ add_executable(${PROJECT_NAME}) -target_sources(${PROJECT_NAME} PRIVATE main.cpp - bitops.h - Graphics.cpp - Graphics.h - Chip8.cpp - Chip8.h - Interpreter.cpp - Interpreter.h - Chip8ControlPanel.cpp - Chip8ControlPanel.h +target_sources( + ${PROJECT_NAME} + PRIVATE + main.cpp + Machine.h Machine.cpp + Interpreter/Interpreter.h Interpreter/Interpreter.cpp + Graphics/Graphics.h Graphics/Graphics.cpp + Graphics/Chip8Display.h Graphics/Chip8Display.cpp ) + + target_link_libraries(${PROJECT_NAME} PRIVATE vendor) \ No newline at end of file diff --git a/src/Graphics/Chip8Display.cpp b/src/Graphics/Chip8Display.cpp new file mode 100644 index 0000000..c3d669b --- /dev/null +++ b/src/Graphics/Chip8Display.cpp @@ -0,0 +1,80 @@ +#include "Chip8Display.h" + +#include +#include +#include +#include + +#include "imgui.h" +#include "SDL3/SDL.h" + +void SDLTextureDestroyer::operator()(SDL_Texture* texture) const { + SDL_DestroyTexture(texture); +} + +Chip8Display::Chip8Display(std::shared_ptr machine_state, SDL_Renderer& renderer): + machine_state{std::move(machine_state)}, + renderer{renderer}, + width(64), + height(32) { + + SDL_Texture* raw_texture = SDL_CreateTexture(&renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, width, height); + this->texture = std::unique_ptr(raw_texture); + SDL_SetTextureScaleMode(texture.get(), SDL_SCALEMODE_LINEAR); +} + +void Chip8Display::render() const { + update_texture(); + display_widget(); +} + +void Chip8Display::update() const { +} + +void Chip8Display::update_texture() const { + SDL_Surface* surface = nullptr; + const auto& display = machine_state->display; + + if (SDL_LockTextureToSurface(texture.get(), nullptr, &surface)) { + SDL_FillSurfaceRect(surface, nullptr, SDL_MapRGB(SDL_GetPixelFormatDetails(surface->format), nullptr, 0, 0, 0)); + + for (int i = 0; i < display.size(); i++) { + if (display[i]) { + const int x = (i % width); + const int y = (i / width); + + SDL_Rect rect = {x, y, 1, 1}; + Uint32 color = SDL_MapRGB( + SDL_GetPixelFormatDetails(surface->format), + nullptr, + 0, + 255, + 0 + ); + + SDL_FillSurfaceRect(surface, &rect, color); + } + } + SDL_UnlockTexture(texture.get()); + } else { + SDL_Log("Failed to lock texture: %s", SDL_GetError()); + } +} + +void Chip8Display::display_widget() const { + ImGui::Begin("CHIP-8 - Display"); + + const ImVec2 available_size = ImGui::GetContentRegionAvail(); + + const int scale_x = static_cast(static_cast(available_size.x) / static_cast(width)); + const int scale_y = static_cast(static_cast(available_size.y) / static_cast(height)); + const int scale = std::max(1, std::min(scale_x, scale_y)); + const ImVec2 scaled_size(static_cast(width * scale), static_cast(height * scale)); + + const ImVec2 cursor_pos = ImGui::GetCursorPos(); + const ImVec2 center_offset((available_size.x - scaled_size.x) / 2, (available_size.y - scaled_size.y) / 2); + ImGui::SetCursorPos(ImVec2(cursor_pos.x + center_offset.x, cursor_pos.y + center_offset.y)); + + ImGui::Image((ImTextureID)((intptr_t)texture.get()), scaled_size); + ImGui::End(); +} diff --git a/src/Graphics/Chip8Display.h b/src/Graphics/Chip8Display.h new file mode 100644 index 0000000..ae04fed --- /dev/null +++ b/src/Graphics/Chip8Display.h @@ -0,0 +1,35 @@ +#ifndef CHIP8DISPLAY_H +#define CHIP8DISPLAY_H +#include + +#include "../Interpreter/MachineState.h" +#include "SDL3/SDL.h" + +struct SDLTextureDestroyer { + void operator()(SDL_Texture* texture) const; +}; + +class Chip8Display { + std::shared_ptr machine_state; + SDL_Renderer& renderer; + + int width; + int height; + + std::unique_ptr texture; + + void update_texture() const; + void display_widget() const; + +public: + Chip8Display( + std::shared_ptr machine_state, + SDL_Renderer& renderer + ); + + void update() const; + void render() const; +}; + + +#endif //CHIP8DISPLAY_H diff --git a/src/Graphics/Graphics.cpp b/src/Graphics/Graphics.cpp new file mode 100644 index 0000000..832419d --- /dev/null +++ b/src/Graphics/Graphics.cpp @@ -0,0 +1,97 @@ +#include "Graphics.h" + +#include + +#include "imgui.h" +#include "imgui_impl_sdl3.h" +#include "imgui_impl_sdlrenderer3.h" + +void SDLWindowDestroyer::operator()(SDL_Window* window) const { + SDL_DestroyWindow(window); +} + +void SDLRendererDestroyer::operator()(SDL_Renderer* renderer) const { + SDL_DestroyRenderer(renderer); +} + +Graphics::Graphics(): window_width{1920}, window_height{1080}, main_scale{1.0f} { + create_sdl(); + create_imgui(); +} + +void Graphics::create_sdl() { + SDL_SetAppMetadata("CHIP-8 Emulator", "0.0.1", "fun.skrd.chip8"); + + if (!SDL_Init(SDL_INIT_VIDEO)) { + throw std::runtime_error(std::format("Couldn't initialize SDL: {}", SDL_GetError())); + } + + SDL_Window* raw_window = nullptr; + SDL_Renderer* raw_renderer = nullptr; + + main_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); + SDL_WindowFlags window_flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_HIGH_PIXEL_DENSITY; + + if (!SDL_CreateWindowAndRenderer( + "CHIP-8 Emulator", + window_width * main_scale, + window_height * main_scale, + window_flags, + &raw_window, + &raw_renderer + )) { + throw std::runtime_error(std::format("Couldn't create window/renderer: {}", SDL_GetError())); + } + + window = std::unique_ptr(raw_window); + renderer = std::unique_ptr(raw_renderer); + + SDL_SetRenderVSync(renderer.get(), 1); + SDL_SetWindowPosition(window.get(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); + SDL_ShowWindow(window.get()); +} + +void Graphics::create_imgui() { + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + + ImGui::StyleColorsDark(); + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); + style.FontScaleDpi = main_scale; + + ImGui_ImplSDL3_InitForSDLRenderer(window.get(), renderer.get()); + ImGui_ImplSDLRenderer3_Init(renderer.get()); +} + +SDL_Renderer& Graphics::get_renderer() { + return *renderer; +} + + +void Graphics::start_render() { + if (SDL_GetWindowFlags(window.get()) & SDL_WINDOW_MINIMIZED) { + SDL_Delay(10); + } + + ImGui_ImplSDLRenderer3_NewFrame(); + ImGui_ImplSDL3_NewFrame(); + ImGui::NewFrame(); + ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport()); +} + +void Graphics::end_render() { + ImGuiIO& io = ImGui::GetIO(); + + ImGui::Render(); + SDL_SetRenderScale(renderer.get(), io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y); + SDL_SetRenderDrawColor(renderer.get(), 0, 0, 0, 0xFF); + SDL_RenderClear(renderer.get()); + + ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), renderer.get()); + SDL_RenderPresent(renderer.get()); +} diff --git a/src/Graphics/Graphics.h b/src/Graphics/Graphics.h new file mode 100644 index 0000000..68af503 --- /dev/null +++ b/src/Graphics/Graphics.h @@ -0,0 +1,35 @@ +#ifndef GRAPHICS_H +#define GRAPHICS_H + +#include + +#include "Chip8Display.h" +#include "SDL3/SDL.h" + +struct SDLWindowDestroyer { + void operator()(SDL_Window* window) const; +}; + +struct SDLRendererDestroyer { + void operator()(SDL_Renderer* renderer) const; +}; + +class Graphics { + int window_width; + int window_height; + float main_scale; + + std::unique_ptr window; + std::unique_ptr renderer; + + void create_sdl(); + void create_imgui(); +public: + Graphics(); + + void start_render(); + void end_render(); + SDL_Renderer& get_renderer(); +}; + +#endif //GRAPHICS_H diff --git a/src/Interpreter/Instruction.h b/src/Interpreter/Instruction.h new file mode 100644 index 0000000..f9e8b95 --- /dev/null +++ b/src/Interpreter/Instruction.h @@ -0,0 +1,19 @@ +#ifndef INSTRUCTION_H +#define INSTRUCTION_H + +#include + +#include "OpCode.h" + +struct Instruction { + OpCode op_code; + uint8_t operation; + uint16_t instruction; + uint8_t x; + uint8_t y; + uint8_t n; + uint8_t kk; + uint16_t nnn; +}; + +#endif //INSTRUCTION_H diff --git a/src/Interpreter/Interpreter.cpp b/src/Interpreter/Interpreter.cpp new file mode 100644 index 0000000..d012158 --- /dev/null +++ b/src/Interpreter/Interpreter.cpp @@ -0,0 +1,458 @@ +#include "Interpreter.h" + +#include +#include + +Interpreter::Interpreter(std::shared_ptr machine_state): + machine_state{std::move(machine_state)}, + random_generator{std::random_device{}()} {} + +void Interpreter::tick() { + const uint8_t high_word = machine_state->memory[machine_state->pc]; + const uint8_t low_word = machine_state->memory[machine_state->pc + 1]; + const uint16_t word = high_word << 8 | low_word; + + const auto instruction = decode(word); + machine_state->pc += 2; + execute_instruction(instruction); + + if (machine_state->pc >= 0xFFF) { + std::cout << "PC Outside of memory, going back 0x200" << std::endl; + machine_state->pc = 0x200; + } +} + +Instruction Interpreter::decode(const uint16_t word) { + const uint8_t operation = (word & 0xF000) >> 12; + const uint8_t x = (word & 0x0F00) >> 8; + const uint8_t y = (word & 0x00F0) >> 4; + const uint8_t n = word & 0x000F; + const uint8_t kk = word & 0x00FF; + const uint16_t nnn = word & 0x0FFF; + + auto op_code = OpCode::NOP; + + if (operation == 0) { + if (kk == 0xE0) { + op_code = OpCode::CLS; + } else if (kk == 0xEE) { + op_code = OpCode::RET; + } else { + op_code = OpCode::SYS_ADDR; + } + } else if (operation == 1) { + op_code = OpCode::JP_ADDR; + } else if (operation == 2) { + op_code = OpCode::CALL_ADDR; + } else if (operation == 3) { + op_code = OpCode::SE_VX_BYTE; + } else if (operation == 4) { + op_code = OpCode::SNE_VX_BYTE; + } else if (operation == 5) { + op_code = OpCode::SE_VX_VY; + } else if (operation == 6) { + op_code = OpCode::LD_VX_BYTE; + } else if (operation == 7) { + op_code = OpCode::ADD_VX_BYTE; + } else if (operation == 8) { + if (n == 0) { + op_code = OpCode::LD_VX_VY; + } else if (n == 1) { + op_code = OpCode::OR_VX_VY; + } else if (n == 2) { + op_code = OpCode::AND_VX_VY; + } else if (n == 3) { + op_code = OpCode::XOR_VX_VY; + } else if (n == 4) { + op_code = OpCode::ADD_VX_VY; + } else if (n == 5) { + op_code = OpCode::SUB_VX_VY; + } else if (n == 6) { + op_code = OpCode::SHR_VX_VY; + } else if (n == 7) { + op_code = OpCode::SUBN_VX_VY; + } else if (n == 0xE) { + op_code = OpCode::SHL_VX_VY; + } + } else if (operation == 9) { + op_code = OpCode::SNE_VX_VY; + } else if (operation == 0xA) { + op_code = OpCode::LD_I_ADDR; + } else if (operation == 0xB) { + op_code = OpCode::JP_V0_ADDR; + } else if (operation == 0xC) { + op_code = OpCode::RND_VX_BYTE; + } else if (operation == 0xD) { + op_code = OpCode::DRW_VX_VY_NIBBLE; + } else if (operation == 0xE) { + if (kk == 0x9E) { + op_code = OpCode::SKP_VX; + } else if (kk == 0xA1) { + op_code = OpCode::SKNP_VX; + } + } else if (operation == 0xF) { + if (kk == 0x07) { + op_code = OpCode::LD_VX_DT; + } else if (kk == 0x0A) { + op_code = OpCode::LD_VX_K; + } else if (kk == 0x15) { + op_code = OpCode::LD_DT_VX; + } else if (kk == 0x18) { + op_code = OpCode::LD_ST_VX; + } else if (kk == 0x1E) { + op_code = OpCode::ADD_I_VX; + } else if (kk == 0x29) { + op_code = OpCode::LD_F_VX; + } else if (kk == 0x33) { + op_code = OpCode::LD_B_VX; + } else if (kk == 0x55) { + op_code = OpCode::LD_I_VX; + } else if (kk == 0x65) { + op_code = OpCode::LD_VX_I; + } + } + + return Instruction{ + .op_code = op_code, + .operation = operation, + .instruction = word, + .x = x, + .y = y, + .n = n, + .kk = kk, + .nnn = nnn + }; +} + +void Interpreter::execute_instruction(const Instruction& instruction) { + if (instruction.op_code == OpCode::CLS) cls(); + else if (instruction.op_code == OpCode::RET) ret(); + else if (instruction.op_code == OpCode::SYS_ADDR) sys_addr(); + else if (instruction.op_code == OpCode::JP_ADDR) jp_addr(instruction); + else if (instruction.op_code == OpCode::CALL_ADDR) call_addr(instruction); + else if (instruction.op_code == OpCode::SE_VX_BYTE) se_vx_byte(instruction); + else if (instruction.op_code == OpCode::SNE_VX_BYTE) sne_vx_byte(instruction); + else if (instruction.op_code == OpCode::SE_VX_VY) se_vx_vy(instruction); + else if (instruction.op_code == OpCode::LD_VX_BYTE) ld_vx_byte(instruction); + else if (instruction.op_code == OpCode::ADD_VX_BYTE) add_vx_byte(instruction); + else if (instruction.op_code == OpCode::LD_VX_VY) ld_vx_vy(instruction); + else if (instruction.op_code == OpCode::OR_VX_VY) or_vx_vy(instruction); + else if (instruction.op_code == OpCode::AND_VX_VY) and_vx_vy(instruction); + else if (instruction.op_code == OpCode::XOR_VX_VY) xor_vx_vy(instruction); + else if (instruction.op_code == OpCode::ADD_VX_VY) add_vx_vy(instruction); + else if (instruction.op_code == OpCode::SUB_VX_VY) sub_vx_vy(instruction); + else if (instruction.op_code == OpCode::SHR_VX_VY) shr_vx_vy(instruction); + else if (instruction.op_code == OpCode::SUBN_VX_VY) subn_vx_vy(instruction); + else if (instruction.op_code == OpCode::SHL_VX_VY) shl_vx_vy(instruction); + else if (instruction.op_code == OpCode::SNE_VX_VY) sne_vx_vy(instruction); + else if (instruction.op_code == OpCode::LD_I_ADDR) ld_i_addr(instruction); + else if (instruction.op_code == OpCode::JP_V0_ADDR) jp_v0_addr(instruction); + else if (instruction.op_code == OpCode::RND_VX_BYTE) rnd_vx_byte(instruction); + else if (instruction.op_code == OpCode::DRW_VX_VY_NIBBLE) drw_vx_vy_nibble(instruction); + else if (instruction.op_code == OpCode::SKP_VX) skp_vx(instruction); + else if (instruction.op_code == OpCode::SKNP_VX) sknp_vx(instruction); + else if (instruction.op_code == OpCode::LD_VX_DT) ld_vx_dt(instruction); + else if (instruction.op_code == OpCode::LD_VX_K) ld_vx_k(instruction); + else if (instruction.op_code == OpCode::LD_DT_VX) ld_dt_vx(instruction); + else if (instruction.op_code == OpCode::LD_ST_VX) ld_st_vx(instruction); + else if (instruction.op_code == OpCode::ADD_I_VX) add_i_vx(instruction); + else if (instruction.op_code == OpCode::LD_F_VX) ld_f_vx(instruction); + else if (instruction.op_code == OpCode::LD_B_VX) ld_b_vx(instruction); + else if (instruction.op_code == OpCode::LD_I_VX) ld_i_vx(instruction); + else if (instruction.op_code == OpCode::LD_VX_I) ld_vx_i(instruction); + else if (instruction.op_code == OpCode::NOP) nop(); +} + + +void Interpreter::sys_addr() { + // NOP +} + +void Interpreter::nop() { + // NOP +} + +void Interpreter::cls() const { + machine_state->display.fill(false); +} + +void Interpreter::ret() const { + machine_state->sp -= 1; + machine_state->pc = machine_state->stack[machine_state->sp]; +} + +void Interpreter::jp_addr(const Instruction& instruction) const { + machine_state->pc = instruction.nnn; +} + +void Interpreter::call_addr(const Instruction& instruction) const { + machine_state->stack[machine_state->sp] = machine_state->pc; + machine_state->sp += 1; + machine_state->pc = instruction.nnn; +} + +void Interpreter::se_vx_byte(const Instruction& instruction) const { + if (machine_state->v[instruction.x] == instruction.kk) { + machine_state->pc += 2; + } +} + +void Interpreter::sne_vx_byte(const Instruction& instruction) const { + if (machine_state->v[instruction.x] != instruction.kk) { + machine_state->pc += 2; + } +} + +void Interpreter::se_vx_vy(const Instruction& instruction) const { + if (machine_state->v[instruction.x] == machine_state->v[instruction.y]) { + machine_state->pc += 2; + } +} + +void Interpreter::ld_vx_byte(const Instruction& instruction) const { + machine_state->v[instruction.x] = instruction.kk; +} + +void Interpreter::add_vx_byte(const Instruction& instruction) const { + machine_state->v[instruction.x] += instruction.kk; +} + +void Interpreter::ld_vx_vy(const Instruction& instruction) const { + machine_state->v[instruction.x] = machine_state->v[instruction.y]; +} + +void Interpreter::or_vx_vy(const Instruction& instruction) const { + machine_state->v[instruction.x] |= machine_state->v[instruction.y]; +} + +void Interpreter::and_vx_vy(const Instruction& instruction) const { + machine_state->v[instruction.x] &= machine_state->v[instruction.y]; +} + +void Interpreter::xor_vx_vy(const Instruction& instruction) const { + machine_state->v[instruction.x] ^= machine_state->v[instruction.y]; +} + +void Interpreter::add_vx_vy(const Instruction& instruction) const { + auto& vx = machine_state->v[instruction.x]; + const auto& vy = machine_state->v[instruction.y]; + auto& vf = machine_state->v[0xF]; + + if (vx + vy > 0xFF) { + vf = 1; + } else { + vf = 0; + } + + vx += vy; +} + +void Interpreter::sub_vx_vy(const Instruction& instruction) const { + auto& vx = machine_state->v[instruction.x]; + const auto& vy = machine_state->v[instruction.y]; + auto& vf = machine_state->v[0xF]; + + if (vx > vy) { + vf = 1; + } else { + vf = 0; + } + + vx -= vy; +} + +void Interpreter::shr_vx_vy(const Instruction& instruction) const { + auto& vx = machine_state->v[instruction.x]; + const auto& vy = machine_state->v[instruction.y]; + auto& vf = machine_state->v[0xF]; + + if (quirks & static_cast(InterpreterQuirks::COSMAC_SHIFT)) { + vx = vy; + } + + if (vx & 0x01) { + vf = 1; + } else { + vf = 0; + } + + vx = vx >> 1; +} + +void Interpreter::subn_vx_vy(const Instruction& instruction) const { + auto& vx = machine_state->v[instruction.x]; + const auto& vy = machine_state->v[instruction.y]; + auto& vf = machine_state->v[0xF]; + + if (vy > vx) { + vf = 1; + } else { + vf = 0; + } + + vx = vy - vx; +} + +void Interpreter::shl_vx_vy(const Instruction& instruction) const { + auto& vx = machine_state->v[instruction.x]; + const auto& vy = machine_state->v[instruction.y]; + auto& vf = machine_state->v[0xF]; + + if (quirks & static_cast(InterpreterQuirks::COSMAC_SHIFT)) { + vx = vy; + } + + if (vx & 0x80) { + vf = 1; + } else { + vf = 0; + } + + vx = vx << 1; +} + +void Interpreter::sne_vx_vy(const Instruction& instruction) const { + if (machine_state->v[instruction.x] != machine_state->v[instruction.y]) { + machine_state->pc += 2; + } +} + +void Interpreter::ld_i_addr(const Instruction& instruction) const { + machine_state->i = instruction.nnn; +} + +void Interpreter::jp_v0_addr(const Instruction& instruction) const { + if (quirks & static_cast(InterpreterQuirks::SUPER_CHIP_JUMP)) { + machine_state->pc = instruction.nnn + machine_state->v[instruction.x]; + } else { + machine_state->pc = instruction.nnn + machine_state->v[0]; + } +} + +void Interpreter::rnd_vx_byte(const Instruction& instruction) { + auto random = std::uniform_int_distribution(0, 0xFF); + const auto value = random(random_generator); + + machine_state->v[instruction.x] = value & instruction.kk; +} + +void Interpreter::drw_vx_vy_nibble(const Instruction& instruction) const { + const auto& memory = machine_state->memory; + auto& display = machine_state->display; + const auto& vx = machine_state->v[instruction.x]; + const auto& vy = machine_state->v[instruction.y]; + auto& vf = machine_state->v[0xF]; + + const uint8_t start_x = vx & 63; + const uint8_t start_y = vy & 31; + vf = 0; + + for (auto row = 0; row < instruction.n; row++) { + const auto current_y = start_y + row; + + if (current_y > 31) { + break; + } + + const auto sprite_byte = memory[machine_state->i + row]; + + for (auto bit = 0; bit < 8; bit++) { + const auto current_x = start_x + bit; + + if (current_x > 63) { + break; + } + + const auto pixel = sprite_byte >> (7 - bit) & 0x01; + const auto index = current_y * 64 + current_x; + + if (pixel) { + if (display[index]) { + display[index] = false; + vf = 1; + } else { + display[index] = true; + } + } + } + } +} + +void Interpreter::skp_vx(const Instruction& instruction) const { + if (machine_state->keyboard & 1 << instruction.x) { + machine_state->pc += 2; + } +} + +void Interpreter::sknp_vx(const Instruction& instruction) const { + if (!(machine_state->keyboard & 1 << instruction.x)) { + machine_state->pc += 2; + } +} + +void Interpreter::ld_vx_dt(const Instruction& instruction) const { + machine_state->v[instruction.x] = machine_state->dt; +} + +void Interpreter::ld_vx_k(const Instruction& instruction) const { + if (machine_state->keyboard == 0) { + machine_state->pc -= 2; + return; + } + + for (auto key = 0; key < 16; key++) { + if (machine_state->keyboard & 1 << key) { + machine_state->v[instruction.x] = key; + break; + } + } +} + +void Interpreter::ld_dt_vx(const Instruction& instruction) const { + machine_state->dt = machine_state->v[instruction.x]; +} + +void Interpreter::ld_st_vx(const Instruction& instruction) const { + machine_state->st = machine_state->v[instruction.x]; +} + +void Interpreter::add_i_vx(const Instruction& instruction) const { + machine_state->i += machine_state->v[instruction.x]; +} + +void Interpreter::ld_f_vx(const Instruction& instruction) const { + machine_state->i = (machine_state->v[instruction.x] & 0xF) * 5 + 0x50; +} + +void Interpreter::ld_b_vx(const Instruction& instruction) const { + const auto number = machine_state->v[instruction.x]; + machine_state->memory[machine_state->i] = number / 100; + machine_state->memory[machine_state->i + 1] = (number - machine_state->memory[machine_state->i] * 100) / 10; + machine_state->memory[machine_state->i + 2] = number - machine_state->memory[machine_state->i] * 100 - machine_state->memory[machine_state->i + 1] * 10; +} + +void Interpreter::ld_i_vx(const Instruction& instruction) const { + const bool use_quirk = quirks & static_cast(InterpreterQuirks::COSMAC_STORE_AND_LOAD); + + for (auto reg = 0; reg <= instruction.x; reg++) { + if (use_quirk) { + machine_state->memory[machine_state->i] = machine_state->v[reg]; + machine_state->i++; + } else { + machine_state->memory[machine_state->i + reg] = machine_state->v[reg]; + } + } +} + +void Interpreter::ld_vx_i(const Instruction& instruction) const { + const bool use_quirk = quirks & static_cast(InterpreterQuirks::COSMAC_STORE_AND_LOAD); + + for (auto reg = 0; reg <= instruction.x; reg++) { + if (use_quirk) { + machine_state->v[reg] = machine_state->memory[machine_state->i]; + machine_state->i++; + } else { + machine_state->v[reg] = machine_state->memory[machine_state->i + reg]; + } + } +} diff --git a/src/Interpreter/Interpreter.h b/src/Interpreter/Interpreter.h new file mode 100644 index 0000000..e8b62b5 --- /dev/null +++ b/src/Interpreter/Interpreter.h @@ -0,0 +1,70 @@ +#ifndef INTERPRETER_H +#define INTERPRETER_H + +#include +#include + +#include "Instruction.h" +#include "MachineState.h" + +enum class InterpreterQuirks { + COSMAC_SHIFT = 1 << 0, + SUPER_CHIP_JUMP = 1 << 1, + COSMAC_STORE_AND_LOAD = 1 << 2, +}; + +class Interpreter { + std::shared_ptr machine_state; + std::mt19937 random_generator; + + uint8_t quirks = 0; + + static Instruction decode(uint16_t word); + void execute_instruction(const Instruction& instruction); + + static void sys_addr(); + static void nop(); + void cls() const; + void ret() const; + void jp_addr(const Instruction& instruction) const; + void call_addr(const Instruction& instruction) const; + void se_vx_byte(const Instruction& instruction) const; + void sne_vx_byte(const Instruction& instruction) const; + void se_vx_vy(const Instruction& instruction) const; + void ld_vx_byte(const Instruction& instruction) const; + void add_vx_byte(const Instruction& instruction) const; + void ld_vx_vy(const Instruction& instruction) const; + void or_vx_vy(const Instruction& instruction) const; + void and_vx_vy(const Instruction& instruction) const; + void xor_vx_vy(const Instruction& instruction) const; + void add_vx_vy(const Instruction& instruction) const; + void sub_vx_vy(const Instruction& instruction) const; + void shr_vx_vy(const Instruction& instruction) const; + void subn_vx_vy(const Instruction& instruction) const; + void shl_vx_vy(const Instruction& instruction) const; + void sne_vx_vy(const Instruction& instruction) const; + void ld_i_addr(const Instruction& instruction) const; + void jp_v0_addr(const Instruction& instruction) const; + void rnd_vx_byte(const Instruction& instruction); + void drw_vx_vy_nibble(const Instruction& instruction) const; + void skp_vx(const Instruction& instruction) const; + void sknp_vx(const Instruction& instruction) const; + void ld_vx_dt(const Instruction& instruction) const; + void ld_vx_k(const Instruction& instruction) const; + void ld_dt_vx(const Instruction& instruction) const; + void ld_st_vx(const Instruction& instruction) const; + void add_i_vx(const Instruction& instruction) const; + void ld_f_vx(const Instruction& instruction) const; + void ld_b_vx(const Instruction& instruction) const; + void ld_i_vx(const Instruction& instruction) const; + void ld_vx_i(const Instruction& instruction) const; + +public: + explicit Interpreter(std::shared_ptr machine_state); + + void tick(); +}; + + + +#endif //INTERPRETER_H diff --git a/src/MachineState.h b/src/Interpreter/MachineState.h similarity index 100% rename from src/MachineState.h rename to src/Interpreter/MachineState.h diff --git a/src/Interpreter/OpCode.h b/src/Interpreter/OpCode.h new file mode 100644 index 0000000..be46f2a --- /dev/null +++ b/src/Interpreter/OpCode.h @@ -0,0 +1,43 @@ +#ifndef OPCODE_H +#define OPCODE_H + +enum class OpCode { + CLS, // 00E0 - CLS + RET, // 00EE - RET + SYS_ADDR, // 0nnn - SYS addr + JP_ADDR, // 1nnn - JP addr + CALL_ADDR, // 2nnn - CALL addr + SE_VX_BYTE, // 3xkk - SE Vx, byte + SNE_VX_BYTE, // 4xkk - SNE Vx, byte + SE_VX_VY, // 5xy0 - SE Vx, Vy + LD_VX_BYTE, // 6xkk - LD Vx, byte + ADD_VX_BYTE, // 7xkk - ADD Vx, byte + LD_VX_VY, // 8xy0 - LD Vx, Vy + OR_VX_VY, // 8xy1 - OR Vx, Vy + AND_VX_VY, // 8xy2 - AND Vx, Vy + XOR_VX_VY, // 8xy3 - XOR Vx, Vy + ADD_VX_VY, // 8xy4 - ADD Vx, Vy + SUB_VX_VY, // 8xy5 - SUB Vx, Vy + SHR_VX_VY, // 8xy6 - SHR Vx {, Vy} + SUBN_VX_VY, // 8xy7 - SUBN Vx, Vy + SHL_VX_VY, // 8xyE - SHL Vx {, Vy} + SNE_VX_VY, // 9xy0 - SNE Vx, Vy + LD_I_ADDR, // Annn - LD I, addr + JP_V0_ADDR, // Bnnn - JP V0, addr + RND_VX_BYTE, // Cxkk - RND Vx, byte + DRW_VX_VY_NIBBLE, // Dxyn - DRW Vx, Vy, nibble + SKP_VX, // Ex9E - SKP Vx + SKNP_VX, // ExA1 - SKNP Vx + LD_VX_DT, // Fx07 - LD Vx, DT + LD_VX_K, // Fx0A - LD Vx, K + LD_DT_VX, // Fx15 - LD DT, Vx + LD_ST_VX, // Fx18 - LD ST, Vx + ADD_I_VX, // Fx1E - ADD I, Vx + LD_F_VX, // Fx29 - LD F, Vx + LD_B_VX, // Fx33 - LD B, Vx + LD_I_VX, // Fx55 - LD [I], Vx + LD_VX_I, // Fx65 - LD Vx, [I] + NOP, // INVALID OPERATION +}; + +#endif //OPCODE_H diff --git a/src/Machine.cpp b/src/Machine.cpp new file mode 100644 index 0000000..2bb57a2 --- /dev/null +++ b/src/Machine.cpp @@ -0,0 +1,48 @@ +#include "Machine.h" + +#include "imgui.h" +#include "imgui_impl_sdl3.h" + +Machine::Machine(): + machine_state{std::make_shared()}, + interpreter{std::make_unique(machine_state)}, + graphics{std::make_unique()}, + chip8_display{std::make_unique(machine_state, graphics->get_renderer())}, + ips{700}, + last_update_time{0}, + accumulator{0} {} + +void Machine::iterate() { + execute_interpreter(); + + chip8_display->update(); + + graphics->start_render(); + ImGui::ShowDemoWindow(); + chip8_display->render(); + graphics->end_render(); +} + +bool Machine::on_event(const SDL_Event* event) { + ImGui_ImplSDL3_ProcessEvent(event); + + if (event->type == SDL_EVENT_QUIT) { + return false; + } + + return true; +} + +void Machine::execute_interpreter() { + const auto target_cycle_time = 1.0 / ips; + const auto current_time = SDL_GetTicks(); + const auto delta_time = static_cast(current_time - last_update_time) / 1000.0; + last_update_time = current_time; + accumulator += delta_time; + + // ReSharper disable once CppDFALoopConditionNotUpdated + while (accumulator >= target_cycle_time) { + interpreter->tick(); + accumulator -= target_cycle_time; + } +} diff --git a/src/Machine.h b/src/Machine.h new file mode 100644 index 0000000..0badf18 --- /dev/null +++ b/src/Machine.h @@ -0,0 +1,27 @@ +#ifndef MACHINE_H +#define MACHINE_H + +#include "Graphics/Chip8Display.h" +#include "Graphics/Graphics.h" +#include "Interpreter/Interpreter.h" +#include "SDL3/SDL_events.h" + +class Machine { + std::shared_ptr machine_state; + std::unique_ptr interpreter; + std::unique_ptr graphics; + std::unique_ptr chip8_display; + + int ips; + uint64_t last_update_time; + double accumulator; + +void execute_interpreter(); + +public: + Machine(); + void iterate(); + static bool on_event(const SDL_Event* event); +}; + +#endif //MACHINE_H diff --git a/src/main.cpp b/src/main.cpp index 9ec76bf..010b331 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,53 +1,34 @@ #define SDL_MAIN_USE_CALLBACKS 1 -#include -#include #include -#include "Chip8.h" -#include "imgui.h" -#include "imgui_impl_sdl3.h" +#include +#include "Machine.h" SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { - const auto chip8 = new Chip8(); - *appstate = chip8; - - if (!chip8->init()) { - return SDL_APP_FAILURE; - } - - int num_keys; - const bool* kb_state = SDL_GetKeyboardState(&num_keys); - const std::span kb_state_view(kb_state, num_keys); - chip8->set_keyboard_state(kb_state_view); + auto machine = std::make_unique(); + *appstate = machine.release(); return SDL_APP_CONTINUE; } SDL_AppResult SDL_AppIterate(void* appstate) { - const auto chip8 = static_cast(appstate); + const auto machine = static_cast(appstate); + + machine->iterate(); - chip8->update(); return SDL_APP_CONTINUE; } SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) { - const auto chip8 = static_cast(appstate); + const auto machine = static_cast(appstate); - ImGui_ImplSDL3_ProcessEvent(event); - - if (event->type == SDL_EVENT_QUIT) { + if (!machine->on_event(event)) { return SDL_APP_SUCCESS; } - if (event->type == SDL_EVENT_KEY_DOWN) { - chip8->on_keydown(event->key.scancode); - } - - return SDL_APP_CONTINUE; } void SDL_AppQuit(void* appstate, SDL_AppResult result) { - const auto chip8 = static_cast(appstate); - delete chip8; + std::unique_ptr chip8(static_cast(appstate)); } diff --git a/src/Chip8.cpp b/src/old/Chip8.cpp similarity index 99% rename from src/Chip8.cpp rename to src/old/Chip8.cpp index 4f19492..5d82c58 100644 --- a/src/Chip8.cpp +++ b/src/old/Chip8.cpp @@ -1,4 +1,4 @@ -#include "Chip8.h" +#include "../Chip8.h" #include #include @@ -74,7 +74,7 @@ void Chip8::load_keyboard() { } -void Chip8::update() { +void Chip8::iterate() { load_keyboard(); std::stringstream buffer; diff --git a/src/Chip8.h b/src/old/Chip8.h similarity index 97% rename from src/Chip8.h rename to src/old/Chip8.h index dc713cd..980c491 100644 --- a/src/Chip8.h +++ b/src/old/Chip8.h @@ -28,7 +28,7 @@ public: bool init(); - void update(); + void iterate(); std::vector read_rom(const std::string& path); diff --git a/src/Chip8ControlPanel.cpp b/src/old/Chip8ControlPanel.cpp similarity index 96% rename from src/Chip8ControlPanel.cpp rename to src/old/Chip8ControlPanel.cpp index dd1cb7b..c6645e4 100644 --- a/src/Chip8ControlPanel.cpp +++ b/src/old/Chip8ControlPanel.cpp @@ -1,4 +1,4 @@ -#include "Chip8ControlPanel.h" +#include "../Chip8ControlPanel.h" #include diff --git a/src/Chip8ControlPanel.h b/src/old/Chip8ControlPanel.h similarity index 100% rename from src/Chip8ControlPanel.h rename to src/old/Chip8ControlPanel.h diff --git a/src/Graphics.cpp b/src/old/Graphics.cpp similarity index 99% rename from src/Graphics.cpp rename to src/old/Graphics.cpp index f2ec2e3..1dfeff5 100644 --- a/src/Graphics.cpp +++ b/src/old/Graphics.cpp @@ -2,7 +2,7 @@ // Created by ryuuji on 6/20/25. // -#include "Graphics.h" +#include "../Graphics.h" #include "Chip8ControlPanel.h" #include diff --git a/src/Graphics.h b/src/old/Graphics.h similarity index 100% rename from src/Graphics.h rename to src/old/Graphics.h diff --git a/src/Interpreter.cpp b/src/old/Interpreter.cpp similarity index 99% rename from src/Interpreter.cpp rename to src/old/Interpreter.cpp index 5854519..fea2fae 100644 --- a/src/Interpreter.cpp +++ b/src/old/Interpreter.cpp @@ -1,4 +1,4 @@ -#include "Interpreter.h" +#include "../Interpreter.h" #include #include diff --git a/src/Interpreter.h b/src/old/Interpreter.h similarity index 99% rename from src/Interpreter.h rename to src/old/Interpreter.h index 02b8114..b29e30c 100644 --- a/src/Interpreter.h +++ b/src/old/Interpreter.h @@ -6,7 +6,7 @@ #include #include -#include "MachineState.h" +#include "../MachineState.h" enum class InterpreterQuirks { COSMAC_SHIFT = 1 << 0, diff --git a/src/bitops.h b/src/old/bitops.h similarity index 100% rename from src/bitops.h rename to src/old/bitops.h