made a c++ interface to the obj-c cocoa api
This commit is contained in:
commit
7c5ebc89b8
5 changed files with 410 additions and 0 deletions
20
CMakeLists.txt
Normal file
20
CMakeLists.txt
Normal file
|
@ -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)
|
38
NSGLWrapper.hpp
Normal file
38
NSGLWrapper.hpp
Normal file
|
@ -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;
|
||||
};
|
95
NSGLWrapper.mm
Normal file
95
NSGLWrapper.mm
Normal file
|
@ -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));
|
||||
}
|
||||
|
41
NSWrapper.hpp
Normal file
41
NSWrapper.hpp
Normal file
|
@ -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;
|
||||
};
|
||||
|
216
NSWrapper.mm
Normal file
216
NSWrapper.mm
Normal file
|
@ -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 a new issue