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