From dcb01b4c6ad8f1da9ea26bdeac2898c8ed65a7a5 Mon Sep 17 00:00:00 2001 From: william Date: Sun, 16 Jun 2024 19:22:40 -0400 Subject: [PATCH] Efficient rendering --- gui/CMakeLists.txt | 6 +- gui/char_map.c | 54 +++++++++++++++ gui/char_map.h | 18 +++++ gui/gui.c | 97 ++++++++++++++++++-------- gui/gui.h | 18 ++--- gui/main_window.c | 63 +++++++++++++++++ gui/main_window.h | 28 ++++++++ gui/pattern_window.c | 155 ++++++++++++++++++++++++++++++++++++++++++ gui/pattern_window.h | 37 ++++++++++ gui/window.c | 119 ++++++++++++++++++++------------ gui/window.h | 17 ++--- include/ppu.h | 7 ++ main.c | 19 ++++-- nintendo-nes-font.ttf | Bin 0 -> 17016 bytes ppu/pattern_table.c | 96 +++++++++++++------------- ppu/ppu.c | 82 ++++++++++++++-------- 16 files changed, 630 insertions(+), 186 deletions(-) create mode 100644 gui/char_map.c create mode 100644 gui/char_map.h create mode 100644 gui/main_window.c create mode 100644 gui/main_window.h create mode 100644 gui/pattern_window.c create mode 100644 gui/pattern_window.h create mode 100644 nintendo-nes-font.ttf diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index e9caf9f..241f190 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -1,9 +1,9 @@ -set(HEADERS canvas.h gui.h window.h) -set(SOURCE canvas.c gui.c window.c) +set(HEADERS canvas.h gui.h window.h main_window.h char_map.h pattern_window.h) +set(SOURCE canvas.c gui.c window.c main_window.c char_map.c pattern_window.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 +target_link_libraries(nes_gui log.c ${SDL2_LIBRARIES} SDL2_ttf) \ No newline at end of file diff --git a/gui/char_map.c b/gui/char_map.c new file mode 100644 index 0000000..e2773c8 --- /dev/null +++ b/gui/char_map.c @@ -0,0 +1,54 @@ +// +// Created by william on 6/8/24. +// + +#include +#include +#include "char_map.h" + +SDL_Texture *map[CHAR_MAP_COUNT]; + +SDL_Texture **char_map_get(char c) { + int char_index = c - CHAR_MAP_MIN; + return &map[char_index]; +} + +void char_map_init(SDL_Renderer *renderer, TTF_Font *font) { + char buffer[2]; + SDL_Color color = CHAR_MAP_COLOR; + SDL_Surface *surface; + SDL_Texture **texture; + + for (int i = 0; i <= CHAR_MAP_COUNT; i++) { + buffer[0] = (char) (i + CHAR_MAP_MIN); + buffer[1] = '\0'; + + surface = TTF_RenderText_Solid(font, buffer, color); + texture = char_map_get(buffer[0]); + + *texture = SDL_CreateTextureFromSurface(renderer, surface); + + SDL_FreeSurface(surface); + } +} + +void char_map_uninit() { + for (int i = 0; i < CHAR_MAP_COUNT; i++) { + SDL_Texture *texture = map[i]; + SDL_DestroyTexture(texture); + } +} + +void char_map_render(SDL_Renderer *renderer, char *text) { + int x = 0; + + for (int i = 0; i < strlen(text); i++) { + char c = text[i]; + SDL_Texture *texture = *char_map_get(c); + SDL_Rect rect = {x, 0, 16, 16}; + + SDL_RenderCopy(renderer, texture, NULL, &rect); + + x += 16; + } +} \ No newline at end of file diff --git a/gui/char_map.h b/gui/char_map.h new file mode 100644 index 0000000..c106274 --- /dev/null +++ b/gui/char_map.h @@ -0,0 +1,18 @@ +// +// Created by william on 6/8/24. +// + +#ifndef NES_EMULATOR_CHAR_MAP_H +#define NES_EMULATOR_CHAR_MAP_H + +#define CHAR_MAP_COLOR {0, 255, 0}; +#define CHAR_MAP_MIN 32 +#define CHAR_MAP_MAX 126 +#define CHAR_MAP_COUNT (CHAR_MAP_MAX - CHAR_MAP_MIN) + +void char_map_init(SDL_Renderer *renderer, TTF_Font *font); +void char_map_uninit(); + +void char_map_render(SDL_Renderer *renderer, char *text); + +#endif //NES_EMULATOR_CHAR_MAP_H diff --git a/gui/gui.c b/gui/gui.c index e873aa8..78ed32f 100644 --- a/gui/gui.c +++ b/gui/gui.c @@ -2,26 +2,58 @@ // Created by william on 16/05/24. // -#include +#include #include "log.h" #include "gui.h" -#include "window.h" +#include "main_window.h" +#include "pattern_window.h" +#include "../include/system.h" typedef struct nes_gui { - NesWindow main_window; - NesWindow debug_pattern_window; + NesMainWindow main_window; + NesPatternWindow pattern_window; + + TTF_Font *font; + + // Debug info + bool debug_enabled; + Uint32 last_frame_tick; + Uint32 frame_delay; } NesGui; NesGui gui; -void gui_init() { - gui.main_window = window_init(WINDOW_MAIN_WIDTH, WINDOW_MAIN_HEIGHT, WINDOW_MAIN_SCALING, "NES Emulator"); - gui.debug_pattern_window = window_init(WINDOW_PATTERN_WIDTH, WINDOW_PATTERN_HEIGHT, WINDOW_PATTERN_SCALING, - "Pattern Table"); +bool gui_init() { + TTF_Init(); + gui.font = TTF_OpenFont("../nintendo-nes-font.ttf", 16); + if (gui.font == NULL) { + log_error("Failed to open TTF font"); + return false; + } + + gui.debug_enabled = true; + + main_window_init(&gui.main_window, gui.font); + + if (gui.debug_enabled) { + pattern_window_init(&gui.pattern_window); + } + + return true; } void gui_uninit() { - window_uninit(&gui.main_window); - window_uninit(&gui.debug_pattern_window); + main_window_uninit(&gui.main_window); + + if (gui.debug_enabled) { + pattern_window_uninit(&gui.pattern_window); + } + + TTF_CloseFont(gui.font); +} + +void gui_post_sysinit() { + byte *pattern_memory = system_get_mapper()->ppu_read(0); + pattern_window_build_table(&gui.pattern_window, pattern_memory); } int gui_input() { @@ -37,33 +69,38 @@ int gui_input() { } void gui_render() { - window_render(&gui.main_window); - window_render(&gui.debug_pattern_window); + main_window_render(&gui.main_window, ppu_get_state()->pixels); + + if (gui.debug_enabled) { + pattern_window_render(&gui.pattern_window); + } } void gui_present() { - window_present(&gui.main_window); - window_present(&gui.debug_pattern_window); + main_window_present(&gui.main_window); + + if (gui.debug_enabled) { + pattern_window_present(&gui.pattern_window); + } } void gui_delay() { - SDL_Delay(16); -} + Uint32 tick_now = SDL_GetTicks(); + gui.frame_delay = tick_now - gui.last_frame_tick; -Canvas *gui_get_canvas(char win_id) { - NesWindow *window; - - switch (win_id) { - case WINDOW_ID_MAIN: - window = &gui.main_window; - break; - case WINDOW_ID_PATTERN: - window = &gui.debug_pattern_window; - break; - default: - log_error("Couldn't get canvas for window ID '%d' because it doesn't exists", win_id); - assert(false); + if (gui.frame_delay < 16) { + Uint32 additional_delay = 16 - gui.frame_delay; + SDL_Delay(additional_delay); + gui.frame_delay += additional_delay; } - return &window->canvas; + gui.last_frame_tick = SDL_GetTicks(); +} + +bool gui_debug_enabled() { + return gui.debug_enabled; +} + +unsigned int gui_get_frame_delay() { + return gui.frame_delay; } \ No newline at end of file diff --git a/gui/gui.h b/gui/gui.h index 7d2eab9..bb98c90 100644 --- a/gui/gui.h +++ b/gui/gui.h @@ -5,26 +5,20 @@ #ifndef NES_EMULATOR_GUI_H #define NES_EMULATOR_GUI_H +#include #include "canvas.h" -#define WINDOW_ID_MAIN 1 -#define WINDOW_MAIN_WIDTH 256 -#define WINDOW_MAIN_HEIGHT 240 -#define WINDOW_MAIN_SCALING 2 - -#define WINDOW_ID_PATTERN 2 -#define WINDOW_PATTERN_WIDTH 128 -#define WINDOW_PATTERN_HEIGHT 256 -#define WINDOW_PATTERN_SCALING 2 - -void gui_init(); +bool gui_init(); void gui_uninit(); +void gui_post_sysinit(); + int gui_input(); void gui_render(); void gui_present(); void gui_delay(); -Canvas *gui_get_canvas(char win_id); +bool gui_debug_enabled(); +unsigned int gui_get_frame_delay(); #endif //NES_EMULATOR_GUI_H diff --git a/gui/main_window.c b/gui/main_window.c new file mode 100644 index 0000000..6771176 --- /dev/null +++ b/gui/main_window.c @@ -0,0 +1,63 @@ +// +// Created by william on 6/8/24. +// + +#include +#include "main_window.h" +#include "log.h" +#include "char_map.h" +#include "gui.h" + +void main_window_init(NesMainWindow *window, TTF_Font *font) { + window->sdl_context = window_init("NES Emulator", MAIN_WINDOW_WIDTH, MAIN_WINDOW_HEIGHT, MAIN_WINDOW_SCALE); + window->texture = SDL_CreateTexture(window->sdl_context.renderer, SDL_PIXELFORMAT_ARGB8888, + SDL_TEXTUREACCESS_STREAMING, MAIN_WINDOW_WIDTH, MAIN_WINDOW_HEIGHT); + char_map_init(window->sdl_context.renderer, font); +} + +void main_window_uninit(NesMainWindow *window) { + char_map_uninit(); + + SDL_DestroyTexture(window->texture); + window_uninit(window->sdl_context); +} + +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); +} + +void main_window_render(NesMainWindow *window, PpuPixel *pixels) { + SDL_RenderClear(window->sdl_context.renderer); + + unsigned int frame_buffer[240 * 256]; + + for (int i = 0; i < 240 * 256; i++) { + PpuPixel pixel = pixels[i]; + + unsigned int *data = &frame_buffer[i]; + *data = 0xff000000; + *data |= pixel.r << 16; + *data |= pixel.g << 8; + *data |= pixel.b; + } + + SDL_UpdateTexture(window->texture, NULL, &frame_buffer, 240 * sizeof(unsigned int)); + SDL_RenderCopy(window->sdl_context.renderer, window->texture, NULL, NULL); + + if (gui_debug_enabled()) { + main_window_render_delay(window->sdl_context.renderer); + } +} + +void main_window_present(NesMainWindow *window) { + SDL_RenderPresent(window->sdl_context.renderer); +} \ No newline at end of file diff --git a/gui/main_window.h b/gui/main_window.h new file mode 100644 index 0000000..7fd3492 --- /dev/null +++ b/gui/main_window.h @@ -0,0 +1,28 @@ +// +// Created by william on 6/8/24. +// + +#ifndef NES_EMULATOR_MAIN_WINDOW_H +#define NES_EMULATOR_MAIN_WINDOW_H + +#include +#include "../include/ppu.h" +#include "window.h" + +#define MAIN_WINDOW_WIDTH 240 +#define MAIN_WINDOW_HEIGHT 256 +#define MAIN_WINDOW_SCALE 2 + +typedef struct nes_main_window { + NesSdlContext sdl_context; + + SDL_Texture *texture; +} NesMainWindow; + +void main_window_init(NesMainWindow *window, TTF_Font *font); +void main_window_uninit(NesMainWindow *window); + +void main_window_render(NesMainWindow *window, PpuPixel* pixels); +void main_window_present(NesMainWindow *window); + +#endif //NES_EMULATOR_MAIN_WINDOW_H diff --git a/gui/pattern_window.c b/gui/pattern_window.c new file mode 100644 index 0000000..f170773 --- /dev/null +++ b/gui/pattern_window.c @@ -0,0 +1,155 @@ +// +// Created by william on 6/14/24. +// + +#include "pattern_window.h" + +#define PW_TILE_SIZE 8 +#define PW_TILE_BYTES (PW_TILE_SIZE * 2) +#define PW_BANK_SIZE 0x1000 +#define PW_BANK_TILE_COUNT (PW_ROW_TILE_COUNT * PW_ROW_TILE_COUNT) + +#define PW_TILE_BORDER_WIDTH 1 +#define PW_TILE_BORDER_COLOR 0xff2223b2 +#define PW_TILE_DRAW_SIZE (PW_TILE_SIZE + PW_TILE_BORDER_WIDTH) + +#define PW_WIDTH (PW_ROW_TILE_COUNT * PW_TILE_DRAW_SIZE + PW_TILE_BORDER_WIDTH) +#define PW_HEIGHT (PW_ROW_TILE_COUNT * PW_TILE_DRAW_SIZE * 2 + PW_TILE_BORDER_WIDTH) +#define PW_SCALE 2 + +void pattern_window_init(NesPatternWindow *window) { + window->sdl_context = window_init("Pattern Table", PW_WIDTH, PW_HEIGHT, PW_SCALE); + + window->texture = SDL_CreateTexture(window->sdl_context.renderer, SDL_PIXELFORMAT_ARGB8888, + SDL_TEXTUREACCESS_STATIC, PW_WIDTH, PW_HEIGHT); +} + +void pattern_window_uninit(NesPatternWindow *window) { + window_uninit(window->sdl_context); +} + +/* + * d888888b d888888b db d88888b .d8888. + * `~~88~~' `88' 88 88' 88' YP + * 88 88 88 88ooooo `8bo. + * 88 88 88 88~~~~~ `Y8b. + * 88 .88. 88booo. 88. db 8D + * YP Y888888P Y88888P Y88888P `8888Y' + */ +static PatternTile pattern_window_build_tile(int x, int y, byte *pattern_memory) { + PatternTile tile; + tile.x = x; + tile.y = y; + + address tile_addr = (x + y * PW_ROW_TILE_COUNT) * PW_TILE_BYTES; + memcpy(tile.data_low, &pattern_memory[tile_addr], 8); + memcpy(tile.data_high, &pattern_memory[tile_addr + 8], 8); + + return tile; +} + +static void pattern_window_build_bank(byte *pattern_memory, PatternTile *bank) { + for (int y = 0; y < PW_ROW_TILE_COUNT; y++) { + for (int x = 0; x < PW_ROW_TILE_COUNT; x++) { + int tile_index = y * PW_ROW_TILE_COUNT + x; + PatternTile *tile = &bank[tile_index]; + *tile = pattern_window_build_tile(x, y, pattern_memory); + } + } +} + +void pattern_window_build_tiles(NesPatternWindow *window, byte *pattern_memory) { + pattern_window_build_bank(pattern_memory, window->tiles_bank_0); + pattern_window_build_bank(&pattern_memory[PW_BANK_SIZE], window->tiles_bank_1); +} + +/* + * d8888b. d88888b d8b db d8888b. d88888b d8888b. d888888b d8b db d888b + * 88 `8D 88' 888o 88 88 `8D 88' 88 `8D `88' 888o 88 88' Y8b + * 88oobY' 88ooooo 88V8o 88 88 88 88ooooo 88oobY' 88 88V8o 88 88 + * 88`8b 88~~~~~ 88 V8o88 88 88 88~~~~~ 88`8b 88 88 V8o88 88 ooo + * 88 `88. 88. 88 V888 88 .8D 88. 88 `88. .88. 88 V888 88. ~8~ + * 88 YD Y88888P VP V8P Y8888D' Y88888P 88 YD Y888888P VP V8P Y888P + */ +static void pattern_window_draw_borders(unsigned int *buffer) { + for (int x = 0; x < PW_WIDTH; x++) { + buffer[x] = PW_TILE_BORDER_COLOR; + } + + for (int y = 1; y < PW_HEIGHT; y++) { + buffer[y * PW_WIDTH] = PW_TILE_BORDER_COLOR; + } +} + +static void pattern_window_draw_tile_borders(int tile_addr, unsigned int *buffer) { + for (int by = 0; by < PW_TILE_DRAW_SIZE; by++) { + int pixel_addr = tile_addr + (by * PW_WIDTH) + PW_TILE_DRAW_SIZE - 1; + buffer[pixel_addr] = PW_TILE_BORDER_COLOR; + } + + for (int bx = 0; bx < PW_TILE_DRAW_SIZE; bx++) { + int pixel_addr = tile_addr + ((PW_TILE_DRAW_SIZE - 1) * PW_WIDTH) + bx; + buffer[pixel_addr] = PW_TILE_BORDER_COLOR; + } +} + +static void pattern_window_draw_tile(PatternTile *tile, unsigned int *buffer) { + int row_addr = (tile->y * PW_TILE_DRAW_SIZE + 1) * PW_WIDTH; + int tile_buffer_addr = row_addr + (tile->x * PW_TILE_DRAW_SIZE + 1); + + for (int fine_y = 0; fine_y < PW_TILE_SIZE; fine_y++) { + byte data_high = tile->data_high[fine_y]; + byte data_low = tile->data_low[fine_y]; + + for (int fine_x = 0; fine_x < PW_TILE_SIZE; fine_x++) { + byte bitmask = 1 << (PW_TILE_SIZE - fine_x - 1); + byte bit_high = data_high & bitmask; + byte bit_low = data_low & bitmask; + + int pixel_addr = tile_buffer_addr + fine_x + fine_y * PW_WIDTH; + unsigned int *pixel_data = &buffer[pixel_addr]; + + if (bit_high && bit_low) { + *pixel_data = 0xffffffff; + } else if (bit_low) { + *pixel_data = 0xffff0000; + } else if (bit_high) { + *pixel_data = 0xff00ffff; + } else { + *pixel_data = 0xff000000; + } + } + } + + pattern_window_draw_tile_borders(tile_buffer_addr, buffer); +} + +void pattern_window_draw_bank(PatternTile *tiles, unsigned int *buffer) { + for (int i = 0; i < PW_BANK_TILE_COUNT; i++) { + PatternTile *tile = &tiles[i]; + pattern_window_draw_tile(tile, buffer); + } +} + +void pattern_window_draw_table(NesPatternWindow *window) { + unsigned int tex_buffer[PW_WIDTH * PW_HEIGHT] = {0}; + pattern_window_draw_borders(tex_buffer); + pattern_window_draw_bank(window->tiles_bank_0, tex_buffer); + pattern_window_draw_bank(window->tiles_bank_1, &tex_buffer[PW_WIDTH * (PW_HEIGHT / 2)]); + + SDL_UpdateTexture(window->texture, NULL, tex_buffer, PW_WIDTH * sizeof(unsigned int)); +} + +void pattern_window_build_table(NesPatternWindow *window, byte *pattern_memory) { + pattern_window_build_tiles(window, pattern_memory); + pattern_window_draw_table(window); +} + +void pattern_window_render(NesPatternWindow *window) { + SDL_RenderClear(window->sdl_context.renderer); + SDL_RenderCopy(window->sdl_context.renderer, window->texture, NULL, NULL); +} + +void pattern_window_present(NesPatternWindow *window) { + SDL_RenderPresent(window->sdl_context.renderer); +} \ No newline at end of file diff --git a/gui/pattern_window.h b/gui/pattern_window.h new file mode 100644 index 0000000..8c83838 --- /dev/null +++ b/gui/pattern_window.h @@ -0,0 +1,37 @@ +// +// Created by william on 6/14/24. +// + +#ifndef NES_EMULATOR_PATTERN_WINDOW_H +#define NES_EMULATOR_PATTERN_WINDOW_H + +#include +#include "window.h" +#include "../include/types.h" + +#define PW_ROW_TILE_COUNT 16 + +typedef struct pattern_tile { + byte x; + byte y; + byte data_low[8]; + byte data_high[8]; +} PatternTile; + +typedef struct nes_pattern_window { + NesSdlContext sdl_context; + + PatternTile tiles_bank_0[PW_ROW_TILE_COUNT * PW_ROW_TILE_COUNT]; + PatternTile tiles_bank_1[PW_ROW_TILE_COUNT * PW_ROW_TILE_COUNT]; + SDL_Texture *texture; +} NesPatternWindow; + +void pattern_window_init(NesPatternWindow *window); +void pattern_window_uninit(NesPatternWindow *window); + +void pattern_window_build_table(NesPatternWindow *window, byte* pattern_memory); + +void pattern_window_render(NesPatternWindow *window); +void pattern_window_present(NesPatternWindow *window); + +#endif //NES_EMULATOR_PATTERN_WINDOW_H diff --git a/gui/window.c b/gui/window.c index 4597972..4852d85 100644 --- a/gui/window.c +++ b/gui/window.c @@ -6,12 +6,8 @@ #include "window.h" #include "log.h" -NesWindow window_init(int width, int height, int scaling, char *title) { - NesWindow win; - win.scaling = scaling; - win.width = width * scaling; - win.height = height * scaling; - win.canvas = canvas_init(width, height); +NesSdlContext window_init(char *title, int width, int height, int scale) { + NesSdlContext context; int renderer_flags = SDL_RENDERER_ACCELERATED; int window_flags = 0; @@ -21,52 +17,85 @@ NesWindow window_init(int width, int height, int scaling, char *title) { exit(-1); } - win.window = SDL_CreateWindow(title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, win.width, win.height, - window_flags); - if (!win.window) { - log_error("Failed to open %d x %d window: %s", win.width, win.height, SDL_GetError()); + int actual_width = width * scale; + int actual_height = height * scale; + context.window = SDL_CreateWindow(title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, actual_width, + actual_height, window_flags); + if (!context.window) { + log_error("Failed to open %d x %d SDL window: %s", actual_width, actual_height, SDL_GetError()); exit(-1); } - SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear"); - - win.renderer = SDL_CreateRenderer(win.window, -1, renderer_flags); - if (!win.renderer) { - log_error("Failed to create renderer: %s\n", SDL_GetError()); + context.renderer = SDL_CreateRenderer(context.window, -1, renderer_flags); + if (!context.renderer) { + log_error("Failed to create renderer: %s", SDL_GetError()); + SDL_DestroyWindow(context.window); exit(-1); } - return win; + return context; } -void window_uninit(NesWindow *window) { - canvas_uninit(&window->canvas); +void window_uninit(NesSdlContext context) { + SDL_DestroyRenderer(context.renderer); + SDL_DestroyWindow(context.window); } -void window_render(NesWindow *window) { - SDL_RenderClear(window->renderer); - - for (int y = 0; y < window->canvas.height; y++) { - for (int x = 0; x < window->canvas.width; x++) { - int pixel_index = x + y * window->canvas.width; - Pixel pixel = window->canvas.pixels[pixel_index]; - - SDL_SetRenderDrawColor(window->renderer, pixel.r, pixel.g, pixel.b, 255); - - for (int i = 0; i < window->scaling; i++) { - for (int j = 0; j < window->scaling; j++) { - int scaled_x = x * window->scaling + i; - int scaled_y = y * window->scaling + j; - SDL_RenderDrawPoint(window->renderer, scaled_x, scaled_y); - } - } - } - } -} - -void window_present(NesWindow *window) { - SDL_RenderPresent(window->renderer); - - // TODO: Check if this is a good location - canvas_reset(&window->canvas); -} \ No newline at end of file +//NesWindow window_init(int width, int height, char *title) { +// NesWindow win; +// win.width = width; +// win.height = height; +// +// 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()); +// exit(-1); +// } +// +// win.window = SDL_CreateWindow(title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, win.width, win.height, +// window_flags); +// if (!win.window) { +// log_error("Failed to open %d x %d sdl_window: %s", win.width, win.height, SDL_GetError()); +// exit(-1); +// } +// +// win.renderer = SDL_CreateRenderer(win.window, -1, renderer_flags); +// if (!win.renderer) { +// log_error("Failed to create renderer: %s\n", SDL_GetError()); +// exit(-1); +// } +// +// return win; +//} +// +//void window_uninit(NesWindow *window) { +// SDL_DestroyRenderer(window->renderer); +// SDL_DestroyWindow(window->window); +//} +// +//void window_render(NesWindow *window) { +// SDL_RenderClear(window->renderer); +// +//// for (int y = 0; y < window->canvas.height; y++) { +//// for (int x = 0; x < window->canvas.width; x++) { +//// int pixel_index = x + y * window->canvas.width; +//// Pixel pixel = window->canvas.pixels[pixel_index]; +//// +//// SDL_SetRenderDrawColor(window->renderer, pixel.r, pixel.g, pixel.b, 255); +//// +//// for (int i = 0; i < window->scaling; i++) { +//// for (int j = 0; j < window->scaling; j++) { +//// int scaled_x = x * window->scaling + i; +//// int scaled_y = y * window->scaling + j; +//// SDL_RenderDrawPoint(window->renderer, scaled_x, scaled_y); +//// } +//// } +//// } +//// } +//} +// +//void window_present(NesWindow *window) { +// SDL_RenderPresent(window->renderer); +//} \ No newline at end of file diff --git a/gui/window.h b/gui/window.h index 848753a..7392e27 100644 --- a/gui/window.h +++ b/gui/window.h @@ -6,22 +6,13 @@ #define NES_EMULATOR_WINDOW_H #include -#include "canvas.h" -typedef struct nes_window { +typedef struct nes_sdl_context { SDL_Renderer *renderer; SDL_Window *window; +} NesSdlContext; - int width; - int height; - int scaling; - Canvas canvas; -} NesWindow; - -NesWindow window_init(int width, int height, int scaling, char *title); -void window_uninit(NesWindow *window); - -void window_render(NesWindow *window); -void window_present(NesWindow *window); +NesSdlContext window_init(char *title, int width, int height, int scale); +void window_uninit(NesSdlContext context); #endif //NES_EMULATOR_WINDOW_H \ No newline at end of file diff --git a/include/ppu.h b/include/ppu.h index ac28c97..41a5d90 100644 --- a/include/ppu.h +++ b/include/ppu.h @@ -69,8 +69,15 @@ typedef struct ppu_tile_fetch { byte pattern_table_tile_high; } PPUTileFetch; +typedef struct ppu_pixel { + byte r; + byte g; + byte b; +} PpuPixel; + typedef struct ppu { PPUMemory memory; + PpuPixel pixels[256 * 240]; byte registers[8]; byte oam_dma_register; diff --git a/main.c b/main.c index 12ebeec..757ef60 100644 --- a/main.c +++ b/main.c @@ -23,17 +23,22 @@ #include "gui.h" int main() { + char *rom_path = "../test_roms/dk_japan.nes"; log_set_level(LOG_INFO); - system_init(); - char *rom_path = "../test_roms/nestest.nes"; - - if (!rom_load(rom_path)) { - system_uninit(); + if (!gui_init()) { return EXIT_FAILURE; } - gui_init(); + system_init(); + + if (!rom_load(rom_path)) { + system_uninit(); + gui_uninit(); + return EXIT_FAILURE; + } + + gui_post_sysinit(); system_start(); bool stop = false; @@ -45,6 +50,7 @@ int main() { system_next_frame(); gui_render(); + gui_present(); gui_delay(); } @@ -53,6 +59,5 @@ int main() { rom_unload(); gui_uninit(); -//// start_debugger(); return EXIT_SUCCESS; } \ No newline at end of file diff --git a/nintendo-nes-font.ttf b/nintendo-nes-font.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ccb8391d5d52ac8a3d410e2d1afc2e3dbe79ba56 GIT binary patch literal 17016 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 = 255; + pixel->g = 255; + pixel->b = 255; + } else if (p2_byte) { + pixel->r = 255; + pixel->g = 0; + pixel->b = 0; + } else if (p1_byte) { + pixel->r = 0; + pixel->g = 255; + pixel->b = 255; + } else { + pixel->r = 0; + pixel->g = 0; + pixel->b = 0; + } +} + void ppu_draw_tile() { PPUTileFetch tile_fetch = ppu_state.tile_fetch; - Canvas *canvas = gui_get_canvas(WINDOW_ID_MAIN); - byte tile_fine_x = (ppu_state.cycle - 1) % 8; - - byte bitmask = 1 << (PATTERN_TILE_SIZE - tile_fine_x - 1); - byte p1_byte = tile_fetch.pattern_table_tile_low & bitmask; - byte p2_byte = tile_fetch.pattern_table_tile_high & bitmask; - - Pixel pixel; -// pixel.r = ~tile_fetch.nametable; -// pixel.g = ~tile_fetch.nametable; -// pixel.b = tile_fetch.nametable; - if (p1_byte && p2_byte) { - pixel.r = 255; - pixel.g = 255; - pixel.b = 255; - } else if (p2_byte) { - pixel.r = 255; - pixel.g = 0; - pixel.b = 0; - } else if (p1_byte) { - pixel.r = 0; - pixel.g = 255; - pixel.b = 255; - } else { - pixel.r = 0; - pixel.g = 0; - pixel.b = 0; - } - - canvas_draw_pos(canvas, pixel, ppu_state.cycle, ppu_state.scanline); + unsigned int pixel_index = ppu_pixel_get_index(ppu_state.scanline, ppu_state.cycle); + PpuPixel *pixel = &ppu_state.pixels[pixel_index]; + byte pixel_mask = ppu_pixel_get_mask(ppu_state.cycle); + ppu_pixel_set_color(pixel, tile_fetch.pattern_table_tile_low, tile_fetch.pattern_table_tile_high, pixel_mask); } void ppu_visible_frame(unsigned int cycle) {