[GTK3] Use frame buffering like the Cocoa frontend
This commit is contained in:
parent
2ea8858dfc
commit
3c29a33043
235
gtk3/main.c
235
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);
|
||||
|
@ -965,65 +965,24 @@ Maximilian Mader https://github.com/max-m</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkApplicationWindow" id="main_no_titlebar">
|
||||
<property name="app_paintable">True</property>
|
||||
<object class="GtkHeaderBar" id="main_header_bar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">SameBoy</property>
|
||||
<property name="default_width">320</property>
|
||||
<property name="default_height">288</property>
|
||||
<child type="titlebar">
|
||||
<placeholder/>
|
||||
</child>
|
||||
<property name="has_subtitle">False</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<child>
|
||||
<object class="GtkGLArea">
|
||||
<object class="GtkMenuButton" id="hamburger_button">
|
||||
<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_no_titlebar" swapped="yes"/>
|
||||
<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>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="direction">none</property>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="hamburger_button">
|
||||
<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>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkGLArea">
|
||||
<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>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkWindow" id="memory_viewer">
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user