mirror of
https://github.com/mauiaaron/apple2.git
synced 2024-06-26 00:29:27 +00:00
305 lines
9.6 KiB
Mathematica
305 lines
9.6 KiB
Mathematica
|
//
|
||
|
// EmulatorGLView.m
|
||
|
// Apple2Mac
|
||
|
//
|
||
|
// Created by Aaron Culliney on 9/27/14.
|
||
|
// Copyright (c) 2014 deadc0de.org. All rights reserved.
|
||
|
//
|
||
|
|
||
|
// Based on sample code from https://developer.apple.com/library/mac/samplecode/GLEssentials/Introduction/Intro.html
|
||
|
|
||
|
#import "EmulatorGLView.h"
|
||
|
|
||
|
// Apple //e common routines
|
||
|
#import "common.h"
|
||
|
#import "video/renderer.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
|
||
|
|
||
|
@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
|
||
|
#if ESSENTIAL_GL_PRACTICES_SUPPORT_GL3
|
||
|
NSOpenGLPFAOpenGLProfile,
|
||
|
NSOpenGLProfileVersion3_2Core,
|
||
|
#endif
|
||
|
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 ESSENTIAL_GL_PRACTICES_SUPPORT_GL3 && 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];
|
||
|
|
||
|
c_initialize_firsttime();
|
||
|
pthread_create(&cpu_thread_id, NULL, (void *)&cpu_thread, (void *)NULL);
|
||
|
|
||
|
// 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_driver_init(0);
|
||
|
#elif TARGET_OS_IPHONE
|
||
|
# error "FBO FIXME TODO"
|
||
|
video_driver_init(otherFBO);
|
||
|
#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
|
||
|
video_driver_reshape((int)viewRectPixels.size.width, (int)viewRectPixels.size.height);
|
||
|
|
||
|
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_driver_render();
|
||
|
CGLFlushDrawable([[self openGLContext] CGLContextObj]);
|
||
|
CGLUnlockContext([[self openGLContext] CGLContextObj]);
|
||
|
}
|
||
|
|
||
|
- (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
|
||
|
video_driver_shutdown();
|
||
|
|
||
|
[super dealloc];
|
||
|
}
|
||
|
@end
|