diff --git a/gtk3/main.c b/gtk3/main.c index 6b5c9ed..976ee1e 100644 --- a/gtk3/main.c +++ b/gtk3/main.c @@ -38,21 +38,23 @@ static GtkWindow *printer; static shader_t shader; -static UserData user_data = { NULL }; +static GuiData gui_data = { NULL }; static GB_gameboy_t gb; static uint32_t *image_buffers[3]; static unsigned char current_buffer; static bool supports_gl; +static bool is_fullscreen; -static bool paused = false; static bool underclock_down = false, rewind_down = false, do_rewind = false, rewind_paused = false, turbo_down = false; static double clock_mutliplier = 1.0; static char *battery_save_path_ptr; static Rect viewport = {0}; static Rect scrollRect = {0}; static bool vram_viewer_visible = false; -static bool running = true; + +static bool running = false; +static bool stopping = false; #define tileset_buffer_length 256 * 192 * 4 static uint32_t tileset_buffer[tileset_buffer_length] = {0}; @@ -81,6 +83,12 @@ static const GActionEntry app_entries[] = { { "open_memory_viewer", activate_open_memory_viewer, NULL, NULL, NULL }, { "open_vram_viewer", activate_open_vram_viewer, NULL, NULL, NULL }, { "preferences", activate_preferences, NULL, NULL, NULL }, + { "reset", activate_reset, NULL, NULL, NULL }, + { "toggle_blend_frames", NULL, NULL, "true", NULL }, + { "toggle_developer_mode", NULL, NULL, "false", NULL }, + { "toggle_mute", NULL, NULL, "false", on_mute_changed }, + { "change_model", NULL, "s", "@s 'CGB'", on_model_changed }, + { "pause", NULL, NULL, "false", on_pause_changed }, }; int main(int argc, char *argv[]) { @@ -91,9 +99,9 @@ int main(int argc, char *argv[]) { GOptionEntry entries[] = { { "version", 'v', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, "Show the application version", NULL }, { "fullscreen", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, "Start in fullscreen mode", NULL }, - { "bootrom", 'b', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &user_data.boot_rom_path, "Path to the boot ROM to use", "" }, + { "bootrom", 'b', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &gui_data.boot_rom_path, "Path to the boot ROM to use", "" }, { "model", 'm', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, NULL, "Override the model type to emulate", "" }, - { "config", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &user_data.config_path, "Override the path of the configuration file", "" }, + { "config", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &gui_data.config_path, "Override the path of the configuration file", "" }, { NULL } }; // Setup our command line information @@ -102,11 +110,11 @@ int main(int argc, char *argv[]) { g_application_set_option_context_summary(G_APPLICATION(main_application), "SameBoy is an open source Game Boy (DMG) and Game Boy Color (CGB) emulator."); // Add signal handlers - g_signal_connect(main_application, "handle-local-options", G_CALLBACK(handle_local_options), &user_data); - g_signal_connect(main_application, "startup", G_CALLBACK(startup), &user_data); - g_signal_connect(main_application, "activate", G_CALLBACK(activate), &user_data); - g_signal_connect(main_application, "open", G_CALLBACK(open), &user_data); - g_signal_connect(main_application, "shutdown", G_CALLBACK(shutdown), &user_data); + g_signal_connect(main_application, "handle-local-options", G_CALLBACK(handle_local_options), &gui_data); + g_signal_connect(main_application, "startup", G_CALLBACK(startup), &gui_data); + g_signal_connect(main_application, "activate", G_CALLBACK(activate), &gui_data); + g_signal_connect(main_application, "open", G_CALLBACK(open), &gui_data); + g_signal_connect(main_application, "shutdown", G_CALLBACK(shutdown), &gui_data); // Start our GApplication main loop int status = g_application_run(G_APPLICATION(main_application), argc, argv); @@ -116,8 +124,8 @@ int main(int argc, char *argv[]) { } // This function gets called after the parsing of the commandline options has occurred. -static gint handle_local_options(GApplication *app, GVariantDict *options, gpointer user_data_gptr) { - UserData *user_data = user_data_gptr; +static gint handle_local_options(GApplication *app, GVariantDict *options, gpointer gui_data_gptr) { + GuiData *gui_data = gui_data_gptr; guint32 count; if (g_variant_dict_lookup(options, "version", "b", &count)) { @@ -126,7 +134,7 @@ static gint handle_local_options(GApplication *app, GVariantDict *options, gpoin } if (g_variant_dict_lookup(options, "fullscreen", "b", &count)) { - user_data->fullscreen = true; + gui_data->fullscreen = true; } // Handle model override @@ -137,42 +145,42 @@ static gint handle_local_options(GApplication *app, GVariantDict *options, gpoin // TODO: Synchronize with GB_model_t (Core/gb.h) if (g_str_has_prefix(model_name, "DMG")) { if (g_str_has_suffix(model_name, "-B") || g_strcmp0(model_name, "DMG") == 0) { - user_data->model = GB_MODEL_DMG_B; + gui_data->model = GB_MODEL_DMG_B; } else { - user_data->model = GB_MODEL_DMG_B; + gui_data->model = GB_MODEL_DMG_B; g_printerr("Unsupported revision: %s\nFalling back to DMG-B", model_name); } } else if (g_str_has_prefix(model_name, "SGB")) { if (g_str_has_suffix(model_name, "-NTSC") || g_strcmp0(model_name, "SGB") == 0) { - user_data->model = GB_MODEL_SGB; + gui_data->model = GB_MODEL_SGB; } else if (g_str_has_suffix(model_name, "-PAL")) { - user_data->model = GB_MODEL_SGB | GB_MODEL_PAL_BIT; + gui_data->model = GB_MODEL_SGB | GB_MODEL_PAL_BIT; } else if (g_str_has_suffix(model_name, "2")) { - user_data->model = GB_MODEL_SGB2; + gui_data->model = GB_MODEL_SGB2; } else { - user_data->model = GB_MODEL_SGB2; + gui_data->model = GB_MODEL_SGB2; g_printerr("Unsupported revision: %s\nFalling back to SGB2", model_name); } } else if (g_str_has_prefix(model_name, "CGB")) { if (g_str_has_suffix(model_name, "-C")) { - user_data->model = GB_MODEL_CGB_C; + gui_data->model = GB_MODEL_CGB_C; } else if (g_str_has_suffix(model_name, "-E") || g_strcmp0(model_name, "CGB") == 0) { - user_data->model = GB_MODEL_CGB_E; + gui_data->model = GB_MODEL_CGB_E; } else { - user_data->model = GB_MODEL_CGB_E; + gui_data->model = GB_MODEL_CGB_E; g_printerr("Unsupported revision: %s\nFalling back to CGB-E", model_name); } } else if (g_str_has_prefix(model_name, "AGB")) { - user_data->model = GB_MODEL_AGB; + gui_data->model = GB_MODEL_AGB; } else { g_printerr("Unknown model: %s\n", model_name); @@ -190,7 +198,7 @@ static gint handle_local_options(GApplication *app, GVariantDict *options, gpoin gboolean test_gl_support(void) { gboolean result = FALSE; - GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL); g_signal_connect(window, "realize", G_CALLBACK(gl_check_realize), &result); gtk_widget_realize(window); gtk_widget_destroy(window); @@ -271,13 +279,18 @@ static gboolean init_controllers() { } static gboolean init_audio() { + bool audio_playing = SDL_GetAudioDeviceStatus(device_id) == SDL_AUDIO_PLAYING; + SDL_PauseAudioDevice(device_id, 1); + SDL_ClearQueuedAudio(device_id); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { g_print("Failed to initialize audio: %s\n", SDL_GetError()); return FALSE; } memset(&want_aspec, 0, sizeof(want_aspec)); - want_aspec.freq = DEFAULT_AUDIO_SAMPLE_RATE; + want_aspec.freq = gui_data.sample_rate; want_aspec.format = AUDIO_S16SYS; want_aspec.channels = 2; want_aspec.samples = 512; @@ -302,7 +315,10 @@ static gboolean init_audio() { device_id = SDL_OpenAudioDevice(0, 0, &want_aspec, &have_aspec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_SAMPLES_CHANGE); - SDL_PauseAudioDevice(device_id, 0); + g_print("Requested Sample Rate: %d Hz\nUsed Sample Rate: %d Hz\n", want_aspec.freq, have_aspec.freq); + + SDL_PauseAudioDevice(device_id, audio_playing? 0 : 1); + GB_set_sample_rate(&gb, have_aspec.freq); return TRUE; } @@ -327,6 +343,8 @@ static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample) { } static char *sync_console_input(GB_gameboy_t *gb) { + console_log(gb, ">", 0); + g_mutex_lock(&debugger_input_mutex); g_cond_wait(&debugger_input_cond, &debugger_input_mutex); @@ -359,12 +377,6 @@ static char *async_console_input(GB_gameboy_t *gb) { return input; } -typedef struct LogData { - GB_gameboy_t *gb; - const char *string; - GB_log_attributes attributes; -} LogData; - static void on_console_log(gpointer user_data_gptr) { LogData *log_data = (LogData *)user_data_gptr; GB_gameboy_t *gb = log_data->gb; @@ -512,21 +524,19 @@ static void setup_menu(GApplication *app) { // Recursively goes through all children of the given container and sets // our `is_separator` function to all children of type`GtkComboBox` static void set_combo_box_row_separator_func(GtkContainer *container) { - GList *list = gtk_container_get_children(container); + GList *children = gtk_container_get_children(container); - while (list) { - if (GTK_IS_COMBO_BOX(list->data)) { - gtk_combo_box_set_row_separator_func(GTK_COMBO_BOX(list->data), is_separator, NULL, NULL); + for (GList *l = children; l; l = l->next) { + if (GTK_IS_COMBO_BOX(l->data)) { + gtk_combo_box_set_row_separator_func(GTK_COMBO_BOX(l->data), is_separator, NULL, NULL); } - if (GTK_IS_CONTAINER(list->data)) { - set_combo_box_row_separator_func(GTK_CONTAINER(list->data)); + if (GTK_IS_CONTAINER(l->data)) { + set_combo_box_row_separator_func(GTK_CONTAINER(l->data)); } - - list = list->next; } - g_list_free_full(list, NULL); + g_list_free(children); } // Determines if a ComboBox entry should be converted into a separator. @@ -536,14 +546,19 @@ static gboolean is_separator(GtkTreeModel *model, GtkTreeIter *iter, gpointer da gtk_tree_model_get(model, iter, 0, &text, -1); gboolean result = g_strcmp0("", text) == 0; + g_free(text); return result; } // Determines how many frame buffers to use static unsigned char number_of_buffers(void) { - // TODO - bool should_blend = !fallback_canvas; + if (fallback_canvas) return 2; + + // TODO: Should we cache the action? + GAction *action = g_action_map_lookup_action(G_ACTION_MAP(main_application), "toggle_blend_frames"); + GVariant *value = g_action_get_state(action); + gboolean should_blend = g_variant_get_boolean(value); return should_blend? 3 : 2; } @@ -573,8 +588,8 @@ static void quit_interrupt(int ignored) { } // 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; +static void startup(GApplication *app, gpointer gui_data_gptr) { + GuiData *gui_data = gui_data_gptr; signal(SIGINT, quit_interrupt); @@ -605,14 +620,21 @@ static void startup(GApplication *app, gpointer user_data_gptr) { preferences = GTK_WINDOW(get_object("preferences")); g_signal_connect(preferences, "realize", G_CALLBACK(on_preferences_realize), (gpointer) builder); - init_settings(user_data->config_path, preferences); + init_settings(gui_data->config_path, preferences); vram_viewer = GTK_WINDOW(get_object("vram_viewer")); memory_viewer = GTK_WINDOW(get_object("memory_viewer")); - + console = GTK_WINDOW(get_object("console")); printer = GTK_WINDOW(get_object("printer")); + if (config.sample_rate == -1) { + gui_data->sample_rate = DEFAULT_AUDIO_SAMPLE_RATE; + } + else { + gui_data->sample_rate = config.sample_rate; + } + // setup main window main_window = GTK_APPLICATION_WINDOW(gtk_application_window_new(GTK_APPLICATION(app))); main_window_container = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0)); @@ -652,18 +674,20 @@ static void startup(GApplication *app, gpointer user_data_gptr) { // Add missing information to the about dialog GtkAboutDialog *about_dialog = GTK_ABOUT_DIALOG(get_object("about_dialog")); - gtk_about_dialog_set_logo(about_dialog, g_list_nth_data(icon_list, 3)); // reuse the 64x64 icon + 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(icon_list); + g_list_free_full(icon_list, g_object_unref); } // This function gets called when the GApplication gets activated, i.e. it is ready to show widgets. -static void activate(GApplication *app, gpointer user_data_gptr) { - UserData *user_data = user_data_gptr; +static void activate(GApplication *app, gpointer gui_data_gptr) { + GuiData *gui_data = gui_data_gptr; - if (user_data->fullscreen) { - gtk_window_fullscreen(GTK_WINDOW(main_window)); - } + // initialize SameBoy core + init(gui_data); + + init_audio(); + init_controllers(); // Connect signal handlers gtk_widget_add_events(GTK_WIDGET(main_window), GDK_KEY_PRESS_MASK); @@ -672,7 +696,8 @@ static void activate(GApplication *app, gpointer user_data_gptr) { g_signal_connect(main_window, "destroy", G_CALLBACK(on_quit), app); g_signal_connect(main_window, "key_press_event", G_CALLBACK(on_key_press), NULL); g_signal_connect(main_window, "key_release_event", G_CALLBACK(on_key_press), NULL); - + g_signal_connect(main_window, "window-state-event", G_CALLBACK(on_window_state_change), NULL); + g_signal_connect(vram_viewer, "realize", G_CALLBACK(on_vram_viewer_realize), NULL); g_signal_connect(vram_viewer, "unrealize", G_CALLBACK(on_vram_viewer_unrealize), NULL); @@ -712,7 +737,7 @@ static void activate(GApplication *app, gpointer user_data_gptr) { 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); + gtk_style_context_add_provider_for_screen(screen, GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); GtkTextView *text_view = builder_get(GTK_TEXT_VIEW, "console_screen"); GtkTextBuffer *text_buf = gtk_text_view_get_buffer(text_view); @@ -720,35 +745,61 @@ static void activate(GApplication *app, gpointer user_data_gptr) { gtk_text_buffer_create_tag(text_buf, "underline", "underline", PANGO_UNDERLINE_SINGLE, "underline-set", TRUE, NULL); gtk_text_buffer_create_tag(text_buf, "dashed_underline", "underline", PANGO_UNDERLINE_DOUBLE, "underline-set", TRUE, NULL); + if (gui_data->fullscreen) { + gtk_window_fullscreen(GTK_WINDOW(main_window)); + } + gtk_application_add_window(GTK_APPLICATION(app), GTK_WINDOW(main_window)); gtk_widget_show_all(GTK_WIDGET(main_window)); - g_thread_new("CoreLoop", run, user_data); + g_mutex_init(&debugger_input_mutex); + g_cond_init(&debugger_input_cond); + g_mutex_init(&console_output_lock); + + if (!debugger_input_queue) { + debugger_input_queue = g_ptr_array_sized_new(4); + } + + // Start the emulation thread + run(gui_data); } // This function gets called when the application is closed. -static void shutdown(GApplication *app, GFile **files, gint n_files, const gchar *hint, gpointer user_data_gptr) { +static void shutdown(GApplication *app, GFile **files, gint n_files, const gchar *hint, gpointer gui_data_gptr) { g_print("SHUTDOWN\n"); + stop(&gui_data); + while (stopping); + + g_object_unref(builder); + save_settings(); free_settings(); SDL_Quit(); + + if (image_buffers[0]) g_free(image_buffers[0]); + if (image_buffers[1]) g_free(image_buffers[1]); + if (image_buffers[2]) g_free(image_buffers[2]); + free_shader(&shader); + free_master_shader(); + + GB_free(&gb); } // This function gets called when there are files to open. // Note: When `open` gets called `activate` won’t fire unless we call it ourselves. -static void open(GApplication *app, GFile **files, gint n_files, const gchar *hint, gpointer user_data_gptr) { - UserData *user_data = user_data_gptr; +static void open(GApplication *app, GFile **files, gint n_files, const gchar *hint, gpointer gui_data_gptr) { + GuiData *gui_data = gui_data_gptr; if (n_files > 1) { g_printerr("More than one file specified\n"); exit(EXIT_FAILURE); } - user_data->file = g_file_dup(files[0]); + gui_data->file = g_file_dup(files[0]); // We have handled the files, now activate the application - activate(app, user_data_gptr); + activate(app, gui_data_gptr); } // Tell our application to quit. @@ -782,6 +833,17 @@ static gboolean on_key_press(GtkWidget *w, GdkEventKey *event, gpointer data) { case GDK_KEY_k: mask = BUTTON_MASK_B; break; case GDK_KEY_l: mask = BUTTON_MASK_A; break; + + case GDK_KEY_F11: { + if (event->type == GDK_KEY_RELEASE) { + if (is_fullscreen) { + gtk_window_unfullscreen(GTK_WINDOW(main_window)); + } + else { + gtk_window_fullscreen(GTK_WINDOW(main_window)); + } + } + } } if (event->type == GDK_KEY_PRESS) { @@ -794,6 +856,10 @@ static gboolean on_key_press(GtkWidget *w, GdkEventKey *event, gpointer data) { return FALSE; } +static void on_window_state_change(GtkWidget *w, GdkEventWindowState *event, gpointer data) { + is_fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN; +} + // app.about GAction // Opens the about dialog static void activate_about(GSimpleAction *action, GVariant *parameter, gpointer app) { @@ -858,6 +924,72 @@ static void activate_quit(GSimpleAction *action, GVariant *parameter, gpointer a quit(G_APPLICATION(app)); } +// app.reset GAction +// Resets the emulation +static void activate_reset(GSimpleAction *action, GVariant *parameter, gpointer app) { + if (gui_data.stopped) { + reset(&gui_data); + } + else { + stop(&gui_data); + reset(&gui_data); + run(&gui_data); + } +} + +static void on_model_changed(GSimpleAction *action, GVariant *value, gpointer user_data) { + const gchar *model_str = g_variant_get_string(value, NULL); + + GtkMessageDialog *dialog = GTK_MESSAGE_DIALOG(gtk_message_dialog_new( + GTK_WINDOW(main_window), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_YES_NO, + "Changing the emulated model requires a reset.\nChange model and reset game?" + )); + + stop(&gui_data); + gint result = gtk_dialog_run(GTK_DIALOG(dialog)); + + switch (result) { + case GTK_RESPONSE_YES: + g_print("TODO: RESET!\n"); + g_simple_action_set_state(action, value); + break; + default: + // Action has been canceled + break; + } + + run(&gui_data); + gtk_widget_destroy(GTK_WIDGET(dialog)); +} + +static void on_mute_changed(GSimpleAction *action, GVariant *value, gpointer user_data) { + gboolean do_mute = g_variant_get_boolean(value); + + if (do_mute) { + SDL_PauseAudioDevice(device_id, 1); + } + else { + SDL_ClearQueuedAudio(device_id); + SDL_PauseAudioDevice(device_id, 0); + } + + g_simple_action_set_state(action, value); +} + +static void on_pause_changed(GSimpleAction *action, GVariant *value, gpointer user_data) { + if (g_variant_get_boolean(value)) { + stop(&gui_data); + } + else { + run(&gui_data); + } + + g_simple_action_set_state(action, value); +} + // `destroy` signal GCallback // Exits the application static void on_quit(GtkWidget *w, gpointer app) { @@ -919,7 +1051,7 @@ static gboolean on_draw_fallback(GtkWidget *widget, cairo_t *cr, gpointer data) guint screen_height = GB_get_screen_height(&gb); gtk_render_background(context, cr, 0, 0, width, height); - + cairo_surface_t *surface = cairo_image_surface_create_for_data( (unsigned char *) get_current_buffer(), CAIRO_FORMAT_RGB24, @@ -1225,6 +1357,7 @@ G_MODULE_EXPORT void on_graphic_filter_changed(GtkWidget *w, gpointer user_data_ GtkComboBox *box = GTK_COMBO_BOX(w); config.shader = (gchar *)gtk_combo_box_get_active_id(box); + free_shader(&shader); init_shader_with_name(&shader, config.shader); } @@ -1248,6 +1381,20 @@ G_MODULE_EXPORT void on_rewind_duration_changed(GtkWidget *w, gpointer user_data config.rewind_duration = g_ascii_strtoll(gtk_combo_box_get_active_id(box), NULL, 10); } +G_MODULE_EXPORT void on_sample_rate_changed(GtkWidget *w, gpointer user_data_gptr) { + GtkComboBox *box = GTK_COMBO_BOX(w); + config.sample_rate = g_ascii_strtoll(gtk_combo_box_get_active_id(box), NULL, 10); + + if (config.sample_rate == -1) { + gui_data.sample_rate = DEFAULT_AUDIO_SAMPLE_RATE; + } + else { + gui_data.sample_rate = config.sample_rate; + } + + init_audio(); +} + G_MODULE_EXPORT void on_sgb_model_changed(GtkWidget *w, gpointer user_data_gptr) { GtkComboBox *box = GTK_COMBO_BOX(w); config.sgb_revision_name = (gchar *)gtk_combo_box_get_active_id(box); @@ -1343,15 +1490,19 @@ static void update_window_geometry() { ); // Setup our image buffers - if (image_buffers[0]) free(image_buffers[0]); - if (image_buffers[1]) free(image_buffers[1]); - if (image_buffers[2]) free(image_buffers[2]); + if (image_buffers[0]) g_free(image_buffers[0]); + if (image_buffers[1]) g_free(image_buffers[1]); + if (image_buffers[2]) g_free(image_buffers[2]); size_t buffer_size = sizeof(image_buffers[0][0]) * GB_get_screen_width(&gb) * GB_get_screen_height(&gb); - image_buffers[0] = malloc(buffer_size); - image_buffers[1] = malloc(buffer_size); - image_buffers[2] = malloc(buffer_size); + image_buffers[0] = g_malloc0(buffer_size); + image_buffers[1] = g_malloc0(buffer_size); + image_buffers[2] = g_malloc0(buffer_size); + + if (GB_is_inited(&gb)) { + GB_set_pixels_output(&gb, get_pixels()); + } } static void handle_events(GB_gameboy_t *gb) { @@ -1630,52 +1781,53 @@ static void vblank(GB_gameboy_t *gb) { g_idle_add((GSourceFunc) on_vblank, NULL); } -static gpointer run(gpointer user_data_gptr) { - UserData *user_data = user_data_gptr; +static void run(GuiData *gui_data) { + if (running) return; + while (stopping); - GB_model_t prev_model = GB_get_model(&gb); - GB_model_t model = user_data->model? user_data->model : GB_MODEL_CGB_E; // TODO: Model from config + g_thread_new("CoreLoop", run_thread, gui_data); +} - if (!debugger_input_queue) { - g_mutex_init(&debugger_input_mutex); - g_cond_init(&debugger_input_cond); - g_mutex_init(&console_output_lock); - debugger_input_queue = g_ptr_array_sized_new(4); - } +static gpointer run_thread(gpointer gui_data_gptr) { + GuiData *gui_data = gui_data_gptr; - if (GB_is_inited(&gb)) { - GB_switch_model_and_reset(&gb, model); - - GtkRequisition minimum_size; - GtkRequisition natural_size; - gtk_widget_get_preferred_size(GTK_WIDGET(main_window), &minimum_size, &natural_size); - - // Check SGB -> non-SGB and non-SGB to SGB transitions - if (GB_get_screen_width(&gb) != minimum_size.width || GB_get_screen_height(&gb) != minimum_size.height) { - update_window_geometry(); - } + if (gui_data->stopped) { + start(gui_data); } else { - init_audio(); - init_controllers(); - - GB_init(&gb, model); - update_window_geometry(); - - GB_set_vblank_callback(&gb, vblank); - GB_set_pixels_output(&gb, get_current_buffer()); - GB_set_rgb_encode_callback(&gb, rgb_encode); - GB_set_sample_rate(&gb, DEFAULT_AUDIO_SAMPLE_RATE); - GB_set_color_correction_mode(&gb, get_color_correction_mode()); - GB_set_highpass_filter_mode(&gb, get_highpass_mode()); - GB_set_rewind_length(&gb, config.rewind_duration); - GB_set_update_input_hint_callback(&gb, handle_events); - GB_apu_set_sample_callback(&gb, gb_audio_callback); - GB_set_input_callback(&gb, sync_console_input); - GB_set_async_input_callback(&gb, async_console_input); - GB_set_log_callback(&gb, console_log); + init(gui_data); + reset(gui_data); + start(gui_data); } + return NULL; +} + +static void init(GuiData *gui_data) { + if (GB_is_inited(&gb)) return; + g_print("init: %p\n", g_thread_self()); + + GB_model_t prev_model = GB_get_model(&gb); + gui_data->model = gui_data->model? gui_data->model : GB_MODEL_CGB_E; // TODO: Model from config + + GB_init(&gb, gui_data->model); + update_window_geometry(); + + GB_set_vblank_callback(&gb, vblank); + GB_set_pixels_output(&gb, get_current_buffer()); + GB_set_rgb_encode_callback(&gb, rgb_encode); + GB_set_sample_rate(&gb, gui_data->sample_rate); + GB_set_color_correction_mode(&gb, get_color_correction_mode()); + GB_set_highpass_filter_mode(&gb, get_highpass_mode()); + GB_set_rewind_length(&gb, config.rewind_duration); + GB_set_update_input_hint_callback(&gb, handle_events); + GB_apu_set_sample_callback(&gb, gb_audio_callback); + GB_set_input_callback(&gb, sync_console_input); + GB_set_async_input_callback(&gb, async_console_input); + GB_set_log_callback(&gb, console_log); +} + +static void load_boot_rom(GuiData *gui_data) { GError *error; char *boot_rom_path; char *boot_rom_name; @@ -1683,15 +1835,15 @@ static gpointer run(gpointer user_data_gptr) { const guchar *boot_rom_data; gsize boot_rom_size; - if (user_data->boot_rom_path != NULL) { - g_print("Trying to load boot ROM from %s\n", user_data->boot_rom_path); - if (GB_load_boot_rom(&gb, user_data->boot_rom_path)) { + if (gui_data->boot_rom_path != NULL) { + g_print("Trying to load boot ROM from %s\n", gui_data->boot_rom_path); + if (GB_load_boot_rom(&gb, gui_data->boot_rom_path)) { g_printerr("Falling back to boot ROM from config\n"); goto config_boot_rom; } } else { config_boot_rom: - switch (model) { + switch (gui_data->model) { case GB_MODEL_DMG_B: boot_rom_name = "dmg_boot.bin"; break; @@ -1722,13 +1874,17 @@ static gpointer run(gpointer user_data_gptr) { g_print("Trying to load boot ROM from %s\n", boot_rom_path); if (GB_load_boot_rom(&gb, boot_rom_path)) { + g_free(boot_rom_path); g_printerr("Falling back to internal boot ROM\n"); goto internal_boot_rom; } + + g_free(boot_rom_path); } else { internal_boot_rom: boot_rom_path = g_build_filename(RESOURCE_PREFIX "bootroms/", boot_rom_name, NULL); boot_rom_f = g_resources_lookup_data(boot_rom_path, G_RESOURCE_LOOKUP_FLAGS_NONE, &error); + g_free(boot_rom_path); if (boot_rom_f == NULL) { g_printerr("Failed to load internal boot ROM: %s\n", boot_rom_path); @@ -1738,30 +1894,79 @@ static gpointer run(gpointer user_data_gptr) { 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); + + g_bytes_unref(boot_rom_f); } } - - if (user_data->file != NULL && GB_load_rom(&gb, g_file_get_path(user_data->file)) == 0) { - /* Run emulation */ - while (running) { - if (paused || rewind_paused) { - handle_events(&gb); - } - 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); - } - } - } - - return NULL; +} + +static void stop(GuiData *gui_data) { + if (!running) return; + + SDL_PauseAudioDevice(device_id, 1); + GB_debugger_set_disabled(&gb, true); + + if (GB_debugger_is_stopped(&gb)) { + // [self interruptDebugInputRead]; + } + + stopping = true; + running = false; + while (stopping); + + GB_debugger_set_disabled(&gb, false); + gui_data->stopped = true; +} + +static void reset(GuiData *gui_data) { + GB_switch_model_and_reset(&gb, gui_data->model); + + GtkRequisition minimum_size; + GtkRequisition natural_size; + gtk_widget_get_preferred_size(GTK_WIDGET(main_window), &minimum_size, &natural_size); + + // Check SGB -> non-SGB and non-SGB to SGB transitions + if (GB_get_screen_width(&gb) != minimum_size.width || GB_get_screen_height(&gb) != minimum_size.height) { + update_window_geometry(); + } + + load_boot_rom(gui_data); + + char *path = g_file_get_path(gui_data->file); + + if (GB_load_rom(&gb, path) != 0) { + g_print("Failed to load ROM: %s", path); + } + + g_free(path); +} + +static void start(GuiData *gui_data) { + running = true; + gui_data->stopped = false; + + SDL_ClearQueuedAudio(device_id); + SDL_PauseAudioDevice(device_id, 0); + + /* Run emulation */ + while (running) { + if (rewind_paused) { + handle_events(&gb); + } + 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); + } + } + + stopping = false; } diff --git a/gtk3/main.h b/gtk3/main.h index 85210b9..bcd355b 100644 --- a/gtk3/main.h +++ b/gtk3/main.h @@ -16,19 +16,39 @@ #include "settings.h" #include "shader.h" -typedef struct UserData { +enum generic_model { + MODEL_NONE, + MODEL_DMG, + MODEL_CGB, + MODEL_AGB, + MODEL_SGB, +}; + +typedef struct GuiData { bool fullscreen; + gint sample_rate; + GFile *file; gchar *boot_rom_path; gchar *config_path; + + enum generic_model generic_model; GB_model_t model; -} UserData; + + bool stopped; +} GuiData; typedef struct{ int16_t x, y; uint16_t w, h; } Rect; +typedef struct LogData { + GB_gameboy_t *gb; + const char *string; + GB_log_attributes attributes; +} LogData; + #define JOYSTICK_HIGH 0x4000 #define JOYSTICK_LOW 0x3800 @@ -46,8 +66,16 @@ int main(int argc, char *argv[]); static gint handle_local_options(GApplication *app, GVariantDict *options, gpointer user_data_gptr); // GtkGlArea crash workaround -void gl_check_realize(GtkWidget *w, gpointer user_data_gptr); gboolean test_gl_support(void); +void gl_check_realize(GtkWidget *w, gpointer user_data_gptr); + +static gboolean init_controllers(); +static gboolean init_audio(); +static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample); +static char *sync_console_input(GB_gameboy_t *gb); +static char *async_console_input(GB_gameboy_t *gb); +static void on_console_log(gpointer user_data_gptr); +static void console_log(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes); static GMenuModel *get_menu_model(GApplication *app, const char *id); static void create_fallback_canvas(void); @@ -62,6 +90,8 @@ static uint32_t *get_current_buffer(void); static uint32_t *get_previous_buffer(void); static void flip(void); +static void quit_interrupt(int ignored); + // GApplication signals static void startup(GApplication *app, gpointer user_data_gptr); static void activate(GApplication *app, gpointer user_data_gptr); @@ -69,6 +99,7 @@ static void shutdown(GApplication *app, GFile **files, gint n_files, const gchar static void open(GApplication *app, GFile **files, gint n_files, const gchar *hint, gpointer user_data_gptr); static void quit(GApplication *app); static gboolean on_key_press(GtkWidget *w, GdkEventKey *event, gpointer data); +static void on_window_state_change(GtkWidget *w, GdkEventWindowState *event, gpointer data); // App actions static void activate_about(GSimpleAction *action, GVariant *parameter, gpointer app); @@ -79,6 +110,13 @@ static void activate_open_vram_viewer(GSimpleAction *action, GVariant *parameter static void activate_open(GSimpleAction *action, GVariant *parameter, gpointer app); static void activate_preferences(GSimpleAction *action, GVariant *parameter, gpointer app); static void activate_quit(GSimpleAction *action, GVariant *parameter, gpointer app); +static void activate_reset(GSimpleAction *action, GVariant *parameter, gpointer app); + +static void on_model_changed(GSimpleAction *action, GVariant *value, gpointer user_data); +static void on_mute_changed(GSimpleAction *action, GVariant *value, gpointer user_data); +static void on_pause_changed(GSimpleAction *action, GVariant *value, gpointer user_data); + +static void on_quit(GtkWidget *w, gpointer app); // Signal callback static void on_quit(GtkWidget *w, gpointer app); @@ -93,8 +131,8 @@ static void resize(); // VRAM viewer bindings static void on_vram_viewer_realize(); static void on_vram_viewer_unrealize(); -static gboolean on_draw_vram_viewer_tilemap(GtkWidget *widget, cairo_t *cr, gpointer data); static gboolean on_draw_vram_viewer_tileset(GtkWidget *widget, cairo_t *cr, gpointer data); +static gboolean on_draw_vram_viewer_tilemap(GtkWidget *widget, cairo_t *cr, gpointer data); static gboolean on_motion_vram_viewer_tileset(GtkWidget *widget, GdkEventMotion *event); static gboolean on_motion_vram_viewer_tilemap(GtkWidget *widget, GdkEventMotion *event); static void on_vram_tab_change(GtkWidget *widget, GParamSpec *pspec, GtkStackSwitcher *self); @@ -111,16 +149,26 @@ G_MODULE_EXPORT void on_keep_aspect_ratio_changed(GtkWidget *w, gpointer user_da G_MODULE_EXPORT void on_rewind_duration_changed(GtkWidget *w, gpointer user_data_gptr); G_MODULE_EXPORT void on_sgb_model_changed(GtkWidget *w, gpointer user_data_gptr); G_MODULE_EXPORT void on_use_integer_scaling_changed(GtkWidget *w, gpointer user_data_gptr); +G_MODULE_EXPORT void console_on_enter(GtkWidget *w, gpointer user_data_gptr); static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b); static void render_texture(void *pixels, void *previous); static void update_viewport(void); static void update_window_geometry(); +static void run(GuiData *gui_data); +static gpointer run_thread(gpointer user_data_gptr); +static void init(GuiData *gui_data); +static void load_boot_rom(GuiData *gui_data); + static void handle_events(GB_gameboy_t *gb); static uint32_t convert_color(uint16_t color); static void palette_color_data_func(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data); +static void on_vblank(gpointer data); static void vblank(GB_gameboy_t *gb); -static gpointer run(gpointer user_data_gptr); + +static void reset(GuiData *gui_data); +static void stop(GuiData *gui_data); +static void start(GuiData *gui_data); #endif /* main_h */ diff --git a/gtk3/resources/gtk/menus-common.ui b/gtk3/resources/gtk/menus-common.ui index 07fdb16..c3b5c3f 100644 --- a/gtk3/resources/gtk/menus-common.ui +++ b/gtk3/resources/gtk/menus-common.ui @@ -288,22 +288,22 @@ Author: Maximilian Mader Game Boy app.change_model - DMG + DMG Super Game Boy app.change_model - SGB + SGB Game Boy Color app.change_model - CGB + CGB Game Boy Advance app.change_model - AGB + AGB diff --git a/gtk3/resources/ui/window.ui b/gtk3/resources/ui/window.ui index a4e1264..133f45e 100644 --- a/gtk3/resources/ui/window.ui +++ b/gtk3/resources/ui/window.ui @@ -195,9 +195,10 @@ Maximilian Mader https://github.com/max-m True False - + True True + word 5 5 5 @@ -235,10 +236,11 @@ Maximilian Mader https://github.com/max-m True False - + True True False + word 5 5 5 @@ -653,23 +655,6 @@ Maximilian Mader https://github.com/max-m 3 - - - Keep Aspect Ratio - True - True - False - 5 - 10 - True - - - - False - True - 4 - - Use Integer Scaling @@ -687,6 +672,23 @@ Maximilian Mader https://github.com/max-m 4 + + + Keep Aspect Ratio + True + True + False + 5 + 10 + True + + + + False + True + 4 + + False @@ -797,6 +799,37 @@ Maximilian Mader https://github.com/max-m 1 + + + True + False + Sample Rate: + + + False + True + 2 + + + + + True + False + + Default + <separator> + 44.1 kHz + 48 kHz + 96 kHz + + + + + False + True + 3 + + 2 diff --git a/gtk3/settings.c b/gtk3/settings.c index 6dc96bc..a656846 100644 --- a/gtk3/settings.c +++ b/gtk3/settings.c @@ -107,6 +107,7 @@ void on_preferences_realize(GtkWidget *w, gpointer builder_ptr) { gtk_toggle_button_set_active(builder_get(GTK_TOGGLE_BUTTON, "integer_scaling_toggle"), config.use_integer_scaling); gtk_toggle_button_set_active(builder_get(GTK_TOGGLE_BUTTON, "aspect_ratio_toggle"), config.keep_aspect_ratio); gtk_combo_box_set_active_id(builder_get(GTK_COMBO_BOX, "highpass_filter_selector"), config.high_pass_filter_id); + gtk_combo_box_set_active_id(builder_get(GTK_COMBO_BOX, "sample_rate_selector"), itoa(config.sample_rate)); #if ! NDEBUG gtk_combo_box_set_active_id(builder_get(GTK_COMBO_BOX, "menubar_override_selector"), config.menubar_override); diff --git a/gtk3/settings.h b/gtk3/settings.h index af84153..9738c5c 100644 --- a/gtk3/settings.h +++ b/gtk3/settings.h @@ -45,6 +45,7 @@ ) \ EXPAND_GROUP(Audio, \ EXPAND_GROUP_MEMBER(high_pass_filter_id, string, "emulate_hardware") \ + EXPAND_GROUP_MEMBER(sample_rate, integer, -1) \ ) \ EXPAND_GROUP(Controls, \ \ diff --git a/gtk3/shader.c b/gtk3/shader.c index aca7624..88a1aa8 100644 --- a/gtk3/shader.c +++ b/gtk3/shader.c @@ -8,6 +8,10 @@ gl_Position = aPosition;\n\ }\n\ "; +static GBytes *master_shader_f = NULL; +static const gchar *master_shader_code; +static gsize master_shader_code_size = 0; + static GLuint create_shader(const char *source, GLenum type) { // Create the shader object @@ -66,9 +70,6 @@ bool init_shader_with_name(shader_t *shader, const char *name) } GError *error = NULL; - GBytes *master_shader_f = NULL; - 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; @@ -108,7 +109,7 @@ bool init_shader_with_name(shader_t *shader, const char *name) 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); - + g_bytes_unref(shader_f); shader->program = create_program(vertex_shader, final_shader_code); @@ -191,11 +192,7 @@ void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous, 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) { + if (epoxy_gl_version() < 32) { return; } @@ -203,3 +200,8 @@ void free_shader(shader_t *shader) glDeleteTextures(1, &shader->texture); glDeleteTextures(1, &shader->previous_texture); } + +void free_master_shader(void) { + g_bytes_unref(master_shader_f); + master_shader_code_size = 0; +} diff --git a/gtk3/shader.h b/gtk3/shader.h index 6923738..033f4ff 100644 --- a/gtk3/shader.h +++ b/gtk3/shader.h @@ -26,5 +26,6 @@ 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); +void free_master_shader(void); #endif /* shader_h */