diff --git a/CMakeLists.txt b/CMakeLists.txt index af9fce9..1564b1e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,7 @@ add_subdirectory(mappers) add_subdirectory(rom) add_subdirectory(debugger) add_subdirectory(utils) +add_subdirectory(gui) list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/cpu" @@ -20,14 +21,15 @@ list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/mappers" "${PROJECT_SOURCE_DIR}/rom" "${PROJECT_SOURCE_DIR}/debugger" - "${PROJECT_SOURCE_DIR}/utils") + "${PROJECT_SOURCE_DIR}/utils" + "${PROJECT_SOURCE_DIR}/gui") set(HEADERS include/system.h include/types.h) set(SOURCE main.c system.c) add_executable(nes_emulator ${HEADERS} ${SOURCE}) -target_link_libraries(nes_emulator nes_cpu nes_ppu nes_mappers nes_rom nes_debugger nes_utils log.c) +target_link_libraries(nes_emulator nes_cpu nes_ppu nes_mappers nes_rom nes_debugger nes_utils nes_gui log.c) target_include_directories(nes_emulator PUBLIC "${PROJECT_BINARY_DIR}" ${EXTRA_INCLUDES}) \ No newline at end of file diff --git a/cpu/cpu.c b/cpu/cpu.c index c5a9d8d..d3de015 100644 --- a/cpu/cpu.c +++ b/cpu/cpu.c @@ -37,10 +37,11 @@ void cpu_init() { cpu_state.status = 0x04; cpu_state.oam_dma_triggered = false; cpu_state.nmi_requested = false; + cpu_state.busy_cycle_count = 0; } void print_registers(byte op, unsigned long cycle_count) { - log_info("%#02x %#02x %s \t A:%#02x X:%#02x Y:%#02x F:%#02x SP:%#02x \t [%d]", + log_debug("%#02x %#02x %s \t A:%#02x X:%#02x Y:%#02x F:%#02x SP:%#02x \t [%d]", cpu_state.program_counter, op, get_op_code_name(op), @@ -77,6 +78,12 @@ void oam_dma_upload() { } void cpu_cycle() { + if (cpu_state.busy_cycle_count > 0) { + // The last operation is not done yet + cpu_state.busy_cycle_count--; + return; + } + if (cpu_state.nmi_requested) { cpu_process_nmi(); } @@ -87,16 +94,13 @@ void cpu_cycle() { return; } - CPU cpu = cpu_state; byte op = cpu_get_next_byte(); - print_registers(op, system_get_cycles()); - process_op_code(op); } void cpu_add_cycles(unsigned int cycle_count) { - system_add_cycles(cycle_count); + cpu_state.busy_cycle_count += cycle_count; } // === Registers === diff --git a/cpu/cpu.h b/cpu/cpu.h index d24fb97..fb451c7 100644 --- a/cpu/cpu.h +++ b/cpu/cpu.h @@ -29,6 +29,8 @@ typedef struct cpu { byte status; bool oam_dma_triggered; bool nmi_requested; + + unsigned int busy_cycle_count; } CPU; CPU *cpu_get_state(); diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt new file mode 100644 index 0000000..9f32737 --- /dev/null +++ b/gui/CMakeLists.txt @@ -0,0 +1,9 @@ +set(HEADERS canvas.h gui.h) +set(SOURCE canvas.c gui.c) + +add_library(nes_gui ${SOURCE} ${HEADERS}) + +find_package(SDL2 REQUIRED) +include_directories(nes_gui ${SDL2_INCLUDE_DIRS}) + +target_link_libraries(nes_gui log.c ${SDL2_LIBRARIES}) \ No newline at end of file diff --git a/gui/canvas.c b/gui/canvas.c new file mode 100644 index 0000000..b98753f --- /dev/null +++ b/gui/canvas.c @@ -0,0 +1,21 @@ +// +// Created by william on 16/05/24. +// + +#include +#include +#include "canvas.h" + +void canvas_draw(Canvas *canvas, Pixel pixel, int x, int y) { + assert(x >= 0); + assert(x < CANVAS_WIDTH); + assert(y >= 0); + assert(y < CANVAS_HEIGHT); + + int pixel_index = CANVAS_INDEX(x, y); + canvas->pixels[pixel_index] = pixel; +} + +void canvas_reset(Canvas *canvas) { + memset(canvas->pixels, 0, sizeof(Pixel) * CANVAS_PIXEL_COUNT); +} \ No newline at end of file diff --git a/gui/canvas.h b/gui/canvas.h new file mode 100644 index 0000000..7c68c48 --- /dev/null +++ b/gui/canvas.h @@ -0,0 +1,29 @@ +// +// Created by william on 16/05/24. +// + +#ifndef NES_EMULATOR_CANVAS_H +#define NES_EMULATOR_CANVAS_H + +#include "../include/types.h" + +#define CANVAS_WIDTH 256 +#define CANVAS_HEIGHT 240 +#define CANVAS_PIXEL_COUNT (CANVAS_WIDTH * CANVAS_HEIGHT) + +#define CANVAS_INDEX(x, y) (y * CANVAS_WIDTH + x) + +typedef struct pixel { + byte r; + byte g; + byte b; +} Pixel; + +typedef struct canvas { + Pixel pixels[CANVAS_PIXEL_COUNT]; +} Canvas; + +void canvas_draw(Canvas *canvas, Pixel pixel, int x, int y); +void canvas_reset(Canvas *canvas); + +#endif //NES_EMULATOR_CANVAS_H diff --git a/gui/gui.c b/gui/gui.c new file mode 100644 index 0000000..caba275 --- /dev/null +++ b/gui/gui.c @@ -0,0 +1,85 @@ +// +// Created by william on 16/05/24. +// + +#include "gui.h" +#include "log.h" + +NesGui gui; + +int gui_init() { + int renderer_flags = SDL_RENDERER_ACCELERATED; + int window_flags = 0; + + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + log_error("Couldn't initialize SDL: %s", SDL_GetError()); + return -1; + } + + gui.window = SDL_CreateWindow("NES Emulator", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, GUI_WIDTH, + GUI_HEIGHT, window_flags); + if (!gui.window) { + log_error("Failed to open %d x %d window: %s", GUI_WIDTH, GUI_HEIGHT, SDL_GetError()); + return -1; + } + + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear"); + + gui.renderer = SDL_CreateRenderer(gui.window, -1, renderer_flags); + if (!gui.renderer) { + log_error("Failed to create renderer: %s\n", SDL_GetError()); + return -1; + } + + return 1; +} + +int gui_input() { + SDL_Event event; + + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + return -1; + default: + break; + } + } + + return 1; +} + +void gui_prepare() { + SDL_SetRenderDrawColor(gui.renderer, 96, 128, 255, 255); + SDL_RenderClear(gui.renderer); +} + +void gui_render() { + for (int x = 0; x < CANVAS_WIDTH; x++) { + for (int y = 0; y < CANVAS_HEIGHT; y++) { + int pixel_index = CANVAS_INDEX(x, y); + Pixel pixel = gui.canvas.pixels[pixel_index]; + + SDL_SetRenderDrawColor(gui.renderer, pixel.r, pixel.g, pixel.b, 255); + + for (int i = 0; i < GUI_SCALING; i++) { + for (int j = 0; j < GUI_SCALING; j++) { + int scaled_x = x * GUI_SCALING + i; + int scaled_y = y * GUI_SCALING + j; + SDL_RenderDrawPoint(gui.renderer, scaled_x, scaled_y); + } + } + } + } +} + +void gui_present() { + SDL_RenderPresent(gui.renderer); +} + +void gui_uninit() { +} + +Canvas *gui_get_canvas() { + return &gui.canvas; +} \ No newline at end of file diff --git a/gui/gui.h b/gui/gui.h new file mode 100644 index 0000000..616a0c0 --- /dev/null +++ b/gui/gui.h @@ -0,0 +1,30 @@ +// +// Created by william on 16/05/24. +// + +#ifndef NES_EMULATOR_GUI_H +#define NES_EMULATOR_GUI_H + +#include +#include "canvas.h" + +#define GUI_SCALING 3 +#define GUI_WIDTH CANVAS_WIDTH * GUI_SCALING +#define GUI_HEIGHT CANVAS_HEIGHT * GUI_SCALING + +typedef struct nes_gui { + SDL_Renderer *renderer; + SDL_Window *window; + Canvas canvas; +} NesGui; + +int gui_init(); +int gui_input(); +void gui_prepare(); +void gui_render(); +void gui_present(); +void gui_uninit(); + +Canvas *gui_get_canvas(); + +#endif //NES_EMULATOR_GUI_H diff --git a/include/system.h b/include/system.h index 65166e4..4fa0351 100644 --- a/include/system.h +++ b/include/system.h @@ -9,11 +9,13 @@ #ifndef NESEMULATOR_SYSTEM_H #define NESEMULATOR_SYSTEM_H -// NTSC NES Master Clock (~21.47 MHz) -#define MASTER_CLOCK 21477272 -#define CPU_CLOCK_DIVISOR 12 -#define PPU_CLOCK_DIVISOR 4 #define FRAME_RATE 60 +#define MASTER_CLOCK 21477272 // NTSC NES Master Clock (~21.47 MHz) +#define MASTER_CYCLE_PER_FRAME (MASTER_CLOCK / FRAME_RATE) +#define CPU_CLOCK_DIVISOR 12 +#define CPU_CYCLE_PER_FRAME (MASTER_CYCLE_PER_FRAME / CPU_CLOCK_DIVISOR) +#define PPU_CLOCK_DIVISOR 4 +#define PPU_CYCLE_PER_CPU_CYCLE (CPU_CLOCK_DIVISOR / PPU_CLOCK_DIVISOR) #define PPU_REGISTERS_BASE_ADDR 0x2000 #define PPU_REGISTER_OAM_DMA_ADDR 0x4014 @@ -33,6 +35,8 @@ void system_init(); void system_start(); +void system_next_frame(); + /** * Starts the main loop of a system. */ @@ -44,6 +48,7 @@ void system_loop(); void system_uninit(); unsigned int system_get_cycles(); + void system_add_cycles(unsigned int cycles); Mapper *system_get_mapper(); diff --git a/main.c b/main.c index 900f7ec..4155c68 100644 --- a/main.c +++ b/main.c @@ -21,22 +21,41 @@ #include "include/rom.h" #include "include/system.h" +#include "gui.h" int main() { log_set_level(LOG_INFO); - system_init(); - char *rom_path = "../test_roms/dk_japan.nes"; + system_init(); + char *rom_path = "../test_roms/dk_jp.nes"; if (!rom_load(rom_path)) { system_uninit(); return EXIT_FAILURE; } + gui_init(); system_start(); -// start_debugger(); - system_loop(); + + bool stop = false; + while (!stop) { + gui_prepare(); + + if (gui_input() < 0) { + stop = true; + } + + system_next_frame(); + + gui_render(); + gui_present(); + + SDL_Delay(16); + } system_uninit(); + gui_uninit(); + +//// start_debugger(); return EXIT_SUCCESS; } \ No newline at end of file diff --git a/system.c b/system.c index 08ba5e1..6485fd0 100644 --- a/system.c +++ b/system.c @@ -27,31 +27,13 @@ void system_start() { cpu_get_state()->program_counter = pc; } -void system_loop() { - assert(CPU_CLOCK_DIVISOR > PPU_CLOCK_DIVISOR); +void system_next_frame() { + for (int cpu_c = 0; cpu_c < CPU_CYCLE_PER_FRAME; cpu_c++) { + cpu_cycle(); - unsigned int master_cycle_per_frame = MASTER_CLOCK / FRAME_RATE; - unsigned int cpu_cycle_per_frame = master_cycle_per_frame / CPU_CLOCK_DIVISOR; - unsigned int ppu_cycle_per_cpu_cycle = CPU_CLOCK_DIVISOR / PPU_CLOCK_DIVISOR; - - long frame = 1; - long cpu_cycle_count = 0; - while (true) { -// log_info("Frame %d", frame); - - while (current_sys.cycle_count < cpu_cycle_per_frame * frame) { - if (cpu_cycle_count == current_sys.cycle_count) { - cpu_cycle(); - } - cpu_cycle_count++; - - for (int ppu_c = 0; ppu_c < ppu_cycle_per_cpu_cycle; ppu_c++) { - ppu_cycle(); - } + for (int ppu_c = 0; ppu_c < PPU_CYCLE_PER_CPU_CYCLE; ppu_c++) { + ppu_cycle(); } - - frame++; - usleep(17000); // Wait 16.6666ms } }