2020-04-11 16:24:55 +00:00
# include <gtk/gtk.h>
2020-04-10 01:22:16 +00:00
# include <epoxy/gl.h>
2020-04-11 16:24:55 +00:00
# include <signal.h>
# include <stdbool.h>
# include <stdio.h>
# include <string.h>
# include <Core/gb.h>
2020-04-10 01:22:16 +00:00
# include "shader.h"
2020-04-11 16:24:55 +00:00
# define str(x) #x
# define xstr(x) str(x)
2019-09-22 22:47:42 +00:00
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 ) ;
2019-09-23 14:35:10 +00:00
GtkApplication * main_application ;
2020-04-11 16:24:55 +00:00
GtkBuilder * builder ;
2019-09-22 22:47:42 +00:00
GtkApplicationWindow * main_window ;
2020-04-11 16:24:55 +00:00
GtkGLArea * gl_area ;
2020-04-10 01:22:16 +00:00
shader_t shader ;
2020-04-11 16:24:55 +00:00
GB_gameboy_t gb ;
2019-09-22 22:47:42 +00:00
static uint32_t * image_buffers [ 3 ] ;
static unsigned char current_buffer ;
2020-04-10 01:22:16 +00:00
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 ;
2019-09-22 22:47:42 +00:00
static Rect rect ;
2020-04-10 01:22:16 +00:00
2019-09-23 14:35:10 +00:00
static bool running = true ;
2019-09-22 22:47:42 +00:00
unsigned char number_of_buffers ( void ) {
bool should_blend = true ;
2020-04-11 16:24:55 +00:00
2019-09-22 22:47:42 +00:00
return should_blend ? 3 : 2 ;
}
2020-04-11 16:24:55 +00:00
2019-09-22 22:47:42 +00:00
void flip ( void ) {
current_buffer = ( current_buffer + 1 ) % number_of_buffers ( ) ;
}
2020-04-10 01:22:16 +00:00
2019-09-22 22:47:42 +00:00
uint32_t * get_pixels ( void ) {
return image_buffers [ ( current_buffer + 1 ) % number_of_buffers ( ) ] ;
}
2020-04-10 01:22:16 +00:00
2019-09-22 22:47:42 +00:00
uint32_t * get_current_buffer ( void ) {
return image_buffers [ current_buffer ] ;
}
uint32_t * get_previous_buffer ( void ) {
return image_buffers [ ( current_buffer + 2 ) % number_of_buffers ( ) ] ;
}
2020-04-10 01:22:16 +00:00
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 ) ;
2019-09-22 22:47:42 +00:00
}
static void vblank ( GB_gameboy_t * gb ) {
flip ( ) ;
GB_set_pixels_output ( gb , get_pixels ( ) ) ;
// Queue drawing of the current frame
gtk_gl_area_queue_render ( gl_area ) ;
while ( gtk_events_pending ( ) ) {
gtk_main_iteration ( ) ;
}
2020-04-10 01:22:16 +00:00
}
void update_viewport ( void ) {
2019-09-22 22:47:42 +00:00
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 ) ;
2020-04-10 01:22:16 +00:00
}
2020-04-11 16:24:55 +00:00
// Determines if a ComboBox entry should be converted into a separator.
// Each element with a text value of `<separator>` will be converted into a separator element.
static gboolean is_separator ( GtkTreeModel * model , GtkTreeIter * iter , gpointer data ) {
gchar * text = NULL ;
gtk_tree_model_get ( model , iter , 0 , & text , - 1 ) ;
gboolean result = g_strcmp0 ( " <separator> " , text ) = = 0 ;
return result ;
}
// 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 ) ;
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 ) ;
}
if ( GTK_IS_CONTAINER ( list - > data ) ) {
set_combo_box_row_separator_func ( GTK_CONTAINER ( list - > data ) ) ;
}
list = list - > next ;
}
g_list_free_full ( list , NULL ) ;
}
// Returns true if the application should show a menubar
static gboolean show_menubar ( void ) {
GtkSettings * settings = gtk_settings_get_default ( ) ;
gboolean result ;
g_object_get ( settings , " gtk-shell-shows-menubar " , & result , NULL ) ;
return result ;
}
// Returns a GObject by ID from our GtkBuilder instance
static GObject * get_object ( gchararray id ) {
return gtk_builder_get_object ( builder , id ) ;
}
// Returns a `GApplication`s `GMenuModel` by ID
// GApplication menus are loaded from `gtk/menus.ui`, `gtk/menus-traditional.ui` and `gtk/menus-common.ui`.
static GMenuModel * get_menu_model ( GApplication * app , const char * id ) {
GMenu * menu ;
menu = gtk_application_get_menu_by_id ( GTK_APPLICATION ( app ) , id ) ;
return menu ? G_MENU_MODEL ( g_object_ref_sink ( menu ) ) : NULL ;
}
2019-09-23 14:35:10 +00:00
static void quit ( GApplication * app ) {
g_application_quit ( app ) ;
running = false ;
}
2020-04-11 16:24:55 +00:00
// app.quit GAction
// Exits the application
static void activate_quit ( GSimpleAction * action , GVariant * parameter , gpointer user_data ) {
2019-09-23 14:35:10 +00:00
quit ( G_APPLICATION ( user_data ) ) ;
2020-04-11 16:24:55 +00:00
}
// app.about GAction
// Opens the about dialog
static void activate_about ( GSimpleAction * action , GVariant * parameter , gpointer user_data ) {
GObject * dialog = get_object ( " about_dialog " ) ;
gtk_dialog_run ( GTK_DIALOG ( dialog ) ) ;
gtk_widget_hide ( GTK_WIDGET ( dialog ) ) ;
}
// List of GActions for the `app` prefix
static GActionEntry app_entries [ ] = {
{ " quit " , activate_quit , NULL , NULL , NULL } ,
{ " about " , activate_about , NULL , NULL , NULL } ,
} ;
2019-09-23 14:35:10 +00:00
G_MODULE_EXPORT void on_quit ( GtkWidget * w , gpointer app ) {
quit ( G_APPLICATION ( app ) ) ;
}
2020-04-11 16:24:55 +00:00
G_MODULE_EXPORT void on_show_window ( GtkWidget * w , gpointer window ) {
gtk_widget_show_all ( GTK_WIDGET ( window ) ) ;
}
G_MODULE_EXPORT void on_boot_rom_location_changed ( GtkWidget * w , gpointer user_data_gptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
g_print ( " Active: %s " , gtk_combo_box_get_active_id ( box ) ) ;
}
2019-09-22 22:47:42 +00:00
G_MODULE_EXPORT void gl_init ( ) {
2020-04-10 01:22:16 +00:00
const char * renderer ;
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 " ) ;
}
}
2019-09-22 22:47:42 +00:00
G_MODULE_EXPORT void gl_resize ( ) {
update_viewport ( ) ;
}
G_MODULE_EXPORT void gl_draw ( ) {
render_texture ( get_current_buffer ( ) , get_previous_buffer ( ) ) ;
}
G_MODULE_EXPORT void gl_finish ( ) { }
2020-04-11 16:24:55 +00:00
// 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;
builder = gtk_builder_new_from_resource ( RESOURCE_PREFIX " ui/window.ui " ) ;
gtk_builder_connect_signals ( builder , NULL ) ;
g_action_map_add_action_entries ( G_ACTION_MAP ( app ) , app_entries , G_N_ELEMENTS ( app_entries ) , app ) ;
GtkWindow * preferences = GTK_WINDOW ( get_object ( " preferences " ) ) ;
set_combo_box_row_separator_func ( GTK_CONTAINER ( preferences ) ) ;
GtkWindow * vram_viewer = GTK_WINDOW ( get_object ( " vram_viewer " ) ) ;
set_combo_box_row_separator_func ( GTK_CONTAINER ( vram_viewer ) ) ;
2019-09-22 22:47:42 +00:00
// setup main window
main_window = GTK_APPLICATION_WINDOW ( gtk_application_window_new ( GTK_APPLICATION ( app ) ) ) ;
gtk_application_window_set_show_menubar ( main_window , true ) ;
2020-04-11 16:24:55 +00:00
2019-09-22 22:47:42 +00:00
// create our renderer area
gl_area = GTK_GL_AREA ( gtk_gl_area_new ( ) ) ;
gtk_gl_area_set_auto_render ( gl_area , false ) ;
g_signal_connect ( gl_area , " realize " , G_CALLBACK ( gl_init ) , NULL ) ;
g_signal_connect ( gl_area , " render " , G_CALLBACK ( gl_draw ) , NULL ) ;
g_signal_connect ( gl_area , " resize " , G_CALLBACK ( gl_resize ) , NULL ) ;
g_signal_connect ( gl_area , " unrealize " , G_CALLBACK ( gl_finish ) , NULL ) ;
gtk_container_add ( GTK_CONTAINER ( main_window ) , GTK_WIDGET ( gl_area ) ) ;
2020-04-11 16:24:55 +00:00
2019-09-22 22:47:42 +00:00
// Handle the whole menubar situation …
if ( show_menubar ( ) ) {
// Show a classic menubar
2020-04-11 16:24:55 +00:00
GMenuModel * menubar = get_menu_model ( app , " menubar " ) ;
gtk_application_set_menubar ( GTK_APPLICATION ( app ) , menubar ) ;
}
else {
2019-09-22 22:47:42 +00:00
// Attach a custom title bar
GtkWidget * titlebar = GTK_WIDGET ( gtk_builder_get_object ( builder , " main_header_bar " ) ) ;
gtk_window_set_titlebar ( GTK_WINDOW ( main_window ) , titlebar ) ;
2020-04-11 16:24:55 +00:00
// Disable menubar
gtk_application_set_menubar ( GTK_APPLICATION ( app ) , NULL ) ;
// Hook menubar up to the hamburger button
GtkMenuButton * hamburger_button = GTK_MENU_BUTTON ( get_object ( " hamburger_button " ) ) ;
GMenuModel * hamburger_menu = get_menu_model ( app , " menubar " ) ;
gtk_menu_button_set_menu_model ( hamburger_button , hamburger_menu ) ;
}
2019-09-22 22:47:42 +00:00
gtk_window_set_title ( GTK_WINDOW ( main_window ) , " SameBoy v " xstr ( VERSION ) ) ;
2020-04-11 16:24:55 +00:00
// Define a set of window icons
GList * icon_list = NULL ;
static char * icons [ ] = {
RESOURCE_PREFIX " logo_256.png " ,
RESOURCE_PREFIX " logo_128.png " ,
RESOURCE_PREFIX " logo_64.png " ,
RESOURCE_PREFIX " logo_48.png " ,
RESOURCE_PREFIX " logo_32.png " ,
RESOURCE_PREFIX " logo_16.png "
} ;
// Create list of GdkPixbufs
for ( int i = 0 ; i < ( sizeof ( icons ) / sizeof ( const char * ) ) ; + + i ) {
GdkPixbuf * icon = gdk_pixbuf_new_from_resource ( icons [ i ] , NULL ) ;
if ( ! icon ) continue ;
icon_list = g_list_prepend ( icon_list , icon ) ;
}
// Let GTK choose the proper icon
2019-09-22 22:47:42 +00:00
gtk_window_set_icon_list ( GTK_WINDOW ( main_window ) , icon_list ) ;
2020-04-11 16:24:55 +00:00
// 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_version ( about_dialog , " v " xstr ( VERSION ) ) ;
g_list_free ( icon_list ) ;
}
// 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 ;
if ( user_data - > fullscreen ) {
2019-09-22 22:47:42 +00:00
gtk_window_fullscreen ( GTK_WINDOW ( main_window ) ) ;
2020-04-11 16:24:55 +00:00
}
2019-09-23 14:35:10 +00:00
g_signal_connect ( main_window , " destroy " , G_CALLBACK ( on_quit ) , app ) ;
2019-09-22 22:47:42 +00:00
gtk_application_add_window ( GTK_APPLICATION ( app ) , GTK_WINDOW ( main_window ) ) ;
2020-04-11 16:24:55 +00:00
gtk_widget_show_all ( GTK_WIDGET ( main_window ) ) ;
2020-04-10 01:22:16 +00:00
2019-09-23 14:35:10 +00:00
// Start the emulation loop.
// This loop takes care of the GTK main loop.
2020-04-10 01:22:16 +00:00
run ( user_data ) ;
2020-04-11 16:24:55 +00:00
}
// 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 ;
if ( n_files > 1 ) {
g_printerr ( " More than one file specified \n " ) ;
g_application_quit ( app ) ;
return ;
}
user_data - > file = files [ 0 ] ;
// We have handled the files, now activate the application
activate ( app , user_data_gptr ) ;
}
2019-09-23 14:35:10 +00:00
static void shutdown ( GApplication * app , GFile * * files , gint n_files , const gchar * hint , gpointer user_data_gptr ) {
g_print ( " SHUTDOWN \n " ) ;
}
2020-04-11 16:24:55 +00:00
// 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 ;
guint32 count ;
if ( g_variant_dict_lookup ( options , " version " , " b " , & count ) ) {
g_print ( " SameBoy v " xstr ( VERSION ) " \n " ) ;
return EXIT_SUCCESS ;
}
if ( g_variant_dict_lookup ( options , " fullscreen " , " b " , & count ) ) {
user_data - > fullscreen = true ;
}
return - 1 ;
}
2020-04-10 01:22:16 +00:00
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 ;
}
2019-09-22 22:47:42 +00:00
static void update_window_geometry ( ) {
// Set size hints
GdkGeometry hints ;
hints . min_width = GB_get_screen_width ( & gb ) ;
hints . min_height = GB_get_screen_height ( & gb ) ;
gtk_window_set_geometry_hints (
GTK_WINDOW ( main_window ) ,
NULL ,
& hints ,
( GdkWindowHints ) ( GDK_HINT_MIN_SIZE )
) ;
gtk_window_resize ( GTK_WINDOW ( main_window ) ,
GB_get_screen_width ( & gb ) * 2 ,
GB_get_screen_height ( & gb ) * 2
) ;
if ( image_buffers [ 0 ] ) free ( image_buffers [ 0 ] ) ;
if ( image_buffers [ 1 ] ) free ( image_buffers [ 1 ] ) ;
if ( image_buffers [ 2 ] ) 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 ) ;
2020-04-10 01:22:16 +00:00
}
static void run ( UserData * user_data ) {
2019-09-22 22:47:42 +00:00
GB_model_t prev_model = GB_get_model ( & gb ) ;
2020-04-10 01:22:16 +00:00
GB_model_t model = GB_MODEL_CGB_E ;
if ( GB_is_inited ( & gb ) ) {
GB_switch_model_and_reset ( & gb , model ) ;
2019-09-22 22:47:42 +00:00
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 ( ) ;
}
2020-04-10 01:22:16 +00:00
}
else {
GB_init ( & gb , model ) ;
2019-09-22 22:47:42 +00:00
update_window_geometry ( ) ;
2020-04-10 01:22:16 +00:00
GB_set_vblank_callback ( & gb , ( GB_vblank_callback_t ) vblank ) ;
2019-09-22 22:47:42 +00:00
GB_set_pixels_output ( & gb , get_current_buffer ( ) ) ;
2020-04-10 01:22:16 +00:00
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 */
2019-09-23 14:35:10 +00:00
while ( running ) {
2020-04-10 01:22:16 +00:00
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 [ ] ) {
2020-04-11 16:24:55 +00:00
// Create our GApplication and tell GTK that we are able to handle files
2019-09-23 14:35:10 +00:00
main_application = gtk_application_new ( APP_ID , G_APPLICATION_HANDLES_OPEN ) ;
2020-04-11 16:24:55 +00:00
UserData user_data = { NULL } ;
// Define our command line parameters
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 } ,
{ NULL }
} ;
// Setup our command line information
2019-09-23 14:35:10 +00:00
g_application_add_main_option_entries ( G_APPLICATION ( main_application ) , entries ) ;
g_application_set_option_context_parameter_string ( G_APPLICATION ( main_application ) , " [FILE…] " ) ;
g_application_set_option_context_summary ( G_APPLICATION ( main_application ) , " SameBoy is an open source Game Boy (DMG) and Game Boy Color (CGB) emulator. " ) ;
2020-04-11 16:24:55 +00:00
// Add signal handlers
2019-09-23 14:35:10 +00:00
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 ) ;
2020-04-11 16:24:55 +00:00
2019-09-22 22:47:42 +00:00
# ifndef NDEBUG
//gtk_window_set_interactive_debugging(true);
# endif
2020-04-11 16:24:55 +00:00
// Start our GApplication main loop
2019-09-23 14:35:10 +00:00
int status = g_application_run ( G_APPLICATION ( main_application ) , argc , argv ) ;
g_object_unref ( main_application ) ;
2020-04-11 16:24:55 +00:00
return status ;
}