Merge branch 'sdl_gui' into libretro_core

# Conflicts:
#	Makefile
#	SDL/main.c
This commit is contained in:
Lior Halphon 2017-12-29 13:12:12 +02:00
commit d3a2e49d38
21 changed files with 2531 additions and 272 deletions

View File

@ -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

View File

@ -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:;
}
}

View File

@ -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 {

View File

@ -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);

View File

@ -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;
}

View File

@ -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)

View File

@ -43,10 +43,11 @@ endif
# Set compilation and linkage flags based on target, platform and configuration
CFLAGS += -Werror -Wall -std=gnu11 -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
SDL_LDFLAGS := -lSDL2 -lopengl32
else
LDFLAGS += -lc -lm
endif
@ -56,7 +57,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 +89,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 $(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 libretro
@ -262,14 +263,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/drop.bmp: SDL/drop.bmp
$(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

BIN
SDL/background.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

1038
SDL/font.c Normal file

File diff suppressed because it is too large Load Diff

16
SDL/font.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef font_h
#define font_h
#include <stdint.h>
extern uint8_t font[];
extern const uint8_t font_max;
#define GLYPH_HEIGHT 8
#define GLYPH_WIDTH 6
#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"
#endif /* font_h */

903
SDL/gui.c Normal file
View File

@ -0,0 +1,903 @@
#include <SDL2/SDL.h>
#include <stdbool.h>
#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 pending_command pending_command;
unsigned command_parameter;
#ifdef __APPLE__
#define MODIFIER_NAME " " CMD_STRING
#else
#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 =
{
.keys = { 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
},
.color_correction_mode = GB_COLOR_CORRECTION_EMULATE_HARDWARE,
.highpass_mode = GB_HIGHPASS_ACCURATE,
.scaling_mode = GB_SDL_SCALING_INTEGER_FACTOR,
.blend_frames = true
};
static const char *help[] ={
"Drop a GB or GBC ROM\n"
"file to play.\n"
"\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"
" Save state: " MODIFIER_NAME "+(0-9)\n"
" Load state: " MODIFIER_NAME "+" SHIFT_STRING "+(0-9)\n"
#ifdef __APPLE__
" Mute/Unmute: " MODIFIER_NAME "+" SHIFT_STRING "+M\n"
#else
" Mute/Unmute: " MODIFIER_NAME "+M\n"
#endif
" Break Debugger: " CTRL_STRING "+C"
};
void update_viewport(void)
{
int 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;
if (configuration.scaling_mode == GB_SDL_SCALING_INTEGER_FACTOR) {
x_factor = (int)(x_factor);
y_factor = (int)(y_factor);
}
if (configuration.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;
rect = (SDL_Rect){(win_width - new_width) / 2, (win_height - new_height) /2,
new_width, new_height};
if (renderer) {
SDL_RenderSetViewport(renderer, &rect);
}
else {
glViewport(rect.x, rect.y, rect.w, rect.h);
}
}
/* Does NOT check for bounds! */
static void draw_char(uint32_t *buffer, unsigned char ch, uint32_t color)
{
if (ch < ' ' || ch > font_max) {
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;
}
}
static void draw_unbordered_text(uint32_t *buffer, unsigned x, unsigned y, const char *string, uint32_t color)
{
unsigned orig_x = x;
while (*string) {
if (*string == '\n') {
x = orig_x;
y += GLYPH_HEIGHT + 4;
string++;
continue;
}
if (x > 160 - GLYPH_WIDTH || y == 0 || y > 144 - GLYPH_HEIGHT) {
break;
}
draw_char(&buffer[x + 160 * y], *string, color);
x += GLYPH_WIDTH;
string++;
}
}
static void draw_text(uint32_t *buffer, unsigned x, unsigned y, const char *string, uint32_t color, uint32_t 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);
}
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);
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;
}
}
struct menu_item {
const char *string;
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;
static unsigned current_selection = 0;
static enum {
SHOWING_DROP_MESSAGE,
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;
}
static unsigned current_help_page = 0;
static void item_help(unsigned index)
{
current_help_page = 0;
gui_state = SHOWING_HELP;
}
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},
{"Exit", item_exit},
{NULL,}
};
static const struct menu_item *const nonpaused_menu = &paused_menu[1];
const char *current_scaling_mode(unsigned index)
{
return (const char *[]){"Fill Entire Window", "Retain Aspect Ratio", "Retain Integer Factor"}
[configuration.scaling_mode];
}
const char *current_color_correction_mode(unsigned index)
{
return (const char *[]){"Disabled", "Correct Color Curves", "Emulate Hardware", "Preserve Brightness"}
[configuration.color_correction_mode];
}
void cycle_scaling(unsigned index)
{
configuration.scaling_mode++;
if (configuration.scaling_mode == GB_SDL_SCALING_MAX) {
configuration.scaling_mode = 0;
}
update_viewport();
render_texture(NULL, NULL);
}
void cycle_scaling_backwards(unsigned index)
{
if (configuration.scaling_mode == 0) {
configuration.scaling_mode = GB_SDL_SCALING_MAX - 1;
}
else {
configuration.scaling_mode--;
}
update_viewport();
render_texture(NULL, NULL);
}
static void cycle_color_correction(unsigned index)
{
if (configuration.color_correction_mode == GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS) {
configuration.color_correction_mode = GB_COLOR_CORRECTION_DISABLED;
}
else {
configuration.color_correction_mode++;
}
}
static void cycle_color_correction_backwards(unsigned index)
{
if (configuration.color_correction_mode == GB_COLOR_CORRECTION_DISABLED) {
configuration.color_correction_mode = GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS;
}
else {
configuration.color_correction_mode--;
}
}
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)
{
current_menu = root_menu;
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,}
};
static void enter_graphics_menu(unsigned index)
{
current_menu = graphics_menu;
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]);
}
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;
}
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,};
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 (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);
}
}
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;
}
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 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},
{"Back", return_to_root_menu},
{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)
{
if (joystick && !SDL_NumJoysticks()) {
if (controller) {
SDL_GameControllerClose(controller);
controller = NULL;
joystick = NULL;
}
else {
SDL_JoystickClose(joystick);
joystick = NULL;
}
}
else if (!joystick && SDL_NumJoysticks()) {
if ((controller = SDL_GameControllerOpen(0))){
joystick = SDL_GameControllerGetJoystick(controller);
}
else {
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"));
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];
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;
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;
}
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};
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;
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.axis == 0) {
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) {
exit(0);
}
else {
pending_command = GB_SDL_QUIT_COMMAND;
return;
}
}
case SDL_WINDOWEVENT: {
if (event.window.event == SDL_WINDOWEVENT_RESIZED) {
update_viewport();
render_texture(NULL, NULL);
}
break;
}
case SDL_DROPFILE: {
set_filename(event.drop.file, true);
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) {
return;
}
else {
if (gui_state == SHOWING_DROP_MESSAGE) {
gui_state = SHOWING_MENU;
}
else if (gui_state == SHOWING_MENU) {
gui_state = SHOWING_DROP_MESSAGE;
}
current_selection = 0;
current_menu = root_menu;
should_render = true;
}
}
if (gui_state == SHOWING_MENU) {
if (event.key.keysym.scancode == SDL_SCANCODE_DOWN && current_menu[current_selection + 1].string) {
current_selection++;
should_render = true;
}
else if (event.key.keysym.scancode == SDL_SCANCODE_UP && current_selection) {
current_selection--;
should_render = true;
}
else if (event.key.keysym.scancode == SDL_SCANCODE_RETURN) {
if (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);
}
return;
}
should_render = true;
}
else {
return;
}
}
else if (event.key.keysym.scancode == SDL_SCANCODE_RIGHT && current_menu[current_selection].backwards_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_selection);
should_render = true;
}
}
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;
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;
}
render_texture(pixels, NULL);
}
} while (SDL_WaitEvent(&event));
}

57
SDL/gui.h Normal file
View File

@ -0,0 +1,57 @@
#ifndef gui_h
#define gui_h
#include <SDL2/SDL.h>
#include <Core/gb.h>
#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,
GB_SDL_SCALING_KEEP_RATIO,
GB_SDL_SCALING_INTEGER_FACTOR,
GB_SDL_SCALING_MAX,
};
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,
GB_SDL_QUIT_COMMAND,
};
extern enum pending_command pending_command;
extern unsigned command_parameter;
typedef struct {
SDL_Scancode keys[9];
GB_color_correction_mode_t color_correction_mode;
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;
char filter[32];
} configuration_t;
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

View File

@ -2,8 +2,9 @@
#include <signal.h>
#include <SDL2/SDL.h>
#include <Core/gb.h>
#include "utils.h"
#include "gui.h"
#include "shader.h"
#ifndef _WIN32
#define AUDIO_FREQUENCY 96000
@ -12,50 +13,26 @@
#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;
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;
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) {
SDL_free(filename);
}
filename = (char *) new_filename;
should_free_filename = new_should_free;
}
static SDL_AudioSpec want_aspec, have_aspec;
static char *captured_log = NULL;
@ -97,62 +74,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 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__
@ -165,15 +86,11 @@ 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: {
if (should_free_filename) {
SDL_free(filename);
}
filename = event.drop.file;
should_free_filename = true;
set_filename(event.drop.file, true);
pending_command = GB_SDL_NEW_FILE_COMMAND;
break;
}
@ -182,36 +99,105 @@ 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 == 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) {
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_JOYAXISMOTION:
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 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);
}
break;
case SDL_KEYDOWN:
switch (event.key.keysym.sym) {
case SDLK_c:
switch (event.key.keysym.scancode) {
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);
}
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
@ -223,27 +209,11 @@ static void handle_events(GB_gameboy_t *gb)
}
break;
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:
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Help", help, window);
#endif
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;
@ -256,34 +226,15 @@ static void handle_events(GB_gameboy_t *gb)
break;
}
case SDL_KEYUP: // Fallthrough
switch (event.key.keysym.sym) {
case SDLK_RIGHT:
GB_set_key_state(gb, GB_KEY_RIGHT, event.type == SDL_KEYDOWN);
break;
case SDLK_LEFT:
GB_set_key_state(gb, GB_KEY_LEFT, event.type == SDL_KEYDOWN);
break;
case SDLK_UP:
GB_set_key_state(gb, GB_KEY_UP, event.type == SDL_KEYDOWN);
break;
case SDLK_DOWN:
GB_set_key_state(gb, GB_KEY_DOWN, event.type == SDL_KEYDOWN);
break;
case SDLK_x:
GB_set_key_state(gb, GB_KEY_A, event.type == SDL_KEYDOWN);
break;
case SDLK_z:
GB_set_key_state(gb, GB_KEY_B, event.type == SDL_KEYDOWN);
break;
case SDLK_BACKSPACE:
GB_set_key_state(gb, GB_KEY_SELECT, event.type == SDL_KEYDOWN);
break;
case SDLK_RETURN:
GB_set_key_state(gb, GB_KEY_START, event.type == SDL_KEYDOWN);
break;
case SDLK_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:
@ -294,10 +245,16 @@ 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);
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);
}
@ -309,8 +266,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);
@ -361,12 +320,17 @@ 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;
}
static void run(void)
{
pending_command = GB_SDL_NO_COMMAND;
restart:
if (GB_is_inited(&gb)) {
GB_switch_model_and_reset(&gb, !dmg);
@ -380,9 +344,11 @@ 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);
GB_set_highpass_filter_mode(&gb, configuration.highpass_mode);
}
bool error = false;
@ -432,7 +398,18 @@ restart:
pending_command = GB_SDL_NO_COMMAND;
}
}
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
@ -464,15 +441,35 @@ 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);
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);
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;
@ -490,60 +487,30 @@ usage:
SDL_OpenAudio(&want_aspec, &have_aspec);
/* Start Audio */
SDL_PauseAudio(false);
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;
}
}
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);
}
start:
atexit(save_configuration);
if (!init_shader_with_name(&shader, configuration.filter)) {
init_shader_with_name(&shader, "NearestNeighbor");
}
update_viewport();
if (filename == NULL) {
run_gui(false);
}
SDL_PauseAudio(false);
run(); // Never returns
return 0;
}

34
SDL/opengl_compat.c Normal file
View File

@ -0,0 +1,34 @@
#define GL_GLEXT_PROTOTYPES
#include <SDL2/SDL_opengl.h>
#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

45
SDL/opengl_compat.h Normal file
View File

@ -0,0 +1,45 @@
#ifndef opengl_compat_h
#define opengl_compat_h
#define GL_GLEXT_PROTOTYPES
#include <SDL2/SDL_opengl.h>
#include <SDL2/SDL_video.h>
#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 */

191
SDL/shader.c Normal file
View File

@ -0,0 +1,191 @@
#include <stdio.h>
#include <string.h>
#include "shader.h"
#include "utils.h"
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)
{
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,};
static signed long 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);
}

23
SDL/shader.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef shader_h
#define shader_h
#include "opengl_compat.h"
#include <stdbool.h>
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 */

View File

@ -1,62 +1,24 @@
#include <SDL2/SDL.h>
#include <stdio.h>
#include <string.h>
#ifdef __APPLE__
#include <mach-o/dyld.h>
#endif
#ifdef _WIN32
#include <direct.h>
#include <windows.h>
#endif
#ifdef __linux__
#include <assert.h>
#include <unistd.h>
#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;
}

View File

@ -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);
}

1
Windows/inttypes.h Executable file
View File

@ -0,0 +1 @@
#include <stdint.h>