diff --git a/gtk3/main.c b/gtk3/main.c index 9e66560..be6e436 100644 --- a/gtk3/main.c +++ b/gtk3/main.c @@ -1,7 +1,6 @@ #define G_LOG_USE_STRUCTURED #include -#include #include #include #include @@ -9,7 +8,9 @@ #include +#include "types.h" #include "config.h" +#include "util.h" #include "shader.h" #include "check_menu_radio_group.h" @@ -29,113 +30,12 @@ #define BUTTON_MASK_LEFT 0x40 #define BUTTON_MASK_RIGHT 0x80 -#define tileset_buffer_length 256 * 192 * 4 -#define tilemap_buffer_length 256 * 256 * 4 - #define str(x) #x #define xstr(x) str(x) #define get_object(id) gtk_builder_get_object(gui_data.builder, id) #define builder_get(type, id) type(get_object(id)) #define action_set_enabled(map, name, value) g_simple_action_set_enabled(G_SIMPLE_ACTION(g_action_map_lookup_action(G_ACTION_MAP(map), name)), value); -typedef struct{ - int16_t x, y; - uint16_t w, h; -} Rect; - -typedef struct GuiData { - struct CliOptionData { - gchar *config_path; - gchar *boot_rom_path; - gboolean fullscreen; - GB_model_t model; - gboolean force_software_renderer; - } cli_options; - - GFile *file; - gint sample_rate; - GDateTime *config_modification_date; - - char *battery_save_path; - char *cheats_save_path; - - GB_model_t prev_model; - - const GThread *main_thread; - volatile bool running; - volatile bool stopping; - volatile bool stopped; - - // GTK pointers - GtkApplication *main_application; - GtkBuilder *builder; - GtkApplicationWindow *main_window; - GtkBox *main_window_container; - GtkGLArea *gl_area; - GtkDrawingArea *fallback_canvas; - GtkWindow *preferences; - GtkWindow *vram_viewer; - GtkWindow *memory_viewer; - GtkWindow *console; - GtkWindow *printer; - - // Debugger state - GtkTextBuffer *pending_console_output; - gboolean in_sync_input; - gchar *last_console_input; - gboolean log_to_sidebar; - gboolean should_clear_sidebar; - GMutex debugger_input_mutex; - GCond debugger_input_cond; - GRecMutex console_output_lock; - GPtrArray *debugger_input_queue; - bool vram_viewer_visible; - bool vram_viewer_updating; - gchar *vram_viewer_active_tab; - gboolean vram_viewer_is_cgb; - uint8_t vram_viewer_palette_data[16][0x40]; - GB_oam_info_t oam_info[40]; - uint16_t oam_count; - uint8_t oam_height; - uint32_t tileset_buffer[tileset_buffer_length]; - uint32_t tilemap_buffer[tilemap_buffer_length]; - GMutex tileset_buffer_mutex; - GMutex tilemap_buffer_mutex; - Rect scroll_rect; - - // Audio and video - bool audio_initialized; - uint32_t *image_buffers[3]; - unsigned char current_buffer; - Rect viewport; - bool border_mode_changed; - bool is_fullscreen; - bool supports_gl; - shader_t shader; - unsigned last_screen_width; - unsigned last_screen_height; - - // Fast forward / slow motion - bool underclock_down; - bool rewind_down; - bool do_rewind; - bool rewind_paused; - bool turbo_down; - double clock_mutliplier; - double analog_clock_multiplier; - bool analog_clock_multiplier_valid; - - // Input - uint8_t pressed_buttons; - struct Controller_t { - SDL_GameController *controller; - SDL_Haptic *haptic; - bool ignore_rumble; - } *controllers; - unsigned controller_count; - struct Controller_t *last_used_controller; // Used for rumble -} GuiData; - // Initialize the GuiData static GuiData gui_data = { .cli_options = { @@ -168,44 +68,9 @@ static GuiData gui_data = { .clock_mutliplier = 1.0, .analog_clock_multiplier = 1.0, }; + GB_gameboy_t gb; -typedef enum { - INPUT_UP, - INPUT_DOWN, - INPUT_LEFT, - INPUT_RIGHT, - INPUT_A, - INPUT_B, - INPUT_START, - INPUT_SELECT, - - INPUT_TURBO, - INPUT_REWIND, - INPUT_SLOWDOWN, - - INPUT_FULLSCREEN, -} input_names_t; - -static unsigned key_map[] = { - [INPUT_UP] = GDK_KEY_w, - [INPUT_LEFT] = GDK_KEY_a, - [INPUT_DOWN] = GDK_KEY_s, - [INPUT_RIGHT] = GDK_KEY_d, - - [INPUT_A] = GDK_KEY_l, - [INPUT_B] = GDK_KEY_k, - - [INPUT_START] = GDK_KEY_h, - [INPUT_SELECT] = GDK_KEY_g, - - [INPUT_TURBO] = GDK_KEY_space, - [INPUT_REWIND] = GDK_KEY_Tab, - [INPUT_SLOWDOWN] = GDK_KEY_Shift_L, - - [INPUT_FULLSCREEN] = GDK_KEY_F11, -}; - // Forward declarations of the actions static void activate_open(GSimpleAction *action, GVariant *parameter, gpointer app); static void activate_close(GSimpleAction *action, GVariant *parameter, gpointer app); @@ -253,52 +118,6 @@ static const GActionEntry app_entries[] = { { "toggle_mute", NULL, NULL, "false", on_mute_changed }, }; - -static void replace_extension(const char *src, size_t length, char *dest, const char *ext) { - memcpy(dest, src, length); - dest[length] = 0; - - /* Remove extension */ - for (size_t i = length; i--;) { - if (dest[i] == '/') break; - if (dest[i] == '.') { - dest[i] = 0; - break; - } - } - - /* Add new extension */ - strcat(dest, ext); -} - -static double clamp_double(double min, double max, double value) { - if (value < min) return min; - if (value > max) return max; - return value; -} - -static double max_double(double a, double b) { - if (a > b) return a; - return b; -} - -static double min_double(double a, double b) { - if (a < b) return a; - return b; -} - -static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) { - return 0xFF000000 | (r << 16) | (g << 8) | b; -} - -static uint32_t convert_color(uint16_t color) { - const uint8_t r = ((uint16_t)(color & 0x1F) * 255) / 31; - const uint8_t g = ((uint16_t)((color >> 5) & 0x1F) * 255) / 31; - const uint8_t b = ((uint16_t)((color >> 10) & 0x1F) * 255) / 31; - - return (r << 16) | (g << 8) | b; -} - static void palette_color_data_func(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data_ptr) { const gchar *title = gtk_tree_view_column_get_title(col); const uint8_t color_index = g_ascii_strtoll(&title[6], NULL, 10); @@ -405,51 +224,6 @@ static gint handle_local_options(GApplication *app, GVariantDict *options, gpoin return -1; } -// The main function for the OpenGL version check workaround -void gl_check_realize(GtkWidget *w, gpointer user_data_ptr) { - gboolean *result = (gboolean *) user_data_ptr; - - GError *error = NULL; - GdkWindow *gdk_window = gtk_widget_get_window(w); - GdkGLContext *context = gdk_window_create_gl_context(gdk_window, &error); - - if (error != NULL) { - g_warning("Failed to create context: %s", error->message); - g_error_free(error); - *result = false; - } - else { - gdk_gl_context_make_current(context); - int version = epoxy_gl_version(); - - g_object_run_dispose(G_OBJECT(context)); - g_object_unref(context); - context = NULL; - - gdk_gl_context_clear_current(); - - g_debug("OpenGL version: %d", version); - - *result = version >= 32; - } -} - -// Workaround to figure out if we have proper OpenGL support. -// Otherwise the application would crash after our GtkGlArea is realized -// and the context it uses is a legacy OpenGL 1.4 context because -// GTK3 calls OpenGL 2.0+ functions on it. -gboolean test_gl_support(void) { - gboolean result = false; - - GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - g_signal_connect(window, "realize", G_CALLBACK(gl_check_realize), &result); - gtk_widget_realize(window); - gtk_widget_destroy(window); - window = NULL; - - return result; -} - static gboolean init_controllers(void) { SDL_version compiled; SDL_version linked; @@ -564,14 +338,6 @@ static gboolean init_audio(void) { return gui_data.audio_initialized = true; } -static GB_model_t config_get_model_type(void) { - if (gui_data.cli_options.model != -1) { - return gui_data.cli_options.model; - } - - return config_get_model(); -} - static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample) { if (gui_data.turbo_down) { static unsigned skip = 0; @@ -806,19 +572,6 @@ static char *async_console_input(GB_gameboy_t *gb) { return input; } -GtkWidget *menubar_to_menu(GtkMenuBar *menubar) { - GtkWidget *menu = gtk_menu_new(); - g_autoptr(GList) iter = gtk_container_get_children(GTK_CONTAINER(menubar)); - - while (iter) { - GtkWidget *item = GTK_WIDGET(iter->data); - gtk_widget_reparent(item, menu); - iter = iter->next; - } - - return menu; -} - // Creating these items in the UI defintion files was buggy in some desktop // environments and the manual call of `g_signal_connect` was needed anyway // because the UI definition can’t define string arguments for signal handlers. @@ -1460,7 +1213,7 @@ static void load_boot_rom(GB_gameboy_t *gb, GB_boot_rom_t type) { static void init(void) { if (GB_is_inited(&gb)) return; - GB_init(&gb, config_get_model_type()); + GB_init(&gb, config_get_model_type(&gui_data)); GB_set_vblank_callback(&gb, vblank); GB_set_pixels_output(&gb, get_current_buffer()); @@ -1487,8 +1240,8 @@ static void init(void) { } static void reset(void) { - g_debug("Reset: %d == %d", config_get_model_type(), gui_data.prev_model); - GB_model_t current_model = config_get_model_type(); + g_debug("Reset: %d == %d", config_get_model_type(&gui_data), gui_data.prev_model); + GB_model_t current_model = config_get_model_type(&gui_data); if (gui_data.prev_model == -1 || gui_data.prev_model == current_model) { GB_reset(&gb); @@ -1499,7 +1252,7 @@ static void reset(void) { GB_set_palette(&gb, config_get_monochrome_palette()); - gui_data.prev_model = config_get_model_type(); + gui_data.prev_model = config_get_model_type(&gui_data); // Check SGB -> non-SGB and non-SGB to SGB transitions if (GB_get_screen_width(&gb) != gui_data.last_screen_width || GB_get_screen_height(&gb) != gui_data.last_screen_height) { diff --git a/gtk3/types.h b/gtk3/types.h new file mode 100644 index 0000000..1d7edef --- /dev/null +++ b/gtk3/types.h @@ -0,0 +1,144 @@ +#ifndef types_h +#define types_h + +#include "SDL.h" +#include "shader.h" + +#define tileset_buffer_length 256 * 192 * 4 +#define tilemap_buffer_length 256 * 256 * 4 + +typedef struct{ + int16_t x, y; + uint16_t w, h; +} Rect; + +typedef struct GuiData { + struct CliOptionData { + gchar *config_path; + gchar *boot_rom_path; + gboolean fullscreen; + GB_model_t model; + gboolean force_software_renderer; + } cli_options; + + GFile *file; + gint sample_rate; + GDateTime *config_modification_date; + + char *battery_save_path; + char *cheats_save_path; + + GB_model_t prev_model; + + const GThread *main_thread; + volatile bool running; + volatile bool stopping; + volatile bool stopped; + + // GTK pointers + GtkApplication *main_application; + GtkBuilder *builder; + GtkApplicationWindow *main_window; + GtkBox *main_window_container; + GtkGLArea *gl_area; + GtkDrawingArea *fallback_canvas; + GtkWindow *preferences; + GtkWindow *vram_viewer; + GtkWindow *memory_viewer; + GtkWindow *console; + GtkWindow *printer; + + // Debugger state + GtkTextBuffer *pending_console_output; + gboolean in_sync_input; + gchar *last_console_input; + gboolean log_to_sidebar; + gboolean should_clear_sidebar; + GMutex debugger_input_mutex; + GCond debugger_input_cond; + GRecMutex console_output_lock; + GPtrArray *debugger_input_queue; + bool vram_viewer_visible; + bool vram_viewer_updating; + gchar *vram_viewer_active_tab; + gboolean vram_viewer_is_cgb; + uint8_t vram_viewer_palette_data[16][0x40]; + GB_oam_info_t oam_info[40]; + uint16_t oam_count; + uint8_t oam_height; + uint32_t tileset_buffer[tileset_buffer_length]; + uint32_t tilemap_buffer[tilemap_buffer_length]; + GMutex tileset_buffer_mutex; + GMutex tilemap_buffer_mutex; + Rect scroll_rect; + + // Audio and video + bool audio_initialized; + uint32_t *image_buffers[3]; + unsigned char current_buffer; + Rect viewport; + bool border_mode_changed; + bool is_fullscreen; + bool supports_gl; + shader_t shader; + unsigned last_screen_width; + unsigned last_screen_height; + + // Fast forward / slow motion + bool underclock_down; + bool rewind_down; + bool do_rewind; + bool rewind_paused; + bool turbo_down; + double clock_mutliplier; + double analog_clock_multiplier; + bool analog_clock_multiplier_valid; + + // Input + uint8_t pressed_buttons; + struct Controller_t { + SDL_GameController *controller; + SDL_Haptic *haptic; + bool ignore_rumble; + } *controllers; + unsigned controller_count; + struct Controller_t *last_used_controller; // Used for rumble +} GuiData; + +typedef enum { + INPUT_UP, + INPUT_DOWN, + INPUT_LEFT, + INPUT_RIGHT, + INPUT_A, + INPUT_B, + INPUT_START, + INPUT_SELECT, + + INPUT_TURBO, + INPUT_REWIND, + INPUT_SLOWDOWN, + + INPUT_FULLSCREEN, +} input_names_t; + +static unsigned key_map[] = { + [INPUT_UP] = GDK_KEY_w, + [INPUT_LEFT] = GDK_KEY_a, + [INPUT_DOWN] = GDK_KEY_s, + [INPUT_RIGHT] = GDK_KEY_d, + + [INPUT_A] = GDK_KEY_l, + [INPUT_B] = GDK_KEY_k, + + [INPUT_START] = GDK_KEY_h, + [INPUT_SELECT] = GDK_KEY_g, + + [INPUT_TURBO] = GDK_KEY_space, + [INPUT_REWIND] = GDK_KEY_Tab, + [INPUT_SLOWDOWN] = GDK_KEY_Shift_L, + + [INPUT_FULLSCREEN] = GDK_KEY_F11, +}; + +#endif diff --git a/gtk3/util.c b/gtk3/util.c new file mode 100644 index 0000000..e22430e --- /dev/null +++ b/gtk3/util.c @@ -0,0 +1,114 @@ +#include "util.h" +#include "config.h" +#include + +// Workaround to figure out if we have proper OpenGL support. +// Otherwise the application would crash after our GtkGlArea is realized +// and the context it uses is a legacy OpenGL 1.4 context because +// GTK3 calls OpenGL 2.0+ functions on it. +bool test_gl_support(void) { + gboolean result = false; + + GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + g_signal_connect(window, "realize", G_CALLBACK(gl_check_realize), &result); + gtk_widget_realize(window); + gtk_widget_destroy(window); + window = NULL; + + return result; +} + +// The main function for the OpenGL version check workaround +void gl_check_realize(GtkWidget *w, gpointer user_data_ptr) { + gboolean *result = (gboolean *) user_data_ptr; + + GError *error = NULL; + GdkWindow *gdk_window = gtk_widget_get_window(w); + GdkGLContext *context = gdk_window_create_gl_context(gdk_window, &error); + + if (error != NULL) { + g_warning("Failed to create context: %s", error->message); + g_error_free(error); + *result = false; + } + else { + gdk_gl_context_make_current(context); + int version = epoxy_gl_version(); + + g_object_run_dispose(G_OBJECT(context)); + g_object_unref(context); + context = NULL; + + gdk_gl_context_clear_current(); + + g_debug("OpenGL version: %d", version); + + *result = version >= 32; + } +} + +void replace_extension(const char *src, size_t length, char *dest, const char *ext) { + memcpy(dest, src, length); + dest[length] = 0; + + /* Remove extension */ + for (size_t i = length; i--;) { + if (dest[i] == '/') break; + if (dest[i] == '.') { + dest[i] = 0; + break; + } + } + + /* Add new extension */ + strcat(dest, ext); +} + +double clamp_double(double min, double max, double value) { + if (value < min) return min; + if (value > max) return max; + return value; +} + +double max_double(double a, double b) { + if (a > b) return a; + return b; +} + +double min_double(double a, double b) { + if (a < b) return a; + return b; +} + +uint32_t convert_color(uint16_t color) { + const uint8_t r = ((uint16_t)(color & 0x1F) * 255) / 31; + const uint8_t g = ((uint16_t)((color >> 5) & 0x1F) * 255) / 31; + const uint8_t b = ((uint16_t)((color >> 10) & 0x1F) * 255) / 31; + + return (r << 16) | (g << 8) | b; +} + +uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) { + return 0xFF000000 | (r << 16) | (g << 8) | b; +} + +GB_model_t config_get_model_type(GuiData *gui_data) { + if (gui_data->cli_options.model != -1) { + return gui_data->cli_options.model; + } + + return config_get_model(); +} + +GtkWidget *menubar_to_menu(GtkMenuBar *menubar) { + GtkWidget *menu = gtk_menu_new(); + g_autoptr(GList) iter = gtk_container_get_children(GTK_CONTAINER(menubar)); + + while (iter) { + GtkWidget *item = GTK_WIDGET(iter->data); + gtk_widget_reparent(item, menu); + iter = iter->next; + } + + return menu; +} diff --git a/gtk3/util.h b/gtk3/util.h new file mode 100644 index 0000000..1d918e9 --- /dev/null +++ b/gtk3/util.h @@ -0,0 +1,25 @@ +#ifndef util_h +#define util_h + +#include +#include +#include +#include "types.h" + +bool test_gl_support(void); +void gl_check_realize(GtkWidget *w, gpointer user_data_ptr); + +void replace_extension(const char *src, size_t length, char *dest, const char *ext); + +double clamp_double(double min, double max, double value); +double max_double(double a, double b); +double min_double(double a, double b); + +uint32_t convert_color(uint16_t color); +uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b); + +GB_model_t config_get_model_type(GuiData *gui_data); + +GtkWidget *menubar_to_menu(GtkMenuBar *menubar); + +#endif \ No newline at end of file