[GTK3] Implement more menu handlers

Also fix some memory leaks.
This commit is contained in:
Maximilian Mader 2019-10-12 23:11:26 +02:00
parent 10ac1bd0a5
commit df9189448d
Signed by: Max
GPG Key ID: F71D56A3151C4FB3
8 changed files with 464 additions and 173 deletions

View File

@ -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", "<file path>" },
{ "bootrom", 'b', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &gui_data.boot_rom_path, "Path to the boot ROM to use", "<file path>" },
{ "model", 'm', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, NULL, "Override the model type to emulate", "<model type>" },
{ "config", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &user_data.config_path, "Override the path of the configuration file", "<file path>" },
{ "config", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &gui_data.config_path, "Override the path of the configuration file", "<file path>" },
{ 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("<separator>", 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` wont 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;
}

View File

@ -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 */

View File

@ -288,22 +288,22 @@ Author: Maximilian Mader
<item>
<attribute name="label" translatable="yes">Game Boy</attribute>
<attribute name="action">app.change_model</attribute>
<attribute name="model">DMG</attribute>
<attribute name="target">DMG</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Super Game Boy</attribute>
<attribute name="action">app.change_model</attribute>
<attribute name="model">SGB</attribute>
<attribute name="target">SGB</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Game Boy Color</attribute>
<attribute name="action">app.change_model</attribute>
<attribute name="model">CGB</attribute>
<attribute name="target">CGB</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Game Boy Advance</attribute>
<attribute name="action">app.change_model</attribute>
<attribute name="model">AGB</attribute>
<attribute name="target">AGB</attribute>
</item>
</section>

View File

@ -195,9 +195,10 @@ Maximilian Mader https://github.com/max-m</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkTextView">
<object class="GtkTextView" id="console_sidebar_input">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="wrap_mode">word</property>
<property name="left_margin">5</property>
<property name="right_margin">5</property>
<property name="top_margin">5</property>
@ -235,10 +236,11 @@ Maximilian Mader https://github.com/max-m</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkTextView">
<object class="GtkTextView" id="console_sidebar_output">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="wrap_mode">word</property>
<property name="left_margin">5</property>
<property name="right_margin">5</property>
<property name="top_margin">5</property>
@ -653,23 +655,6 @@ Maximilian Mader https://github.com/max-m</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="aspect_ratio_toggle">
<property name="label" translatable="yes">Keep Aspect Ratio</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="margin_top">5</property>
<property name="margin_bottom">10</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_keep_aspect_ratio_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="integer_scaling_toggle">
<property name="label" translatable="yes">Use Integer Scaling</property>
@ -687,6 +672,23 @@ Maximilian Mader https://github.com/max-m</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="aspect_ratio_toggle">
<property name="label" translatable="yes">Keep Aspect Ratio</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="margin_top">5</property>
<property name="margin_bottom">10</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_keep_aspect_ratio_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="menubar_override_selector_label">
<property name="can_focus">False</property>
@ -797,6 +799,37 @@ Maximilian Mader https://github.com/max-m</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Sample Rate:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="sample_rate_selector">
<property name="visible">True</property>
<property name="can_focus">False</property>
<items>
<item id="-1" translatable="yes">Default</item>
<item translatable="yes">&lt;separator&gt;</item>
<item id="44100" translatable="yes">44.1 kHz</item>
<item id="48000" translatable="yes">48 kHz</item>
<item id="96000" translatable="yes">96 kHz</item>
</items>
<signal name="changed" handler="on_sample_rate_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="position">2</property>

View File

@ -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);

View File

@ -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, \
\

View File

@ -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;
}

View File

@ -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 */