GUI and CPU/PPU timing
This commit is contained in:
parent
9629efeeb9
commit
b7287c5786
|
@ -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})
|
14
cpu/cpu.c
14
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 ===
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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})
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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();
|
||||
|
|
27
main.c
27
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;
|
||||
}
|
24
system.c
24
system.c
|
@ -27,32 +27,14 @@ void system_start() {
|
|||
cpu_get_state()->program_counter = pc;
|
||||
}
|
||||
|
||||
void system_loop() {
|
||||
assert(CPU_CLOCK_DIVISOR > PPU_CLOCK_DIVISOR);
|
||||
|
||||
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) {
|
||||
void system_next_frame() {
|
||||
for (int cpu_c = 0; cpu_c < CPU_CYCLE_PER_FRAME; cpu_c++) {
|
||||
cpu_cycle();
|
||||
}
|
||||
cpu_cycle_count++;
|
||||
|
||||
for (int ppu_c = 0; ppu_c < ppu_cycle_per_cpu_cycle; ppu_c++) {
|
||||
for (int ppu_c = 0; ppu_c < PPU_CYCLE_PER_CPU_CYCLE; ppu_c++) {
|
||||
ppu_cycle();
|
||||
}
|
||||
}
|
||||
|
||||
frame++;
|
||||
usleep(17000); // Wait 16.6666ms
|
||||
}
|
||||
}
|
||||
|
||||
void system_uninit() {
|
||||
|
|
Loading…
Reference in New Issue