diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 285daf4..e2b12c0 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -56,7 +56,16 @@ enum model { }; @interface Document () +@property GBAudioClient *audioClient; +@end + +@implementation Document { + GB_gameboy_t gb; + volatile bool running; + volatile bool stopping; + NSConditionLock *has_debugger_input; + NSMutableArray *debugger_input_queue; NSMutableAttributedString *pending_console_output; NSRecursiveLock *console_output_lock; @@ -66,10 +75,10 @@ enum model { bool fullScreen; bool in_sync_input; HFController *hex_controller; - + NSString *lastConsoleInput; HFLineCountingRepresenter *lineRep; - + CVImageBufferRef cameraImage; AVCaptureSession *cameraSession; AVCaptureConnection *cameraConnection; @@ -111,25 +120,6 @@ enum model { void (^ volatile _pendingAtomicBlock)(); } -@property GBAudioClient *audioClient; -- (void) vblank; -- (void) log: (const char *) log withAttributes: (GB_log_attributes) attributes; -- (char *) getDebuggerInput; -- (char *) getAsyncDebuggerInput; -- (void) cameraRequestUpdate; -- (uint8_t) cameraGetPixelAtX:(uint8_t)x andY:(uint8_t)y; -- (void) printImage:(uint32_t *)image height:(unsigned) height - topMargin:(unsigned) topMargin bottomMargin: (unsigned) bottomMargin - exposure:(unsigned) exposure; -- (void) gotNewSample:(GB_sample_t *)sample; -- (void) rumbleChanged:(double)amp; -- (void) loadBootROM:(GB_boot_rom_t)type; -- (void)linkCableBitStart:(bool)bit; -- (bool)linkCableBitEnd; -- (void)infraredStateChanged:(bool)state; - -@end - static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type) { Document *self = (__bridge Document *)GB_get_user_data(gb); @@ -139,7 +129,7 @@ static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type) static void vblank(GB_gameboy_t *gb, GB_vblank_type_t type) { Document *self = (__bridge Document *)GB_get_user_data(gb); - [self vblank]; + [self vblankWithType:type]; } static void consoleLog(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) @@ -227,15 +217,6 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) } -@implementation Document -{ - GB_gameboy_t gb; - volatile bool running; - volatile bool stopping; - NSConditionLock *has_debugger_input; - NSMutableArray *debugger_input_queue; -} - - (instancetype)init { self = [super init]; @@ -341,26 +322,29 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) self.osdView.usesSGBScale = GB_get_screen_width(&gb) == 256; } -- (void) vblank +- (void) vblankWithType:(GB_vblank_type_t)type { if (_gbsVisualizer) { dispatch_async(dispatch_get_main_queue(), ^{ [_gbsVisualizer setNeedsDisplay:true]; }); } - [self.view flip]; - if (borderModeChanged) { - dispatch_sync(dispatch_get_main_queue(), ^{ - size_t previous_width = GB_get_screen_width(&gb); - GB_set_border_mode(&gb, (GB_border_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBBorderMode"]); - if (GB_get_screen_width(&gb) != previous_width) { - [self.view screenSizeChanged]; - [self updateMinSize]; - } - }); - borderModeChanged = false; + if (type != GB_VBLANK_TYPE_REPEAT) { + [self.view flip]; + if (borderModeChanged) { + dispatch_sync(dispatch_get_main_queue(), ^{ + size_t previous_width = GB_get_screen_width(&gb); + GB_set_border_mode(&gb, (GB_border_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBBorderMode"]); + if (GB_get_screen_width(&gb) != previous_width) { + [self.view screenSizeChanged]; + [self updateMinSize]; + } + }); + borderModeChanged = false; + } + GB_set_pixels_output(&gb, self.view.pixels); } - GB_set_pixels_output(&gb, self.view.pixels); + if (self.vramWindow.isVisible) { dispatch_async(dispatch_get_main_queue(), ^{ self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSWindowStyleMaskFullScreen) != 0; diff --git a/Core/display.c b/Core/display.c index e0b2c9b..27c1b02 100644 --- a/Core/display.c +++ b/Core/display.c @@ -107,7 +107,7 @@ typedef struct __attribute__((packed)) { } object_t; void GB_display_vblank(GB_gameboy_t *gb, GB_vblank_type_t type) -{ +{ gb->vblank_just_occured = true; gb->cycles_since_vblank_callback = 0; gb->lcd_disabled_outside_of_vblank = false; @@ -123,9 +123,19 @@ void GB_display_vblank(GB_gameboy_t *gb, GB_vblank_type_t type) } } + if (GB_is_cgb(gb) && type == GB_VBLANK_TYPE_NORMAL_FRAME && gb->frame_repeat_countdown > 0 && gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON) { + GB_handle_rumble(gb); + + if (gb->vblank_callback) { + gb->vblank_callback(gb, GB_VBLANK_TYPE_REPEAT); + } + GB_timing_sync(gb); + return; + } + bool is_ppu_stopped = !GB_is_cgb(gb) && gb->stopped && gb->io_registers[GB_IO_LCDC] & 0x80; - if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || is_ppu_stopped) || gb->cgb_repeated_a_frame || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) { + if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || is_ppu_stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) { /* LCD is off, set screen to white or black (if LCD is on in stop mode) */ if (!GB_is_sgb(gb)) { uint32_t color = 0; @@ -313,9 +323,9 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border) new_b = new_b * 15 / 16 + (r + g ) / 32; if (agb) { - new_r = new_r * (224 - 40) / 255 + 20; - new_g = new_g * (220 - 36) / 255 + 18; - new_b = new_b * (216 - 32) / 255 + 16; + new_r = new_r * (224 - 20) / 255 + 20; + new_g = new_g * (220 - 18) / 255 + 18; + new_b = new_b * (216 - 16) / 255 + 16; } else { new_r = new_r * (220 - 40) / 255 + 40; @@ -1398,6 +1408,13 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force) gb->mode_for_interrupt = 3; } gb->cycles_since_vblank_callback += cycles / 2; + + if (cycles < gb->frame_repeat_countdown) { + gb->frame_repeat_countdown -= cycles; + } + else { + gb->frame_repeat_countdown = 0; + } /* The PPU does not advance while in STOP mode on the DMG */ if (gb->stopped && !GB_is_cgb(gb)) { @@ -1457,7 +1474,6 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force) GB_SLEEP(gb, display, 1, LCDC_PERIOD - gb->cycles_since_vblank_callback); } GB_display_vblank(gb, GB_VBLANK_TYPE_LCD_OFF); - gb->cgb_repeated_a_frame = true; } return; } @@ -1987,14 +2003,14 @@ skip_slow_mode_3: if (gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON) { if (GB_is_cgb(gb)) { GB_display_vblank(gb, GB_VBLANK_TYPE_NORMAL_FRAME); - gb->frame_skip_state = GB_FRAMESKIP_FIRST_FRAME_SKIPPED; + gb->frame_skip_state = GB_FRAMESKIP_FIRST_FRAME_RENDERED; } else { if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) { gb->is_odd_frame ^= true; GB_display_vblank(gb, GB_VBLANK_TYPE_NORMAL_FRAME); } - gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; + gb->frame_skip_state = GB_FRAMESKIP_FIRST_FRAME_RENDERED; } } else { @@ -2002,16 +2018,19 @@ skip_slow_mode_3: gb->is_odd_frame ^= true; GB_display_vblank(gb, GB_VBLANK_TYPE_NORMAL_FRAME); } - if (gb->frame_skip_state == GB_FRAMESKIP_FIRST_FRAME_SKIPPED) { - gb->cgb_repeated_a_frame = true; - gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; - } - else { - gb->cgb_repeated_a_frame = false; - } } } + /* 3640 is just a few cycles less than 4 lines, no clue where the + AGB constant comes from (These are measured and confirmed) */ + gb->frame_repeat_countdown = LINES * LINE_LENGTH * 2 + (gb->model > GB_MODEL_CGB_E? 5982 : 3640); // 8MHz units + if (gb->display_cycles < gb->frame_repeat_countdown) { + gb->frame_repeat_countdown -= gb->display_cycles; + } + else { + gb->frame_repeat_countdown = 0; + } + GB_SLEEP(gb, display, 13, LINE_LENGTH - 5); } diff --git a/Core/display.h b/Core/display.h index 0c5c64e..38105bb 100644 --- a/Core/display.h +++ b/Core/display.h @@ -9,6 +9,7 @@ typedef enum { GB_VBLANK_TYPE_NORMAL_FRAME, // An actual Vblank-triggered frame GB_VBLANK_TYPE_LCD_OFF, // An artificial frame pushed while the LCD was off GB_VBLANK_TYPE_ARTIFICIAL, // An artificial frame pushed for some other reason + GB_VBLANK_TYPE_REPEAT, // A frame that would not render on actual hardware, but the screen should retain the previous frame } GB_vblank_type_t; #ifdef GB_INTERNAL diff --git a/Core/gb.c b/Core/gb.c index f8a9321..923a97f 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -1183,6 +1183,11 @@ void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output) gb->screen = output; } +uint32_t *GB_get_pixels_output(GB_gameboy_t *gb) +{ + return gb->screen; +} + void GB_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback) { gb->vblank_callback = callback; @@ -1220,6 +1225,11 @@ void GB_set_lcd_line_callback(GB_gameboy_t *gb, GB_lcd_line_callback_t callback) gb->lcd_line_callback = callback; } +void GB_set_lcd_status_callback(GB_gameboy_t *gb, GB_lcd_status_callback_t callback) +{ + gb->lcd_status_callback = callback; +} + const GB_palette_t GB_PALETTE_GREY = {{{0x00, 0x00, 0x00}, {0x55, 0x55, 0x55}, {0xAA, 0xAA, 0xAA}, {0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF}}}; const GB_palette_t GB_PALETTE_DMG = {{{0x08, 0x18, 0x10}, {0x39, 0x61, 0x39}, {0x84, 0xA5, 0x63}, {0xC6, 0xDE, 0x8C}, {0xD2, 0xE6, 0xA6}}}; const GB_palette_t GB_PALETTE_MGB = {{{0x07, 0x10, 0x0E}, {0x3A, 0x4C, 0x3A}, {0x81, 0x8D, 0x66}, {0xC2, 0xCE, 0x93}, {0xCF, 0xDA, 0xAC}}}; diff --git a/Core/gb.h b/Core/gb.h index 64e1acd..f308613 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -304,6 +304,7 @@ typedef void (*GB_boot_rom_load_callback_t)(GB_gameboy_t *gb, GB_boot_rom_t type typedef void (*GB_execution_callback_t)(GB_gameboy_t *gb, uint16_t address, uint8_t opcode); typedef void (*GB_lcd_line_callback_t)(GB_gameboy_t *gb, uint8_t line); +typedef void (*GB_lcd_status_callback_t)(GB_gameboy_t *gb, bool on); struct GB_breakpoint_s; struct GB_watchpoint_s; @@ -600,8 +601,8 @@ struct GB_gameboy_internal_s { GB_FRAMESKIP_LCD_TURNED_ON, // On a DMG, the LCD renders a blank screen during this state, // on a CGB, the previous frame is repeated (which might be // blank if the LCD was off for more than a few cycles) - GB_FRAMESKIP_FIRST_FRAME_SKIPPED, // This state is 'skipped' when emulating a DMG - GB_FRAMESKIP_SECOND_FRAME_RENDERED, + GB_FRAMESKIP_FIRST_FRAME_SKIPPED__DEPRECATED, + GB_FRAMESKIP_FIRST_FRAME_RENDERED, } frame_skip_state; bool oam_read_blocked; bool vram_read_blocked; @@ -650,9 +651,10 @@ struct GB_gameboy_internal_s { bool is_odd_frame; uint16_t last_tile_data_address; uint16_t last_tile_index_address; - bool cgb_repeated_a_frame; + GB_PADDING(bool, cgb_repeated_a_frame); uint8_t data_for_sel_glitch; bool delayed_glitch_hblank_interrupt; + uint32_t frame_repeat_countdown; ) /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ @@ -732,7 +734,7 @@ struct GB_gameboy_internal_s { GB_workboy_get_time_callback workboy_get_time_callback; GB_execution_callback_t execution_callback; GB_lcd_line_callback_t lcd_line_callback; - + GB_lcd_status_callback_t lcd_status_callback; /*** Debugger ***/ volatile bool debug_stopped, debug_disable; bool debug_fin_command, debug_next_command; @@ -894,6 +896,7 @@ void GB_log(GB_gameboy_t *gb, const char *fmt, ...) __printflike(2, 3); void GB_attributed_log(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, ...) __printflike(3, 4); void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output); +uint32_t *GB_get_pixels_output(GB_gameboy_t *gb); void GB_set_border_mode(GB_gameboy_t *gb, GB_border_mode_t border_mode); void GB_set_infrared_input(GB_gameboy_t *gb, bool state); @@ -911,6 +914,7 @@ void GB_set_boot_rom_load_callback(GB_gameboy_t *gb, GB_boot_rom_load_callback_t void GB_set_execution_callback(GB_gameboy_t *gb, GB_execution_callback_t callback); void GB_set_lcd_line_callback(GB_gameboy_t *gb, GB_lcd_line_callback_t callback); +void GB_set_lcd_status_callback(GB_gameboy_t *gb, GB_lcd_status_callback_t callback); void GB_set_palette(GB_gameboy_t *gb, const GB_palette_t *palette); const GB_palette_t *GB_get_palette(GB_gameboy_t *gb); diff --git a/Core/memory.c b/Core/memory.c index 22c041f..bac55e0 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -1440,6 +1440,9 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_IO_LCDC: if ((value & 0x80) && !(gb->io_registers[GB_IO_LCDC] & 0x80)) { // LCD turned on + if (gb->lcd_status_callback) { + gb->lcd_status_callback(gb, true); + } if (!gb->lcd_disabled_outside_of_vblank && (gb->cycles_since_vblank_callback > 10 * 456 || GB_is_sgb(gb))) { // Trigger a vblank here so we don't exceed LCDC_PERIOD @@ -1451,15 +1454,18 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->double_speed_alignment = 0; gb->cycles_for_line = 0; if (GB_is_sgb(gb)) { - gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; + gb->frame_skip_state = GB_FRAMESKIP_FIRST_FRAME_RENDERED; } - else if (gb->frame_skip_state == GB_FRAMESKIP_SECOND_FRAME_RENDERED) { + else { gb->frame_skip_state = GB_FRAMESKIP_LCD_TURNED_ON; } GB_timing_sync(gb); } else if (!(value & 0x80) && (gb->io_registers[GB_IO_LCDC] & 0x80)) { /* Sync after turning off LCD */ + if (gb->lcd_status_callback) { + gb->lcd_status_callback(gb, false); + } gb->double_speed_alignment = 0; GB_timing_sync(gb); GB_lcd_off(gb); diff --git a/SDL/main.c b/SDL/main.c index 7b87392..c3f9594 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -509,15 +509,17 @@ static void vblank(GB_gameboy_t *gb, GB_vblank_type_t type) true); osd_countdown--; } - if (configuration.blending_mode) { - render_texture(active_pixel_buffer, previous_pixel_buffer); - uint32_t *temp = active_pixel_buffer; - active_pixel_buffer = previous_pixel_buffer; - previous_pixel_buffer = temp; - GB_set_pixels_output(gb, active_pixel_buffer); - } - else { - render_texture(active_pixel_buffer, NULL); + if (type != GB_VBLANK_TYPE_REPEAT) { + if (configuration.blending_mode) { + render_texture(active_pixel_buffer, previous_pixel_buffer); + uint32_t *temp = active_pixel_buffer; + active_pixel_buffer = previous_pixel_buffer; + previous_pixel_buffer = temp; + GB_set_pixels_output(gb, active_pixel_buffer); + } + else { + render_texture(active_pixel_buffer, NULL); + } } do_rewind = rewind_down; handle_events(gb); diff --git a/libretro/libretro.c b/libretro/libretro.c index 2f51bd8..eb5f2dd 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -99,6 +99,8 @@ static bool auto_sgb_enabled[2] = { static uint32_t *frame_buf = NULL; static uint32_t *frame_buf_copy = NULL; +static uint32_t retained_frame_1[256 * 224]; +static uint32_t retained_frame_2[256 * 224]; static struct retro_log_callback logging; static retro_log_printf_t log_cb; @@ -250,14 +252,42 @@ static void audio_callback(GB_gameboy_t *gb, GB_sample_t *sample) static void vblank1(GB_gameboy_t *gb, GB_vblank_type_t type) { + if (type == GB_VBLANK_TYPE_REPEAT) { + memcpy(GB_get_pixels_output(gb), + retained_frame_1, + GB_get_screen_width(gb) * GB_get_screen_height(gb) * sizeof(uint32_t)); + } vblank1_occurred = true; } static void vblank2(GB_gameboy_t *gb, GB_vblank_type_t type) { + if (type == GB_VBLANK_TYPE_REPEAT) { + memcpy(GB_get_pixels_output(gb), + retained_frame_2, + GB_get_screen_width(gb) * GB_get_screen_height(gb) * sizeof(uint32_t)); + } vblank2_occurred = true; } +static void lcd_status_change_1(GB_gameboy_t *gb, bool on) +{ + if (!on) { + memcpy(retained_frame_1, + GB_get_pixels_output(gb), + GB_get_screen_width(gb) * GB_get_screen_height(gb) * sizeof(uint32_t)); + } +} + +static void lcd_status_change_2(GB_gameboy_t *gb, bool on) +{ + if (!on) { + memcpy(retained_frame_2, + GB_get_pixels_output(gb), + GB_get_screen_width(gb) * GB_get_screen_height(gb) * sizeof(uint32_t)); + } +} + static bool bit_to_send1 = true, bit_to_send2 = true; static void serial_start1(GB_gameboy_t *gb, bool bit_received) @@ -627,8 +657,10 @@ static void init_for_current_model(unsigned id) /* todo: attempt to make these more generic */ GB_set_vblank_callback(&gameboy[0], (GB_vblank_callback_t) vblank1); + GB_set_lcd_status_callback(&gameboy[0], lcd_status_change_1); if (emulated_devices == 2) { GB_set_vblank_callback(&gameboy[1], (GB_vblank_callback_t) vblank2); + GB_set_lcd_status_callback(&gameboy[2], lcd_status_change_2); if (link_cable_emulation) { set_link_cable_state(true); }