From 1393193a731c2efbaa23695fb4ca84281473375c Mon Sep 17 00:00:00 2001 From: cochrane Date: Fri, 18 Nov 2016 18:44:37 +0100 Subject: [PATCH 1/6] Use mach_absolute_time for time retrieval The Microseconds function is a leftover from the days of Carbon and deprecated since 10.8. --- src/platform/osx/main.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/platform/osx/main.cpp b/src/platform/osx/main.cpp index eb6bd33..128fdce 100644 --- a/src/platform/osx/main.cpp +++ b/src/platform/osx/main.cpp @@ -1,5 +1,8 @@ #include "game.h" +#include +#include + bool isQuit = false; WindowRef window; AGLContext context; @@ -139,9 +142,14 @@ OSStatus eventHandler(EventHandlerCallRef handler, EventRef event, void* userDat } int getTime() { - UInt64 t; - Microseconds((UnsignedWide*)&t); - return int(t / 1000); + 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; From 4d1738dd0ae547732238f40afe32222419172c9a Mon Sep 17 00:00:00 2001 From: cochrane Date: Fri, 18 Nov 2016 21:19:11 +0100 Subject: [PATCH 2/6] Switch OpenLara to use Cocoa on OS X MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Yes, that does amount to pretty much a rewrite of the main.cpp. Don’t worry, it’s not that long. It also got renamed to main.mm because it’s an Objective-C++ file now. There are still a few rough edges, since I didn’t want to add a nib to any of this. Among other things, the display link probably starts a tiny bit too early, resulting in gameplay noises being heard before the game is actually visible. Resizing may leave white flashes; not quite sure what that’s about but they are brief. Finally, no menus of any kind. At least a system and window menu would be necessary (to support Cmd+Q). --- .../osx/OpenLara.xcodeproj/project.pbxproj | 20 +- src/platform/osx/main.cpp | 244 -------------- src/platform/osx/main.mm | 315 ++++++++++++++++++ 3 files changed, 327 insertions(+), 252 deletions(-) delete mode 100644 src/platform/osx/main.cpp create mode 100644 src/platform/osx/main.mm 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; +} From a4c16b8d9512a46fe16f7e073716b1165eed022e Mon Sep 17 00:00:00 2001 From: cochrane Date: Fri, 18 Nov 2016 21:45:23 +0100 Subject: [PATCH 3/6] Add proper main menu for OS X MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OS X code did not create a main menu. While it arguably doesn’t really need one, it’s considered impolite to forgo it on this platform, especially since all standard key commands interact with the menu bar (they quickly highlight the menu item that has the actual user-selectable command to provide some feedback). With this menu added, the Command+Q shortcut to quit the application now also works. --- src/platform/osx/main.mm | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/platform/osx/main.mm b/src/platform/osx/main.mm index 2960e3f..d0c9bf8 100644 --- a/src/platform/osx/main.mm +++ b/src/platform/osx/main.mm @@ -277,6 +277,44 @@ int main() { view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; mainWindow.contentView = view; [view.openGLContext makeCurrentContext]; + + // Init main menu + NSMenu *mainMenu = [[NSMenu alloc] initWithTitle:@""]; + NSMenuItem *appMenu = [[NSMenuItem alloc] initWithTitle:@"OpenLara" action:nil keyEquivalent:@""]; + [mainMenu addItem:appMenu]; + appMenu.submenu = [[NSMenu alloc] initWithTitle:@"OpenLara"]; + + // - app menu (no preferences) + [appMenu.submenu addItemWithTitle:@"About OpenLara" action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; + + [appMenu.submenu addItem:[NSMenuItem separatorItem]]; + + NSMenuItem *servicesItem = [[NSMenuItem alloc] initWithTitle:@"Services" action:nil keyEquivalent:@""]; + servicesItem.submenu = [[NSMenu alloc] initWithTitle:@"Services"]; + [appMenu.submenu addItem:servicesItem]; + + [appMenu.submenu addItem:[NSMenuItem separatorItem]]; + + [appMenu.submenu addItemWithTitle:@"Hide OpenLara" action:@selector(hide:) keyEquivalent:@"h"]; + NSMenuItem *hideOthersItem = [appMenu.submenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; + hideOthersItem.keyEquivalentModifierMask = NSAlternateKeyMask | NSCommandKeyMask; + [appMenu.submenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; + + [appMenu.submenu addItem:[NSMenuItem separatorItem]]; + + [appMenu.submenu addItemWithTitle:@"Quit OpenLara" action:@selector(terminate:) keyEquivalent:@"q"]; + + // - window menu + NSMenuItem *windowMenu= [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; + [mainMenu addItem:windowMenu]; + windowMenu.submenu = [[NSMenu alloc] initWithTitle:@"Window"]; + + [windowMenu.submenu addItemWithTitle:@"Minimize" action:@selector(miniaturize:) keyEquivalent:@"m"]; + [windowMenu.submenu addItemWithTitle:@"Zoom" action:@selector(zoom:) keyEquivalent:@""]; + + application.mainMenu = mainMenu; + application.windowsMenu = windowMenu.submenu; + application.servicesMenu = servicesItem.submenu; // get path to game content NSBundle *bundle = [NSBundle mainBundle]; From cc8853a9ef93a2359b8575c38bba5119a0264d71 Mon Sep 17 00:00:00 2001 From: cochrane Date: Fri, 18 Nov 2016 21:58:12 +0100 Subject: [PATCH 4/6] Ensure window is centered on being created, not squished somewhere in the corner. --- src/platform/osx/main.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/osx/main.mm b/src/platform/osx/main.mm index d0c9bf8..a1e0f18 100644 --- a/src/platform/osx/main.mm +++ b/src/platform/osx/main.mm @@ -327,6 +327,7 @@ int main() { Game::init(); // show window + [mainWindow center]; [mainWindow makeKeyAndOrderFront:nil]; // Set up DisplayLink. This will call our callback in time with display From d764b3c8fa1c9bf94e358d55362e50e1948d42aa Mon Sep 17 00:00:00 2001 From: cochrane Date: Sat, 19 Nov 2016 07:24:59 +0100 Subject: [PATCH 5/6] Change return code in case of negative time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I’m not sure what to actually return here anyway, but apparently this symbol does not always exist. --- src/platform/osx/main.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/osx/main.mm b/src/platform/osx/main.mm index a1e0f18..ed9f9ea 100644 --- a/src/platform/osx/main.mm +++ b/src/platform/osx/main.mm @@ -223,7 +223,7 @@ CVReturn displayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *no // TODO: This should probably get the time from the outputTime parameter int time = getTime(); if (time <= lastTime) - return kCVReturnUnsupported; + return kCVReturnSuccess; // TODO: This should probably run the update in a separate thread // and only do rendering here From 8769bdb3beb5cb4b0a6bdb658914879130f8f15b Mon Sep 17 00:00:00 2001 From: cochrane Date: Sat, 19 Nov 2016 07:38:32 +0100 Subject: [PATCH 6/6] Use resources URL instead of main bundle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because it’s nicer. --- src/platform/osx/main.mm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/osx/main.mm b/src/platform/osx/main.mm index ed9f9ea..1cedbd8 100644 --- a/src/platform/osx/main.mm +++ b/src/platform/osx/main.mm @@ -318,10 +318,10 @@ int main() { // get path to game content NSBundle *bundle = [NSBundle mainBundle]; - NSURL *bundleURL = bundle.bundleURL; + NSURL *resourceURL = bundle.resourceURL; contentPath = new char[1024]; - [bundleURL getFileSystemRepresentation:contentPath maxLength:1024]; - strcat(contentPath, "/Contents/Resources/"); + [resourceURL getFileSystemRepresentation:contentPath maxLength:1024]; + strcat(contentPath, "/"); soundInit(); Game::init();