From d63560d3c1f89b911d0c18af465a96ac7dbd1dbc Mon Sep 17 00:00:00 2001 From: Maximilian Mader Date: Tue, 19 May 2020 02:31:31 +0200 Subject: [PATCH] [GTK3] Implement GbScreen widget for rendering --- gtk3/gb_screen.c | 339 ++++++++++++++++++++++++++++++++++++ gtk3/gb_screen.h | 25 +++ gtk3/main.c | 349 +++++--------------------------------- gtk3/types.h | 12 +- gtk3/vram_viewer_window.c | 8 +- 5 files changed, 412 insertions(+), 321 deletions(-) create mode 100644 gtk3/gb_screen.c create mode 100644 gtk3/gb_screen.h diff --git a/gtk3/gb_screen.c b/gtk3/gb_screen.c new file mode 100644 index 0000000..446f7a3 --- /dev/null +++ b/gtk3/gb_screen.c @@ -0,0 +1,339 @@ +#include "gb_screen.h" +#include "config.h" +#include "util.h" +#include + +struct _GbScreen { + GtkBin parent; + + GtkGLArea *gl_area; + GtkDrawingArea *fallback; + + bool use_gl; + shader_t shader; + + uint32_t *image_buffers[3]; + unsigned char current_buffer; + + unsigned screen_width; + unsigned screen_height; + + GB_frame_blending_mode_t blending_mode; +}; + +G_DEFINE_TYPE(GbScreen, gb_screen, GTK_TYPE_BIN); + +typedef enum { + PROP_USE_GL = 1, + N_PROPERTIES +} GbScreenProperty; + +static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, }; + +static void gb_screen_finalize(GObject *object) { + GbScreen *self = (GbScreen *) object; + + if (self->image_buffers[0]) g_free(self->image_buffers[0]); + if (self->image_buffers[1]) g_free(self->image_buffers[1]); + if (self->image_buffers[2]) g_free(self->image_buffers[2]); + + free_shader(&self->shader); + free_master_shader(); + + G_OBJECT_CLASS(gb_screen_parent_class)->finalize(object); +} + +static void gb_screen_get_natural_size(GbScreen *self, gint *natural_width, gint *natural_height, double *scale_x_ptr, double *scale_y_ptr) { + int width = gtk_widget_get_allocated_width(GTK_WIDGET(self)); + int height = gtk_widget_get_allocated_height(GTK_WIDGET(self)); + + double scale_x = width / (double)self->screen_width; + double scale_y = height / (double)self->screen_height; + + if (config.video.use_integer_scaling) { + scale_x = (unsigned)(scale_x); + scale_y = (unsigned)(scale_y); + } + + if (config.video.keep_aspect_ratio) { + if (scale_x > scale_y) { + scale_x = scale_y; + } + else { + scale_y = scale_x; + } + } + + scale_x = max_double(1.0, scale_x); + scale_y = max_double(1.0, scale_y); + + if (natural_width) *natural_width = self->screen_width * scale_x; + if (natural_height) *natural_height = self->screen_height * scale_y; + if (scale_x_ptr) *scale_x_ptr = scale_x; + 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 gint scaled_width, scaled_height; + static double scale_x, scale_y; + + GbScreen *self = (GbScreen *) widget; + + 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 + }; + + if (self->use_gl) { + 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 + ); + + // 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) { + GbScreen *self = (GbScreen *)widget; + + *minimum_width = self->screen_width; + gb_screen_get_natural_size(self, natural_width, NULL, NULL, NULL); +} + +static void gb_screen_get_preferred_height(GtkWidget *widget, gint *minimum_height, gint *natural_height) { + GbScreen *self = (GbScreen *)widget; + + *minimum_height = self->screen_height; + gb_screen_get_natural_size(self, NULL, natural_height, NULL, NULL); +} + +static void gb_screen_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { + GbScreen *self = (GbScreen *) object; + + switch ((GbScreenProperty) property_id) { + case PROP_USE_GL: + self->use_gl = g_value_get_boolean(value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void gb_screen_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { + GbScreen *self = (GbScreen *) object; + + switch ((GbScreenProperty) property_id) { + case PROP_USE_GL: + g_value_set_boolean(value, self->use_gl); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void gb_screen_gl_area_realized(GtkWidget *widget, GObject *object) { + GbScreen *self = (GbScreen *)object; + GtkGLArea *gl_area = GTK_GL_AREA(widget); + + g_debug("GL Context: %p", gtk_gl_area_get_context(self->gl_area)); + + gtk_gl_area_make_current(self->gl_area); + if (gtk_gl_area_get_error(self->gl_area) != NULL) { + goto error; + } + + const char *renderer = (char *)glGetString(GL_RENDERER); + g_debug("GtkGLArea on %s", renderer ? renderer : "Unknown"); + + 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); + } + else { + g_info("Using OpenGL for rendering"); + G_OBJECT_CLASS(gb_screen_parent_class)->constructed(object); + return; + } + + error: + if (gtk_gl_area_get_error(self->gl_area) != NULL) { + g_warning("GtkGLArea: %s", gtk_gl_area_get_error(self->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 + // 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); + } +} + +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_OBJECT_CLASS(class)->finalize = gb_screen_finalize; + G_OBJECT_CLASS(class)->set_property = gb_screen_set_property; + G_OBJECT_CLASS(class)->get_property = gb_screen_get_property; + G_OBJECT_CLASS(class)->constructed = gb_screen_constructed; + + GTK_WIDGET_CLASS(class)->draw = gb_screen_draw; + GTK_WIDGET_CLASS(class)->get_preferred_width = gb_screen_get_preferred_width; + GTK_WIDGET_CLASS(class)->get_preferred_height = gb_screen_get_preferred_height; + + g_object_class_install_properties(G_OBJECT_CLASS(class), N_PROPERTIES, obj_properties); +} + +static void gb_screen_init(GbScreen *self) { + gb_screen_set_resolution(self, 160, 144); + gb_screen_clear(self); +} + +GbScreen *gb_screen_new(bool force_fallback) { + g_debug("force_fallback: %d", force_fallback); + return g_object_new(GB_SCREEN_TYPE, "use-gl", !force_fallback, NULL); +} + +void gb_screen_clear(GbScreen *self) { + for (unsigned i = 0; i < 3; i++) { + memset(self->image_buffers[i], 0, self->screen_width * self->screen_height * sizeof(uint32_t)); + } +} + +bool gb_screen_uses_fallback(GbScreen *self) { + return !self->use_gl; +} + +// Determines how many frame buffers to use +static uint8_t number_of_buffers(GbScreen *self) { + if (!self->use_gl) return 2; + + bool should_blend = config_get_frame_blending_mode() != GB_FRAME_BLENDING_MODE_DISABLED; + + return should_blend? 3 : 2; +} + +// Returns the buffer that should be used by the Core to render a new frame to +uint32_t *gb_screen_get_pixels(GbScreen *self) { + return self->image_buffers[(self->current_buffer + 1) % number_of_buffers(self)]; +} + +// Returns the current finished frame +uint32_t *gb_screen_get_current_buffer(GbScreen *self) { + return self->image_buffers[self->current_buffer]; +} + +// Returns the previous finished frame +uint32_t *gb_screen_get_previous_buffer(GbScreen *self) { + return self->image_buffers[(self->current_buffer + 2) % number_of_buffers(self)]; +} + +// Cycles the buffers +void gb_screen_flip(GbScreen *self) { + self->current_buffer = (self->current_buffer + 1) % number_of_buffers(self); +} + +void gb_screen_set_resolution(GbScreen *self, unsigned width, unsigned height) { + self->screen_width = width; + self->screen_height = height; + + for (unsigned i = 0; i < 3; i++) { + self->image_buffers[i] = g_realloc_n( + self->image_buffers[i], + self->screen_width * self->screen_height, sizeof(uint32_t) + ); + } + + gtk_widget_queue_resize(GTK_WIDGET(self)); +} + +void gb_screen_set_blending_mode(GbScreen *self, GB_frame_blending_mode_t mode) { + self->blending_mode = mode; +} + +void gb_screen_set_shader(GbScreen *self, const char *shader_name) { + if (!self->use_gl) return; + + free_shader(&self->shader); + init_shader_with_name(&self->shader, shader_name); +} diff --git a/gtk3/gb_screen.h b/gtk3/gb_screen.h new file mode 100644 index 0000000..91803fe --- /dev/null +++ b/gtk3/gb_screen.h @@ -0,0 +1,25 @@ +#ifndef gb_screen_h +#define gb_screen_h + +#include +#include +#include +#include +#include "shader.h" + +#define GB_SCREEN_TYPE (gb_screen_get_type()) +G_DECLARE_FINAL_TYPE(GbScreen, gb_screen, SAMEBOY, BIN, GtkBin) + +GbScreen *gb_screen_new(bool force_fallback); +void gb_screen_clear(GbScreen *self); +bool gb_screen_uses_fallback(GbScreen *self); + +uint32_t *gb_screen_get_pixels(GbScreen *self); +uint32_t *gb_screen_get_current_buffer(GbScreen *self); +uint32_t *gb_screen_get_previous_buffer(GbScreen *self); +void gb_screen_flip(GbScreen *self); +void gb_screen_set_resolution(GbScreen *self, unsigned width, unsigned height); +void gb_screen_set_blending_mode(GbScreen *self, GB_frame_blending_mode_t mode); +void gb_screen_set_shader(GbScreen *self, const char *shader_name); + +#endif diff --git a/gtk3/main.c b/gtk3/main.c index cad07c4..d4caa4a 100644 --- a/gtk3/main.c +++ b/gtk3/main.c @@ -11,9 +11,9 @@ #include "types.h" #include "config.h" #include "util.h" -#include "shader.h" #include "check_menu_radio_group.h" +#include "gb_screen.h" #include "vram_viewer_window.h" // used for audio and game controllers @@ -641,73 +641,6 @@ static void set_combo_box_row_separator_func(GtkContainer *container) { g_list_free(children); } -// Determines how many frame buffers to use -static unsigned char number_of_buffers(void) { - if (gui_data.fallback_canvas) return 2; - - bool should_blend = config_get_frame_blending_mode() != GB_FRAME_BLENDING_MODE_DISABLED; - - return should_blend? 3 : 2; -} - -// Returns the buffer that should be used by the Core to render a new frame to -static uint32_t *get_pixels(void) { - return gui_data.image_buffers[(gui_data.current_buffer + 1) % number_of_buffers()]; -} - -// Returns the current finished frame -static uint32_t *get_current_buffer(void) { - return gui_data.image_buffers[gui_data.current_buffer]; -} - -// Returns the previous finished frame -static uint32_t *get_previous_buffer(void) { - return gui_data.image_buffers[(gui_data.current_buffer + 2) % number_of_buffers()]; -} - -// Cycles the buffers -static void flip(void) { - gui_data.current_buffer = (gui_data.current_buffer + 1) % number_of_buffers(); -} - -static void update_viewport(void) { - GtkWidget *w = gui_data.fallback_canvas ? GTK_WIDGET(gui_data.fallback_canvas) : GTK_WIDGET(gui_data.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); - - if (config.video.use_integer_scaling) { - x_factor = (int)(x_factor); - y_factor = (int)(y_factor); - } - - if (config.video.keep_aspect_ratio) { - 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); - - gui_data.viewport = (Rect){ - (win_width - new_width) / 2, - (win_height - new_height) / 2, - new_width, - new_height - }; - - if (gui_data.gl_area) { - glViewport(gui_data.viewport.x, gui_data.viewport.y, gui_data.viewport.width, gui_data.viewport.height); - } -} - // WHY DO WE NEED SUCH AN UGLY METHOD, GTK?! static void action_entries_set_enabled(const GActionEntry *entries, unsigned n_entries, bool value) { // Assumes null-terminated if n_entries == -1 @@ -719,55 +652,6 @@ static void action_entries_set_enabled(const GActionEntry *entries, unsigned n_e } } -static void update_window_geometry(void) { - g_debug("update_window_geometry: %u×%u → %u×%u", gui_data.last_screen_width, gui_data.last_screen_height, GB_get_screen_width(&gb), GB_get_screen_height(&gb)); - - GtkWidget *w = gui_data.fallback_canvas ? GTK_WIDGET(gui_data.fallback_canvas) : GTK_WIDGET(gui_data.gl_area); - signed win_width = gtk_widget_get_allocated_width(w); - signed win_height = gtk_widget_get_allocated_height(w); - signed menu_height = gtk_widget_get_allocated_height(builder_get(GTK_WIDGET, "main_menu")); - - unsigned _factor = win_width > win_height ? win_width / GB_get_screen_width(&gb) : win_height / GB_get_screen_height(&gb); - unsigned factor = _factor < 2 ? 2 : _factor; - - unsigned new_width = GB_get_screen_width(&gb) * factor; - unsigned new_height = GB_get_screen_height(&gb) * factor + menu_height; - - // Set size hints - GdkGeometry hints; - hints.min_width = GB_get_screen_width(&gb); - hints.min_height = GB_get_screen_height(&gb) + menu_height; - - gtk_window_set_geometry_hints( - GTK_WINDOW(gui_data.main_window), - NULL, - &hints, - (GdkWindowHints)(GDK_HINT_MIN_SIZE) - ); - - if (new_width > win_width || new_height > win_height) { - gtk_window_resize(GTK_WINDOW(gui_data.main_window), new_width, new_height); - } - - // Setup our image buffers - if (gui_data.image_buffers[0]) g_free(gui_data.image_buffers[0]); - if (gui_data.image_buffers[1]) g_free(gui_data.image_buffers[1]); - if (gui_data.image_buffers[2]) g_free(gui_data.image_buffers[2]); - - size_t buffer_size = sizeof(gui_data.image_buffers[0][0]) * GB_get_screen_width(&gb) * GB_get_screen_height(&gb); - - gui_data.image_buffers[0] = g_malloc0(buffer_size); - gui_data.image_buffers[1] = g_malloc0(buffer_size); - gui_data.image_buffers[2] = g_malloc0(buffer_size); - - gui_data.last_screen_width = GB_get_screen_width(&gb); - gui_data.last_screen_height = GB_get_screen_height(&gb); - - if (GB_is_inited(&gb)) { - GB_set_pixels_output(&gb, get_pixels()); - } -} - static void stop(void) { if (!gui_data.running) return; @@ -787,30 +671,25 @@ static void stop(void) { } static gboolean on_vblank(GB_gameboy_t *gb) { - // Queue drawing of the current frame - if (gui_data.fallback_canvas) { - gtk_widget_queue_draw(GTK_WIDGET(gui_data.main_window)); - } - else if (gui_data.gl_area) { - gtk_gl_area_queue_render(gui_data.gl_area); - } - + gtk_widget_queue_draw(GTK_WIDGET(gui_data.screen)); gtk_widget_queue_draw(GTK_WIDGET(gui_data.vram_viewer)); return false; } static void vblank(GB_gameboy_t *gb) { - flip(); + gb_screen_flip(gui_data.screen); if (gui_data.border_mode_changed) { GB_set_border_mode(gb, config_get_display_border_mode()); - update_window_geometry(); + + gb_screen_set_resolution(gui_data.screen, GB_get_screen_width(gb), GB_get_screen_height(gb)); + GB_set_pixels_output(gb, gb_screen_get_pixels(gui_data.screen)); gui_data.border_mode_changed = false; } - GB_set_pixels_output(gb, get_pixels()); + GB_set_pixels_output(gb, gb_screen_get_pixels(gui_data.screen)); // Handle the speed modifiers: // The binary slowdown is limited to half speed. @@ -837,6 +716,24 @@ static void vblank(GB_gameboy_t *gb) { vram_viewer_update(gui_data.vram_viewer, gb); + GB_frame_blending_mode_t mode = config_get_frame_blending_mode(); + + if (!gb_screen_get_previous_buffer(gui_data.screen)) { + mode = GB_FRAME_BLENDING_MODE_DISABLED; + } + else if (mode == GB_FRAME_BLENDING_MODE_ACCURATE) { + mode = GB_FRAME_BLENDING_MODE_DISABLED; + + if (GB_is_sgb(gb)) { + mode = GB_FRAME_BLENDING_MODE_SIMPLE; + } + else { + mode = GB_is_odd_frame(gb)? GB_FRAME_BLENDING_MODE_ACCURATE_ODD : GB_FRAME_BLENDING_MODE_ACCURATE_EVEN; + } + } + + gb_screen_set_blending_mode(gui_data.screen, mode); + g_idle_add((GSourceFunc) on_vblank, gb); } @@ -1005,7 +902,7 @@ static void init(void) { GB_init(&gb, config_get_model_type(&gui_data)); GB_set_vblank_callback(&gb, vblank); - GB_set_pixels_output(&gb, get_current_buffer()); + GB_set_pixels_output(&gb, gb_screen_get_current_buffer(gui_data.screen)); GB_set_rgb_encode_callback(&gb, rgb_encode); GB_set_sample_rate(&gb, GB_audio_get_sample_rate()); GB_set_color_correction_mode(&gb, config_get_color_correction_mode()); @@ -1024,8 +921,6 @@ static void init(void) { if (config_get_display_border_mode() <= GB_BORDER_ALWAYS) { GB_set_border_mode(&gb, config_get_display_border_mode()); } - - update_window_geometry(); } static void reset(void) { @@ -1045,7 +940,8 @@ static void reset(void) { // Check SGB -> non-SGB and non-SGB to SGB transitions if (GB_get_screen_width(&gb) != gui_data.last_screen_width || GB_get_screen_height(&gb) != gui_data.last_screen_height) { - update_window_geometry(); + gb_screen_set_resolution(gui_data.screen, GB_get_screen_width(&gb), GB_get_screen_height(&gb)); + GB_set_pixels_output(&gb, gb_screen_get_pixels(gui_data.screen)); } bool success = false; @@ -1290,20 +1186,6 @@ static void startup(GApplication *app, gpointer null_ptr) { g_debug("GTK version %u.%u.%u", gtk_get_major_version(), gtk_get_minor_version(), gtk_get_micro_version()); - if (gui_data.cli_options.force_software_renderer) { - g_message("Forcing fallback renderer!"); - } - else { - // 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. - gui_data.supports_gl = test_gl_support(); - g_debug("OpenGL supported: %s", gui_data.supports_gl? "Yes" : "No"); - } - gui_data.builder = gtk_builder_new_from_resource(RESOURCE_PREFIX "ui/window.ui"); gtk_builder_connect_signals(gui_data.builder, NULL); @@ -1319,6 +1201,7 @@ static void startup(GApplication *app, gpointer null_ptr) { g_signal_connect(gui_data.preferences, "realize", G_CALLBACK(on_preferences_realize), (gpointer) gui_data.builder); init_config(app, gui_data.cli_options.config_path, &gui_data.config_modification_date, gui_data.preferences); + gui_data.screen = gb_screen_new(gui_data.cli_options.force_software_renderer); gui_data.vram_viewer = vram_viewer_new(); gui_data.memory_viewer = GTK_WINDOW(get_object("memory_viewer")); @@ -1339,6 +1222,7 @@ static void startup(GApplication *app, gpointer null_ptr) { gtk_window_set_title(GTK_WINDOW(gui_data.main_window), "SameBoy"); gtk_application_window_set_show_menubar(gui_data.main_window, false); gtk_container_add(GTK_CONTAINER(gui_data.main_window), GTK_WIDGET(gui_data.main_window_container)); + gtk_box_pack_end(GTK_BOX(gui_data.main_window_container), GTK_WIDGET(gui_data.screen), true, true, 0); setup_menu(app); @@ -1374,6 +1258,11 @@ static void startup(GApplication *app, gpointer null_ptr) { gtk_about_dialog_set_logo(about_dialog, gdk_pixbuf_new_from_resource(icons[2], NULL)); // reuse the 64x64 icon gtk_about_dialog_set_version(about_dialog, "v" xstr(VERSION)); g_list_free_full(icon_list, g_object_unref); + + GdkScreen *screen = gdk_screen_get_default(); + GtkCssProvider *provider = gtk_css_provider_new(); + gtk_css_provider_load_from_resource(provider, RESOURCE_PREFIX "css/main.css"); + gtk_style_context_add_provider_for_screen(screen, GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); } G_MODULE_EXPORT void on_quit_activate(GtkWidget *w, gpointer user_data_ptr) { @@ -1443,145 +1332,6 @@ static void connect_signal_handlers(GApplication *app) { g_signal_connect(gui_data.printer, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL); } -// TODO: Comment -static void gl_draw(void) { - uint32_t *pixels = get_current_buffer(); - uint32_t *previous = get_previous_buffer(); - - static void *_pixels = NULL; - - if (pixels) { - _pixels = pixels; - } - - glClearColor(0, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - - GB_frame_blending_mode_t mode = config_get_frame_blending_mode(); - if (!previous) { - mode = GB_FRAME_BLENDING_MODE_DISABLED; - } - else if (mode == GB_FRAME_BLENDING_MODE_ACCURATE) { - if (GB_is_sgb(&gb)) { - mode = GB_FRAME_BLENDING_MODE_SIMPLE; - } - else { - mode = GB_is_odd_frame(&gb)? GB_FRAME_BLENDING_MODE_ACCURATE_ODD : GB_FRAME_BLENDING_MODE_ACCURATE_EVEN; - } - } - - render_bitmap_with_shader( - &gui_data.shader, _pixels, previous, - GB_get_screen_width(&gb), GB_get_screen_height(&gb), - gui_data.viewport.x, gui_data.viewport.y, gui_data.viewport.width, gui_data.viewport.height, - mode - ); -} - -// TODO: Comment -static void gl_finish(void) { } - -// TODO: Comment -static void resize(void) { - update_viewport(); -} - -// TODO: Comment -static 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); - - guint screen_width = GB_get_screen_width(&gb); - guint screen_height = GB_get_screen_height(&gb); - - gtk_render_background(context, cr, 0, 0, width, height); - gtk_render_frame(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) - ); - - cairo_translate(cr, gui_data.viewport.x, gui_data.viewport.y); - cairo_scale(cr, gui_data.viewport.width / screen_width, gui_data.viewport.height / 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; -} - -// Create a GtkDrawingArea as a fallback in case we can’t use OpenGL -static void create_fallback_canvas(void) { - gui_data.fallback_canvas = GTK_DRAWING_AREA(gtk_drawing_area_new()); - g_signal_connect(gui_data.fallback_canvas, "draw", G_CALLBACK(on_draw_fallback), NULL); - g_signal_connect(gui_data.fallback_canvas, "size-allocate", G_CALLBACK(resize), NULL); - gtk_box_pack_end(GTK_BOX(gui_data.main_window_container), GTK_WIDGET(gui_data.fallback_canvas), true, true, 0); -} - -// TODO: Comment -static void gl_init(GtkWidget *w) { - GtkGLArea *gl_area = GTK_GL_AREA(w); - - g_debug("GL_INIT"); - const char *renderer; - - g_debug("GL Context: %p", 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_debug("GtkGLArea on %s", renderer ? renderer : "Unknown"); - - if (config.video.shader == NULL || (!init_shader_with_name(&gui_data.shader, config.video.shader) && !init_shader_with_name(&gui_data.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_warning("GtkGLArea: %s", gtk_gl_area_get_error(gl_area)->message); - } - - create_fallback_canvas(); -} - -static void create_canvas(void) { - // create our renderer area - if (gui_data.supports_gl) { - gui_data.gl_area = GTK_GL_AREA(gtk_gl_area_new()); - gtk_gl_area_set_required_version(gui_data.gl_area, 3, 2); - gtk_gl_area_set_auto_render(gui_data.gl_area, false); - gtk_gl_area_set_has_alpha(gui_data.gl_area, false); - gtk_gl_area_set_has_depth_buffer(gui_data.gl_area, false); - gtk_gl_area_set_has_stencil_buffer(gui_data.gl_area, false); - g_signal_connect(gui_data.gl_area, "realize", G_CALLBACK(gl_init), NULL); - gtk_box_pack_end(GTK_BOX(gui_data.main_window_container), GTK_WIDGET(gui_data.gl_area), true, true, 0); - } - else { - create_fallback_canvas(); - } - - GdkScreen *screen = gdk_screen_get_default(); - GtkCssProvider *provider = gtk_css_provider_new(); - gtk_css_provider_load_from_resource(provider, RESOURCE_PREFIX "css/main.css"); - gtk_style_context_add_provider_for_screen(screen, GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); -} - static void setup_console(void) { GtkTextView *text_view = builder_get(GTK_TEXT_VIEW, "console_screen"); GtkTextBuffer *text_buf = gtk_text_view_get_buffer(text_view); @@ -1614,7 +1364,7 @@ static void activate(GApplication *app, gpointer null_ptr) { init_controllers(); connect_signal_handlers(app); - create_canvas(); + // create_canvas(); setup_console(); if (gui_data.cli_options.fullscreen) { @@ -1624,8 +1374,6 @@ static void activate(GApplication *app, gpointer null_ptr) { gtk_application_add_window(GTK_APPLICATION(app), GTK_WINDOW(gui_data.main_window)); gtk_widget_show_all(GTK_WIDGET(gui_data.main_window)); - update_window_geometry(); - // Start the emulation thread run(); } @@ -1637,12 +1385,7 @@ static void shutdown(GApplication *app, GFile **files, gint n_files, const gchar stop(); SDL_Quit(); - if (gui_data.image_buffers[0]) g_free(gui_data.image_buffers[0]); - if (gui_data.image_buffers[1]) g_free(gui_data.image_buffers[1]); - if (gui_data.image_buffers[2]) g_free(gui_data.image_buffers[2]); - free_shader(&gui_data.shader); - free_master_shader(); - + gtk_widget_destroy(GTK_WIDGET(gui_data.screen)); GB_free(&gb); g_object_unref(gui_data.builder); @@ -1705,19 +1448,10 @@ static void close_rom(void) { stop(); GB_free(&gb); - // Clear the screen as side effect - update_window_geometry(); - - if (gui_data.fallback_canvas) { - gtk_widget_queue_draw(GTK_WIDGET(gui_data.main_window)); - } - else if (gui_data.gl_area) { - gtk_gl_area_queue_render(gui_data.gl_area); - } + gb_screen_clear(gui_data.screen); + gtk_widget_queue_draw(GTK_WIDGET(gui_data.screen)); vram_viewer_clear(gui_data.vram_viewer); - - // Redraw the VRAM viewer gtk_widget_queue_draw(GTK_WIDGET(gui_data.vram_viewer)); // Update menu action states @@ -1884,8 +1618,7 @@ G_MODULE_EXPORT void on_graphic_filter_changed(GtkWidget *w, gpointer user_data_ GtkComboBox *box = GTK_COMBO_BOX(w); config.video.shader = (gchar *)gtk_combo_box_get_active_id(box); - free_shader(&gui_data.shader); - init_shader_with_name(&gui_data.shader, config.video.shader); + gb_screen_set_shader(gui_data.screen, config.video.shader); } G_MODULE_EXPORT void on_highpass_filter_changed(GtkWidget *w, gpointer user_data_ptr) { @@ -1900,7 +1633,6 @@ G_MODULE_EXPORT void on_keep_aspect_ratio_changed(GtkWidget *w, gpointer user_da GtkCheckButton *button = GTK_CHECK_BUTTON(w); gboolean value = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)); config.video.keep_aspect_ratio = value; - update_viewport(); } G_MODULE_EXPORT void on_rewind_duration_changed(GtkWidget *w, gpointer user_data_ptr) { @@ -1932,7 +1664,6 @@ G_MODULE_EXPORT void on_use_integer_scaling_changed(GtkWidget *w, gpointer user_ GtkCheckButton *button = GTK_CHECK_BUTTON(w); gboolean value = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)); config.video.use_integer_scaling = value; - update_viewport(); } G_MODULE_EXPORT void console_on_enter(GtkWidget *w, gpointer user_data_ptr) { diff --git a/gtk3/types.h b/gtk3/types.h index 3d781a5..b09c222 100644 --- a/gtk3/types.h +++ b/gtk3/types.h @@ -2,7 +2,7 @@ #define types_h #include "SDL.h" -#include "shader.h" +#include "gb_screen.h" #include "vram_viewer_window.h" typedef struct{ @@ -38,8 +38,9 @@ typedef struct GuiData { GtkBuilder *builder; GtkApplicationWindow *main_window; GtkBox *main_window_container; - GtkGLArea *gl_area; - GtkDrawingArea *fallback_canvas; + + GbScreen *screen; + GtkWindow *preferences; VramViewerWindow *vram_viewer; GtkWindow *memory_viewer; @@ -59,13 +60,8 @@ typedef struct GuiData { // Audio and video bool audio_initialized; - uint32_t *image_buffers[3]; - unsigned char current_buffer; - Rect viewport; bool border_mode_changed; bool is_fullscreen; - bool supports_gl; - shader_t shader; unsigned last_screen_width; unsigned last_screen_height; diff --git a/gtk3/vram_viewer_window.c b/gtk3/vram_viewer_window.c index 4126370..18734ce 100644 --- a/gtk3/vram_viewer_window.c +++ b/gtk3/vram_viewer_window.c @@ -52,8 +52,8 @@ static void visible_tab_changed(GObject *stack, GParamSpec *pspec, VramViewerWin static gboolean draw_tileset_canvas(GtkWidget *widget, cairo_t *cr, VramViewerWindow *window) { GtkStyleContext *context = gtk_widget_get_style_context(widget); - guint width = gtk_widget_get_allocated_width(widget); - guint height = gtk_widget_get_allocated_height(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); @@ -100,8 +100,8 @@ static gboolean draw_tileset_canvas(GtkWidget *widget, cairo_t *cr, VramViewerWi static gboolean draw_tilemap_canvas(GtkWidget *widget, cairo_t *cr, VramViewerWindow *window) { GtkStyleContext *context = gtk_widget_get_style_context(widget); - guint width = gtk_widget_get_allocated_width(widget); - guint height = gtk_widget_get_allocated_height(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);