[GTK3] Experiment with GtkGLArea as parent of GbScreen
This commit is contained in:
parent
d63560d3c1
commit
d57efec2d3
191
gtk3/gb_screen.c
191
gtk3/gb_screen.c
@ -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,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;
|
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;
|
||||||
|
|
||||||
|
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;
|
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);
|
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);
|
||||||
@ -96,49 +139,23 @@ static gboolean gb_screen_draw(GtkWidget *widget, cairo_t *cr) {
|
|||||||
scaled_height
|
scaled_height
|
||||||
};
|
};
|
||||||
|
|
||||||
if (self->use_gl) {
|
cairo_surface_t *surface = cairo_image_surface_create_for_data(
|
||||||
glViewport(viewport.x, viewport.y, scaled_width, scaled_height);
|
(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);
|
cairo_translate(cr, viewport.x, viewport.y);
|
||||||
uint32_t *previous = gb_screen_get_previous_buffer(self);
|
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) {
|
return true;
|
||||||
_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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *renderer = (char *)glGetString(GL_RENDERER);
|
if (context) {
|
||||||
g_debug("GtkGLArea on %s", renderer ? renderer : "Unknown");
|
gtk_gl_area_make_current(gl_area);
|
||||||
|
|
||||||
if (config.video.shader == NULL || (!init_shader_with_name(&self->shader, config.video.shader) && !init_shader_with_name(&self->shader, "NearestNeighbor"))) {
|
int major, minor;
|
||||||
GError *error = g_error_new_literal(g_quark_from_string("sameboy-gl-error"), 1, "Failed to initialize shaders");
|
gdk_gl_context_get_version(context, &major, &minor);
|
||||||
gtk_gl_area_set_error(self->gl_area, error);
|
|
||||||
|
if (gtk_gl_area_get_error(gl_area) != NULL) {
|
||||||
|
goto 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 {
|
else {
|
||||||
g_info("Using OpenGL for rendering");
|
GTK_WIDGET_CLASS(gb_screen_parent_class)->realize(widget);
|
||||||
G_OBJECT_CLASS(gb_screen_parent_class)->constructed(object);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 context’s version in the realize callback.
|
// 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();
|
self->use_gl = self->use_gl && test_gl_support();
|
||||||
|
|
||||||
if (self->use_gl) {
|
G_OBJECT_CLASS(gb_screen_parent_class)->constructed(object);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user