From 3c29a33043f2b58587a808dfa1d65d3a76c1e958 Mon Sep 17 00:00:00 2001 From: Maximilian Mader Date: Mon, 23 Sep 2019 00:47:42 +0200 Subject: [PATCH] [GTK3] Use frame buffering like the Cocoa frontend --- gtk3/main.c | 235 ++++++++++++++++++++++-------------- gtk3/resources/ui/window.ui | 65 ++-------- gtk3/shader.c | 7 +- 3 files changed, 165 insertions(+), 142 deletions(-) diff --git a/gtk3/main.c b/gtk3/main.c index f2fa267..2f7df23 100644 --- a/gtk3/main.c +++ b/gtk3/main.c @@ -10,22 +10,6 @@ #define str(x) #x #define xstr(x) str(x) -GtkBuilder *builder; -GtkWindow *main_window; -GtkGLArea *gl_area; -shader_t shader; - -GB_gameboy_t gb; -static bool paused = false; -static uint32_t pixel_buffer_1[256 * 224], pixel_buffer_2[256 * 224]; -static uint32_t *active_pixel_buffer = pixel_buffer_1, *previous_pixel_buffer = pixel_buffer_2; -static bool underclock_down = false, rewind_down = false, do_rewind = false, rewind_paused = false, turbo_down = false; -static double clock_mutliplier = 1.0; - -static typeof(free) *free_function = NULL; -static char *battery_save_path_ptr; - - typedef struct UserData { bool fullscreen; GFile *file; @@ -38,8 +22,43 @@ typedef struct{ static void run(UserData *user_data); +GtkBuilder *builder; +GtkApplicationWindow *main_window; +GtkGLArea *gl_area; +shader_t shader; + +GB_gameboy_t gb; +static uint32_t *image_buffers[3]; +static unsigned char current_buffer; + +static bool paused = false; +static bool underclock_down = false, rewind_down = false, do_rewind = false, rewind_paused = false, turbo_down = false; +static double clock_mutliplier = 1.0; +static char *battery_save_path_ptr; static Rect rect; +unsigned char number_of_buffers(void) { + bool should_blend = true; + + return should_blend? 3 : 2; +} + +void flip(void) { + current_buffer = (current_buffer + 1) % number_of_buffers(); +} + +uint32_t *get_pixels(void) { + return image_buffers[(current_buffer + 1) % number_of_buffers()]; +} + +uint32_t *get_current_buffer(void) { + return image_buffers[current_buffer]; +} + +uint32_t *get_previous_buffer(void) { + return image_buffers[(current_buffer + 2) % number_of_buffers()]; +} + void render_texture(void *pixels, void *previous) { static void *_pixels = NULL; if (pixels) { @@ -50,36 +69,47 @@ void render_texture(void *pixels, void *previous) { glClear(GL_COLOR_BUFFER_BIT); render_bitmap_with_shader(&shader, _pixels, previous, GB_get_screen_width(&gb), GB_get_screen_height(&gb), rect.x, rect.y, rect.w, rect.h); - gtk_widget_queue_draw(GTK_WIDGET(gl_area)); +} + +static void vblank(GB_gameboy_t *gb) { + flip(); + GB_set_pixels_output(gb, get_pixels()); + + // Queue drawing of the current frame + gtk_gl_area_queue_render(gl_area); + + while (gtk_events_pending()) { + gtk_main_iteration(); + } } void update_viewport(void) { - int win_width = gtk_widget_get_allocated_width(GTK_WIDGET(gl_area)); - int win_height = gtk_widget_get_allocated_height(GTK_WIDGET(gl_area)); - - double x_factor = win_width / (double) GB_get_screen_width(&gb); - double y_factor = win_height / (double) GB_get_screen_height(&gb); - - if (true /*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 * GB_get_screen_width(&gb); - unsigned new_height = y_factor * GB_get_screen_height(&gb); - - rect = (Rect){(win_width - new_width) / 2, (win_height - new_height) / 2, new_width, new_height}; - - glViewport(rect.x, rect.y, rect.w, rect.h); + int win_width = gtk_widget_get_allocated_width(GTK_WIDGET(gl_area)); + int win_height = gtk_widget_get_allocated_height(GTK_WIDGET(gl_area)); + + double x_factor = win_width / (double) GB_get_screen_width(&gb); + double y_factor = win_height / (double) GB_get_screen_height(&gb); + + if (true /*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 * GB_get_screen_width(&gb); + unsigned new_height = y_factor * GB_get_screen_height(&gb); + + rect = (Rect){(win_width - new_width) / 2, (win_height - new_height) / 2, new_width, new_height}; + + glViewport(rect.x, rect.y, rect.w, rect.h); } // Determines if a ComboBox entry should be converted into a separator. @@ -168,30 +198,9 @@ G_MODULE_EXPORT void on_boot_rom_location_changed(GtkWidget *w, gpointer user_da g_print("Active: %s", gtk_combo_box_get_active_id(box)); } -G_MODULE_EXPORT void gl_draw(GtkWidget *window) { - render_texture(active_pixel_buffer, NULL); -} - -G_MODULE_EXPORT void gl_init(GtkWidget *window) { +G_MODULE_EXPORT void gl_init() { const char *renderer; - GList *children = gtk_container_get_children(GTK_CONTAINER(window)); - - while (children) { - if (GTK_IS_GL_AREA(children->data)) { - gl_area = GTK_GL_AREA(children->data); - break; - } - - children = children->next; - } - g_list_free(children); - - if (gl_area == NULL) { - g_printerr("Unable to find GtkGLArea in window\n"); - return; - } - gtk_gl_area_make_current(gl_area); if (gtk_gl_area_get_error(gl_area) != NULL) { return; @@ -205,6 +214,16 @@ G_MODULE_EXPORT void gl_init(GtkWidget *window) { } } +G_MODULE_EXPORT void gl_resize() { + update_viewport(); +} + +G_MODULE_EXPORT void gl_draw() { + render_texture(get_current_buffer(), get_previous_buffer()); +} + +G_MODULE_EXPORT void gl_finish() { } + // This functions gets called immediately after registration of the GApplication static void startup(GApplication *app, gpointer user_data_gptr) { // UserData *user_data = user_data_gptr; @@ -220,24 +239,29 @@ static void startup(GApplication *app, gpointer user_data_gptr) { GtkWindow *vram_viewer = GTK_WINDOW(get_object("vram_viewer")); set_combo_box_row_separator_func(GTK_CONTAINER(vram_viewer)); + // setup main window + main_window = GTK_APPLICATION_WINDOW(gtk_application_window_new(GTK_APPLICATION(app))); + gtk_application_window_set_show_menubar(main_window, true); + + // create our renderer area + gl_area = GTK_GL_AREA(gtk_gl_area_new()); + gtk_gl_area_set_auto_render(gl_area, false); + g_signal_connect(gl_area, "realize", G_CALLBACK(gl_init), NULL); + g_signal_connect(gl_area, "render", G_CALLBACK(gl_draw), NULL); + g_signal_connect(gl_area, "resize", G_CALLBACK(gl_resize), NULL); + g_signal_connect(gl_area, "unrealize", G_CALLBACK(gl_finish), NULL); + gtk_container_add(GTK_CONTAINER(main_window), GTK_WIDGET(gl_area)); + // Handle the whole menubar situation … if (show_menubar()) { - // Use a standard window as main window - main_window = GTK_WINDOW(get_object("main_no_titlebar")); - gtk_widget_destroy(GTK_WIDGET(get_object("main_with_titlebar"))); - - // Hide hamburger button - GtkMenuButton *hamburger_button = GTK_MENU_BUTTON(get_object("hamburger_button")); - gtk_widget_hide(GTK_WIDGET(hamburger_button)); - gtk_widget_set_no_show_all(GTK_WIDGET(hamburger_button), TRUE); - + // Show a classic menubar GMenuModel *menubar = get_menu_model(app, "menubar"); gtk_application_set_menubar(GTK_APPLICATION(app), menubar); } else { - // Use a window with a custom title bar - main_window = GTK_WINDOW(get_object("main_with_titlebar")); - gtk_widget_destroy(GTK_WIDGET(get_object("main_no_titlebar"))); + // Attach a custom title bar + GtkWidget *titlebar = GTK_WIDGET(gtk_builder_get_object(builder, "main_header_bar")); + gtk_window_set_titlebar(GTK_WINDOW(main_window), titlebar); // Disable menubar gtk_application_set_menubar(GTK_APPLICATION(app), NULL); @@ -248,7 +272,7 @@ static void startup(GApplication *app, gpointer user_data_gptr) { gtk_menu_button_set_menu_model(hamburger_button, hamburger_menu); } - gtk_window_set_title(main_window, "SameBoy v" xstr(VERSION)); + gtk_window_set_title(GTK_WINDOW(main_window), "SameBoy v" xstr(VERSION)); // Define a set of window icons GList *icon_list = NULL; @@ -270,7 +294,7 @@ static void startup(GApplication *app, gpointer user_data_gptr) { } // Let GTK choose the proper icon - gtk_window_set_icon_list(main_window, icon_list); + gtk_window_set_icon_list(GTK_WINDOW(main_window), icon_list); // Add missing information to the about dialog GtkAboutDialog *about_dialog = GTK_ABOUT_DIALOG(get_object("about_dialog")); @@ -284,14 +308,13 @@ static void activate(GApplication *app, gpointer user_data_gptr) { UserData *user_data = user_data_gptr; if (user_data->fullscreen) { - gtk_window_fullscreen(main_window); + gtk_window_fullscreen(GTK_WINDOW(main_window)); } g_signal_connect(main_window, "destroy", G_CALLBACK(gtk_main_quit), NULL); - gtk_application_add_window(GTK_APPLICATION(app), main_window); + gtk_application_add_window(GTK_APPLICATION(app), GTK_WINDOW(main_window)); gtk_widget_show_all(GTK_WIDGET(main_window)); - update_viewport(); run(user_data); } @@ -334,25 +357,57 @@ static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) { return color; } -static void vblank(GB_gameboy_t *gb) { - // render_texture(active_pixel_buffer, NULL); +static void update_window_geometry() { + // Set size hints + GdkGeometry hints; + hints.min_width = GB_get_screen_width(&gb); + hints.min_height = GB_get_screen_height(&gb); + + gtk_window_set_geometry_hints( + GTK_WINDOW(main_window), + NULL, + &hints, + (GdkWindowHints)(GDK_HINT_MIN_SIZE) + ); - while (gtk_events_pending()) { - gtk_main_iteration(); - } + gtk_window_resize(GTK_WINDOW(main_window), + GB_get_screen_width(&gb) * 2, + GB_get_screen_height(&gb) * 2 + ); + + if (image_buffers[0]) free(image_buffers[0]); + if (image_buffers[1]) free(image_buffers[1]); + if (image_buffers[2]) free(image_buffers[2]); + + size_t buffer_size = sizeof(image_buffers[0][0]) * GB_get_screen_width(&gb) * GB_get_screen_height(&gb); + + image_buffers[0] = malloc(buffer_size); + image_buffers[1] = malloc(buffer_size); + image_buffers[2] = malloc(buffer_size); } static void run(UserData *user_data) { + GB_model_t prev_model = GB_get_model(&gb); GB_model_t model = GB_MODEL_CGB_E; if (GB_is_inited(&gb)) { GB_switch_model_and_reset(&gb, model); + + GtkRequisition minimum_size; + GtkRequisition natural_size; + gtk_widget_get_preferred_size(GTK_WIDGET(main_window), &minimum_size, &natural_size); + + // Check SGB -> non-SGB and non-SGB to SGB transitions + if (GB_get_screen_width(&gb) != minimum_size.width || GB_get_screen_height(&gb) != minimum_size.height) { + update_window_geometry(); + } } else { GB_init(&gb, model); + update_window_geometry(); GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); - GB_set_pixels_output(&gb, active_pixel_buffer); + GB_set_pixels_output(&gb, get_current_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); @@ -422,6 +477,10 @@ int main(int argc, char *argv[]) { g_signal_connect(app, "activate", G_CALLBACK(activate), &user_data); g_signal_connect(app, "open", G_CALLBACK(open), &user_data); +#ifndef NDEBUG + //gtk_window_set_interactive_debugging(true); +#endif + // Start our GApplication main loop int status = g_application_run(G_APPLICATION(app), argc, argv); g_object_unref(app); diff --git a/gtk3/resources/ui/window.ui b/gtk3/resources/ui/window.ui index 069c702..8879d96 100644 --- a/gtk3/resources/ui/window.ui +++ b/gtk3/resources/ui/window.ui @@ -965,65 +965,24 @@ Maximilian Mader https://github.com/max-m - - True + + True False - SameBoy - 320 - 288 - - - + False + True - + True - True - False - - - - - - - - True - True - True - True - SameBoy - 320 - 288 - - - True - False - False - True + True + True + none - - True - True - True - none - - - - - - end - + - - - - True - True - False - - - - + + end + diff --git a/gtk3/shader.c b/gtk3/shader.c index f2d347f..af9912b 100644 --- a/gtk3/shader.c +++ b/gtk3/shader.c @@ -75,7 +75,6 @@ bool init_shader_with_name(shader_t *shader, const char *name) GBytes *master_shader_f; static const gchar *master_shader_code; static gsize master_shader_code_size; - static char final_shader_code[0x10801] = {0,}; static signed long filter_token_location = 0; @@ -85,11 +84,14 @@ bool init_shader_with_name(shader_t *shader, const char *name) if (!master_shader_f) { g_printerr("Failed to load master shader: %s", error->message); + g_error_free(error); return false; } filter_token_location = strstr(master_shader_code, "{filter}") - master_shader_code; + if (filter_token_location < 0) { + g_error_free(error); return false; } } @@ -100,6 +102,7 @@ bool init_shader_with_name(shader_t *shader, const char *name) GBytes *shader_f = g_resources_lookup_data(shader_path, G_RESOURCE_LOOKUP_FLAGS_NONE, &error); if (!shader_f) { g_printerr("Failed to load shader \"%s\": %s", shader_path, error->message); + g_error_free(error); return false; } @@ -112,6 +115,8 @@ bool init_shader_with_name(shader_t *shader, const char *name) strcat(final_shader_code + filter_token_location, master_shader_code + filter_token_location + sizeof("{filter}") - 1); + g_bytes_unref(shader_f); + shader->program = create_program(vertex_shader, final_shader_code); // Attributes