GUI and CPU/PPU timing

This commit is contained in:
FyloZ 2024-05-17 00:33:37 -04:00
parent 9629efeeb9
commit b7287c5786
Signed by: william
GPG Key ID: 835378AE9AF4AE97
11 changed files with 226 additions and 38 deletions

View File

@ -13,6 +13,7 @@ add_subdirectory(mappers)
add_subdirectory(rom) add_subdirectory(rom)
add_subdirectory(debugger) add_subdirectory(debugger)
add_subdirectory(utils) add_subdirectory(utils)
add_subdirectory(gui)
list(APPEND EXTRA_INCLUDES list(APPEND EXTRA_INCLUDES
"${PROJECT_SOURCE_DIR}/cpu" "${PROJECT_SOURCE_DIR}/cpu"
@ -20,14 +21,15 @@ list(APPEND EXTRA_INCLUDES
"${PROJECT_SOURCE_DIR}/mappers" "${PROJECT_SOURCE_DIR}/mappers"
"${PROJECT_SOURCE_DIR}/rom" "${PROJECT_SOURCE_DIR}/rom"
"${PROJECT_SOURCE_DIR}/debugger" "${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(HEADERS include/system.h include/types.h)
set(SOURCE main.c system.c) set(SOURCE main.c system.c)
add_executable(nes_emulator ${HEADERS} ${SOURCE}) 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 target_include_directories(nes_emulator PUBLIC
"${PROJECT_BINARY_DIR}" "${PROJECT_BINARY_DIR}"
${EXTRA_INCLUDES}) ${EXTRA_INCLUDES})

View File

@ -37,10 +37,11 @@ void cpu_init() {
cpu_state.status = 0x04; cpu_state.status = 0x04;
cpu_state.oam_dma_triggered = false; cpu_state.oam_dma_triggered = false;
cpu_state.nmi_requested = false; cpu_state.nmi_requested = false;
cpu_state.busy_cycle_count = 0;
} }
void print_registers(byte op, unsigned long cycle_count) { 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, cpu_state.program_counter,
op, op,
get_op_code_name(op), get_op_code_name(op),
@ -77,6 +78,12 @@ void oam_dma_upload() {
} }
void cpu_cycle() { 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) { if (cpu_state.nmi_requested) {
cpu_process_nmi(); cpu_process_nmi();
} }
@ -87,16 +94,13 @@ void cpu_cycle() {
return; return;
} }
CPU cpu = cpu_state;
byte op = cpu_get_next_byte(); byte op = cpu_get_next_byte();
print_registers(op, system_get_cycles()); print_registers(op, system_get_cycles());
process_op_code(op); process_op_code(op);
} }
void cpu_add_cycles(unsigned int cycle_count) { void cpu_add_cycles(unsigned int cycle_count) {
system_add_cycles(cycle_count); cpu_state.busy_cycle_count += cycle_count;
} }
// === Registers === // === Registers ===

View File

@ -29,6 +29,8 @@ typedef struct cpu {
byte status; byte status;
bool oam_dma_triggered; bool oam_dma_triggered;
bool nmi_requested; bool nmi_requested;
unsigned int busy_cycle_count;
} CPU; } CPU;
CPU *cpu_get_state(); CPU *cpu_get_state();

9
gui/CMakeLists.txt Normal file
View File

@ -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})

21
gui/canvas.c Normal file
View File

@ -0,0 +1,21 @@
//
// Created by william on 16/05/24.
//
#include <assert.h>
#include <string.h>
#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);
}

29
gui/canvas.h Normal file
View File

@ -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

85
gui/gui.c Normal file
View File

@ -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;
}

30
gui/gui.h Normal file
View File

@ -0,0 +1,30 @@
//
// Created by william on 16/05/24.
//
#ifndef NES_EMULATOR_GUI_H
#define NES_EMULATOR_GUI_H
#include <SDL.h>
#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

View File

@ -9,11 +9,13 @@
#ifndef NESEMULATOR_SYSTEM_H #ifndef NESEMULATOR_SYSTEM_H
#define 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 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_REGISTERS_BASE_ADDR 0x2000
#define PPU_REGISTER_OAM_DMA_ADDR 0x4014 #define PPU_REGISTER_OAM_DMA_ADDR 0x4014
@ -33,6 +35,8 @@ void system_init();
void system_start(); void system_start();
void system_next_frame();
/** /**
* Starts the main loop of a system. * Starts the main loop of a system.
*/ */
@ -44,6 +48,7 @@ void system_loop();
void system_uninit(); void system_uninit();
unsigned int system_get_cycles(); unsigned int system_get_cycles();
void system_add_cycles(unsigned int cycles); void system_add_cycles(unsigned int cycles);
Mapper *system_get_mapper(); Mapper *system_get_mapper();

27
main.c
View File

@ -21,22 +21,41 @@
#include "include/rom.h" #include "include/rom.h"
#include "include/system.h" #include "include/system.h"
#include "gui.h"
int main() { int main() {
log_set_level(LOG_INFO); 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)) { if (!rom_load(rom_path)) {
system_uninit(); system_uninit();
return EXIT_FAILURE; return EXIT_FAILURE;
} }
gui_init();
system_start(); 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(); system_uninit();
gui_uninit();
//// start_debugger();
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@ -27,31 +27,13 @@ void system_start() {
cpu_get_state()->program_counter = pc; cpu_get_state()->program_counter = pc;
} }
void system_loop() { void system_next_frame() {
assert(CPU_CLOCK_DIVISOR > PPU_CLOCK_DIVISOR); 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; for (int ppu_c = 0; ppu_c < PPU_CYCLE_PER_CPU_CYCLE; ppu_c++) {
unsigned int cpu_cycle_per_frame = master_cycle_per_frame / CPU_CLOCK_DIVISOR; ppu_cycle();
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();
}
} }
frame++;
usleep(17000); // Wait 16.6666ms
} }
} }