diff --git a/src/platform/osx/OpenLara.xcodeproj/project.pbxproj b/src/platform/osx/OpenLara.xcodeproj/project.pbxproj index c9f67f2..7611e7c 100644 --- a/src/platform/osx/OpenLara.xcodeproj/project.pbxproj +++ b/src/platform/osx/OpenLara.xcodeproj/project.pbxproj @@ -7,18 +7,20 @@ objects = { /* Begin PBXBuildFile section */ - 99BFB6A21DCA7F5300E2E997 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 99BFB6A11DCA7F5300E2E997 /* main.cpp */; }; + 523F97E41DDF7AA5006FE2FC /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 523F97E31DDF7AA5006FE2FC /* Cocoa.framework */; }; + 523F97E61DDF926E006FE2FC /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 523F97E51DDF926E006FE2FC /* QuartzCore.framework */; }; + 99BFB6A21DCA7F5300E2E997 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 99BFB6A11DCA7F5300E2E997 /* main.mm */; }; 99BFB6A51DCA872D00E2E997 /* LEVEL2_DEMO.PHD in Resources */ = {isa = PBXBuildFile; fileRef = 99BFB6A31DCA872D00E2E997 /* LEVEL2_DEMO.PHD */; }; 99BFB6A61DCA872D00E2E997 /* 05.ogg in Resources */ = {isa = PBXBuildFile; fileRef = 99BFB6A41DCA872D00E2E997 /* 05.ogg */; }; 99BFB6A91DCA87BF00E2E997 /* stb_vorbis.c in Sources */ = {isa = PBXBuildFile; fileRef = 99BFB6A81DCA87BF00E2E997 /* stb_vorbis.c */; }; 99BFB6AB1DCA87C500E2E997 /* minimp3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 99BFB6AA1DCA87C500E2E997 /* minimp3.cpp */; }; - 99C4C0B71796AB730032DE85 /* AGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99C4C0951796A9730032DE85 /* AGL.framework */; }; - 99C4C0B91796AB7B0032DE85 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99C4C0B81796AB7B0032DE85 /* Carbon.framework */; }; 99C4C0BA1796AB810032DE85 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99C4C0931796A96F0032DE85 /* OpenGL.framework */; }; 99DF7BC41DCBA30B00C40D0A /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99DF7BC31DCBA30B00C40D0A /* AudioToolbox.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 523F97E31DDF7AA5006FE2FC /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; + 523F97E51DDF926E006FE2FC /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 99BFB68D1DCA7F1700E2E997 /* lara.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lara.h; path = ../../lara.h; sourceTree = ""; }; 99BFB68E1DCA7F1700E2E997 /* format.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = format.h; path = ../../format.h; sourceTree = ""; }; 99BFB68F1DCA7F1700E2E997 /* level.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = level.h; path = ../../level.h; sourceTree = ""; }; @@ -37,7 +39,7 @@ 99BFB69C1DCA7F1700E2E997 /* game.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = game.h; path = ../../game.h; sourceTree = ""; }; 99BFB69D1DCA7F1700E2E997 /* texture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = texture.h; path = ../../texture.h; sourceTree = ""; }; 99BFB69E1DCA7F1700E2E997 /* libs */ = {isa = PBXFileReference; lastKnownFileType = folder; name = libs; path = ../../libs; sourceTree = ""; }; - 99BFB6A11DCA7F5300E2E997 /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; }; + 99BFB6A11DCA7F5300E2E997 /* main.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = ""; }; 99BFB6A31DCA872D00E2E997 /* LEVEL2_DEMO.PHD */ = {isa = PBXFileReference; lastKnownFileType = file; name = LEVEL2_DEMO.PHD; path = ../../../bin/LEVEL2_DEMO.PHD; sourceTree = SOURCE_ROOT; }; 99BFB6A41DCA872D00E2E997 /* 05.ogg */ = {isa = PBXFileReference; lastKnownFileType = file; name = 05.ogg; path = ../../../bin/05.ogg; sourceTree = SOURCE_ROOT; }; 99BFB6A81DCA87BF00E2E997 /* stb_vorbis.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = stb_vorbis.c; path = ../../libs/stb_vorbis/stb_vorbis.c; sourceTree = ""; }; @@ -57,10 +59,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 523F97E61DDF926E006FE2FC /* QuartzCore.framework in Frameworks */, + 523F97E41DDF7AA5006FE2FC /* Cocoa.framework in Frameworks */, 99DF7BC41DCBA30B00C40D0A /* AudioToolbox.framework in Frameworks */, 99C4C0BA1796AB810032DE85 /* OpenGL.framework in Frameworks */, - 99C4C0B91796AB7B0032DE85 /* Carbon.framework in Frameworks */, - 99C4C0B71796AB730032DE85 /* AGL.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -73,7 +75,7 @@ 99BFB6AA1DCA87C500E2E997 /* minimp3.cpp */, 99BFB6A81DCA87BF00E2E997 /* stb_vorbis.c */, 99BFB69E1DCA7F1700E2E997 /* libs */, - 99BFB6A11DCA7F5300E2E997 /* main.cpp */, + 99BFB6A11DCA7F5300E2E997 /* main.mm */, 99BFB68D1DCA7F1700E2E997 /* lara.h */, 99BFB68E1DCA7F1700E2E997 /* format.h */, 99BFB68F1DCA7F1700E2E997 /* level.h */, @@ -108,6 +110,8 @@ 99C4C0821796A8230032DE85 /* Frameworks */ = { isa = PBXGroup; children = ( + 523F97E51DDF926E006FE2FC /* QuartzCore.framework */, + 523F97E31DDF7AA5006FE2FC /* Cocoa.framework */, 99DF7BC31DCBA30B00C40D0A /* AudioToolbox.framework */, 99DF7BC11DCBA2D100C40D0A /* CoreAudioKit.framework */, 99DF7BBF1DCBA2BF00C40D0A /* CoreAudio.framework */, @@ -193,7 +197,7 @@ buildActionMask = 2147483647; files = ( 99BFB6AB1DCA87C500E2E997 /* minimp3.cpp in Sources */, - 99BFB6A21DCA7F5300E2E997 /* main.cpp in Sources */, + 99BFB6A21DCA7F5300E2E997 /* main.mm in Sources */, 99BFB6A91DCA87BF00E2E997 /* stb_vorbis.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/src/platform/osx/main.cpp b/src/platform/osx/main.cpp deleted file mode 100644 index 128fdce..0000000 --- a/src/platform/osx/main.cpp +++ /dev/null @@ -1,244 +0,0 @@ -#include "game.h" - -#include -#include - -bool isQuit = false; -WindowRef window; -AGLContext context; - -#define SND_SIZE 8192 - -static AudioQueueRef audioQueue; - -void soundFill(void* inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) { - void* frames = inBuffer->mAudioData; - UInt32 count = inBuffer->mAudioDataBytesCapacity / 4; - Sound::fill((Sound::Frame*)frames, count); - inBuffer->mAudioDataByteSize = count * 4; - AudioQueueEnqueueBuffer(audioQueue, inBuffer, 0, NULL); - // TODO: mutex -} - -void soundInit() { - AudioStreamBasicDescription deviceFormat; - deviceFormat.mSampleRate = 44100; - deviceFormat.mFormatID = kAudioFormatLinearPCM; - deviceFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; - deviceFormat.mBytesPerPacket = 4; - deviceFormat.mFramesPerPacket = 1; - deviceFormat.mBytesPerFrame = 4; - deviceFormat.mChannelsPerFrame = 2; - deviceFormat.mBitsPerChannel = 16; - deviceFormat.mReserved = 0; - - AudioQueueNewOutput(&deviceFormat, soundFill, NULL, NULL, NULL, 0, &audioQueue); - - for (int i = 0; i < 2; i++) { - AudioQueueBufferRef mBuffer; - AudioQueueAllocateBuffer(audioQueue, SND_SIZE, &mBuffer); - soundFill(NULL, audioQueue, mBuffer); - } - AudioQueueStart(audioQueue, NULL); -} - -// common input functions -InputKey keyToInputKey(int code) { - static const int codes[] = { - 0x7B, 0x7C, 0x7E, 0x7D, 0x31, 0x24, 0x35, 0x38, 0x3B, 0x3A, - 0x1D, 0x12, 0x13, 0x14, 0x15, 0x17, 0x16, 0x1A, 0x1C, 0x19, // 0..9 - 0x00, 0x0B, 0x08, 0x02, 0x0E, 0x03, 0x05, 0x04, 0x22, 0x26, 0x28, 0x25, 0x2E, // A..M - 0x2D, 0x1F, 0x23, 0x0C, 0x0F, 0x01, 0x11, 0x20, 0x09, 0x0D, 0x07, 0x10, 0x06, // N..Z - }; - - for (int i = 0; i < sizeof(codes) / sizeof(codes[0]); i++) - if (codes[i] == code) - return (InputKey)(ikLeft + i); - return ikNone; -} - -InputKey mouseToInputKey(int btn) { - switch (btn) { - case 1 : return ikMouseL; - case 2 : return ikMouseR; - case 3 : return ikMouseM; - } - return ikNone; -} - -OSStatus eventHandler(EventHandlerCallRef handler, EventRef event, void* userData) { - OSType eventClass = GetEventClass(event); - UInt32 eventKind = GetEventKind(event); - - switch (eventClass) { - case kEventClassWindow : - switch (eventKind) { - case kEventWindowClosed : - isQuit = true; - break; - case kEventWindowBoundsChanged : { - aglUpdateContext(context); - Rect rect; - GetWindowPortBounds(window, &rect); - Core::width = rect.right - rect.left; - Core::height = rect.bottom - rect.top; - break; - } - } - break; - - case kEventClassMouse : { - EventMouseButton mouseButton; - CGPoint mousePos; - Rect wndRect; - - GetEventParameter(event, kEventParamMouseLocation, typeHIPoint, NULL, sizeof(mousePos), NULL, &mousePos); - GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(mouseButton), nil, &mouseButton); - - GetWindowBounds(window, kWindowContentRgn, &wndRect); - mousePos.x -= wndRect.left; - mousePos.y -= wndRect.top; - vec2 pos(mousePos.x, mousePos.y); - - switch (eventKind) { - case kEventMouseDown : - case kEventMouseUp : { - InputKey key = mouseToInputKey(mouseButton); - Input::setPos(key, pos); - Input::setDown(key, eventKind == kEventMouseDown); - break; - } - case kEventMouseDragged : - Input::setPos(ikMouseL, pos); - break; - } - break; - } - - case kEventClassKeyboard : { - switch (eventKind) { - case kEventRawKeyDown : - case kEventRawKeyUp : { - uint32 keyCode; - if (GetEventParameter(event, kEventParamKeyCode, typeUInt32, NULL, sizeof(keyCode), NULL, &keyCode) == noErr) - Input::setDown(keyToInputKey(keyCode), eventKind != kEventRawKeyUp); - break; - } - case kEventRawKeyModifiersChanged : { - uint32 modifiers; - if (GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(modifiers), NULL, &modifiers) == noErr) { - Input::setDown(ikShift, modifiers & shiftKey); - Input::setDown(ikCtrl, modifiers & controlKey); - Input::setDown(ikAlt, modifiers & optionKey); - } - break; - } - } - break; - } - } - - return CallNextEventHandler(handler, event); -} - -int getTime() { - static mach_timebase_info_data_t timebaseInfo; - if (timebaseInfo.denom == 0) { - mach_timebase_info(&timebaseInfo); - } - - uint64_t absolute = mach_absolute_time(); - uint64_t milliseconds = absolute * timebaseInfo.numer / (timebaseInfo.denom * 1000000ULL); - return int(milliseconds); -} - -char *contentPath; - -int main() { -// init window - Rect rect = {0, 0, 720, 1280}; - CreateNewWindow(kDocumentWindowClass, kWindowCloseBoxAttribute | kWindowCollapseBoxAttribute | kWindowFullZoomAttribute | kWindowResizableAttribute | kWindowStandardHandlerAttribute, &rect, &window); - SetWTitle(window, "\pOpenLara"); - - // init OpenGL context - GLint attribs[] = { - AGL_RGBA, - AGL_DOUBLEBUFFER, - AGL_BUFFER_SIZE, 32, - AGL_DEPTH_SIZE, 24, - AGL_STENCIL_SIZE, 8, - AGL_NONE - }; - AGLPixelFormat format = aglChoosePixelFormat(NULL, 0, (GLint*)&attribs); - context = aglCreateContext(format, NULL); - aglDestroyPixelFormat(format); - - aglSetDrawable(context, GetWindowPort(window)); - aglSetCurrentContext(context); - - // get path to game content - CFBundleRef bundle = CFBundleGetMainBundle(); - CFURLRef bundleURL = CFBundleCopyBundleURL(bundle); - CFStringRef pathStr = CFURLCopyFileSystemPath(bundleURL, kCFURLPOSIXPathStyle); - contentPath = new char[1024]; - CFStringGetFileSystemRepresentation(pathStr, contentPath, 1024); - strcat(contentPath, "/Contents/Resources/"); - - soundInit(); - Game::init(); - - // show window - const int events[][2] = { - { kEventClassWindow, kEventWindowClosed }, - { kEventClassWindow, kEventWindowBoundsChanged }, - { kEventClassKeyboard, kEventRawKeyDown }, - { kEventClassKeyboard, kEventRawKeyUp }, - { kEventClassKeyboard, kEventRawKeyModifiersChanged }, - { kEventClassMouse, kEventMouseDown }, - { kEventClassMouse, kEventMouseUp }, - { kEventClassMouse, kEventMouseDragged }, - }; - - InstallEventHandler(GetApplicationEventTarget(), (EventHandlerUPP)eventHandler, sizeof(events) / sizeof(events[0]), (EventTypeSpec*)&events, NULL, NULL); - SelectWindow(window); - ShowWindow(window); - - int lastTime = getTime(), fpsTime = lastTime + 1000, fps = 0; - - EventRecord event; - while (!isQuit) - if (!GetNextEvent(0xffff, &event)) { - int time = getTime(); - if (time <= lastTime) - continue; - - float delta = (time - lastTime) * 0.001f; - while (delta > EPS) { - Core::deltaTime = min(delta, 1.0f / 30.0f); - Game::update(); - delta -= Core::deltaTime; - } - lastTime = time; - - Core::stats.dips = 0; - Core::stats.tris = 0; - Game::render(); - aglSwapBuffers(context); - - if (fpsTime < getTime()) { - LOG("FPS: %d DIP: %d TRI: %d\n", fps, Core::stats.dips, Core::stats.tris); - fps = 0; - fpsTime = getTime() + 1000; - } else - fps++; - } - - Game::free(); - delete[] contentPath; - // TODO: sndFree - - aglSetCurrentContext(NULL); - ReleaseWindow(window); - - return 0; -} diff --git a/src/platform/osx/main.mm b/src/platform/osx/main.mm new file mode 100644 index 0000000..2960e3f --- /dev/null +++ b/src/platform/osx/main.mm @@ -0,0 +1,315 @@ +#include "game.h" + +#include +#include +#include + +bool isQuit = false; +WindowRef window; +AGLContext context; + +#define SND_SIZE 8192 + +static AudioQueueRef audioQueue; + +void soundFill(void* inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) { + void* frames = inBuffer->mAudioData; + UInt32 count = inBuffer->mAudioDataBytesCapacity / 4; + Sound::fill((Sound::Frame*)frames, count); + inBuffer->mAudioDataByteSize = count * 4; + AudioQueueEnqueueBuffer(audioQueue, inBuffer, 0, NULL); + // TODO: mutex +} + +void soundInit() { + AudioStreamBasicDescription deviceFormat; + deviceFormat.mSampleRate = 44100; + deviceFormat.mFormatID = kAudioFormatLinearPCM; + deviceFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; + deviceFormat.mBytesPerPacket = 4; + deviceFormat.mFramesPerPacket = 1; + deviceFormat.mBytesPerFrame = 4; + deviceFormat.mChannelsPerFrame = 2; + deviceFormat.mBitsPerChannel = 16; + deviceFormat.mReserved = 0; + + AudioQueueNewOutput(&deviceFormat, soundFill, NULL, NULL, NULL, 0, &audioQueue); + + for (int i = 0; i < 2; i++) { + AudioQueueBufferRef mBuffer; + AudioQueueAllocateBuffer(audioQueue, SND_SIZE, &mBuffer); + soundFill(NULL, audioQueue, mBuffer); + } + AudioQueueStart(audioQueue, NULL); +} + +// common input functions +InputKey keyToInputKey(int code) { + static const int codes[] = { + 0x7B, 0x7C, 0x7E, 0x7D, 0x31, 0x24, 0x35, 0x38, 0x3B, 0x3A, + 0x1D, 0x12, 0x13, 0x14, 0x15, 0x17, 0x16, 0x1A, 0x1C, 0x19, // 0..9 + 0x00, 0x0B, 0x08, 0x02, 0x0E, 0x03, 0x05, 0x04, 0x22, 0x26, 0x28, 0x25, 0x2E, // A..M + 0x2D, 0x1F, 0x23, 0x0C, 0x0F, 0x01, 0x11, 0x20, 0x09, 0x0D, 0x07, 0x10, 0x06, // N..Z + }; + + for (int i = 0; i < sizeof(codes) / sizeof(codes[0]); i++) + if (codes[i] == code) + return (InputKey)(ikLeft + i); + return ikNone; +} + +InputKey mouseToInputKey(int btn) { + switch (btn) { + case 1 : return ikMouseL; + case 2 : return ikMouseR; + case 3 : return ikMouseM; + } + return ikNone; +} + +int lastTime; +int fpsTime; +int fps; +CVDisplayLinkRef displayLink; + +int getTime() { + static mach_timebase_info_data_t timebaseInfo; + if (timebaseInfo.denom == 0) { + mach_timebase_info(&timebaseInfo); + } + + uint64_t absolute = mach_absolute_time(); + uint64_t milliseconds = absolute * timebaseInfo.numer / (timebaseInfo.denom * 1000000ULL); + return int(milliseconds); +} + +/* + * Specific OpenGLView. This subclass is necessary primarily to handle input. + * Capturing and dispatching events manually on OS X is definitely not worth it. + */ +@interface OpenLaraGLView : NSOpenGLView + +@end + +@implementation OpenLaraGLView + +- (InputKey)inputKeyForMouseEvent:(NSEvent *)theEvent { + switch (theEvent.buttonNumber) { + case 0: return ikMouseL; + case 1: return ikMouseR; + case 2: return ikMouseM; + default: return ikNone; + } +} + +- (vec2)inputPositionForMouseEvent:(NSEvent *)theEvent { + NSPoint inWindow = theEvent.locationInWindow; + NSPoint inView = [self convertPoint:inWindow fromView:nil]; + // TODO Do we need to flip y, due to OS X having the origin at the bottom + // left as opposed to top left in every single other system? The original + // code didn't so I won't either for now. + return vec2(inView.x, inView.y); +} + +- (void)mouseDown:(NSEvent *)theEvent { + InputKey inputKey = [self inputKeyForMouseEvent:theEvent]; + Input::setPos(inputKey, [self inputPositionForMouseEvent:theEvent]); + Input::setDown(inputKey, true); +} + +- (void)rightMouseDown:(NSEvent *)theEvent { + [self mouseDown:theEvent]; +} + +- (void)otherMouseDown:(NSEvent *)theEvent { + [self mouseDown:theEvent]; +} + +- (void)mouseUp:(NSEvent *)theEvent { + InputKey inputKey = [self inputKeyForMouseEvent:theEvent]; + Input::setPos(inputKey, [self inputPositionForMouseEvent:theEvent]); + Input::setDown(inputKey, false); +} + +- (void)rightMouseUp:(NSEvent *)theEvent { + [self mouseUp:theEvent]; +} + +- (void)otherMouseUp:(NSEvent *)theEvent { + [self mouseUp:theEvent]; +} + +- (void)mouseDragged:(NSEvent *)theEvent { + InputKey inputKey = [self inputKeyForMouseEvent:theEvent]; + Input::setPos(inputKey, [self inputPositionForMouseEvent:theEvent]); +} + +- (void)rightMouseDragged:(NSEvent *)theEvent { + [self mouseDragged:theEvent]; +} + +- (void)otherMouseDragged:(NSEvent *)theEvent { + [self mouseDragged:theEvent]; +} + +- (void)keyDown:(NSEvent *)theEvent { + unsigned short keyCode = theEvent.keyCode; + Input::setDown(keyToInputKey(keyCode), true); +} + +- (void)keyUp:(NSEvent *)theEvent { + unsigned short keyCode = theEvent.keyCode; + Input::setDown(keyToInputKey(keyCode), false); +} + +- (void)flagsChanged:(NSEvent *)theEvent { + NSEventModifierFlags modifiers = theEvent.modifierFlags; + Input::setDown(ikShift, modifiers & NSShiftKeyMask); + Input::setDown(ikCtrl, modifiers & NSControlKeyMask); + Input::setDown(ikAlt, modifiers & NSAlternateKeyMask); +} + +- (BOOL)acceptsFirstResponder { + return YES; +} + +- (void)reshape { + NSRect bounds = self.bounds; + Core::width = bounds.size.width; + Core::height = bounds.size.height; +} + +@end + +/* + * Delegate to deal with things that happen at the window level + */ +@interface OpenLaraWindowDelegate : NSObject +@end + +@implementation OpenLaraWindowDelegate + +- (void)windowWillClose:(NSNotification *)notification { + [[NSApplication sharedApplication] terminate:self]; +} + +- (void)windowDidMiniaturize:(NSNotification *)notification { + // Pause game + CVDisplayLinkStop(displayLink); + Input::reset(); +} + +- (void)windowDidDeminiaturize:(NSNotification *)notification { + // End paused game. + lastTime = getTime(); + CVDisplayLinkStart(displayLink); +} + +@end + +char *contentPath; + +/* + * Callback for the CVDisplayLink, an OS X mechanism to get precise timing for + * multi-media applications. This runs the whole game loop, for simplicitly's + * sake. This is not really the idea of the displayLinkCallback, which should + * more or less just swap the OpenGL buffer here and at least have the update + * running in a different thread entirely. But it works. + */ +CVReturn displayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *now, const CVTimeStamp *outputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *context) { + OpenLaraGLView *view = (OpenLaraGLView *) context; + [view.openGLContext makeCurrentContext]; + + // TODO: This should probably get the time from the outputTime parameter + int time = getTime(); + if (time <= lastTime) + return kCVReturnUnsupported; + + // TODO: This should probably run the update in a separate thread + // and only do rendering here + float delta = (time - lastTime) * 0.001f; + while (delta > EPS) { + Core::deltaTime = min(delta, 1.0f / 30.0f); + Game::update(); + delta -= Core::deltaTime; + } + lastTime = time; + + // TODO: Rendering should probably happen a bit in advance with only the + // flushBuffer here + Core::stats.dips = 0; + Core::stats.tris = 0; + Game::render(); + [view.openGLContext flushBuffer]; + + if (fpsTime < getTime()) { + LOG("FPS: %d DIP: %d TRI: %d\n", fps, Core::stats.dips, Core::stats.tris); + fps = 0; + fpsTime = getTime() + 1000; + } else + fps++; + + return kCVReturnSuccess; +} + +int main() { + NSApplication *application = [NSApplication sharedApplication]; + + // init window + NSRect rect = NSMakeRect(0, 0, 1280, 720); + NSWindow *mainWindow = [[NSWindow alloc] initWithContentRect:rect styleMask:NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask backing:NSBackingStoreBuffered defer:YES]; + mainWindow.title = @"OpenLara"; + mainWindow.acceptsMouseMovedEvents = YES; + mainWindow.delegate = [[OpenLaraWindowDelegate alloc] init]; + + // init OpenGL context + NSOpenGLPixelFormatAttribute attribs[] = { + NSOpenGLPFADoubleBuffer, + NSOpenGLPFAColorSize, 32, + NSOpenGLPFADepthSize, 24, + NSOpenGLPFAStencilSize, 8, + NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersionLegacy, + 0 + }; + NSOpenGLPixelFormat *format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs]; + + OpenLaraGLView *view = [[OpenLaraGLView alloc] initWithFrame:mainWindow.contentLayoutRect pixelFormat:format]; + view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + mainWindow.contentView = view; + [view.openGLContext makeCurrentContext]; + + // get path to game content + NSBundle *bundle = [NSBundle mainBundle]; + NSURL *bundleURL = bundle.bundleURL; + contentPath = new char[1024]; + [bundleURL getFileSystemRepresentation:contentPath maxLength:1024]; + strcat(contentPath, "/Contents/Resources/"); + + soundInit(); + Game::init(); + + // show window + [mainWindow makeKeyAndOrderFront:nil]; + + // Set up DisplayLink. This will call our callback in time with display + // refresh rate. + CVReturn cvreturn = CVDisplayLinkCreateWithActiveCGDisplays(&displayLink); + if (cvreturn != kCVReturnSuccess) { + NSLog(@"Could not create Display Link: %d", (int) cvreturn); + } + cvreturn = CVDisplayLinkSetOutputCallback(displayLink, displayLinkCallback, view); + if (cvreturn != kCVReturnSuccess) { + NSLog(@"Could not create set callback for display link: %d", (int) cvreturn); + } + + lastTime = getTime(); + fpsTime = lastTime + 1000; + cvreturn = CVDisplayLinkStart(displayLink); + if (cvreturn != kCVReturnSuccess) { + NSLog(@"Could not start display link: %d", (int) cvreturn); + } + + // Start application main loop + [application run]; + return 0; +}