[GTK3] Use frame buffering like the Cocoa frontend

This commit is contained in:
Maximilian Mader 2019-09-23 00:47:42 +02:00
parent 4d4d272a5c
commit 6e65945c35
Signed by: Max
GPG Key ID: F71D56A3151C4FB3
3 changed files with 165 additions and 142 deletions

View File

@ -10,22 +10,6 @@
#define str(x) #x #define str(x) #x
#define xstr(x) str(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 { typedef struct UserData {
bool fullscreen; bool fullscreen;
GFile *file; GFile *file;
@ -38,8 +22,43 @@ typedef struct{
static void run(UserData *user_data); 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; 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) { void render_texture(void *pixels, void *previous) {
static void *_pixels = NULL; static void *_pixels = NULL;
if (pixels) { if (pixels) {
@ -50,36 +69,47 @@ void render_texture(void *pixels, void *previous) {
glClear(GL_COLOR_BUFFER_BIT); 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); 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) { void update_viewport(void) {
int win_width = gtk_widget_get_allocated_width(GTK_WIDGET(gl_area)); int win_width = gtk_widget_get_allocated_width(GTK_WIDGET(gl_area));
int win_height = gtk_widget_get_allocated_height(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 x_factor = win_width / (double) GB_get_screen_width(&gb);
double y_factor = win_height / (double) GB_get_screen_height(&gb); double y_factor = win_height / (double) GB_get_screen_height(&gb);
if (true /*configuration.scaling_mode == GB_SDL_SCALING_INTEGER_FACTOR*/) { if (true /*configuration.scaling_mode == GB_SDL_SCALING_INTEGER_FACTOR*/) {
x_factor = (int)(x_factor); x_factor = (int)(x_factor);
y_factor = (int)(y_factor); y_factor = (int)(y_factor);
} }
/*if (configuration.scaling_mode != GB_SDL_SCALING_ENTIRE_WINDOW) { /*if (configuration.scaling_mode != GB_SDL_SCALING_ENTIRE_WINDOW) {
if (x_factor > y_factor) { if (x_factor > y_factor) {
x_factor = y_factor; x_factor = y_factor;
} }
else { else {
y_factor = x_factor; y_factor = x_factor;
} }
}*/ }*/
unsigned new_width = x_factor * GB_get_screen_width(&gb); unsigned new_width = x_factor * GB_get_screen_width(&gb);
unsigned new_height = y_factor * GB_get_screen_height(&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}; 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); glViewport(rect.x, rect.y, rect.w, rect.h);
} }
// Determines if a ComboBox entry should be converted into a separator. // 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_print("Active: %s", gtk_combo_box_get_active_id(box));
} }
G_MODULE_EXPORT void gl_draw(GtkWidget *window) { G_MODULE_EXPORT void gl_init() {
render_texture(active_pixel_buffer, NULL);
}
G_MODULE_EXPORT void gl_init(GtkWidget *window) {
const char *renderer; 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); gtk_gl_area_make_current(gl_area);
if (gtk_gl_area_get_error(gl_area) != NULL) { if (gtk_gl_area_get_error(gl_area) != NULL) {
return; 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 // This functions gets called immediately after registration of the GApplication
static void startup(GApplication *app, gpointer user_data_gptr) { static void startup(GApplication *app, gpointer user_data_gptr) {
// UserData *user_data = 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")); GtkWindow *vram_viewer = GTK_WINDOW(get_object("vram_viewer"));
set_combo_box_row_separator_func(GTK_CONTAINER(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 … // Handle the whole menubar situation …
if (show_menubar()) { if (show_menubar()) {
// Use a standard window as main window // Show a classic menubar
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);
GMenuModel *menubar = get_menu_model(app, "menubar"); GMenuModel *menubar = get_menu_model(app, "menubar");
gtk_application_set_menubar(GTK_APPLICATION(app), menubar); gtk_application_set_menubar(GTK_APPLICATION(app), menubar);
} }
else { else {
// Use a window with a custom title bar // Attach a custom title bar
main_window = GTK_WINDOW(get_object("main_with_titlebar")); GtkWidget *titlebar = GTK_WIDGET(gtk_builder_get_object(builder, "main_header_bar"));
gtk_widget_destroy(GTK_WIDGET(get_object("main_no_titlebar"))); gtk_window_set_titlebar(GTK_WINDOW(main_window), titlebar);
// Disable menubar // Disable menubar
gtk_application_set_menubar(GTK_APPLICATION(app), NULL); 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_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 // Define a set of window icons
GList *icon_list = NULL; GList *icon_list = NULL;
@ -270,7 +294,7 @@ static void startup(GApplication *app, gpointer user_data_gptr) {
} }
// Let GTK choose the proper icon // 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 // Add missing information to the about dialog
GtkAboutDialog *about_dialog = GTK_ABOUT_DIALOG(get_object("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; UserData *user_data = user_data_gptr;
if (user_data->fullscreen) { 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); 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)); gtk_widget_show_all(GTK_WIDGET(main_window));
update_viewport();
run(user_data); 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; return color;
} }
static void vblank(GB_gameboy_t *gb) { static void update_window_geometry() {
// render_texture(active_pixel_buffer, NULL); // Set size hints
GdkGeometry hints;
hints.min_width = GB_get_screen_width(&gb);
hints.min_height = GB_get_screen_height(&gb);
while (gtk_events_pending()) { gtk_window_set_geometry_hints(
gtk_main_iteration(); GTK_WINDOW(main_window),
} NULL,
&hints,
(GdkWindowHints)(GDK_HINT_MIN_SIZE)
);
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) { static void run(UserData *user_data) {
GB_model_t prev_model = GB_get_model(&gb);
GB_model_t model = GB_MODEL_CGB_E; GB_model_t model = GB_MODEL_CGB_E;
if (GB_is_inited(&gb)) { if (GB_is_inited(&gb)) {
GB_switch_model_and_reset(&gb, model); 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 { else {
GB_init(&gb, model); GB_init(&gb, model);
update_window_geometry();
GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); 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_rgb_encode_callback(&gb, rgb_encode);
// GB_set_sample_rate(&gb, have_aspec.freq); // GB_set_sample_rate(&gb, have_aspec.freq);
// GB_set_color_correction_mode(&gb, configuration.color_correction_mode); // 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, "activate", G_CALLBACK(activate), &user_data);
g_signal_connect(app, "open", G_CALLBACK(open), &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 // Start our GApplication main loop
int status = g_application_run(G_APPLICATION(app), argc, argv); int status = g_application_run(G_APPLICATION(app), argc, argv);
g_object_unref(app); g_object_unref(app);

View File

@ -965,65 +965,24 @@ Maximilian Mader https://github.com/max-m</property>
</object> </object>
</child> </child>
</object> </object>
<object class="GtkApplicationWindow" id="main_no_titlebar"> <object class="GtkHeaderBar" id="main_header_bar">
<property name="app_paintable">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="title" translatable="yes">SameBoy</property> <property name="has_subtitle">False</property>
<property name="default_width">320</property> <property name="show_close_button">True</property>
<property name="default_height">288</property>
<child type="titlebar">
<placeholder/>
</child>
<child> <child>
<object class="GtkGLArea"> <object class="GtkMenuButton" id="hamburger_button">
<property name="visible">True</property> <property name="visible">True</property>
<property name="app_paintable">True</property> <property name="can_focus">True</property>
<property name="can_focus">False</property> <property name="receives_default">True</property>
<signal name="realize" handler="gl_init" object="main_no_titlebar" swapped="yes"/> <property name="direction">none</property>
<signal name="render" handler="gl_draw" object="main_no_titlebar" swapped="yes"/>
<signal name="unrealize" handler="gl_finish" object="main_no_titlebar" swapped="yes"/>
</object>
</child>
</object>
<object class="GtkApplicationWindow" id="main_with_titlebar">
<property name="app_paintable">True</property>
<property name="can_focus">True</property>
<property name="has_focus">True</property>
<property name="is_focus">True</property>
<property name="title" translatable="yes">SameBoy</property>
<property name="default_width">320</property>
<property name="default_height">288</property>
<child type="titlebar">
<object class="GtkHeaderBar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="has_subtitle">False</property>
<property name="show_close_button">True</property>
<child> <child>
<object class="GtkMenuButton" id="hamburger_button"> <placeholder/>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="direction">none</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="pack_type">end</property>
</packing>
</child> </child>
</object> </object>
</child> <packing>
<child> <property name="pack_type">end</property>
<object class="GtkGLArea"> </packing>
<property name="visible">True</property>
<property name="app_paintable">True</property>
<property name="can_focus">False</property>
<signal name="realize" handler="gl_init" object="main_with_titlebar" swapped="yes"/>
<signal name="render" handler="gl_draw" object="main_with_titlebar" swapped="yes"/>
<signal name="unrealize" handler="gl_finish" object="main_with_titlebar" swapped="yes"/>
</object>
</child> </child>
</object> </object>
<object class="GtkWindow" id="memory_viewer"> <object class="GtkWindow" id="memory_viewer">

View File

@ -75,7 +75,6 @@ bool init_shader_with_name(shader_t *shader, const char *name)
GBytes *master_shader_f; GBytes *master_shader_f;
static const gchar *master_shader_code; static const gchar *master_shader_code;
static gsize master_shader_code_size; static gsize master_shader_code_size;
static char final_shader_code[0x10801] = {0,}; static char final_shader_code[0x10801] = {0,};
static signed long filter_token_location = 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) { if (!master_shader_f) {
g_printerr("Failed to load master shader: %s", error->message); g_printerr("Failed to load master shader: %s", error->message);
g_error_free(error);
return false; return false;
} }
filter_token_location = strstr(master_shader_code, "{filter}") - master_shader_code; filter_token_location = strstr(master_shader_code, "{filter}") - master_shader_code;
if (filter_token_location < 0) { if (filter_token_location < 0) {
g_error_free(error);
return false; 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); GBytes *shader_f = g_resources_lookup_data(shader_path, G_RESOURCE_LOOKUP_FLAGS_NONE, &error);
if (!shader_f) { if (!shader_f) {
g_printerr("Failed to load shader \"%s\": %s", shader_path, error->message); g_printerr("Failed to load shader \"%s\": %s", shader_path, error->message);
g_error_free(error);
return false; return false;
} }
@ -112,6 +115,8 @@ bool init_shader_with_name(shader_t *shader, const char *name)
strcat(final_shader_code + filter_token_location, strcat(final_shader_code + filter_token_location,
master_shader_code + filter_token_location + sizeof("{filter}") - 1); master_shader_code + filter_token_location + sizeof("{filter}") - 1);
g_bytes_unref(shader_f);
shader->program = create_program(vertex_shader, final_shader_code); shader->program = create_program(vertex_shader, final_shader_code);
// Attributes // Attributes