mirror of
https://github.com/mauiaaron/apple2.git
synced 2024-12-29 04:29:32 +00:00
367 lines
12 KiB
Objective-C
367 lines
12 KiB
Objective-C
/*
|
|
* 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
|
|
*
|
|
*/
|
|
|
|
// Based on sample code from https://developer.apple.com/library/mac/samplecode/GLEssentials/Introduction/Intro.html
|
|
|
|
#import "EmulatorGLView.h"
|
|
#import "EmulatorJoystickController.h"
|
|
|
|
// 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
|
|
|
|
const NSString *kDrawTimerNotification = @"kDrawTimerNotification";
|
|
|
|
@interface EmulatorGLView ()
|
|
|
|
#if USE_DISPLAYLINK
|
|
@property (nonatomic, assign) CVDisplayLinkRef displayLink;
|
|
#else
|
|
@property (nonatomic, retain) NSTimer *timer;
|
|
#endif
|
|
|
|
- (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
|
|
{
|
|
// 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.
|
|
@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
|
|
}
|
|
return kCVReturnSuccess;
|
|
}
|
|
|
|
// 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[] =
|
|
{
|
|
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");
|
|
}
|
|
|
|
NSOpenGLContext* context = [[[NSOpenGLContext alloc] initWithFormat:pf shareContext:nil] autorelease];
|
|
|
|
#if defined(DEBUG)
|
|
// 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);
|
|
#endif
|
|
|
|
[self setPixelFormat:pf];
|
|
[self setOpenGLContext:context];
|
|
|
|
#if SUPPORT_RETINA_RESOLUTION
|
|
// Opt-In to Retina resolution
|
|
[self setWantsBestResolutionOpenGLSurface:YES];
|
|
#endif // SUPPORT_RETINA_RESOLUTION
|
|
}
|
|
|
|
- (void)prepareOpenGL
|
|
{
|
|
[super prepareOpenGL];
|
|
|
|
// Make all the OpenGL calls to setup rendering
|
|
// and build the necessary rendering objects
|
|
[self initGL];
|
|
|
|
#if USE_DISPLAYLINK
|
|
// 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);
|
|
#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
|
|
|
|
// 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]];
|
|
}
|
|
|
|
- (void)windowWillClose:(NSNotification*)notification
|
|
{
|
|
// 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.
|
|
#if USE_DISPLAYLINK
|
|
CVDisplayLinkStop(_displayLink);
|
|
#else
|
|
[self.timer invalidate];
|
|
self.timer = nil;
|
|
#endif
|
|
}
|
|
|
|
- (void)initGL
|
|
{
|
|
// 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];
|
|
|
|
[EmulatorJoystickController sharedInstance];
|
|
|
|
#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);
|
|
# elif TEST_DISPLAY
|
|
extern int test_display(int, char *[]);
|
|
test_display(local_argc, local_argv);
|
|
# 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);
|
|
# elif TEST_UI
|
|
extern int test_ui(int, char *[]);
|
|
test_ui(local_argc, local_argv);
|
|
# elif TEST_VM
|
|
extern int test_vm(int, char *[]);
|
|
test_vm(local_argc, local_argv);
|
|
# else
|
|
# error "OOPS, no testsuite specified"
|
|
# endif
|
|
#endif
|
|
|
|
cpu_pause();
|
|
emulator_start();
|
|
cpu_resume();
|
|
|
|
// 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
|
|
}
|
|
|
|
- (void)reshape
|
|
{
|
|
[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];
|
|
|
|
#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 (2x) 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
|
|
|
|
// Set the new dimensions in our renderer
|
|
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);
|
|
|
|
CGLUnlockContext([[self openGLContext] CGLContextObj]);
|
|
}
|
|
|
|
- (void)renewGState
|
|
{
|
|
// 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];
|
|
}
|
|
|
|
- (void)drawRect:(NSRect)theRect
|
|
{
|
|
NSAssert([NSThread isMainThread], @"drawRect called on non-main thread!");
|
|
// Called during resize operations
|
|
// Avoid flickering during resize by drawing
|
|
[[self openGLContext] makeCurrentContext];
|
|
[self drawView];
|
|
}
|
|
|
|
- (void)drawView
|
|
{
|
|
CGLLockContext([[self openGLContext] CGLContextObj]);
|
|
video_render();
|
|
CGLFlushDrawable([[self openGLContext] CGLContextObj]);
|
|
CGLUnlockContext([[self openGLContext] CGLContextObj]);
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:(NSString *)kDrawTimerNotification object:nil];
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
// Stop the display link BEFORE releasing anything in the view
|
|
// otherwise the display link thread may call into the view and crash
|
|
// when it encounters something that has been release
|
|
#if USE_DISPLAYLINK
|
|
CVDisplayLinkStop(_displayLink);
|
|
CVDisplayLinkRelease(_displayLink);
|
|
#else
|
|
[self.timer invalidate];
|
|
self.timer = nil;
|
|
#endif
|
|
|
|
// shut down common OpenGL stuff AFTER display link has been released
|
|
emulator_shutdown();
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Application Delegate methods
|
|
|
|
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)application
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)application
|
|
{
|
|
cpu_pause();
|
|
disk6_eject(0);
|
|
disk6_eject(1);
|
|
return NSTerminateNow;
|
|
}
|
|
|
|
@end
|