// // NSWrapper.mm // XcodeOpenCL // // Created by Joshua Moerman on 13/04/14. // // #include "NSWrapper.hpp" #include "NSGLWrapper.hpp" #include #import #import GLViewFunctionality simple_draw(std::function f){ return {nullptr, [f](ContextParameters){ f(); }, nullptr}; } @interface GLView : NSOpenGLView // All three will only be called with the current gl context already set // May be called from different threads (but the properties are atomic by default) @property GLViewFunctionality functionality; @end @interface MySlider : NSSlider - (instancetype) initWithFrame:(NSRect)frame; - (void)action:(MySlider*)sender; @property std::function callback; @end @implementation MySlider - (instancetype) initWithFrame:(NSRect)frame{ if(self = [super initWithFrame: frame]){ self.target = self; self.action = @selector(action:); } return self; } - (void)action:(MySlider*)sender{ if(self.callback){ self.callback(sender.doubleValue); } } @end struct NSAppWrapperImpl { NSAppWrapperImpl() { app = [NSApplication sharedApplication]; app.activationPolicy = NSApplicationActivationPolicyRegular; appName = [[NSProcessInfo processInfo] processName]; } void set_up_menu() { NSMenu* appMenu = [NSMenu new]; [appMenu addItemWithTitle:[@"Quit " stringByAppendingString:appName] action:@selector(terminate:) keyEquivalent:@"q"]; NSMenuItem * menuItem = [NSMenuItem new]; menuItem.submenu = appMenu; app.mainMenu = [NSMenu new]; [app.mainMenu addItem:menuItem]; } void create_window(GLViewFunctionality const & f) { NSWindow * window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 500, 500) styleMask:NSTitledWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask backing:NSBackingStoreBuffered defer:YES]; window.title = appName; window.collectionBehavior |= NSWindowCollectionBehaviorFullScreenPrimary; if(windows.empty()){ [window center]; } else { NSWindow * previous_window = windows.back(); NSPoint top_right = previous_window.frame.origin; top_right.x += previous_window.frame.size.width; top_right.y += previous_window.frame.size.height; [window setFrameTopLeftPoint: top_right]; } [window makeKeyAndOrderFront:nil]; NSRect frame = [window contentRectForFrameRect:window.frame]; frame.origin = {0, 0}; GLView * view = [[GLView alloc] initWithFrame:frame]; view.functionality = f; window.contentView = view; windows.push_back(window); } void create_window(ControlFunctionality const & f) { const CGFloat width = 200; NSWindow * window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, width, 500) styleMask:NSTitledWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask backing:NSBackingStoreBuffered defer:YES]; window.title = appName; if(windows.empty()){ [window center]; } else { NSWindow * previous_window = windows.back(); NSPoint top_right = previous_window.frame.origin; top_right.x += previous_window.frame.size.width; top_right.y += previous_window.frame.size.height; [window setFrameTopLeftPoint: top_right]; } [window makeKeyAndOrderFront:nil]; NSRect frame = [window contentRectForFrameRect:window.frame]; frame.origin = {0, 0}; NSView * view = [[NSView alloc] initWithFrame:frame]; CGFloat y = 0; CGFloat h = 20; CGFloat padding = 8; for(auto&& c : f.sliders){ NSTextField * label = [[NSTextField alloc] initWithFrame:{padding, y+padding, 20, h}]; label.editable = NO; label.bezeled = NO; label.drawsBackground = NO; label.stringValue = [NSString stringWithUTF8String: c.name.c_str()]; [view addSubview: label]; MySlider * slider = [[MySlider alloc] initWithFrame:{20+padding, y+padding, width-20-2*padding, h}]; slider.minValue = c.min; slider.maxValue = c.max; slider.doubleValue = c.start; slider.callback = c.callback; [view addSubview: slider]; y += h; } window.contentView = view; windows.push_back(window); } void run() { [app run]; } NSApplication * app; std::vector windows; NSString * appName; }; NSAppWrapper::NSAppWrapper() : impl(new NSAppWrapperImpl){ impl->set_up_menu(); } NSAppWrapper::~NSAppWrapper() = default; void NSAppWrapper::run() { impl->run(); } void NSAppWrapper::create_window(GLViewFunctionality const & f){ impl->create_window(f); } void NSAppWrapper::create_window(ControlFunctionality const & f){ impl->create_window(f); } /* Some general notes about GLView: - It is in a multithreaded context. The CVDisplayLink will run on a diferent thread than the main thread. However the main thread *will also draw* (eg. when reshaping, to avoid flickering). So there are some locks here and there. - Hopefully the C++ objects are destructed nicely - There is syntax for the constructor, so I used unique_ptr... */ @implementation GLView { std::unique_ptr context; std::unique_ptr linky; } @synthesize functionality; - (id) initWithFrame:(NSRect)frameRect { if(self = [super initWithFrame:frameRect]){ context = std::unique_ptr(new GLContext); self.pixelFormat = [[NSOpenGLPixelFormat alloc] initWithCGLPixelFormatObj: context->pix]; self.openGLContext = [[NSOpenGLContext alloc] initWithCGLContextObj: context->ctx]; self.wantsBestResolutionOpenGLSurface = YES; // Opt-in for retina } return self; } - (void) prepareOpenGL{ [super prepareOpenGL]; context->set_as_current_context(); // Synchronize buffer swaps with vertical refresh rate GLint swapInt = 1; [[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; if(functionality.initialize_callback){ functionality.initialize_callback({*context, nullptr, nullptr}); } // Create a display link capable of being used with all active displays linky = std::unique_ptr(new CVDisplayLinky(*context)); linky->callback = [=]{ // will be called in a different thread => need new autoreleasepool @autoreleasepool { [self drawView]; } }; linky->start(); // 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{ // The default OpenGL render buffers will be destroyed. If display link // continues to fire without renderbuffers, OpenGL draw calls will set errors. linky->stop(); } - (void) reshape{ [super reshape]; context->lock(); // [NSView convertRectToBacking] converts point sizes to pixel sizes. // 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:self.bounds]; if(functionality.resize_callback){ functionality.resize_callback({*context, nullptr, nullptr}, viewRectPixels.size.width, viewRectPixels.size.height); } context->unlock(); } - (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{ [self drawView]; } - (void) drawView{ context->set_as_current_context(); // 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 context->lock(); NSRect viewRectPixels = [self convertRectToBacking:self.bounds]; auto& ctx = context->ctx; if(functionality.draw_callback){ functionality.draw_callback({*context, [viewRectPixels]{ glBindFramebuffer(GL_FRAMEBUFFER, 0); glViewport(0, 0, (int)viewRectPixels.size.width, (int)viewRectPixels.size.height); }, [&ctx]{ CGLFlushDrawable(ctx); } }); } context->unlock(); } @end