diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index c1fc504..cda30bc 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -30,7 +30,8 @@ @"GBTurbo": @(kVK_Space), @"GBFilter": @"NearestNeighbor", - @"GBHighpassFilter": @(GB_HIGHPASS_REMOVE_DC_OFFSET), + @"GBColorCorrection": @(GB_COLOR_CORRECTION_EMULATE_HARDWARE), + @"GBHighpassFilter": @(GB_HIGHPASS_REMOVE_DC_OFFSET) }]; } diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 0997d49..bc3acc5 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -145,6 +145,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, GB_set_log_callback(&gb, (GB_log_callback_t) consoleLog); GB_set_input_callback(&gb, (GB_input_callback_t) consoleInput); GB_set_async_input_callback(&gb, (GB_input_callback_t) asyncConsoleInput); + GB_set_color_correction_mode(&gb, (GB_color_correction_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"]); GB_set_rgb_encode_callback(&gb, rgbEncode); GB_set_camera_get_pixel_callback(&gb, cameraGetPixel); GB_set_camera_update_request_callback(&gb, cameraRequestUpdate); @@ -302,6 +303,11 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, name:@"GBHighpassFilterChanged" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateColorCorrectionMode) + name:@"GBColorCorrectionChanged" + object:nil]; + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateDMG"]) { [self initDMG]; } @@ -1287,4 +1293,11 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, } } +- (void) updateColorCorrectionMode +{ + if (GB_is_inited(&gb)) { + GB_set_color_correction_mode(&gb, (GB_color_correction_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"]); + } +} + @end diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index c8f3191..52d26e0 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -5,4 +5,5 @@ @property IBOutlet NSPopUpButton *graphicsFilterPopupButton; @property (strong) IBOutlet NSButton *aspectRatioCheckbox; @property (strong) IBOutlet NSPopUpButton *highpassFilterPopupButton; +@property (strong) IBOutlet NSPopUpButton *colorCorrectionPopupButton; @end diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 38b4f3b..af88ab3 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -10,6 +10,7 @@ NSPopUpButton *_graphicsFilterPopupButton; NSPopUpButton *_highpassFilterPopupButton; + NSPopUpButton *_colorCorrectionPopupButton; NSButton *_aspectRatioCheckbox; } @@ -52,6 +53,18 @@ return _highpassFilterPopupButton; } +- (void)setColorCorrectionPopupButton:(NSPopUpButton *)colorCorrectionPopupButton +{ + _colorCorrectionPopupButton = colorCorrectionPopupButton; + NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"]; + [_colorCorrectionPopupButton selectItemAtIndex:mode]; +} + +- (NSPopUpButton *)colorCorrectionPopupButton +{ + return _colorCorrectionPopupButton; +} + - (void)setHighpassFilterPopupButton:(NSPopUpButton *)highpassFilterPopupButton { _highpassFilterPopupButton = highpassFilterPopupButton; @@ -128,6 +141,14 @@ [[NSNotificationCenter defaultCenter] postNotificationName:@"GBAspectChanged" object:nil]; } +- (IBAction)colorCorrectionChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem]) + forKey:@"GBColorCorrection"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorCorrectionChanged" object:nil]; + +} + - (NSButton *)aspectRatioCheckbox { return _aspectRatioCheckbox; diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 61ce4ce..6b62561 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -1,8 +1,8 @@ - + - + @@ -17,14 +17,14 @@ - + - + - - + + @@ -33,7 +33,7 @@ - + @@ -66,6 +66,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -87,7 +117,7 @@ - + @@ -106,7 +136,7 @@ - + @@ -177,12 +207,13 @@ + - + diff --git a/Core/display.c b/Core/display.c index 97398ee..42c43b6 100755 --- a/Core/display.c +++ b/Core/display.c @@ -222,21 +222,77 @@ static void display_vblank(GB_gameboy_t *gb) static inline uint8_t scale_channel(uint8_t x) { - x &= 0x1f; return (x << 3) | (x >> 2); } +static inline uint8_t scale_channel_with_curve(uint8_t x) +{ + return (uint8_t[]){0,2,4,7,12,18,25,34,42,52,62,73,85,97,109,121,134,146,158,170,182,193,203,213,221,230,237,243,248,251,253,255,}[x]; +} + +uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color) +{ + uint8_t r = (color) & 0x1F; + uint8_t g = (color >> 5) & 0x1F; + uint8_t b = (color >> 10) & 0x1F; + + if (gb->color_correction_mode == GB_COLOR_CORRECTION_DISABLED) { + r = scale_channel(r); + g = scale_channel(g); + b = scale_channel(b); + } + else { + r = scale_channel_with_curve(r); + g = scale_channel_with_curve(g); + b = scale_channel_with_curve(b); + + if (gb->color_correction_mode != GB_COLOR_CORRECTION_CORRECT_CURVES) { + uint8_t new_g = (g * 3 + b) / 4; + uint8_t new_r = r, new_b = b; + if (gb->color_correction_mode == GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS) { + uint8_t old_max = MAX(r, MAX(g, b)); + uint8_t new_max = MAX(new_r, MAX(new_g, new_b)); + + if (new_max != 0) { + new_r = new_r * old_max / new_max; + new_g = new_g * old_max / new_max; + new_b = new_b * old_max / new_max; + } + + uint8_t old_min = MIN(r, MIN(g, b)); + uint8_t new_min = MIN(new_r, MIN(new_g, new_b)); + + if (new_min != 0xff) { + new_r = 0xff - (0xff - new_r) * (0xff - old_min) / (0xff - new_min); + new_g = 0xff - (0xff - new_g) * (0xff - old_min) / (0xff - new_min); + new_b = 0xff - (0xff - new_b) * (0xff - old_min) / (0xff - new_min);; + } + } + r = new_r; + g = new_g; + b = new_b; + } + } + + return gb->rgb_encode_callback(gb, r, g, b); +} + void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index) { + if (!gb->rgb_encode_callback) return; uint8_t *palette_data = background_palette? gb->background_palettes_data : gb->sprite_palettes_data; uint16_t color = palette_data[index & ~1] | (palette_data[index | 1] << 8); - // No need to &, scale channel does that. - uint8_t r = scale_channel(color); - uint8_t g = scale_channel(color >> 5); - uint8_t b = scale_channel(color >> 10); - assert (gb->rgb_encode_callback); - (background_palette? gb->background_palettes_rgb : gb->sprite_palettes_rgb)[index / 2] = gb->rgb_encode_callback(gb, r, g, b); + (background_palette? gb->background_palettes_rgb : gb->sprite_palettes_rgb)[index / 2] = GB_convert_rgb15(gb, color); +} + +void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t mode) +{ + gb->color_correction_mode = mode; + for (unsigned i = 0; i < 32; i++) { + GB_palette_changed(gb, false, i * 2); + GB_palette_changed(gb, true, i * 2); + } } /* diff --git a/Core/display.h b/Core/display.h index a622923..06448c4 100644 --- a/Core/display.h +++ b/Core/display.h @@ -34,8 +34,16 @@ typedef struct { bool obscured_by_line_limit; } GB_oam_info_t; +typedef enum { + GB_COLOR_CORRECTION_DISABLED, + GB_COLOR_CORRECTION_CORRECT_CURVES, + GB_COLOR_CORRECTION_EMULATE_HARDWARE, + GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS, +} GB_color_correction_mode_t; + void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index); void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index, GB_map_type_t map_type, GB_tileset_type_t tileset_type); uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_height); - +uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color); +void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t mode); #endif /* display_h */ diff --git a/Core/gb.c b/Core/gb.c index 6109c93..ea4bb61 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -541,6 +541,10 @@ void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t * *size = sizeof(gb->sprite_palettes_data); *bank = 0; return &gb->sprite_palettes_data; + case GB_DIRECT_ACCESS_IE: + *size = sizeof(gb->interrupt_enable); + *bank = 0; + return &gb->interrupt_enable; default: *size = 0; *bank = 0; diff --git a/Core/gb.h b/Core/gb.h index 91f9fea..2f9b4ab 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -162,6 +162,14 @@ typedef enum { #define DIV_CYCLES (0x100) #define INTERNAL_DIV_CYCLES (0x40000) #define FRAME_LENGTH 16742706 // in nanoseconds + +#if !defined(MIN) +#define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; }) +#endif + +#if !defined(MAX) +#define MAX(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; }) +#endif #endif typedef void (*GB_vblank_callback_t)(GB_gameboy_t *gb); @@ -361,8 +369,6 @@ struct GB_gameboy_internal_s { uint8_t oam[0xA0]; uint8_t background_palettes_data[0x40]; uint8_t sprite_palettes_data[0x40]; - uint32_t background_palettes_rgb[0x20]; - uint32_t sprite_palettes_rgb[0x20]; int16_t previous_lcdc_x; bool stat_interrupt_line; uint8_t effective_scx; @@ -405,6 +411,9 @@ struct GB_gameboy_internal_s { /* I/O */ uint32_t *screen; + uint32_t background_palettes_rgb[0x20]; + uint32_t sprite_palettes_rgb[0x20]; + GB_color_correction_mode_t color_correction_mode; bool keys[GB_KEY_MAX]; /* Timing */ @@ -512,6 +521,7 @@ typedef enum { GB_DIRECT_ACCESS_OAM, GB_DIRECT_ACCESS_BGP, GB_DIRECT_ACCESS_OBP, + GB_DIRECT_ACCESS_IE, } GB_direct_access_t; /* Returns a mutable pointer to various hardware memories. If that memory is banked, the current bank diff --git a/Core/save_state.c b/Core/save_state.c index cd579aa..24604c1 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -213,6 +213,11 @@ int GB_load_state(GB_gameboy_t *gb, const char *path) gb->rumble_callback(gb, gb->rumble_state); } + for (unsigned i = 0; i < 32; i++) { + GB_palette_changed(gb, false, i * 2); + GB_palette_changed(gb, true, i * 2); + } + error: fclose(f); return errno;