| @@ -18,6 +18,7 @@ typedef enum { | |||
| input_Result_Cancel, | |||
| input_Result_View, | |||
| input_Result_Refresh, | |||
| input_Result_Scale, | |||
| } nes_Input_Result; | |||
| #define nes_controller_num_buttons (8U) | |||
| @@ -268,19 +268,22 @@ char* run_main_menu(menu_state* state, nese_Components* comp, | |||
| } | |||
| int run_game_menu(menu_state* state, nese_Components* comp, | |||
| const cart_info* cart) { | |||
| static char* items[] = { | |||
| const nese_State* nese) { | |||
| char* items[] = { | |||
| "Resume", | |||
| "Save", | |||
| "Restore", | |||
| "Reset", | |||
| "Select ROM", | |||
| #ifndef STANDALONE | |||
| "Toggle Fullscreen", | |||
| (nese->flags & (1 << State_Bit_Fullscreen)) ? | |||
| "Windowed" : "Fullscreen", | |||
| #endif | |||
| (nese->flags & (1 << State_Bit_Integer_Scale)) ? | |||
| "Force Aspect" : "Integer Scaling", | |||
| "Exit", | |||
| }; | |||
| static int choices[] = { | |||
| static const int choices[] = { | |||
| input_Result_OK, | |||
| input_Result_Save, | |||
| input_Result_Load, | |||
| @@ -289,9 +292,10 @@ int run_game_menu(menu_state* state, nese_Components* comp, | |||
| #ifndef STANDALONE | |||
| input_Result_View, | |||
| #endif | |||
| input_Result_Scale, | |||
| input_Result_Quit, | |||
| }; | |||
| static const file_list options = { | |||
| const file_list options = { | |||
| .files = items, | |||
| .count = (sizeof(items) / sizeof(*items)), | |||
| }; | |||
| @@ -299,7 +303,8 @@ int run_game_menu(menu_state* state, nese_Components* comp, | |||
| menu_state menu = {0}; | |||
| if (NULL != state) menu = *state; | |||
| int status = run_menu(&menu, &options, 100, comp, cart); | |||
| int status = run_menu(&menu, &options, 100, | |||
| comp, &nese->cart); | |||
| if (input_Result_Menu == status) { | |||
| status = input_Result_Cancel; | |||
| @@ -35,7 +35,7 @@ char* run_main_menu(menu_state*, nese_Components*, | |||
| // Returns nes_Input_Result indicating the choice | |||
| int run_game_menu(menu_state*, nese_Components*, | |||
| const cart_info*); | |||
| const nese_State*); | |||
| int modal_popup(const char* message, nese_Components*, | |||
| const cart_info*); | |||
| @@ -169,18 +169,19 @@ static int do_game_menu(menu_state* menu, | |||
| menu_state rom_menu = {0}; | |||
| while (1) { | |||
| status = run_game_menu(menu, comp, &state->cart); | |||
| status = run_game_menu(menu, comp, state); | |||
| if (input_Result_View == status) { | |||
| if ( input_Result_View == status || | |||
| input_Result_Scale == status ) { | |||
| if (input_Result_View == status) { | |||
| #ifndef STANDALONE | |||
| state->flags ^= (1 << State_Bit_Fullscreen); | |||
| state->flags ^= (1 << State_Bit_Fullscreen); | |||
| #endif | |||
| // We call this both times since it does both | |||
| // the toggle and the recalculation. | |||
| nes_render_fullscreen( | |||
| comp->rend, | |||
| state->flags & (1 << State_Bit_Fullscreen) | |||
| ); | |||
| } else { | |||
| state->flags ^= (1 << State_Bit_Integer_Scale); | |||
| } | |||
| nes_render_set_flags(comp->rend, state->flags); | |||
| continue; | |||
| } else if (input_Result_Menu == status) { | |||
| @@ -223,11 +224,7 @@ int main(int argc, char* argv[]) { | |||
| if (status == 0) { | |||
| status = nes_render_init(components.rend); | |||
| if (0 == status) { | |||
| nes_render_fullscreen( | |||
| components.rend, | |||
| (state.flags & (1 << State_Bit_Fullscreen)) | |||
| ); | |||
| nes_render_refresh(components.rend); | |||
| nes_render_set_flags( components.rend, state.flags); | |||
| } | |||
| } | |||
| @@ -10,7 +10,7 @@ typedef 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 (*set_flags)(struct nes_Renderer_t*, uint32_t flags); | |||
| void (*refresh)(struct nes_Renderer_t*); | |||
| void (*draw_last_frame)(struct nes_Renderer_t*, int dim); | |||
| @@ -19,6 +19,7 @@ typedef struct nes_Renderer_t { | |||
| void (*draw_done)(struct nes_Renderer_t*); | |||
| Overlay overlay; | |||
| uint32_t flags; | |||
| void* data; | |||
| } nes_Renderer; | |||
| @@ -34,9 +35,13 @@ static inline int nes_render(nes_Renderer* rend, nes_ppu* ppu) { | |||
| return rend->render(rend, ppu); | |||
| } | |||
| static inline void nes_render_fullscreen(nes_Renderer* rend, | |||
| int enable) { | |||
| rend->fullscreen(rend, enable); | |||
| static inline uint32_t nes_render_set_flags(nes_Renderer* rend, | |||
| uint32_t flags) { | |||
| uint32_t old_flags = rend->flags; | |||
| rend->set_flags(rend, flags); | |||
| rend->flags = flags; | |||
| return old_flags; | |||
| } | |||
| static inline void nes_render_refresh(nes_Renderer* rend) { | |||
| @@ -152,14 +152,7 @@ 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; | |||
| } | |||
| @@ -37,7 +37,6 @@ static inline uint8_t* chr_mem(const nes_ppu* ppu, | |||
| return ppu->mapper->chr_addr(ppu->map_data, addr); | |||
| } | |||
| typedef struct { | |||
| SDL_Window* window; | |||
| SDL_Renderer* renderer; | |||
| @@ -47,17 +46,84 @@ typedef struct { | |||
| sdl_overlay_font font; | |||
| SDL_Texture* texture; | |||
| int win_w; | |||
| int win_h; | |||
| SDL_Rect view; | |||
| } sdl_render_data; | |||
| static sdl_render_data the_render_data = {0}; | |||
| static void sdl_render_refresh(nes_Renderer* rend) { | |||
| sdl_render_data* data = (sdl_render_data*)rend->data; | |||
| int win_w = 0, win_h = 0; | |||
| SDL_GetWindowSize(data->window, &win_w, &win_h); | |||
| // Determine the viewport within the screen | |||
| int w = win_w; | |||
| int h = win_h; | |||
| if (!(rend->flags & (1 << State_Bit_Integer_Scale))) { | |||
| // Fullscreen | |||
| if ((w * nes_ppu_scan_h) > (h * nes_ppu_scan_w)) { | |||
| w = (h * nes_ppu_scan_w) / nes_ppu_scan_h; | |||
| } else { | |||
| h = (w * nes_ppu_scan_h) / nes_ppu_scan_w; | |||
| } | |||
| } else { | |||
| // Integer Scale | |||
| int scale_y = 1, scale_x = 1; | |||
| if ((w * nes_ppu_scan_h) > (h * nes_ppu_scan_w)) { | |||
| // Wide Window | |||
| // printf("Wide\n"); | |||
| scale_y = h / nes_ppu_scan_h; | |||
| scale_x = ( (scale_y * nes_ppu_scan_w) + | |||
| (nes_ppu_render_w / 2) ) / | |||
| nes_ppu_render_w; | |||
| if ((scale_x * nes_ppu_render_w) > w) { | |||
| scale_x = w / nes_ppu_render_w; | |||
| } | |||
| } else { | |||
| // Tall Window | |||
| // printf("Tall\n"); | |||
| scale_x = w / nes_ppu_render_w; | |||
| scale_y = ( (scale_x * nes_ppu_render_w) + | |||
| (nes_ppu_scan_w / 2) ) / nes_ppu_scan_w; | |||
| if ((scale_y * nes_ppu_render_h) > h) { | |||
| scale_y = h / nes_ppu_render_h; | |||
| } | |||
| } | |||
| h = scale_y * nes_ppu_render_h; | |||
| w = scale_x * nes_ppu_render_w; | |||
| /* | |||
| printf("Scale %d, %d : %d, %d -> %d, %d\n", | |||
| scale_x, scale_y, win_w, win_h, w, h); | |||
| */ | |||
| if (!(rend->flags & (1 << State_Bit_Fullscreen))) { | |||
| SDL_SetWindowSize(data->window, w, h); | |||
| int x = 0, y = 0; | |||
| SDL_GetWindowPosition(data->window, &x, &y); | |||
| SDL_SetWindowPosition(data->window, | |||
| x - ((w - win_w) / 2), | |||
| y - ((h - win_h) / 2)); | |||
| } | |||
| } | |||
| data->view.x = (win_w - w) / 2; | |||
| data->view.y = (win_h - h) / 2; | |||
| data->view.w = w; | |||
| data->view.h = h; | |||
| SDL_RenderSetClipRect(data->renderer, &data->view); | |||
| } | |||
| static int sdl_render_init(nes_Renderer* rend) { | |||
| sdl_render_data* data = &the_render_data; | |||
| rend->data = &the_render_data; | |||
| int status = SDL_Init(SDL_INIT_VIDEO); | |||
| if (0 != status) { | |||
| @@ -70,21 +136,11 @@ static int sdl_render_init(nes_Renderer* rend) { | |||
| SDL_GetCurrentDisplayMode(0, &mode); | |||
| int yscale = (mode.h - 1) / nes_ppu_scan_h; | |||
| int xscale = mode.w / nes_ppu_scan_w; | |||
| int scale = (xscale < yscale ? xscale : yscale); | |||
| data->win_w = (nes_ppu_scan_w * scale); | |||
| data->win_h = (nes_ppu_scan_h * scale); | |||
| data->view.w = data->win_w; | |||
| data->view.h = data->win_h; | |||
| data->window = SDL_CreateWindow( | |||
| "NESe", | |||
| SDL_WINDOWPOS_UNDEFINED, | |||
| SDL_WINDOWPOS_UNDEFINED, | |||
| data->win_w, data->win_h, 0 | |||
| SDL_WINDOWPOS_CENTERED, | |||
| SDL_WINDOWPOS_CENTERED, | |||
| mode.w, mode.h, 0 | |||
| ); | |||
| if (NULL == data->window) { | |||
| fprintf(stderr, "SDL: Failed to create window\n"); | |||
| @@ -106,6 +162,7 @@ static int sdl_render_init(nes_Renderer* rend) { | |||
| status = -1; | |||
| } else { | |||
| sdl_render_refresh(rend); | |||
| overlay_init(&rend->overlay); | |||
| sdl_overlay_font_init(data->renderer, &data->font); | |||
| } | |||
| @@ -171,8 +228,6 @@ static int sdl_render_init(nes_Renderer* rend) { | |||
| SDL_SetPaletteColors(data->foreground->format->palette, | |||
| nes_palette, 0U, 64U); | |||
| SDL_SetColorKey(data->foreground, SDL_TRUE, 0xFFU); | |||
| rend->data = &the_render_data; | |||
| } | |||
| return status; | |||
| @@ -190,38 +245,16 @@ static void sdl_render_done(nes_Renderer* rend) { | |||
| SDL_Quit(); | |||
| } | |||
| 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) { | |||
| static void sdl_render_set_flags(nes_Renderer* rend, | |||
| uint32_t flags) { | |||
| sdl_render_data* data = (sdl_render_data*)rend->data; | |||
| SDL_GetWindowSize(data->window, | |||
| &data->win_w, &data->win_h); | |||
| // Determine the viewport within the screen | |||
| int w = data->win_w; | |||
| int h = data->win_h; | |||
| if ((w * nes_ppu_scan_h) > (h * nes_ppu_scan_w)) { | |||
| w = (h * nes_ppu_scan_w) / nes_ppu_scan_h; | |||
| } else { | |||
| h = (w * nes_ppu_scan_h) / nes_ppu_scan_w; | |||
| } | |||
| data->view.x = (data->win_w - w) / 2; | |||
| data->view.y = (data->win_h - h) / 2; | |||
| data->view.w = w; | |||
| 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); | |||
| SDL_SetWindowFullscreen( | |||
| data->window, | |||
| (flags & (1 << State_Bit_Fullscreen)) ? | |||
| SDL_WINDOW_FULLSCREEN : 0 | |||
| ); | |||
| rend->flags = flags; | |||
| sdl_render_refresh(rend); | |||
| } | |||
| static inline void render_sprite_line( | |||
| @@ -661,7 +694,7 @@ nes_Renderer sdl_renderer = { | |||
| .init = sdl_render_init, | |||
| .done = sdl_render_done, | |||
| .render = sdl_render, | |||
| .fullscreen = sdl_render_fullscreen, | |||
| .set_flags = sdl_render_set_flags, | |||
| .refresh = sdl_render_refresh, | |||
| .draw_last_frame = sdl_redraw_frame, | |||
| .draw_text = sdl_draw_text, | |||
| @@ -43,12 +43,16 @@ static const ini_datum prefs_schema = { | |||
| .type = ini_section, | |||
| .name = ".flags", | |||
| .offset = offsetof(nese_State, flags), | |||
| .count = 1, | |||
| .count = 2, | |||
| .data = (ini_datum[]){ | |||
| { | |||
| .type = ini_flag, | |||
| .name = "fullscreen", | |||
| .shift = State_Bit_Fullscreen, | |||
| }, { | |||
| .type = ini_flag, | |||
| .name = "integer_scale", | |||
| .shift = State_Bit_Integer_Scale, | |||
| }, | |||
| } | |||
| }, | |||
| @@ -34,11 +34,12 @@ void cart_info_done(cart_info*); | |||
| typedef enum { | |||
| State_Bit_Fullscreen = 0, | |||
| } nese_State_Flags; | |||
| State_Bit_Integer_Scale = 1, | |||
| } nese_State_Flag_Bits; | |||
| typedef struct { | |||
| cart_info cart; | |||
| nese_State_Flags flags; | |||
| uint32_t flags; // nese_State_Flag_Bits | |||
| } nese_State; | |||