[GTK3] Experiment with GtkGLArea as parent of GbScreen

This commit is contained in:
Maximilian Mader 2020-05-19 16:21:14 +02:00
parent d63560d3c1
commit d57efec2d3
Signed by: Max
GPG Key ID: F71D56A3151C4FB3
2 changed files with 106 additions and 87 deletions

View File

@ -4,10 +4,7 @@
#include <stdint.h> #include <stdint.h>
struct _GbScreen { struct _GbScreen {
GtkBin parent; GtkGLArea parent;
GtkGLArea *gl_area;
GtkDrawingArea *fallback;
bool use_gl; bool use_gl;
shader_t shader; shader_t shader;
@ -21,7 +18,7 @@ struct _GbScreen {
GB_frame_blending_mode_t blending_mode; 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 { typedef enum {
PROP_USE_GL = 1, PROP_USE_GL = 1,
@ -73,20 +70,17 @@ static void gb_screen_get_natural_size(GbScreen *self, gint *natural_width, gint
if (scale_y_ptr) *scale_y_ptr = scale_y; if (scale_y_ptr) *scale_y_ptr = scale_y;
} }
static gboolean gb_screen_draw(GtkWidget *widget, cairo_t *cr) { static gboolean gb_screen_render(GtkGLArea *gl_area, GdkGLContext *context) {
GTK_WIDGET_CLASS(gb_screen_parent_class)->draw(widget, cr); GbScreen *self = (GbScreen *) gl_area;
if (!self->use_gl) return true;
static gint scaled_width, scaled_height; static gint scaled_width, scaled_height;
static double scale_x, scale_y; static double scale_x, scale_y;
GbScreen *self = (GbScreen *) widget; GtkWidget *widget = GTK_WIDGET(gl_area);
GtkStyleContext *context = gtk_widget_get_style_context(widget);
int width = gtk_widget_get_allocated_width(widget); int width = gtk_widget_get_allocated_width(widget);
int height = gtk_widget_get_allocated_height(widget); int height = gtk_widget_get_allocated_height(widget);
gtk_render_background(context, cr, 0, 0, width, height);
gtk_render_frame(context, cr, 0, 0, width, height);
gb_screen_get_natural_size(self, &scaled_width, &scaled_height, &scale_x, &scale_y); gb_screen_get_natural_size(self, &scaled_width, &scaled_height, &scale_x, &scale_y);
Rect viewport = { Rect viewport = {
@ -96,7 +90,6 @@ static gboolean gb_screen_draw(GtkWidget *widget, cairo_t *cr) {
scaled_height scaled_height
}; };
if (self->use_gl) {
glViewport(viewport.x, viewport.y, scaled_width, scaled_height); glViewport(viewport.x, viewport.y, scaled_width, scaled_height);
uint32_t *pixels = gb_screen_get_current_buffer(self); uint32_t *pixels = gb_screen_get_current_buffer(self);
@ -118,9 +111,34 @@ static gboolean gb_screen_draw(GtkWidget *widget, cairo_t *cr) {
self->blending_mode self->blending_mode
); );
// gtk_gl_area_queue_render(self->gl_area); 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);
} }
else {
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);
gtk_render_background(context, cr, 0, 0, width, height);
gtk_render_frame(context, cr, 0, 0, width, height);
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
};
cairo_surface_t *surface = cairo_image_surface_create_for_data( cairo_surface_t *surface = cairo_image_surface_create_for_data(
(unsigned char *) gb_screen_get_current_buffer(self), (unsigned char *) gb_screen_get_current_buffer(self),
CAIRO_FORMAT_RGB24, CAIRO_FORMAT_RGB24,
@ -136,9 +154,8 @@ static gboolean gb_screen_draw(GtkWidget *widget, cairo_t *cr) {
cairo_paint(cr); cairo_paint(cr);
cairo_surface_destroy(surface); cairo_surface_destroy(surface);
}
return false; return true;
} }
static void gb_screen_get_preferred_width(GtkWidget *widget, gint *minimum_width, gint *natural_width) { 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) { static void gb_screen_realize(GtkWidget *widget) {
GbScreen *self = (GbScreen *)object; GbScreen *self = (GbScreen *)widget;
GtkGLArea *gl_area = GTK_GL_AREA(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_gl_area_make_current(self->gl_area); GTK_WIDGET_CLASS(gb_screen_parent_class)->realize(widget);
if (gtk_gl_area_get_error(self->gl_area) != NULL) { GdkGLContext *context = gtk_gl_area_get_context(gl_area);
if (context) {
gtk_gl_area_make_current(gl_area);
int major, minor;
gdk_gl_context_get_version(context, &major, &minor);
if (gtk_gl_area_get_error(gl_area) != NULL) {
goto error; goto error;
} }
const char *renderer = (char *)glGetString(GL_RENDERER); g_debug("GL (%d.%d) Context: %p", major, minor, context);
g_debug("GtkGLArea on %s", renderer ? renderer : "Unknown"); 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"))) { 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"); 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); gtk_gl_area_set_error(gl_area, error);
} }
else { else {
g_info("Using OpenGL for rendering");
G_OBJECT_CLASS(gb_screen_parent_class)->constructed(object);
return; return;
} }
}
}
else {
GTK_WIDGET_CLASS(gb_screen_parent_class)->realize(widget);
}
error: error:
if (gtk_gl_area_get_error(self->gl_area) != NULL) { if (gtk_gl_area_get_error(gl_area) != NULL) {
g_warning("GtkGLArea: %s", gtk_gl_area_get_error(self->gl_area)->message); 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; self->use_gl = false;
g_info("Using Cairo for rendering"); g_info("Using Cairo for rendering");
G_OBJECT_CLASS(gb_screen_parent_class)->constructed(object);
} }
static void gb_screen_constructed(GObject *object) { static void gb_screen_constructed(GObject *object) {
GbScreen *self = (GbScreen *)object; GbScreen *self = (GbScreen *)object;
// Very ugly workaround for GtkGlArea! // Very ugly workaround for GtkGLArea!
// When a GtkGlArea is realized and it creates a legacy GL 1.4 context // 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. // 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 // 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 // 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. // we just realize and destroy the dummy window and compare the contexts version in the realize callback.
self->use_gl = self->use_gl && test_gl_support(); 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) { static void gb_screen_class_init(GbScreenClass *class) {
obj_properties[PROP_USE_GL] = g_param_spec_boolean( obj_properties[PROP_USE_GL] = g_param_spec_boolean(
"use-gl", "Use OpenGL", "Whether to use OpenGL for rendering.", "use-gl", "Use OpenGL", "Whether to use OpenGL for rendering.",
true, 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; 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_width = gb_screen_get_preferred_width;
GTK_WIDGET_CLASS(class)->get_preferred_height = gb_screen_get_preferred_height; 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); g_object_class_install_properties(G_OBJECT_CLASS(class), N_PROPERTIES, obj_properties);
} }

View File

@ -8,7 +8,7 @@
#include "shader.h" #include "shader.h"
#define GB_SCREEN_TYPE (gb_screen_get_type()) #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); GbScreen *gb_screen_new(bool force_fallback);
void gb_screen_clear(GbScreen *self); void gb_screen_clear(GbScreen *self);