diff --git a/Makefile b/Makefile index 394084a..65a5b19 100644 --- a/Makefile +++ b/Makefile @@ -108,8 +108,11 @@ ifneq ($(findstring gtk3,$(MAKECMDGOALS)),) $(error The gtk3 target requires pkg-config) endif else -GTK3_CFLAGS := $(shell $(PKG_CONFIG) --cflags gtk+-3.0) -DGTK_DISABLE_DEPRECATED=1 -DG_DISABLE_DEPRECATED=1 -GTK3_LDFLAGS := $(shell $(PKG_CONFIG) --libs gtk+-3.0) +GTK3_CFLAGS := $(shell $(PKG_CONFIG) --cflags gio-2.0 gtk+-3.0 epoxy) -DGTK_DISABLE_DEPRECATED=1 -DG_DISABLE_DEPRECATED=1 -DRESOURCE_PREFIX=\"/io/github/sameboy/\" -DAPP_ID=\"io.github.sameboy\" +GTK3_LDFLAGS := $(shell $(PKG_CONFIG) --libs gio-2.0 gtk+-3.0 epoxy) + +# TODO: REMOVE DISABLE UNUSED WARNINGS +GTK3_CFLAGS += -Wno-unused endif ifeq ($(PLATFORM),windows32) diff --git a/gtk3/main.c b/gtk3/main.c index ddfa699..f2fa267 100644 --- a/gtk3/main.c +++ b/gtk3/main.c @@ -1,27 +1,87 @@ #include +#include #include #include #include #include #include +#include "shader.h" #define str(x) #x #define xstr(x) str(x) -#define RESOURCE_PREFIX "/io/github/sameboy/" -#define APP_ID "io.github.sameboy" - GtkBuilder *builder; GtkWindow *main_window; GtkGLArea *gl_area; +shader_t shader; GB_gameboy_t gb; +static bool paused = false; +static uint32_t pixel_buffer_1[256 * 224], pixel_buffer_2[256 * 224]; +static uint32_t *active_pixel_buffer = pixel_buffer_1, *previous_pixel_buffer = pixel_buffer_2; +static bool underclock_down = false, rewind_down = false, do_rewind = false, rewind_paused = false, turbo_down = false; +static double clock_mutliplier = 1.0; + +static typeof(free) *free_function = NULL; +static char *battery_save_path_ptr; + typedef struct UserData { bool fullscreen; GFile *file; } UserData; +typedef struct{ + int16_t x, y; + uint16_t w, h; +} Rect; + +static void run(UserData *user_data); + +static Rect rect; + +void render_texture(void *pixels, void *previous) { + static void *_pixels = NULL; + if (pixels) { + _pixels = pixels; + } + + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + + render_bitmap_with_shader(&shader, _pixels, previous, GB_get_screen_width(&gb), GB_get_screen_height(&gb), rect.x, rect.y, rect.w, rect.h); + gtk_widget_queue_draw(GTK_WIDGET(gl_area)); +} + +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)); + + double x_factor = win_width / (double) GB_get_screen_width(&gb); + double y_factor = win_height / (double) GB_get_screen_height(&gb); + + if (true /*configuration.scaling_mode == GB_SDL_SCALING_INTEGER_FACTOR*/) { + x_factor = (int)(x_factor); + y_factor = (int)(y_factor); + } + + /*if (configuration.scaling_mode != GB_SDL_SCALING_ENTIRE_WINDOW) { + 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); + + rect = (Rect){(win_width - new_width) / 2, (win_height - new_height) / 2, new_width, new_height}; + + glViewport(rect.x, rect.y, rect.w, rect.h); +} + // Determines if a ComboBox entry should be converted into a separator. // Each element with a text value of `` will be converted into a separator element. static gboolean is_separator(GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { @@ -108,6 +168,43 @@ G_MODULE_EXPORT void on_boot_rom_location_changed(GtkWidget *w, gpointer user_da g_print("Active: %s", gtk_combo_box_get_active_id(box)); } +G_MODULE_EXPORT void gl_draw(GtkWidget *window) { + render_texture(active_pixel_buffer, NULL); +} + +G_MODULE_EXPORT void gl_init(GtkWidget *window) { + const char *renderer; + + GList *children = gtk_container_get_children(GTK_CONTAINER(window)); + + while (children) { + if (GTK_IS_GL_AREA(children->data)) { + gl_area = GTK_GL_AREA(children->data); + break; + } + + children = children->next; + } + g_list_free(children); + + if (gl_area == NULL) { + g_printerr("Unable to find GtkGLArea in window\n"); + return; + } + + gtk_gl_area_make_current(gl_area); + if (gtk_gl_area_get_error(gl_area) != NULL) { + return; + } + + renderer = (char *) glGetString(GL_RENDERER); + g_print("GtkGLArea on %s\n", renderer ? renderer : "Unknown"); + + if (!init_shader_with_name(&shader, /*configuration.filter*/ "OmniScale")) { + init_shader_with_name(&shader, "NearestNeighbor"); + } +} + // 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; @@ -180,10 +277,6 @@ static void startup(GApplication *app, gpointer user_data_gptr) { gtk_about_dialog_set_logo(about_dialog, g_list_nth_data(icon_list, 3)); // reuse the 64x64 icon gtk_about_dialog_set_version(about_dialog, "v" xstr(VERSION)); g_list_free(icon_list); - - GList *children = gtk_container_get_children(GTK_CONTAINER(main_window)); - gl_area = GTK_GL_AREA(g_list_first(children)); - g_list_free(children); } // This function gets called when the GApplication gets activated, i.e. it is ready to show widgets. @@ -197,6 +290,9 @@ static void activate(GApplication *app, gpointer user_data_gptr) { g_signal_connect(main_window, "destroy", G_CALLBACK(gtk_main_quit), NULL); gtk_application_add_window(GTK_APPLICATION(app), main_window); gtk_widget_show_all(GTK_WIDGET(main_window)); + + update_viewport(); + run(user_data); } // This function gets called when there are files to open. @@ -233,7 +329,77 @@ static gint handle_local_options(GApplication *app, GVariantDict *options, gpoin return -1; } -int main (int argc, char *argv[]) { +static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) { + uint32_t color = 0xFF000000 | (b << 16) | (g << 8) | r; // abgr + return color; +} + +static void vblank(GB_gameboy_t *gb) { + // render_texture(active_pixel_buffer, NULL); + + while (gtk_events_pending()) { + gtk_main_iteration(); + } +} + +static void run(UserData *user_data) { + GB_model_t model = GB_MODEL_CGB_E; + + if (GB_is_inited(&gb)) { + GB_switch_model_and_reset(&gb, model); + } + else { + GB_init(&gb, model); + + GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); + GB_set_pixels_output(&gb, active_pixel_buffer); + GB_set_rgb_encode_callback(&gb, rgb_encode); + // GB_set_sample_rate(&gb, have_aspec.freq); + // GB_set_color_correction_mode(&gb, configuration.color_correction_mode); + // GB_set_highpass_filter_mode(&gb, configuration.highpass_mode); + // GB_set_rewind_length(&gb, configuration.rewind_length); + // GB_set_update_input_hint_callback(&gb, handle_events); + // GB_apu_set_sample_callback(&gb, gb_audio_callback); + } + + GError *gerror; + GBytes *boot_rom_f; + const guchar *boot_rom_data; + gsize boot_rom_size; + + boot_rom_f = g_resources_lookup_data(RESOURCE_PREFIX "bootroms/cgb_boot.bin", G_RESOURCE_LOOKUP_FLAGS_NONE, &gerror); + boot_rom_data = g_bytes_get_data(boot_rom_f, &boot_rom_size); + + GB_load_boot_rom_from_buffer(&gb, boot_rom_data, boot_rom_size); + + if (user_data->file != NULL) { + GB_load_rom(&gb, g_file_get_path(user_data->file)); + } + + /* Run emulation */ + while (true) { + if (paused || rewind_paused) { + while (gtk_events_pending()) { + gtk_main_iteration(); + } + } + else { + if (do_rewind) { + GB_rewind_pop(&gb); + if (turbo_down) { + GB_rewind_pop(&gb); + } + if (!GB_rewind_pop(&gb)) { + rewind_paused = true; + } + do_rewind = false; + } + GB_run(&gb); + } + } +} + +int main(int argc, char *argv[]) { // Create our GApplication and tell GTK that we are able to handle files GtkApplication *app = gtk_application_new(APP_ID, G_APPLICATION_HANDLES_OPEN); diff --git a/gtk3/resources/ui/window.ui b/gtk3/resources/ui/window.ui index 4dc41ae..069c702 100644 --- a/gtk3/resources/ui/window.ui +++ b/gtk3/resources/ui/window.ui @@ -979,6 +979,9 @@ Maximilian Mader https://github.com/max-m True True False + + + @@ -1017,6 +1020,9 @@ Maximilian Mader https://github.com/max-m True True False + + + diff --git a/gtk3/shader.c b/gtk3/shader.c new file mode 100644 index 0000000..f2d347f --- /dev/null +++ b/gtk3/shader.c @@ -0,0 +1,206 @@ +#include +#include +#include "shader.h" + +static const char *vertex_shader = "\n\ +#version 150 \n\ +in vec4 aPosition;\n\ +void main(void) {\n\ +gl_Position = aPosition;\n\ +}\n\ +"; + +static GLuint create_shader(const char *source, GLenum type) +{ + // Create the shader object + GLuint shader = glCreateShader(type); + // Load the shader source + glShaderSource(shader, 1, &source, 0); + // Compile the shader + glCompileShader(shader); + // Check for errors + GLint status = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + if (status == GL_FALSE) { + GLchar messages[1024]; + glGetShaderInfoLog(shader, sizeof(messages), 0, &messages[0]); + g_printerr("GLSL Shader Error: %s", messages); + } + return shader; +} + +static GLuint create_program(const char *vsh, const char *fsh) +{ + // Build shaders + GLuint vertex_shader = create_shader(vsh, GL_VERTEX_SHADER); + GLuint fragment_shader = create_shader(fsh, GL_FRAGMENT_SHADER); + + // Create program + GLuint program = glCreateProgram(); + + // Attach shaders + glAttachShader(program, vertex_shader); + glAttachShader(program, fragment_shader); + + // Link program + glLinkProgram(program); + // Check for errors + GLint status; + glGetProgramiv(program, GL_LINK_STATUS, &status); + + if (status == GL_FALSE) { + GLchar messages[1024]; + glGetProgramInfoLog(program, sizeof(messages), 0, &messages[0]); + g_printerr("GLSL Program Error: %s", messages); + } + + // Delete shaders + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + + return program; +} + +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) { + return false; + } + + GError *error; + GBytes *master_shader_f; + static const gchar *master_shader_code; + static gsize master_shader_code_size; + + static char final_shader_code[0x10801] = {0,}; + static signed long filter_token_location = 0; + + if (!master_shader_code_size) { + master_shader_f = g_resources_lookup_data(RESOURCE_PREFIX "Shaders/MasterShader.fsh", G_RESOURCE_LOOKUP_FLAGS_NONE, &error); + master_shader_code = g_bytes_get_data(master_shader_f, &master_shader_code_size); + + if (!master_shader_f) { + g_printerr("Failed to load master shader: %s", error->message); + return false; + } + + filter_token_location = strstr(master_shader_code, "{filter}") - master_shader_code; + if (filter_token_location < 0) { + return false; + } + } + + char shader_path[1024]; + g_snprintf(shader_path, sizeof(shader_path), RESOURCE_PREFIX "Shaders/%s.fsh", name); + + GBytes *shader_f = g_resources_lookup_data(shader_path, G_RESOURCE_LOOKUP_FLAGS_NONE, &error); + if (!shader_f) { + g_printerr("Failed to load shader \"%s\": %s", shader_path, error->message); + return false; + } + + gsize shader_code_size; + const gchar *shader_code = g_bytes_get_data(shader_f, &shader_code_size); + + memset(final_shader_code, 0, sizeof(final_shader_code)); + memcpy(final_shader_code, master_shader_code, filter_token_location); + strcpy(final_shader_code + filter_token_location, shader_code); + strcat(final_shader_code + filter_token_location, + master_shader_code + filter_token_location + sizeof("{filter}") - 1); + + shader->program = create_program(vertex_shader, final_shader_code); + + // Attributes + shader->position_attribute = glGetAttribLocation(shader->program, "aPosition"); + // Uniforms + shader->resolution_uniform = glGetUniformLocation(shader->program, "output_resolution"); + shader->origin_uniform = glGetUniformLocation(shader->program, "origin"); + + glGenTextures(1, &shader->texture); + glBindTexture(GL_TEXTURE_2D, shader->texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, 0); + shader->texture_uniform = glGetUniformLocation(shader->program, "image"); + + glGenTextures(1, &shader->previous_texture); + glBindTexture(GL_TEXTURE_2D, shader->previous_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, 0); + shader->previous_texture_uniform = glGetUniformLocation(shader->program, "previous_image"); + + shader->mix_previous_uniform = glGetUniformLocation(shader->program, "mix_previous"); + + // Program + + glUseProgram(shader->program); + + GLuint vao; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + GLuint vbo; + glGenBuffers(1, &vbo); + + // Attributes + + static GLfloat const quad[16] = { + -1.f, -1.f, 0, 1, + -1.f, +1.f, 0, 1, + +1.f, -1.f, 0, 1, + +1.f, +1.f, 0, 1, + }; + + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(quad), quad, GL_STATIC_DRAW); + glEnableVertexAttribArray(shader->position_attribute); + glVertexAttribPointer(shader->position_attribute, 4, GL_FLOAT, GL_FALSE, 0, 0); + + return true; +} + +void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous, + unsigned source_width, unsigned source_height, + unsigned x, unsigned y, unsigned w, unsigned h) +{ + glUseProgram(shader->program); + glUniform2f(shader->origin_uniform, x, y); + glUniform2f(shader->resolution_uniform, w, h); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, shader->texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, source_width, source_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap); + glUniform1i(shader->texture_uniform, 0); + glUniform1i(shader->mix_previous_uniform, previous != NULL); + if (previous) { + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, shader->previous_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, source_width, source_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, previous); + glUniform1i(shader->previous_texture_uniform, 1); + } + glBindFragDataLocation(shader->program, 0, "frag_color"); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); +} + +void free_shader(shader_t *shader) +{ + GLint major = 0, minor = 0; + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MINOR_VERSION, &minor); + + if (major * 0x100 + minor < 0x302) { + return; + } + + glDeleteProgram(shader->program); + glDeleteTextures(1, &shader->texture); + glDeleteTextures(1, &shader->previous_texture); +} diff --git a/gtk3/shader.h b/gtk3/shader.h new file mode 100644 index 0000000..ebd3433 --- /dev/null +++ b/gtk3/shader.h @@ -0,0 +1,25 @@ +#ifndef shader_h +#define shader_h +#include +#include + +typedef struct shader_s { + GLuint resolution_uniform; + GLuint origin_uniform; + GLuint texture_uniform; + GLuint previous_texture_uniform; + GLuint mix_previous_uniform; + + GLuint position_attribute; + GLuint texture; + GLuint previous_texture; + GLuint program; +} shader_t; + +bool init_shader_with_name(shader_t *shader, const char *name); +void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous, + unsigned source_width, unsigned source_height, + unsigned x, unsigned y, unsigned w, unsigned h); +void free_shader(struct shader_s *shader); + +#endif /* shader_h */