2015-10-22 05:13:26 +00:00
/ *
* Apple // emulator for * ix
*
* This software package is subject to the GNU General Public License
* version 3 or later ( your choice ) as published by the Free Software
* Foundation .
*
* Copyright 2014 , 2015 Aaron Culliney
*
* /
2014-09-27 18:03:51 +00:00
// Based on sample code from https : // developer . apple . com / library / mac / samplecode / GLEssentials / Introduction / Intro . html
# import "EmulatorGLView.h"
2016-04-23 21:37:26 +00:00
# import "EmulatorJoystickController.h"
2014-09-27 18:03:51 +00:00
// Apple // e common routines
# import "common.h"
# if TARGET_OS _MAC
# define USE_DISPLAYLINK 0
# define BROKEN_DISPLAYLINK 1
# else
// iOS uses CADisplayLink
# define USE_DISPLAYLINK 1
# endif
# define SUPPORT_RETINA _RESOLUTION 1
2014-11-30 23:35:21 +00:00
const NSString * kDrawTimerNotification = @ "kDrawTimerNotification" ;
2014-09-27 18:03:51 +00:00
@ interface EmulatorGLView ( )
# if USE_DISPLAYLINK
@ property ( nonatomic , assign ) CVDisplayLinkRef displayLink ;
# else
@ property ( nonatomic , retain ) NSTimer * timer ;
# endif
2018-11-18 22:27:17 +00:00
@ property ( nonatomic ) BOOL prepared ;
2014-09-27 18:03:51 +00:00
- ( void ) initGL ;
@ end
@ implementation EmulatorGLView
# if USE_DISPLAYLINK
@ synthesize displayLink = _displayLink ;
# else
@ synthesize timer = _timer ;
# endif
# pragma mark CVDisplayLink / NSTimer stuff
# if USE_DISPLAYLINK
- ( CVReturn ) getFrameForTime : ( const CVTimeStamp * ) outputTime
{
2015-03-06 05:28:55 +00:00
// There is no autorelease pool when this method is called
// because it will be called from a background thread .
// It ' s important to create one or app can leak objects .
2014-09-27 18:03:51 +00:00
@ autoreleasepool {
// We draw on a secondary thread through the display link
// When resizing the view , - reshape is called automatically on the main
// thread . Add a mutex around to avoid the threads accessing the context
// simultaneously when resizing
# if BROKEN_DISPLAYLINK
# warning ASC NOTE 2014 / 09 / 27 multi - threaded OpenGL on Mac considered harmful to developer sanity
// 2014 / 09 / 27 Kinda defeats the purpose of using CVDisplayLink . . . but keeps it from crashing to dispatch to main queue
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
# endif
[ [ self openGLContext ] makeCurrentContext ] ;
[ self drawView ] ;
# if BROKEN_DISPLAYLINK
} ) ;
# endif
}
2015-03-06 05:28:55 +00:00
return kCVReturnSuccess ;
2014-09-27 18:03:51 +00:00
}
// This is the renderer output callback function
static CVReturn displayLinkCallback ( CVDisplayLinkRef displayLink , const CVTimeStamp * now , const CVTimeStamp * outputTime , CVOptionFlags flagsIn , CVOptionFlags * flagsOut , void * displayLinkContext )
{
CVReturn result = [ ( EmulatorGLView * ) displayLinkContext getFrameForTime : outputTime ] ;
return result ;
}
# else // use NSTimer
- ( void ) targetMethod : ( NSTimer * ) theTimer
{
NSAssert ( [ NSThread isMainThread ] , @ "Timer fired on non-main thread!" ) ;
[ [ self openGLContext ] makeCurrentContext ] ;
[ self drawView ] ;
}
# endif
# pragma mark -
- ( void ) awakeFromNib
{
NSOpenGLPixelFormatAttribute attrs [ ] =
2015-03-06 05:28:55 +00:00
{
NSOpenGLPFADoubleBuffer ,
NSOpenGLPFADepthSize , 24 ,
// Must specify the 3.2 Core Profile to use OpenGL 3.2
NSOpenGLPFAOpenGLProfile ,
NSOpenGLProfileVersion3_2Core ,
0
} ;
NSOpenGLPixelFormat * pf = [ [ [ NSOpenGLPixelFormat alloc ] initWithAttributes : attrs ] autorelease ] ;
if ( ! pf )
{
NSLog ( @ "No OpenGL pixel format" ) ;
}
2014-09-27 18:03:51 +00:00
NSOpenGLContext * context = [ [ [ NSOpenGLContext alloc ] initWithFormat : pf shareContext : nil ] autorelease ] ;
2014-10-08 04:59:21 +00:00
# if defined ( DEBUG )
2015-03-06 05:28:55 +00:00
// When we ' re using a CoreProfile context , crash if we call a legacy OpenGL function
// This will make it much more obvious where and when such a function call is made so
// that we can remove such calls .
// Without this we ' d simply get GL_INVALID _OPERATION error for calling legacy functions
// but it would be more difficult to see where that function was called .
CGLEnable ( [ context CGLContextObj ] , kCGLCECrashOnRemovedFunctions ) ;
2014-09-27 18:03:51 +00:00
# endif
2015-03-06 05:28:55 +00:00
2014-09-27 18:03:51 +00:00
[ self setPixelFormat : pf ] ;
[ self setOpenGLContext : context ] ;
# if SUPPORT_RETINA _RESOLUTION
// Opt - In to Retina resolution
[ self setWantsBestResolutionOpenGLSurface : YES ] ;
# endif // SUPPORT_RETINA _RESOLUTION
}
- ( void ) prepareOpenGL
{
2015-03-06 05:28:55 +00:00
[ super prepareOpenGL ] ;
// Make all the OpenGL calls to setup rendering
// and build the necessary rendering objects
[ self initGL ] ;
2014-09-27 18:03:51 +00:00
# if USE_DISPLAYLINK
2015-03-06 05:28:55 +00:00
// Create a display link capable of being used with all active displays
CVDisplayLinkCreateWithActiveCGDisplays ( & _displayLink ) ;
// Set the renderer output callback function
CVDisplayLinkSetOutputCallback ( _displayLink , & displayLinkCallback , self ) ;
// Set the display link for the current renderer
CGLContextObj cglContext = [ [ self openGLContext ] CGLContextObj ] ;
CGLPixelFormatObj cglPixelFormat = [ [ self pixelFormat ] CGLPixelFormatObj ] ;
CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext ( _displayLink , cglContext , cglPixelFormat ) ;
// Activate the display link
CVDisplayLinkStart ( _displayLink ) ;
2014-09-27 18:03:51 +00:00
# else
[ self . timer invalidate ] ;
self . timer = nil ;
self . timer = [ NSTimer scheduledTimerWithTimeInterval : 1.0 / 60.0 target : self selector : @ selector ( targetMethod : ) userInfo : nil repeats : YES ] ;
# endif
2015-03-06 05:28:55 +00:00
// Register to be notified when the window closes so we can stop the displaylink
[ [ NSNotificationCenter defaultCenter ] addObserver : self selector : @ selector ( windowWillClose : ) name : NSWindowWillCloseNotification object : [ self window ] ] ;
2018-11-18 22:27:17 +00:00
self . prepared = true ;
2014-09-27 18:03:51 +00:00
}
- ( void ) windowWillClose : ( NSNotification * ) notification
{
2015-03-06 05:28:55 +00:00
// Stop the display link when the window is closing because default
// OpenGL render buffers will be destroyed . If display link continues to
// fire without renderbuffers , OpenGL draw calls will set errors .
2014-09-27 18:03:51 +00:00
# if USE_DISPLAYLINK
2015-03-06 05:28:55 +00:00
CVDisplayLinkStop ( _displayLink ) ;
2014-09-27 18:03:51 +00:00
# else
[ self . timer invalidate ] ;
self . timer = nil ;
# endif
}
- ( void ) initGL
{
2015-03-06 05:28:55 +00:00
// The reshape function may have changed the thread to which our OpenGL
// context is attached before prepareOpenGL and initGL are called . So call
// makeCurrentContext to ensure that our OpenGL context current to this
// thread ( i . e . makeCurrentContext directs all OpenGL calls on this thread
// to [ self openGLContext ] )
[ [ self openGLContext ] makeCurrentContext ] ;
2014-09-27 18:03:51 +00:00
2016-04-23 21:37:26 +00:00
[ EmulatorJoystickController sharedInstance ] ;
2016-06-26 15:41:20 +00:00
# if TESTING
char * local_argv [ ] = {
"-f" ,
NULL
} ;
int local_argc = 0 ;
for ( char * * p = & local_argv [ 0 ] ; * p ! = NULL ; p + + ) {
+ + local_argc ;
}
# if TEST_CPU
// Currently this test is the only one that blocks current thread and runs as a black screen
extern int test_cpu ( int , char * [ ] ) ;
test_cpu ( local_argc , local_argv ) ;
# elif TEST_DISK
extern int test_disk ( int , char * [ ] ) ;
test_disk ( local_argc , local_argv ) ;
2016-07-24 00:23:36 +00:00
# elif TEST_DISPLAY
extern int test_display ( int , char * [ ] ) ;
test_display ( local_argc , local_argv ) ;
2016-06-26 15:41:20 +00:00
# elif TEST_PREFS
extern void test_prefs ( int , char * [ ] ) ;
test_prefs ( local_argc , local_argv ) ;
# elif TEST_TRACE
extern void test_trace ( int , char * [ ] ) ;
test_trace ( local_argc , local_argv ) ;
2016-09-11 18:53:59 +00:00
# elif TEST_UI
extern int test_ui ( int , char * [ ] ) ;
test_ui ( local_argc , local_argv ) ;
2016-07-24 00:23:36 +00:00
# elif TEST_VM
extern int test_vm ( int , char * [ ] ) ;
test_vm ( local_argc , local_argv ) ;
2016-06-26 15:41:20 +00:00
# else
# error "OOPS, no testsuite specified"
# endif
# endif
2018-01-20 16:14:08 +00:00
cpu_pause ( ) ;
emulator_start ( ) ;
cpu_resume ( ) ;
2018-01-21 01:36:43 +00:00
// Synchronize buffer swaps with vertical refresh rate
GLint swapInt = 1 ;
[ [ self openGLContext ] setValues : & swapInt forParameter : NSOpenGLCPSwapInterval ] ;
// Init our renderer . Use 0 for the defaultFBO which is appropriate for
// OSX ( but not iOS since iOS apps must create their own FBO )
# if TARGET_OS _MAC
video_init ( ) ;
# elif TARGET_OS _IPHONE
# error this is OSX specific
# else
# error " unknown / unsupported Apple platform
# endif
2014-09-27 18:03:51 +00:00
}
- ( void ) reshape
2015-03-06 05:28:55 +00:00
{
[ super reshape ] ;
// We draw on a secondary thread through the display link . However , when
// resizing the view , - drawRect is called on the main thread .
// Add a mutex around to avoid the threads accessing the context
// simultaneously when resizing .
CGLLockContext ( [ [ self openGLContext ] CGLContextObj ] ) ;
// Get the view size in Points
NSRect viewRectPoints = [ self bounds ] ;
2014-09-27 18:03:51 +00:00
# if SUPPORT_RETINA _RESOLUTION
// Rendering at retina resolutions will reduce aliasing , but at the potential
// cost of framerate and battery life due to the GPU needing to render more
// pixels .
// Any calculations the renderer does which use pixel dimentions , must be
// in "retina" space . [ NSView convertRectToBacking ] converts point sizes
// to pixel sizes . Thus the renderer gets the size in pixels , not points ,
// so that it can set it ' s viewport and perform and other pixel based
// calculations appropriately .
// viewRectPixels will be larger ( 2 x ) than viewRectPoints for retina displays .
// viewRectPixels will be the same as viewRectPoints for non - retina displays
NSRect viewRectPixels = [ self convertRectToBacking : viewRectPoints ] ;
# else // if ! SUPPORT_RETINA _RESOLUTION
// App will typically render faster and use less power rendering at
// non - retina resolutions since the GPU needs to render less pixels . There
// is the cost of more aliasing , but it will be no - worse than on a Mac
// without a retina display .
// Points : Pixels is always 1 : 1 when not supporting retina resolutions
NSRect viewRectPixels = viewRectPoints ;
# endif // ! SUPPORT_RETINA _RESOLUTION
2015-03-06 05:28:55 +00:00
// Set the new dimensions in our renderer
2016-04-23 21:37:26 +00:00
prefs_setLongValue ( PREF_DOMAIN _INTERFACE , PREF_DEVICE _WIDTH , ( int ) viewRectPixels . size . width ) ;
prefs_setLongValue ( PREF_DOMAIN _INTERFACE , PREF_DEVICE _HEIGHT , ( int ) viewRectPixels . size . height ) ;
prefs_setLongValue ( PREF_DOMAIN _INTERFACE , PREF_DEVICE _LANDSCAPE , true ) ;
prefs_sync ( PREF_DOMAIN _INTERFACE ) ;
2015-03-06 05:28:55 +00:00
CGLUnlockContext ( [ [ self openGLContext ] CGLContextObj ] ) ;
2014-09-27 18:03:51 +00:00
}
- ( void ) renewGState
2015-03-06 05:28:55 +00:00
{
// Called whenever graphics state updated ( such as window resize )
// OpenGL rendering is not synchronous with other rendering on the OSX .
// Therefore , call disableScreenUpdatesUntilFlush so the window server
// doesn ' t render non - OpenGL content in the window asynchronously from
// OpenGL content , which could cause flickering . ( non - OpenGL content
// includes the title bar and drawing done by the app with other APIs )
[ [ self window ] disableScreenUpdatesUntilFlush ] ;
[ super renewGState ] ;
2014-09-27 18:03:51 +00:00
}
- ( void ) drawRect : ( NSRect ) theRect
{
NSAssert ( [ NSThread isMainThread ] , @ "drawRect called on non-main thread!" ) ;
2015-03-06 05:28:55 +00:00
// Called during resize operations
// Avoid flickering during resize by drawing
2014-09-27 18:03:51 +00:00
[ [ self openGLContext ] makeCurrentContext ] ;
2018-11-18 22:27:17 +00:00
if ( UNLIKELY ( ! self . prepared ) ) {
NSAlert * alert = [ NSAlert alertWithError : [ NSError errorWithDomain : @ "NSOpenGLView is now deprecated and has a bug ... please restart this app. FIXME TODO : likely we're being forced to move to Metal because Ninjaz and Rockstarz said so :P" code : -1 userInfo : nil ] ] ;
[ alert beginSheetModalForWindow : [ self window ] completionHandler : nil ] ;
return ;
}
2015-03-06 05:28:55 +00:00
[ self drawView ] ;
2014-09-27 18:03:51 +00:00
}
- ( void ) drawView
{
CGLLockContext ( [ [ self openGLContext ] CGLContextObj ] ) ;
2016-02-07 05:23:40 +00:00
video_render ( ) ;
2015-03-06 05:28:55 +00:00
CGLFlushDrawable ( [ [ self openGLContext ] CGLContextObj ] ) ;
2014-09-27 18:03:51 +00:00
CGLUnlockContext ( [ [ self openGLContext ] CGLContextObj ] ) ;
2014-11-30 23:35:21 +00:00
[ [ NSNotificationCenter defaultCenter ] postNotificationName : ( NSString * ) kDrawTimerNotification object : nil ] ;
2014-09-27 18:03:51 +00:00
}
- ( void ) dealloc
{
2015-03-06 05:28:55 +00:00
// Stop the display link BEFORE releasing anything in the view
2014-09-27 18:03:51 +00:00
// otherwise the display link thread may call into the view and crash
// when it encounters something that has been release
# if USE_DISPLAYLINK
2015-03-06 05:28:55 +00:00
CVDisplayLinkStop ( _displayLink ) ;
CVDisplayLinkRelease ( _displayLink ) ;
2014-09-27 18:03:51 +00:00
# else
[ self . timer invalidate ] ;
self . timer = nil ;
# endif
2015-03-06 05:28:55 +00:00
// shut down common OpenGL stuff AFTER display link has been released
2015-09-11 07:00:04 +00:00
emulator_shutdown ( ) ;
2015-03-06 05:28:55 +00:00
[ super dealloc ] ;
2014-09-27 18:03:51 +00:00
}
2014-10-18 19:50:46 +00:00
# pragma mark -
# pragma mark Application Delegate methods
- ( BOOL ) applicationShouldTerminateAfterLastWindowClosed : ( NSApplication * ) application
{
return YES ;
}
- ( NSApplicationTerminateReply ) applicationShouldTerminate : ( NSApplication * ) application
{
2017-09-22 00:30:09 +00:00
cpu_pause ( ) ;
2018-11-22 18:14:46 +00:00
prefs_save ( ) ;
2015-11-14 07:13:21 +00:00
disk6_eject ( 0 ) ;
disk6_eject ( 1 ) ;
2014-10-18 19:50:46 +00:00
return NSTerminateNow ;
}
2014-09-27 18:03:51 +00:00
@ end