diff --git a/iOS/GBViewController.m b/iOS/GBViewController.m index 4933812..ef1bb6e 100644 --- a/iOS/GBViewController.m +++ b/iOS/GBViewController.m @@ -2,8 +2,32 @@ #import "GBHorizontalLayout.h" #import "GBVerticalLayout.h" #import "GBViewMetal.h" +#import "GBAudioClient.h" #include +@implementation GBViewController +{ + GB_gameboy_t _gb; + GBView *_gbView; + volatile bool _running; + volatile bool _stopping; + GBLayout *_currentLayout; + GBHorizontalLayout *_horizontalLayout; + GBVerticalLayout *_verticalLayout; + UIImageView *_backgroundView; + UIImageView *_dpadView; + UIImageView *_aButtonView; + UIImageView *_bButtonView; + UIImageView *_startButtonView; + UIImageView *_selectButtonView; + NSCondition *_audioLock; + GB_sample_t *_audioBuffer; + size_t _audioBufferSize; + size_t _audioBufferPosition; + size_t _audioBufferNeeded; + GBAudioClient *_audioClient; +} + static void positionView(UIImageView *view, CGPoint position) { double center = view.image.size.width / 2 * [UIScreen mainScreen].scale; @@ -17,19 +41,63 @@ static void positionView(UIImageView *view, CGPoint position) } -@implementation GBViewController +static void loadBootROM(GB_gameboy_t *gb, GB_boot_rom_t type) { - GBLayout *_currentLayout; - GBHorizontalLayout *_horizontalLayout; - GBVerticalLayout *_verticalLayout; - UIImageView *_backgroundView; - UIImageView *_dpadView; - UIImageView *_aButtonView; - UIImageView *_bButtonView; - UIImageView *_startButtonView; - UIImageView *_selectButtonView; - GBView *_gbView; - GB_gameboy_t _gb; + GBViewController *self = (__bridge GBViewController *)GB_get_user_data(gb); + [self loadBootROM:type]; +} + +static void vblank(GB_gameboy_t *gb, GB_vblank_type_t type) +{ + GBViewController *self = (__bridge GBViewController *)GB_get_user_data(gb); + [self vblankWithType:type]; +} + +static void consoleLog(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) +{ + static NSString *buffer = @""; + buffer = [buffer stringByAppendingString:@(string)]; + if ([buffer containsString:@"\n"]) { + NSLog(@"%@", buffer); + buffer = @""; + } +} + +static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) +{ + return (r << 0) | (g << 8) | (b << 16) | 0xFF000000; +} + +static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) +{ + GBViewController *self = (__bridge GBViewController *)GB_get_user_data(gb); + [self gotNewSample:sample]; +} + +static void rumbleCallback(GB_gameboy_t *gb, double amp) +{ + GBViewController *self = (__bridge GBViewController *)GB_get_user_data(gb); + [self rumbleChanged:amp]; +} + +- (void)initGameBoy +{ + GB_init(&_gb, GB_MODEL_CGB_E); + GB_set_user_data(&_gb, (__bridge void *)(self)); + GB_set_boot_rom_load_callback(&_gb, (GB_boot_rom_load_callback_t)loadBootROM); + GB_set_vblank_callback(&_gb, (GB_vblank_callback_t) vblank); + GB_set_log_callback(&_gb, (GB_log_callback_t) consoleLog); + GB_set_color_correction_mode(&_gb, (GB_color_correction_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"]); + GB_set_light_temperature(&_gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBLightTemperature"]); + GB_set_interference_volume(&_gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBInterferenceVolume"]); + GB_set_border_mode(&_gb, GB_BORDER_NEVER); + [self updatePalette]; + GB_set_rgb_encode_callback(&_gb, rgbEncode); + GB_set_highpass_filter_mode(&_gb, (GB_highpass_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBHighpassFilter"]); + GB_set_rtc_mode(&_gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRTCMode"]); + GB_apu_set_sample_callback(&_gb, audioCallback); + GB_set_rumble_callback(&_gb, rumbleCallback); + [self updateRumbleMode]; } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions @@ -54,7 +122,7 @@ static void positionView(UIImageView *view, CGPoint position) _selectButtonView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"button2"]]; _gbView = [[GBViewMetal alloc] initWithFrame:CGRectZero]; - GB_init(&_gb, GB_MODEL_CGB_E); + [self initGameBoy]; _gbView.gb = &_gb; [_gbView screenSizeChanged]; @@ -68,9 +136,21 @@ static void positionView(UIImageView *view, CGPoint position) [_backgroundView addSubview:_selectButtonView]; [_backgroundView addSubview:_gbView]; + _audioLock = [[NSCondition alloc] init]; + return true; } +- (void)applicationDidBecomeActive:(UIApplication *)application +{ + [self start]; +} + +- (void)applicationWillResignActive:(UIApplication *)application +{ + [self stop]; +} + - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration { if (orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown) { @@ -96,7 +176,6 @@ static void positionView(UIImageView *view, CGPoint position) screenFrame.size.height /= [UIScreen mainScreen].scale; _gbView.frame = screenFrame; - memset(_gbView.pixels, rand(), 160 * 144 * 4); [_gbView flip]; } @@ -109,4 +188,176 @@ static void positionView(UIImageView *view, CGPoint position) return true; } +- (void)preRun +{ + GB_set_pixels_output(&_gb, _gbView.pixels); + GB_set_sample_rate(&_gb, 96000); + _audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) { + [_audioLock lock]; + + if (_audioBufferPosition < nFrames) { + _audioBufferNeeded = nFrames; + [_audioLock waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.125]]; + } + + if (_stopping) { + memset(buffer, 0, nFrames * sizeof(*buffer)); + [_audioLock unlock]; + return; + } + + if (_audioBufferPosition < nFrames) { + // Not enough audio + memset(buffer, 0, (nFrames - _audioBufferPosition) * sizeof(*buffer)); + memcpy(buffer, _audioBuffer, _audioBufferPosition * sizeof(*buffer)); + _audioBufferPosition = 0; + } + else if (_audioBufferPosition < nFrames + 4800) { + memcpy(buffer, _audioBuffer, nFrames * sizeof(*buffer)); + memmove(_audioBuffer, _audioBuffer + nFrames, (_audioBufferPosition - nFrames) * sizeof(*buffer)); + _audioBufferPosition = _audioBufferPosition - nFrames; + } + else { + memcpy(buffer, _audioBuffer + (_audioBufferPosition - nFrames), nFrames * sizeof(*buffer)); + _audioBufferPosition = 0; + } + [_audioLock unlock]; + } andSampleRate:96000]; + + [_audioClient start]; +} + +- (void)run +{ + [self preRun]; + while (_running) { + GB_run(&_gb); + } + [self postRun]; + _stopping = false; +} + +- (void)postRun +{ + [_audioLock lock]; + memset(_audioBuffer, 0, (_audioBufferSize - _audioBufferPosition) * sizeof(*_audioBuffer)); + _audioBufferPosition = _audioBufferNeeded; + [_audioLock signal]; + [_audioLock unlock]; + [_audioClient stop]; + _audioClient = nil; + + // Todo + //GB_save_battery(&gb, self.savPath.UTF8String); +} + +- (void)start +{ + if (_running) return; + _running = true; + [[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start]; +} + +- (void)stop +{ + if (!_running) return; + [_audioLock lock]; + _stopping = true; + [_audioLock signal]; + [_audioLock unlock]; + _running = false; + while (_stopping) { + [_audioLock lock]; + [_audioLock signal]; + [_audioLock unlock]; + } +} +- (void)loadBootROM: (GB_boot_rom_t)type +{ + static NSString *const names[] = { + [GB_BOOT_ROM_DMG_0] = @"dmg0_boot", + [GB_BOOT_ROM_DMG] = @"dmg_boot", + [GB_BOOT_ROM_MGB] = @"mgb_boot", + [GB_BOOT_ROM_SGB] = @"sgb_boot", + [GB_BOOT_ROM_SGB2] = @"sgb2_boot", + [GB_BOOT_ROM_CGB_0] = @"cgb0_boot", + [GB_BOOT_ROM_CGB] = @"cgb_boot", + [GB_BOOT_ROM_AGB] = @"agb_boot", + }; + GB_load_boot_rom(&_gb, [[[NSBundle mainBundle] pathForResource:names[type] ofType:@"bin"] UTF8String]); +} + +- (void)vblankWithType:(GB_vblank_type_t)type +{ + if (type != GB_VBLANK_TYPE_REPEAT) { + [_gbView flip]; + GB_set_pixels_output(&_gb, _gbView.pixels); + } +} + +- (void)gotNewSample:(GB_sample_t *)sample +{ + [_audioLock lock]; + if (_audioClient.isPlaying) { + if (_audioBufferPosition == _audioBufferSize) { + if (_audioBufferSize >= 0x4000) { + _audioBufferPosition = 0; + [_audioLock unlock]; + return; + } + + if (_audioBufferSize == 0) { + _audioBufferSize = 512; + } + else { + _audioBufferSize += _audioBufferSize >> 2; + } + _audioBuffer = realloc(_audioBuffer, sizeof(*sample) * _audioBufferSize); + } + _audioBuffer[_audioBufferPosition++] = *sample; + } + if (_audioBufferPosition == _audioBufferNeeded) { + [_audioLock signal]; + _audioBufferNeeded = 0; + } + [_audioLock unlock]; +} + +- (void)rumbleChanged:(double)amp +{ + // TODO +} + +- (void)updateRumbleMode +{ + GB_set_rumble_mode(&_gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRumbleMode"]); +} + +- (const GB_palette_t *)userPalette +{ + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + switch ([defaults integerForKey:@"GBColorPalette"]) { + case 1: return &GB_PALETTE_DMG; + case 2: return &GB_PALETTE_MGB; + case 3: return &GB_PALETTE_GBL; + default: return &GB_PALETTE_GREY; + case -1: { + static GB_palette_t customPalette; + NSArray *colors = [defaults dictionaryForKey:@"GBThemes"][[defaults stringForKey:@"GBCurrentTheme"]][@"Colors"]; + if (colors.count == 5) { + unsigned i = 0; + for (NSNumber *color in colors) { + uint32_t c = [color unsignedIntValue]; + customPalette.colors[i++] = (struct GB_color_s) {c, c >> 8, c >> 16}; + } + } + return &customPalette; + } + } +} + +- (void)updatePalette +{ + GB_set_palette(&_gb, [self userPalette]); +} @end diff --git a/iOS/main.m b/iOS/main.m index 74c22f4..689f8e7 100644 --- a/iOS/main.m +++ b/iOS/main.m @@ -1,7 +1,85 @@ #import #import "GBViewController.h" +#include +#include "GBView.h" int main(int argc, char * argv[]) { + @autoreleasepool { + [[NSUserDefaults standardUserDefaults] registerDefaults:@{ + @"GBFilter": @"NearestNeighbor", + @"GBColorCorrection": @(GB_COLOR_CORRECTION_MODERN_BALANCED), + @"GBHighpassFilter": @(GB_HIGHPASS_REMOVE_DC_OFFSET), + @"GBFrameBlendingMode": @(GB_FRAME_BLENDING_MODE_ACCURATE), + + @"GBDMGModel": @(GB_MODEL_DMG_B), + @"GBCGBModel": @(GB_MODEL_CGB_E), + @"GBAGBModel": @(GB_MODEL_AGB_A), + @"GBSGBModel": @(GB_MODEL_SGB2), + @"GBRumbleMode": @(GB_RUMBLE_CARTRIDGE_ONLY), + + @"GBVolume": @(1.0), + + // Default themes + @"GBThemes": @{ + @"Desert": @{ + @"BrightnessBias": @0.0, + @"Colors": @[@0xff302f3e, @0xff576674, @0xff839ba4, @0xffb1d0d2, @0xffb7d7d8], + @"DisabledLCDColor": @YES, + @"HueBias": @0.10087773904382469, + @"HueBiasStrength": @0.062142056772908363, + @"Manual": @NO, + }, + @"Evening": @{ + @"BrightnessBias": @-0.10168700106441975, + @"Colors": @[@0xff362601, @0xff695518, @0xff899853, @0xffa6e4ae, @0xffa9eebb], + @"DisabledLCDColor": @YES, + @"HueBias": @0.60027079191058874, + @"HueBiasStrength": @0.33816297305747867, + @"Manual": @NO, + }, + @"Fog": @{ + @"BrightnessBias": @0.0, + @"Colors": @[@0xff373c34, @0xff737256, @0xff9da386, @0xffc3d2bf, @0xffc7d8c6], + @"DisabledLCDColor": @YES, + @"HueBias": @0.55750435756972117, + @"HueBiasStrength": @0.18424738545816732, + @"Manual": @NO, + }, + @"Magic Eggplant": @{ + @"BrightnessBias": @0.0, + @"Colors": @[@0xff3c2136, @0xff942e84, @0xffc7699d, @0xfff1e4b0, @0xfff6f9b2], + @"DisabledLCDColor": @YES, + @"HueBias": @0.87717878486055778, + @"HueBiasStrength": @0.65018052788844627, + @"Manual": @NO, + }, + @"Radioactive Pea": @{ + @"BrightnessBias": @-0.48079556772908372, + @"Colors": @[@0xff215200, @0xff1f7306, @0xff169e34, @0xff03ceb8, @0xff00d4d1], + @"DisabledLCDColor": @YES, + @"HueBias": @0.3795131972111554, + @"HueBiasStrength": @0.34337649402390436, + @"Manual": @NO, + }, + @"Seaweed": @{ + @"BrightnessBias": @-0.28532744023904377, + @"Colors": @[@0xff3f0015, @0xff426532, @0xff58a778, @0xff95e0df, @0xffa0e7ee], + @"DisabledLCDColor": @YES, + @"HueBias": @0.2694067480079681, + @"HueBiasStrength": @0.51565612549800799, + @"Manual": @NO, + }, + @"Twilight": @{ + @"BrightnessBias": @-0.091789093625498031, + @"Colors": @[@0xff3f0015, @0xff461286, @0xff6254bd, @0xff97d3e9, @0xffa0e7ee], + @"DisabledLCDColor": @YES, + @"HueBias": @0.0, + @"HueBiasStrength": @0.49710532868525897, + @"Manual": @NO, + }, + }, + }]; + } return UIApplicationMain(argc, argv, nil, NSStringFromClass([GBViewController class])); }