#include "pch.h" #include "log.h" #include "shader.h" #include "shape.h" #include "texture.h" #include "image.h" #include "app.h" #include "keymap.h" #include "main.h" #include #include #include #include #import "objc_utils.h" #include #include std::deque> tasklist; std::mutex task_mutex; void global_exception_handler(NSException* e) { NSAlert *alert = [NSAlert alertWithMessageText:@"Recovery" defaultButton:@"OK" alternateButton:@"Cancel" otherButton:nil informativeTextWithFormat:@"Exception"]; [alert runModal]; if (App::I.canvas && App::I.canvas->m_canvas) App::I.canvas->m_canvas->project_save_thread(App::I.data_path + "/recovery.pano"); std::terminate(); } void global_signal_handler(int e) { NSAlert *alert = [NSAlert alertWithMessageText:@"Recovery" defaultButton:@"OK" alternateButton:@"Cancel" otherButton:nil informativeTextWithFormat:@"Signal"]; [alert runModal]; if (App::I.canvas && App::I.canvas->m_canvas) App::I.canvas->m_canvas->project_save_thread(App::I.data_path + "/recovery.pano"); std::terminate(); } @implementation View - (void)async_lock { CGLLockContext([glctx CGLContextObj]); [glctx makeCurrentContext]; } - (void)async_unlock { CGLUnlockContext([glctx CGLContextObj]); } - (void)async_swap { CGLFlushDrawable([glctx CGLContextObj]); } - (void)close { [[NSApplication sharedApplication] terminate:nil]; } - (std::string)pick_file { NSOpenPanel *panel = [NSOpenPanel openPanel]; [panel setCanChooseFiles:YES]; [panel setCanChooseDirectories:NO]; [panel setAllowsMultipleSelection:NO]; // yes if more than one dir is allowed NSArray* fileTypes = [NSArray arrayWithObjects:@"png", @"PNG", @"jpg", @"JPG", @"jpeg", nil]; [panel setAllowedFileTypes:fileTypes]; NSInteger clicked = [panel runModal]; std::string ret; if (clicked == NSFileHandlingPanelOKButton) { for (NSURL *url in [panel URLs]) { LOG("selected file: %s", [[url path] cStringUsingEncoding:NSUTF8StringEncoding]); ret = [[url path] cStringUsingEncoding:NSUTF8StringEncoding]; } } return ret; } - (instancetype)initWithFrame:(NSRect)frameRect { gl_ready = false; NSOpenGLPixelFormatAttribute attrs[] = { NSOpenGLPFADoubleBuffer, NSOpenGLPFADepthSize, 24, // Must specify the 3.2 Core Profile to use OpenGL 3.2 NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, // Multisample NSOpenGLPFAMultisample, NSOpenGLPFASamples, 2, NSOpenGLPFASampleBuffers, 1, 0 }; NSOpenGLPixelFormat *pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; NSOpenGLContext* context = [[NSOpenGLContext alloc] initWithFormat:pf shareContext:nil]; self = [super initWithFrame:frameRect pixelFormat:pf]; [self setPixelFormat:pf]; [self setOpenGLContext:context]; return self; } - (void)prepareOpenGL { NSLog(@"prepare"); // Synchronize buffer swaps with vertical refresh rate GLint swapInt = 1; [[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; // Create a display link capable of being used with all active displays CVDisplayLinkCreateWithActiveCGDisplays(&dl); // Set the renderer output callback function CVDisplayLinkSetOutputCallback(dl, &MyDisplayLinkCallback, (__bridge void*)self); // Set the display link for the current renderer CGLContextObj cglContext = [[self openGLContext] CGLContextObj]; CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj]; CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(dl, cglContext, cglPixelFormat); CGLEnable([self.openGLContext CGLContextObj], kCGLCECrashOnRemovedFunctions); // Activate the display link CVDisplayLinkStart(dl); cgl = [[self openGLContext] CGLContextObj]; glctx = [self openGLContext]; CGLLockContext([[self openGLContext] CGLContextObj]); App::I.init(); CGLUnlockContext([[self openGLContext] CGLContextObj]); gl_ready = true; } - (void)terminateGL { CGLLockContext([[self openGLContext] CGLContextObj]); CVDisplayLinkRelease(dl); CGLUnlockContext([[self openGLContext] CGLContextObj]); } // This is the renderer output callback function static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext) { CVReturn result = [(__bridge View*)displayLinkContext getFrameForTime:outputTime]; return result; } - (CVReturn)getFrameForTime:(const CVTimeStamp*)outputTime { static double _timeFreq = CVGetHostClockFrequency(); static double _prevTime = (double)outputTime->hostTime / _timeFreq; double hostTime = (double)outputTime->hostTime; double now = hostTime / _timeFreq; // this will not update unless 1/30th of a second has passed since the last update if (1 /*now < _prevTime + (1.0 / 30.0) &&*/ ) { std::deque> working_list; { std::lock_guard lock(task_mutex); working_list = std::move(tasklist); } // 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 CGLLockContext([glctx CGLContextObj]); [glctx makeCurrentContext]; while (!working_list.empty()) { working_list.front()(); working_list.pop_front(); } if (App::I.redraw) { App::I.clear(); App::I.update(now - _prevTime); CGLFlushDrawable([glctx CGLContextObj]); _prevTime = now; } //[[self openGLContext] flushBuffer]; // returning NO will cause the layer to NOT be redrawn CGLUnlockContext([glctx CGLContextObj]); return NO; } else { // change whatever you want to change here, as a function of time elapsed _prevTime = now; // return YES to have your layer redrawn return YES; } return kCVReturnSuccess; } - (void)dealloc { // Release the display link CVDisplayLinkRelease(dl); } - (void)drawRect:(NSRect)dirtyRect { NSLog(@"drawRect"); // 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 CGLLockContext(cgl); [glctx makeCurrentContext]; App::I.redraw = true; App::I.clear(); App::I.update(0); //[[self openGLContext] flushBuffer]; // returning NO will cause the layer to NOT be redrawn CGLFlushDrawable(cgl); CGLUnlockContext(cgl); } - (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 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 App::I.resize(viewRectPixels.size.width, 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)mouseDown:(NSEvent *)theEvent { auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; std::lock_guard lock(task_mutex); tasklist.emplace_back([mouseLoc,p=theEvent.pressure] { App::I.mouse_down(0, mouseLoc.x, App::I.height - mouseLoc.y - 1, p, kEventSource::Mouse); }); } - (void)rightMouseDown:(NSEvent *)theEvent { auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; std::lock_guard lock(task_mutex); tasklist.emplace_back([mouseLoc,p=theEvent.pressure] { App::I.mouse_down(1, mouseLoc.x, App::I.height - mouseLoc.y - 1, p, kEventSource::Mouse); }); } - (void)mouseUp:(NSEvent *)theEvent { auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; std::lock_guard lock(task_mutex); tasklist.emplace_back([mouseLoc] { App::I.mouse_up(0, mouseLoc.x, App::I.height - mouseLoc.y - 1, kEventSource::Mouse); }); } - (void)rightMouseUp:(NSEvent *)theEvent { auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; std::lock_guard lock(task_mutex); tasklist.emplace_back([mouseLoc] { App::I.mouse_up(1, mouseLoc.x, App::I.height - mouseLoc.y - 1, kEventSource::Mouse); }); } - (void)mouseMoved:(NSEvent *)theEvent { auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; std::lock_guard lock(task_mutex); tasklist.emplace_back([mouseLoc,p=theEvent.pressure] { App::I.mouse_move(mouseLoc.x, App::I.height - mouseLoc.y - 1, p, kEventSource::Mouse); }); } -(void)mouseDragged:(NSEvent *)theEvent { auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; std::lock_guard lock(task_mutex); tasklist.emplace_back([mouseLoc,p=theEvent.pressure] { App::I.mouse_move(mouseLoc.x, App::I.height - mouseLoc.y - 1, p, kEventSource::Mouse); }); } - (void)rightMouseDragged:(NSEvent *)theEvent { auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; std::lock_guard lock(task_mutex); tasklist.emplace_back([mouseLoc,p=theEvent.pressure] { App::I.mouse_move(mouseLoc.x, App::I.height - mouseLoc.y - 1, p, kEventSource::Mouse); }); } - (void)scrollWheel:(NSEvent *)theEvent { auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; std::lock_guard lock(task_mutex); tasklist.emplace_back([mouseLoc,d=[theEvent deltaY]] { App::I.mouse_scroll(mouseLoc.x, App::I.height - mouseLoc.y - 1, d); }); } - (void)keyDown:(NSEvent *)theEvent { auto keyCode = [theEvent keyCode]; auto chars = [theEvent characters]; std::string s = [chars cStringUsingEncoding:NSASCIIStringEncoding]; std::lock_guard lock(task_mutex); tasklist.emplace_back([keyCode, s] { if (App::I.keys[(int)kKey::KeyCtrl]) { App::I.key_down(convert_key(keyCode)); App::I.key_up(convert_key(keyCode)); } else { App::I.key_down(convert_key(keyCode)); if (!s.empty()) App::I.key_char(s[0]); } }); } - (void)keyUp:(NSEvent *)theEvent { auto keyCode = [theEvent keyCode]; std::lock_guard lock(task_mutex); tasklist.emplace_back([keyCode] { App::I.key_up(convert_key(keyCode)); }); } - (void)flagsChanged:(NSEvent *)event { [super flagsChanged:event]; auto flags = [event modifierFlags]; std::lock_guard lock(task_mutex); tasklist.emplace_back([flags] { static bool pressed_shift = false; if (pressed_shift != (flags & NSShiftKeyMask)) { pressed_shift = (flags & NSShiftKeyMask); pressed_shift ? App::I.key_down(kKey::KeyShift) : App::I.key_up(kKey::KeyShift); } static bool pressed_alt = false; if (pressed_alt != (flags & NSAlternateKeyMask)) { pressed_alt = (flags & NSAlternateKeyMask); pressed_alt ? App::I.key_down(kKey::KeyAlt) : App::I.key_up(kKey::KeyAlt); } static bool pressed_cmd = false; if (pressed_cmd != (flags & NSCommandKeyMask)) { pressed_cmd = (flags & NSCommandKeyMask); pressed_cmd ? App::I.key_down(kKey::KeyCtrl) : App::I.key_up(kKey::KeyCtrl); } }); } @end @implementation Window @end @implementation Controller - (void)windowWillClose:(NSNotification *)notification { App::I.terminate(); [[NSApplication sharedApplication] terminate:nil]; } -(BOOL)windowShouldClose:(NSWindow *)sender { return App::I.request_close(); } @end @interface AppOSX : NSApplication { Window* window; Controller* controller; View* view; } @end @implementation AppOSX - (instancetype)init { self = [super init]; [self setActivationPolicy:NSApplicationActivationPolicyRegular]; // make it to the front [self setDelegate:self]; return self; } - (void)applicationWillTerminate:(NSNotification *)notification { [view terminateGL]; [window close]; } - (void)applicationDidFinishLaunching:(NSNotification *)notification { if (!App::I.check_license()) return; App::I.initLog(); App::I.create(); NSRect r = NSMakeRect(0, 0, App::I.width, App::I.height); view = [[View alloc] initWithFrame:r]; controller = [[Controller alloc] initWithWindow:window]; App::I.osx_view = view; auto style = NSTitledWindowMask|NSMiniaturizableWindowMask|NSResizableWindowMask|NSClosableWindowMask; window = [[Window alloc] initWithContentRect:r styleMask:style backing:NSBackingStoreBuffered defer:NO]; [window setDelegate:controller]; [window setTitle:[NSString stringWithUTF8String:g_window_title]]; [window center]; [window makeKeyAndOrderFront:controller]; [window setContentView:view]; [window setAcceptsMouseMovedEvents:true]; [window makeFirstResponder:view]; view->wnd = window; auto menubar = [NSMenu new]; auto appMenuItem = [NSMenuItem new]; [menubar addItem:appMenuItem]; [self setMainMenu:menubar]; auto appMenu = [NSMenu new]; auto appName = [[NSProcessInfo processInfo] processName]; auto quitTitle = [@"Quit " stringByAppendingString:appName]; auto quitMenuItem = [[NSMenuItem alloc] initWithTitle:quitTitle action:@selector(terminate:) keyEquivalent:@"q"]; [appMenu addItem:quitMenuItem]; [appMenuItem setSubmenu:appMenu]; NSLog(@"app launched"); } @end int main(int argc, const char * argv[]) { AppOSX* app = [AppOSX sharedApplication]; NSSetUncaughtExceptionHandler(&global_exception_handler); signal(SIGFPE, global_signal_handler); [app run]; return 0; }