From 9cc8b3320afa38e37682cfecd60c8acc0ecabfd6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 4 Oct 2017 12:43:31 +0300 Subject: [PATCH 01/21] Starting to add a GUI to the SDL port --- Makefile | 4 +- SDL/background.bmp | Bin 0 -> 11592 bytes SDL/drop.bmp | Bin 69176 -> 0 bytes SDL/font.c | 956 +++++++++++++++++++++++++++++++++++++++++++++ SDL/font.h | 9 + SDL/gui.c | 213 ++++++++++ SDL/gui.h | 25 ++ SDL/main.c | 148 +------ 8 files changed, 1218 insertions(+), 137 deletions(-) create mode 100644 SDL/background.bmp delete mode 100644 SDL/drop.bmp create mode 100644 SDL/font.c create mode 100644 SDL/font.h create mode 100644 SDL/gui.c create mode 100644 SDL/gui.h diff --git a/Makefile b/Makefile index c185cf9..fcde67e 100755 --- a/Makefile +++ b/Makefile @@ -88,7 +88,7 @@ endif cocoa: $(BIN)/SameBoy.app quicklook: $(BIN)/SameBoy.qlgenerator -sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/drop.bmp +sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp bootroms: $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/dmg_boot.bin tester: $(TESTER_TARGET) $(BIN)/tester/dmg_boot.bin $(BIN)/tester/cgb_boot.bin all: cocoa sdl tester @@ -267,7 +267,7 @@ $(BIN)/SDL/LICENSE: LICENSE $(BIN)/SDL/registers.sym: Misc/registers.sym cp -f $^ $@ -$(BIN)/SDL/drop.bmp: SDL/drop.bmp +$(BIN)/SDL/background.bmp: SDL/background.bmp cp -f $^ $@ # Boot ROMs diff --git a/SDL/background.bmp b/SDL/background.bmp new file mode 100644 index 0000000000000000000000000000000000000000..4624cb2395bcfab8eba53aea3be99868f0fb6ab0 GIT binary patch literal 11592 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 */ + _, _, _, _, _, _ , + X, _, _, _, _, _ , + _, X, X, _, _, _ , + _, _, _, X, X, _ , + _, _, _, X, X, _ , + _, X, X, _, _, _ , + X, _, _, _, _, _ , + _, _, _, _, _, _ , + + /* ? */ + _, X, X, X, _, _ , + X, _, _, _, X, _ , + _, _, _, _, X, _ , + _, _, X, X, _, _ , + _, _, X, _, _, _ , + _, _, _, _, _, _ , + _, _, X, _, _, _ , + _, _, _, _, _, _ , + + /* @ */ + _, X, X, X, _, _ , + X, _, _, _, X, _ , + X, _, _, X, X, _ , + X, _, X, _, X, _ , + X, _, X, _, X, _ , + X, _, _, X, X, _ , + X, _, _, _, _, _ , + _, X, X, X, X, _ , + + /* A */ + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, X, _, X, _, _ , + _, X, _, X, _, _ , + _, X, X, X, _, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + _, _, _, _, _, _ , + + /* B */ + X, X, X, X, _, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, X, X, X, _, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, X, X, X, _, _ , + _, _, _, _, _, _ , + + /* C */ + _, X, X, X, _, _ , + X, _, _, _, X, _ , + X, _, _, _, _, _ , + X, _, _, _, _, _ , + X, _, _, _, _, _ , + X, _, _, _, X, _ , + _, X, X, X, _, _ , + _, _, _, _, _, _ , + + /* D */ + X, X, X, X, _, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, X, X, X, _, _ , + _, _, _, _, _, _ , + + /* E */ + X, X, X, X, X, _ , + X, _, _, _, _, _ , + X, _, _, _, _, _ , + X, X, X, X, _, _ , + X, _, _, _, _, _ , + X, _, _, _, _, _ , + X, X, X, X, X, _ , + _, _, _, _, _, _ , + + /* F */ + X, X, X, X, X, _ , + X, _, _, _, _, _ , + X, _, _, _, _, _ , + X, _, _, _, _, _ , + X, X, X, X, _, _ , + X, _, _, _, _, _ , + X, _, _, _, _, _ , + _, _, _, _, _, _ , + + /* G */ + _, X, X, X, _, _ , + X, _, _, _, X, _ , + X, _, _, _, _, _ , + X, _, X, X, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + _, X, X, X, _, _ , + _, _, _, _, _, _ , + + /* H */ + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, X, X, X, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + _, _, _, _, _, _ , + + /* I */ + X, X, X, X, X, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + X, X, X, X, X, _ , + _, _, _, _, _, _ , + + /* J */ + _, _, X, X, X, _ , + _, _, _, _, X, _ , + _, _, _, _, X, _ , + _, _, _, _, X, _ , + _, _, _, _, X, _ , + X, _, _, _, X, _ , + _, X, X, X, _, _ , + _, _, _, _, _, _ , + + /* K */ + X, _, _, _, X, _ , + X, _, _, X, _, _ , + X, _, X, _, _, _ , + X, X, _, _, _, _ , + X, _, X, _, _, _ , + X, _, _, X, _, _ , + X, _, _, _, X, _ , + _, _, _, _, _, _ , + + /* L */ + X, _, _, _, _, _ , + X, _, _, _, _, _ , + X, _, _, _, _, _ , + X, _, _, _, _, _ , + X, _, _, _, _, _ , + X, _, _, _, _, _ , + X, X, X, X, X, _ , + _, _, _, _, _, _ , + + /* M */ + X, _, _, _, X, _ , + X, X, _, X, X, _ , + X, X, _, X, X, _ , + X, _, X, _, X, _ , + X, _, X, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + _, _, _, _, _, _ , + + /* N */ + X, _, _, _, X, _ , + X, X, _, _, X, _ , + X, _, X, _, X, _ , + X, _, X, _, X, _ , + X, _, X, _, X, _ , + X, _, _, X, X, _ , + X, _, _, _, X, _ , + _, _, _, _, _, _ , + + /* O */ + _, X, X, X, _, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + _, X, X, X, _, _ , + _, _, _, _, _, _ , + + /* P */ + X, X, X, X, _, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, X, X, X, _, _ , + X, _, _, _, _, _ , + X, _, _, _, _, _ , + _, _, _, _, _, _ , + + /* Q */ + _, X, X, X, _, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, X, _, X, _ , + _, X, X, X, _, _ , + _, _, _, _, X, X , + + /* R */ + X, X, X, X, _, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, X, X, X, _, _ , + X, _, X, _, _, _ , + X, _, _, X, _, _ , + X, _, _, _, X, _ , + _, _, _, _, _, _ , + + /* S */ + _, X, X, X, _, _ , + X, _, _, _, X, _ , + X, _, _, _, _, _ , + _, X, X, X, _, _ , + _, _, _, _, X, _ , + X, _, _, _, X, _ , + _, X, X, X, _, _ , + _, _, _, _, _, _ , + + /* T */ + X, X, X, X, X, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, _, _, _, _ , + + /* U */ + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + _, X, X, X, _, _ , + _, _, _, _, _, _ , + + /* V */ + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + _, X, _, X, _, _ , + _, X, _, X, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, _, _, _, _ , + + /* W */ + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, X, _, X, _ , + X, _, X, _, X, _ , + X, X, _, X, X, _ , + X, X, _, X, X, _ , + X, _, _, _, X, _ , + _, _, _, _, _, _ , + + /* X */ + X, _, _, _, X, _ , + _, X, _, X, _, _ , + _, X, _, X, _, _ , + _, _, X, _, _, _ , + _, X, _, X, _, _ , + _, X, _, X, _, _ , + X, _, _, _, X, _ , + _, _, _, _, _, _ , + + /* Y */ + X, _, _, _, X, _ , + X, _, _, _, X, _ , + _, X, _, X, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, _, _, _, _ , + + /* Z */ + X, X, X, X, X, _ , + _, _, _, _, X, _ , + _, _, _, X, _, _ , + _, _, X, _, _, _ , + _, X, _, _, _, _ , + X, _, _, _, _, _ , + X, X, X, X, X, _ , + _, _, _, _, _, _ , + + /* [ */ + _, X, X, X, _, _ , + _, X, _, _, _, _ , + _, X, _, _, _, _ , + _, X, _, _, _, _ , + _, X, _, _, _, _ , + _, X, _, _, _, _ , + _, X, _, _, _, _ , + _, X, X, X, _, _ , + + /* \ */ + _, X, _, _, _, _ , + _, X, _, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, _, X, _, _ , + _, _, _, X, _, _ , + _, _, _, _, X, _ , + _, _, _, _, X, _ , + + /* ] */ + _, X, X, X, _, _ , + _, _, _, X, _, _ , + _, _, _, X, _, _ , + _, _, _, X, _, _ , + _, _, _, X, _, _ , + _, _, _, X, _, _ , + _, _, _, X, _, _ , + _, X, X, X, _, _ , + + /* ^ */ + _, _, X, _, _, _ , + _, X, _, X, _, _ , + _, X, _, X, _, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + _, _, _, _, _, _ , + _, _, _, _, _, _ , + _, _, _, _, _, _ , + + /* _ */ + _, _, _, _, _, _ , + _, _, _, _, _, _ , + _, _, _, _, _, _ , + _, _, _, _, _, _ , + _, _, _, _, _, _ , + _, _, _, _, _, _ , + _, _, _, _, _, _ , + X, X, X, X, X, X , + + /* ` */ + _, X, _, _, _, _ , + _, _, X, _, _, _ , + _, _, _, X, _, _ , + _, _, _, _, _, _ , + _, _, _, _, _, _ , + _, _, _, _, _, _ , + _, _, _, _, _, _ , + _, _, _, _, _, _ , + + /* a */ + _, _, _, _, _, _ , + _, _, _, _, _, _ , + _, X, X, _, _, _ , + _, _, _, X, _, _ , + _, X, X, X, _, _ , + X, _, _, X, _, _ , + _, X, X, _, X, _ , + _, _, _, _, _, _ , + + /* b */ + X, _, _, _, _, _ , + X, _, _, _, _, _ , + X, _, X, X, _, _ , + X, X, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, X, X, X, _, _ , + _, _, _, _, _, _ , + + /* c */ + _, _, _, _, _, _ , + _, _, _, _, _, _ , + _, X, X, X, _, _ , + X, _, _, _, X, _ , + X, _, _, _, _, _ , + X, _, _, _, X, _ , + _, X, X, X, _, _ , + _, _, _, _, _, _ , + + /* d */ + _, _, _, _, X, _ , + _, _, _, _, X, _ , + _, X, X, X, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + _, X, X, X, X, _ , + _, _, _, _, _, _ , + + /* e */ + _, _, _, _, _, _ , + _, _, _, _, _, _ , + _, X, X, X, _, _ , + X, _, _, _, X, _ , + X, X, X, X, X, _ , + X, _, _, _, _, _ , + _, X, X, X, X, _ , + _, _, _, _, _, _ , + + /* f */ + _, _, X, X, _, _ , + _, X, _, _, _, _ , + X, X, X, X, _, _ , + _, X, _, _, _, _ , + _, X, _, _, _, _ , + _, X, _, _, _, _ , + _, X, _, _, _, _ , + _, _, _, _, _, _ , + + /* g */ + _, _, _, _, _, _ , + _, _, _, _, _, _ , + _, X, X, X, X, _ , + X, _, _, X, _, _ , + _, X, X, _, _, _ , + _, _, _, X, X, _ , + X, _, _, _, X, _ , + _, X, X, X, _, _ , + + /* h */ + X, _, _, _, _, _ , + X, _, _, _, _, _ , + X, _, X, X, _, _ , + X, X, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + _, _, _, _, _, _ , + + /* i */ + _, _, X, _, _, _ , + _, _, _, _, _, _ , + X, X, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, _, _, _, _ , + + /* j */ + _, _, X, _, _, _ , + _, _, _, _, _, _ , + X, X, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + X, X, _, _, _, _ , + + /* k */ + X, _, _, _, _, _ , + X, _, _, _, _, _ , + X, _, _, _, X, _ , + X, _, X, X, _, _ , + X, X, _, _, _, _ , + X, _, X, X, _, _ , + X, _, _, _, X, _ , + _, _, _, _, _, _ , + + /* l */ + X, X, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, _, X, X, _ , + _, _, _, _, _, _ , + + /* m */ + _, _, _, _, _, _ , + _, _, _, _, _, _ , + X, X, X, X, _, _ , + X, _, X, _, X, _ , + X, _, X, _, X, _ , + X, _, X, _, X, _ , + X, _, X, _, X, _ , + _, _, _, _, _, _ , + + /* n */ + _, _, _, _, _, _ , + _, _, _, _, _, _ , + X, _, X, X, _, _ , + X, X, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + _, _, _, _, _, _ , + + /* o */ + _, _, _, _, _, _ , + _, _, _, _, _, _ , + _, X, X, X, _, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + _, X, X, X, _, _ , + _, _, _, _, _, _ , + + /* p */ + _, _, _, _, _, _ , + _, _, _, _, _, _ , + X, X, X, X, _, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, X, _, _, X, _ , + X, _, X, X, _, _ , + X, _, _, _, _, _ , + + /* q */ + _, _, _, _, _, _ , + _, _, _, _, _, _ , + _, X, X, X, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, X, X, _ , + _, X, X, _, X, _ , + _, _, _, _, X, _ , + + /* r */ + _, _, _, _, _, _ , + _, _, _, _, _, _ , + _, X, _, X, _, _ , + _, X, X, _, X, _ , + _, X, _, _, _, _ , + _, X, _, _, _, _ , + _, X, _, _, _, _ , + _, _, _, _, _, _ , + + /* s */ + _, _, _, _, _, _ , + _, _, _, _, _, _ , + _, X, X, X, X, _ , + X, _, _, _, _, _ , + _, X, X, X, _, _ , + _, _, _, _, X, _ , + X, X, X, X, _, _ , + _, _, _, _, _, _ , + + /* t */ + _, _, _, _, _, _ , + _, X, _, _, _, _ , + X, X, X, X, X, _ , + _, X, _, _, _, _ , + _, X, _, _, _, _ , + _, X, _, _, _, _ , + _, _, X, X, X, _ , + _, _, _, _, _, _ , + + /* u */ + _, _, _, _, _, _ , + _, _, _, _, _, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, X, X, _ , + _, X, X, _, X, _ , + _, _, _, _, _, _ , + + /* v */ + _, _, _, _, _, _ , + _, _, _, _, _, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + _, X, _, X, _, _ , + _, X, _, X, _, _ , + _, _, X, _, _, _ , + _, _, _, _, _, _ , + + /* w */ + _, _, _, _, _, _ , + _, _, _, _, _, _ , + X, _, _, _, X, _ , + X, _, X, _, X, _ , + X, _, X, _, X, _ , + _, X, _, X, _, _ , + _, X, _, X, _, _ , + _, _, _, _, _, _ , + + /* x */ + _, _, _, _, _, _ , + _, _, _, _, _, _ , + X, _, _, _, X, _ , + _, X, _, X, _, _ , + _, _, X, _, _, _ , + _, X, _, X, _, _ , + X, _, _, _, X, _ , + _, _, _, _, _, _ , + + /* y */ + _, _, _, _, _, _ , + _, _, _, _, _, _ , + X, _, _, _, X, _ , + X, _, _, _, X, _ , + X, _, _, X, X, _ , + _, X, X, _, X, _ , + _, _, _, _, X, _ , + _, X, X, X, _, _ , + + /* z */ + _, _, _, _, _, _ , + _, _, _, _, _, _ , + X, X, X, X, X, _ , + _, _, _, X, _, _ , + _, _, X, _, _, _ , + _, X, _, _, _, _ , + X, X, X, X, X, _ , + _, _, _, _, _, _ , + + /* { */ + _, _, X, X, _, _ , + _, X, _, _, _, _ , + _, X, _, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, X, _, _, _, _ , + _, X, _, _, _, _ , + _, _, X, X, _, _ , + + /* | */ + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + + /* } */ + _, X, X, _, _, _ , + _, _, _, X, _, _ , + _, _, _, X, _, _ , + _, _, X, _, _, _ , + _, _, X, _, _, _ , + _, _, _, X, _, _ , + _, _, _, X, _, _ , + _, X, X, _, _, _ , + + /* ~ */ + _, _, _, _, _, X , + _, _, _, _, _, _ , + _, _, X, _, _, X , + _, X, _, X, _, X , + _, X, _, _, X, _ , + _, _, _, _, _, _ , + _, _, _, _, _, _ , + _, _, _, _, _, _ , +}; diff --git a/SDL/font.h b/SDL/font.h new file mode 100644 index 0000000..0c2c78a --- /dev/null +++ b/SDL/font.h @@ -0,0 +1,9 @@ +#ifndef font_h +#define font_h + +#include +extern uint8_t font[]; +#define GLYPH_HEIGHT 8 +#define GLYPH_WIDTH 6 + +#endif /* font_h */ diff --git a/SDL/gui.c b/SDL/gui.c new file mode 100644 index 0000000..dab10ef --- /dev/null +++ b/SDL/gui.c @@ -0,0 +1,213 @@ +#include +#include +#include "utils.h" +#include "gui.h" +#include "font.h" + +static const SDL_Color gui_palette[4] = {{8, 24, 16,}, {57, 97, 57,}, {132, 165, 99}, {198, 222, 140}}; +static uint32_t gui_palette_native[4]; + +SDL_Window *window = NULL; +SDL_Renderer *renderer = NULL; +SDL_Texture *texture = NULL; +SDL_PixelFormat *pixel_format = NULL; +enum scaling_mode scaling_mode = GB_SDL_SCALING_INTEGER_FACTOR; + + +#ifdef __APPLE__ +#define MODIFIER_NAME "Cmd" +#else +#define MODIFIER_NAME "Ctrl" +#endif + + +static const char help[] = +"Drop a GB or GBC ROM file to play.\n" +"\n" +"Controls:\n" +" D-Pad: Arrow Keys\n" +" A: X\n" +" B: Z\n" +" Start: Enter\n" +" Select: Backspace\n" +"\n" +"Keyboard Shortcuts: \n" +" Restart: " MODIFIER_NAME "+R\n" +" Pause: " MODIFIER_NAME "+P\n" +" Turbo: Space\n" +#ifdef __APPLE__ +" Mute/Unmute: " MODIFIER_NAME "+Shift+M\n" +#else +" Mute/Unmute: " MODIFIER_NAME "+M\n" +#endif +" Save state: " MODIFIER_NAME "+Number (0-9)\n" +" Load state: " MODIFIER_NAME "+Shift+Number (0-9)\n" +" Cycle between DMG/CGB emulation: " MODIFIER_NAME "+T\n" +" Cycle scaling modes: Tab" +; + +void cycle_scaling(void) +{ + scaling_mode++; + scaling_mode %= GB_SDL_SCALING_MAX; + update_viewport(); + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); +} + +void update_viewport(void) +{ + int win_width, win_height; + SDL_GetWindowSize(window, &win_width, &win_height); + double x_factor = win_width / 160.0; + double y_factor = win_height / 144.0; + + if (scaling_mode == GB_SDL_SCALING_INTEGER_FACTOR) { + x_factor = (int)(x_factor); + y_factor = (int)(y_factor); + } + + if (scaling_mode != GB_SDL_SCALING_ENTIRE_WINDOW) { + if (x_factor > y_factor) { + x_factor = y_factor; + } + else { + y_factor = x_factor; + } + } + + unsigned new_width = x_factor * 160; + unsigned new_height = y_factor * 144; + + SDL_Rect rect = (SDL_Rect){(win_width - new_width) / 2, (win_height - new_height) /2, + new_width, new_height}; + SDL_RenderSetViewport(renderer, &rect); +} + +void show_help(void) +{ + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Help", help, window); +} + +/* Does NOT check for bounds! */ +static void draw_char(uint32_t *buffer, unsigned char ch, uint32_t color) +{ + if (ch < ' ' || ch > '~') { + ch = '?'; + } + + uint8_t *data = &font[(ch - ' ') * GLYPH_WIDTH * GLYPH_HEIGHT]; + + for (unsigned y = GLYPH_HEIGHT; y--;) { + for (unsigned x = GLYPH_WIDTH; x--;) { + if (*(data++)) { + (*buffer) = color; + } + buffer++; + } + buffer += 160 - GLYPH_WIDTH; + } +} + +/* Does NOT check for bounds! */ +static void draw_bordered_char(uint32_t *buffer, unsigned char ch, uint32_t color, uint32_t border) +{ + draw_char(buffer - 1, ch, border); + draw_char(buffer + 1, ch, border); + draw_char(buffer - 160, ch, border); + draw_char(buffer + 160, ch, border); + draw_char(buffer, ch, color); +} + +static void draw_text(uint32_t *buffer, unsigned x, unsigned y, const char *string, uint32_t color, uint32_t border) +{ + unsigned orig_x = x; + while (*string) { + if (*string == '\n') { + x = orig_x; + y += GLYPH_HEIGHT + 4; + string++; + continue; + } + + if (x == 0 || x > 160 - GLYPH_WIDTH - 1 || y == 0 || y > 144 - GLYPH_HEIGHT - 1) { + break; + } + + draw_bordered_char(&buffer[x + 160 * y], *string, color, border); + x += GLYPH_WIDTH; + string++; + } +} + +static void draw_text_centered(uint32_t *buffer, unsigned y, const char *string, uint32_t color, uint32_t border) +{ + draw_text(buffer, 160 / 2 - (unsigned) strlen(string) * GLYPH_WIDTH / 2, y, string, color, border); +} + + +extern void set_filename(const char *new_filename, bool new_should_free); +void run_gui(void) +{ + /* Draw the "Drop file" screen */ + static SDL_Surface *converted_background = NULL; + if (!converted_background) { + SDL_Surface *background = SDL_LoadBMP(executable_relative_path("background.bmp")); + SDL_SetPaletteColors(background->format->palette, gui_palette, 0, 4); + converted_background = SDL_ConvertSurface(background, pixel_format, 0); + SDL_LockSurface(converted_background); + SDL_FreeSurface(background); + + for (unsigned i = 4; i--; ) { + gui_palette_native[i] = SDL_MapRGB(pixel_format, gui_palette[i].r, gui_palette[i].g, gui_palette[i].b); + } + } + + uint32_t pixels[160 * 144]; + memcpy(pixels, converted_background->pixels, sizeof(pixels)); + + draw_text_centered(pixels, 116, "Drop a GB or GBC", gui_palette_native[3], gui_palette_native[0]); + draw_text_centered(pixels, 128, "file to play", gui_palette_native[3], gui_palette_native[0]); + + SDL_UpdateTexture(texture, NULL, pixels, 160 * sizeof (uint32_t)); + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); + + SDL_Event event; + while (SDL_WaitEvent(&event)) { + switch (event.type) { + case SDL_QUIT: { + exit(0); + } + case SDL_WINDOWEVENT: { + if (event.window.event == SDL_WINDOWEVENT_RESIZED) { + update_viewport(); + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); + } + break; + } + case SDL_DROPFILE: { + set_filename(event.drop.file, true); + return; + } + case SDL_KEYDOWN: + if (event.key.keysym.sym == SDLK_TAB) { + cycle_scaling(); + } +#ifndef __APPLE__ + else if (event.key.keysym.sym == SDLK_F1) { + show_help(); + } +#else + else if (event.key.keysym.sym == SDLK_QUESTION || (event.key.keysym.sym && (event.key.keysym.mod & KMOD_SHIFT))) { + show_help(); + } +#endif + break; + } + } +} diff --git a/SDL/gui.h b/SDL/gui.h new file mode 100644 index 0000000..28ffc55 --- /dev/null +++ b/SDL/gui.h @@ -0,0 +1,25 @@ +#ifndef gui_h +#define gui_h + +#include + +extern SDL_Window *window; +extern SDL_Renderer *renderer; +extern SDL_Texture *texture; +extern SDL_PixelFormat *pixel_format; + +enum scaling_mode { + GB_SDL_SCALING_ENTIRE_WINDOW, + GB_SDL_SCALING_KEEP_RATIO, + GB_SDL_SCALING_INTEGER_FACTOR, + GB_SDL_SCALING_MAX, +}; + +extern enum scaling_mode scaling_mode; + +void update_viewport(void); +void cycle_scaling(void); +void show_help(void); + +void run_gui(void); +#endif diff --git a/SDL/main.c b/SDL/main.c index e1fd764..d5035b9 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -2,8 +2,8 @@ #include #include #include "gb.h" - #include "utils.h" +#include "gui.h" #ifndef _WIN32 #define AUDIO_FREQUENCY 96000 @@ -12,37 +12,6 @@ #define AUDIO_FREQUENCY 44100 #endif -#ifdef __APPLE__ -#define MODIFIER_NAME "Cmd" -#else -#define MODIFIER_NAME "Ctrl" -#endif - -static const char help[] = -"Drop a GB or GBC ROM file to play.\n" -"\n" -"Controls:\n" -" D-Pad: Arrow Keys\n" -" A: X\n" -" B: Z\n" -" Start: Enter\n" -" Select: Backspace\n" -"\n" -"Keyboard Shortcuts: \n" -" Restart: " MODIFIER_NAME "+R\n" -" Pause: " MODIFIER_NAME "+P\n" -" Turbo: Space\n" -#ifdef __APPLE__ -" Mute/Unmute: " MODIFIER_NAME "+Shift+M\n" -#else -" Mute/Unmute: " MODIFIER_NAME "+M\n" -#endif -" Save state: " MODIFIER_NAME "+Number (0-9)\n" -" Load state: " MODIFIER_NAME "+Shift+Number (0-9)\n" -" Cycle between DMG/CGB emulation: " MODIFIER_NAME "+T\n" -" Cycle scaling modes: Tab" -; - GB_gameboy_t gb; static bool dmg = false; static bool paused = false; @@ -52,10 +21,15 @@ static char *filename = NULL; static bool should_free_filename = false; static char *battery_save_path_ptr; -static SDL_Window *window = NULL; -static SDL_Renderer *renderer = NULL; -static SDL_Texture *texture = NULL; -static SDL_PixelFormat *pixel_format = NULL; +void set_filename(const char *new_filename, bool new_should_free) +{ + if (filename && should_free_filename) { + free(filename); + } + filename = (char *) new_filename; + should_free_filename = new_should_free; +} + static SDL_AudioSpec want_aspec, have_aspec; static char *captured_log = NULL; @@ -106,53 +80,8 @@ static enum { GB_SDL_TOGGLE_MODEL_COMMAND, } pending_command; -static enum { - GB_SDL_SCALING_ENTIRE_WINDOW, - GB_SDL_SCALING_KEEP_RATIO, - GB_SDL_SCALING_INTEGER_FACTOR, - GB_SDL_SCALING_MAX, -} scaling_mode = GB_SDL_SCALING_INTEGER_FACTOR; static unsigned command_parameter; -static void update_viewport(void) -{ - int win_width, win_height; - SDL_GetWindowSize(window, &win_width, &win_height); - double x_factor = win_width / 160.0; - double y_factor = win_height / 144.0; - - if (scaling_mode == GB_SDL_SCALING_INTEGER_FACTOR) { - x_factor = (int)(x_factor); - y_factor = (int)(y_factor); - } - - if (scaling_mode != GB_SDL_SCALING_ENTIRE_WINDOW) { - if (x_factor > y_factor) { - x_factor = y_factor; - } - else { - y_factor = x_factor; - } - } - - unsigned new_width = x_factor * 160; - unsigned new_height = y_factor * 144; - - SDL_Rect rect = (SDL_Rect){(win_width - new_width) / 2, (win_height - new_height) /2, - new_width, new_height}; - SDL_RenderSetViewport(renderer, &rect); -} - -static void cycle_scaling(void) -{ - scaling_mode++; - scaling_mode %= GB_SDL_SCALING_MAX; - update_viewport(); - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); -} - static void handle_events(GB_gameboy_t *gb) { #ifdef __APPLE__ @@ -169,11 +98,7 @@ static void handle_events(GB_gameboy_t *gb) exit(0); case SDL_DROPFILE: { - if (should_free_filename) { - SDL_free(filename); - } - filename = event.drop.file; - should_free_filename = true; + set_filename(filename, true); pending_command = GB_SDL_NEW_FILE_COMMAND; break; } @@ -236,7 +161,7 @@ static void handle_events(GB_gameboy_t *gb) break; } case SDLK_QUESTION: - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Help", help, window); + show_help(); #endif default: @@ -495,55 +420,8 @@ usage: SDL_EventState(SDL_DROPFILE, SDL_ENABLE); if (filename == NULL) { - /* Draw the "Drop file" screen */ - SDL_Surface *drop_backround = SDL_LoadBMP(executable_relative_path("drop.bmp")); - SDL_Surface *drop_converted = SDL_ConvertSurface(drop_backround, pixel_format, 0); - SDL_LockSurface(drop_converted); - SDL_UpdateTexture(texture, NULL, drop_converted->pixels, drop_converted->pitch); - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); - SDL_FreeSurface(drop_converted); - SDL_FreeSurface(drop_backround); - SDL_Event event; - while (SDL_WaitEvent(&event)) - { - switch (event.type) { - case SDL_QUIT: { - exit(0); - } - case SDL_WINDOWEVENT: { - if (event.window.event == SDL_WINDOWEVENT_RESIZED) { - update_viewport(); - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); - } - break; - } - case SDL_DROPFILE: { - filename = event.drop.file; - should_free_filename = true; - goto start; - } - case SDL_KEYDOWN: - if (event.key.keysym.sym == SDLK_TAB) { - cycle_scaling(); - } -#ifndef __APPLE__ - else if (event.key.keysym.sym == SDLK_F1) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Help", help, window); - } -#else - else if (event.key.keysym.sym == SDLK_QUESTION || (event.key.keysym.sym && (event.key.keysym.mod & KMOD_SHIFT))) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Help", help, window); - } -#endif - break; - } - } + run_gui(); } -start: run(); // Never returns return 0; } From d171a0ccee1018373cd4eb6efba12296de821492 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 4 Oct 2017 12:43:46 +0300 Subject: [PATCH 02/21] Updating the DMG palette to match the new SDL GUI --- BootROMs/cgb_boot.asm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index c22aab9..92f0671 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -504,7 +504,7 @@ Palettes: dw $7FFF, $1BEF, $6180, $0000 ; Sameboy "Exclusives" dw $7FFF, $7FEA, $7D5F, $0000 ; CGA 1 - dw $1B77, $0AD2, $25E9, $1545 ; DMG LCD + dw $4778, $3290, $1D87, $0861 ; DMG LCD KeyCombinationPalettes db 1 ; Right From 947c62bea97de0d3b7135204fade6e93eaf1e432 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 4 Oct 2017 13:14:37 +0300 Subject: [PATCH 03/21] Oops --- SDL/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SDL/main.c b/SDL/main.c index d5035b9..ebd2779 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -24,7 +24,7 @@ static char *battery_save_path_ptr; void set_filename(const char *new_filename, bool new_should_free) { if (filename && should_free_filename) { - free(filename); + SDL_free(filename); } filename = (char *) new_filename; should_free_filename = new_should_free; @@ -98,7 +98,7 @@ static void handle_events(GB_gameboy_t *gb) exit(0); case SDL_DROPFILE: { - set_filename(filename, true); + set_filename(event.drop.file, true); pending_command = GB_SDL_NEW_FILE_COMMAND; break; } From c66e9a06cff8d3522b5031771ba99ccefd10eaad Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 14 Oct 2017 01:41:51 +0300 Subject: [PATCH 04/21] Added basic menu --- SDL/font.c | 1594 +++++++++++++++++++++++++++------------------------- SDL/font.h | 7 +- SDL/gui.c | 213 +++++-- SDL/gui.h | 15 +- SDL/main.c | 30 +- 5 files changed, 1013 insertions(+), 846 deletions(-) diff --git a/SDL/font.c b/SDL/font.c index fe6bbe7..1ee3999 100644 --- a/SDL/font.c +++ b/SDL/font.c @@ -5,952 +5,1024 @@ uint8_t font[] = { /* */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, /* ! */ - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, X, _, _, _ , - _, _, _, _, _, _ , + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, /* " */ - X, X, _, X, X, _ , - _, X, _, X, _, _ , - _, X, _, X, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , + X, X, _, X, X, _, + _, X, _, X, _, _, + _, X, _, X, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, /* # */ - _, X, _, X, _, _ , - _, X, _, X, _, _ , - X, X, X, X, X, _ , - _, X, _, X, _, _ , - X, X, X, X, X, _ , - _, X, _, X, _, _ , - _, X, _, X, _, _ , - _, _, _, _, _, _ , + _, X, _, X, _, _, + _, X, _, X, _, _, + X, X, X, X, X, _, + _, X, _, X, _, _, + X, X, X, X, X, _, + _, X, _, X, _, _, + _, X, _, X, _, _, + _, _, _, _, _, _, /* $ */ - _, _, X, _, _, _ , - _, X, X, X, _, _ , - X, _, X, _, X, _ , - _, X, X, _, _, _ , - _, _, X, X, _, _ , - X, _, X, _, X, _ , - _, X, X, X, _, _ , - _, _, X, _, _, _ , + _, _, X, _, _, _, + _, X, X, X, _, _, + X, _, X, _, X, _, + _, X, X, _, _, _, + _, _, X, X, _, _, + X, _, X, _, X, _, + _, X, X, X, _, _, + _, _, X, _, _, _, /* % */ - _, _, _, _, _, _ , - X, X, _, _, _, X , - X, X, _, _, X, _ , - _, _, _, X, _, _ , - _, _, X, _, _, _ , - _, X, _, _, X, X , - X, _, _, _, X, X , - _, _, _, _, _, _ , + _, _, _, _, _, _, + X, X, _, _, _, X, + X, X, _, _, X, _, + _, _, _, X, _, _, + _, _, X, _, _, _, + _, X, _, _, X, X, + X, _, _, _, X, X, + _, _, _, _, _, _, /* & */ - _, X, X, _, _, _ , - X, _, _, X, _, _ , - X, _, _, X, _, _ , - _, X, X, _, X, _ , - X, _, _, X, _, _ , - X, _, _, X, _, _ , - _, X, X, _, X, _ , - _, _, _, _, _, _ , + _, X, X, _, _, _, + X, _, _, X, _, _, + X, _, _, X, _, _, + _, X, X, _, X, _, + X, _, _, X, _, _, + X, _, _, X, _, _, + _, X, X, _, X, _, + _, _, _, _, _, _, /* ' */ - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, /* ( */ - _, _, _, X, _, _ , - _, _, X, _, _, _ , - _, X, _, _, _, _ , - _, X, _, _, _, _ , - _, X, _, _, _, _ , - _, X, _, _, _, _ , - _, _, X, _, _, _ , - _, _, _, X, _, _ , + _, _, _, X, _, _, + _, _, X, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, _, X, _, _, _, + _, _, _, X, _, _, /* ) */ - _, X, _, _, _, _ , - _, _, X, _, _, _ , - _, _, _, X, _, _ , - _, _, _, X, _, _ , - _, _, _, X, _, _ , - _, _, _, X, _, _ , - _, _, X, _, _, _ , - _, X, _, _, _, _ , + _, X, _, _, _, _, + _, _, X, _, _, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, X, _, _, _, + _, X, _, _, _, _, /* * */ - _, _, _, _, _, _ , - _, _, X, _, _, _ , - X, _, X, _, X, _ , - _, X, X, X, _, _ , - X, _, X, _, X, _ , - _, _, X, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , + _, _, _, _, _, _, + _, _, X, _, _, _, + X, _, X, _, X, _, + _, X, X, X, _, _, + X, _, X, _, X, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, /* + */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - X, X, X, X, X, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, _, _, _, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + X, X, X, X, X, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, - /* , */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, X, _, _, _, _ , + /*, */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, X, _, _, _, _, /* - */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - X, X, X, X, X, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, /* . */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, X, _, _, _ , - _, _, _, _, _, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, /* / */ - _, _, _, _, X, _ , - _, _, _, _, X, _ , - _, _, _, X, _, _ , - _, _, _, X, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, X, _, _, _, _ , - _, X, _, _, _, _ , + _, _, _, _, X, _, + _, _, _, _, X, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, /* 0 */ - _, X, X, X, _, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - _, X, X, X, _, _ , - _, _, _, _, _, _ , + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, /* 1 */ - _, _, X, _, _, _ , - X, X, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - X, X, X, X, X, _ , - _, _, _, _, _, _ , + _, _, X, _, _, _, + X, X, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, /* 2 */ - _, X, X, X, _, _ , - X, _, _, _, X, _ , - _, _, _, _, X, _ , - _, _, X, X, _, _ , - _, X, _, _, _, _ , - X, _, _, _, _, _ , - X, X, X, X, X, _ , - _, _, _, _, _, _ , + _, X, X, X, _, _, + X, _, _, _, X, _, + _, _, _, _, X, _, + _, _, X, X, _, _, + _, X, _, _, _, _, + X, _, _, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, /* 3 */ - _, X, X, X, _, _ , - X, _, _, _, X, _ , - _, _, _, _, X, _ , - _, _, X, X, _, _ , - _, _, _, _, X, _ , - X, _, _, _, X, _ , - _, X, X, X, _, _ , - _, _, _, _, _, _ , + _, X, X, X, _, _, + X, _, _, _, X, _, + _, _, _, _, X, _, + _, _, X, X, _, _, + _, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, /* 4 */ - _, _, _, X, _, _ , - _, _, X, X, _, _ , - _, X, _, X, _, _ , - X, _, _, X, _, _ , - X, X, X, X, X, _ , - _, _, _, X, _, _ , - _, _, _, X, _, _ , - _, _, _, _, _, _ , + _, _, _, X, _, _, + _, _, X, X, _, _, + _, X, _, X, _, _, + X, _, _, X, _, _, + X, X, X, X, X, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, _, _, _, _, /* 5 */ - X, X, X, X, X, _ , - X, _, _, _, _, _ , - X, _, _, _, _, _ , - X, X, X, X, _, _ , - _, _, _, _, X, _ , - X, _, _, _, X, _ , - _, X, X, X, _, _ , - _, _, _, _, _, _ , + X, X, X, X, X, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, X, X, X, _, _, + _, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, /* 6 */ - _, X, X, X, _, _ , - X, _, _, _, X, _ , - X, _, _, _, _, _ , - X, X, X, X, _, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - _, X, X, X, _, _ , - _, _, _, _, _, _ , + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, _, _, + X, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, /* 7 */ - X, X, X, X, X, _ , - _, _, _, _, X, _ , - _, _, _, X, _, _ , - _, _, _, X, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, _, _, _, _ , + X, X, X, X, X, _, + _, _, _, _, X, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, /* 8 */ - _, X, X, X, _, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - _, X, X, X, _, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - _, X, X, X, _, _ , - _, _, _, _, _, _ , + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, /* 9 */ - _, X, X, X, _, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - _, X, X, X, X, _ , - _, _, _, _, X, _ , - X, _, _, _, X, _ , - _, X, X, X, _, _ , - _, _, _, _, _, _ , + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, X, _, + _, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, /* : */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, X, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, X, _, _, _ , - _, _, _, _, _, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, /* ; */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, X, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, X, _, _, _, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, X, _, _, _, _, /* < */ - _, _, _, _, _, _ , - _, _, _, _, X, _ , - _, _, X, X, _, _ , - X, X, _, _, _, _ , - X, X, _, _, _, _ , - _, _, X, X, _, _ , - _, _, _, _, X, _ , - _, _, _, _, _, _ , + _, _, _, _, _, _, + _, _, _, _, X, _, + _, _, X, X, _, _, + X, X, _, _, _, _, + X, X, _, _, _, _, + _, _, X, X, _, _, + _, _, _, _, X, _, + _, _, _, _, _, _, /* = */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - X, X, X, X, X, _ , - _, _, _, _, _, _ , - X, X, X, X, X, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, + _, _, _, _, _, _, /* > */ - _, _, _, _, _, _ , - X, _, _, _, _, _ , - _, X, X, _, _, _ , - _, _, _, X, X, _ , - _, _, _, X, X, _ , - _, X, X, _, _, _ , - X, _, _, _, _, _ , - _, _, _, _, _, _ , + _, _, _, _, _, _, + X, _, _, _, _, _, + _, X, X, _, _, _, + _, _, _, X, X, _, + _, _, _, X, X, _, + _, X, X, _, _, _, + X, _, _, _, _, _, + _, _, _, _, _, _, /* ? */ - _, X, X, X, _, _ , - X, _, _, _, X, _ , - _, _, _, _, X, _ , - _, _, X, X, _, _ , - _, _, X, _, _, _ , - _, _, _, _, _, _ , - _, _, X, _, _, _ , - _, _, _, _, _, _ , + _, X, X, X, _, _, + X, _, _, _, X, _, + _, _, _, _, X, _, + _, _, X, X, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, /* @ */ - _, X, X, X, _, _ , - X, _, _, _, X, _ , - X, _, _, X, X, _ , - X, _, X, _, X, _ , - X, _, X, _, X, _ , - X, _, _, X, X, _ , - X, _, _, _, _, _ , - _, X, X, X, X, _ , + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, X, X, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + X, _, _, X, X, _, + X, _, _, _, _, _, + _, X, X, X, X, _, /* A */ - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, X, _, X, _, _ , - _, X, _, X, _, _ , - _, X, X, X, _, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - _, _, _, _, _, _ , + _, _, X, _, _, _, + _, _, X, _, _, _, + _, X, _, X, _, _, + _, X, _, X, _, _, + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, _, _, _, _, _, /* B */ - X, X, X, X, _, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, X, X, X, _, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, X, X, X, _, _ , - _, _, _, _, _, _ , + X, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, X, X, _, _, + _, _, _, _, _, _, /* C */ - _, X, X, X, _, _ , - X, _, _, _, X, _ , - X, _, _, _, _, _ , - X, _, _, _, _, _ , - X, _, _, _, _, _ , - X, _, _, _, X, _ , - _, X, X, X, _, _ , - _, _, _, _, _, _ , + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, /* D */ - X, X, X, X, _, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, X, X, X, _, _ , - _, _, _, _, _, _ , + X, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, X, X, _, _, + _, _, _, _, _, _, /* E */ - X, X, X, X, X, _ , - X, _, _, _, _, _ , - X, _, _, _, _, _ , - X, X, X, X, _, _ , - X, _, _, _, _, _ , - X, _, _, _, _, _ , - X, X, X, X, X, _ , - _, _, _, _, _, _ , + X, X, X, X, X, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, X, X, X, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, /* F */ - X, X, X, X, X, _ , - X, _, _, _, _, _ , - X, _, _, _, _, _ , - X, _, _, _, _, _ , - X, X, X, X, _, _ , - X, _, _, _, _, _ , - X, _, _, _, _, _ , - _, _, _, _, _, _ , + X, X, X, X, X, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, X, X, X, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + _, _, _, _, _, _, /* G */ - _, X, X, X, _, _ , - X, _, _, _, X, _ , - X, _, _, _, _, _ , - X, _, X, X, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - _, X, X, X, _, _ , - _, _, _, _, _, _ , + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, _, _, + X, _, X, X, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, /* H */ - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, X, X, X, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - _, _, _, _, _, _ , + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, X, X, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, _, _, _, _, _, /* I */ - X, X, X, X, X, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - X, X, X, X, X, _ , - _, _, _, _, _, _ , + X, X, X, X, X, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, /* J */ - _, _, X, X, X, _ , - _, _, _, _, X, _ , - _, _, _, _, X, _ , - _, _, _, _, X, _ , - _, _, _, _, X, _ , - X, _, _, _, X, _ , - _, X, X, X, _, _ , - _, _, _, _, _, _ , + _, _, X, X, X, _, + _, _, _, _, X, _, + _, _, _, _, X, _, + _, _, _, _, X, _, + _, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, /* K */ - X, _, _, _, X, _ , - X, _, _, X, _, _ , - X, _, X, _, _, _ , - X, X, _, _, _, _ , - X, _, X, _, _, _ , - X, _, _, X, _, _ , - X, _, _, _, X, _ , - _, _, _, _, _, _ , + X, _, _, _, X, _, + X, _, _, X, _, _, + X, _, X, _, _, _, + X, X, _, _, _, _, + X, _, X, _, _, _, + X, _, _, X, _, _, + X, _, _, _, X, _, + _, _, _, _, _, _, /* L */ - X, _, _, _, _, _ , - X, _, _, _, _, _ , - X, _, _, _, _, _ , - X, _, _, _, _, _ , - X, _, _, _, _, _ , - X, _, _, _, _, _ , - X, X, X, X, X, _ , - _, _, _, _, _, _ , + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, /* M */ - X, _, _, _, X, _ , - X, X, _, X, X, _ , - X, X, _, X, X, _ , - X, _, X, _, X, _ , - X, _, X, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - _, _, _, _, _, _ , + X, _, _, _, X, _, + X, X, _, X, X, _, + X, X, _, X, X, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, _, _, _, _, _, /* N */ - X, _, _, _, X, _ , - X, X, _, _, X, _ , - X, _, X, _, X, _ , - X, _, X, _, X, _ , - X, _, X, _, X, _ , - X, _, _, X, X, _ , - X, _, _, _, X, _ , - _, _, _, _, _, _ , + X, _, _, _, X, _, + X, X, _, _, X, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + X, _, _, X, X, _, + X, _, _, _, X, _, + _, _, _, _, _, _, /* O */ - _, X, X, X, _, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - _, X, X, X, _, _ , - _, _, _, _, _, _ , + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, /* P */ - X, X, X, X, _, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, X, X, X, _, _ , - X, _, _, _, _, _ , - X, _, _, _, _, _ , - _, _, _, _, _, _ , + X, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, X, X, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + _, _, _, _, _, _, /* Q */ - _, X, X, X, _, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, X, _, X, _ , - _, X, X, X, _, _ , - _, _, _, _, X, X , + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, X, _, X, _, + _, X, X, X, _, _, + _, _, _, _, X, X, /* R */ - X, X, X, X, _, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, X, X, X, _, _ , - X, _, X, _, _, _ , - X, _, _, X, _, _ , - X, _, _, _, X, _ , - _, _, _, _, _, _ , + X, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, X, X, _, _, + X, _, X, _, _, _, + X, _, _, X, _, _, + X, _, _, _, X, _, + _, _, _, _, _, _, /* S */ - _, X, X, X, _, _ , - X, _, _, _, X, _ , - X, _, _, _, _, _ , - _, X, X, X, _, _ , - _, _, _, _, X, _ , - X, _, _, _, X, _ , - _, X, X, X, _, _ , - _, _, _, _, _, _ , + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, _, _, + _, X, X, X, _, _, + _, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, /* T */ - X, X, X, X, X, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, _, _, _, _ , + X, X, X, X, X, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, /* U */ - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - _, X, X, X, _, _ , - _, _, _, _, _, _ , + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, /* V */ - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - _, X, _, X, _, _ , - _, X, _, X, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, _, _, _, _ , + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, _, X, _, _, + _, X, _, X, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, /* W */ - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, X, _, X, _ , - X, _, X, _, X, _ , - X, X, _, X, X, _ , - X, X, _, X, X, _ , - X, _, _, _, X, _ , - _, _, _, _, _, _ , + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + X, X, _, X, X, _, + X, X, _, X, X, _, + X, _, _, _, X, _, + _, _, _, _, _, _, /* X */ - X, _, _, _, X, _ , - _, X, _, X, _, _ , - _, X, _, X, _, _ , - _, _, X, _, _, _ , - _, X, _, X, _, _ , - _, X, _, X, _, _ , - X, _, _, _, X, _ , - _, _, _, _, _, _ , + X, _, _, _, X, _, + _, X, _, X, _, _, + _, X, _, X, _, _, + _, _, X, _, _, _, + _, X, _, X, _, _, + _, X, _, X, _, _, + X, _, _, _, X, _, + _, _, _, _, _, _, /* Y */ - X, _, _, _, X, _ , - X, _, _, _, X, _ , - _, X, _, X, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, _, _, _, _ , + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, _, X, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, /* Z */ - X, X, X, X, X, _ , - _, _, _, _, X, _ , - _, _, _, X, _, _ , - _, _, X, _, _, _ , - _, X, _, _, _, _ , - X, _, _, _, _, _ , - X, X, X, X, X, _ , - _, _, _, _, _, _ , + X, X, X, X, X, _, + _, _, _, _, X, _, + _, _, _, X, _, _, + _, _, X, _, _, _, + _, X, _, _, _, _, + X, _, _, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, /* [ */ - _, X, X, X, _, _ , - _, X, _, _, _, _ , - _, X, _, _, _, _ , - _, X, _, _, _, _ , - _, X, _, _, _, _ , - _, X, _, _, _, _ , - _, X, _, _, _, _ , - _, X, X, X, _, _ , + _, X, X, X, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, X, X, _, _, /* \ */ - _, X, _, _, _, _ , - _, X, _, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, _, X, _, _ , - _, _, _, X, _, _ , - _, _, _, _, X, _ , - _, _, _, _, X, _ , + _, X, _, _, _, _, + _, X, _, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, _, _, X, _, + _, _, _, _, X, _, /* ] */ - _, X, X, X, _, _ , - _, _, _, X, _, _ , - _, _, _, X, _, _ , - _, _, _, X, _, _ , - _, _, _, X, _, _ , - _, _, _, X, _, _ , - _, _, _, X, _, _ , - _, X, X, X, _, _ , + _, X, X, X, _, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, X, X, X, _, _, /* ^ */ - _, _, X, _, _, _ , - _, X, _, X, _, _ , - _, X, _, X, _, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , + _, _, X, _, _, _, + _, X, _, X, _, _, + _, X, _, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, /* _ */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - X, X, X, X, X, X , + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + X, X, X, X, X, X, /* ` */ - _, X, _, _, _, _ , - _, _, X, _, _, _ , - _, _, _, X, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , + _, X, _, _, _, _, + _, _, X, _, _, _, + _, _, _, X, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, /* a */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, X, X, _, _, _ , - _, _, _, X, _, _ , - _, X, X, X, _, _ , - X, _, _, X, _, _ , - _, X, X, _, X, _ , - _, _, _, _, _, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, X, _, _, _, + _, _, _, X, _, _, + _, X, X, X, _, _, + X, _, _, X, _, _, + _, X, X, _, X, _, + _, _, _, _, _, _, /* b */ - X, _, _, _, _, _ , - X, _, _, _, _, _ , - X, _, X, X, _, _ , - X, X, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, X, X, X, _, _ , - _, _, _, _, _, _ , + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, X, X, _, _, + X, X, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, X, X, _, _, + _, _, _, _, _, _, /* c */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, X, X, X, _, _ , - X, _, _, _, X, _ , - X, _, _, _, _, _ , - X, _, _, _, X, _ , - _, X, X, X, _, _ , - _, _, _, _, _, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, _, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, /* d */ - _, _, _, _, X, _ , - _, _, _, _, X, _ , - _, X, X, X, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - _, X, X, X, X, _ , - _, _, _, _, _, _ , + _, _, _, _, X, _, + _, _, _, _, X, _, + _, X, X, X, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, X, _, + _, _, _, _, _, _, /* e */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, X, X, X, _, _ , - X, _, _, _, X, _ , - X, X, X, X, X, _ , - X, _, _, _, _, _ , - _, X, X, X, X, _ , - _, _, _, _, _, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, X, X, _, _, + X, _, _, _, X, _, + X, X, X, X, X, _, + X, _, _, _, _, _, + _, X, X, X, X, _, + _, _, _, _, _, _, /* f */ - _, _, X, X, _, _ , - _, X, _, _, _, _ , - X, X, X, X, _, _ , - _, X, _, _, _, _ , - _, X, _, _, _, _ , - _, X, _, _, _, _ , - _, X, _, _, _, _ , - _, _, _, _, _, _ , + _, _, X, X, _, _, + _, X, _, _, _, _, + X, X, X, X, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, _, _, _, _, _, /* g */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, X, X, X, X, _ , - X, _, _, X, _, _ , - _, X, X, _, _, _ , - _, _, _, X, X, _ , - X, _, _, _, X, _ , - _, X, X, X, _, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, X, X, X, _, + X, _, _, X, _, _, + _, X, X, _, _, _, + _, _, _, X, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, /* h */ - X, _, _, _, _, _ , - X, _, _, _, _, _ , - X, _, X, X, _, _ , - X, X, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - _, _, _, _, _, _ , + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, X, X, _, _, + X, X, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, _, _, _, _, _, /* i */ - _, _, X, _, _, _ , - _, _, _, _, _, _ , - X, X, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, _, _, _, _ , + _, _, X, _, _, _, + _, _, _, _, _, _, + X, X, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, /* j */ - _, _, X, _, _, _ , - _, _, _, _, _, _ , - X, X, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - X, X, _, _, _, _ , + _, _, X, _, _, _, + _, _, _, _, _, _, + X, X, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + X, X, _, _, _, _, /* k */ - X, _, _, _, _, _ , - X, _, _, _, _, _ , - X, _, _, _, X, _ , - X, _, X, X, _, _ , - X, X, _, _, _, _ , - X, _, X, X, _, _ , - X, _, _, _, X, _ , - _, _, _, _, _, _ , + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, X, _, + X, _, X, X, _, _, + X, X, _, _, _, _, + X, _, X, X, _, _, + X, _, _, _, X, _, + _, _, _, _, _, _, /* l */ - X, X, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, _, X, X, _ , - _, _, _, _, _, _ , + X, X, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, X, X, _, + _, _, _, _, _, _, /* m */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - X, X, X, X, _, _ , - X, _, X, _, X, _ , - X, _, X, _, X, _ , - X, _, X, _, X, _ , - X, _, X, _, X, _ , - _, _, _, _, _, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + X, X, X, X, _, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + _, _, _, _, _, _, /* n */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - X, _, X, X, _, _ , - X, X, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - _, _, _, _, _, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + X, _, X, X, _, _, + X, X, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, _, _, _, _, _, /* o */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, X, X, X, _, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - _, X, X, X, _, _ , - _, _, _, _, _, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, /* p */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - X, X, X, X, _, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, X, _, _, X, _ , - X, _, X, X, _, _ , - X, _, _, _, _, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + X, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, _, _, X, _, + X, _, X, X, _, _, + X, _, _, _, _, _, /* q */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, X, X, X, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, X, X, _ , - _, X, X, _, X, _ , - _, _, _, _, X, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, X, X, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, X, X, _, + _, X, X, _, X, _, + _, _, _, _, X, _, /* r */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, X, _, X, _, _ , - _, X, X, _, X, _ , - _, X, _, _, _, _ , - _, X, _, _, _, _ , - _, X, _, _, _, _ , - _, _, _, _, _, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, _, X, _, _, + _, X, X, _, X, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, _, _, _, _, _, /* s */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, X, X, X, X, _ , - X, _, _, _, _, _ , - _, X, X, X, _, _ , - _, _, _, _, X, _ , - X, X, X, X, _, _ , - _, _, _, _, _, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, X, X, X, _, + X, _, _, _, _, _, + _, X, X, X, _, _, + _, _, _, _, X, _, + X, X, X, X, _, _, + _, _, _, _, _, _, /* t */ - _, _, _, _, _, _ , - _, X, _, _, _, _ , - X, X, X, X, X, _ , - _, X, _, _, _, _ , - _, X, _, _, _, _ , - _, X, _, _, _, _ , - _, _, X, X, X, _ , - _, _, _, _, _, _ , + _, _, _, _, _, _, + _, X, _, _, _, _, + X, X, X, X, X, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, _, X, X, X, _, + _, _, _, _, _, _, /* u */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, X, X, _ , - _, X, X, _, X, _ , - _, _, _, _, _, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, X, X, _, + _, X, X, _, X, _, + _, _, _, _, _, _, /* v */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - _, X, _, X, _, _ , - _, X, _, X, _, _ , - _, _, X, _, _, _ , - _, _, _, _, _, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, _, X, _, _, + _, X, _, X, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, /* w */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - X, _, _, _, X, _ , - X, _, X, _, X, _ , - X, _, X, _, X, _ , - _, X, _, X, _, _ , - _, X, _, X, _, _ , - _, _, _, _, _, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + X, _, _, _, X, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + _, X, _, X, _, _, + _, X, _, X, _, _, + _, _, _, _, _, _, /* x */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - X, _, _, _, X, _ , - _, X, _, X, _, _ , - _, _, X, _, _, _ , - _, X, _, X, _, _ , - X, _, _, _, X, _ , - _, _, _, _, _, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + X, _, _, _, X, _, + _, X, _, X, _, _, + _, _, X, _, _, _, + _, X, _, X, _, _, + X, _, _, _, X, _, + _, _, _, _, _, _, /* y */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - X, _, _, _, X, _ , - X, _, _, _, X, _ , - X, _, _, X, X, _ , - _, X, X, _, X, _ , - _, _, _, _, X, _ , - _, X, X, X, _, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, X, X, _, + _, X, X, _, X, _, + _, _, _, _, X, _, + _, X, X, X, _, _, /* z */ - _, _, _, _, _, _ , - _, _, _, _, _, _ , - X, X, X, X, X, _ , - _, _, _, X, _, _ , - _, _, X, _, _, _ , - _, X, _, _, _, _ , - X, X, X, X, X, _ , - _, _, _, _, _, _ , + _, _, _, _, _, _, + _, _, _, _, _, _, + X, X, X, X, X, _, + _, _, _, X, _, _, + _, _, X, _, _, _, + _, X, _, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, /* { */ - _, _, X, X, _, _ , - _, X, _, _, _, _ , - _, X, _, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, X, _, _, _, _ , - _, X, _, _, _, _ , - _, _, X, X, _, _ , + _, _, X, X, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, _, X, X, _, _, /* | */ - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, /* } */ - _, X, X, _, _, _ , - _, _, _, X, _, _ , - _, _, _, X, _, _ , - _, _, X, _, _, _ , - _, _, X, _, _, _ , - _, _, _, X, _, _ , - _, _, _, X, _, _ , - _, X, X, _, _, _ , + _, X, X, _, _, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, X, X, _, _, _, /* ~ */ - _, _, _, _, _, X , - _, _, _, _, _, _ , - _, _, X, _, _, X , - _, X, _, X, _, X , - _, X, _, _, X, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , - _, _, _, _, _, _ , + _, _, _, _, _, X, + _, _, _, _, _, _, + _, _, X, _, _, X, + _, X, _, X, _, X, + _, X, _, _, X, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + + /* Custom characters */ + /* Selection */ + _, X, _, _, _, _, + _, X, X, _, _, _, + _, X, X, X, _, _, + _, X, X, X, X, _, + _, X, X, X, _, _, + _, X, X, _, _, _, + _, X, _, _, _, _, + _, _, _, _, _, _, + + + /* CTRL symbol */ + _, X, X, X, _, X, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + X, X, _, X, X, X, + X, _, _, X, _, _, + X, _, _, X, _, _, + X, _, _, X, _, _, + X, _, _, X, X, X, + X, _, _, X, _, X, + X, _, _, X, _, _, + _, _, _, _, _, _, + + _, _, X, _, _, _, + X, _, X, _, _, _, + X, _, X, _, _, _, + X, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + X, _, X, X, X, X, + _, _, _, _, _, _, + + /* Shift symbol */ + _, _, X, X, _, _, + _, X, X, X, X, _, + X, X, X, X, X, X, + _, X, X, X, X, _, + _, X, X, X, X, _, + _, X, X, X, X, _, + _, X, X, X, X, _, + _, _, _, _, _, _, + + /* Cmd symbol */ + + _, _, _, _, _, X, + _, _, _, _, X, _, + _, _, _, _, _, X, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, X, + _, _, _, _, X, _, + _, _, _, _, _, X, + + _, _, _, _, X, _, + X, _, _, X, _, X, + X, X, X, X, X, _, + X, _, _, X, _, _, + X, _, _, X, _, _, + X, X, X, X, X, _, + X, _, _, X, _, X, + _, _, _, _, X, _, }; + +const uint8_t font_max = sizeof(font) / GLYPH_HEIGHT / GLYPH_WIDTH + ' '; diff --git a/SDL/font.h b/SDL/font.h index 0c2c78a..e4bc9ca 100644 --- a/SDL/font.h +++ b/SDL/font.h @@ -3,7 +3,12 @@ #include extern uint8_t font[]; +extern const uint8_t font_max; #define GLYPH_HEIGHT 8 #define GLYPH_WIDTH 6 - +#define SELECTION_STRING "\x7f" +#define CTRL_STRING "\x80\x81\x82" +#define SHIFT_STRING "\x83" +#define CMD_STRING "\x84\x85" #endif /* font_h */ + diff --git a/SDL/gui.c b/SDL/gui.c index dab10ef..afb9c5d 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -12,39 +12,48 @@ SDL_Renderer *renderer = NULL; SDL_Texture *texture = NULL; SDL_PixelFormat *pixel_format = NULL; enum scaling_mode scaling_mode = GB_SDL_SCALING_INTEGER_FACTOR; +enum pending_command pending_command; +unsigned command_parameter; #ifdef __APPLE__ -#define MODIFIER_NAME "Cmd" +#define MODIFIER_NAME " " CMD_STRING #else -#define MODIFIER_NAME "Ctrl" +#define MODIFIER_NAME CTRL_STRING #endif -static const char help[] = -"Drop a GB or GBC ROM file to play.\n" +static const char *help[] ={ +"Drop a GB or GBC ROM\n" +"file to play.\n" "\n" + "Controls:\n" -" D-Pad: Arrow Keys\n" -" A: X\n" -" B: Z\n" -" Start: Enter\n" -" Select: Backspace\n" +" D-Pad: Arrow Keys\n" +" A: X\n" +" B: Z\n" +" Start: Enter\n" +" Select: Backspace\n" "\n" +" Turbo: Space\n" +" Menu: Escape\n", "Keyboard Shortcuts: \n" -" Restart: " MODIFIER_NAME "+R\n" -" Pause: " MODIFIER_NAME "+P\n" -" Turbo: Space\n" +" Reset: " MODIFIER_NAME "+R\n" +" Pause: " MODIFIER_NAME "+P\n" +" Toggle DMG/CGB: " MODIFIER_NAME "+T\n" +"\n" +" Save state: " MODIFIER_NAME "+(0-9)\n" +" Load state: " MODIFIER_NAME "+" SHIFT_STRING "+(0-9)\n" +"\n" #ifdef __APPLE__ -" Mute/Unmute: " MODIFIER_NAME "+Shift+M\n" +" Mute/Unmute: " MODIFIER_NAME "+" SHIFT_STRING "+M\n" #else -" Mute/Unmute: " MODIFIER_NAME "+M\n" +" Mute/Unmute: " MODIFIER_NAME "+M\n" #endif -" Save state: " MODIFIER_NAME "+Number (0-9)\n" -" Load state: " MODIFIER_NAME "+Shift+Number (0-9)\n" -" Cycle between DMG/CGB emulation: " MODIFIER_NAME "+T\n" -" Cycle scaling modes: Tab" -; +" Cycle scaling modes: Tab" +"\n" +" Break Debugger: " CTRL_STRING "+C" +}; void cycle_scaling(void) { @@ -85,15 +94,10 @@ void update_viewport(void) SDL_RenderSetViewport(renderer, &rect); } -void show_help(void) -{ - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Help", help, window); -} - /* Does NOT check for bounds! */ static void draw_char(uint32_t *buffer, unsigned char ch, uint32_t color) { - if (ch < ' ' || ch > '~') { + if (ch < ' ' || ch > font_max) { ch = '?'; } @@ -110,17 +114,7 @@ static void draw_char(uint32_t *buffer, unsigned char ch, uint32_t color) } } -/* Does NOT check for bounds! */ -static void draw_bordered_char(uint32_t *buffer, unsigned char ch, uint32_t color, uint32_t border) -{ - draw_char(buffer - 1, ch, border); - draw_char(buffer + 1, ch, border); - draw_char(buffer - 160, ch, border); - draw_char(buffer + 160, ch, border); - draw_char(buffer, ch, color); -} - -static void draw_text(uint32_t *buffer, unsigned x, unsigned y, const char *string, uint32_t color, uint32_t border) +static void draw_unbordered_text(uint32_t *buffer, unsigned x, unsigned y, const char *string, uint32_t color) { unsigned orig_x = x; while (*string) { @@ -131,24 +125,75 @@ static void draw_text(uint32_t *buffer, unsigned x, unsigned y, const char *stri continue; } - if (x == 0 || x > 160 - GLYPH_WIDTH - 1 || y == 0 || y > 144 - GLYPH_HEIGHT - 1) { + if (x > 160 - GLYPH_WIDTH || y == 0 || y > 144 - GLYPH_HEIGHT) { break; } - draw_bordered_char(&buffer[x + 160 * y], *string, color, border); + draw_char(&buffer[x + 160 * y], *string, color); x += GLYPH_WIDTH; string++; } } -static void draw_text_centered(uint32_t *buffer, unsigned y, const char *string, uint32_t color, uint32_t border) +static void draw_text(uint32_t *buffer, unsigned x, unsigned y, const char *string, uint32_t color, uint32_t border) { - draw_text(buffer, 160 / 2 - (unsigned) strlen(string) * GLYPH_WIDTH / 2, y, string, color, border); + draw_unbordered_text(buffer, x - 1, y, string, border); + draw_unbordered_text(buffer, x + 1, y, string, border); + draw_unbordered_text(buffer, x, y - 1, string, border); + draw_unbordered_text(buffer, x, y + 1, string, border); + draw_unbordered_text(buffer, x, y, string, color); } +static void draw_text_centered(uint32_t *buffer, unsigned y, const char *string, uint32_t color, uint32_t border, bool show_selection) +{ + unsigned x = 160 / 2 - (unsigned) strlen(string) * GLYPH_WIDTH / 2; + draw_text(buffer, x, y, string, color, border); + if (show_selection) { + draw_text(buffer, x - GLYPH_WIDTH, y, SELECTION_STRING, color, border); + } +} + +struct menu_item { + const char *string; + void (*handler)(void); +}; +static const struct menu_item *current_menu = NULL; +static unsigned current_selection = 0; + +static enum { + SHOWING_DROP_MESSAGE, + SHOWING_MENU, + SHOWING_HELP, +} gui_state; + +static void item_exit(void) +{ + exit(0); +} + +static unsigned current_help_page = 0; +static void item_help(void) +{ + current_help_page = 0; + gui_state = SHOWING_HELP; +} + +static const struct menu_item paused_menu[] = { + {"Resume", NULL}, + {"Help", item_help}, + {"Exit", item_exit}, + {NULL,} +}; + +static const struct menu_item nonpaused_menu[] = { + {"Help", item_help}, + {"Exit", item_exit}, + {NULL,} +}; + extern void set_filename(const char *new_filename, bool new_should_free); -void run_gui(void) +void run_gui(bool is_running) { /* Draw the "Drop file" screen */ static SDL_Surface *converted_background = NULL; @@ -165,18 +210,37 @@ void run_gui(void) } uint32_t pixels[160 * 144]; - memcpy(pixels, converted_background->pixels, sizeof(pixels)); - - draw_text_centered(pixels, 116, "Drop a GB or GBC", gui_palette_native[3], gui_palette_native[0]); - draw_text_centered(pixels, 128, "file to play", gui_palette_native[3], gui_palette_native[0]); - - SDL_UpdateTexture(texture, NULL, pixels, 160 * sizeof (uint32_t)); - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); - SDL_Event event; + gui_state = is_running? SHOWING_MENU : SHOWING_DROP_MESSAGE; + bool should_render = true; + current_menu = is_running? paused_menu : nonpaused_menu; while (SDL_WaitEvent(&event)) { + if (should_render) { + should_render = false; + memcpy(pixels, converted_background->pixels, sizeof(pixels)); + + switch (gui_state) { + case SHOWING_DROP_MESSAGE: + draw_text_centered(pixels, 116, "Drop a GB or GBC", gui_palette_native[3], gui_palette_native[0], false); + draw_text_centered(pixels, 128, "file to play", gui_palette_native[3], gui_palette_native[0], false); + break; + case SHOWING_MENU: + draw_text_centered(pixels, 16, "SameBoy", gui_palette_native[3], gui_palette_native[0], false); + unsigned i = 0; + for (const struct menu_item *item = current_menu; item->string; item++, i++) { + draw_text_centered(pixels, 12 * i + 40, item->string, gui_palette_native[3], gui_palette_native[0], i == current_selection); + } + break; + case SHOWING_HELP: + draw_text(pixels, 2, 2, help[current_help_page], gui_palette_native[3], gui_palette_native[0]); + break; + } + + SDL_UpdateTexture(texture, NULL, pixels, 160 * sizeof (uint32_t)); + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); + } switch (event.type) { case SDL_QUIT: { exit(0); @@ -192,21 +256,54 @@ void run_gui(void) } case SDL_DROPFILE: { set_filename(event.drop.file, true); + pending_command = GB_SDL_NEW_FILE_COMMAND; return; } case SDL_KEYDOWN: if (event.key.keysym.sym == SDLK_TAB) { cycle_scaling(); } -#ifndef __APPLE__ - else if (event.key.keysym.sym == SDLK_F1) { - show_help(); + else if (event.key.keysym.sym == SDLK_ESCAPE) { + if (is_running) { + return; + } + else { + if (gui_state == SHOWING_DROP_MESSAGE) { + gui_state = SHOWING_MENU; + } + else if (gui_state == SHOWING_MENU) { + gui_state = SHOWING_DROP_MESSAGE; + } + should_render = true; + } } -#else - else if (event.key.keysym.sym == SDLK_QUESTION || (event.key.keysym.sym && (event.key.keysym.mod & KMOD_SHIFT))) { - show_help(); + + if (gui_state == SHOWING_MENU) { + if (event.key.keysym.sym == SDLK_DOWN && current_menu[current_selection + 1].string) { + current_selection++; + should_render = true; + } + else if (event.key.keysym.sym == SDLK_UP && current_selection) { + current_selection--; + should_render = true; + } + else if (event.key.keysym.sym == SDLK_RETURN) { + if (current_menu[current_selection].handler) { + current_menu[current_selection].handler(); + should_render = true; + } + else { + return; + } + } + } + else if(gui_state == SHOWING_HELP) { + current_help_page++; + if (current_help_page == sizeof(help) / sizeof(help[0])) { + gui_state = SHOWING_MENU; + } + should_render = true; } -#endif break; } } diff --git a/SDL/gui.h b/SDL/gui.h index 28ffc55..35458b3 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -17,9 +17,20 @@ enum scaling_mode { extern enum scaling_mode scaling_mode; +enum pending_command { + GB_SDL_NO_COMMAND, + GB_SDL_SAVE_STATE_COMMAND, + GB_SDL_LOAD_STATE_COMMAND, + GB_SDL_RESET_COMMAND, + GB_SDL_NEW_FILE_COMMAND, + GB_SDL_TOGGLE_MODEL_COMMAND, +}; + +extern enum pending_command pending_command; +extern unsigned command_parameter; + void update_viewport(void); void cycle_scaling(void); -void show_help(void); -void run_gui(void); +void run_gui(bool is_running); #endif diff --git a/SDL/main.c b/SDL/main.c index ebd2779..2ee4aa1 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -71,17 +71,6 @@ static const char *end_capturing_logs(bool show_popup, bool should_exit) return captured_log; } -static enum { - GB_SDL_NO_COMMAND, - GB_SDL_SAVE_STATE_COMMAND, - GB_SDL_LOAD_STATE_COMMAND, - GB_SDL_RESET_COMMAND, - GB_SDL_NEW_FILE_COMMAND, - GB_SDL_TOGGLE_MODEL_COMMAND, -} pending_command; - -static unsigned command_parameter; - static void handle_events(GB_gameboy_t *gb) { #ifdef __APPLE__ @@ -111,6 +100,10 @@ static void handle_events(GB_gameboy_t *gb) case SDL_KEYDOWN: switch (event.key.keysym.sym) { + case SDLK_ESCAPE: + run_gui(true); + break; + case SDLK_c: if (event.type == SDL_KEYDOWN && (event.key.keysym.mod & KMOD_CTRL)) { GB_debugger_break(gb); @@ -151,18 +144,6 @@ static void handle_events(GB_gameboy_t *gb) case SDLK_TAB: cycle_scaling(); break; -#ifndef __APPLE__ - case SDLK_F1: - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Help", help, window); - break; -#else - case SDLK_SLASH: - if (!(event.key.keysym.sym && (event.key.keysym.mod & KMOD_SHIFT))) { - break; - } - case SDLK_QUESTION: - show_help(); -#endif default: /* Save states */ @@ -292,6 +273,7 @@ static bool handle_pending_command(void) static void run(void) { + pending_command = GB_SDL_NO_COMMAND; restart: if (GB_is_inited(&gb)) { GB_switch_model_and_reset(&gb, !dmg); @@ -420,7 +402,7 @@ usage: SDL_EventState(SDL_DROPFILE, SDL_ENABLE); if (filename == NULL) { - run_gui(); + run_gui(false); } run(); // Never returns return 0; From 2dfe22e8341b8c266ba7db20fab108ee9397f268 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 14 Oct 2017 14:10:26 +0300 Subject: [PATCH 05/21] Added graphics options menu, fixed issues with exiting not saving battery in some cases --- SDL/gui.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++----- SDL/gui.h | 3 +++ SDL/main.c | 12 +++++++-- 3 files changed, 82 insertions(+), 8 deletions(-) diff --git a/SDL/gui.c b/SDL/gui.c index afb9c5d..2abc09d 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -14,7 +14,7 @@ SDL_PixelFormat *pixel_format = NULL; enum scaling_mode scaling_mode = GB_SDL_SCALING_INTEGER_FACTOR; enum pending_command pending_command; unsigned command_parameter; - +GB_color_correction_mode_t color_correction_mode = GB_COLOR_CORRECTION_EMULATE_HARDWARE; #ifdef __APPLE__ #define MODIFIER_NAME " " CMD_STRING @@ -156,8 +156,10 @@ static void draw_text_centered(uint32_t *buffer, unsigned y, const char *string, struct menu_item { const char *string; void (*handler)(void); + const char *(*value_getter)(void); }; static const struct menu_item *current_menu = NULL; +static const struct menu_item *root_menu = NULL; static unsigned current_selection = 0; static enum { @@ -168,7 +170,7 @@ static enum { static void item_exit(void) { - exit(0); + pending_command = GB_SDL_QUIT_COMMAND; } static unsigned current_help_page = 0; @@ -178,19 +180,61 @@ static void item_help(void) gui_state = SHOWING_HELP; } +static void enter_graphics_menu(void); + static const struct menu_item paused_menu[] = { {"Resume", NULL}, + {"Graphic Options", enter_graphics_menu}, {"Help", item_help}, {"Exit", item_exit}, {NULL,} }; static const struct menu_item nonpaused_menu[] = { + {"Graphic Options", enter_graphics_menu}, {"Help", item_help}, {"Exit", item_exit}, {NULL,} }; +const char *current_scaling_mode(void) +{ + return (const char *[]){"Fill Entire Window", "Retain Aspect Ratio", "Retain Integer Factor"}[scaling_mode]; +} + +const char *current_color_correction_mode(void) +{ + return (const char *[]){"Disabled", "Correct Color Curves", "Emulate Hardware", "Preserve Brightness"}[color_correction_mode]; +} + +static void cycle_color_correction(void) +{ + if (color_correction_mode == GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS) { + color_correction_mode = GB_COLOR_CORRECTION_DISABLED; + } + else { + color_correction_mode++; + } +} + +static void return_to_root_menu(void) +{ + current_menu = root_menu; + current_selection = 0; +} + +static const struct menu_item graphics_menu[] = { + {"Scaling Mode:", cycle_scaling, current_scaling_mode}, + {"Color Correction:", cycle_color_correction, current_color_correction_mode}, + {"Back", return_to_root_menu}, + {NULL,} +}; + +static void enter_graphics_menu(void) +{ + current_menu = graphics_menu; + current_selection = 0; +} extern void set_filename(const char *new_filename, bool new_should_free); void run_gui(bool is_running) @@ -213,7 +257,8 @@ void run_gui(bool is_running) SDL_Event event; gui_state = is_running? SHOWING_MENU : SHOWING_DROP_MESSAGE; bool should_render = true; - current_menu = is_running? paused_menu : nonpaused_menu; + current_menu = root_menu = is_running? paused_menu : nonpaused_menu; + current_selection = 0; while (SDL_WaitEvent(&event)) { if (should_render) { should_render = false; @@ -226,9 +271,14 @@ void run_gui(bool is_running) break; case SHOWING_MENU: draw_text_centered(pixels, 16, "SameBoy", gui_palette_native[3], gui_palette_native[0], false); - unsigned i = 0; + unsigned i = 0, y = 40; for (const struct menu_item *item = current_menu; item->string; item++, i++) { - draw_text_centered(pixels, 12 * i + 40, item->string, gui_palette_native[3], gui_palette_native[0], i == current_selection); + draw_text_centered(pixels, y, item->string, gui_palette_native[3], gui_palette_native[0], i == current_selection); + y += 12; + if (item->value_getter) { + draw_text_centered(pixels, y, item->value_getter(), gui_palette_native[3], gui_palette_native[0], false); + y += 12; + } } break; case SHOWING_HELP: @@ -243,7 +293,14 @@ void run_gui(bool is_running) } switch (event.type) { case SDL_QUIT: { - exit(0); + if (!is_running) { + exit(0); + } + else { + pending_command = GB_SDL_QUIT_COMMAND; + return; + } + } case SDL_WINDOWEVENT: { if (event.window.event == SDL_WINDOWEVENT_RESIZED) { @@ -290,6 +347,12 @@ void run_gui(bool is_running) else if (event.key.keysym.sym == SDLK_RETURN) { if (current_menu[current_selection].handler) { current_menu[current_selection].handler(); + if (pending_command) { + if (!is_running && pending_command == GB_SDL_QUIT_COMMAND) { + exit(0); + } + return; + } should_render = true; } else { diff --git a/SDL/gui.h b/SDL/gui.h index 35458b3..9182d10 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -2,6 +2,7 @@ #define gui_h #include +#include extern SDL_Window *window; extern SDL_Renderer *renderer; @@ -24,10 +25,12 @@ enum pending_command { GB_SDL_RESET_COMMAND, GB_SDL_NEW_FILE_COMMAND, GB_SDL_TOGGLE_MODEL_COMMAND, + GB_SDL_QUIT_COMMAND, }; extern enum pending_command pending_command; extern unsigned command_parameter; +extern GB_color_correction_mode_t color_correction_mode; void update_viewport(void); void cycle_scaling(void); diff --git a/SDL/main.c b/SDL/main.c index 2ee4aa1..d9d79c8 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -83,8 +83,8 @@ static void handle_events(GB_gameboy_t *gb) { switch (event.type) { case SDL_QUIT: - GB_save_battery(gb, battery_save_path_ptr); - exit(0); + pending_command = GB_SDL_QUIT_COMMAND; + break; case SDL_DROPFILE: { set_filename(event.drop.file, true); @@ -102,6 +102,7 @@ static void handle_events(GB_gameboy_t *gb) switch (event.key.keysym.sym) { case SDLK_ESCAPE: run_gui(true); + GB_set_color_correction_mode(gb, color_correction_mode); break; case SDLK_c: @@ -215,8 +216,10 @@ static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) static void debugger_interrupt(int ignore) { + if (!GB_is_inited(&gb)) return; /* ^C twice to exit */ if (GB_debugger_is_stopped(&gb)) { + GB_save_battery(&gb, battery_save_path_ptr); exit(0); } GB_debugger_break(&gb); @@ -267,6 +270,10 @@ static bool handle_pending_command(void) case GB_SDL_TOGGLE_MODEL_COMMAND: dmg = !dmg; return true; + + case GB_SDL_QUIT_COMMAND: + GB_save_battery(&gb, battery_save_path_ptr); + exit(0); } return false; } @@ -290,6 +297,7 @@ restart: GB_set_pixels_output(&gb, pixels); GB_set_rgb_encode_callback(&gb, rgb_encode); GB_set_sample_rate(&gb, have_aspec.freq); + GB_set_color_correction_mode(&gb, color_correction_mode); } bool error = false; From 47d5b485d4a3614ac9dde5664ca4b003212ca4ea Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 14 Oct 2017 17:08:52 +0300 Subject: [PATCH 06/21] Better controls and visuals for settings --- SDL/font.c | 10 +++++++ SDL/font.h | 4 ++- SDL/gui.c | 87 +++++++++++++++++++++++++++++++++++++++++++----------- 3 files changed, 83 insertions(+), 18 deletions(-) diff --git a/SDL/font.c b/SDL/font.c index 1ee3999..6354818 100644 --- a/SDL/font.c +++ b/SDL/font.c @@ -1023,6 +1023,16 @@ uint8_t font[] = { X, X, X, X, X, _, X, _, _, X, _, X, _, _, _, _, X, _, + + /* Left Arrow */ + _, _, _, _, X, _, + _, _, _, X, X, _, + _, _, X, X, X, _, + _, X, X, X, X, _, + _, _, X, X, X, _, + _, _, _, X, X, _, + _, _, _, _, X, _, + _, _, _, _, _, _, }; const uint8_t font_max = sizeof(font) / GLYPH_HEIGHT / GLYPH_WIDTH + ' '; diff --git a/SDL/font.h b/SDL/font.h index e4bc9ca..21753a8 100644 --- a/SDL/font.h +++ b/SDL/font.h @@ -6,7 +6,9 @@ extern uint8_t font[]; extern const uint8_t font_max; #define GLYPH_HEIGHT 8 #define GLYPH_WIDTH 6 -#define SELECTION_STRING "\x7f" +#define LEFT_ARROW_STRING "\x86" +#define RIGHT_ARROW_STRING "\x7f" +#define SELECTION_STRING RIGHT_ARROW_STRING #define CTRL_STRING "\x80\x81\x82" #define SHIFT_STRING "\x83" #define CMD_STRING "\x84\x85" diff --git a/SDL/gui.c b/SDL/gui.c index 2abc09d..43ac725 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -55,16 +55,6 @@ static const char *help[] ={ " Break Debugger: " CTRL_STRING "+C" }; -void cycle_scaling(void) -{ - scaling_mode++; - scaling_mode %= GB_SDL_SCALING_MAX; - update_viewport(); - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); -} - void update_viewport(void) { int win_width, win_height; @@ -144,12 +134,27 @@ static void draw_text(uint32_t *buffer, unsigned x, unsigned y, const char *stri draw_unbordered_text(buffer, x, y, string, color); } -static void draw_text_centered(uint32_t *buffer, unsigned y, const char *string, uint32_t color, uint32_t border, bool show_selection) +enum decoration { + DECORATION_NONE, + DECORATION_SELECTION, + DECORATION_ARROWS, +}; + +static void draw_text_centered(uint32_t *buffer, unsigned y, const char *string, uint32_t color, uint32_t border, enum decoration decoration) { unsigned x = 160 / 2 - (unsigned) strlen(string) * GLYPH_WIDTH / 2; draw_text(buffer, x, y, string, color, border); - if (show_selection) { - draw_text(buffer, x - GLYPH_WIDTH, y, SELECTION_STRING, color, border); + switch (decoration) { + case DECORATION_SELECTION: + draw_text(buffer, x - GLYPH_WIDTH, y, SELECTION_STRING, color, border); + break; + case DECORATION_ARROWS: + draw_text(buffer, x - GLYPH_WIDTH, y, LEFT_ARROW_STRING, color, border); + draw_text(buffer, 160 - x, y, RIGHT_ARROW_STRING, color, border); + break; + + case DECORATION_NONE: + break; } } @@ -157,6 +162,7 @@ struct menu_item { const char *string; void (*handler)(void); const char *(*value_getter)(void); + void (*backwards_handler)(void); }; static const struct menu_item *current_menu = NULL; static const struct menu_item *root_menu = NULL; @@ -207,6 +213,32 @@ const char *current_color_correction_mode(void) return (const char *[]){"Disabled", "Correct Color Curves", "Emulate Hardware", "Preserve Brightness"}[color_correction_mode]; } +void cycle_scaling(void) +{ + scaling_mode++; + if (scaling_mode == GB_SDL_SCALING_MAX) { + scaling_mode = 0; + } + update_viewport(); + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); +} + +void cycle_scaling_backwards(void) +{ + if (scaling_mode == 0) { + scaling_mode = GB_SDL_SCALING_MAX - 1; + } + else { + scaling_mode--; + } + update_viewport(); + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); +} + static void cycle_color_correction(void) { if (color_correction_mode == GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS) { @@ -217,6 +249,17 @@ static void cycle_color_correction(void) } } +static void cycle_color_correction_backwards(void) +{ + if (color_correction_mode == GB_COLOR_CORRECTION_DISABLED) { + color_correction_mode = GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS; + } + else { + color_correction_mode--; + } +} + + static void return_to_root_menu(void) { current_menu = root_menu; @@ -224,8 +267,8 @@ static void return_to_root_menu(void) } static const struct menu_item graphics_menu[] = { - {"Scaling Mode:", cycle_scaling, current_scaling_mode}, - {"Color Correction:", cycle_color_correction, current_color_correction_mode}, + {"Scaling Mode:", cycle_scaling, current_scaling_mode, cycle_scaling_backwards}, + {"Color Correction:", cycle_color_correction, current_color_correction_mode, cycle_color_correction_backwards}, {"Back", return_to_root_menu}, {NULL,} }; @@ -273,10 +316,12 @@ void run_gui(bool is_running) draw_text_centered(pixels, 16, "SameBoy", gui_palette_native[3], gui_palette_native[0], false); unsigned i = 0, y = 40; for (const struct menu_item *item = current_menu; item->string; item++, i++) { - draw_text_centered(pixels, y, item->string, gui_palette_native[3], gui_palette_native[0], i == current_selection); + draw_text_centered(pixels, y, item->string, gui_palette_native[3], gui_palette_native[0], + i == current_selection && !item->value_getter ? DECORATION_SELECTION : DECORATION_NONE); y += 12; if (item->value_getter) { - draw_text_centered(pixels, y, item->value_getter(), gui_palette_native[3], gui_palette_native[0], false); + draw_text_centered(pixels, y, item->value_getter(), gui_palette_native[3], gui_palette_native[0], + i == current_selection ? DECORATION_ARROWS : DECORATION_NONE); y += 12; } } @@ -359,6 +404,14 @@ void run_gui(bool is_running) return; } } + else if (event.key.keysym.sym == SDLK_RIGHT && current_menu[current_selection].backwards_handler) { + current_menu[current_selection].handler(); + should_render = true; + } + else if (event.key.keysym.sym == SDLK_LEFT && current_menu[current_selection].backwards_handler) { + current_menu[current_selection].backwards_handler(); + should_render = true; + } } else if(gui_state == SHOWING_HELP) { current_help_page++; From 8e61098fec415ef6bc0f06a950c82797c886d3db Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 14 Oct 2017 20:24:13 +0300 Subject: [PATCH 07/21] Use scan codes instead of key symbols --- SDL/gui.c | 14 +++++++------- SDL/main.c | 40 ++++++++++++++++++++-------------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/SDL/gui.c b/SDL/gui.c index 43ac725..1254d20 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -362,10 +362,10 @@ void run_gui(bool is_running) return; } case SDL_KEYDOWN: - if (event.key.keysym.sym == SDLK_TAB) { + if (event.key.keysym.scancode == SDL_SCANCODE_TAB) { cycle_scaling(); } - else if (event.key.keysym.sym == SDLK_ESCAPE) { + else if (event.key.keysym.scancode == SDL_SCANCODE_ESCAPE) { if (is_running) { return; } @@ -381,15 +381,15 @@ void run_gui(bool is_running) } if (gui_state == SHOWING_MENU) { - if (event.key.keysym.sym == SDLK_DOWN && current_menu[current_selection + 1].string) { + if (event.key.keysym.scancode == SDL_SCANCODE_DOWN && current_menu[current_selection + 1].string) { current_selection++; should_render = true; } - else if (event.key.keysym.sym == SDLK_UP && current_selection) { + else if (event.key.keysym.scancode == SDL_SCANCODE_UP && current_selection) { current_selection--; should_render = true; } - else if (event.key.keysym.sym == SDLK_RETURN) { + else if (event.key.keysym.scancode == SDL_SCANCODE_RETURN) { if (current_menu[current_selection].handler) { current_menu[current_selection].handler(); if (pending_command) { @@ -404,11 +404,11 @@ void run_gui(bool is_running) return; } } - else if (event.key.keysym.sym == SDLK_RIGHT && current_menu[current_selection].backwards_handler) { + else if (event.key.keysym.scancode == SDL_SCANCODE_RIGHT && current_menu[current_selection].backwards_handler) { current_menu[current_selection].handler(); should_render = true; } - else if (event.key.keysym.sym == SDLK_LEFT && current_menu[current_selection].backwards_handler) { + else if (event.key.keysym.scancode == SDL_SCANCODE_LEFT && current_menu[current_selection].backwards_handler) { current_menu[current_selection].backwards_handler(); should_render = true; } diff --git a/SDL/main.c b/SDL/main.c index d9d79c8..a047978 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -99,38 +99,38 @@ static void handle_events(GB_gameboy_t *gb) } case SDL_KEYDOWN: - switch (event.key.keysym.sym) { - case SDLK_ESCAPE: + switch (event.key.keysym.scancode) { + case SDL_SCANCODE_ESCAPE: run_gui(true); GB_set_color_correction_mode(gb, color_correction_mode); break; - case SDLK_c: + case SDL_SCANCODE_C: if (event.type == SDL_KEYDOWN && (event.key.keysym.mod & KMOD_CTRL)) { GB_debugger_break(gb); } break; - case SDLK_r: + case SDL_SCANCODE_R: if (event.key.keysym.mod & MODIFIER) { pending_command = GB_SDL_RESET_COMMAND; } break; - case SDLK_t: + case SDL_SCANCODE_T: if (event.key.keysym.mod & MODIFIER) { pending_command = GB_SDL_TOGGLE_MODEL_COMMAND; } break; - case SDLK_p: + case SDL_SCANCODE_P: if (event.key.keysym.mod & MODIFIER) { paused = !paused; } break; - case SDLK_m: + case SDL_SCANCODE_M: if (event.key.keysym.mod & MODIFIER) { #ifdef __APPLE__ // Can't override CMD+M (Minimize) in SDL @@ -142,15 +142,15 @@ static void handle_events(GB_gameboy_t *gb) } break; - case SDLK_TAB: + case SDL_SCANCODE_TAB: cycle_scaling(); break; default: /* Save states */ - if (event.key.keysym.sym >= SDLK_0 && event.key.keysym.sym <= SDLK_9) { + if (event.key.keysym.scancode >= SDL_SCANCODE_0 && event.key.keysym.scancode <= SDL_SCANCODE_9) { if (event.key.keysym.mod & MODIFIER) { - command_parameter = event.key.keysym.sym - SDLK_0; + command_parameter = event.key.keysym.scancode - SDL_SCANCODE_0; if (event.key.keysym.mod & KMOD_SHIFT) { pending_command = GB_SDL_LOAD_STATE_COMMAND; @@ -163,32 +163,32 @@ static void handle_events(GB_gameboy_t *gb) break; } case SDL_KEYUP: // Fallthrough - switch (event.key.keysym.sym) { - case SDLK_RIGHT: + switch (event.key.keysym.scancode) { + case SDL_SCANCODE_RIGHT: GB_set_key_state(gb, GB_KEY_RIGHT, event.type == SDL_KEYDOWN); break; - case SDLK_LEFT: + case SDL_SCANCODE_LEFT: GB_set_key_state(gb, GB_KEY_LEFT, event.type == SDL_KEYDOWN); break; - case SDLK_UP: + case SDL_SCANCODE_UP: GB_set_key_state(gb, GB_KEY_UP, event.type == SDL_KEYDOWN); break; - case SDLK_DOWN: + case SDL_SCANCODE_DOWN: GB_set_key_state(gb, GB_KEY_DOWN, event.type == SDL_KEYDOWN); break; - case SDLK_x: + case SDL_SCANCODE_X: GB_set_key_state(gb, GB_KEY_A, event.type == SDL_KEYDOWN); break; - case SDLK_z: + case SDL_SCANCODE_Z: GB_set_key_state(gb, GB_KEY_B, event.type == SDL_KEYDOWN); break; - case SDLK_BACKSPACE: + case SDL_SCANCODE_BACKSPACE: GB_set_key_state(gb, GB_KEY_SELECT, event.type == SDL_KEYDOWN); break; - case SDLK_RETURN: + case SDL_SCANCODE_RETURN: GB_set_key_state(gb, GB_KEY_START, event.type == SDL_KEYDOWN); break; - case SDLK_SPACE: + case SDL_SCANCODE_SPACE: GB_set_turbo_mode(gb, event.type == SDL_KEYDOWN, false); break; } From 19c382c9e079dcd748bf102bc142fa78ca8cbf35 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 3 Dec 2017 21:07:34 +0200 Subject: [PATCH 08/21] Fixed ei_sequence test --- Core/z80_cpu.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 072b1a7..16dbd08 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -1064,7 +1064,7 @@ static void ld_a_da16(GB_gameboy_t *gb, uint8_t opcode) static void di(GB_gameboy_t *gb, uint8_t opcode) { /* DI is NOT delayed, not even on a CGB. Mooneye's di_timing-GS test fails on a CGB - for different reasons.*/ + for different reasons. */ GB_advance_cycles(gb, 4); gb->ime = false; } @@ -1073,8 +1073,9 @@ static void ei(GB_gameboy_t *gb, uint8_t opcode) { /* ei is actually "disable interrupts for one instruction, then enable them". */ GB_advance_cycles(gb, 4); - gb->ime = false; - gb->ime_toggle = true; + if (!gb->ime && !gb->ime_toggle) { + gb->ime_toggle = true; + } } static void ld_hl_sp_r8(GB_gameboy_t *gb, uint8_t opcode) From f0e772ca977b148a8a695ffc79ad0e5a3c6b4aac Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 22 Dec 2017 21:58:31 +0200 Subject: [PATCH 09/21] Fixed: Loading states in DMG mode results in a black screen --- Core/display.c | 2 +- Core/save_state.c | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index 4466e34..f9533ba 100755 --- a/Core/display.c +++ b/Core/display.c @@ -279,7 +279,7 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color) void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index) { - if (!gb->rgb_encode_callback) return; + if (!gb->rgb_encode_callback || !gb->is_cgb) return; uint8_t *palette_data = background_palette? gb->background_palettes_data : gb->sprite_palettes_data; uint16_t color = palette_data[index & ~1] | (palette_data[index | 1] << 8); diff --git a/Core/save_state.c b/Core/save_state.c index 24604c1..933b417 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -303,6 +303,11 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le gb->rumble_callback(gb, gb->rumble_state); } + for (unsigned i = 0; i < 32; i++) { + GB_palette_changed(gb, false, i * 2); + GB_palette_changed(gb, true, i * 2); + } + return 0; } From f48194f48072a8300de94afc99ff528ed0f3531c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 22 Dec 2017 22:25:53 +0200 Subject: [PATCH 10/21] Added configurable controls --- SDL/font.c | 8 +- SDL/gui.c | 237 +++++++++++++++++++++++++++++++++-------------------- SDL/gui.h | 10 ++- SDL/main.c | 45 +++------- 4 files changed, 168 insertions(+), 132 deletions(-) diff --git a/SDL/font.c b/SDL/font.c index 6354818..93f3fa9 100644 --- a/SDL/font.c +++ b/SDL/font.c @@ -967,13 +967,13 @@ uint8_t font[] = { /* CTRL symbol */ - _, X, X, X, _, X, + _, X, X, _, _, X, + X, _, _, X, _, _, X, _, _, _, _, _, X, _, _, _, _, _, X, _, _, _, _, _, - X, _, _, _, _, _, - X, _, _, _, _, _, - _, X, X, X, _, _, + X, _, _, X, _, _, + _, X, X, _, _, _, _, _, _, _, _, _, X, X, _, X, X, X, diff --git a/SDL/gui.c b/SDL/gui.c index 1254d20..bb5e206 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -11,10 +11,8 @@ SDL_Window *window = NULL; SDL_Renderer *renderer = NULL; SDL_Texture *texture = NULL; SDL_PixelFormat *pixel_format = NULL; -enum scaling_mode scaling_mode = GB_SDL_SCALING_INTEGER_FACTOR; enum pending_command pending_command; unsigned command_parameter; -GB_color_correction_mode_t color_correction_mode = GB_COLOR_CORRECTION_EMULATE_HARDWARE; #ifdef __APPLE__ #define MODIFIER_NAME " " CMD_STRING @@ -22,36 +20,39 @@ GB_color_correction_mode_t color_correction_mode = GB_COLOR_CORRECTION_EMULATE_H #define MODIFIER_NAME CTRL_STRING #endif +configuration_t configuration = +{ + { SDL_SCANCODE_RIGHT, + SDL_SCANCODE_LEFT, + SDL_SCANCODE_UP, + SDL_SCANCODE_DOWN, + SDL_SCANCODE_X, + SDL_SCANCODE_Z, + SDL_SCANCODE_BACKSPACE, + SDL_SCANCODE_RETURN, + SDL_SCANCODE_SPACE + }, + GB_COLOR_CORRECTION_EMULATE_HARDWARE, + GB_SDL_SCALING_INTEGER_FACTOR, +}; + static const char *help[] ={ "Drop a GB or GBC ROM\n" "file to play.\n" "\n" - -"Controls:\n" -" D-Pad: Arrow Keys\n" -" A: X\n" -" B: Z\n" -" Start: Enter\n" -" Select: Backspace\n" -"\n" -" Turbo: Space\n" -" Menu: Escape\n", -"Keyboard Shortcuts: \n" +"Keyboard Shortcuts:\n" +" Open Menu: Escape\n" " Reset: " MODIFIER_NAME "+R\n" " Pause: " MODIFIER_NAME "+P\n" " Toggle DMG/CGB: " MODIFIER_NAME "+T\n" -"\n" " Save state: " MODIFIER_NAME "+(0-9)\n" " Load state: " MODIFIER_NAME "+" SHIFT_STRING "+(0-9)\n" -"\n" #ifdef __APPLE__ " Mute/Unmute: " MODIFIER_NAME "+" SHIFT_STRING "+M\n" #else " Mute/Unmute: " MODIFIER_NAME "+M\n" #endif -" Cycle scaling modes: Tab" -"\n" " Break Debugger: " CTRL_STRING "+C" }; @@ -62,12 +63,12 @@ void update_viewport(void) double x_factor = win_width / 160.0; double y_factor = win_height / 144.0; - if (scaling_mode == GB_SDL_SCALING_INTEGER_FACTOR) { + if (configuration.scaling_mode == GB_SDL_SCALING_INTEGER_FACTOR) { x_factor = (int)(x_factor); y_factor = (int)(y_factor); } - if (scaling_mode != GB_SDL_SCALING_ENTIRE_WINDOW) { + if (configuration.scaling_mode != GB_SDL_SCALING_ENTIRE_WINDOW) { if (x_factor > y_factor) { x_factor = y_factor; } @@ -160,9 +161,9 @@ static void draw_text_centered(uint32_t *buffer, unsigned y, const char *string, struct menu_item { const char *string; - void (*handler)(void); - const char *(*value_getter)(void); - void (*backwards_handler)(void); + void (*handler)(unsigned); + const char *(*value_getter)(unsigned); + void (*backwards_handler)(unsigned); }; static const struct menu_item *current_menu = NULL; static const struct menu_item *root_menu = NULL; @@ -172,25 +173,28 @@ static enum { SHOWING_DROP_MESSAGE, SHOWING_MENU, SHOWING_HELP, + WAITING_FOR_KEY, } gui_state; -static void item_exit(void) +static void item_exit(unsigned index) { pending_command = GB_SDL_QUIT_COMMAND; } static unsigned current_help_page = 0; -static void item_help(void) +static void item_help(unsigned index) { current_help_page = 0; gui_state = SHOWING_HELP; } -static void enter_graphics_menu(void); +static void enter_graphics_menu(unsigned index); +static void enter_controls_menu(unsigned index); static const struct menu_item paused_menu[] = { {"Resume", NULL}, {"Graphic Options", enter_graphics_menu}, + {"Controls", enter_controls_menu}, {"Help", item_help}, {"Exit", item_exit}, {NULL,} @@ -198,26 +202,29 @@ static const struct menu_item paused_menu[] = { static const struct menu_item nonpaused_menu[] = { {"Graphic Options", enter_graphics_menu}, + {"Controls", enter_controls_menu}, {"Help", item_help}, {"Exit", item_exit}, {NULL,} }; -const char *current_scaling_mode(void) +const char *current_scaling_mode(unsigned index) { - return (const char *[]){"Fill Entire Window", "Retain Aspect Ratio", "Retain Integer Factor"}[scaling_mode]; + return (const char *[]){"Fill Entire Window", "Retain Aspect Ratio", "Retain Integer Factor"} + [configuration.scaling_mode]; } -const char *current_color_correction_mode(void) +const char *current_color_correction_mode(unsigned index) { - return (const char *[]){"Disabled", "Correct Color Curves", "Emulate Hardware", "Preserve Brightness"}[color_correction_mode]; + return (const char *[]){"Disabled", "Correct Color Curves", "Emulate Hardware", "Preserve Brightness"} + [configuration.color_correction_mode]; } -void cycle_scaling(void) +void cycle_scaling(unsigned index) { - scaling_mode++; - if (scaling_mode == GB_SDL_SCALING_MAX) { - scaling_mode = 0; + configuration.scaling_mode++; + if (configuration.scaling_mode == GB_SDL_SCALING_MAX) { + configuration.scaling_mode = 0; } update_viewport(); SDL_RenderClear(renderer); @@ -225,13 +232,13 @@ void cycle_scaling(void) SDL_RenderPresent(renderer); } -void cycle_scaling_backwards(void) +void cycle_scaling_backwards(unsigned index) { - if (scaling_mode == 0) { - scaling_mode = GB_SDL_SCALING_MAX - 1; + if (configuration.scaling_mode == 0) { + configuration.scaling_mode = GB_SDL_SCALING_MAX - 1; } else { - scaling_mode--; + configuration.scaling_mode--; } update_viewport(); SDL_RenderClear(renderer); @@ -239,28 +246,28 @@ void cycle_scaling_backwards(void) SDL_RenderPresent(renderer); } -static void cycle_color_correction(void) +static void cycle_color_correction(unsigned index) { - if (color_correction_mode == GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS) { - color_correction_mode = GB_COLOR_CORRECTION_DISABLED; + if (configuration.color_correction_mode == GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS) { + configuration.color_correction_mode = GB_COLOR_CORRECTION_DISABLED; } else { - color_correction_mode++; + configuration.color_correction_mode++; } } -static void cycle_color_correction_backwards(void) +static void cycle_color_correction_backwards(unsigned index) { - if (color_correction_mode == GB_COLOR_CORRECTION_DISABLED) { - color_correction_mode = GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS; + if (configuration.color_correction_mode == GB_COLOR_CORRECTION_DISABLED) { + configuration.color_correction_mode = GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS; } else { - color_correction_mode--; + configuration.color_correction_mode--; } } -static void return_to_root_menu(void) +static void return_to_root_menu(unsigned index) { current_menu = root_menu; current_selection = 0; @@ -273,12 +280,42 @@ static const struct menu_item graphics_menu[] = { {NULL,} }; -static void enter_graphics_menu(void) +static void enter_graphics_menu(unsigned index) { current_menu = graphics_menu; current_selection = 0; } +static const char *key_name(unsigned index) +{ + return SDL_GetScancodeName(configuration.keys[index]); +} + +static void modify_key(unsigned index) +{ + gui_state = WAITING_FOR_KEY; +} + +static const struct menu_item controls_menu[] = { + {"Right:", modify_key, key_name,}, + {"Left:", modify_key, key_name,}, + {"Up:", modify_key, key_name,}, + {"Down:", modify_key, key_name,}, + {"A:", modify_key, key_name,}, + {"B:", modify_key, key_name,}, + {"Select:", modify_key, key_name,}, + {"Start:", modify_key, key_name,}, + {"Turbo:", modify_key, key_name,}, + {"Back", return_to_root_menu}, + {NULL,} +}; + +static void enter_controls_menu(unsigned index) +{ + current_menu = controls_menu; + current_selection = 0; +} + extern void set_filename(const char *new_filename, bool new_should_free); void run_gui(bool is_running) { @@ -297,45 +334,12 @@ void run_gui(bool is_running) } uint32_t pixels[160 * 144]; - SDL_Event event; + SDL_Event event = {0,}; gui_state = is_running? SHOWING_MENU : SHOWING_DROP_MESSAGE; bool should_render = true; current_menu = root_menu = is_running? paused_menu : nonpaused_menu; current_selection = 0; - while (SDL_WaitEvent(&event)) { - if (should_render) { - should_render = false; - memcpy(pixels, converted_background->pixels, sizeof(pixels)); - - switch (gui_state) { - case SHOWING_DROP_MESSAGE: - draw_text_centered(pixels, 116, "Drop a GB or GBC", gui_palette_native[3], gui_palette_native[0], false); - draw_text_centered(pixels, 128, "file to play", gui_palette_native[3], gui_palette_native[0], false); - break; - case SHOWING_MENU: - draw_text_centered(pixels, 16, "SameBoy", gui_palette_native[3], gui_palette_native[0], false); - unsigned i = 0, y = 40; - for (const struct menu_item *item = current_menu; item->string; item++, i++) { - draw_text_centered(pixels, y, item->string, gui_palette_native[3], gui_palette_native[0], - i == current_selection && !item->value_getter ? DECORATION_SELECTION : DECORATION_NONE); - y += 12; - if (item->value_getter) { - draw_text_centered(pixels, y, item->value_getter(), gui_palette_native[3], gui_palette_native[0], - i == current_selection ? DECORATION_ARROWS : DECORATION_NONE); - y += 12; - } - } - break; - case SHOWING_HELP: - draw_text(pixels, 2, 2, help[current_help_page], gui_palette_native[3], gui_palette_native[0]); - break; - } - - SDL_UpdateTexture(texture, NULL, pixels, 160 * sizeof (uint32_t)); - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); - } + do { switch (event.type) { case SDL_QUIT: { if (!is_running) { @@ -362,10 +366,7 @@ void run_gui(bool is_running) return; } case SDL_KEYDOWN: - if (event.key.keysym.scancode == SDL_SCANCODE_TAB) { - cycle_scaling(); - } - else if (event.key.keysym.scancode == SDL_SCANCODE_ESCAPE) { + if (event.key.keysym.scancode == SDL_SCANCODE_ESCAPE) { if (is_running) { return; } @@ -376,6 +377,8 @@ void run_gui(bool is_running) else if (gui_state == SHOWING_MENU) { gui_state = SHOWING_DROP_MESSAGE; } + current_selection = 0; + current_menu = root_menu; should_render = true; } } @@ -391,7 +394,7 @@ void run_gui(bool is_running) } else if (event.key.keysym.scancode == SDL_SCANCODE_RETURN) { if (current_menu[current_selection].handler) { - current_menu[current_selection].handler(); + current_menu[current_selection].handler(current_selection); if (pending_command) { if (!is_running && pending_command == GB_SDL_QUIT_COMMAND) { exit(0); @@ -405,22 +408,74 @@ void run_gui(bool is_running) } } else if (event.key.keysym.scancode == SDL_SCANCODE_RIGHT && current_menu[current_selection].backwards_handler) { - current_menu[current_selection].handler(); + current_menu[current_selection].handler(current_selection); should_render = true; } else if (event.key.keysym.scancode == SDL_SCANCODE_LEFT && current_menu[current_selection].backwards_handler) { - current_menu[current_selection].backwards_handler(); + current_menu[current_selection].backwards_handler(current_selection); should_render = true; } } - else if(gui_state == SHOWING_HELP) { + else if (gui_state == SHOWING_HELP) { current_help_page++; if (current_help_page == sizeof(help) / sizeof(help[0])) { gui_state = SHOWING_MENU; } should_render = true; } + else if (gui_state == WAITING_FOR_KEY) { + configuration.keys[current_selection] = event.key.keysym.scancode; + gui_state = SHOWING_MENU; + should_render = true; + } break; } - } + + if (should_render) { + should_render = false; + memcpy(pixels, converted_background->pixels, sizeof(pixels)); + + switch (gui_state) { + case SHOWING_DROP_MESSAGE: + draw_text_centered(pixels, 116, "Drop a GB or GBC", gui_palette_native[3], gui_palette_native[0], false); + draw_text_centered(pixels, 128, "file to play", gui_palette_native[3], gui_palette_native[0], false); + break; + case SHOWING_MENU: + draw_text_centered(pixels, 8, "SameBoy", gui_palette_native[3], gui_palette_native[0], false); + unsigned i = 0, y = 24; + for (const struct menu_item *item = current_menu; item->string; item++, i++) { + if (item->value_getter && !item->backwards_handler) { + char line[25]; + snprintf(line, sizeof(line), "%s%*s", item->string, 24 - (int)strlen(item->string), item->value_getter(i)); + draw_text_centered(pixels, y, line, gui_palette_native[3], gui_palette_native[0], + i == current_selection ? DECORATION_SELECTION : DECORATION_NONE); + y += 12; + + } + else { + draw_text_centered(pixels, y, item->string, gui_palette_native[3], gui_palette_native[0], + i == current_selection && !item->value_getter ? DECORATION_SELECTION : DECORATION_NONE); + y += 12; + if (item->value_getter) { + draw_text_centered(pixels, y, item->value_getter(i), gui_palette_native[3], gui_palette_native[0], + i == current_selection ? DECORATION_ARROWS : DECORATION_NONE); + y += 12; + } + } + } + break; + case SHOWING_HELP: + draw_text(pixels, 2, 2, help[current_help_page], gui_palette_native[3], gui_palette_native[0]); + break; + case WAITING_FOR_KEY: + draw_text_centered(pixels, 68, "Press a Key", gui_palette_native[3], gui_palette_native[0], DECORATION_NONE); + break; + } + + SDL_UpdateTexture(texture, NULL, pixels, 160 * sizeof (uint32_t)); + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); + } + } while (SDL_WaitEvent(&event)); } diff --git a/SDL/gui.h b/SDL/gui.h index 9182d10..3f82420 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -16,7 +16,6 @@ enum scaling_mode { GB_SDL_SCALING_MAX, }; -extern enum scaling_mode scaling_mode; enum pending_command { GB_SDL_NO_COMMAND, @@ -30,10 +29,15 @@ enum pending_command { extern enum pending_command pending_command; extern unsigned command_parameter; -extern GB_color_correction_mode_t color_correction_mode; +typedef struct { + SDL_Scancode keys[9]; + GB_color_correction_mode_t color_correction_mode; + enum scaling_mode scaling_mode; +} configuration_t; + +extern configuration_t configuration; void update_viewport(void); -void cycle_scaling(void); void run_gui(bool is_running); #endif diff --git a/SDL/main.c b/SDL/main.c index a047978..8bb23ba 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -102,7 +102,7 @@ static void handle_events(GB_gameboy_t *gb) switch (event.key.keysym.scancode) { case SDL_SCANCODE_ESCAPE: run_gui(true); - GB_set_color_correction_mode(gb, color_correction_mode); + GB_set_color_correction_mode(gb, configuration.color_correction_mode); break; case SDL_SCANCODE_C: @@ -142,10 +142,6 @@ static void handle_events(GB_gameboy_t *gb) } break; - case SDL_SCANCODE_TAB: - cycle_scaling(); - break; - default: /* Save states */ if (event.key.keysym.scancode >= SDL_SCANCODE_0 && event.key.keysym.scancode <= SDL_SCANCODE_9) { @@ -163,34 +159,15 @@ static void handle_events(GB_gameboy_t *gb) break; } case SDL_KEYUP: // Fallthrough - switch (event.key.keysym.scancode) { - case SDL_SCANCODE_RIGHT: - GB_set_key_state(gb, GB_KEY_RIGHT, event.type == SDL_KEYDOWN); - break; - case SDL_SCANCODE_LEFT: - GB_set_key_state(gb, GB_KEY_LEFT, event.type == SDL_KEYDOWN); - break; - case SDL_SCANCODE_UP: - GB_set_key_state(gb, GB_KEY_UP, event.type == SDL_KEYDOWN); - break; - case SDL_SCANCODE_DOWN: - GB_set_key_state(gb, GB_KEY_DOWN, event.type == SDL_KEYDOWN); - break; - case SDL_SCANCODE_X: - GB_set_key_state(gb, GB_KEY_A, event.type == SDL_KEYDOWN); - break; - case SDL_SCANCODE_Z: - GB_set_key_state(gb, GB_KEY_B, event.type == SDL_KEYDOWN); - break; - case SDL_SCANCODE_BACKSPACE: - GB_set_key_state(gb, GB_KEY_SELECT, event.type == SDL_KEYDOWN); - break; - case SDL_SCANCODE_RETURN: - GB_set_key_state(gb, GB_KEY_START, event.type == SDL_KEYDOWN); - break; - case SDL_SCANCODE_SPACE: - GB_set_turbo_mode(gb, event.type == SDL_KEYDOWN, false); - break; + if (event.key.keysym.scancode == configuration.keys[8]) { + GB_set_turbo_mode(gb, event.type == SDL_KEYDOWN, false); + } + else { + for (unsigned i = 0; i < GB_KEY_MAX; i++) { + if (event.key.keysym.scancode == configuration.keys[i]) { + GB_set_key_state(gb, i, event.type == SDL_KEYDOWN); + } + } } break; default: @@ -297,7 +274,7 @@ restart: GB_set_pixels_output(&gb, pixels); GB_set_rgb_encode_callback(&gb, rgb_encode); GB_set_sample_rate(&gb, have_aspec.freq); - GB_set_color_correction_mode(&gb, color_correction_mode); + GB_set_color_correction_mode(&gb, configuration.color_correction_mode); } bool error = false; From c03ccba8dbecfa97ad24e70ef5372429a57736bf Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 23 Dec 2017 00:39:04 +0200 Subject: [PATCH 11/21] Basic Joypad support --- SDL/gui.c | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++- SDL/gui.h | 7 +- SDL/main.c | 34 +++++++++ 3 files changed, 238 insertions(+), 4 deletions(-) diff --git a/SDL/gui.c b/SDL/gui.c index bb5e206..1a12e28 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -174,8 +174,12 @@ static enum { SHOWING_MENU, SHOWING_HELP, WAITING_FOR_KEY, + WAITING_FOR_JBUTTON, } gui_state; +unsigned auto_detect_progress = 0; +unsigned auto_detect_inputs[3]; + static void item_exit(unsigned index) { pending_command = GB_SDL_QUIT_COMMAND; @@ -190,11 +194,13 @@ static void item_help(unsigned index) static void enter_graphics_menu(unsigned index); static void enter_controls_menu(unsigned index); +static void enter_joypad_menu(unsigned index); static const struct menu_item paused_menu[] = { {"Resume", NULL}, {"Graphic Options", enter_graphics_menu}, - {"Controls", enter_controls_menu}, + {"Keyboard", enter_controls_menu}, + {"Joypad", enter_joypad_menu}, {"Help", item_help}, {"Exit", item_exit}, {NULL,} @@ -202,7 +208,8 @@ static const struct menu_item paused_menu[] = { static const struct menu_item nonpaused_menu[] = { {"Graphic Options", enter_graphics_menu}, - {"Controls", enter_controls_menu}, + {"Keyboard", enter_controls_menu}, + {"Joypad", enter_joypad_menu}, {"Help", item_help}, {"Exit", item_exit}, {NULL,} @@ -316,10 +323,102 @@ static void enter_controls_menu(unsigned index) current_selection = 0; } +static unsigned joypad_index = 0; +SDL_Joystick *joystick = NULL; +const char *current_joypad_name(unsigned index) +{ + static char name[23] = {0,}; + const char *orig_name = joystick? SDL_JoystickName(joystick) : NULL; + if (!orig_name) return "Not Found"; + unsigned i = 0; + + // SDL returns a name with repeated and trailing spaces + while (*orig_name && i < sizeof(name) - 2) { + if (orig_name[0] != ' ' || orig_name[1] != ' ') { + name[i++] = *orig_name; + } + orig_name++; + } + if (i && name[i - 1] == ' ') { + i--; + } + name[i] = 0; + + return name; +} + +static void cycle_joypads(unsigned index) +{ + joypad_index++; + if (joypad_index >= SDL_NumJoysticks()) { + joypad_index = 0; + } + if (joystick) { + SDL_JoystickClose(joystick); + } + joystick = SDL_JoystickOpen(joypad_index); +} + +unsigned fix_joypad_button(unsigned button) +{ + if (configuration.div_joystick) { + button >>= 1; + } + + if (button < 4) { + if (configuration.swap_joysticks_bits_1_and_2) { + button = (int[]){0, 2, 1, 3}[button]; + } + + if (configuration.flip_joystick_bit_1) { + button ^= 1; + } + } + + return button; +} + +static void cycle_joypads_backwards(unsigned index) +{ + joypad_index++; + if (joypad_index >= SDL_NumJoysticks()) { + joypad_index = SDL_NumJoysticks() - 1; + } + if (joystick) { + SDL_JoystickClose(joystick); + } + joystick = SDL_JoystickOpen(joypad_index); +} + +static void detect_joypad_layout(unsigned index) +{ + gui_state = WAITING_FOR_JBUTTON; + auto_detect_progress = 0; +} + +static const struct menu_item joypad_menu[] = { + {"Joypad:", cycle_joypads, current_joypad_name, cycle_joypads_backwards}, + {"Detect layout", detect_joypad_layout}, + {NULL,} +}; + +static void enter_joypad_menu(unsigned index) +{ + current_menu = joypad_menu; + current_selection = 0; +} + extern void set_filename(const char *new_filename, bool new_should_free); void run_gui(bool is_running) { - /* Draw the "Drop file" screen */ + if (joystick && !SDL_NumJoysticks()) { + SDL_JoystickClose(joystick); + joystick = NULL; + } + else if (!joystick && SDL_NumJoysticks()) { + joystick = SDL_JoystickOpen(0); + } + /* Draw the background screen */ static SDL_Surface *converted_background = NULL; if (!converted_background) { SDL_Surface *background = SDL_LoadBMP(executable_relative_path("background.bmp")); @@ -340,6 +439,63 @@ void run_gui(bool is_running) current_menu = root_menu = is_running? paused_menu : nonpaused_menu; current_selection = 0; do { + /* Convert Joypad events (We only generate down events) */ + if (gui_state != WAITING_FOR_KEY && gui_state != WAITING_FOR_JBUTTON) { + switch (event.type) { + case SDL_JOYBUTTONDOWN: + event.type = SDL_KEYDOWN; + event.jbutton.button = fix_joypad_button(event.jbutton.button); + if (event.jbutton.button < 4) { + event.key.keysym.scancode = (event.jbutton.button & 1) ? SDL_SCANCODE_RETURN : SDL_SCANCODE_ESCAPE; + } + else if (event.jbutton.button == 8 || event.jbutton.button == 9) { + event.key.keysym.scancode = SDL_SCANCODE_ESCAPE; + } + break; + + case SDL_JOYAXISMOTION: { + static bool axis_active[2] = {false, false}; + if ((event.jaxis.axis >> configuration.div_joystick) & 1) { + if (event.jaxis.value > 0x4000) { + if (!axis_active[1]) { + event.type = SDL_KEYDOWN; + event.key.keysym.scancode = SDL_SCANCODE_DOWN; + } + axis_active[1] = true; + } + else if (event.jaxis.value < -0x4000) { + if (!axis_active[0]) { + event.type = SDL_KEYDOWN; + event.key.keysym.scancode = SDL_SCANCODE_UP; + } + axis_active[1] = true; + } + else { + axis_active[1] = false; + } + } + else { + if (event.jaxis.value > 0x4000) { + if (!axis_active[0]) { + event.type = SDL_KEYDOWN; + event.key.keysym.scancode = SDL_SCANCODE_RIGHT; + } + axis_active[0] = true; + } + else if (event.jaxis.value < -0x4000) { + if (!axis_active[0]) { + event.type = SDL_KEYDOWN; + event.key.keysym.scancode = SDL_SCANCODE_LEFT; + } + axis_active[0] = true; + } + else { + axis_active[0] = false; + } + } + } + } + } switch (event.type) { case SDL_QUIT: { if (!is_running) { @@ -365,6 +521,36 @@ void run_gui(bool is_running) pending_command = GB_SDL_NEW_FILE_COMMAND; return; } + case SDL_JOYBUTTONDOWN: + { + if (gui_state == WAITING_FOR_JBUTTON) { + should_render = true; + auto_detect_inputs[auto_detect_progress++] = event.jbutton.button; + if (auto_detect_progress == 3) { + gui_state = SHOWING_MENU; + + configuration.div_joystick = + ((auto_detect_inputs[0] | auto_detect_inputs[1] | auto_detect_inputs[2]) & 1) == 0 && + auto_detect_inputs[0] > 9; + + if (configuration.div_joystick) { + auto_detect_inputs[0] >>= 1; + auto_detect_inputs[1] >>= 1; + auto_detect_inputs[2] >>= 1; + } + + configuration.swap_joysticks_bits_1_and_2 = + (auto_detect_inputs[1] & 1) == (auto_detect_inputs[2] & 1); + + if (configuration.swap_joysticks_bits_1_and_2) { + auto_detect_inputs[1] = (int[]){0, 2, 1, 3}[auto_detect_inputs[1]]; + auto_detect_inputs[2] = (int[]){0, 2, 1, 3}[auto_detect_inputs[2]]; + } + + configuration.flip_joystick_bit_1 = auto_detect_inputs[2] & 1; + } + } + } case SDL_KEYDOWN: if (event.key.keysym.scancode == SDL_SCANCODE_ESCAPE) { if (is_running) { @@ -470,6 +656,15 @@ void run_gui(bool is_running) case WAITING_FOR_KEY: draw_text_centered(pixels, 68, "Press a Key", gui_palette_native[3], gui_palette_native[0], DECORATION_NONE); break; + case WAITING_FOR_JBUTTON: + draw_text_centered(pixels, 68, (const char *[]) + { + "Press button for Start", + "Press button for A", + "Press button for B", + } [auto_detect_progress], + gui_palette_native[3], gui_palette_native[0], DECORATION_NONE); + break; } SDL_UpdateTexture(texture, NULL, pixels, 160 * sizeof (uint32_t)); diff --git a/SDL/gui.h b/SDL/gui.h index 3f82420..6e08c6b 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -33,11 +33,16 @@ typedef struct { SDL_Scancode keys[9]; GB_color_correction_mode_t color_correction_mode; enum scaling_mode scaling_mode; + + bool div_joystick; + bool flip_joystick_bit_1; + bool swap_joysticks_bits_1_and_2; } configuration_t; extern configuration_t configuration; void update_viewport(void); - void run_gui(bool is_running); +unsigned fix_joypad_button(unsigned button); + #endif diff --git a/SDL/main.c b/SDL/main.c index 8bb23ba..8320b67 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -96,8 +96,42 @@ static void handle_events(GB_gameboy_t *gb) if (event.window.event == SDL_WINDOWEVENT_RESIZED) { update_viewport(); } + break; } + case SDL_JOYBUTTONUP: + case SDL_JOYBUTTONDOWN: + event.jbutton.button = fix_joypad_button(event.jbutton.button); + if (event.jbutton.button < 4) { + GB_set_key_state(gb, (event.jbutton.button & 1) ? GB_KEY_A : GB_KEY_B, + event.type == SDL_JOYBUTTONDOWN); + } + else if (event.jbutton.button == 8) { + GB_set_key_state(gb, GB_KEY_SELECT, event.type == SDL_JOYBUTTONDOWN); + } + else if (event.jbutton.button == 9) { + GB_set_key_state(gb, GB_KEY_START, event.type == SDL_JOYBUTTONDOWN); + } + else if (event.jbutton.button & 1) { + GB_set_turbo_mode(gb, event.type == SDL_JOYBUTTONDOWN, false); + } + else { + run_gui(true); + GB_set_color_correction_mode(gb, configuration.color_correction_mode); + } + break; + + case SDL_JOYAXISMOTION: + if ((event.jaxis.axis >> configuration.div_joystick) & 1) { + GB_set_key_state(gb, GB_KEY_DOWN, event.jaxis.value > 0x4000); + GB_set_key_state(gb, GB_KEY_UP, event.jaxis.value < -0x4000); + } + else { + GB_set_key_state(gb, GB_KEY_RIGHT, event.jaxis.value > 0x4000); + GB_set_key_state(gb, GB_KEY_LEFT, event.jaxis.value < -0x4000); + } + break; + case SDL_KEYDOWN: switch (event.key.keysym.scancode) { case SDL_SCANCODE_ESCAPE: From d262dde71aee83fa320fcfcb3f629bae92c17d78 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 23 Dec 2017 17:29:42 +0200 Subject: [PATCH 12/21] Scaling filters in SDL --- Makefile | 13 ++- SDL/gui.c | 132 ++++++++++++++++++++++++---- SDL/gui.h | 5 ++ SDL/main.c | 39 ++++++--- SDL/shader.c | 184 +++++++++++++++++++++++++++++++++++++++ SDL/shader.h | 23 +++++ Shaders/MasterShader.fsh | 6 ++ 7 files changed, 370 insertions(+), 32 deletions(-) create mode 100644 SDL/shader.c create mode 100644 SDL/shader.h diff --git a/Makefile b/Makefile index fcde67e..dde767d 100755 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ endif # Set compilation and linkage flags based on target, platform and configuration CFLAGS += -Werror -Wall -std=gnu11 -ICore -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES -SDL_LDFLAGS := -lSDL2 +SDL_LDFLAGS := -lSDL2 -lGL ifeq ($(PLATFORM),windows32) CFLAGS += -IWindows LDFLAGS += -lmsvcrt -lSDL2main -Wl,/MANIFESTFILE:NUL @@ -56,7 +56,7 @@ SYSROOT := $(shell xcodebuild -sdk macosx -version Path 2> /dev/null) CFLAGS += -F/Library/Frameworks OCFLAGS += -x objective-c -fobjc-arc -Wno-deprecated-declarations -isysroot $(SYSROOT) -mmacosx-version-min=10.9 LDFLAGS += -framework AppKit -framework PreferencePanes -framework Carbon -framework QuartzCore -SDL_LDFLAGS := -F/Library/Frameworks -framework SDL2 +SDL_LDFLAGS := -F/Library/Frameworks -framework SDL2 -framework OpenGL endif CFLAGS += -Wno-deprecated-declarations ifeq ($(PLATFORM),windows32) @@ -88,7 +88,7 @@ endif cocoa: $(BIN)/SameBoy.app quicklook: $(BIN)/SameBoy.qlgenerator -sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp +sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp $(BIN)/SDL/Shaders bootroms: $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/dmg_boot.bin tester: $(TESTER_TARGET) $(BIN)/tester/dmg_boot.bin $(BIN)/tester/cgb_boot.bin all: cocoa sdl tester @@ -262,14 +262,21 @@ $(BIN)/SameBoy.app/Contents/Resources/%.bin: $(BOOTROMS_DIR)/%.bin cp -f $^ $@ $(BIN)/SDL/LICENSE: LICENSE + -@$(MKDIR) -p $(dir $@) cp -f $^ $@ $(BIN)/SDL/registers.sym: Misc/registers.sym + -@$(MKDIR) -p $(dir $@) cp -f $^ $@ $(BIN)/SDL/background.bmp: SDL/background.bmp + -@$(MKDIR) -p $(dir $@) cp -f $^ $@ +$(BIN)/SDL/Shaders: Shaders + -@$(MKDIR) -p $(dir $@) + cp -rf $^ $@ + # Boot ROMs $(BIN)/BootROMs/%.bin: BootROMs/%.asm diff --git a/SDL/gui.c b/SDL/gui.c index 1a12e28..bbee0d8 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -20,6 +20,31 @@ unsigned command_parameter; #define MODIFIER_NAME CTRL_STRING #endif +shader_t shader; +SDL_Rect rect; + +void render_texture(void *pixels, void *previous) +{ + if (renderer) { + if (pixels) { + SDL_UpdateTexture(texture, NULL, pixels, 160 * sizeof (uint32_t)); + } + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); + } + else { + static void *_pixels = NULL; + if (pixels) { + _pixels = pixels; + } + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + render_bitmap_with_shader(&shader, _pixels, previous, rect.x, rect.y, rect.w, rect.h); + SDL_GL_SwapWindow(window); + } +} + configuration_t configuration = { { SDL_SCANCODE_RIGHT, @@ -59,7 +84,7 @@ static const char *help[] ={ void update_viewport(void) { int win_width, win_height; - SDL_GetWindowSize(window, &win_width, &win_height); + SDL_GL_GetDrawableSize(window, &win_width, &win_height); double x_factor = win_width / 160.0; double y_factor = win_height / 144.0; @@ -80,9 +105,15 @@ void update_viewport(void) unsigned new_width = x_factor * 160; unsigned new_height = y_factor * 144; - SDL_Rect rect = (SDL_Rect){(win_width - new_width) / 2, (win_height - new_height) /2, + rect = (SDL_Rect){(win_width - new_width) / 2, (win_height - new_height) /2, new_width, new_height}; - SDL_RenderSetViewport(renderer, &rect); + + if (renderer) { + SDL_RenderSetViewport(renderer, &rect); + } + else { + glViewport(rect.x, rect.y, rect.w, rect.h); + } } /* Does NOT check for bounds! */ @@ -234,9 +265,7 @@ void cycle_scaling(unsigned index) configuration.scaling_mode = 0; } update_viewport(); - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); + render_texture(NULL, NULL); } void cycle_scaling_backwards(unsigned index) @@ -248,9 +277,7 @@ void cycle_scaling_backwards(unsigned index) configuration.scaling_mode--; } update_viewport(); - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); + render_texture(NULL, NULL); } static void cycle_color_correction(unsigned index) @@ -273,6 +300,82 @@ static void cycle_color_correction_backwards(unsigned index) } } +struct shader_name { + const char *file_name; + const char *display_name; +} shaders[] = +{ + {"NearestNeighbor", "Nearest Neighbor"}, + {"Bilinear", "Bilinear"}, + {"SmoothBilinear", "Smooth Bilinear"}, + {"Scale2x", "Scale2x"}, + {"Scale4x", "Scale4x"}, + {"AAScale2x", "Anti-aliased Scale2x"}, + {"AAScale4x", "Anti-aliased Scale4x"}, + {"HQ2x", "HQ2x"}, + {"OmniScale", "OmniScale"}, + {"OmniScaleLegacy", "OmniScale Legacy"}, + {"AAOmniScaleLegacy", "AA OmniScale Legacy"}, +}; + +static void cycle_filter(unsigned index) +{ + unsigned i = 0; + for (; i < sizeof(shaders) / sizeof(shaders[0]); i++) { + if (strcmp(shaders[i].file_name, configuration.filter) == 0) { + break; + } + } + + + i += 1; + if (i >= sizeof(shaders) / sizeof(shaders[0])) { + i -= sizeof(shaders) / sizeof(shaders[0]); + } + + strcpy(configuration.filter, shaders[i].file_name); + free_shader(&shader); + if (!init_shader_with_name(&shader, configuration.filter)) { + init_shader_with_name(&shader, "NearestNeighbor"); + } +} + +static void cycle_filter_backwards(unsigned index) +{ + unsigned i = 0; + for (; i < sizeof(shaders) / sizeof(shaders[0]); i++) { + if (strcmp(shaders[i].file_name, configuration.filter) == 0) { + break; + } + } + + i -= 1; + if (i >= sizeof(shaders) / sizeof(shaders[0])) { + i = sizeof(shaders) / sizeof(shaders[0]) - 1; + } + + strcpy(configuration.filter, shaders[i].file_name); + free_shader(&shader); + if (!init_shader_with_name(&shader, configuration.filter)) { + init_shader_with_name(&shader, "NearestNeighbor"); + } + +} +const char *current_filter_name(unsigned index) +{ + unsigned i = 0; + for (; i < sizeof(shaders) / sizeof(shaders[0]); i++) { + if (strcmp(shaders[i].file_name, configuration.filter) == 0) { + break; + } + } + + if (i == sizeof(shaders) / sizeof(shaders[0])) { + i = 0; + } + + return shaders[i].display_name; +} static void return_to_root_menu(unsigned index) { @@ -282,6 +385,7 @@ static void return_to_root_menu(unsigned index) static const struct menu_item graphics_menu[] = { {"Scaling Mode:", cycle_scaling, current_scaling_mode, cycle_scaling_backwards}, + {"Scaling Filter:", cycle_filter, current_filter_name, cycle_filter_backwards}, {"Color Correction:", cycle_color_correction, current_color_correction_mode, cycle_color_correction_backwards}, {"Back", return_to_root_menu}, {NULL,} @@ -399,6 +503,7 @@ static void detect_joypad_layout(unsigned index) static const struct menu_item joypad_menu[] = { {"Joypad:", cycle_joypads, current_joypad_name, cycle_joypads_backwards}, {"Detect layout", detect_joypad_layout}, + {"Back", return_to_root_menu}, {NULL,} }; @@ -510,9 +615,7 @@ void run_gui(bool is_running) case SDL_WINDOWEVENT: { if (event.window.event == SDL_WINDOWEVENT_RESIZED) { update_viewport(); - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); + render_texture(NULL, NULL); } break; } @@ -667,10 +770,7 @@ void run_gui(bool is_running) break; } - SDL_UpdateTexture(texture, NULL, pixels, 160 * sizeof (uint32_t)); - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); + render_texture(pixels, NULL); } } while (SDL_WaitEvent(&event)); } diff --git a/SDL/gui.h b/SDL/gui.h index 6e08c6b..5a7e94a 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -3,11 +3,13 @@ #include #include +#include "shader.h" extern SDL_Window *window; extern SDL_Renderer *renderer; extern SDL_Texture *texture; extern SDL_PixelFormat *pixel_format; +extern shader_t shader; enum scaling_mode { GB_SDL_SCALING_ENTIRE_WINDOW, @@ -37,6 +39,8 @@ typedef struct { bool div_joystick; bool flip_joystick_bit_1; bool swap_joysticks_bits_1_and_2; + + char filter[32]; } configuration_t; extern configuration_t configuration; @@ -44,5 +48,6 @@ extern configuration_t configuration; void update_viewport(void); void run_gui(bool is_running); unsigned fix_joypad_button(unsigned button); +void render_texture(void *pixels, void *previous); #endif diff --git a/SDL/main.c b/SDL/main.c index 8320b67..bac5936 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -4,6 +4,7 @@ #include "gb.h" #include "utils.h" #include "gui.h" +#include "shader.h" #ifndef _WIN32 #define AUDIO_FREQUENCY 96000 @@ -212,10 +213,7 @@ static void handle_events(GB_gameboy_t *gb) static void vblank(GB_gameboy_t *gb) { - SDL_UpdateTexture(texture, NULL, pixels, 160 * sizeof (uint32_t)); - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); + render_texture(pixels, NULL); handle_events(gb); } @@ -358,7 +356,7 @@ restart: pending_command = GB_SDL_NO_COMMAND; } } - + int main(int argc, char **argv) { #define str(x) #x @@ -390,15 +388,25 @@ usage: SDL_Init( SDL_INIT_EVERYTHING ); - window = SDL_CreateWindow("SameBoy v" xstr(VERSION), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - 160 * 2, 144 * 2, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); - SDL_SetWindowMinimumSize(window, 160, 144); - renderer = SDL_CreateRenderer(window, -1, 0); - - texture = SDL_CreateTexture(renderer, SDL_GetWindowPixelFormat(window), SDL_TEXTUREACCESS_STREAMING, 160, 144); - - pixel_format = SDL_AllocFormat(SDL_GetWindowPixelFormat(window)); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + window = SDL_CreateWindow("SameBoy v" xstr(VERSION), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + 160 * 2, 144 * 2, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + SDL_SetWindowMinimumSize(window, 160, 144); + + SDL_GLContext gl_context = SDL_GL_CreateContext(window); + if (gl_context == NULL) { + renderer = SDL_CreateRenderer(window, -1, 0); + texture = SDL_CreateTexture(renderer, SDL_GetWindowPixelFormat(window), SDL_TEXTUREACCESS_STREAMING, 160, 144); + pixel_format = SDL_AllocFormat(SDL_GetWindowPixelFormat(window)); + } + else { + pixel_format = SDL_AllocFormat(SDL_PIXELFORMAT_ABGR8888); + } + + /* Configure Audio */ memset(&want_aspec, 0, sizeof(want_aspec)); want_aspec.freq = AUDIO_FREQUENCY; @@ -420,6 +428,11 @@ usage: SDL_EventState(SDL_DROPFILE, SDL_ENABLE); + if (!init_shader_with_name(&shader, configuration.filter)) { + init_shader_with_name(&shader, "NearestNeighbor"); + } + update_viewport(); + if (filename == NULL) { run_gui(false); } diff --git a/SDL/shader.c b/SDL/shader.c new file mode 100644 index 0000000..47c02d6 --- /dev/null +++ b/SDL/shader.c @@ -0,0 +1,184 @@ +#include +#include +#include "shader.h" +#include "utils.h" +#include + +static const char *vertex_shader = "\n\ +#version 150 \n\ +in vec4 aPosition;\n\ +void main(void) {\n\ +gl_Position = aPosition;\n\ +}\n\ +"; + +static GLuint create_shader(const char *source, GLenum type) +{ + // Create the shader object + GLuint shader = glCreateShader(type); + // Load the shader source + glShaderSource(shader, 1, &source, 0); + // Compile the shader + glCompileShader(shader); + // Check for errors + GLint status = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + if (status == GL_FALSE) { + GLchar messages[1024]; + glGetShaderInfoLog(shader, sizeof(messages), 0, &messages[0]); + fprintf(stderr, "GLSL Shader Error: %s", messages); + } + return shader; +} + +static GLuint create_program(const char *vsh, const char *fsh) +{ + // Build shaders + GLuint vertex_shader = create_shader(vsh, GL_VERTEX_SHADER); + GLuint fragment_shader = create_shader(fsh, GL_FRAGMENT_SHADER); + + // Create program + GLuint program = glCreateProgram(); + + // Attach shaders + glAttachShader(program, vertex_shader); + glAttachShader(program, fragment_shader); + + // Link program + glLinkProgram(program); + // Check for errors + GLint status; + glGetProgramiv(program, GL_LINK_STATUS, &status); + + if (status == GL_FALSE) { + GLchar messages[1024]; + glGetProgramInfoLog(program, sizeof(messages), 0, &messages[0]); + fprintf(stderr, "GLSL Program Error: %s", messages); + } + + // Delete shaders + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + + return program; +} + +bool init_shader_with_name(shader_t *shader, const char *name) +{ + static char master_shader_code[0x801] = {0,}; + static char shader_code[0x10001] = {0,}; + static char final_shader_code[0x10801] = {0,}; + static ssize_t filter_token_location = 0; + + if (!master_shader_code[0]) { + FILE *master_shader_f = fopen(executable_relative_path("Shaders/MasterShader.fsh"), "r"); + if (!master_shader_f) return false; + fread(master_shader_code, 1, sizeof(master_shader_code) - 1, master_shader_f); + fclose(master_shader_f); + filter_token_location = strstr(master_shader_code, "{filter}") - master_shader_code; + if (filter_token_location < 0) { + master_shader_code[0] = 0; + return false; + } + } + + char shader_path[1024]; + sprintf(shader_path, "Shaders/%s.fsh", name); + + FILE *shader_f = fopen(executable_relative_path(shader_path), "r"); + if (!shader_f) return false; + memset(shader_code, 0, sizeof(shader_code)); + fread(shader_code, 1, sizeof(shader_code) - 1, shader_f); + fclose(shader_f); + + memset(final_shader_code, 0, sizeof(final_shader_code)); + memcpy(final_shader_code, master_shader_code, filter_token_location); + strcpy(final_shader_code + filter_token_location, shader_code); + strcat(final_shader_code + filter_token_location, + master_shader_code + filter_token_location + sizeof("{filter}") - 1); + + shader->program = create_program(vertex_shader, final_shader_code); + + // Attributes + shader->position_attribute = glGetAttribLocation(shader->program, "aPosition"); + // Uniforms + shader->resolution_uniform = glGetUniformLocation(shader->program, "uResolution"); + shader->origin_uniform = glGetUniformLocation(shader->program, "uOrigin"); + + glGenTextures(1, &shader->texture); + glBindTexture(GL_TEXTURE_2D, shader->texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, 0); + shader->texture_uniform = glGetUniformLocation(shader->program, "image"); + + glGenTextures(1, &shader->previous_texture); + glBindTexture(GL_TEXTURE_2D, shader->previous_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, 0); + shader->previous_texture_uniform = glGetUniformLocation(shader->program, "previousImage"); + + shader->mix_previous_uniform = glGetUniformLocation(shader->program, "uMixPrevious"); + + // Program + + glUseProgram(shader->program); + + GLuint vao; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + GLuint vbo; + glGenBuffers(1, &vbo); + + // Attributes + + + static GLfloat const quad[16] = { + -1.f, -1.f, 0, 1, + -1.f, +1.f, 0, 1, + +1.f, -1.f, 0, 1, + +1.f, +1.f, 0, 1, + }; + + + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(quad), quad, GL_STATIC_DRAW); + glEnableVertexAttribArray(shader->position_attribute); + glVertexAttribPointer(shader->position_attribute, 4, GL_FLOAT, GL_FALSE, 0, 0); + + return true; +} + +void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous,unsigned x, unsigned y, unsigned w, unsigned h) +{ + glUseProgram(shader->program); + glUniform2f(shader->origin_uniform, x, y); + glUniform2f(shader->resolution_uniform, w, h); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, shader->texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 160, 144, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap); + glUniform1i(shader->texture_uniform, 0); + glUniform1i(shader->mix_previous_uniform, previous != NULL); + if (previous) { + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, shader->previous_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 160, 144, 0, GL_RGBA, GL_UNSIGNED_BYTE, previous); + glUniform1i(shader->previous_texture_uniform, 1); + } + glBindFragDataLocation(shader->program, 0, "frag_color"); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); +} + +void free_shader(shader_t *shader) +{ + glDeleteProgram(shader->program); + glDeleteTextures(1, &shader->texture); + glDeleteTextures(1, &shader->previous_texture); + +} diff --git a/SDL/shader.h b/SDL/shader.h new file mode 100644 index 0000000..74a9250 --- /dev/null +++ b/SDL/shader.h @@ -0,0 +1,23 @@ +#ifndef shader_h +#define shader_h +#include +#include + +typedef struct shader_s { + GLuint resolution_uniform; + GLuint origin_uniform; + GLuint texture_uniform; + GLuint previous_texture_uniform; + GLuint mix_previous_uniform; + + GLuint position_attribute; + GLuint texture; + GLuint previous_texture; + GLuint program; +} shader_t; + +bool init_shader_with_name(shader_t *shader, const char *name); +void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous,unsigned x, unsigned y, unsigned w, unsigned h); +void free_shader(struct shader_s *shader); + +#endif /* shader_h */ diff --git a/Shaders/MasterShader.fsh b/Shaders/MasterShader.fsh index 642026e..4103056 100644 --- a/Shaders/MasterShader.fsh +++ b/Shaders/MasterShader.fsh @@ -4,14 +4,20 @@ uniform sampler2D previousImage; uniform bool uMixPrevious; uniform vec2 uResolution; +uniform vec2 uOrigin; const vec2 textureDimensions = vec2(160, 144); out vec4 frag_color; +vec4 modified_frag_cord; +#define gl_FragCoord modified_frag_cord #line 1 {filter} +#undef gl_FragCoord void main() { + modified_frag_cord = gl_FragCoord - vec4(uOrigin.x, uOrigin.y, 0, 0); + if (uMixPrevious) { frag_color = mix(scale(image), scale(previousImage), 0.5); } From 3c1a805770fe8b3289a35b46ad471f1c38162be6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 23 Dec 2017 17:41:47 +0200 Subject: [PATCH 13/21] Frame blending --- SDL/gui.c | 18 +++++++++++++++--- SDL/gui.h | 1 + SDL/main.c | 17 ++++++++++++++--- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/SDL/gui.c b/SDL/gui.c index bbee0d8..3076ddd 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -47,7 +47,7 @@ void render_texture(void *pixels, void *previous) configuration_t configuration = { - { SDL_SCANCODE_RIGHT, + .keys = { SDL_SCANCODE_RIGHT, SDL_SCANCODE_LEFT, SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, @@ -57,8 +57,9 @@ configuration_t configuration = SDL_SCANCODE_RETURN, SDL_SCANCODE_SPACE }, - GB_COLOR_CORRECTION_EMULATE_HARDWARE, - GB_SDL_SCALING_INTEGER_FACTOR, + .color_correction_mode = GB_COLOR_CORRECTION_EMULATE_HARDWARE, + .scaling_mode = GB_SDL_SCALING_INTEGER_FACTOR, + .blend_frames = true }; @@ -383,10 +384,21 @@ static void return_to_root_menu(unsigned index) current_selection = 0; } +static void toggle_blend_frames(unsigned index) +{ + configuration.blend_frames ^= true; +} + +const char *blend_frames_string(unsigned index) +{ + return configuration.blend_frames? "Enabled" : "Disabled"; +} + static const struct menu_item graphics_menu[] = { {"Scaling Mode:", cycle_scaling, current_scaling_mode, cycle_scaling_backwards}, {"Scaling Filter:", cycle_filter, current_filter_name, cycle_filter_backwards}, {"Color Correction:", cycle_color_correction, current_color_correction_mode, cycle_color_correction_backwards}, + {"Blend Frames:", toggle_blend_frames, blend_frames_string, toggle_blend_frames}, {"Back", return_to_root_menu}, {NULL,} }; diff --git a/SDL/gui.h b/SDL/gui.h index 5a7e94a..41ac374 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -35,6 +35,7 @@ typedef struct { SDL_Scancode keys[9]; GB_color_correction_mode_t color_correction_mode; enum scaling_mode scaling_mode; + bool blend_frames; bool div_joystick; bool flip_joystick_bit_1; diff --git a/SDL/main.c b/SDL/main.c index bac5936..9df3e43 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -16,7 +16,9 @@ GB_gameboy_t gb; static bool dmg = false; static bool paused = false; -static uint32_t pixels[160*144]; +static uint32_t pixel_buffer_1[160*144], pixel_buffer_2[160*144]; +static uint32_t *active_pixel_buffer = pixel_buffer_1, *previous_pixel_buffer = pixel_buffer_2; + static char *filename = NULL; static bool should_free_filename = false; @@ -213,7 +215,16 @@ static void handle_events(GB_gameboy_t *gb) static void vblank(GB_gameboy_t *gb) { - render_texture(pixels, NULL); + if (configuration.blend_frames) { + render_texture(active_pixel_buffer, previous_pixel_buffer); + uint32_t *temp = active_pixel_buffer; + active_pixel_buffer = previous_pixel_buffer; + previous_pixel_buffer = temp; + GB_set_pixels_output(gb, active_pixel_buffer); + } + else { + render_texture(active_pixel_buffer, NULL); + } handle_events(gb); } @@ -303,7 +314,7 @@ restart: } GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); - GB_set_pixels_output(&gb, pixels); + GB_set_pixels_output(&gb, active_pixel_buffer); GB_set_rgb_encode_callback(&gb, rgb_encode); GB_set_sample_rate(&gb, have_aspec.freq); GB_set_color_correction_mode(&gb, configuration.color_correction_mode); From dc59fdf40ec5c9bdd0728b2c3b8e162947be3815 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 23 Dec 2017 21:11:44 +0200 Subject: [PATCH 14/21] Highpass filter in SDL --- Core/apu.h | 1 + SDL/gui.c | 47 +++++++++++++++++++++++++++++++++++++++-------- SDL/gui.h | 2 ++ SDL/main.c | 23 ++++++++++++++++++++--- 4 files changed, 62 insertions(+), 11 deletions(-) diff --git a/Core/apu.h b/Core/apu.h index 3bca456..bace09a 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -103,6 +103,7 @@ typedef enum { GB_HIGHPASS_OFF, // Do not apply any filter, keep DC offset GB_HIGHPASS_ACCURATE, // Apply a highpass filter similar to the one used on hardware GB_HIGHPASS_REMOVE_DC_OFFSET, // Remove DC Offset without affecting the waveform + GB_HIGHPASS_MAX } GB_highpass_mode_t; typedef struct { diff --git a/SDL/gui.c b/SDL/gui.c index 3076ddd..d44cc00 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -58,6 +58,7 @@ configuration_t configuration = SDL_SCANCODE_SPACE }, .color_correction_mode = GB_COLOR_CORRECTION_EMULATE_HARDWARE, + .highpass_mode = GB_HIGHPASS_ACCURATE, .scaling_mode = GB_SDL_SCALING_INTEGER_FACTOR, .blend_frames = true }; @@ -227,10 +228,12 @@ static void item_help(unsigned index) static void enter_graphics_menu(unsigned index); static void enter_controls_menu(unsigned index); static void enter_joypad_menu(unsigned index); +static void enter_audio_menu(unsigned index); static const struct menu_item paused_menu[] = { {"Resume", NULL}, {"Graphic Options", enter_graphics_menu}, + {"Audio Options", enter_audio_menu}, {"Keyboard", enter_controls_menu}, {"Joypad", enter_joypad_menu}, {"Help", item_help}, @@ -238,14 +241,7 @@ static const struct menu_item paused_menu[] = { {NULL,} }; -static const struct menu_item nonpaused_menu[] = { - {"Graphic Options", enter_graphics_menu}, - {"Keyboard", enter_controls_menu}, - {"Joypad", enter_joypad_menu}, - {"Help", item_help}, - {"Exit", item_exit}, - {NULL,} -}; +static const struct menu_item *const nonpaused_menu = &paused_menu[1]; const char *current_scaling_mode(unsigned index) { @@ -409,6 +405,41 @@ static void enter_graphics_menu(unsigned index) current_selection = 0; } +const char *highpass_filter_string(unsigned index) +{ + return (const char *[]){"None (Keep DC Offset)", "Accurate", "Preserve Waveform"} + [configuration.highpass_mode]; +} + +void cycle_highpass_filter(unsigned index) +{ + configuration.highpass_mode++; + if (configuration.highpass_mode == GB_HIGHPASS_MAX) { + configuration.highpass_mode = 0; + } +} + +void cycle_highpass_filter_backwards(unsigned index) +{ + if (configuration.highpass_mode == 0) { + configuration.highpass_mode = GB_HIGHPASS_MAX - 1; + } + else { + configuration.highpass_mode--; + } +} + +static const struct menu_item audio_menu[] = { + {"Highpass Filter:", cycle_highpass_filter, highpass_filter_string, cycle_highpass_filter_backwards}, + {"Back", return_to_root_menu}, + {NULL,} +}; + +static void enter_audio_menu(unsigned index) +{ + current_menu = audio_menu; + current_selection = 0; +} static const char *key_name(unsigned index) { return SDL_GetScancodeName(configuration.keys[index]); diff --git a/SDL/gui.h b/SDL/gui.h index 41ac374..9a4b56e 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -37,6 +37,8 @@ typedef struct { enum scaling_mode scaling_mode; bool blend_frames; + GB_highpass_mode_t highpass_mode; + bool div_joystick; bool flip_joystick_bit_1; bool swap_joysticks_bits_1_and_2; diff --git a/SDL/main.c b/SDL/main.c index 9df3e43..59469fd 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -119,8 +119,16 @@ static void handle_events(GB_gameboy_t *gb) GB_set_turbo_mode(gb, event.type == SDL_JOYBUTTONDOWN, false); } else { + bool audio_playing = SDL_GetAudioStatus() == SDL_AUDIO_PLAYING; + if (audio_playing) { + SDL_PauseAudio(true); + } run_gui(true); + if (audio_playing) { + SDL_PauseAudio(false); + } GB_set_color_correction_mode(gb, configuration.color_correction_mode); + GB_set_highpass_filter_mode(gb, configuration.highpass_mode); } break; @@ -137,11 +145,19 @@ static void handle_events(GB_gameboy_t *gb) case SDL_KEYDOWN: switch (event.key.keysym.scancode) { - case SDL_SCANCODE_ESCAPE: + case SDL_SCANCODE_ESCAPE: { + bool audio_playing = SDL_GetAudioStatus() == SDL_AUDIO_PLAYING; + if (audio_playing) { + SDL_PauseAudio(true); + } run_gui(true); + if (audio_playing) { + SDL_PauseAudio(false); + } GB_set_color_correction_mode(gb, configuration.color_correction_mode); + GB_set_highpass_filter_mode(gb, configuration.highpass_mode); break; - + } case SDL_SCANCODE_C: if (event.type == SDL_KEYDOWN && (event.key.keysym.mod & KMOD_CTRL)) { GB_debugger_break(gb); @@ -318,6 +334,7 @@ restart: GB_set_rgb_encode_callback(&gb, rgb_encode); GB_set_sample_rate(&gb, have_aspec.freq); GB_set_color_correction_mode(&gb, configuration.color_correction_mode); + GB_set_highpass_filter_mode(&gb, configuration.highpass_mode); } bool error = false; @@ -435,7 +452,6 @@ usage: SDL_OpenAudio(&want_aspec, &have_aspec); /* Start Audio */ - SDL_PauseAudio(false); SDL_EventState(SDL_DROPFILE, SDL_ENABLE); @@ -447,6 +463,7 @@ usage: if (filename == NULL) { run_gui(false); } + SDL_PauseAudio(false); run(); // Never returns return 0; } From c559a308ad95f4e6514dd07e46a327e33742937f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 27 Dec 2017 23:20:06 +0200 Subject: [PATCH 15/21] Slightly improved background --- SDL/background.bmp | Bin 11592 -> 11592 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/SDL/background.bmp b/SDL/background.bmp index 4624cb2395bcfab8eba53aea3be99868f0fb6ab0..0f6192d6db58b5a88cea4624e667a63de5c7b388 100644 GIT binary patch delta 398 zcmX>Rbs}m*8SCT=EbNo-u;@?bVU^|(Pyz$NiGjM4=Wt7NGD7%@0uuvufC{9z6qyv6 zKw?5*kr<$ef{=m`Sd{`yjerse0s+t%oyj0Ij7lH~1d0$flH4G3AZ9@oNpgb>f*1x@ z1u+Rkf>qUUi1R9eje-~mR(FO&l3NLG99T$(6XGneX<(5YpokLKSwM$@&Dz5$!vS>` z#5yh+4zMv$N5*i;vLm@>4VN4n*i&H5f4JmXLH+^h-0Z{M#>C9X$T0aJi`3@#yvmG> xjFW}==dcR`86fsH{#oo0CgbFAfjK~tF9P!>8wrU`ULkmUva_J*<{3g8qySX=P-p-E delta 424 zcmX|+u};G<5Qby7CW(`%_O()*C_rr1@+K@yNL3$!nW-~7$^@!9Ga!K!iHU`+L*Jke z(WN6p@6IVc$vXf3{@>^AV!QZyQmOO2oO!vwZF5znG6>$1uvBH{z`;K=N2bgy*dPJg z=1f^nMXMnxSF_wnC%yDUVi;kwyzyM5xicrcATyx7=cWZe+GqCQv}kY=ERJPzl^_`s zX$K~ENtsb(h-IlxE6r9)WaaU!U|e|hC#@6X4d(Qd={f-p3FoE0jCrbX{LohyVDKM2 z$e+Ge^n1$g?y_e&h(y%>4y*1^+z4#PqjhXVB&h!$y~TJ<)jpUt?nkp3;4K=)?YrGe MYp|Vkukcx&05Z-}V*mgE From 61f9dbd95d0cdfac1e361137ee7cce2a8f63b82e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 28 Dec 2017 20:22:54 +0200 Subject: [PATCH 16/21] =?UTF-8?q?Use=20SDL=E2=80=99s=20key=20mapping=20whe?= =?UTF-8?q?n=20available?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/apu.c | 3 +- SDL/gui.c | 124 ++++++++++++++++++++++++++++++++++++++++++++--------- SDL/gui.h | 1 + SDL/main.c | 18 +++++++- 4 files changed, 123 insertions(+), 23 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 5ba1911..c74bb6d 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -93,7 +93,8 @@ static void render(GB_gameboy_t *gb) gb->apu_output.highpass_diff = (GB_double_sample_t) {left_volume * (1 - gb->apu_output.highpass_rate) + gb->apu_output.highpass_diff.left * gb->apu_output.highpass_rate, right_volume * (1 - gb->apu_output.highpass_rate) + gb->apu_output.highpass_diff.right * gb->apu_output.highpass_rate}; - + + case GB_HIGHPASS_MAX:; } } diff --git a/SDL/gui.c b/SDL/gui.c index d44cc00..ba2cb73 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -472,6 +472,7 @@ static void enter_controls_menu(unsigned index) static unsigned joypad_index = 0; SDL_Joystick *joystick = NULL; +SDL_GameController *controller = NULL; const char *current_joypad_name(unsigned index) { static char name[23] = {0,}; @@ -500,14 +501,92 @@ static void cycle_joypads(unsigned index) if (joypad_index >= SDL_NumJoysticks()) { joypad_index = 0; } - if (joystick) { - SDL_JoystickClose(joystick); + if (controller) { + SDL_GameControllerClose(controller); + controller = NULL; } - joystick = SDL_JoystickOpen(joypad_index); + else if (joystick) { + SDL_JoystickClose(joystick); + joystick = NULL; + } + if ((controller = SDL_GameControllerOpen(joypad_index))){ + joystick = SDL_GameControllerGetJoystick(controller); + } + else { + joystick = SDL_JoystickOpen(joypad_index); + } +} + +static void cycle_joypads_backwards(unsigned index) +{ + joypad_index++; + if (joypad_index >= SDL_NumJoysticks()) { + joypad_index = SDL_NumJoysticks() - 1; + } + if (controller) { + SDL_GameControllerClose(controller); + controller = NULL; + } + else if (joystick) { + SDL_JoystickClose(joystick); + joystick = NULL; + } + if ((controller = SDL_GameControllerOpen(joypad_index))){ + joystick = SDL_GameControllerGetJoystick(controller); + } + else { + joystick = SDL_JoystickOpen(joypad_index); + } +} + +unsigned fix_joypad_axis(unsigned axis) +{ + if (controller) { + /* Convert to the mapping used by generic Xbox-style controllers */ + for (SDL_GameControllerAxis i = 0; i < SDL_CONTROLLER_AXIS_MAX; i++) { + if (SDL_GameControllerGetBindForAxis(controller, i).value.axis == axis) { + if (i == SDL_CONTROLLER_AXIS_LEFTX || i == SDL_CONTROLLER_AXIS_RIGHTX) return 0; + if (i == SDL_CONTROLLER_AXIS_LEFTY || i == SDL_CONTROLLER_AXIS_RIGHTY) return 1; + return i; + } + } + return -1; + } + + + if (configuration.div_joystick) { + axis >>= 1; + } + + return axis & 1; } unsigned fix_joypad_button(unsigned button) { + if (controller) { + /* Convert to the mapping used by generic Xbox-style controllers */ + for (SDL_GameControllerButton i = 0; i < SDL_CONTROLLER_BUTTON_MAX; i++) { + if (SDL_GameControllerGetBindForButton(controller, i).value.button == button) { + if (i == SDL_CONTROLLER_BUTTON_START) { + return 9; + } + if (i == 9) { + return SDL_CONTROLLER_BUTTON_START; + } + + if (i == SDL_CONTROLLER_BUTTON_BACK) { + return 8; + } + if (i == 8) { + return SDL_CONTROLLER_BUTTON_BACK; + } + return i; + } + } + return -1; + } + + if (configuration.div_joystick) { button >>= 1; } @@ -525,18 +604,6 @@ unsigned fix_joypad_button(unsigned button) return button; } -static void cycle_joypads_backwards(unsigned index) -{ - joypad_index++; - if (joypad_index >= SDL_NumJoysticks()) { - joypad_index = SDL_NumJoysticks() - 1; - } - if (joystick) { - SDL_JoystickClose(joystick); - } - joystick = SDL_JoystickOpen(joypad_index); -} - static void detect_joypad_layout(unsigned index) { gui_state = WAITING_FOR_JBUTTON; @@ -560,11 +627,23 @@ extern void set_filename(const char *new_filename, bool new_should_free); void run_gui(bool is_running) { if (joystick && !SDL_NumJoysticks()) { - SDL_JoystickClose(joystick); - joystick = NULL; + if (controller) { + SDL_GameControllerClose(controller); + controller = NULL; + joystick = NULL; + } + else { + SDL_JoystickClose(joystick); + joystick = NULL; + } } else if (!joystick && SDL_NumJoysticks()) { - joystick = SDL_JoystickOpen(0); + if ((controller = SDL_GameControllerOpen(0))){ + joystick = SDL_GameControllerGetJoystick(controller); + } + else { + joystick = SDL_JoystickOpen(0); + } } /* Draw the background screen */ static SDL_Surface *converted_background = NULL; @@ -599,11 +678,16 @@ void run_gui(bool is_running) else if (event.jbutton.button == 8 || event.jbutton.button == 9) { event.key.keysym.scancode = SDL_SCANCODE_ESCAPE; } + else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_UP) event.key.keysym.scancode = SDL_SCANCODE_UP; + else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) event.key.keysym.scancode = SDL_SCANCODE_DOWN; + else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT) event.key.keysym.scancode = SDL_SCANCODE_LEFT; + else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT) event.key.keysym.scancode = SDL_SCANCODE_RIGHT; break; case SDL_JOYAXISMOTION: { static bool axis_active[2] = {false, false}; - if ((event.jaxis.axis >> configuration.div_joystick) & 1) { + event.jaxis.axis = fix_joypad_axis(event.jaxis.axis); + if (event.jaxis.axis == 1) { if (event.jaxis.value > 0x4000) { if (!axis_active[1]) { event.type = SDL_KEYDOWN; @@ -622,7 +706,7 @@ void run_gui(bool is_running) axis_active[1] = false; } } - else { + else if (event.jaxis.axis == 0) { if (event.jaxis.value > 0x4000) { if (!axis_active[0]) { event.type = SDL_KEYDOWN; diff --git a/SDL/gui.h b/SDL/gui.h index 9a4b56e..b68d0d1 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -51,6 +51,7 @@ extern configuration_t configuration; void update_viewport(void); void run_gui(bool is_running); unsigned fix_joypad_button(unsigned button); +unsigned fix_joypad_axis(unsigned axis); void render_texture(void *pixels, void *previous); #endif diff --git a/SDL/main.c b/SDL/main.c index 59469fd..79271a9 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -115,9 +115,22 @@ static void handle_events(GB_gameboy_t *gb) else if (event.jbutton.button == 9) { GB_set_key_state(gb, GB_KEY_START, event.type == SDL_JOYBUTTONDOWN); } + else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_UP) { + GB_set_key_state(gb, GB_KEY_UP, event.type == SDL_JOYBUTTONDOWN); + } + else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) { + GB_set_key_state(gb, GB_KEY_DOWN, event.type == SDL_JOYBUTTONDOWN); + } + else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT) { + GB_set_key_state(gb, GB_KEY_LEFT, event.type == SDL_JOYBUTTONDOWN); + } + else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT) { + GB_set_key_state(gb, GB_KEY_RIGHT, event.type == SDL_JOYBUTTONDOWN); + } else if (event.jbutton.button & 1) { GB_set_turbo_mode(gb, event.type == SDL_JOYBUTTONDOWN, false); } + else { bool audio_playing = SDL_GetAudioStatus() == SDL_AUDIO_PLAYING; if (audio_playing) { @@ -133,11 +146,12 @@ static void handle_events(GB_gameboy_t *gb) break; case SDL_JOYAXISMOTION: - if ((event.jaxis.axis >> configuration.div_joystick) & 1) { + event.jaxis.axis = fix_joypad_axis(event.jaxis.axis); + if (event.jaxis.axis == 1) { GB_set_key_state(gb, GB_KEY_DOWN, event.jaxis.value > 0x4000); GB_set_key_state(gb, GB_KEY_UP, event.jaxis.value < -0x4000); } - else { + else if (event.jaxis.axis == 0) { GB_set_key_state(gb, GB_KEY_RIGHT, event.jaxis.value > 0x4000); GB_set_key_state(gb, GB_KEY_LEFT, event.jaxis.value < -0x4000); } From effc02ce8e4b4e1c39edf7e1da3d612b29ab7ebe Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 28 Dec 2017 20:29:15 +0200 Subject: [PATCH 17/21] Use SDL_GetBasePath for better portability --- SDL/utils.c | 54 ++++++++--------------------------------------------- 1 file changed, 8 insertions(+), 46 deletions(-) diff --git a/SDL/utils.c b/SDL/utils.c index d14beed..539e351 100644 --- a/SDL/utils.c +++ b/SDL/utils.c @@ -1,62 +1,24 @@ +#include #include #include -#ifdef __APPLE__ -#include -#endif -#ifdef _WIN32 -#include -#include -#endif -#ifdef __linux__ -#include -#include -#endif #include "utils.h" const char *executable_folder(void) { - static char path[1024] = {0,}; - if (path[0]) { - return path; - } - /* Ugly unportable code! :( */ -#ifdef __APPLE__ - unsigned int length = sizeof(path) - 1; - _NSGetExecutablePath(&path[0], &length); -#else -#ifdef __linux__ - ssize_t __attribute__((unused)) length = readlink("/proc/self/exe", &path[0], sizeof(path) - 1); - assert (length != -1); -#else -#ifdef _WIN32 - HMODULE hModule = GetModuleHandle(NULL); - GetModuleFileName(hModule, path, sizeof(path) - 1); -#else - /* No OS-specific way, assume running from CWD */ - getcwd(&path[0], sizeof(path) - 1); - return path; -#endif -#endif -#endif - size_t pos = strlen(path); - while (pos) { - pos--; -#ifdef _WIN32 - if (path[pos] == '\\') { -#else - if (path[pos] == '/') { -#endif - path[pos] = 0; - break; + static const char *ret = NULL; + if (!ret) { + ret = SDL_GetBasePath(); + if (!ret) { + ret = "./"; } } - return path; + return ret; } char *executable_relative_path(const char *filename) { static char path[1024]; - snprintf(path, sizeof(path), "%s/%s", executable_folder(), filename); + snprintf(path, sizeof(path), "%s%s", executable_folder(), filename); return path; } From ca92c51f51722acd13e870009b18393ecc6bbc7f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 28 Dec 2017 21:46:59 +0200 Subject: [PATCH 18/21] Save user configuration --- SDL/main.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/SDL/main.c b/SDL/main.c index 79271a9..0641544 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -399,6 +399,17 @@ restart: } } +static char prefs_path[1024] = {0, }; + +static void save_configuration(void) +{ + FILE *prefs_file = fopen(prefs_path, "wb"); + if (prefs_file) { + fwrite(&configuration, 1, sizeof(configuration), prefs_file); + fclose(prefs_file); + } +} + int main(int argc, char **argv) { #define str(x) #x @@ -469,6 +480,18 @@ usage: SDL_EventState(SDL_DROPFILE, SDL_ENABLE); + char *prefs_dir = SDL_GetPrefPath("", "SameBoy"); + snprintf(prefs_path, sizeof(prefs_path) - 1, "%sprefs.bin", prefs_dir); + SDL_free(prefs_dir); + + FILE *prefs_file = fopen(prefs_path, "rb"); + if (prefs_file) { + fread(&configuration, 1, sizeof(configuration), prefs_file); + fclose(prefs_file); + } + + atexit(save_configuration); + if (!init_shader_with_name(&shader, configuration.filter)) { init_shader_with_name(&shader, "NearestNeighbor"); } From b1edf540d8ffa8705cd800fc34999931535ad74d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 29 Dec 2017 00:43:19 +0200 Subject: [PATCH 19/21] Handle failures to create an OpenGL context better --- SDL/main.c | 10 ++++++++++ SDL/shader.c | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/SDL/main.c b/SDL/main.c index 0641544..d5a569d 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -450,6 +450,16 @@ usage: SDL_SetWindowMinimumSize(window, 160, 144); SDL_GLContext gl_context = SDL_GL_CreateContext(window); + + GLint major, minor; + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MINOR_VERSION, &minor); + + if (major * 0x100 + minor < 0x302) { + SDL_GL_DeleteContext(gl_context); + gl_context = NULL; + } + if (gl_context == NULL) { renderer = SDL_CreateRenderer(window, -1, 0); texture = SDL_CreateTexture(renderer, SDL_GetWindowPixelFormat(window), SDL_TEXTUREACCESS_STREAMING, 160, 144); diff --git a/SDL/shader.c b/SDL/shader.c index 47c02d6..47f5d39 100644 --- a/SDL/shader.c +++ b/SDL/shader.c @@ -65,6 +65,14 @@ static GLuint create_program(const char *vsh, const char *fsh) bool init_shader_with_name(shader_t *shader, const char *name) { + GLint major, minor; + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MINOR_VERSION, &minor); + + if (major * 0x100 + minor < 0x302) { + return false; + } + static char master_shader_code[0x801] = {0,}; static char shader_code[0x10001] = {0,}; static char final_shader_code[0x10801] = {0,}; From 3d8e22ed504b9740846d7486fe0106d7b7901944 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 29 Dec 2017 11:35:09 +0200 Subject: [PATCH 20/21] =?UTF-8?q?Use=20SDL=E2=80=99s=20OpenGL=20headers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SDL/shader.c | 1 - SDL/shader.h | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SDL/shader.c b/SDL/shader.c index 47f5d39..013eeab 100644 --- a/SDL/shader.c +++ b/SDL/shader.c @@ -2,7 +2,6 @@ #include #include "shader.h" #include "utils.h" -#include static const char *vertex_shader = "\n\ #version 150 \n\ diff --git a/SDL/shader.h b/SDL/shader.h index 74a9250..baad223 100644 --- a/SDL/shader.h +++ b/SDL/shader.h @@ -1,6 +1,7 @@ #ifndef shader_h #define shader_h -#include +#define GL_GLEXT_PROTOTYPES +#include #include typedef struct shader_s { From 600f0eadd96adf47756a5144037d2d4cb48b7dd5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 29 Dec 2017 13:06:38 +0200 Subject: [PATCH 21/21] Better Windows and Linux compatibility --- Makefile | 1 + SDL/opengl_compat.c | 34 ++++++++++++++++++++++++++++++++++ SDL/opengl_compat.h | 45 +++++++++++++++++++++++++++++++++++++++++++++ SDL/shader.c | 2 +- SDL/shader.h | 3 +-- Windows/inttypes.h | 1 + 6 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 SDL/opengl_compat.c create mode 100644 SDL/opengl_compat.h create mode 100755 Windows/inttypes.h diff --git a/Makefile b/Makefile index dde767d..36fa1ec 100755 --- a/Makefile +++ b/Makefile @@ -47,6 +47,7 @@ SDL_LDFLAGS := -lSDL2 -lGL ifeq ($(PLATFORM),windows32) CFLAGS += -IWindows LDFLAGS += -lmsvcrt -lSDL2main -Wl,/MANIFESTFILE:NUL +SDL_LDFLAGS := -lSDL2 -lopengl32 else LDFLAGS += -lc -lm endif diff --git a/SDL/opengl_compat.c b/SDL/opengl_compat.c new file mode 100644 index 0000000..aed2a76 --- /dev/null +++ b/SDL/opengl_compat.c @@ -0,0 +1,34 @@ +#define GL_GLEXT_PROTOTYPES +#include + +#ifndef __APPLE__ +#define GL_COMPAT_NAME(func) gl_compat_##func +#define GL_COMPAT_VAR(func) typeof(func) *GL_COMPAT_NAME(func) + +GL_COMPAT_VAR(glCreateShader); +GL_COMPAT_VAR(glGetAttribLocation); +GL_COMPAT_VAR(glGetUniformLocation); +GL_COMPAT_VAR(glUseProgram); +GL_COMPAT_VAR(glGenVertexArrays); +GL_COMPAT_VAR(glBindVertexArray); +GL_COMPAT_VAR(glGenBuffers); +GL_COMPAT_VAR(glBindBuffer); +GL_COMPAT_VAR(glBufferData); +GL_COMPAT_VAR(glEnableVertexAttribArray); +GL_COMPAT_VAR(glVertexAttribPointer); +GL_COMPAT_VAR(glCreateProgram); +GL_COMPAT_VAR(glAttachShader); +GL_COMPAT_VAR(glLinkProgram); +GL_COMPAT_VAR(glGetProgramiv); +GL_COMPAT_VAR(glGetProgramInfoLog); +GL_COMPAT_VAR(glDeleteShader); +GL_COMPAT_VAR(glUniform2f); +GL_COMPAT_VAR(glActiveTexture); +GL_COMPAT_VAR(glUniform1i); +GL_COMPAT_VAR(glBindFragDataLocation); +GL_COMPAT_VAR(glDeleteProgram); +GL_COMPAT_VAR(glShaderSource); +GL_COMPAT_VAR(glCompileShader); +GL_COMPAT_VAR(glGetShaderiv); +GL_COMPAT_VAR(glGetShaderInfoLog); +#endif diff --git a/SDL/opengl_compat.h b/SDL/opengl_compat.h new file mode 100644 index 0000000..9fd1ca9 --- /dev/null +++ b/SDL/opengl_compat.h @@ -0,0 +1,45 @@ +#ifndef opengl_compat_h +#define opengl_compat_h + +#define GL_GLEXT_PROTOTYPES +#include +#include + +#ifndef __APPLE__ +#define GL_COMPAT_NAME(func) gl_compat_##func + +#define GL_COMPAT_WRAPPER(func) \ +({ extern typeof(func) *GL_COMPAT_NAME(func); \ +if(!GL_COMPAT_NAME(func)) GL_COMPAT_NAME(func) = SDL_GL_GetProcAddress(#func); \ + GL_COMPAT_NAME(func); \ +}) + +#define glCreateShader GL_COMPAT_WRAPPER(glCreateShader) +#define glGetAttribLocation GL_COMPAT_WRAPPER(glGetAttribLocation) +#define glGetUniformLocation GL_COMPAT_WRAPPER(glGetUniformLocation) +#define glUseProgram GL_COMPAT_WRAPPER(glUseProgram) +#define glGenVertexArrays GL_COMPAT_WRAPPER(glGenVertexArrays) +#define glBindVertexArray GL_COMPAT_WRAPPER(glBindVertexArray) +#define glGenBuffers GL_COMPAT_WRAPPER(glGenBuffers) +#define glBindBuffer GL_COMPAT_WRAPPER(glBindBuffer) +#define glBufferData GL_COMPAT_WRAPPER(glBufferData) +#define glEnableVertexAttribArray GL_COMPAT_WRAPPER(glEnableVertexAttribArray) +#define glVertexAttribPointer GL_COMPAT_WRAPPER(glVertexAttribPointer) +#define glCreateProgram GL_COMPAT_WRAPPER(glCreateProgram) +#define glAttachShader GL_COMPAT_WRAPPER(glAttachShader) +#define glLinkProgram GL_COMPAT_WRAPPER(glLinkProgram) +#define glGetProgramiv GL_COMPAT_WRAPPER(glGetProgramiv) +#define glGetProgramInfoLog GL_COMPAT_WRAPPER(glGetProgramInfoLog) +#define glDeleteShader GL_COMPAT_WRAPPER(glDeleteShader) +#define glUniform2f GL_COMPAT_WRAPPER(glUniform2f) +#define glActiveTexture GL_COMPAT_WRAPPER(glActiveTexture) +#define glUniform1i GL_COMPAT_WRAPPER(glUniform1i) +#define glBindFragDataLocation GL_COMPAT_WRAPPER(glBindFragDataLocation) +#define glDeleteProgram GL_COMPAT_WRAPPER(glDeleteProgram) +#define glShaderSource GL_COMPAT_WRAPPER(glShaderSource) +#define glCompileShader GL_COMPAT_WRAPPER(glCompileShader) +#define glGetShaderiv GL_COMPAT_WRAPPER(glGetShaderiv) +#define glGetShaderInfoLog GL_COMPAT_WRAPPER(glGetShaderInfoLog) +#endif + +#endif /* opengl_compat_h */ diff --git a/SDL/shader.c b/SDL/shader.c index 013eeab..7aebc88 100644 --- a/SDL/shader.c +++ b/SDL/shader.c @@ -75,7 +75,7 @@ bool init_shader_with_name(shader_t *shader, const char *name) static char master_shader_code[0x801] = {0,}; static char shader_code[0x10001] = {0,}; static char final_shader_code[0x10801] = {0,}; - static ssize_t filter_token_location = 0; + static signed long filter_token_location = 0; if (!master_shader_code[0]) { FILE *master_shader_f = fopen(executable_relative_path("Shaders/MasterShader.fsh"), "r"); diff --git a/SDL/shader.h b/SDL/shader.h index baad223..20baf76 100644 --- a/SDL/shader.h +++ b/SDL/shader.h @@ -1,7 +1,6 @@ #ifndef shader_h #define shader_h -#define GL_GLEXT_PROTOTYPES -#include +#include "opengl_compat.h" #include typedef struct shader_s { diff --git a/Windows/inttypes.h b/Windows/inttypes.h new file mode 100755 index 0000000..9a6118b --- /dev/null +++ b/Windows/inttypes.h @@ -0,0 +1 @@ +#include