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(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})
|
14
cpu/cpu.c
14
cpu/cpu.c
|
@ -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 ===
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
#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
27
main.c
|
@ -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;
|
||||||
}
|
}
|
28
system.c
28
system.c
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue