Joshua Moerman
11 years ago
commit
7c5ebc89b8
5 changed files with 410 additions and 0 deletions
@ -0,0 +1,20 @@ |
|||||
|
cmake_minimum_required(VERSION 2.8) |
||||
|
|
||||
|
set(LIBNAME JNSAppWrapper) |
||||
|
|
||||
|
file(GLOB sources "*.mm") |
||||
|
file(GLOB headers "*.hpp") |
||||
|
|
||||
|
add_library(${LIBNAME} ${headers} ${sources}) |
||||
|
|
||||
|
find_library(FOUNDATION_LIBRARY Foundation) |
||||
|
find_library(COCOA_LIBRARY Cocoa) |
||||
|
find_library(COREGRAPHICS_LIBRARY CoreGraphics) |
||||
|
find_library(OPENGL_LIBRARY OpenGL) |
||||
|
|
||||
|
set(libs ${FOUNDATION_LIBRARY} ${COCOA_LIBRARY} ${COREGRAPHICS_LIBRARY} ${OPENGL_LIBRARY}) |
||||
|
|
||||
|
target_link_libraries(${LIBNAME} ${libs}) |
||||
|
|
||||
|
install(TARGETS ${LIBNAME} DESTINATION lib) |
||||
|
install(FILES ${headers} DESTINATION include/J) |
@ -0,0 +1,38 @@ |
|||||
|
//
|
||||
|
// NSGLWrapper.hpp
|
||||
|
// XcodeOpenCL
|
||||
|
//
|
||||
|
// Created by Joshua Moerman on 13/04/14.
|
||||
|
//
|
||||
|
//
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <memory> |
||||
|
#include <functional> |
||||
|
|
||||
|
#include <OpenGL/OpenGL.h> |
||||
|
#include <CoreVideo/CVDisplayLink.h> |
||||
|
|
||||
|
struct GLContext { |
||||
|
GLContext(); |
||||
|
~GLContext(); |
||||
|
|
||||
|
void set_as_current_context() const; |
||||
|
void lock(); |
||||
|
void unlock(); |
||||
|
|
||||
|
CGLContextObj ctx{}; |
||||
|
CGLPixelFormatObj pix{}; |
||||
|
}; |
||||
|
|
||||
|
struct CVDisplayLinky { |
||||
|
CVDisplayLinky(GLContext const &); |
||||
|
~CVDisplayLinky(); |
||||
|
|
||||
|
void start(); |
||||
|
void stop(); |
||||
|
|
||||
|
std::function<void()> callback; |
||||
|
CVDisplayLinkRef displayLink; |
||||
|
}; |
@ -0,0 +1,95 @@ |
|||||
|
// |
||||
|
// NSGLWrapper.mm |
||||
|
// XcodeOpenCL |
||||
|
// |
||||
|
// Created by Joshua Moerman on 13/04/14. |
||||
|
// |
||||
|
// |
||||
|
|
||||
|
#include "NSGLWrapper.hpp" |
||||
|
|
||||
|
#include <iostream> |
||||
|
#include <stdexcept> |
||||
|
|
||||
|
#import <Cocoa/Cocoa.h> |
||||
|
#import <OpenGL/OpenGL.h> |
||||
|
|
||||
|
static void check_cgl(CGLError e) { |
||||
|
if(e != kCGLNoError){ |
||||
|
throw std::runtime_error(CGLErrorString(e)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
GLContext::GLContext() { |
||||
|
GLint npix{}; |
||||
|
const CGLPixelFormatAttribute attributes[]{ |
||||
|
kCGLPFADoubleBuffer, |
||||
|
kCGLPFADepthSize, static_cast<CGLPixelFormatAttribute>(24), |
||||
|
kCGLPFAOpenGLProfile, static_cast<CGLPixelFormatAttribute>(kCGLOGLPVersion_3_2_Core), |
||||
|
static_cast<CGLPixelFormatAttribute>(0) |
||||
|
}; |
||||
|
|
||||
|
check_cgl(CGLChoosePixelFormat(attributes, &pix, &npix)); |
||||
|
check_cgl(CGLCreateContext(pix, 0, &ctx)); |
||||
|
|
||||
|
GLint swap_interval = 1; |
||||
|
check_cgl(CGLSetParameter(ctx, kCGLCPSwapInterval, &swap_interval)); |
||||
|
|
||||
|
set_as_current_context(); |
||||
|
} |
||||
|
|
||||
|
GLContext::~GLContext() { |
||||
|
CGLReleaseContext(ctx); |
||||
|
CGLReleasePixelFormat(pix); |
||||
|
} |
||||
|
|
||||
|
void GLContext::set_as_current_context() const { |
||||
|
check_cgl(CGLSetCurrentContext(ctx)); |
||||
|
} |
||||
|
|
||||
|
void GLContext::lock() { |
||||
|
check_cgl(CGLLockContext(ctx)); |
||||
|
} |
||||
|
|
||||
|
void GLContext::unlock() { |
||||
|
check_cgl(CGLUnlockContext(ctx)); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
static void check_cv(CVReturn r) { |
||||
|
if(r != kCVReturnSuccess){ |
||||
|
throw std::runtime_error("Some CV display link issue"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static CVReturn CVDisplayLinkyInvokeCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext) { |
||||
|
if(!displayLinkContext){ |
||||
|
throw std::runtime_error("There is no context in the display link"); |
||||
|
} |
||||
|
auto& foo = *(CVDisplayLinky*)displayLinkContext; |
||||
|
if(foo.callback){ |
||||
|
foo.callback(); |
||||
|
} |
||||
|
return kCVReturnSuccess; |
||||
|
} |
||||
|
|
||||
|
CVDisplayLinky::CVDisplayLinky(GLContext const & c) { |
||||
|
check_cv(CVDisplayLinkCreateWithActiveCGDisplays(&displayLink)); |
||||
|
check_cv(CVDisplayLinkSetOutputCallback(displayLink, &CVDisplayLinkyInvokeCallback, this)); |
||||
|
check_cv(CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(displayLink, c.ctx, c.pix)); |
||||
|
} |
||||
|
|
||||
|
CVDisplayLinky::~CVDisplayLinky() { |
||||
|
stop(); |
||||
|
CVDisplayLinkRelease(displayLink); |
||||
|
} |
||||
|
|
||||
|
void CVDisplayLinky::start() { |
||||
|
check_cv(CVDisplayLinkStart(displayLink)); |
||||
|
} |
||||
|
|
||||
|
void CVDisplayLinky::stop() { |
||||
|
check_cv(CVDisplayLinkStop(displayLink)); |
||||
|
} |
||||
|
|
@ -0,0 +1,41 @@ |
|||||
|
//
|
||||
|
// NSWrapper.hpp
|
||||
|
// XcodeOpenCL
|
||||
|
//
|
||||
|
// Created by Joshua Moerman on 13/04/14.
|
||||
|
//
|
||||
|
//
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <memory> |
||||
|
#include <functional> |
||||
|
|
||||
|
#include <CoreGraphics/CoreGraphics.h> |
||||
|
|
||||
|
struct GLContext; |
||||
|
struct ContextParameters{ |
||||
|
GLContext & context; |
||||
|
}; |
||||
|
|
||||
|
struct GLViewFunctionality { |
||||
|
// All three will only be called with the current gl context already set
|
||||
|
// May be called from different threads
|
||||
|
std::function<void(ContextParameters)> initialize_callback; |
||||
|
std::function<void(ContextParameters)> draw_callback; |
||||
|
std::function<void(ContextParameters, CGFloat, CGFloat)> resize_callback; |
||||
|
}; |
||||
|
|
||||
|
// returns a GLViewFunctionality with only its draw function set.
|
||||
|
GLViewFunctionality simple_draw(std::function<void()> f); |
||||
|
|
||||
|
struct NSAppWrapper { |
||||
|
NSAppWrapper(); |
||||
|
~NSAppWrapper(); |
||||
|
|
||||
|
void run(); |
||||
|
void create_window(GLViewFunctionality const &); |
||||
|
|
||||
|
std::unique_ptr<struct NSAppWrapperImpl> impl; |
||||
|
}; |
||||
|
|
@ -0,0 +1,216 @@ |
|||||
|
// |
||||
|
// NSWrapper.mm |
||||
|
// XcodeOpenCL |
||||
|
// |
||||
|
// Created by Joshua Moerman on 13/04/14. |
||||
|
// |
||||
|
// |
||||
|
|
||||
|
#include "NSWrapper.hpp" |
||||
|
#include "NSGLWrapper.hpp" |
||||
|
|
||||
|
#include <vector> |
||||
|
#import <Cocoa/Cocoa.h> |
||||
|
#import <GLKit/GLKit.h> |
||||
|
|
||||
|
|
||||
|
GLViewFunctionality simple_draw(std::function<void()> 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 |
||||
|
|
||||
|
|
||||
|
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; |
||||
|
[window center]; |
||||
|
[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 run() { |
||||
|
[app run]; |
||||
|
} |
||||
|
|
||||
|
NSApplication * app; |
||||
|
std::vector<NSWindow *> 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); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
/* |
||||
|
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<GLContext> context; |
||||
|
std::unique_ptr<CVDisplayLinky> linky; |
||||
|
} |
||||
|
|
||||
|
@synthesize functionality; |
||||
|
|
||||
|
- (id) initWithFrame:(NSRect)frameRect { |
||||
|
if(self = [super initWithFrame:frameRect]){ |
||||
|
context = std::unique_ptr<GLContext>(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}); |
||||
|
} |
||||
|
|
||||
|
// Create a display link capable of being used with all active displays |
||||
|
linky = std::unique_ptr<CVDisplayLinky>(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}, 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]; |
||||
|
|
||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0); |
||||
|
glViewport(0, 0, (int)viewRectPixels.size.width, (int)viewRectPixels.size.height); |
||||
|
|
||||
|
if(functionality.draw_callback){ |
||||
|
functionality.draw_callback({*context}); |
||||
|
} |
||||
|
|
||||
|
CGLFlushDrawable(context->ctx); |
||||
|
context->unlock(); |
||||
|
} |
||||
|
|
||||
|
@end |
||||
|
|
Reference in new issue