diff --git a/desmume/src/frontend/cocoa/openemu/NDSGameCore.mm b/desmume/src/frontend/cocoa/openemu/NDSGameCore.mm index 3f2dae6df..154112fdb 100644 --- a/desmume/src/frontend/cocoa/openemu/NDSGameCore.mm +++ b/desmume/src/frontend/cocoa/openemu/NDSGameCore.mm @@ -16,17 +16,18 @@ */ #import "NDSGameCore.h" -#import "cocoa_cheat.h" -#import "cocoa_globals.h" -#import "cocoa_file.h" -#import "cocoa_firmware.h" -#import "cocoa_GPU.h" -#import "cocoa_input.h" -#import "ClientDisplayView.h" -#import "ClientInputHandler.h" #import "OESoundInterface.h" #import "OENDSSystemResponderClient.h" +#import "../cocoa_cheat.h" +#import "../cocoa_globals.h" +#import "../cocoa_file.h" +#import "../cocoa_firmware.h" +#import "../cocoa_GPU.h" +#import "../cocoa_input.h" +#import "../ClientDisplayView.h" +#import "../ClientInputHandler.h" + #include #include "../../NDSSystem.h" #include "../../GPU.h" @@ -82,22 +83,22 @@ volatile bool execute = true; NDS_Init(); // Set up the DS GPU - cdsGPU = [[[[CocoaDSGPU alloc] init] retain] autorelease]; + cdsGPU = [[CocoaDSGPU alloc] init]; [cdsGPU setRender3DThreads:0]; // Pass 0 to automatically set the number of rendering threads [cdsGPU setRender3DRenderingEngine:CORE3DLIST_SWRASTERIZE]; [cdsGPU setGpuScale:1]; [cdsGPU setGpuColorFormat:NDSColorFormat_BGR666_Rev]; // Set up the DS controller - cdsController = [[[[CocoaDSController alloc] init] retain] autorelease]; + cdsController = [[CocoaDSController alloc] init]; // Set up the cheat system - cdsCheats = [[[[CocoaDSCheatManager alloc] init] retain] autorelease]; + cdsCheats = [[CocoaDSCheatManager alloc] init]; [cdsCheats setRwlockCoreExecute:&rwlockCoreExecute]; addedCheatsDict = [[NSMutableDictionary alloc] initWithCapacity:128]; // Set up the DS firmware using the internal firmware - cdsFirmware = [[[[CocoaDSFirmware alloc] init] retain] autorelease]; + cdsFirmware = [[CocoaDSFirmware alloc] init]; [cdsFirmware applySettings]; // Set up the sound core @@ -105,7 +106,7 @@ volatile bool execute = true; CommonSettings.spuInterpolationMode = SPUInterpolation_Cosine; CommonSettings.SPU_sync_mode = SPU_SYNC_MODE_SYNCHRONOUS; CommonSettings.SPU_sync_method = SPU_SYNC_METHOD_N; - openEmuSoundInterfaceBuffer = [self ringBufferAtIndex:0]; + openEmuSoundInterfaceBuffer = [self audioBufferAtIndex:0]; NSInteger result = SPU_ChangeSoundCore(SNDCORE_OPENEMU, (int)SPU_BUFFER_BYTES); if (result == -1) @@ -130,10 +131,10 @@ volatile bool execute = true; NDS_DeInit(); [addedCheatsDict release]; - [self setCdsCheats:nil]; - [self setCdsController:nil]; - [self setCdsGPU:nil]; - [self setCdsFirmware:nil]; + [cdsCheats release]; + [cdsController release]; + [cdsGPU release]; + [cdsFirmware release]; pthread_rwlock_destroy(&rwlockCoreExecute); apple_unfairlock_destroy(unfairlockDisplayMode); @@ -193,7 +194,13 @@ volatile bool execute = true; #pragma mark - Starting -- (BOOL)loadFileAtPath:(NSString*)path +/*! + * @method loadFileAtPath:error + * @discussion + * Try to load a ROM and return NO if it fails, or YES if it succeeds. + * You can do any setup you want here. + */ +- (BOOL)loadFileAtPath:(NSString *)path error:(NSError **)error { BOOL isRomLoaded = NO; NSString *openEmuDataPath = [self batterySavesDirectoryPath]; @@ -223,13 +230,56 @@ volatile bool execute = true; return isRomLoaded; } +#pragma mark - Stopping + +/*! + * @method stopEmulation + * @discussion + * Shut down the core. In non-debugging modes of core execution, + * the process will be exit immediately after, so you don't need to + * free any CPU or OpenGL resources. + * + * The OpenGL context is available in this method. + */ +//- (void)stopEmulation; +//- (void)stopEmulationWithCompletionHandler:(void(^)(void))completionHandler; + #pragma mark - Execution +/*! + * @property frameInterval + * @abstract The ideal *frequency* (in Hz) of -executeFrame calls when rate=1.0. + * This property is only read at the start and cannot be changed. + * @discussion Even though the property name and type indicate that + * a *period* in seconds should be returned (i.e. 1/60.0 ~= 0.01667 for 60 FPS execution), + * this method shall return a frequency in Hz instead (the inverse of that period). + * This naming mistake must be mantained for backwards compatibility. + */ - (NSTimeInterval)frameInterval { return DS_FRAMES_PER_SECOND; } +/*! + * @property rate + * @discussion + * The rate the game is currently running at. Generally 1.0. + * If 0, the core is paused. + * If >1.0, the core is fast-forwarding and -executeFrame will be called more often. + * Values <1.0 are not expected. + * + * There is no need to check this property if your core does all work inside -executeFrame. + */ +//@property (nonatomic, assign) float rate; + +/*! + * @method executeFrame + * @discussion + * Called every 1/(rate*frameInterval) seconds by -runGameLoop:. + * The core should produce 1 frameInterval worth of audio and can output 1 frame of video. + * If the game core option OEGameCoreOptionCanSkipFrames is set, the property shouldSkipFrame may be YES. + * In this case the core can read from videoBuffer but must not write to it. All work done to render video can be skipped. + */ - (void)executeFrame { ClientInputHandler *inputHandler = [cdsController inputHandler]; @@ -243,6 +293,10 @@ volatile bool execute = true; SPU_Emulate_user(); } +/*! + * @method resetEmulation + * @abstract Presses the reset button on the console. + */ - (void)resetEmulation { pthread_rwlock_wrlock(&rwlockCoreExecute); @@ -251,43 +305,103 @@ volatile bool execute = true; execute = true; } -- (void)executeFrameSkippingFrame:(BOOL)skip -{ - if (skip) - { - NDS_SkipNextFrame(); - } - - [self executeFrame]; -} +/*! + * @method beginPausedExecution + * @abstract Run the thread without appearing to execute the game. + * @discussion OpenEmu may ask the core to save the game, etc. even though it is paused. + * Some cores need to run their -executeFrame to process the save message (e.g. Mupen). + * Call this method from inside the save method to handle this case without disturbing the UI. + */ +//- (void)beginPausedExecution; +//- (void)endPausedExecution; #pragma mark - Video +/*! + * @method getVideoBufferWithHint: + * @param hint If possible, use 'hint' as the video buffer for this frame. + * @discussion + * Called before each -executeFrame call. The method should return + * a video buffer containing 'bufferSize' packed pixels, and -executeFrame + * should draw into this buffer. If 'hint' is set, using that as the video + * buffer may be faster. Besides that, returning the same buffer each time + * may be faster. + */ - (const void *)getVideoBufferWithHint:(void *)hint { return GPU->GetDisplayInfo().masterCustomBuffer; } +/*! + * @method tryToResizeVideoTo: + * @discussion + * If the core can natively draw at any resolution, change the resolution + * to 'size' and return YES. Otherwise, return NO. If YES, the next call to + * -executeFrame will have a newly sized framebuffer. + * It is assumed that only 3D cores can do this. + */ +//- (BOOL)tryToResizeVideoTo:(OEIntSize)size; + +/*! + * @property gameCoreRendering + * @discussion + * What kind of 3D API the core requires, or none. + * Defaults to 2D. + */ - (OEGameCoreRendering)gameCoreRendering { return OEGameCoreRendering2DVideo; } +/*! + * @property hasAlternateRenderingThread + * @abstract If the core starts another thread to do 3D operations on. + * @discussion + * 3D - + * OE will provide one extra GL context for this thread to avoid corruption + * of the main context. More than one rendering thread is not supported. + */ - (BOOL)hasAlternateRenderingThread { return NO; } +/*! + * @property needsDoubleBufferedFBO + * @abstract If the game flickers when rendering directly to IOSurface. + * @discussion + * 3D - + * Some cores' OpenGL renderers accidentally cause the IOSurface to update early, + * either by calling glFlush() or through GL driver bugs. This implements a workaround. + * Used by Mupen64Plus. + */ - (BOOL)needsDoubleBufferedFBO { return NO; } +/*! + * @property bufferSize + * @discussion + * 2D - + * The size in pixels to allocate the framebuffer at. + * Cores should output at their largest native size, including overdraw, without aspect ratio correction. + * 3D - + * The initial size to allocate the framebuffer at. + * The user may decide to resize it later, but OE will try to request new sizes at the same aspect ratio as bufferSize. + */ - (OEIntSize)bufferSize { return OEIntSizeMake(GPU_FRAMEBUFFER_NATIVE_WIDTH, GPU_FRAMEBUFFER_NATIVE_HEIGHT * 2); } +/*! + * @property screenRect + * @discussion + * The rect inside the framebuffer showing the currently displayed picture, + * not including overdraw, but without aspect ratio correction. + * Aspect ratio correction is not used for 3D. + */ - (OEIntRect)screenRect { apple_unfairlock_lock(unfairlockDisplayMode); @@ -297,6 +411,13 @@ volatile bool execute = true; return theRect; } +/*! + * @property aspectSize + * @discussion + * The size at the display aspect ratio (DAR) of the picture. + * The actual pixel values are not used; only the ratio is used. + * Aspect ratio correction is not used for 3D. + */ - (OEIntSize)aspectSize { apple_unfairlock_lock(unfairlockDisplayMode); @@ -306,23 +427,79 @@ volatile bool execute = true; return theAspectRatio; } -- (GLenum)pixelType -{ - return GL_UNSIGNED_INT_8_8_8_8_REV; -} - -- (GLenum)pixelFormat -{ - return GL_RGBA; -} - +/*! + * @property internalPixelFormat + * @discussion + * The 'internalFormat' parameter to glTexImage2D, used to create the framebuffer. + * Defaults to GL_RGB (sometimes GL_SRGB8). You probably do not need to override this. + * Ignored for 3D cores. + */ - (GLenum)internalPixelFormat { return GL_RGBA; } +/*! + * @property pixelFormat + * @discussion + * The 'type' parameter to glTexImage2D, used to create the framebuffer. + * GL_BGRA is preferred, but avoid doing any conversions inside the core. + * Ignored for 3D cores. + */ +- (GLenum)pixelType +{ + return GL_UNSIGNED_INT_8_8_8_8_REV; +} + +/*! + * @property pixelFormat + * @discussion + * The 'format' parameter to glTexImage2D, used to create the framebuffer. + * GL_UNSIGNED_SHORT_1_5_5_5_REV or GL_UNSIGNED_INT_8_8_8_8_REV are preferred, but + * avoid doing any conversions inside the core. + * Ignored for 3D cores. + */ +- (GLenum)pixelFormat +{ + return GL_RGBA; +} + +/*! + * @property bytesPerRow + * @discussion + * If the core outputs pixels with custom padding, and that padding cannot be expressed + * as overscan with bufferSize, you can implement this to return the distance between + * the first two rows in bytes. + * Ignored for 3D cores. + */ +//@property(readonly) NSInteger bytesPerRow; + +/*! + * @property shouldSkipFrame + * @abstract See -executeFrame. + */ +//@property(assign) BOOL shouldSkipFrame; + #pragma mark - Audio +//- (void)getAudioBuffer:(void *)buffer frameCount:(NSUInteger)frameCount bufferIndex:(NSUInteger)index; + +/** + * Returns the OEAudioBuffer associated to the specified audio track. + * @discussion A concrete game core can override this method to customize + * its audio buffering system. OpenEmu never calls the -write:maxLength: method + * of a buffer returned by this method. + * @param index The audio track index. + * @returns The audio buffer from which to read audio samples. + */ +//- (id)audioBufferAtIndex:(NSUInteger)index; + +/*! + * @property audioBufferCount + * @discussion + * Defaults to 1. Return a value other than 1 if the core can export + * multiple audio tracks. There is currently not much need for this. + */ - (NSUInteger)audioBufferCount { return 1; @@ -348,16 +525,24 @@ volatile bool execute = true; return [self channelCount]; } -- (NSUInteger)audioBufferSizeForBuffer:(NSUInteger)buffer -{ - return (NSUInteger)SPU_BUFFER_BYTES; -} - - (double)audioSampleRateForBuffer:(NSUInteger)buffer { return [self audioSampleRate]; } +/*! + * @method audioBufferSizeForBuffer: + * Returns the number of audio frames that are enquequed by the game core into + * the ring buffer every video frame. + * @param buffer The index of the buffer. + * @note If this method is not overridden by a concrete game core, it + * returns a very conservative frame count. + */ +- (NSUInteger)audioBufferSizeForBuffer:(NSUInteger)buffer +{ + return (NSUInteger)SPU_BUFFER_BYTES; +} + #pragma mark - Save States - (void)saveStateToFileAtPath:(NSString *)fileName completionHandler:(void(^)(BOOL success, NSError *error))block @@ -371,7 +556,7 @@ volatile bool execute = true; } - (void)loadStateFromFileAtPath:(NSString *)fileName completionHandler:(void(^)(BOOL success, NSError *error))block -{ +{ BOOL fileSuccess = [CocoaDSFile loadState:[NSURL fileURLWithPath:fileName]]; if (block != nil) @@ -387,6 +572,13 @@ volatile bool execute = true; return 0; } +//- (void)setRandomByte; + +#pragma mark - Save state - Optional + +//- (NSData *)serializeStateWithError:(NSError **)outError; +//- (BOOL)deserializeState:(NSData *)state withError:(NSError **)outError; + #pragma mark - Cheats - Optional - (void)setCheat:(NSString *)code setType:(NSString *)type setEnabled:(BOOL)enabled @@ -426,8 +618,70 @@ volatile bool execute = true; } } +#pragma mark - Display Mode - Optional + +/** An array describing the available display mode options and the + * appearance of the menu used to select them. + * @discussion Each NSDictionary in the array corresponds to an item + * in the Display Modes menu. + * Each item can represent one of these things, depending on the keys + * contained in the dictionary: + * - A label + * - A separator + * - A binary (toggleable) option + * - An option mutually exclusive with other options + * - A nested group of options (which appears as a submenu) + * See OEGameCoreController.h for a detailed discussion of the keys contained + * in each item dictionary. */ +//@property(readonly) NSArray *> *displayModes; + +/** Change display mode. + * @param displayMode The name of the display mode to enable or disable, as + * specified in its OEGameCoreDisplayModeNameKey key. */ +//- (void)changeDisplayWithMode:(NSString *)displayMode; + +- (void)changeDisplayMode +{ + switch (displayMode) + { + case ClientDisplayMode_Main: + [self setDisplayMode:ClientDisplayMode_Touch]; + break; + + case ClientDisplayMode_Touch: + [self setDisplayMode:ClientDisplayMode_Dual]; + break; + + case ClientDisplayMode_Dual: + [self setDisplayMode:ClientDisplayMode_Main]; + break; + + default: + return; + break; + } +} + #pragma mark - Internal +// There should be no need to override these methods. +/*! + * @method runGameLoop: + * @discussion + * Cores may implement this if they wish to control their entire event loop. + * This is not recommended. + */ +//- (void)runGameLoop:(id)anArgument; + +/*! + * @method startEmulation + * @discussion + * A method called on OEGameCore after -setupEmulation and + * before -executeFrame. You may implement it for organizational + * purposes but it is not necessary. + * + * The OpenGL context is available in this method. + */ - (void)startEmulation { [cdsController startHardwareMicDevice]; @@ -437,6 +691,31 @@ volatile bool execute = true; [super startEmulation]; } +/*! + * @method setupEmulation + * @discussion + * Try to setup emulation as much as possible before the UI appears. + * Audio/video properties don't need to be valid before this method, but + * do need to be valid after. + * + * It's not necessary to implement this, all setup can be done in loadFileAtPath + * or in the first executeFrame. But you're more likely to run into OE bugs that way. + */ +//- (void)setupEmulation; + +//- (void)didStopEmulation; +//- (void)runStartUpFrameWithCompletionHandler:(void(^)(void))handler; + +//- (void)stopEmulationWithCompletionHandler:(void(^)(void))completionHandler; + +/*! + * @property pauseEmulation + * @discussion Pauses the emulator "nicely". + * When set to YES, pauses emulation. When set to NO, + * resets the rate to whatever it previously was. + * The FPS limiter will stop, causing your rendering thread to pause. + * You should probably not override this. + */ - (void)setPauseEmulation:(BOOL)pauseEmulation { [cdsController setHardwareMicPause:pauseEmulation]; @@ -444,6 +723,9 @@ volatile bool execute = true; [super setPauseEmulation:pauseEmulation]; } +/// When didExecute is called, will be the next wakeup time. +//@property (nonatomic, readonly) NSTimeInterval nextFrameTime; + #pragma mark - Input - (oneway void)didPushNDSButton:(OENDSButton)button forPlayer:(NSUInteger)player @@ -536,26 +818,4 @@ volatile bool execute = true; [cdsController setTouchState:NO location:touchLocation]; } -- (void)changeDisplayMode -{ - switch (displayMode) - { - case ClientDisplayMode_Main: - [self setDisplayMode:ClientDisplayMode_Touch]; - break; - - case ClientDisplayMode_Touch: - [self setDisplayMode:ClientDisplayMode_Dual]; - break; - - case ClientDisplayMode_Dual: - [self setDisplayMode:ClientDisplayMode_Main]; - break; - - default: - return; - break; - } -} - @end diff --git a/desmume/src/frontend/cocoa/openemu/OESoundInterface.mm b/desmume/src/frontend/cocoa/openemu/OESoundInterface.mm index aac425e04..550b02dd6 100644 --- a/desmume/src/frontend/cocoa/openemu/OESoundInterface.mm +++ b/desmume/src/frontend/cocoa/openemu/OESoundInterface.mm @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2017 DeSmuME team + Copyright (C) 2012-2022 DeSmuME team This file is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ #import "OESoundInterface.h" -#import "cocoa_globals.h" +#import "../cocoa_globals.h" #include @@ -70,11 +70,7 @@ void SNDOpenEmuUpdateAudio(s16 *buffer, u32 num_samples) u32 SNDOpenEmuGetAudioSpace() { - // TODO: Use the newer-named method, [OERingBuffer freeBytes], when a newer version of OpenEmu.app released. - // But for now, use the older-named method, [OERingBuffer usedBytes]. - - //return (u32)[openEmuSoundInterfaceBuffer freeBytes] / SPU_SAMPLE_SIZE; - return (u32)[openEmuSoundInterfaceBuffer usedBytes] / SPU_SAMPLE_SIZE; + return (u32)[openEmuSoundInterfaceBuffer freeBytes] / SPU_SAMPLE_SIZE; } void SNDOpenEmuMuteAudio()