SameBoy | Accurate GB/GBC emulator |
| download: https://git.y1.nz/archives/sameboy.tar.gz | |
| README | Files | Log | Refs | LICENSE |
commit aff7f1706c380861eecadae2a3e9d54ebb66db27 parent 33d237706e18d92fb79e3fd7313d5181d8a806cd Author: Lior Halphon <LIJI32@gmail.com> Date: Fri, 4 Jul 2025 14:39:44 +0300 Add turbo cap options to the core and frontends, improve frame skipping, replace iOS' turbo speed option with the new turbo cap. Addresses #708. Diffstat:
| M | Cocoa/Document.m | 9 | +++++++++ |
| M | Cocoa/GBApp.m | 1 | + |
| M | Cocoa/GBPreferencesWindow.h | 3 | +++ |
| M | Cocoa/GBPreferencesWindow.m | 38 | ++++++++++++++++++++++++++++++++++++++ |
| M | Cocoa/Preferences.xib | 69 | +++++++++++++++++++++++++++++++++++++++++++++++++++------------------ |
| M | Core/gb.c | 8 | ++++++++ |
| M | Core/gb.h | 3 | +++ |
| M | Core/timing.c | 27 | ++++++++++++++++++--------- |
| M | SDL/configuration.h | 1 | + |
| M | SDL/gui.c | 40 | ++++++++++++++++++++++++++++++++++++++++ |
| M | SDL/main.c | 20 | +++++++++++++++++--- |
| M | iOS/GBSettingsViewController.m | 91 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------- |
| M | iOS/GBViewController.m | 7 | ++++--- |
| M | iOS/main.m | 9 | ++++++++- |
14 files changed, 269 insertions(+), 57 deletions(-)
diff --git a/Cocoa/Document.m b/Cocoa/Document.m @@ -346,6 +346,12 @@ static void debuggerReloadCallback(GB_gameboy_t *gb) [self observeStandardDefaultsKey:@"GBDebuggerFontSize" withBlock:^(NSString *value) { [weakSelf updateFonts]; }]; + + [self observeStandardDefaultsKey:@"GBTurboCap" withBlock:^(NSNumber *value) { + if (!_master) { + GB_set_turbo_cap(gb, value.doubleValue); + } + }]; } - (void)updateMinSize @@ -2586,6 +2592,8 @@ enum GBWindowResizeAction } GB_set_turbo_mode(&_gb, false, false); GB_set_turbo_mode(&partner->_gb, false, false); + GB_set_turbo_cap(&_gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBTurboCap"]); + GB_set_turbo_cap(&partner->_gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBTurboCap"]); } } @@ -2601,6 +2609,7 @@ enum GBWindowResizeAction GB_set_turbo_mode(&partner->_gb, true, true); _slave = partner; partner->_master = self; + GB_set_turbo_cap(&partner->_gb, 0); _linkOffset = 0; GB_set_serial_transfer_bit_start_callback(&_gb, _linkCableBitStart); GB_set_serial_transfer_bit_start_callback(&partner->_gb, _linkCableBitStart); diff --git a/Cocoa/GBApp.m b/Cocoa/GBApp.m @@ -100,6 +100,7 @@ static uint32_t color_to_int(NSColor *color) @"GBDebuggerFontSize": @12, @"GBColorPalette": @1, + @"GBTurboCap": @0, // Default themes @"GBThemes": @{ diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h @@ -19,6 +19,9 @@ @property IBOutlet NSPopUpButton *colorPalettePopupButton; @property IBOutlet NSPopUpButton *hotkey1PopupButton; @property IBOutlet NSPopUpButton *hotkey2PopupButton; +@property IBOutlet NSButton *turboCapButton; +@property IBOutlet NSSlider *turboCapSlider; +@property IBOutlet NSTextField *turboCapLabel; @property IBOutlet GBTitledPopUpButton *fontPopupButton; @property IBOutlet NSStepper *fontSizeStepper; diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m @@ -342,6 +342,13 @@ static inline NSString *keyEquivalentString(NSMenuItem *item) _fontSizeStepper.intValue = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBDebuggerFontSize"]; [self updateFonts]; + + double cap = [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBTurboCap"]; + if (cap) { + _turboCapSlider.intValue = round(cap * 100); + _turboCapButton.state = NSOnState; + } + [self turboCapToggled:_turboCapButton]; } - (IBAction)fontSizeChanged:(id)sender @@ -555,4 +562,35 @@ static inline NSString *keyEquivalentString(NSMenuItem *item) [GBJoyConManager sharedInstance].arrangementMode = false; } +- (IBAction)turboCapToggled:(NSButton *)sender +{ + if (sender.state) { + _turboCapSlider.enabled = true; + [self turboCapChanged:_turboCapSlider]; + if (@available(macOS 10.10, *)) { + _turboCapLabel.textColor = [NSColor labelColor]; + } + else { + _turboCapLabel.textColor = [NSColor blackColor]; + } + } + else { + _turboCapSlider.enabled = false; + _turboCapLabel.enabled = false; + [[NSUserDefaults standardUserDefaults] setDouble:0 forKey:@"GBTurboCap"]; + if (@available(macOS 10.10, *)) { + _turboCapLabel.textColor = [NSColor disabledControlTextColor]; + } + else { + _turboCapLabel.textColor = [NSColor colorWithWhite:0 alpha:0.25]; + } + } +} + +- (IBAction)turboCapChanged:(NSSlider *)sender +{ + _turboCapLabel.stringValue = [NSString stringWithFormat:@"%d%%", sender.intValue]; + [[NSUserDefaults standardUserDefaults] setDouble:sender.doubleValue / 100.0 forKey:@"GBTurboCap"]; +} + @end diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib @@ -94,6 +94,9 @@ <outlet property="playerListButton" destination="gWx-7h-0xq" id="zo6-82-JId"/> <outlet property="preferredJoypadButton" destination="0Az-0R-oNw" id="7JM-tw-BhK"/> <outlet property="skipButton" destination="d2I-jU-sLb" id="udX-8K-0sK"/> + <outlet property="turboCapButton" destination="T4Z-h2-2N2" id="GAy-dG-u63"/> + <outlet property="turboCapLabel" destination="PUc-hN-OU6" id="hpv-OM-J9y"/> + <outlet property="turboCapSlider" destination="Dq8-vH-5Do" id="LFx-Es-hZ3"/> </connections> <point key="canvasLocation" x="183" y="354"/> </window> @@ -456,11 +459,11 @@ <point key="canvasLocation" x="-501" y="175.5"/> </customView> <customView id="ymk-46-SX7"> - <rect key="frame" x="0.0" y="0.0" width="320" height="434"/> + <rect key="frame" x="0.0" y="0.0" width="320" height="487"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <subviews> <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="w9w-yX-KxB"> - <rect key="frame" x="18" y="342" width="280" height="17"/> + <rect key="frame" x="18" y="395" width="280" height="17"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Rewinding duration:" id="JaO-5h-ugl"> <font key="font" metaFont="system"/> @@ -469,7 +472,7 @@ </textFieldCell> </textField> <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="o3Z-34-FJk"> - <rect key="frame" x="18" y="287" width="280" height="17"/> + <rect key="frame" x="18" y="340" width="280" height="17"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Real Time Clock emulation:" id="Qoi-ub-YtI"> <font key="font" metaFont="system"/> @@ -478,7 +481,7 @@ </textFieldCell> </textField> <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MI2-ql-f6M"> - <rect key="frame" x="18" y="397" width="280" height="17"/> + <rect key="frame" x="18" y="450" width="280" height="17"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Boot ROMs location:" id="nj0-Cb-gEA"> <font key="font" metaFont="system"/> @@ -487,7 +490,7 @@ </textFieldCell> </textField> <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Wg8-hJ-df9"> - <rect key="frame" x="18" y="215" width="280" height="17"/> + <rect key="frame" x="18" y="213" width="280" height="17"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Game Boy revision:" id="GIA-ep-SBi"> <font key="font" metaFont="system"/> @@ -496,7 +499,7 @@ </textFieldCell> </textField> <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LFw-Uk-cPR" customClass="GBPreferencePopUpButton"> - <rect key="frame" x="30" y="183" width="262" height="25"/> + <rect key="frame" x="30" y="181" width="262" height="25"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <popUpButtonCell key="cell" type="push" title="DMG-CPU B" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="2" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" selectedItem="aXT-sE-m5Z" id="FuX-Hc-uO7"> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> @@ -515,7 +518,7 @@ </userDefinedRuntimeAttributes> </popUpButton> <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MAq-1X-Gpo"> - <rect key="frame" x="18" y="105" width="280" height="17"/> + <rect key="frame" x="18" y="103" width="280" height="17"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Game Boy Color revision:" id="edD-t7-vwk"> <font key="font" metaFont="system"/> @@ -524,7 +527,7 @@ </textFieldCell> </textField> <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dlD-sk-SHO" customClass="GBPreferencePopUpButton"> - <rect key="frame" x="30" y="73" width="262" height="25"/> + <rect key="frame" x="30" y="71" width="262" height="25"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <popUpButtonCell key="cell" type="push" title="CPU CGB E" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="517" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" selectedItem="3lF-1Q-2SS" id="Edt-1S-dXz"> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> @@ -545,7 +548,7 @@ </userDefinedRuntimeAttributes> </popUpButton> <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tAa-0A-0fP"> - <rect key="frame" x="18" y="160" width="280" height="17"/> + <rect key="frame" x="18" y="158" width="280" height="17"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Super Game Boy model:" id="d0g-rk-FK0"> <font key="font" metaFont="system"/> @@ -554,11 +557,11 @@ </textFieldCell> </textField> <box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="mdm-eW-ia1"> - <rect key="frame" x="12" y="238" width="296" height="5"/> + <rect key="frame" x="12" y="236" width="296" height="5"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> </box> <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tFf-H1-XUL" customClass="GBPreferencePopUpButton"> - <rect key="frame" x="30" y="255" width="262" height="25"/> + <rect key="frame" x="30" y="308" width="262" height="25"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <popUpButtonCell key="cell" type="push" title="Sync to system clock" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="arp-Qi-Xix" id="uRs-Ag-Sbw"> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> @@ -577,7 +580,7 @@ </userDefinedRuntimeAttributes> </popUpButton> <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wC4-aJ-mhQ"> - <rect key="frame" x="30" y="365" width="262" height="25"/> + <rect key="frame" x="30" y="418" width="262" height="25"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <popUpButtonCell key="cell" type="push" title="Use built-in boot ROMs" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Tnm-SR-ZEm" id="T3Y-Ln-Onl"> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> @@ -601,7 +604,7 @@ </popUpButtonCell> </popUpButton> <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7fg-Ww-JjR" customClass="GBPreferencePopUpButton"> - <rect key="frame" x="30" y="310" width="262" height="25"/> + <rect key="frame" x="30" y="363" width="262" height="25"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <popUpButtonCell key="cell" type="push" title="Disabled" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="lxQ-4n-kEv" id="lvb-QF-0Ht"> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> @@ -625,7 +628,7 @@ </userDefinedRuntimeAttributes> </popUpButton> <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5r5-qY-b8h"> - <rect key="frame" x="18" y="50" width="280" height="17"/> + <rect key="frame" x="18" y="48" width="280" height="17"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Game Boy Advance revision:" id="R5q-dJ-NvD"> <font key="font" metaFont="system"/> @@ -634,7 +637,7 @@ </textFieldCell> </textField> <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3Dl-S6-O7c" customClass="GBPreferencePopUpButton"> - <rect key="frame" x="30" y="18" width="262" height="25"/> + <rect key="frame" x="30" y="16" width="262" height="25"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <popUpButtonCell key="cell" type="push" title="CPU AGB A (GBA)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="519" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" selectedItem="8bk-wP-Cbr" id="EhC-I2-5Th"> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> @@ -655,7 +658,7 @@ </userDefinedRuntimeAttributes> </popUpButton> <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dza-T7-RkX" customClass="GBPreferencePopUpButton"> - <rect key="frame" x="30" y="128" width="262" height="25"/> + <rect key="frame" x="30" y="126" width="262" height="25"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <popUpButtonCell key="cell" type="push" title="Super Game Boy (NTSC)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="4" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" selectedItem="x5A-7f-ef9" id="2Mt-ci-bB0"> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> @@ -672,8 +675,38 @@ <userDefinedRuntimeAttribute type="string" keyPath="preferenceName" value="GBSGBModel"/> </userDefinedRuntimeAttributes> </popUpButton> + <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T4Z-h2-2N2"> + <rect key="frame" x="18" y="285" width="284" height="18"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> + <buttonCell key="cell" type="check" title="Cap turbo speed to:" bezelStyle="regularSquare" imagePosition="left" inset="2" id="m3e-2o-U6J"> + <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + <connections> + <action selector="turboCapToggled:" target="QvC-M9-y7g" id="A0p-gr-hgq"/> + </connections> + </button> + <slider verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Dq8-vH-5Do"> + <rect key="frame" x="31" y="252" width="210" height="28"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <sliderCell key="cell" continuous="YES" enabled="NO" state="on" alignment="left" minValue="150" maxValue="400" doubleValue="200" tickMarkPosition="above" numberOfTickMarks="6" sliderType="linear" id="wFe-MN-jqm"> + <font key="font" metaFont="cellTitle"/> + </sliderCell> + <connections> + <action selector="turboCapChanged:" target="QvC-M9-y7g" id="lh6-Kx-M7o"/> + </connections> + </slider> + <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PUc-hN-OU6"> + <rect key="frame" x="245" y="261" width="53" height="16"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="200%" id="10g-W1-uBl"> + <font key="font" metaFont="system"/> + <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + </textField> </subviews> - <point key="canvasLocation" x="-501" y="697"/> + <point key="canvasLocation" x="-501" y="723.5"/> </customView> <customView id="Zn1-Y5-RbR"> <rect key="frame" x="0.0" y="0.0" width="320" height="201"/> @@ -1048,7 +1081,7 @@ </connections> </button> <box horizontalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="Bm0-C4-acq"> - <rect key="frame" x="314" y="20" width="5" height="372"/> + <rect key="frame" x="313" y="20" width="5" height="372"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" heightSizable="YES"/> </box> <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uw7-6d-2xu"> diff --git a/Core/gb.c b/Core/gb.c @@ -1222,8 +1222,10 @@ uint64_t GB_run_frame(GB_gameboy_t *gb) /* Configure turbo temporarily, the user wants to handle FPS capping manually. */ bool old_turbo = gb->turbo; bool old_dont_skip = gb->turbo_dont_skip; + double old_turbo_cap = gb->turbo_cap_multiplier; gb->turbo = true; gb->turbo_dont_skip = true; + gb->turbo_cap_multiplier = 0; gb->cycles_since_last_sync = 0; while (true) { @@ -1234,6 +1236,7 @@ uint64_t GB_run_frame(GB_gameboy_t *gb) } gb->turbo = old_turbo; gb->turbo_dont_skip = old_dont_skip; + gb->turbo_cap_multiplier = old_turbo_cap; return gb->cycles_since_last_sync * 1000000000LL / 2 / GB_get_clock_rate(gb); /* / 2 because we use 8MHz units */ } @@ -1418,6 +1421,11 @@ void GB_set_turbo_mode(GB_gameboy_t *gb, bool on, bool no_frame_skip) gb->turbo_dont_skip = no_frame_skip; } +void GB_set_turbo_cap(GB_gameboy_t *gb, double multiplier) +{ + gb->turbo_cap_multiplier = multiplier; +} + void GB_set_rendering_disabled(GB_gameboy_t *gb, bool disabled) { gb->disable_rendering = disabled; diff --git a/Core/gb.h b/Core/gb.h @@ -700,6 +700,7 @@ struct GB_gameboy_internal_s { /* Timing */ uint64_t last_sync; + uint64_t last_render; uint64_t cycles_since_last_sync; // In 8MHz units GB_rtc_mode_t rtc_mode; uint32_t rtc_second_length; @@ -833,6 +834,7 @@ struct GB_gameboy_internal_s { /* Misc */ bool turbo; bool turbo_dont_skip; + double turbo_cap_multiplier; bool enable_skipped_frame_vblank_callbacks; bool disable_rendering; uint8_t boot_rom[0x900]; @@ -953,6 +955,7 @@ void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t int GB_load_battery(GB_gameboy_t *gb, const char *path); void GB_set_turbo_mode(GB_gameboy_t *gb, bool on, bool no_frame_skip); +void GB_set_turbo_cap(GB_gameboy_t *gb, double multiplier); // Use 0 to use no cap void GB_set_rendering_disabled(GB_gameboy_t *gb, bool disabled); void GB_log(GB_gameboy_t *gb, const char *fmt, ...) __printflike(2, 3); diff --git a/Core/timing.c b/Core/timing.c @@ -47,10 +47,10 @@ bool GB_timing_sync_turbo(GB_gameboy_t *gb) #endif if (!gb->turbo_dont_skip) { int64_t nanoseconds = get_nanoseconds(); - if (nanoseconds <= gb->last_sync + (1000000000LL * LCDC_PERIOD / GB_get_clock_rate(gb))) { + if (nanoseconds <= gb->last_render + 1000000000 / 60) { return true; } - gb->last_sync = nanoseconds; + gb->last_render = nanoseconds; } return false; } @@ -63,23 +63,32 @@ void GB_timing_sync(GB_gameboy_t *gb) /* Prevent syncing if not enough time has passed.*/ if (gb->cycles_since_last_sync < LCDC_PERIOD / 3) return; + unsigned target_clock_rate; if (gb->turbo) { - gb->cycles_since_last_sync = 0; - if (gb->update_input_hint_callback) { - gb->update_input_hint_callback(gb); + if (gb->turbo_cap_multiplier) { + target_clock_rate = GB_get_clock_rate(gb) * gb->turbo_cap_multiplier; } - return; + else { + gb->cycles_since_last_sync = 0; + if (gb->update_input_hint_callback) { + gb->update_input_hint_callback(gb); + } + return; + } + } + else { + target_clock_rate = GB_get_clock_rate(gb); } - uint64_t target_nanoseconds = gb->cycles_since_last_sync * 1000000000LL / 2 / GB_get_clock_rate(gb); /* / 2 because we use 8MHz units */ + uint64_t target_nanoseconds = gb->cycles_since_last_sync * 1000000000LL / 2 / target_clock_rate; /* / 2 because we use 8MHz units */ int64_t nanoseconds = get_nanoseconds(); int64_t time_to_sleep = target_nanoseconds + gb->last_sync - nanoseconds; - if (time_to_sleep > 0 && time_to_sleep < LCDC_PERIOD * 1200000000LL / GB_get_clock_rate(gb)) { // +20% to be more forgiving + if (time_to_sleep > 0 && time_to_sleep < LCDC_PERIOD * 1200000000LL / target_clock_rate) { // +20% to be more forgiving nsleep(time_to_sleep); gb->last_sync += target_nanoseconds; } else { - if (time_to_sleep < 0 && -time_to_sleep < LCDC_PERIOD * 1200000000LL / GB_get_clock_rate(gb)) { + if (time_to_sleep < 0 && -time_to_sleep < LCDC_PERIOD * 1200000000LL / target_clock_rate) { // We're running a bit too slow, but the difference is small enough, // just skip this sync and let it even out return; diff --git a/SDL/configuration.h b/SDL/configuration.h @@ -164,6 +164,7 @@ typedef struct { /* v1.0.2 */ int8_t vsync_mode; + uint8_t turbo_cap; }; } configuration_t; diff --git a/SDL/gui.c b/SDL/gui.c @@ -1173,6 +1173,45 @@ static const char *current_agb_revision_string(unsigned index) return "CPU AGB A (AGB)"; } +static void cycle_turbo_cap(unsigned index) +{ + + if (configuration.turbo_cap >= 16) { // 400% + configuration.turbo_cap = 0; // uncapped + } + else if (configuration.turbo_cap == 0) { // uncapped + configuration.turbo_cap = 6; // 150% + } + else { + configuration.turbo_cap++; + } +} + +static void cycle_turbo_cap_backwards(unsigned index) +{ + + if (configuration.turbo_cap == 0) { // uncapped + configuration.turbo_cap = 16; // 400% + } + else if (configuration.turbo_cap == 6) { // 150% + configuration.turbo_cap = 0; // uncapped + } + else { + configuration.turbo_cap--; + } +} + +static const char *current_turbo_cap_string(unsigned index) +{ + if (configuration.turbo_cap == 0) { + return "Uncapped"; + } + static char ret[5]; + snprintf(ret, sizeof(ret), "%d%%", configuration.turbo_cap * 25); + return ret; +} + + static const struct menu_item emulation_menu[] = { {"Emulated Model:", cycle_model, current_model_string, cycle_model_backwards}, {"SGB Revision:", cycle_sgb_revision, current_sgb_revision_string, cycle_sgb_revision_backwards}, @@ -1181,6 +1220,7 @@ static const struct menu_item emulation_menu[] = { {"Boot ROMs Folder:", toggle_bootrom, current_bootrom_string, toggle_bootrom}, {"Rewind Length:", cycle_rewind, current_rewind_string, cycle_rewind_backwards}, {"Real Time Clock:", toggle_rtc_mode, current_rtc_mode_string, toggle_rtc_mode}, + {"Turbo speed cap:", cycle_turbo_cap, current_turbo_cap_string, cycle_turbo_cap_backwards}, {"Back", enter_options_menu}, {NULL,} }; diff --git a/SDL/main.c b/SDL/main.c @@ -277,6 +277,7 @@ static void open_menu(void) GB_set_highpass_filter_mode(&gb, configuration.highpass_mode); GB_set_rewind_length(&gb, configuration.rewind_length); GB_set_rtc_mode(&gb, configuration.rtc_mode); + GB_set_turbo_cap(&gb, configuration.turbo_cap / 4.0); if (previous_width != GB_get_screen_width(&gb)) { signed current_window_width, current_window_height; SDL_GetWindowSize(window, ¤t_window_width, ¤t_window_height); @@ -381,6 +382,7 @@ static void handle_events(GB_gameboy_t *gb) GB_audio_clear_queue(); turbo_down = event.type == SDL_JOYBUTTONDOWN; GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down); + SDL_GL_SetSwapInterval(turbo_down? 0 : configuration.vsync_mode); } else if (button == JOYPAD_BUTTON_SLOW_MOTION) { underclock_down = event.type == SDL_JOYBUTTONDOWN; @@ -391,6 +393,7 @@ static void handle_events(GB_gameboy_t *gb) rewind_paused = false; } GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down); + SDL_GL_SetSwapInterval(turbo_down? 0 : configuration.vsync_mode); } else if (button == JOYPAD_BUTTON_MENU && event.type == SDL_JOYBUTTONDOWN) { open_menu(); @@ -610,6 +613,7 @@ static void handle_events(GB_gameboy_t *gb) turbo_down = event.type == SDL_KEYDOWN; GB_audio_clear_queue(); GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down); + SDL_GL_SetSwapInterval(turbo_down? 0 : configuration.vsync_mode); } else if (event.key.keysym.scancode == configuration.keys_2[GB_CONF_KEYS2_REWIND]) { rewind_down = event.type == SDL_KEYDOWN; @@ -617,6 +621,7 @@ static void handle_events(GB_gameboy_t *gb) rewind_paused = false; } GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down); + SDL_GL_SetSwapInterval(turbo_down? 0 : configuration.vsync_mode); } else if (event.key.keysym.scancode == configuration.keys_2[GB_CONF_KEYS2_UNDERCLOCK]) { underclock_down = event.type == SDL_KEYDOWN; @@ -737,15 +742,23 @@ static void debugger_reset(int ignore) #endif static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample) -{ +{ if (turbo_down) { static unsigned skip = 0; skip++; if (skip == GB_audio_get_frequency() / 8) { skip = 0; } - if (skip > GB_audio_get_frequency() / 16) { - return; + if (configuration.turbo_cap) { + if (skip > GB_audio_get_frequency() / 8 * 4 / configuration.turbo_cap) { + return; + } + } + else { + + if (skip > GB_audio_get_frequency() / 16) { + return; + } } } @@ -1095,6 +1108,7 @@ restart:; GB_set_highpass_filter_mode(&gb, configuration.highpass_mode); GB_set_rewind_length(&gb, configuration.rewind_length); GB_set_rtc_mode(&gb, configuration.rtc_mode); + GB_set_turbo_cap(&gb, configuration.turbo_cap / 4.0); GB_set_update_input_hint_callback(&gb, handle_events); GB_apu_set_sample_callback(&gb, gb_audio_callback); diff --git a/iOS/GBSettingsViewController.m b/iOS/GBSettingsViewController.m @@ -15,7 +15,8 @@ static NSString const *typeCheck = @"check"; static NSString const *typeDisabled = @"disabled"; static NSString const *typeSeparator = @"separator"; static NSString const *typeSlider = @"slider"; -static NSString const *typeLightTemp = @"typeLightTemp"; +static NSString const *typeLightTemp = @"lightTemp"; +static NSString const *typeTurboSlider = @"turboSlider"; @implementation GBSettingsViewController { @@ -23,6 +24,7 @@ static NSString const *typeLightTemp = @"typeLightTemp"; UINavigationController *_detailsNavigation; NSArray<NSArray<GBTheme *> *> *_themes; // For prewarming bool _iPadRoot; + __weak UISlider *_turboSlider; } + (UIImage *)settingsImageNamed:(NSString *)name @@ -67,17 +69,32 @@ static NSString const *typeLightTemp = @"typeLightTemp"; ] }, @{ - @"header": @"Turbo Speed", + @"header": @"Turbo Speed Cap", @"items": @[ - @{@"type": typeRadio, @"pref": @"GBTurboSpeed", @"title": @"200%", @"value": @2,}, - @{@"type": typeRadio, @"pref": @"GBTurboSpeed", @"title": @"400%", @"value": @4,}, - @{@"type": typeRadio, @"pref": @"GBTurboSpeed", @"title": @"Uncapped", @"value": @1,}, + @{@"type": typeCheck, @"pref": @"GBTurboCap", @"title": @"Cap Turbo Speed", @"block": ^void(GBSettingsViewController *controller) { + if ([[NSUserDefaults standardUserDefaults] floatForKey:@"GBTurboCap"] == 1.0) { + if (controller->_turboSlider) { + [[NSUserDefaults standardUserDefaults] setFloat:controller->_turboSlider.value forKey:@"GBTurboCap"]; + controller->_turboSlider.enabled = true; + } + else { + [[NSUserDefaults standardUserDefaults] setFloat:2.0 forKey:@"GBTurboCap"]; + } + } + else { + controller->_turboSlider.enabled = false; + } + }}, + @{@"type": typeTurboSlider, @"pref": @"GBTurboCap", @"min": @1.5, @"max": @4, @"minImage": @"tortoise.fill", @"maxImage": @"hare.fill"}, ], @"footer": ^NSString *(){ if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBDynamicSpeed"]) { - return @"This setting will have no effect because horizontal swipes are configured to dynamically control speed in the “Controls” settings"; + return @"This setting will have no effect because horizontal swipes are configured to dynamically control speed in the “Controls” settings."; } - return @""; + if ([[NSUserDefaults standardUserDefaults] doubleForKey:@"GBTurboCap"]) { + return [NSString stringWithFormat:@"Turbo speed is capped to %u%%", (unsigned)round([[NSUserDefaults standardUserDefaults] doubleForKey:@"GBTurboCap"] * 100)]; + } + return @"Turbo speed is not capped"; }, }, @{ @@ -741,10 +758,11 @@ static id ValueForItem(NSDictionary *item) UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:nil]; cell.textLabel.text = item[@"title"]; - if (item[@"type"] == typeSubmenu || item[@"type"] == typeOptionSubmenu || item[@"type"] == typeBlock) { + NSString *type = item[@"type"]; + if (type == typeSubmenu || type == typeOptionSubmenu || type == typeBlock) { cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; cell.selectionStyle = UITableViewCellSelectionStyleBlue; - if (item[@"type"] == typeOptionSubmenu) { + if (type == typeOptionSubmenu) { for (NSDictionary *section in item[@"submenu"]) { for (NSDictionary *item in section[@"items"]) { if (item[@"value"] && [ValueForItem(item) isEqual:item[@"value"]]) { @@ -758,14 +776,14 @@ static id ValueForItem(NSDictionary *item) cell.detailTextLabel.text = [[NSUserDefaults standardUserDefaults] stringForKey:item[@"pref"]]; } } - else if (item[@"type"] == typeRadio) { + else if (type == typeRadio) { id settingValue = ValueForItem(item); id itemValue = item[@"value"]; if (settingValue == itemValue || [settingValue isEqual:itemValue]) { cell.accessoryType = UITableViewCellAccessoryCheckmark; } } - else if (item[@"type"] == typeCheck) { + else if (type == typeCheck) { UISwitch *button = [[UISwitch alloc] init]; cell.accessoryView = button; if ([[NSUserDefaults standardUserDefaults] boolForKey:item[@"pref"]]) { @@ -775,6 +793,9 @@ static id ValueForItem(NSDictionary *item) __weak typeof(self) weakSelf = self; id block = ^(){ [[NSUserDefaults standardUserDefaults] setBool:button.on forKey:item[@"pref"]]; + if (item[@"block"]) { + ((void(^)(GBSettingsViewController *))item[@"block"])(self); + } unsigned section = [indexPath indexAtPosition:0]; UITableViewHeaderFooterView *view = [weakSelf.tableView footerViewForSection:section]; view.textLabel.text = [weakSelf tableView:weakSelf.tableView titleForFooterInSection:section]; @@ -789,7 +810,7 @@ static id ValueForItem(NSDictionary *item) [button addTarget:block action:@selector(invoke) forControlEvents:UIControlEventValueChanged]; cell.selectionStyle = UITableViewCellSelectionStyleNone; } - else if (item[@"type"] == typeDisabled) { + else if (type == typeDisabled) { cell.selectionStyle = UITableViewCellSelectionStyleNone; if (@available(iOS 13.0, *)) { cell.textLabel.textColor = [UIColor separatorColor]; @@ -798,24 +819,34 @@ static id ValueForItem(NSDictionary *item) cell.textLabel.textColor = [UIColor colorWithWhite:0 alpha:0.75]; } } - else if (item[@"type"] == typeSeparator) { + else if (type == typeSeparator) { cell.backgroundColor = [UIColor clearColor]; cell.separatorInset = UIEdgeInsetsZero; } - else if (item[@"type"] == typeSlider || - item[@"type"] == typeLightTemp) { + else if (type == typeSlider || + type == typeLightTemp || + type == typeTurboSlider) { CGRect rect = cell.contentView.bounds; rect.size.width -= 24; rect.size.height -= 24; rect.origin.x += 12; rect.origin.y += 12; - UISlider *slider = [item[@"type"] == typeLightTemp? [GBSlider alloc] : [UISlider alloc] initWithFrame:rect]; + UISlider *slider = [type == typeLightTemp? [GBSlider alloc] : [UISlider alloc] initWithFrame:rect]; slider.continuous = true; slider.minimumValue = [item[@"min"] floatValue]; slider.maximumValue = [item[@"max"] floatValue]; slider.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [cell.contentView addSubview:slider]; - slider.value = [[NSUserDefaults standardUserDefaults] floatForKey:item[@"pref"]]; + if (type == typeTurboSlider) { + slider.value = [[NSUserDefaults standardUserDefaults] floatForKey:item[@"pref"]] ?: 2.0; + _turboSlider = slider; + if (![[NSUserDefaults standardUserDefaults] floatForKey:item[@"pref"]]) { + slider.enabled = false; + } + } + else { + slider.value = [[NSUserDefaults standardUserDefaults] floatForKey:item[@"pref"]]; + } cell.selectionStyle = UITableViewCellSelectionStyleNone; if (@available(iOS 13.0, *)) { @@ -826,8 +857,19 @@ static id ValueForItem(NSDictionary *item) } } + __weak typeof(self) weakSelf = self; id block = ^(){ [[NSUserDefaults standardUserDefaults] setDouble:slider.value forKey:item[@"pref"]]; + if (type == typeTurboSlider) { + unsigned section = [indexPath indexAtPosition:0]; + UITableViewHeaderFooterView *view = [weakSelf.tableView footerViewForSection:section]; + view.textLabel.text = [weakSelf tableView:weakSelf.tableView titleForFooterInSection:section]; + [UIView setAnimationsEnabled:false]; + [weakSelf.tableView beginUpdates]; + [view sizeToFit]; + [weakSelf.tableView endUpdates]; + [UIView setAnimationsEnabled:true]; + } }; objc_setAssociatedObject(cell, "RetainedBlock", block, OBJC_ASSOCIATION_RETAIN); @@ -854,7 +896,8 @@ static id ValueForItem(NSDictionary *item) - (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath { NSDictionary *item = [self itemForIndexPath:indexPath]; - if (item[@"type"] == typeSubmenu || item[@"type"] == typeOptionSubmenu) { + NSString *type = item[@"type"]; + if (type == typeSubmenu || type == typeOptionSubmenu) { UITableViewStyle style = UITableViewStyleGrouped; if (@available(iOS 13.0, *)) { style = UITableViewStyleInsetGrouped; @@ -878,7 +921,7 @@ static id ValueForItem(NSDictionary *item) } return indexPath; } - else if (item[@"type"] == typeRadio) { + else if (type == typeRadio) { if (item[@"setter"]) { ((void(^)(id))item[@"setter"])(item[@"value"]); } @@ -887,7 +930,7 @@ static id ValueForItem(NSDictionary *item) } [self.tableView reloadData]; } - else if (item[@"type"] == typeBlock) { + else if (type == typeBlock) { if (((bool(^)(GBSettingsViewController *))item[@"block"])(self)) { return indexPath; } @@ -898,11 +941,13 @@ static id ValueForItem(NSDictionary *item) - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { NSDictionary *item = [self itemForIndexPath:indexPath]; - if (item[@"type"] == typeSeparator) { + NSString *type = item[@"type"]; + if (type == typeSeparator) { return 8; } - if (item[@"type"] == typeSlider || - item[@"type"] == typeLightTemp) { + if (type == typeSlider || + type == typeLightTemp || + type == typeTurboSlider) { return 63; } return [super tableView:tableView heightForRowAtIndexPath:indexPath]; diff --git a/iOS/GBViewController.m b/iOS/GBViewController.m @@ -267,6 +267,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) GB_set_rewind_length(gb, [newValue unsignedIntValue]); } forKey:@"GBRewindLength"]; [self addDefaultObserver:^(id newValue) { + GB_set_turbo_cap(gb, [newValue doubleValue]); + } forKey:@"GBTurboCap"]; + [self addDefaultObserver:^(id newValue) { [[AVAudioSession sharedInstance] setCategory:[newValue isEqual:@"on"]? AVAudioSessionCategoryPlayback : AVAudioSessionCategorySoloAmbient mode:AVAudioSessionModeDefault routeSharingPolicy:AVAudioSessionRouteSharingPolicyDefault @@ -1803,9 +1806,7 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response if (_runMode == GBRunModeNormal || _runMode == GBRunModeUnderclock || !([[NSUserDefaults standardUserDefaults] boolForKey:@"GBDynamicSpeed"] && !ignoreDynamicSpeed)) { if (_runMode == GBRunModeTurbo) { - double multiplier = [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBTurboSpeed"]; - GB_set_turbo_mode(&_gb, multiplier == 1, false); - GB_set_clock_multiplier(&_gb, multiplier); + GB_set_turbo_mode(&_gb, true, false); } else if (_runMode == GBRunModeUnderclock) { GB_set_clock_multiplier(&_gb, 0.5); diff --git a/iOS/main.m b/iOS/main.m @@ -3,6 +3,13 @@ #import "GBViewController.h" #import "GBView.h" +static double MigrateTurboSpeed(void) +{ + unsigned old = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBTurboSpeed"]; + if (old == 1) return 0; + return old; +} + int main(int argc, char * argv[]) { @autoreleasepool { @@ -22,7 +29,7 @@ int main(int argc, char * argv[]) @"GBRumbleMode": @(GB_RUMBLE_CARTRIDGE_ONLY), @"GBButtonHaptics": @YES, @"GBHapticsStrength": @0.75, - @"GBTurboSpeed": @1, + @"GBTurboCap": @(MigrateTurboSpeed()), @"GBRewindSpeed": @1, @"GBDynamicSpeed": @NO, @"GBFauxAnalogInputs": @NO,
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.