diff --git a/gtk3/gb_screen.c b/gtk3/gb_screen.c index 446f7a3..a058edb 100644 --- a/gtk3/gb_screen.c +++ b/gtk3/gb_screen.c @@ -4,10 +4,7 @@ #include struct _GbScreen { - GtkBin parent; - - GtkGLArea *gl_area; - GtkDrawingArea *fallback; + GtkGLArea parent; bool use_gl; shader_t shader; @@ -21,7 +18,7 @@ struct _GbScreen { GB_frame_blending_mode_t blending_mode; }; -G_DEFINE_TYPE(GbScreen, gb_screen, GTK_TYPE_BIN); +G_DEFINE_TYPE(GbScreen, gb_screen, GTK_TYPE_GL_AREA); typedef enum { PROP_USE_GL = 1, @@ -73,14 +70,60 @@ static void gb_screen_get_natural_size(GbScreen *self, gint *natural_width, gint if (scale_y_ptr) *scale_y_ptr = scale_y; } -static gboolean gb_screen_draw(GtkWidget *widget, cairo_t *cr) { - GTK_WIDGET_CLASS(gb_screen_parent_class)->draw(widget, cr); +static gboolean gb_screen_render(GtkGLArea *gl_area, GdkGLContext *context) { + GbScreen *self = (GbScreen *) gl_area; + if (!self->use_gl) return true; static gint scaled_width, scaled_height; static double scale_x, scale_y; + GtkWidget *widget = GTK_WIDGET(gl_area); + int width = gtk_widget_get_allocated_width(widget); + int height = gtk_widget_get_allocated_height(widget); + + gb_screen_get_natural_size(self, &scaled_width, &scaled_height, &scale_x, &scale_y); + + Rect viewport = { + (width - scaled_width) / 2, + (height - scaled_height) / 2, + scaled_width, + scaled_height + }; + + glViewport(viewport.x, viewport.y, scaled_width, scaled_height); + + uint32_t *pixels = gb_screen_get_current_buffer(self); + uint32_t *previous = gb_screen_get_previous_buffer(self); + + static void *_pixels = NULL; + + if (pixels) { + _pixels = pixels; + } + + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + + render_bitmap_with_shader( + &self->shader, _pixels, previous, + self->screen_width, self->screen_height, + viewport.x, viewport.y, viewport.width, viewport.height, + self->blending_mode + ); + + return true; +} + +static gboolean gb_screen_draw(GtkWidget *widget, cairo_t *cr) { GbScreen *self = (GbScreen *) widget; + if (self->use_gl) { + return GTK_WIDGET_CLASS(gb_screen_parent_class)->draw(widget, cr); + } + + static gint scaled_width, scaled_height; + static double scale_x, scale_y; + GtkStyleContext *context = gtk_widget_get_style_context(widget); int width = gtk_widget_get_allocated_width(widget); int height = gtk_widget_get_allocated_height(widget); @@ -96,49 +139,23 @@ static gboolean gb_screen_draw(GtkWidget *widget, cairo_t *cr) { scaled_height }; - if (self->use_gl) { - glViewport(viewport.x, viewport.y, scaled_width, scaled_height); + cairo_surface_t *surface = cairo_image_surface_create_for_data( + (unsigned char *) gb_screen_get_current_buffer(self), + CAIRO_FORMAT_RGB24, + self->screen_width, + self->screen_height, + cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, self->screen_width) + ); - uint32_t *pixels = gb_screen_get_current_buffer(self); - uint32_t *previous = gb_screen_get_previous_buffer(self); + cairo_translate(cr, viewport.x, viewport.y); + cairo_scale(cr, scale_x, scale_y); + cairo_set_source_surface(cr, surface, 0, 0); + cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST); + cairo_paint(cr); - static void *_pixels = NULL; + cairo_surface_destroy(surface); - if (pixels) { - _pixels = pixels; - } - - glClearColor(0, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - - render_bitmap_with_shader( - &self->shader, _pixels, previous, - self->screen_width, self->screen_height, - viewport.x, viewport.y, viewport.width, viewport.height, - self->blending_mode - ); - - // gtk_gl_area_queue_render(self->gl_area); - } - else { - cairo_surface_t *surface = cairo_image_surface_create_for_data( - (unsigned char *) gb_screen_get_current_buffer(self), - CAIRO_FORMAT_RGB24, - self->screen_width, - self->screen_height, - cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, self->screen_width) - ); - - cairo_translate(cr, viewport.x, viewport.y); - cairo_scale(cr, scale_x, scale_y); - cairo_set_source_surface(cr, surface, 0, 0); - cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST); - cairo_paint(cr); - - cairo_surface_destroy(surface); - } - - return false; + return true; } static void gb_screen_get_preferred_width(GtkWidget *widget, gint *minimum_width, gint *natural_width) { @@ -181,75 +198,73 @@ static void gb_screen_get_property(GObject *object, guint property_id, GValue *v } } -static void gb_screen_gl_area_realized(GtkWidget *widget, GObject *object) { - GbScreen *self = (GbScreen *)object; +static void gb_screen_realize(GtkWidget *widget) { + GbScreen *self = (GbScreen *)widget; GtkGLArea *gl_area = GTK_GL_AREA(widget); - g_debug("GL Context: %p", gtk_gl_area_get_context(self->gl_area)); + if (self->use_gl) { + gtk_gl_area_set_required_version(gl_area, 3, 2); + gtk_gl_area_set_auto_render(gl_area, true); + 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); + + GTK_WIDGET_CLASS(gb_screen_parent_class)->realize(widget); + GdkGLContext *context = gtk_gl_area_get_context(gl_area); - gtk_gl_area_make_current(self->gl_area); - if (gtk_gl_area_get_error(self->gl_area) != NULL) { - goto error; - } + if (context) { + gtk_gl_area_make_current(gl_area); - const char *renderer = (char *)glGetString(GL_RENDERER); - g_debug("GtkGLArea on %s", renderer ? renderer : "Unknown"); + int major, minor; + gdk_gl_context_get_version(context, &major, &minor); + + if (gtk_gl_area_get_error(gl_area) != NULL) { + goto error; + } - if (config.video.shader == NULL || (!init_shader_with_name(&self->shader, config.video.shader) && !init_shader_with_name(&self->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(self->gl_area, error); + g_debug("GL (%d.%d) Context: %p", major, minor, context); + g_debug("GtkGLArea on %s", glGetString(GL_RENDERER)); + + if (config.video.shader == NULL || (!init_shader_with_name(&self->shader, config.video.shader) && !init_shader_with_name(&self->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 { + return; + } + } } else { - g_info("Using OpenGL for rendering"); - G_OBJECT_CLASS(gb_screen_parent_class)->constructed(object); - return; + GTK_WIDGET_CLASS(gb_screen_parent_class)->realize(widget); } error: - if (gtk_gl_area_get_error(self->gl_area) != NULL) { - g_warning("GtkGLArea: %s", gtk_gl_area_get_error(self->gl_area)->message); + if (gtk_gl_area_get_error(gl_area) != NULL) { + g_warning("GtkGLArea: %s", gtk_gl_area_get_error(gl_area)->message); } - gtk_widget_destroy(GTK_WIDGET(self->gl_area)); - self->gl_area = NULL; self->use_gl = false; - g_info("Using Cairo for rendering"); - G_OBJECT_CLASS(gb_screen_parent_class)->constructed(object); } static void gb_screen_constructed(GObject *object) { GbScreen *self = (GbScreen *)object; - // Very ugly workaround for GtkGlArea! - // When a GtkGlArea is realized and it creates a legacy GL 1.4 context + // 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 context’s version in the realize callback. self->use_gl = self->use_gl && test_gl_support(); - if (self->use_gl) { - self->gl_area = GTK_GL_AREA(gtk_gl_area_new()); - g_signal_connect(self->gl_area, "realize", G_CALLBACK(gb_screen_gl_area_realized), object); - - gtk_gl_area_set_required_version(self->gl_area, 3, 2); - gtk_gl_area_set_auto_render(self->gl_area, false); - gtk_gl_area_set_has_alpha(self->gl_area, false); - gtk_gl_area_set_has_depth_buffer(self->gl_area, false); - gtk_gl_area_set_has_stencil_buffer(self->gl_area, false); - gtk_container_add(GTK_CONTAINER(self), GTK_WIDGET(self->gl_area)); - } - else { - g_info("Using Cairo for rendering"); - G_OBJECT_CLASS(gb_screen_parent_class)->constructed(object); - } + G_OBJECT_CLASS(gb_screen_parent_class)->constructed(object); } static void gb_screen_class_init(GbScreenClass *class) { obj_properties[PROP_USE_GL] = g_param_spec_boolean( "use-gl", "Use OpenGL", "Whether to use OpenGL for rendering.", true, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_PRIVATE + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE ); G_OBJECT_CLASS(class)->finalize = gb_screen_finalize; @@ -261,6 +276,10 @@ static void gb_screen_class_init(GbScreenClass *class) { GTK_WIDGET_CLASS(class)->get_preferred_width = gb_screen_get_preferred_width; GTK_WIDGET_CLASS(class)->get_preferred_height = gb_screen_get_preferred_height; + GTK_WIDGET_CLASS(class)->realize = gb_screen_realize; + + GTK_GL_AREA_CLASS(class)->render = gb_screen_render; + g_object_class_install_properties(G_OBJECT_CLASS(class), N_PROPERTIES, obj_properties); } diff --git a/gtk3/gb_screen.h b/gtk3/gb_screen.h index 91803fe..93c177b 100644 --- a/gtk3/gb_screen.h +++ b/gtk3/gb_screen.h @@ -8,7 +8,7 @@ #include "shader.h" #define GB_SCREEN_TYPE (gb_screen_get_type()) -G_DECLARE_FINAL_TYPE(GbScreen, gb_screen, SAMEBOY, BIN, GtkBin) +G_DECLARE_FINAL_TYPE(GbScreen, gb_screen, SAMEBOY, GLAREA, GtkGLArea) GbScreen *gb_screen_new(bool force_fallback); void gb_screen_clear(GbScreen *self);