Add working basic component based GUI system. Allows to open and close windows without restarting.

This commit is contained in:
william 2024-10-03 19:24:03 -04:00
parent f18ad715fb
commit eee13026b0
25 changed files with 364 additions and 186 deletions

View File

@ -3,7 +3,6 @@ This is the repository of a Nintendo Entertainment System (NES) emulator in acti
currently complete but can run and partially display a ROM. Note that the project may support Windows and MacOS, but was currently complete but can run and partially display a ROM. Note that the project may support Windows and MacOS, but was
only tested on Linux. Here is how to run the project: only tested on Linux. Here is how to run the project:
- Change the ```rom_path``` at line 26 of ```main.c``` - Change the ```rom_path``` at line 26 of ```main.c```
- Optionally, change ```gui.debug_enabled``` to ```true``` at line 33 of ```gui.c``` to enable debugging
- Generate the Makefile with CMake: ```cmake .``` - Generate the Makefile with CMake: ```cmake .```
- Build the project with Make: ```make``` - Build the project with Make: ```make```
- Run the emulator: ```./nes_emulator``` - Run the emulator: ```./nes_emulator```
@ -21,6 +20,7 @@ only tested on Linux. Here is how to run the project:
- SDL - SDL
## Development Roadmap ## Development Roadmap
- Basic component based GUI: Done
- CPU - CPU
- RAM: Done - RAM: Done
- ROM: Done (iNes 1.0 format only) - ROM: Done (iNes 1.0 format only)
@ -42,7 +42,7 @@ only tested on Linux. Here is how to run the project:
- MMC5: To Do - MMC5: To Do
- ... - ...
- Debug - Debug
- Frame Delay: Done - Frame Delay: Broken, removed
- Pattern Table Viewer: Done - Pattern Table Viewer: Done
- Nametable Viewer: Done - Nametable Viewer: Done
- CPU Debugger: To Do - CPU Debugger: To Do

View File

@ -1,10 +1,5 @@
set(HEADERS gui.h main_window.h) set(HEADERS actions.h gui.h main_window.h char_map.h pattern_window.h nametable_window.h dbg_pattern_table.h dbg_nametable.h dbg_palette.h)
set(SOURCE gui.c main_window.c) set(SOURCE actions.c gui.c main_window.c char_map.c pattern_window.c nametable_window.c dbg_pattern_table.c dbg_nametable.c dbg_palette.c)
if (NES_DEBUG)
list(APPEND HEADERS char_map.h pattern_window.h nametable_window.h dbg_pattern_table.h dbg_nametable.h dbg_palette.h)
list(APPEND SOURCE char_map.c pattern_window.c nametable_window.c dbg_pattern_table.c dbg_nametable.c dbg_palette.c)
endif (NES_DEBUG)
add_library(nes_gui ${SOURCE} ${HEADERS}) add_library(nes_gui ${SOURCE} ${HEADERS})

17
gui/actions.c Normal file
View File

@ -0,0 +1,17 @@
//
// Created by william on 9/2/24.
//
#include "actions.h"
#include "gui.h"
void process_action(ActionType type) {
switch (type) {
case ACTION_TYPE_OPEN_WINDOW_NAMETABLE:
gui_window_create(WINDOW_TYPE_NAMETABLE);
break;
case ACTION_TYPE_OPEN_WINDOW_PATTERN_TABLE:
gui_window_create(WINDOW_TYPE_PATTERN_TABLE);
break;
}
}

15
gui/actions.h Normal file
View File

@ -0,0 +1,15 @@
//
// Created by william on 9/2/24.
//
#ifndef NES_EMULATOR_ACTIONS_H
#define NES_EMULATOR_ACTIONS_H
typedef enum {
ACTION_TYPE_OPEN_WINDOW_NAMETABLE = 1,
ACTION_TYPE_OPEN_WINDOW_PATTERN_TABLE = 2
} ActionType;
void process_action(ActionType type);
#endif //NES_EMULATOR_ACTIONS_H

View File

@ -7,6 +7,9 @@
#include "log.h" #include "log.h"
#include "component.h" #include "component.h"
// A sequential window ID
static int next_window_id = 1;
/** /**
* Easy declaration of commonly used component loop. The current component is in the 'component' variable. * Easy declaration of commonly used component loop. The current component is in the 'component' variable.
* Put the loop's body between FOR_EACH_COMPONENT and END_FOR_EACH_COMPONENT. * Put the loop's body between FOR_EACH_COMPONENT and END_FOR_EACH_COMPONENT.
@ -68,6 +71,7 @@ Window window_create(char *title, int width, int height, int scale) {
} }
Window window; Window window;
window.id = next_window_id++;
window.width = width; window.width = width;
window.height = height; window.height = height;
window.scale = scale; window.scale = scale;

View File

@ -15,6 +15,7 @@ typedef struct window_sdl_context {
} WindowSdlContext; } WindowSdlContext;
typedef struct window { typedef struct window {
int id;
int width; int width;
int height; int height;
int scale; int scale;

View File

@ -49,12 +49,13 @@ static inline MenuItemComponent *menu_get_next_item(LinkedListCursor *cursor) {
return next_node->data; return next_node->data;
} }
MenuComponent *menu_create(Window *window, TTF_Font *font) { MenuComponent *menu_create(Window *window, TTF_Font *font, menu_action_processor action_processor) {
MenuComponent *menu = malloc(sizeof(MenuComponent)); MenuComponent *menu = malloc(sizeof(MenuComponent));
menu->window_width = window->width * window->scale; menu->window_width = window->width * window->scale;
menu->visible = false; menu->visible = false;
menu->action_processor = action_processor;
menu->items = linked_list_create(false); menu->items = linked_list_create(false);
menu->highlight_item = NULL; menu->highlight_item = NULL;
@ -66,11 +67,11 @@ MenuComponent *menu_create(Window *window, TTF_Font *font) {
return menu; return menu;
} }
MenuItemComponent *menu_item_create(char *label, on_click_callback on_click) { MenuItemComponent *menu_item_create(char *label, int click_action_type) {
MenuItemComponent *menu_item = malloc(sizeof(MenuItemComponent)); MenuItemComponent *menu_item = malloc(sizeof(MenuItemComponent));
menu_item->label = label; menu_item->label = label;
menu_item->on_click = on_click; menu_item->click_action_type = click_action_type;
menu_item->sub_items = linked_list_create(false); menu_item->sub_items = linked_list_create(false);
menu_item->is_highlighted = false; menu_item->is_highlighted = false;
@ -234,7 +235,9 @@ bool menu_mouse_click(MenuComponent *menu) {
return false; return false;
} }
log_info("%s", menu->highlight_item->label); int action_type = menu->highlight_item->click_action_type;
menu->action_processor(action_type);
return true; return true;
} }

View File

@ -17,11 +17,9 @@
#define MENU_ITEM_MARGIN_X 20 #define MENU_ITEM_MARGIN_X 20
#define MENU_ITEM_MARGIN_Y 4 #define MENU_ITEM_MARGIN_Y 4
typedef void (*on_click_callback)();
typedef struct menu_item_component { typedef struct menu_item_component {
char *label; char *label;
on_click_callback on_click; int click_action_type;
bool is_highlighted; bool is_highlighted;
@ -31,10 +29,13 @@ typedef struct menu_item_component {
SDL_Rect collision_rect; SDL_Rect collision_rect;
} MenuItemComponent; } MenuItemComponent;
typedef void (*menu_action_processor)(int);
typedef struct menu_component { typedef struct menu_component {
int window_width; int window_width;
bool visible; bool visible;
menu_action_processor action_processor;
LinkedList items; LinkedList items;
MenuItemComponent *highlight_item; MenuItemComponent *highlight_item;
@ -48,9 +49,10 @@ typedef struct menu_component {
* Creates a menu fow a window. * Creates a menu fow a window.
* @param window A reference to the window * @param window A reference to the window
* @param font A reference to the TTF font to use to render text * @param font A reference to the TTF font to use to render text
* @param action_processor A reference to a function that will processes actions
* @return A reference to the menu component * @return A reference to the menu component
*/ */
MenuComponent *menu_create(Window *window, TTF_Font *font); MenuComponent *menu_create(Window *window, TTF_Font *font, menu_action_processor action_processor);
/** /**
* Creates a menu item. Can be configured with a callback function which will be called when the menu item is clicked. * Creates a menu item. Can be configured with a callback function which will be called when the menu item is clicked.
@ -59,7 +61,7 @@ MenuComponent *menu_create(Window *window, TTF_Font *font);
* @param on_click The callback function to call when clicked * @param on_click The callback function to call when clicked
* @return A reference to the menu item * @return A reference to the menu item
*/ */
MenuItemComponent *menu_item_create(char *label, on_click_callback on_click); MenuItemComponent *menu_item_create(char *label, int click_action_type);
/** /**
* Adds an item to a menu. * Adds an item to a menu.

View File

@ -12,7 +12,15 @@
DebugNameTable dbg_nametable; DebugNameTable dbg_nametable;
void dbg_nametable_init() { void dbg_nametable_init() {
if (dbg_nametable.initialized) {
// Already initialized
return;
}
dbg_pattern_table_init();
dbg_nametable.vertical_mirroring = rom_get()->nametable_mirrored; dbg_nametable.vertical_mirroring = rom_get()->nametable_mirrored;
dbg_nametable.initialized = true;
} }
void dbg_nametable_build_bank(byte *nametable, DebugTile *bank) { void dbg_nametable_build_bank(byte *nametable, DebugTile *bank) {

View File

@ -22,6 +22,7 @@ typedef struct dbg_nametable {
DebugTile bank_0[NAMETABLE_BANK_SIZE]; DebugTile bank_0[NAMETABLE_BANK_SIZE];
DebugTile bank_1[NAMETABLE_BANK_SIZE]; DebugTile bank_1[NAMETABLE_BANK_SIZE];
bool vertical_mirroring; bool vertical_mirroring;
bool initialized;
} DebugNameTable; } DebugNameTable;
/** /**

View File

@ -18,7 +18,7 @@ pixel dbg_color_list[0x40] = COLOR_LIST;
COPY_PALETTE((memory)[(base_addr) + 0xd], (dest)[3]) \ COPY_PALETTE((memory)[(base_addr) + 0xd], (dest)[3]) \
void dbg_palette_init() { void dbg_palette_update() {
byte *memory = ppu_get_state()->memory.palette; byte *memory = ppu_get_state()->memory.palette;
palette_memory.universal_background_color = memory[0]; palette_memory.universal_background_color = memory[0];

View File

@ -19,7 +19,7 @@ typedef struct dbg_palette_memory {
DebugPalette sprite_palettes[PALETTE_COUNT]; DebugPalette sprite_palettes[PALETTE_COUNT];
} DebugPaletteMemory; } DebugPaletteMemory;
void dbg_palette_init(); void dbg_palette_update();
pixel dbg_get_background_color(byte palette, byte data); pixel dbg_get_background_color(byte palette, byte data);

View File

@ -23,10 +23,16 @@ void dbg_pattern_table_build_bank(DebugPattern *bank, byte *pattern_memory) {
} }
void dbg_pattern_table_init() { void dbg_pattern_table_init() {
if (pattern_table.initialized) {
// Already initialized
return;
}
byte *pattern_memory = system_get_mapper()->ppu_read(0); byte *pattern_memory = system_get_mapper()->ppu_read(0);
dbg_pattern_table_build_bank(pattern_table.bank_0, pattern_memory); dbg_pattern_table_build_bank(pattern_table.bank_0, pattern_memory);
dbg_pattern_table_build_bank(pattern_table.bank_1, &pattern_memory[PATTERN_BANK_SIZE]); dbg_pattern_table_build_bank(pattern_table.bank_1, &pattern_memory[PATTERN_BANK_SIZE]);
pattern_table.initialized = true;
} }
DebugPattern dbg_pattern_get(int pattern_id, int bank) { DebugPattern dbg_pattern_get(int pattern_id, int bank) {

View File

@ -5,6 +5,7 @@
#ifndef NES_EMULATOR_DBG_PATTERN_TABLE_H #ifndef NES_EMULATOR_DBG_PATTERN_TABLE_H
#define NES_EMULATOR_DBG_PATTERN_TABLE_H #define NES_EMULATOR_DBG_PATTERN_TABLE_H
#include <stdbool.h>
#include "../include/types.h" #include "../include/types.h"
#define PATTERN_BANK_SIZE 0x1000 #define PATTERN_BANK_SIZE 0x1000
@ -29,6 +30,7 @@ typedef struct dbg_pattern {
typedef struct dbg_pattern_table { typedef struct dbg_pattern_table {
DebugPattern bank_0[PATTERN_TABLE_SIZE]; DebugPattern bank_0[PATTERN_TABLE_SIZE];
DebugPattern bank_1[PATTERN_TABLE_SIZE]; DebugPattern bank_1[PATTERN_TABLE_SIZE];
bool initialized;
} DebugPatternTable; } DebugPatternTable;
/** /**

199
gui/gui.c
View File

@ -3,6 +3,7 @@
// //
#include <SDL_ttf.h> #include <SDL_ttf.h>
#include <assert.h>
#include "log.h" #include "log.h"
#include "gui.h" #include "gui.h"
#include "main_window.h" #include "main_window.h"
@ -14,29 +15,95 @@
#include "char_map.h" #include "char_map.h"
#include "dbg_palette.h" #include "dbg_palette.h"
#if DEBUG
#define WINDOW_ID_MAIN 3
#else
#define WINDOW_ID_MAIN 1
#endif
typedef struct nes_gui { typedef struct nes_gui {
NesMainWindow main_window; bool window_types_open[WINDOW_TYPE_MAX + 1];
NesPatternWindow pattern_window; void *window_types_ref[WINDOW_TYPE_MAX + 1];
NesNametableWindow nametable_window; int window_types_ids[WINDOW_TYPE_MAX + 1];
TTF_Font *font; TTF_Font *font;
Uint32 last_frame_tick; Uint32 last_frame_tick;
Uint32 frame_delay; Uint32 frame_delay;
#if DEBUG
unsigned long tick; unsigned long tick;
#endif
} NesGui; } NesGui;
NesGui gui; NesGui gui;
#define GUI_WINDOW_OPEN_(window_type) gui.window_types_open[window_type]
#define GUI_WINDOW_REF_(window_type) gui.window_types_ref[window_type]
#define GUI_WINDOW_ACTION_BASE(window_type, call) \
if (GUI_WINDOW_OPEN_(window_type)) { \
call(GUI_WINDOW_REF_(window_type)); \
}
void gui_window_create(WindowType type) {
void **ref = &gui.window_types_ref[type];
if (gui.window_types_open[type]) {
return;
}
gui.window_types_open[type] = true;
int window_id = -1;
switch (type) {
case WINDOW_TYPE_MAIN:
*ref = main_window_create(&window_id);
break;
case WINDOW_TYPE_NAMETABLE:
*ref = nametable_window_create(&window_id);
break;
case WINDOW_TYPE_PATTERN_TABLE:
*ref = pattern_window_create(&window_id);
break;
}
gui.window_types_ids[type] = window_id;
}
void gui_window_destroy(WindowType type) {
bool *open = &gui.window_types_open[type];
if (*open == false) {
// The window doesn't exist
return;
}
void *ref = gui.window_types_ref[type];
switch (type) {
case WINDOW_TYPE_MAIN:
main_window_destroy(ref);
break;
case WINDOW_TYPE_NAMETABLE:
nametable_window_destroy(ref);
break;
case WINDOW_TYPE_PATTERN_TABLE:
pattern_window_destroy(ref);
break;
}
gui.window_types_ids[type] = -1;
*open = false;
}
void gui_window_destroy_by_id(int window_id) {
WindowType type = -1;
for (int i = 0; i <= WINDOW_TYPE_MAX; i++) {
if (gui.window_types_ids[i] == window_id) {
type = i;
}
}
if (type == -1) {
// Close event is sent twice?
log_error("Couldn't find window with ID %d", window_id);
return;
}
gui_window_destroy(type);
}
bool gui_init() { bool gui_init() {
memset(gui.window_types_open, false, sizeof(bool) * WINDOW_TYPE_MAX);
memset(gui.window_types_ref, 0, sizeof(void *) * WINDOW_TYPE_MAX);
TTF_Init(); TTF_Init();
gui.font = TTF_OpenFont("./nintendo-nes-font.ttf", 16); gui.font = TTF_OpenFont("./nintendo-nes-font.ttf", 16);
if (gui.font == NULL) { if (gui.font == NULL) {
@ -44,56 +111,40 @@ bool gui_init() {
return false; return false;
} }
#if DEBUG memset(gui.window_types_ids, -1, (WINDOW_TYPE_MAX + 1) * sizeof(*gui.window_types_ids));
gui.tick = 0; gui_window_create(WINDOW_TYPE_MAIN);
pattern_window_init(&gui.pattern_window);
nametable_window_init(&gui.nametable_window);
char_map_init(gui.main_window.window.sdl_context.renderer, gui.font);
#endif
main_window_init(&gui.main_window);
return true; return true;
} }
void gui_uninit() { void gui_free() {
main_window_uninit(&gui.main_window); for (int i = 0; i < WINDOW_TYPE_MAX; i++) {
gui_window_destroy(i);
#if DEBUG }
char_map_uninit();
pattern_window_uninit(&gui.pattern_window);
nametable_window_uninit(&gui.nametable_window);
#endif
TTF_CloseFont(gui.font); TTF_CloseFont(gui.font);
} }
void gui_post_sysinit() {
#if DEBUG
dbg_palette_init();
dbg_pattern_table_init();
dbg_nametable_init();
// TODO: The background_texture is rendered before the palette data is in the PPU memory, so the only color is grey
pattern_window_build_table(&gui.pattern_window);
nametable_window_update(&gui.nametable_window);
#endif
}
bool lctrl = false; bool lctrl = false;
int gui_input() { int gui_input() {
assert(gui.window_types_open[WINDOW_TYPE_MAIN]);
SDL_Event event; SDL_Event event;
#if DEBUG
PPUDebugFlags *ppu_debug = &ppu_get_state()->debug; PPUDebugFlags *ppu_debug = &ppu_get_state()->debug;
#endif
while (SDL_PollEvent(&event)) { while (SDL_PollEvent(&event)) {
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE) { if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE) {
return -1; Uint32 window_id = event.window.windowID;
for (int type = 0; type <= WINDOW_TYPE_MAX; type++) {
bool is_main_window = gui.window_types_ids[WINDOW_TYPE_MAIN] == window_id;
gui_window_destroy_by_id(window_id);
if (is_main_window) {
return -1;
}
}
} }
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_LCTRL) { if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_LCTRL) {
@ -108,59 +159,53 @@ int gui_input() {
case SDLK_p: case SDLK_p:
system_toggle_pause(lctrl); system_toggle_pause(lctrl);
break; break;
#if DEBUG case SDLK_t:
case SDLK_t: ppu_debug->flags.tile_debugger = !ppu_debug->flags.tile_debugger;
ppu_debug->flags.tile_debugger = !ppu_debug->flags.tile_debugger; break;
break; case SDLK_n:
case SDLK_n: ppu_debug->flags.tile_debugger_pattern_half = (ppu_debug->flags.tile_debugger_pattern_half + 1) % 3;
ppu_debug->flags.tile_debugger_pattern_half = (ppu_debug->flags.tile_debugger_pattern_half + 1) % 3; break;
break; default:
default: if (gui.window_types_open[WINDOW_TYPE_PATTERN_TABLE]) {
pattern_window_key_up(&gui.pattern_window, event.key.keysym.sym); pattern_window_key_up(gui.window_types_ref[WINDOW_TYPE_PATTERN_TABLE], event.key.keysym.sym);
break; }
#else
default:
break; break;
#endif
} }
} }
void *main_window_ref = gui.window_types_ref[WINDOW_TYPE_MAIN];
if (event.type == SDL_MOUSEMOTION) { if (event.type == SDL_MOUSEMOTION) {
int x = event.motion.x; int x = event.motion.x;
int y = event.motion.y; int y = event.motion.y;
if (event.window.windowID == WINDOW_ID_MAIN) { if (gui.window_types_ids[WINDOW_TYPE_MAIN] == event.window.windowID) {
main_window_mouse_motion(&gui.main_window, x, y); main_window_mouse_motion(main_window_ref, x, y);
} }
} }
if (event.type == SDL_MOUSEBUTTONUP && event.window.windowID == WINDOW_ID_MAIN) { if (event.type == SDL_MOUSEBUTTONUP && gui.window_types_ids[WINDOW_TYPE_MAIN] == event.window.windowID) {
main_window_mouse_click(&gui.main_window); main_window_mouse_click(main_window_ref);
} }
} }
return 1; return 1;
} }
#define GUI_WINDOW_RENDER(window_type, prefix) GUI_WINDOW_ACTION_BASE(window_type, prefix ## _window_render)
void gui_render() { void gui_render() {
main_window_render(&gui.main_window, ppu_get_state()->pixels); assert(gui.window_types_open[WINDOW_TYPE_MAIN]);
main_window_render(gui.window_types_ref[WINDOW_TYPE_MAIN], ppu_get_state()->pixels);
#if DEBUG // Update the nametable
dbg_palette_init(); GUI_WINDOW_ACTION_BASE(WINDOW_TYPE_NAMETABLE, nametable_window_update);
pattern_window_render(&gui.pattern_window); GUI_WINDOW_RENDER(WINDOW_TYPE_NAMETABLE, nametable);
nametable_window_update(&gui.nametable_window); // Update the pattern table
nametable_window_render(&gui.nametable_window); GUI_WINDOW_ACTION_BASE(WINDOW_TYPE_PATTERN_TABLE, pattern_window_update);
GUI_WINDOW_RENDER(WINDOW_TYPE_PATTERN_TABLE, pattern)
gui.tick++; gui.tick++;
#endif
}
void gui_present() {
#if DEBUG
pattern_window_present(&gui.pattern_window);
nametable_window_present(&gui.nametable_window);
#endif
} }
void gui_delay() { void gui_delay() {
@ -178,8 +223,4 @@ void gui_delay() {
TTF_Font *gui_get_font() { TTF_Font *gui_get_font() {
return gui.font; return gui.font;
}
unsigned int gui_get_frame_delay() {
return gui.frame_delay;
} }

View File

@ -8,17 +8,50 @@
#include <stdbool.h> #include <stdbool.h>
#include <SDL_ttf.h> #include <SDL_ttf.h>
typedef enum {
WINDOW_TYPE_MAIN = 0,
WINDOW_TYPE_NAMETABLE = 1,
WINDOW_TYPE_PATTERN_TABLE = 2,
} WindowType;
#define WINDOW_TYPE_MAX WINDOW_TYPE_PATTERN_TABLE
/**
* Initializes the graphical user interface of the emulator.
* @return A boolean indicating if the GUI was successfully initialized.
*/
bool gui_init(); bool gui_init();
void gui_uninit();
void gui_post_sysinit(); /**
* Free the resources used by the graphical user interface.
*/
void gui_free();
/**
* Creates and open a window. If a window of the given type already exists, focus it.
* @param type The type of window to open.
*/
void gui_window_create(WindowType type);
/**
* Process user input events received since the last call to gui_input.
* @return An integer indicating if the user closed the main window (-1).
*/
int gui_input(); int gui_input();
/**
* Renders the graphical user interface to the screen.
*/
void gui_render(); void gui_render();
void gui_present();
/**
* Blocks until the next frame should be drawn, making the frame rate 60 hertz.
*/
void gui_delay(); void gui_delay();
/**
* Gets the font used for the graphical user interface.
* @return A reference to the TTF font.
*/
TTF_Font* gui_get_font(); TTF_Font* gui_get_font();
unsigned int gui_get_frame_delay();
#endif //NES_EMULATOR_GUI_H #endif //NES_EMULATOR_GUI_H

View File

@ -8,52 +8,46 @@
#include "char_map.h" #include "char_map.h"
#include "gui.h" #include "gui.h"
#include "components/window_menu.h" #include "components/window_menu.h"
#include "actions.h"
void main_window_init(NesMainWindow *window) { void main_window_menu_process_action(int action_type) {
process_action(action_type);
}
MainWindow *main_window_create(int* window_id) {
MainWindow *window = malloc(sizeof(MainWindow));
window->window = window_create("NES Emulator", MAIN_WINDOW_WIDTH, MAIN_WINDOW_HEIGHT, MAIN_WINDOW_SCALE); window->window = window_create("NES Emulator", MAIN_WINDOW_WIDTH, MAIN_WINDOW_HEIGHT, MAIN_WINDOW_SCALE);
window->texture = window_create_texture(&window->window, SDL_TEXTUREACCESS_STREAMING); window->texture = window_create_texture(&window->window, SDL_TEXTUREACCESS_STREAMING);
MenuComponent *menu = menu_create(&window->window, gui_get_font()); MenuComponent *menu = menu_create(&window->window, gui_get_font(), &main_window_menu_process_action);
MenuItemComponent *mi_file = menu_item_create("FILE", NULL); // MenuItemComponent *mi_file = menu_item_create("FILE", ACTION_TYPE_OPEN_);
menu_append(menu, mi_file); // menu_append(menu, mi_file);
MenuItemComponent *mi_file_open = menu_item_create("OPEN ROM...", NULL); // MenuItemComponent *mi_file_open = menu_item_create("OPEN ROM...", NULL);
menu_item_append(mi_file, mi_file_open); // menu_item_append(mi_file, mi_file_open);
MenuItemComponent *mi_debug = menu_item_create("DEBUG", NULL); MenuItemComponent *mi_debug = menu_item_create("DEBUG", -1);
menu_append(menu, mi_debug); menu_append(menu, mi_debug);
MenuItemComponent *mi_debug_nametable = menu_item_create("NAMETABLE", NULL); MenuItemComponent *mi_debug_nametable = menu_item_create("NAMETABLE", ACTION_TYPE_OPEN_WINDOW_NAMETABLE);
menu_item_append(mi_debug, mi_debug_nametable); menu_item_append(mi_debug, mi_debug_nametable);
MenuItemComponent *mi_debug_pattern = menu_item_create("PATTERN TABLE", NULL); MenuItemComponent *mi_debug_pattern = menu_item_create("PATTERN TABLE", ACTION_TYPE_OPEN_WINDOW_PATTERN_TABLE);
menu_item_append(mi_debug, mi_debug_pattern); menu_item_append(mi_debug, mi_debug_pattern);
menu_build(menu); menu_build(menu);
window_add_component(&window->window, menu_as_component(menu)); window_add_component(&window->window, menu_as_component(menu));
*window_id = window->window.id;
return window;
} }
void main_window_uninit(NesMainWindow *window) { void main_window_destroy(MainWindow *window) {
SDL_DestroyTexture(window->texture); SDL_DestroyTexture(window->texture);
window_destroy(&window->window); window_destroy(&window->window);
free(window);
} }
#if DEBUG void main_window_render(MainWindow *window, pixel *pixels) {
void main_window_render_delay(SDL_Renderer *renderer) {
Uint32 delay = gui_get_frame_delay();
char buffer[5];
buffer[0] = (char) ((delay / 10) + 48);
buffer[1] = (char) ((delay % 10) + 48);
buffer[2] = ' ';
buffer[3] = 'm';
buffer[4] = 's';
char_map_render(renderer, buffer);
}
#endif
void main_window_render(NesMainWindow *window, pixel *pixels) {
SDL_UpdateTexture(window->texture, NULL, pixels, 256 * sizeof(pixel)); SDL_UpdateTexture(window->texture, NULL, pixels, 256 * sizeof(pixel));
window_render_texture(&window->window, window->texture); window_render_texture(&window->window, window->texture);
@ -62,10 +56,10 @@ void main_window_render(NesMainWindow *window, pixel *pixels) {
window_present(&window->window); window_present(&window->window);
} }
void main_window_mouse_motion(NesMainWindow *window, int x, int y) { void main_window_mouse_motion(MainWindow *window, int x, int y) {
window_mouse_motion(&window->window, x, y); window_mouse_motion(&window->window, x, y);
} }
void main_window_mouse_click(NesMainWindow *window) { void main_window_mouse_click(MainWindow *window) {
window_mouse_click(&window->window); window_mouse_click(&window->window);
} }

View File

@ -17,13 +17,24 @@ typedef struct nes_main_window {
Window window; Window window;
SDL_Texture *texture; SDL_Texture *texture;
} NesMainWindow; } MainWindow;
void main_window_init(NesMainWindow *window); /**
void main_window_uninit(NesMainWindow *window); * Creates an instance of the main window.
* @return A reference to the created main window instance
*/
MainWindow *main_window_create(int *window_id);
void main_window_render(NesMainWindow *window, pixel* pixels); /**
void main_window_mouse_motion(NesMainWindow *window, int x, int y); * Destroys an instance of the main window.
void main_window_mouse_click(NesMainWindow *window); * @param window A reference to the window to destroy
*/
void main_window_destroy(MainWindow *window);
void main_window_render(MainWindow *window, pixel *pixels);
void main_window_mouse_motion(MainWindow *window, int x, int y);
void main_window_mouse_click(MainWindow *window);
#endif //NES_EMULATOR_MAIN_WINDOW_H #endif //NES_EMULATOR_MAIN_WINDOW_H

View File

@ -4,22 +4,32 @@
#include "nametable_window.h" #include "nametable_window.h"
#include "dbg_nametable.h" #include "dbg_nametable.h"
#include "dbg_palette.h"
#define NW_WIDTH (NW_ROW_TILE_COUNT * PATTERN_DRAW_SIZE) #define NW_WIDTH (NW_ROW_TILE_COUNT * PATTERN_DRAW_SIZE)
#define NW_HEIGHT (NW_ROW_COUNT * PATTERN_DRAW_SIZE) #define NW_HEIGHT (NW_ROW_COUNT * PATTERN_DRAW_SIZE)
#define NW_BUFFER_SIZE (NAMETABLE_ROW_WIDTH * NAMETABLE_COL_HEIGHT * PATTERN_DRAW_SIZE * PATTERN_DRAW_SIZE) #define NW_BUFFER_SIZE (NAMETABLE_ROW_WIDTH * NAMETABLE_COL_HEIGHT * PATTERN_DRAW_SIZE * PATTERN_DRAW_SIZE)
void nametable_window_init(NesNametableWindow *window) { NametableWindow *nametable_window_create(int *window_id) {
NametableWindow *window = malloc(sizeof(NametableWindow));
window->window = window_create("Nametable", NW_WIDTH, NW_HEIGHT, NW_SCALE); window->window = window_create("Nametable", NW_WIDTH, NW_HEIGHT, NW_SCALE);
window->texture = window_create_texture(&window->window, SDL_TEXTUREACCESS_STREAMING); window->texture = window_create_texture(&window->window, SDL_TEXTUREACCESS_STREAMING);
dbg_nametable_init();
*window_id = window->window.id;
return window;
} }
void nametable_window_uninit(NesNametableWindow *window) { void nametable_window_destroy(NametableWindow *window) {
SDL_DestroyTexture(window->texture); SDL_DestroyTexture(window->texture);
window_destroy(&window->window); window_destroy(&window->window);
free(window);
} }
void nametable_window_update_bank(NesNametableWindow *window, int bank, pixel *buffer) { void nametable_window_update_bank(NametableWindow *window, int bank, pixel *buffer) {
dbg_nametable_render_bank(bank, buffer); dbg_nametable_render_bank(bank, buffer);
SDL_Rect rect; SDL_Rect rect;
@ -31,7 +41,8 @@ void nametable_window_update_bank(NesNametableWindow *window, int bank, pixel *b
SDL_UpdateTexture(window->texture, &rect, buffer, (NW_WIDTH / 2) * sizeof(pixel)); SDL_UpdateTexture(window->texture, &rect, buffer, (NW_WIDTH / 2) * sizeof(pixel));
} }
void nametable_window_update(NesNametableWindow *window) { void nametable_window_update(NametableWindow *window) {
dbg_palette_update();
dbg_nametable_update(); dbg_nametable_update();
pixel buffer[NW_BUFFER_SIZE * 4] = {0}; pixel buffer[NW_BUFFER_SIZE * 4] = {0};
@ -41,10 +52,7 @@ void nametable_window_update(NesNametableWindow *window) {
nametable_window_update_bank(window, 3, buffer); nametable_window_update_bank(window, 3, buffer);
} }
void nametable_window_render(NesNametableWindow *window) { void nametable_window_render(NametableWindow *window) {
window_render_texture(&window->window, window->texture); window_render_texture(&window->window, window->texture);
}
void nametable_window_present(NesNametableWindow *window) {
window_present(&window->window); window_present(&window->window);
} }

View File

@ -15,16 +15,30 @@
typedef struct nes_nametable_window { typedef struct nes_nametable_window {
Window window; Window window;
SDL_Texture *texture; SDL_Texture *texture;
} NesNametableWindow; } NametableWindow;
void nametable_window_init(NesNametableWindow *window); /**
* Creates a nametable window instance.
* @return A reference to the created window
*/
NametableWindow *nametable_window_create(int *window_id);
void nametable_window_uninit(NesNametableWindow *window); /**
* Destroys a nametable window instance.
* @param window A reference to the window to destroy
*/
void nametable_window_destroy(NametableWindow *window);
void nametable_window_update(NesNametableWindow *window); /**
* Updates the content of a nametable window with the current data of the PPU.
* @param window A reference to the window to update
*/
void nametable_window_update(NametableWindow *window);
void nametable_window_render(NesNametableWindow *window); /**
* Renders a nemtable window to the screen.
void nametable_window_present(NesNametableWindow *window); * @param window A reference to the window to render
*/
void nametable_window_render(NametableWindow *window);
#endif //NES_EMULATOR_NAMETABLE_WINDOW_H #endif //NES_EMULATOR_NAMETABLE_WINDOW_H

View File

@ -4,23 +4,35 @@
#include "pattern_window.h" #include "pattern_window.h"
#include "dbg_pattern_table.h" #include "dbg_pattern_table.h"
#include "dbg_palette.h"
#define PW_WIDTH (PW_ROW_TILE_COUNT * PATTERN_DRAW_SIZE) #define PW_WIDTH (PW_ROW_TILE_COUNT * PATTERN_DRAW_SIZE)
#define PW_HEIGHT (PW_WIDTH * 2) #define PW_HEIGHT (PW_WIDTH * 2)
#define PW_BUFFER_SIZE (PW_WIDTH * PW_HEIGHT) #define PW_BUFFER_SIZE (PW_WIDTH * PW_HEIGHT)
void pattern_window_init(NesPatternWindow *window) { PatternWindow *pattern_window_create(int *window_id) {
PatternWindow *window = malloc(sizeof(PatternWindow));
window->window = window_create("Pattern Table", PW_WIDTH, PW_HEIGHT, PW_SCALE); window->window = window_create("Pattern Table", PW_WIDTH, PW_HEIGHT, PW_SCALE);
window->texture = window_create_texture(&window->window, SDL_TEXTUREACCESS_STATIC); window->texture = window_create_texture(&window->window, SDL_TEXTUREACCESS_STATIC);
window->palette = 0; window->palette = 0;
dbg_pattern_table_init();
*window_id = window->window.id;
return window;
} }
void pattern_window_uninit(NesPatternWindow *window) { void pattern_window_destroy(PatternWindow *window) {
SDL_DestroyTexture(window->texture); SDL_DestroyTexture(window->texture);
window_destroy(&window->window); window_destroy(&window->window);
free(window);
} }
void pattern_window_build_table(NesPatternWindow *window) { void pattern_window_update(PatternWindow *window) {
dbg_palette_update();
pixel buffer[PW_BUFFER_SIZE] = {0}; pixel buffer[PW_BUFFER_SIZE] = {0};
dbg_pattern_draw_bank(PATTERN_BANK_0, buffer, window->palette); dbg_pattern_draw_bank(PATTERN_BANK_0, buffer, window->palette);
dbg_pattern_draw_bank(PATTERN_BANK_1, &buffer[PW_WIDTH * (PW_HEIGHT / 2)], window->palette); dbg_pattern_draw_bank(PATTERN_BANK_1, &buffer[PW_WIDTH * (PW_HEIGHT / 2)], window->palette);
@ -28,7 +40,7 @@ void pattern_window_build_table(NesPatternWindow *window) {
SDL_UpdateTexture(window->texture, NULL, buffer, PW_WIDTH * sizeof(pixel)); SDL_UpdateTexture(window->texture, NULL, buffer, PW_WIDTH * sizeof(pixel));
} }
void pattern_window_key_up(NesPatternWindow *window, SDL_KeyCode keycode) { void pattern_window_key_up(PatternWindow *window, SDL_KeyCode keycode) {
if (keycode != SDLK_o) { if (keycode != SDLK_o) {
return; return;
} }
@ -38,13 +50,10 @@ void pattern_window_key_up(NesPatternWindow *window, SDL_KeyCode keycode) {
window->palette = 0; window->palette = 0;
} }
pattern_window_build_table(window); pattern_window_update(window);
} }
void pattern_window_render(NesPatternWindow *window) { void pattern_window_render(PatternWindow *window) {
window_render_texture(&window->window, window->texture); window_render_texture(&window->window, window->texture);
}
void pattern_window_present(NesPatternWindow *window) {
window_present(&window->window); window_present(&window->window);
} }

View File

@ -16,15 +16,37 @@ typedef struct nes_pattern_window {
Window window; Window window;
SDL_Texture *texture; SDL_Texture *texture;
byte palette; byte palette;
} NesPatternWindow; } PatternWindow;
void pattern_window_init(NesPatternWindow *window); /**
void pattern_window_uninit(NesPatternWindow *window); * Creates a pattern window instance.
* @return A reference to the created window instance
*/
PatternWindow *pattern_window_create(int *window_id);
void pattern_window_build_table(NesPatternWindow *window); /**
* Destroys a pattern window instance.
* @param window A reference to the window to destroy
*/
void pattern_window_destroy(PatternWindow *window);
void pattern_window_key_up(NesPatternWindow *window, SDL_KeyCode keycode); /**
void pattern_window_render(NesPatternWindow *window); * Updates the content of a pattern window with the current data from the ROM mapper.
void pattern_window_present(NesPatternWindow *window); * @param window A reference to the window to update
*/
void pattern_window_update(PatternWindow *window);
/**
* Renders a pattern window to the screen.
* @param window A reference to the window to render
*/
void pattern_window_render(PatternWindow *window);
/**
* Sends a key up event to a pattern window.
* @param window A reference to the window to send the event to
* @param keycode The code of the key of the event
*/
void pattern_window_key_up(PatternWindow *window, SDL_KeyCode keycode);
#endif //NES_EMULATOR_PATTERN_WINDOW_H #endif //NES_EMULATOR_PATTERN_WINDOW_H

View File

@ -69,7 +69,6 @@ typedef struct ppu_tile_fetch {
byte pattern_table_tile_high; byte pattern_table_tile_high;
} PPUTileFetch; } PPUTileFetch;
#if DEBUG
typedef union { typedef union {
struct { struct {
byte tile_debugger: 1; byte tile_debugger: 1;
@ -77,7 +76,6 @@ typedef union {
} flags; } flags;
byte flags_byte; byte flags_byte;
} PPUDebugFlags; } PPUDebugFlags;
#endif
typedef struct ppu { typedef struct ppu {
PPUMemory memory; PPUMemory memory;
@ -103,9 +101,7 @@ typedef struct ppu {
unsigned int scanline; unsigned int scanline;
unsigned int cycle; unsigned int cycle;
#if DEBUG
PPUDebugFlags debug; PPUDebugFlags debug;
#endif
} PPU; } PPU;
PPU *ppu_get_state(); PPU *ppu_get_state();

14
main.c
View File

@ -101,30 +101,28 @@ int main() {
if (!rom_load(rom_path)) { if (!rom_load(rom_path)) {
system_uninit(); system_uninit();
gui_uninit(); gui_free();
return EXIT_FAILURE; return EXIT_FAILURE;
} }
gui_post_sysinit();
system_start(); system_start();
bool stop = false; while (true) {
while (!stop) {
if (gui_input() < 0) { if (gui_input() < 0) {
stop = true; // The main window has been closed, stop the emulation
break;
} }
system_next_frame(); system_next_frame();
gui_render(); gui_render();
gui_present(); // Delay the next frame to lock the emulation to 60hz
gui_delay(); gui_delay();
} }
system_uninit(); system_uninit();
rom_unload(); rom_unload();
gui_uninit(); gui_free();
close_logging(); close_logging();

View File

@ -20,8 +20,8 @@
#include "../cpu/cpu.h" #include "../cpu/cpu.h"
#include "../include/rom.h" #include "../include/rom.h"
#include "colors.h" #include "colors.h"
#include "tile_debugger.h"
#include "log.h" #include "log.h"
#include "tile_debugger.h"
#define PPU_SCANLINE_VISIBLE_MAX 240 #define PPU_SCANLINE_VISIBLE_MAX 240
#define PPU_SCANLINE_POST_RENDER_MIN PPU_SCANLINE_VISIBLE_MAX #define PPU_SCANLINE_POST_RENDER_MIN PPU_SCANLINE_VISIBLE_MAX
@ -132,7 +132,6 @@ void ppu_draw_tile() {
} }
byte ppu_get_pattern(byte tile_index, byte high) { byte ppu_get_pattern(byte tile_index, byte high) {
#if DEBUG
if (ppu_state.debug.flags.tile_debugger) { if (ppu_state.debug.flags.tile_debugger) {
if ((ppu_state.debug.flags.tile_debugger_pattern_half == 1 && high) || if ((ppu_state.debug.flags.tile_debugger_pattern_half == 1 && high) ||
(ppu_state.debug.flags.tile_debugger_pattern_half == 2 && !high)) { (ppu_state.debug.flags.tile_debugger_pattern_half == 2 && !high)) {
@ -141,7 +140,6 @@ byte ppu_get_pattern(byte tile_index, byte high) {
return tile_debugger_encode_number_as_pattern(tile_index, ppu_state.scanline % 8); return tile_debugger_encode_number_as_pattern(tile_index, ppu_state.scanline % 8);
} }
#endif
byte tile_row_index = (ppu_state.scanline + ppu_state.y_scroll) % 8; byte tile_row_index = (ppu_state.scanline + ppu_state.y_scroll) % 8;
address pattern_addr = ppu_state.bg_pattern_table_addr | tile_index << 4 | high << 3 | tile_row_index; address pattern_addr = ppu_state.bg_pattern_table_addr | tile_index << 4 | high << 3 | tile_row_index;