| @@ -22,12 +22,13 @@ endif | |||
| CC = $(CROSS_COMPILE)gcc | |||
| LD = $(CC) | |||
| PFLAGS += -g | |||
| #PFLAGS += -O3 | |||
| #PFLAGS += -s | |||
| #PFLAGS += -O3 -s | |||
| #PFLAGS += -DSTANDALONE | |||
| #PFLAGS += -DDEBUG_MAPPER | |||
| #PFLAGS += -DDEBUG_RENDER | |||
| #PFLAGS += -DDEBUG_PPU -DDEBUG_VRAM -DDEBUG_OAM | |||
| #PFLAGS += -DDEBUG_APU | |||
| #PFLAGS += -DDEBUG_INPUT | |||
| #PFLAGS += -DE6502_DEBUG | |||
| PFLAGS += -DE6502_ILLEGAL | |||
| CFLAGS += $(PFLAGS) -Wall -Werror -Wshadow -Wunused -I../ -Isrc/ | |||
| @@ -1,10 +1,6 @@ | |||
| #ifndef NESE_INI_H_ | |||
| #define NESE_INI_H_ | |||
| #define DBG_LOG(...) fprintf(stderr, __VA_ARGS__) | |||
| #define ERR_LOG(...) DBG_LOG(__VA_ARGS__) | |||
| #define INI_ERR(...) ERR_LOG(__VA_ARGS__) | |||
| typedef enum { | |||
| ini_invalid = -1, | |||
| @@ -59,11 +59,15 @@ void nes_input_write(nes_input* input, uint16_t addr, uint8_t val); | |||
| // System Glue | |||
| struct nese_Components; | |||
| typedef struct nes_Input_Reader_t { | |||
| int (*init)(struct nes_Input_Reader_t*); | |||
| void (*done)(struct nes_Input_Reader_t*); | |||
| int (*update)(struct nes_Input_Reader_t*, nes_input*); | |||
| int (*update)(struct nes_Input_Reader_t*, | |||
| struct nese_Components*); | |||
| void* data; | |||
| int menu_timer; | |||
| } nes_Input_Reader; | |||
| static inline int nes_input_init(nes_Input_Reader* reader) { | |||
| @@ -74,9 +78,11 @@ static inline void nes_input_done(nes_Input_Reader* reader) { | |||
| reader->done(reader); | |||
| } | |||
| static inline int nes_input_update(nes_Input_Reader* reader, | |||
| nes_input* input) { | |||
| return reader->update(reader, input); | |||
| static inline int nes_input_update( | |||
| nes_Input_Reader* reader, | |||
| struct nese_Components* comp | |||
| ) { | |||
| return reader->update(reader, comp); | |||
| } | |||
| @@ -8,10 +8,9 @@ | |||
| #include "timer.h" | |||
| static int get_input(nes_Input_Reader* reader, | |||
| nes_input* input, int *last) { | |||
| int status = nes_input_update(reader, input); | |||
| int new_buttons = input->controllers[0].buttons; | |||
| static int get_input(nese_Components* comp, int *last) { | |||
| int status = nes_input_update(comp->reader, comp); | |||
| int new_buttons = comp->sys->input.controllers[0].buttons; | |||
| if (0 == status) { | |||
| status = (~*last & new_buttons); | |||
| } else { | |||
| @@ -21,25 +20,23 @@ static int get_input(nes_Input_Reader* reader, | |||
| return status; | |||
| } | |||
| static int wait_for_input(nes_Input_Reader* reader, | |||
| nes_input* input) { | |||
| int buttons = input->controllers[0].buttons; | |||
| static int wait_for_input(nese_Components* comp) { | |||
| int buttons = comp->sys->input.controllers[0].buttons; | |||
| int status = 0; | |||
| for ( ; | |||
| 0 == status; | |||
| status = get_input(reader, input, &buttons) ) { | |||
| status = get_input(comp, &buttons) ) { | |||
| time_sleep(US_PER_S / 60); | |||
| } | |||
| return status; | |||
| } | |||
| static int wait_for_input_quiet(nes_Input_Reader* reader, | |||
| nes_input* input) { | |||
| static int wait_for_input_quiet(nese_Components* comp) { | |||
| int status = 0; | |||
| while ( input_Result_Quit != status && | |||
| input->controllers[0].buttons) { | |||
| comp->sys->input.controllers[0].buttons) { | |||
| time_sleep(US_PER_S / 60); | |||
| status = nes_input_update(reader, input); | |||
| status = nes_input_update(comp->reader, comp); | |||
| } | |||
| return ( input_Result_Quit == status ? | |||
| input_Result_Quit : 0); | |||
| @@ -157,8 +154,8 @@ static void show_menu(const menu_state* menu, int dim, int x, | |||
| } | |||
| static int run_menu(menu_state* state, const file_list* files, | |||
| int x, nes_Renderer* rend, | |||
| nes_Input_Reader* input, nes* sys) { | |||
| int x, nese_Components* comp, | |||
| const cart_info* cart) { | |||
| menu_state menu = {0}; | |||
| if (NULL != state) { | |||
| menu = *state; | |||
| @@ -181,14 +178,13 @@ static int run_menu(menu_state* state, const file_list* files, | |||
| menu.top = (files->count - 1) - visible; | |||
| } | |||
| show_menu(&menu, NULL != sys->cart.mapper, | |||
| x, rend, files); | |||
| show_menu(&menu, NULL != cart->file, x, | |||
| comp->rend, files); | |||
| int buttons = wait_for_input(input, &sys->input); | |||
| int buttons = wait_for_input(comp); | |||
| int special = (buttons >> 8); | |||
| if ( input_Result_Quit == special || | |||
| input_Result_Refresh == special) { | |||
| if (input_Result_Quit == special) { | |||
| status = special; | |||
| } else if ( input_Result_Menu == special || | |||
| @@ -220,22 +216,17 @@ static int run_menu(menu_state* state, const file_list* files, | |||
| } | |||
| char* run_main_menu(menu_state* state, nes_Renderer* rend, | |||
| nes_Input_Reader* input, nes* sys, | |||
| const char* cur_filename) { | |||
| char* run_main_menu(menu_state* state, nese_Components* comp, | |||
| const cart_info* cart) { | |||
| char* cart_filename = NULL; | |||
| DIR* dir = opendir("rom"); | |||
| if (NULL == dir) { | |||
| nes_draw_last_frame(rend, NULL != sys->cart.mapper); | |||
| nes_draw_text( | |||
| rend, | |||
| modal_popup( | |||
| "No ROMS found!\nPress any key to exit", | |||
| 10, 21, color_error | |||
| comp, cart | |||
| ); | |||
| nes_draw_done(rend); | |||
| wait_for_input(input, &sys->input); | |||
| } else { | |||
| file_list files = {0}; | |||
| @@ -245,18 +236,14 @@ char* run_main_menu(menu_state* state, nes_Renderer* rend, | |||
| menu_state menu = {0}; | |||
| if (NULL != state) menu = *state; | |||
| if (NULL != cur_filename) { | |||
| if (NULL != cart->filename) { | |||
| // Add 4 to skip past "rom/" | |||
| int current = find_file(&files, cur_filename + 4); | |||
| int current = find_file(&files, cart->filename + 4); | |||
| if (current >= 0) menu.cursor = current; | |||
| } | |||
| // Don't let window refreshes interrupt us. | |||
| int status = input_Result_Refresh; | |||
| while (input_Result_Refresh == status) { | |||
| status = run_menu(&menu, &files, 20, | |||
| rend, input, sys); | |||
| } | |||
| int status = run_menu(&menu, &files, 20, comp, cart); | |||
| if (input_Result_Quit == status) { | |||
| cart_filename = (char*)-1; | |||
| @@ -280,15 +267,17 @@ char* run_main_menu(menu_state* state, nes_Renderer* rend, | |||
| return cart_filename; | |||
| } | |||
| int run_game_menu(menu_state* state, nes_Renderer* rend, | |||
| nes_Input_Reader* input, nes* sys) { | |||
| int run_game_menu(menu_state* state, nese_Components* comp, | |||
| const cart_info* cart) { | |||
| static char* items[] = { | |||
| "Resume", | |||
| "Save", | |||
| "Restore", | |||
| "Reset", | |||
| "Select ROM", | |||
| #ifndef STANDALONE | |||
| "Toggle Fullscreen", | |||
| #endif | |||
| "Exit", | |||
| }; | |||
| static int choices[] = { | |||
| @@ -297,7 +286,9 @@ int run_game_menu(menu_state* state, nes_Renderer* rend, | |||
| input_Result_Load, | |||
| input_Result_Reset, | |||
| input_Result_Menu, | |||
| #ifndef STANDALONE | |||
| input_Result_View, | |||
| #endif | |||
| input_Result_Quit, | |||
| }; | |||
| static const file_list options = { | |||
| @@ -308,8 +299,7 @@ int run_game_menu(menu_state* state, nes_Renderer* rend, | |||
| menu_state menu = {0}; | |||
| if (NULL != state) menu = *state; | |||
| int status = run_menu(&menu, &options, 100, | |||
| rend, input, sys); | |||
| int status = run_menu(&menu, &options, 100, comp, cart); | |||
| if (input_Result_Menu == status) { | |||
| status = input_Result_Cancel; | |||
| @@ -317,19 +307,19 @@ int run_game_menu(menu_state* state, nes_Renderer* rend, | |||
| if (0 == status) status = choices[menu.cursor]; | |||
| wait_for_input_quiet(input, &sys->input); | |||
| wait_for_input_quiet(comp); | |||
| if (NULL != state) *state = menu; | |||
| return status; | |||
| } | |||
| int modal_popup(const char* message, nes_Renderer* rend, | |||
| nes_Input_Reader* input, nes* sys) { | |||
| int modal_popup(const char* message, nese_Components* comp, | |||
| const cart_info* cart) { | |||
| int w = 0; | |||
| int h = 0; | |||
| nes_text_size(rend, message, &w, &h); | |||
| nes_text_size(comp->rend, message, &w, &h); | |||
| int x = ((int)nes_ppu_render_w - w) / 2; | |||
| int y = ((int)nes_ppu_render_h - h) / 2; | |||
| @@ -337,9 +327,9 @@ int modal_popup(const char* message, nes_Renderer* rend, | |||
| if (x < 5) x = 5; | |||
| if (y < 5) y = 5; | |||
| nes_draw_last_frame(rend, NULL != sys->cart.mapper); | |||
| nes_draw_text(rend, message, x, y, color_error); | |||
| nes_draw_done(rend); | |||
| nes_draw_last_frame(comp->rend, NULL != cart->file); | |||
| nes_draw_text(comp->rend, message, x, y, color_error); | |||
| nes_draw_done(comp->rend); | |||
| return wait_for_input(input, &sys->input); | |||
| return wait_for_input(comp); | |||
| } | |||
| @@ -4,6 +4,7 @@ | |||
| #include "nes.h" | |||
| #include "render.h" | |||
| #include "input.h" | |||
| #include "state.h" | |||
| #define color_white (0xFFffffffU) | |||
| @@ -29,16 +30,15 @@ typedef struct { | |||
| // Returns filename of selected ROM | |||
| char* run_main_menu(menu_state*, nes_Renderer*, | |||
| nes_Input_Reader*, nes*, | |||
| const char* cur_filename); | |||
| char* run_main_menu(menu_state*, nese_Components*, | |||
| const cart_info*); | |||
| // Returns nes_Input_Result indicating the choice | |||
| int run_game_menu(menu_state*, nes_Renderer*, | |||
| nes_Input_Reader*, nes*); | |||
| int run_game_menu(menu_state*, nese_Components*, | |||
| const cart_info*); | |||
| int modal_popup(const char* message, | |||
| nes_Renderer*, nes_Input_Reader*, nes*); | |||
| int modal_popup(const char* message, nese_Components*, | |||
| const cart_info*); | |||
| #endif // NESE_MENU_H_ | |||
| @@ -107,8 +107,8 @@ static int loadsave_tick(loadsave_state* loadsave) { | |||
| return action; | |||
| } | |||
| static int select_rom(menu_state* menu, nes_Renderer* rend, | |||
| nes_Input_Reader* input, nes* sys, | |||
| static int select_rom(menu_state* menu, | |||
| nese_Components* comp, | |||
| cart_info* cur_cart) { | |||
| int status = 0; | |||
| cart_info cart = {0}; | |||
| @@ -120,7 +120,7 @@ static int select_rom(menu_state* menu, nes_Renderer* rend, | |||
| char message[1024]; | |||
| snprintf(message, sizeof(message) - 1, | |||
| "Could not load\n%s", cart.filename + 4); | |||
| int button = modal_popup(message, rend, input, sys); | |||
| int button = modal_popup(message, comp, cur_cart); | |||
| if (input_Result_Quit == (button >> 8)) { | |||
| // Program closed inside modal | |||
| status = input_Result_Quit; | |||
| @@ -130,9 +130,7 @@ static int select_rom(menu_state* menu, nes_Renderer* rend, | |||
| } | |||
| if (0 == status) { | |||
| cart.filename = run_main_menu( | |||
| menu, rend, input, sys, cur_cart->filename | |||
| ); | |||
| cart.filename = run_main_menu(menu, comp, cur_cart); | |||
| if ((char*)-1 == cart.filename) { | |||
| // This means that we quit | |||
| @@ -150,49 +148,43 @@ static int select_rom(menu_state* menu, nes_Renderer* rend, | |||
| } | |||
| if (0 == status && NULL != cart.file) { | |||
| save_sram(&sys->cart, cur_cart->filename); | |||
| save_sram(&comp->sys->cart, cur_cart->filename); | |||
| nes_cart_done(&sys->cart); | |||
| nes_cart_done(&comp->sys->cart); | |||
| cart_info_done(cur_cart); | |||
| sys->cart = new_cart; | |||
| comp->sys->cart = new_cart; | |||
| *cur_cart = cart; | |||
| nes_setup_cart(sys); | |||
| nes_setup_cart(comp->sys); | |||
| } | |||
| return status; | |||
| } | |||
| static int do_game_menu(menu_state* menu, nes_Renderer* rend, | |||
| nes_Input_Reader* input, nes* sys, | |||
| static int do_game_menu(menu_state* menu, | |||
| nese_Components* comp, | |||
| nese_State* state) { | |||
| int status = 0; | |||
| menu_state rom_menu = {0}; | |||
| while (1) { | |||
| status = run_game_menu(menu, rend, input, sys); | |||
| status = run_game_menu(menu, comp, &state->cart); | |||
| if ( input_Result_View == status || | |||
| input_Result_Refresh == status) { | |||
| if (input_Result_View == status) { | |||
| state->flags ^= (1 << State_Bit_Fullscreen); | |||
| } else { | |||
| // We need to do this to flush both buffers | |||
| nes_draw_last_frame(rend, 1); | |||
| nes_draw_done(rend); | |||
| } | |||
| if (input_Result_View == status) { | |||
| #ifndef STANDALONE | |||
| state->flags ^= (1 << State_Bit_Fullscreen); | |||
| #endif | |||
| // We call this both times since it does both | |||
| // the toggle and the recalculation. | |||
| nes_render_fullscreen( | |||
| rend, | |||
| comp->rend, | |||
| state->flags & (1 << State_Bit_Fullscreen) | |||
| ); | |||
| continue; | |||
| } else if (input_Result_Menu == status) { | |||
| status = select_rom(&rom_menu, rend, input, | |||
| sys, &state->cart); | |||
| status = select_rom(&rom_menu, comp, &state->cart); | |||
| if (input_Result_Cancel == status) { | |||
| status = input_Result_OK; | |||
| @@ -212,31 +204,39 @@ int main(int argc, char* argv[]) { | |||
| nese_State state = {0}; | |||
| load_prefs_filename(&state, "nese.prefs"); | |||
| #ifdef STANDALONE | |||
| state.flags |= (1 << State_Bit_Fullscreen); | |||
| #endif | |||
| nes sys = {0}; | |||
| if (status == 0) { | |||
| status = nes_init(&sys, audio_freq); | |||
| } | |||
| nese_Components components = { | |||
| .rend = &sdl_renderer, | |||
| .reader = &sdl_input, | |||
| .audio = &sdl_audio, | |||
| .sys = &sys, | |||
| }; | |||
| nes_Renderer* rend = &sdl_renderer; | |||
| if (status == 0) { | |||
| status = nes_render_init(rend); | |||
| status = nes_render_init(components.rend); | |||
| if (0 == status) { | |||
| nes_render_fullscreen( | |||
| rend, | |||
| components.rend, | |||
| (state.flags & (1 << State_Bit_Fullscreen)) | |||
| ); | |||
| nes_render_refresh(components.rend); | |||
| } | |||
| } | |||
| nes_Input_Reader* input = &sdl_input; | |||
| if (status == 0) { | |||
| status = nes_input_init(input); | |||
| } | |||
| nes_Audio_Stream* audio = &sdl_audio; | |||
| if (status == 0) { | |||
| status = nes_audio_init(audio, audio_freq); | |||
| status = nes_input_init(components.reader); | |||
| } | |||
| nes sys = {0}; | |||
| if (status == 0) { | |||
| status = nes_init(&sys, audio_freq); | |||
| status = nes_audio_init(components.audio, audio_freq); | |||
| } | |||
| if (0 == status && argc > 1) { | |||
| @@ -255,17 +255,16 @@ int main(int argc, char* argv[]) { | |||
| // If we didn't launch with a file, run the loader | |||
| if (0 == status && NULL == state.cart.file) { | |||
| status = select_rom(NULL, rend, input, | |||
| &sys, &state.cart); | |||
| status = select_rom(NULL, &components, &state.cart); | |||
| } | |||
| if (status == 0) { | |||
| menu_state game_menu = {0}; | |||
| loadsave_state loadsave = { | |||
| .overlay = &rend->overlay, | |||
| .overlay = &components.rend->overlay, | |||
| }; | |||
| nes_render(rend, &sys.ppu); | |||
| nes_render(components.rend, &sys.ppu); | |||
| time_us t_target = time_now(); | |||
| uint64_t cycle_last_frame = 0; | |||
| @@ -278,7 +277,7 @@ int main(int argc, char* argv[]) { | |||
| if ( result == ppu_Result_Ready || | |||
| result == ppu_Result_VBlank_Off) { | |||
| status = nes_render(rend, &sys.ppu); | |||
| status = nes_render(components.rend, &sys.ppu); | |||
| if (status > 0) { | |||
| // Load/Save Operations | |||
| @@ -309,25 +308,28 @@ int main(int argc, char* argv[]) { | |||
| cycle_last_frame = total_cycles; | |||
| // Update button states every rendered frame | |||
| status = nes_input_update(input, &sys.input); | |||
| status = nes_input_update(components.reader, | |||
| &components); | |||
| if (input_Result_Menu == status) { | |||
| if (input_Result_Refresh == status) { | |||
| status = input_Result_OK; | |||
| } if (input_Result_Menu == status) { | |||
| status = do_game_menu( | |||
| &game_menu, rend, | |||
| input, &sys, &state | |||
| &game_menu, &components, &state | |||
| ); | |||
| if ( input_Result_Load == status || | |||
| input_Result_Save == status) { | |||
| loadsave.mode = status; | |||
| loadsave.timer = 0; | |||
| status = 0; | |||
| status = input_Result_OK; | |||
| } | |||
| // Allow other options to fall through | |||
| } | |||
| if (input_Result_Reset == status) { | |||
| overlay_add_message( | |||
| &rend->overlay, | |||
| &components.rend->overlay, | |||
| "Game reset", | |||
| 60 * 3 | |||
| ); | |||
| @@ -351,7 +353,9 @@ int main(int argc, char* argv[]) { | |||
| if (status == 0) { | |||
| // Update audio, too | |||
| status = nes_audio_fill(audio, &sys.apu); | |||
| status = nes_audio_fill( | |||
| components.audio, &sys.apu | |||
| ); | |||
| } | |||
| } | |||
| } else if (result == ppu_Result_Halt) { | |||
| @@ -362,9 +366,11 @@ int main(int argc, char* argv[]) { | |||
| float ms_run = ( total_cycles * 1000. * | |||
| nes_clock_master_den) / | |||
| nes_clock_master_num; | |||
| fprintf(stdout, "Ran %f ms, %"PRIu64" master cycles (%s)\n", | |||
| ms_run, total_cycles, | |||
| status == 0 ? "OK" : "Halted"); | |||
| fprintf(stdout, | |||
| "Ran %f ms, %"PRIu64" master cycles (%s)\n", | |||
| ms_run, total_cycles, | |||
| status == 0 ? "OK" : "Halted" | |||
| ); | |||
| // Failure might mean there's nothing to save | |||
| save_sram(&sys.cart, state.cart.filename); | |||
| @@ -375,9 +381,9 @@ int main(int argc, char* argv[]) { | |||
| nes_done(&sys); | |||
| nes_audio_done(audio); | |||
| nes_input_done(input); | |||
| nes_render_done(rend); | |||
| nes_audio_done(components.audio); | |||
| nes_input_done(components.reader); | |||
| nes_render_done(components.rend); | |||
| return status; | |||
| } | |||
| @@ -9,7 +9,9 @@ typedef struct nes_Renderer_t { | |||
| int (*init)(struct nes_Renderer_t*); | |||
| void (*done)(struct nes_Renderer_t*); | |||
| int (*render)(struct nes_Renderer_t*, nes_ppu*); | |||
| void (*fullscreen)(struct nes_Renderer_t*, int enable); | |||
| void (*refresh)(struct nes_Renderer_t*); | |||
| void (*draw_last_frame)(struct nes_Renderer_t*, int dim); | |||
| void (*draw_text)(struct nes_Renderer_t*, const char*, int x, int y, uint32_t color); | |||
| @@ -37,6 +39,10 @@ static inline void nes_render_fullscreen(nes_Renderer* rend, | |||
| rend->fullscreen(rend, enable); | |||
| } | |||
| static inline void nes_render_refresh(nes_Renderer* rend) { | |||
| rend->refresh(rend); | |||
| } | |||
| static inline void nes_draw_last_frame(nes_Renderer* rend, | |||
| int dim) { | |||
| rend->draw_last_frame(rend, dim); | |||
| @@ -1,26 +1,51 @@ | |||
| #include <SDL.h> | |||
| #include "input.h" | |||
| #include "state.h" | |||
| #define DBG_LOG(...) printf(__VA_ARGS__) | |||
| #define ERR_LOG(...) DBG_LOG(__VA_ARGS__) | |||
| #ifdef DEBUG_INPUT | |||
| #define INPUT_DBG DBG_LOG | |||
| #define INPUT_INFO DBG_LOG | |||
| #else | |||
| #define INPUT_DBG(...) | |||
| #define INPUT_INFO(...) | |||
| #endif | |||
| #define INPUT_ERR ERR_LOG | |||
| #define axis_threshold (8192L) | |||
| typedef struct { | |||
| int16_t last_x_axis; | |||
| int16_t last_y_axis; | |||
| SDL_GameController* gamepad; | |||
| } sdl_input_data; | |||
| static sdl_input_data the_input_data = {0}; | |||
| static SDL_GameController* sdl_find_gamepad() { | |||
| int i = SDL_NumJoysticks() - 1; | |||
| printf("Found %d joysticks\n", i + 1); | |||
| INPUT_INFO("Found %d joysticks\n", i + 1); | |||
| for ( ; i >= 0 && !SDL_IsGameController(i); --i); | |||
| if (i >= 0) printf("Joystick %d is a gamepad\n", i); | |||
| if (i >= 0) INPUT_INFO("Joystick %d is a gamepad\n", i); | |||
| return (i < 0 ? NULL : SDL_GameControllerOpen(i)); | |||
| } | |||
| static void sdl_lose_gamepad(void* data) { | |||
| SDL_GameController* gamepad = (SDL_GameController*)data; | |||
| if (NULL != gamepad) SDL_GameControllerClose(gamepad); | |||
| static void sdl_lose_gamepad(SDL_GameController* gamepad) { | |||
| if (NULL != gamepad) { | |||
| SDL_GameControllerClose(gamepad); | |||
| } | |||
| } | |||
| static int sdl_match_gamepad(SDL_JoystickID id, void* data) { | |||
| SDL_GameController* gamepad = (SDL_GameController*)data; | |||
| return ( SDL_JoystickInstanceID( | |||
| SDL_GameControllerGetJoystick(gamepad)) | |||
| == id); | |||
| static int sdl_match_gamepad(SDL_JoystickID id, | |||
| SDL_GameController* gamepad) { | |||
| return ( id == SDL_JoystickInstanceID( | |||
| SDL_GameControllerGetJoystick(gamepad))); | |||
| } | |||
| /* | |||
| static int sdl_event_filter(void*, SDL_Event* event) { | |||
| @@ -35,27 +60,32 @@ static int sdl_event_filter(void*, SDL_Event* event) { | |||
| } | |||
| */ | |||
| static int sdl_input_init(nes_Input_Reader* reader) { | |||
| reader->data = &the_input_data; | |||
| sdl_input_data* data = (sdl_input_data*)reader->data; | |||
| int status = SDL_Init(SDL_INIT_EVENTS | | |||
| SDL_INIT_GAMECONTROLLER); | |||
| if (status == 0) { | |||
| reader->data = sdl_find_gamepad(); | |||
| data->gamepad = sdl_find_gamepad(); | |||
| // SDL_SetEventFilter(sdl_event_filter, NULL); | |||
| if (NULL != reader->data) { | |||
| printf("Gamepad found\n"); | |||
| if (NULL != data->gamepad) { | |||
| INPUT_INFO("Gamepad found\n"); | |||
| } | |||
| } | |||
| return status; | |||
| } | |||
| static void sdl_input_done(nes_Input_Reader* input) { | |||
| sdl_lose_gamepad(input->data); | |||
| static void sdl_input_done(nes_Input_Reader* reader) { | |||
| sdl_input_data* data = (sdl_input_data*)reader->data; | |||
| sdl_lose_gamepad(data->gamepad); | |||
| } | |||
| static const int sdl_menu_key = SDLK_ESCAPE; | |||
| static const int sdl_save_key = SDLK_F1; | |||
| static const int sdl_load_key = SDLK_F2; | |||
| static const int sdl_alt_start_key = SDLK_RETURN; | |||
| static const int sdl_keycodes[nes_controller_num_buttons] = { | |||
| SDLK_a, | |||
| @@ -89,10 +119,31 @@ static int button_index(int keycode, const int* codes) { | |||
| return index; | |||
| } | |||
| #define input_debug(o, ...) INPUT_DBG(__VA_ARGS__) | |||
| /* | |||
| static void input_debug(Overlay* overlay, | |||
| const char* fmt, ...) { | |||
| char msg[100] = {0}; | |||
| va_list args; | |||
| va_start(args, fmt); | |||
| vsnprintf(msg, sizeof(msg) - 1, fmt, args); | |||
| va_end(args); | |||
| overlay_add_message(overlay, msg, 60); | |||
| } | |||
| */ | |||
| static int sdl_input_update(nes_Input_Reader* reader, | |||
| nes_input* input) { | |||
| nese_Components* comp) { | |||
| int status = input_Result_OK; | |||
| sdl_input_data* data = (sdl_input_data*)reader->data; | |||
| if (reader->menu_timer > 0 && --reader->menu_timer == 0) { | |||
| status = input_Result_Menu; | |||
| } | |||
| SDL_Event event = {0}; | |||
| while (0 == status && 0 != SDL_PollEvent(&event)) { | |||
| if (SDL_QUIT == event.type) { | |||
| @@ -101,6 +152,14 @@ static int sdl_input_update(nes_Input_Reader* reader, | |||
| } else if (SDL_WINDOWEVENT == event.type) { | |||
| if ( SDL_WINDOWEVENT_EXPOSED == | |||
| event.window.event) { | |||
| // We need to do this to flush both buffers | |||
| // nes_draw_last_frame(comp->rend, 1); | |||
| // nes_draw_done(comp->rend); | |||
| // We call this both times since it does both | |||
| // the toggle and the recalculation. | |||
| nes_render_refresh(comp->rend); | |||
| status = input_Result_Refresh; | |||
| } | |||
| @@ -108,15 +167,32 @@ static int sdl_input_update(nes_Input_Reader* reader, | |||
| SDL_KEYUP == event.type) && | |||
| 0 == event.key.repeat | |||
| ) { | |||
| int index = button_index(event.key.keysym.sym, | |||
| sdl_keycodes); | |||
| input_debug(&comp->rend->overlay, | |||
| "Input: K %d S %d\n", | |||
| SDL_KEYDOWN == event.type ? 1 : 0, | |||
| event.key.keysym.sym); | |||
| int index = ( sdl_alt_start_key == | |||
| event.key.keysym.sym) ? | |||
| Button_Start : button_index( | |||
| event.key.keysym.sym, | |||
| sdl_keycodes); | |||
| if (index >= 0) { | |||
| uint8_t mask = (1 << index); | |||
| if (SDL_KEYDOWN == event.type) { | |||
| input->controllers[0].buttons |= mask; | |||
| comp->sys->input.controllers[0].buttons |= mask; | |||
| } else { | |||
| input->controllers[0].buttons &= ~mask; | |||
| comp->sys->input.controllers[0].buttons &= ~mask; | |||
| } | |||
| /* | |||
| input_debug( | |||
| &comp->rend->overlay, | |||
| "Input: Key: %s %02x\n", | |||
| (SDL_KEYDOWN == event.type) ? | |||
| "Set" : "Clear", | |||
| mask | |||
| ); | |||
| */ | |||
| } else if ( sdl_menu_key == event.key.keysym.sym && | |||
| SDL_KEYDOWN == event.type) { | |||
| @@ -139,14 +215,37 @@ static int sdl_input_update(nes_Input_Reader* reader, | |||
| } else if ( SDL_CONTROLLERBUTTONDOWN == event.type || | |||
| SDL_CONTROLLERBUTTONUP == event.type) { | |||
| input_debug(&comp->rend->overlay, | |||
| "Input: B %d B %d\n", | |||
| SDL_CONTROLLERBUTTONDOWN == event.type ? | |||
| 1 : 0, | |||
| event.cbutton.button); | |||
| int index = button_index(event.cbutton.button, | |||
| sdl_buttons); | |||
| if (index >= 0) { | |||
| uint8_t mask = (1 << index); | |||
| if (SDL_CONTROLLERBUTTONDOWN == event.type) { | |||
| input->controllers[0].buttons |= mask; | |||
| comp->sys->input.controllers[0].buttons |= mask; | |||
| } else { | |||
| input->controllers[0].buttons &= ~mask; | |||
| comp->sys->input.controllers[0].buttons &= ~mask; | |||
| } | |||
| /* | |||
| input_debug( | |||
| &comp->rend->overlay, | |||
| "Input: Button: %s %02x\n", | |||
| (SDL_CONTROLLERBUTTONDOWN == event.type) ? | |||
| "Set" : "Clear", | |||
| mask | |||
| ); | |||
| */ | |||
| if (Button_Start == index) { | |||
| if (SDL_CONTROLLERBUTTONDOWN == event.type) { | |||
| reader->menu_timer = 60; | |||
| } else { | |||
| reader->menu_timer = 0; | |||
| } | |||
| } | |||
| } else if (sdl_menu_button == event.cbutton.button) { | |||
| @@ -175,50 +274,73 @@ static int sdl_input_update(nes_Input_Reader* reader, | |||
| uint8_t mask_set = 0; | |||
| uint8_t mask_clear = 0; | |||
| int dir = 0; | |||
| if (value <= -axis_threshold) dir = -1; | |||
| else if (value >= axis_threshold) dir = 1; | |||
| if (SDL_CONTROLLER_AXIS_LEFTX == axis) { | |||
| mask_clear = (1 << Button_Left) | | |||
| (1 << Button_Right); | |||
| if (value <= -8192) { | |||
| mask_set = (1 << Button_Left); | |||
| } else if (value >= 8192) { | |||
| mask_set = (1 << Button_Right); | |||
| if (dir != data->last_x_axis) { | |||
| mask_clear = (1 << Button_Left) | | |||
| (1 << Button_Right); | |||
| if (dir < 0) { | |||
| mask_set = (1 << Button_Left); | |||
| } else if (dir > 0) { | |||
| mask_set = (1 << Button_Right); | |||
| } | |||
| } | |||
| data->last_x_axis = dir; | |||
| } else if (SDL_CONTROLLER_AXIS_LEFTY == axis) { | |||
| mask_clear = (1 << Button_Down) | | |||
| (1 << Button_Up); | |||
| if (value <= -8192) { | |||
| mask_set = (1 << Button_Up); | |||
| } else if (value >= 8192) { | |||
| mask_set = (1 << Button_Down); | |||
| if (dir != data->last_y_axis) { | |||
| mask_clear = (1 << Button_Down) | | |||
| (1 << Button_Up); | |||
| if (dir < 0) { | |||
| mask_set = (1 << Button_Up); | |||
| } else if (dir > 0) { | |||
| mask_set = (1 << Button_Down); | |||
| } | |||
| } | |||
| data->last_y_axis = dir; | |||
| } | |||
| input->controllers[0].buttons &= ~mask_clear; | |||
| input->controllers[0].buttons |= mask_set; | |||
| uint32_t old_buttons = comp->sys->input.controllers[0].buttons; | |||
| uint32_t new_buttons = ((old_buttons & ~mask_clear) | mask_set); | |||
| comp->sys->input.controllers[0].buttons = new_buttons; | |||
| /* | |||
| if (old_buttons != new_buttons) { | |||
| input_debug(&comp->rend->overlay, | |||
| "Input: Axis: S %02x C %02x V %d\n", | |||
| mask_set, mask_clear, value); | |||
| } | |||
| */ | |||
| } else if (SDL_CONTROLLERDEVICEADDED == event.type) { | |||
| if (NULL == reader->data) { | |||
| printf("New gamepad connected\n"); | |||
| reader->data = sdl_find_gamepad(); | |||
| if (reader->data) printf("Using new gamepad\n"); | |||
| if (NULL == data->gamepad) { | |||
| INPUT_INFO("New gamepad connected\n"); | |||
| data->gamepad = sdl_find_gamepad(); | |||
| if (data->gamepad) INPUT_INFO("Using new gamepad\n"); | |||
| } else { | |||
| printf("Redundant gamepad connected\n"); | |||
| INPUT_INFO("Redundant gamepad connected\n"); | |||
| } | |||
| } else if (SDL_CONTROLLERDEVICEREMOVED == event.type) { | |||
| if (sdl_match_gamepad(event.cdevice.which, | |||
| reader->data)) { | |||
| printf("Gamepad disconnected\n"); | |||
| data->gamepad)) { | |||
| INPUT_INFO("Gamepad disconnected\n"); | |||
| sdl_lose_gamepad(reader->data); | |||
| reader->data = sdl_find_gamepad(); | |||
| if (reader->data) printf("Using another gamepad\n"); | |||
| data->gamepad = sdl_find_gamepad(); | |||
| if (data->gamepad) INPUT_INFO("Using another gamepad\n"); | |||
| } else { | |||
| printf("Redundant gamepad disconnected\n"); | |||
| INPUT_INFO("Redundant gamepad disconnected\n"); | |||
| } | |||
| } | |||
| } | |||
| if (0 != status) { | |||
| input_debug(&comp->rend->overlay, | |||
| "Input: Returning %d\n", status); | |||
| } | |||
| return status; | |||
| } | |||
| @@ -164,7 +164,7 @@ int sdl_overlay_frame(Overlay* overlay, sdl_overlay_font* font, | |||
| overlay_message* last = NULL, *next = NULL; | |||
| for ( overlay_message* message = overlay->messages; | |||
| NULL != message; | |||
| last = message, message = next) { | |||
| message = next) { | |||
| next = message->next; | |||
| render_string(rend, x, y, vx, vy, sx, sy, | |||
| @@ -179,6 +179,8 @@ int sdl_overlay_frame(Overlay* overlay, sdl_overlay_font* font, | |||
| } | |||
| free(message->string); | |||
| free(message); | |||
| } else { | |||
| last = message; | |||
| } | |||
| } | |||
| @@ -192,9 +192,12 @@ static void sdl_render_done(nes_Renderer* rend) { | |||
| static void sdl_render_fullscreen(nes_Renderer* rend, int on) { | |||
| sdl_render_data* data = (sdl_render_data*)rend->data; | |||
| SDL_SetWindowFullscreen(data->window, | |||
| on ? SDL_WINDOW_FULLSCREEN : 0); | |||
| } | |||
| static void sdl_render_refresh(nes_Renderer* rend) { | |||
| sdl_render_data* data = (sdl_render_data*)rend->data; | |||
| SDL_GetWindowSize(data->window, | |||
| &data->win_w, &data->win_h); | |||
| @@ -215,6 +218,10 @@ static void sdl_render_fullscreen(nes_Renderer* rend, int on) { | |||
| data->view.h = h; | |||
| SDL_RenderSetClipRect(data->renderer, &data->view); | |||
| // We need to do this to flush both buffers | |||
| // nes_draw_last_frame(rend, 1); | |||
| // nes_draw_done(rend); | |||
| } | |||
| static inline void render_sprite_line( | |||
| @@ -581,6 +588,8 @@ static int sdl_render(nes_Renderer* rend, nes_ppu* ppu) { | |||
| } else { | |||
| REND_LOG("Scanline %3d -> Postrender\n", ppu->scanline); | |||
| SDL_RenderClear(data->renderer); | |||
| SDL_UnlockTexture(data->texture); | |||
| SDL_RenderCopy(data->renderer, data->texture, | |||
| NULL, &data->view); | |||
| @@ -653,6 +662,7 @@ nes_Renderer sdl_renderer = { | |||
| .done = sdl_render_done, | |||
| .render = sdl_render, | |||
| .fullscreen = sdl_render_fullscreen, | |||
| .refresh = sdl_render_refresh, | |||
| .draw_last_frame = sdl_redraw_frame, | |||
| .draw_text = sdl_draw_text, | |||
| .text_size = sdl_text_size, | |||
| @@ -4,6 +4,13 @@ | |||
| #include "ini.h" | |||
| // Running State | |||
| // TODO | |||
| // Persistent State | |||
| void cart_info_done(cart_info* cart) { | |||
| if (cart->file) fclose(cart->file); | |||
| free(cart->filename); | |||
| @@ -5,6 +5,24 @@ | |||
| #include <stdio.h> | |||
| #include <stdlib.h> | |||
| #include "input.h" | |||
| #include "overlay.h" | |||
| #include "render.h" | |||
| #include "audio.h" | |||
| #include "nes.h" | |||
| // Running State | |||
| typedef struct nese_Components { | |||
| nes_Renderer* rend; | |||
| nes_Input_Reader* reader; | |||
| nes_Audio_Stream* audio; | |||
| nes* sys; | |||
| } nese_Components; | |||
| // Persistent State | |||
| typedef struct { | |||
| char* filename; | |||