[GTK3] Somewhat working GtkDrawingArea fallback

This commit is contained in:
Maximilian Mader 2019-09-30 01:49:41 +02:00
parent af7ed7b10e
commit 8385a18233
Signed by: Max
GPG Key ID: F71D56A3151C4FB3
5 changed files with 284 additions and 107 deletions

View File

@ -39,8 +39,10 @@ static void run(GApplication *app, UserData *user_data);
static GtkApplication *main_application;
static GtkBuilder *builder;
static GtkGLArea *gl_area;
static GtkDrawingArea *fallback_canvas;
static GtkApplicationWindow *main_window;
static GtkBox *main_window_container;
static GtkWindow *preferences;
static GtkWindow *vram_viewer;
static GtkWindow *memory_viewer;
@ -54,6 +56,8 @@ static GB_gameboy_t gb;
static uint32_t *image_buffers[3];
static unsigned char current_buffer;
static bool supports_gl;
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;
@ -69,7 +73,7 @@ static const size_t tilemap_buffer_length = 256 * 256 * 4;
static uint32_t tilemap_buffer[tilemap_buffer_length] = {0};
static unsigned char number_of_buffers(void) {
bool should_blend = true;
bool should_blend = !fallback_canvas;
return should_blend? 3 : 2;
}
@ -122,7 +126,12 @@ static void vblank(GB_gameboy_t *gb) {
GB_set_pixels_output(gb, get_pixels());
// Queue drawing of the current frame
gtk_gl_area_queue_render(gl_area);
if (fallback_canvas) {
gtk_widget_queue_draw(GTK_WIDGET(main_window));
}
else if (gl_area) {
gtk_gl_area_queue_render(gl_area);
}
if (vram_viewer_visible) {
// TODO: Only update what is needed
@ -135,8 +144,10 @@ static void vblank(GB_gameboy_t *gb) {
}
static 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));
GtkWidget *w = fallback_canvas ? GTK_WIDGET(fallback_canvas) : GTK_WIDGET(gl_area);
int win_width = gtk_widget_get_allocated_width(w);
int win_height = gtk_widget_get_allocated_height(w);
double x_factor = win_width / (double) GB_get_screen_width(&gb);
double y_factor = win_height / (double) GB_get_screen_height(&gb);
@ -165,7 +176,7 @@ static void update_viewport(void) {
new_height
};
glViewport(rect.x, rect.y, rect.w, rect.h);
if (!fallback_canvas) glViewport(rect.x, rect.y, rect.w, rect.h);
}
// Determines if a ComboBox entry should be converted into a separator.
@ -199,22 +210,6 @@ static void set_combo_box_row_separator_func(GtkContainer *container) {
g_list_free_full(list, NULL);
}
// Returns true if the application should show a menubar
static gboolean show_menubar(void) {
switch (get_show_menubar()) {
case MENUBAR_AUTO: {
GtkSettings *settings = gtk_settings_get_default();
gboolean result;
g_object_get(settings, "gtk-shell-shows-menubar", &result, NULL);
return result;
}
case MENUBAR_SHOW: return true;
case MENUBAR_HIDE: return false;
}
}
// Returns a `GApplication`s `GMenuModel` by ID
// GApplication menus are loaded from `gtk/menus.ui`, `gtk/menus-traditional.ui` and `gtk/menus-common.ui`.
static GMenuModel *get_menu_model(GApplication *app, const char *id) {
@ -332,32 +327,86 @@ G_MODULE_EXPORT void on_show_window(GtkWidget *w, gpointer window) {
gtk_widget_show_all(GTK_WIDGET(window));
}
G_MODULE_EXPORT void gl_init() {
const char *renderer;
G_MODULE_EXPORT gboolean on_draw_fallback(GtkWidget *widget, cairo_t *cr, gpointer data) {
GtkStyleContext *context = gtk_widget_get_style_context(widget);
guint width = gtk_widget_get_allocated_width(widget);
guint height = gtk_widget_get_allocated_height(widget);
gtk_gl_area_make_current(gl_area);
if (gtk_gl_area_get_error(gl_area) != NULL) {
return;
}
guint screen_width = GB_get_screen_width(&gb);
guint screen_height = GB_get_screen_height(&gb);
renderer = (char *) glGetString(GL_RENDERER);
g_print("GtkGLArea on %s\n", renderer ? renderer : "Unknown");
gtk_render_background(context, cr, 0, 0, width, height);
cairo_surface_t *surface = cairo_image_surface_create_for_data(
(unsigned char *) get_current_buffer(),
CAIRO_FORMAT_RGB24,
screen_width,
screen_height,
cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, screen_width)
);
if (config.shader == NULL || !init_shader_with_name(&shader, config.shader)) {
init_shader_with_name(&shader, "NearestNeighbor");
}
cairo_translate(cr, rect.x, rect.y);
cairo_scale(cr, rect.w / screen_width, rect.h / screen_height);
cairo_set_source_surface(cr, surface, 0, 0);
cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
cairo_paint(cr);
return FALSE;
}
G_MODULE_EXPORT void gl_resize() {
G_MODULE_EXPORT void resize() {
update_viewport();
}
static void create_fallback_canvas(void) {
fallback_canvas = GTK_DRAWING_AREA(gtk_drawing_area_new());
g_signal_connect(fallback_canvas, "draw", G_CALLBACK(on_draw_fallback), NULL);
g_signal_connect(fallback_canvas, "size-allocate", G_CALLBACK(resize), NULL);
gtk_box_pack_end(GTK_BOX(main_window_container), GTK_WIDGET(fallback_canvas), TRUE, TRUE, 0);
}
G_MODULE_EXPORT void gl_draw() {
render_texture(get_current_buffer(), get_previous_buffer());
}
G_MODULE_EXPORT void gl_finish() { }
G_MODULE_EXPORT void gl_init(GtkWidget *w) {
GtkGLArea *gl_area = GTK_GL_AREA(w);
g_print("GL_INIT\n");
const char *renderer;
g_print("GL Context: %p\n", gtk_gl_area_get_context(gl_area));
gtk_gl_area_make_current(gl_area);
if (gtk_gl_area_get_error(gl_area) != NULL) {
goto error;
}
renderer = (char *)glGetString(GL_RENDERER);
g_print("GtkGLArea on %s\n", renderer ? renderer : "Unknown");
if (config.shader == NULL || !init_shader_with_name(&shader, config.shader) || !init_shader_with_name(&shader, "NearestNeighbor")) {
GError *error = g_error_new_literal(g_quark_from_string("sameboy-gl-error"), 1, "Failed to initialize shaders");
gtk_gl_area_set_error(gl_area, error);
}
else {
g_signal_connect(gl_area, "render", G_CALLBACK(gl_draw), NULL);
g_signal_connect(gl_area, "resize", G_CALLBACK(resize), NULL);
g_signal_connect(gl_area, "unrealize", G_CALLBACK(gl_finish), NULL);
return;
}
error:
if (gtk_gl_area_get_error(gl_area) != NULL) {
g_printerr("GtkGLArea: %s\n", gtk_gl_area_get_error(gl_area)->message);
}
create_fallback_canvas();
}
G_MODULE_EXPORT void on_vram_viewer_realize() {
vram_viewer_visible = true;
}
@ -390,7 +439,7 @@ G_MODULE_EXPORT gboolean on_draw_vram_viewer_tileset(GtkWidget *widget, cairo_t
return FALSE;
}
G_MODULE_EXPORT gboolean on_vram_viewer_tilemap(GtkWidget *widget, cairo_t *cr, gpointer data) {
G_MODULE_EXPORT gboolean on_draw_vram_viewer_tilemap(GtkWidget *widget, cairo_t *cr, gpointer data) {
guint width, height;
GtkStyleContext *context;
@ -497,10 +546,133 @@ G_MODULE_EXPORT void on_color_menubar_override_changed(GtkWidget *w, gpointer us
config.menubar_override = (gchar *)gtk_combo_box_get_active_id(GTK_COMBO_BOX(w));
}
static void setup_menu(GApplication *app) {
GMenuModel *menubar_model = get_menu_model(app, "menubar");
enum menubar_type_t menubar_type = get_show_menubar();
// Try to use a sane default
if (menubar_type == MENUBAR_AUTO) {
GtkSettings *settings = gtk_settings_get_default();
gboolean show_in_shell;
g_object_get(settings, "gtk-shell-shows-menubar", &show_in_shell, NULL);
const gchar *xdg_current_desktop = g_getenv("XDG_CURRENT_DESKTOP");
const gchar *gdm_session = g_getenv("GDMSESSION");
const gchar *desktop_session = g_getenv("DESKTOP_SESSION");
gchar *desktop = (gchar *)xdg_current_desktop;
if (desktop == NULL || g_str_equal(desktop, "")) desktop = (gchar *)gdm_session;
if (desktop == NULL || g_str_equal(desktop, "")) desktop = (gchar *)desktop_session;
g_print("XDG_CURRENT_DESKTOP: %s\nGDMSESSION: %s\nDESKTOP_SESSION: %s\nChosen value: %s\nShow menu in shell: %d\n", xdg_current_desktop, gdm_session, desktop_session, desktop, show_in_shell);
if (desktop != NULL && show_in_shell) {
menubar_type = MENUBAR_SHOW_IN_SHELL;
}
else if (desktop != NULL && g_str_match_string("GNOME", desktop, FALSE)) {
if (g_str_match_string("GNOME-Flashback", desktop, FALSE) || g_str_match_string("GNOME-Classic", desktop, FALSE)) {
menubar_type = MENUBAR_SHOW_IN_WINDOW;
}
else if (gdm_session != NULL && (g_str_match_string("gnome-classic", gdm_session, FALSE) || g_str_match_string("gnome-flashback", gdm_session, FALSE))) {
menubar_type = MENUBAR_SHOW_IN_WINDOW;
}
else {
menubar_type = MENUBAR_SHOW_HAMBURGER;
}
}
else {
menubar_type = MENUBAR_SHOW_IN_WINDOW;
}
}
switch (menubar_type) {
case MENUBAR_AUTO:
g_error("Unreachable\n");
break;
case MENUBAR_SHOW_IN_SHELL:
g_print("Showing menu in the shell\n");
gtk_application_set_menubar(GTK_APPLICATION(app), menubar_model);
break;
case MENUBAR_SHOW_IN_WINDOW: {
g_print("Showing menu in the window\n");
GtkMenuBar *menubar = GTK_MENU_BAR(gtk_menu_bar_new_from_model(menubar_model));
gtk_box_pack_start(GTK_BOX(main_window_container), GTK_WIDGET(menubar), FALSE, FALSE, 0);
break;
}
case MENUBAR_SHOW_HAMBURGER: {
g_print("Showing hamburger\n");
// Attach a custom title bar
GtkWidget *titlebar = gtkget(GTK_WIDGET, "main_header_bar");
gtk_window_set_titlebar(GTK_WINDOW(main_window), titlebar);
// Disable menubar
gtk_application_set_menubar(GTK_APPLICATION(app), NULL);
// Hook menubar up to the hamburger button
GtkMenuButton *hamburger_button = GTK_MENU_BUTTON(get_object("hamburger_button"));
gtk_menu_button_set_menu_model(hamburger_button, menubar_model);
break;
}
}
}
G_MODULE_EXPORT void gl_check_realize(GtkWidget *w, gpointer user_data_gptr) {
gboolean *result = (gboolean *) user_data_gptr;
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_printerr("Failed to create context: %s\n", 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_print("OpenGL version: %d\n", version);
*result = version >= 32;
}
}
gboolean test_gl_support(void) {
gboolean result = FALSE;
gtk_init(NULL, NULL);
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;
}
// 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;
// Very ugly workaround for GtkGlArea!
// When a GtkGlArea is realized and it creates a legacy GL 1.4 context
// it tries to use GL 2.0 functions to render the window which leads to the application crashing.
// So we initialize GTK, create a dummy GtkWindow object, attach a `realize` callback and
// in this callback create a GdkGLContext on this window. But instead of running the GTK main loop
// we just realize and destroy the dummy window and compare the contexts version in the realize callback.
supports_gl = test_gl_support();
g_print("OpenGL supported: %s\n", supports_gl? "Yes" : "No");
builder = gtk_builder_new_from_resource(RESOURCE_PREFIX "ui/window.ui");
gtk_builder_connect_signals(builder, NULL);
@ -529,65 +701,19 @@ static void startup(GApplication *app, gpointer user_data_gptr) {
// setup main window
main_window = GTK_APPLICATION_WINDOW(gtk_application_window_new(GTK_APPLICATION(app)));
gtk_application_window_set_show_menubar(main_window, false);
main_window_container = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
// create our renderer area
gl_area = GTK_GL_AREA(gtk_gl_area_new());
gtk_gl_area_set_auto_render(gl_area, false);
gtk_window_set_title(GTK_WINDOW(main_window), "SameBoy");
gtk_application_window_set_show_menubar(main_window, false);
gtk_container_add(GTK_CONTAINER(main_window), GTK_WIDGET(main_window_container));
setup_menu(app);
// Insert separators into `GtkComboBox`es
set_combo_box_row_separator_func(GTK_CONTAINER(preferences));
set_combo_box_row_separator_func(GTK_CONTAINER(vram_viewer));
set_combo_box_row_separator_func(GTK_CONTAINER(memory_viewer));
// Connect signal handlers
gtk_widget_add_events(GTK_WIDGET(main_window), GDK_KEY_PRESS_MASK);
gtk_widget_add_events(GTK_WIDGET(main_window), GDK_KEY_RELEASE_MASK);
g_signal_connect(main_window, "key_press_event", G_CALLBACK(on_key_press), NULL);
g_signal_connect(main_window, "key_release_event", G_CALLBACK(on_key_press), NULL);
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);
g_signal_connect(vram_viewer, "realize", G_CALLBACK(on_vram_viewer_realize), NULL);
g_signal_connect(vram_viewer, "unrealize", G_CALLBACK(on_vram_viewer_unrealize), NULL);
// Just hide our sub-windows when closing them
g_signal_connect(preferences, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
g_signal_connect(vram_viewer, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
g_signal_connect(memory_viewer, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
g_signal_connect(console, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
g_signal_connect(printer, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
g_signal_connect(get_object("vram_viewer_tileset_canvas"), "draw", G_CALLBACK(on_draw_vram_viewer_tileset), NULL);
g_signal_connect(get_object("vram_viewer_tilemap_canvas"), "draw", G_CALLBACK(on_vram_viewer_tilemap), NULL);
gtk_container_add(GTK_CONTAINER(main_window), GTK_WIDGET(gl_area));
// Handle the whole menubar situation …
GMenuModel *menubar = get_menu_model(app, "menubar");
if (show_menubar()) {
// Show a classic menubar
gtk_application_set_menubar(GTK_APPLICATION(app), menubar);
}
else {
// Attach a custom title bar
GtkWidget *titlebar = gtkget(GTK_WIDGET, "main_header_bar");
gtk_window_set_titlebar(GTK_WINDOW(main_window), titlebar);
// Disable menubar
gtk_application_set_menubar(GTK_APPLICATION(app), NULL);
// Hook menubar up to the hamburger button
GtkMenuButton *hamburger_button = GTK_MENU_BUTTON(get_object("hamburger_button"));
gtk_menu_button_set_menu_model(hamburger_button, menubar);
}
gtk_window_set_title(GTK_WINDOW(main_window), "SameBoy");
// Define a set of window icons
GList *icon_list = NULL;
static char* icons[] = {
@ -625,7 +751,41 @@ static void activate(GApplication *app, gpointer user_data_gptr) {
gtk_window_fullscreen(GTK_WINDOW(main_window));
}
// Connect signal handlers
gtk_widget_add_events(GTK_WIDGET(main_window), GDK_KEY_PRESS_MASK);
gtk_widget_add_events(GTK_WIDGET(main_window), GDK_KEY_RELEASE_MASK);
g_signal_connect(main_window, "destroy", G_CALLBACK(on_quit), app);
g_signal_connect(main_window, "key_press_event", G_CALLBACK(on_key_press), NULL);
g_signal_connect(main_window, "key_release_event", G_CALLBACK(on_key_press), NULL);
g_signal_connect(vram_viewer, "realize", G_CALLBACK(on_vram_viewer_realize), NULL);
g_signal_connect(vram_viewer, "unrealize", G_CALLBACK(on_vram_viewer_unrealize), NULL);
// Just hide our sub-windows when closing them
g_signal_connect(preferences, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
g_signal_connect(vram_viewer, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
g_signal_connect(memory_viewer, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
g_signal_connect(console, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
g_signal_connect(printer, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
g_signal_connect(get_object("vram_viewer_tileset_canvas"), "draw", G_CALLBACK(on_draw_vram_viewer_tileset), NULL);
g_signal_connect(get_object("vram_viewer_tilemap_canvas"), "draw", G_CALLBACK(on_draw_vram_viewer_tilemap), NULL);
// create our renderer area
if (supports_gl) {
gl_area = GTK_GL_AREA(gtk_gl_area_new());
gtk_gl_area_set_required_version(gl_area, 3, 2);
gtk_gl_area_set_auto_render(gl_area, false);
gtk_gl_area_set_has_alpha(gl_area, false);
gtk_gl_area_set_has_depth_buffer(gl_area, false);
gtk_gl_area_set_has_stencil_buffer(gl_area, false);
g_signal_connect(gl_area, "realize", G_CALLBACK(gl_init), NULL);
gtk_box_pack_end(GTK_BOX(main_window_container), GTK_WIDGET(gl_area), TRUE, TRUE, 0);
}
else {
create_fallback_canvas();
}
gtk_application_add_window(GTK_APPLICATION(app), GTK_WINDOW(main_window));
gtk_widget_show_all(GTK_WIDGET(main_window));
@ -741,6 +901,18 @@ static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) {
return color;
}
static uint32_t rgb_encode_fallback(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) {
#ifdef GB_LITTLE_ENDIAN
// ARGB
uint32_t color = 0xFF000000 | (r << 16) | (g << 8) | b;
#else
// BGRA
uint32_t color = (b << 24) | (g << 16) | (r << 8) | 0xFF;
#endif
return color;
}
static void update_window_geometry() {
// Set size hints
GdkGeometry hints;
@ -793,7 +965,7 @@ static void run(GApplication *app, UserData *user_data) {
GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank);
GB_set_pixels_output(&gb, get_current_buffer());
GB_set_rgb_encode_callback(&gb, rgb_encode);
GB_set_rgb_encode_callback(&gb, fallback_canvas? rgb_encode_fallback : rgb_encode);
// GB_set_sample_rate(&gb, have_aspec.freq);
GB_set_color_correction_mode(&gb, get_color_correction_mode());
GB_set_highpass_filter_mode(&gb, get_highpass_mode());

View File

@ -190,17 +190,20 @@ void update_boot_rom_selector(GtkBuilder *builder) {
gtk_combo_box_text_append(combo_box, "other", "Other");
}
enum menubar_override get_show_menubar(void) {
enum menubar_type_t get_show_menubar(void) {
if (config.menubar_override == NULL) goto default_value;
if (g_strcmp0(config.menubar_override, "auto") == 0) {
return MENUBAR_AUTO;
}
else if (g_strcmp0(config.menubar_override, "show") == 0) {
return MENUBAR_SHOW;
else if (g_strcmp0(config.menubar_override, "show_in_shell") == 0) {
return MENUBAR_SHOW_IN_SHELL;
}
else if (g_strcmp0(config.menubar_override, "hide") == 0) {
return MENUBAR_HIDE;
else if (g_strcmp0(config.menubar_override, "show_in_window") == 0) {
return MENUBAR_SHOW_IN_WINDOW;
}
else if (g_strcmp0(config.menubar_override, "show_hamburger") == 0) {
return MENUBAR_SHOW_HAMBURGER;
}
// This should not happen
@ -208,16 +211,19 @@ enum menubar_override get_show_menubar(void) {
default_value: return MENUBAR_AUTO;
}
void set_show_menubar(enum menubar_override value) {
void set_show_menubar(enum menubar_type_t value) {
switch (value) {
case MENUBAR_AUTO:
config.menubar_override = "auto";
break;
case MENUBAR_SHOW:
config.menubar_override = "show";
case MENUBAR_SHOW_IN_SHELL:
config.menubar_override = "show_in_shell";
break;
case MENUBAR_HIDE:
config.menubar_override = "hide";
case MENUBAR_SHOW_IN_WINDOW:
config.menubar_override = "show_in_window";
break;
case MENUBAR_SHOW_HAMBURGER:
config.menubar_override = "show_hamburger";
break;
}
}

View File

@ -58,10 +58,11 @@ typedef struct config_t {
#undef EXPAND_GROUP_MEMBER
} config_t;
enum menubar_override {
enum menubar_type_t {
MENUBAR_AUTO,
MENUBAR_SHOW,
MENUBAR_HIDE
MENUBAR_SHOW_IN_SHELL,
MENUBAR_SHOW_IN_WINDOW,
MENUBAR_SHOW_HAMBURGER
};
gchar* settings_file_path;
@ -80,8 +81,8 @@ void free_settings(void);
void update_boot_rom_selector(GtkBuilder *builder);
enum menubar_override get_show_menubar(void);
void set_show_menubar(enum menubar_override);
enum menubar_type_t get_show_menubar(void);
void set_show_menubar(enum menubar_type_t);
GB_color_correction_mode_t get_color_correction_mode(void);
void set_color_correction_mode(GB_color_correction_mode_t);

View File

@ -63,11 +63,7 @@ static GLuint create_program(const char *vsh, const char *fsh)
bool init_shader_with_name(shader_t *shader, const char *name)
{
GLint major = 0, minor = 0;
glGetIntegerv(GL_MAJOR_VERSION, &major);
glGetIntegerv(GL_MINOR_VERSION, &minor);
if (major * 0x100 + minor < 0x302) {
if (epoxy_gl_version() < 32) {
return false;
}

View File

@ -14,6 +14,8 @@ typedef struct shader_s {
GLuint texture;
GLuint previous_texture;
GLuint program;
bool compat_mode;
} shader_t;
bool init_shader_with_name(shader_t *shader, const char *name);