Skip to content

Instantly share code, notes, and snippets.

@mstange
Created June 17, 2016 15:07
Show Gist options
  • Save mstange/7b91f9fabbbdda54673c551410193c8b to your computer and use it in GitHub Desktop.
Save mstange/7b91f9fabbbdda54673c551410193c8b to your computer and use it in GitHub Desktop.
OS X sample that creates and leaks an IOSurface, draws a bunch of circles into it, attaches it to a texture and presents it using an NSOpenGLContext
/**
* clang++ main.mm -framework Cocoa -framework OpenGL -framework IOSurface -o test && ./test
**/
#import <Cocoa/Cocoa.h>
#include <OpenGL/gl.h>
@interface TestView: NSView
{
NSOpenGLContext* mContext;
GLuint mProgramID;
GLuint mTexture;
GLuint mTextureUniform;
GLuint mPosAttribute;
GLuint mVertexbuffer;
}
@end
@implementation TestView
- (id)initWithFrame:(NSRect)aFrame
{
if (self = [super initWithFrame:aFrame]) {
NSOpenGLPixelFormatAttribute attribs[] = {
NSOpenGLPFAAccelerated,
NSOpenGLPFADoubleBuffer,
(NSOpenGLPixelFormatAttribute)nil
};
NSOpenGLPixelFormat* pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs];
mContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil];
GLint swapInt = 1;
[mContext setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
GLint opaque = 1;
[mContext setValues:&opaque forParameter:NSOpenGLCPSurfaceOpacity];
[mContext makeCurrentContext];
[self _initGL];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(_surfaceNeedsUpdate:)
name:NSViewGlobalFrameDidChangeNotification
object:self];
}
return self;
}
- (void)dealloc
{
[self _cleanupGL];
[mContext release];
[super dealloc];
}
static GLuint
CompileShaders(const char* vertexShader, const char* fragmentShader)
{
// Create the shaders
GLuint vertexShaderID = glCreateShader(GL_VERTEX_SHADER);
GLuint fragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
GLint result = GL_FALSE;
int infoLogLength;
// Compile Vertex Shader
glShaderSource(vertexShaderID, 1, &vertexShader , NULL);
glCompileShader(vertexShaderID);
// Check Vertex Shader
glGetShaderiv(vertexShaderID, GL_COMPILE_STATUS, &result);
glGetShaderiv(vertexShaderID, GL_INFO_LOG_LENGTH, &infoLogLength);
if (infoLogLength > 0) {
char* vertexShaderErrorMessage = new char[infoLogLength+1];
glGetShaderInfoLog(vertexShaderID, infoLogLength, NULL, vertexShaderErrorMessage);
printf("%s\n", vertexShaderErrorMessage);
delete[] vertexShaderErrorMessage;
}
// Compile Fragment Shader
glShaderSource(fragmentShaderID, 1, &fragmentShader , NULL);
glCompileShader(fragmentShaderID);
// Check Fragment Shader
glGetShaderiv(fragmentShaderID, GL_COMPILE_STATUS, &result);
glGetShaderiv(fragmentShaderID, GL_INFO_LOG_LENGTH, &infoLogLength);
if (infoLogLength > 0) {
char* fragmentShaderErrorMessage = new char[infoLogLength+1];
glGetShaderInfoLog(fragmentShaderID, infoLogLength, NULL, fragmentShaderErrorMessage);
printf("%s\n", fragmentShaderErrorMessage);
delete[] fragmentShaderErrorMessage;
}
// Link the program
GLuint programID = glCreateProgram();
glAttachShader(programID, vertexShaderID);
glAttachShader(programID, fragmentShaderID);
glLinkProgram(programID);
// Check the program
glGetProgramiv(programID, GL_LINK_STATUS, &result);
glGetProgramiv(programID, GL_INFO_LOG_LENGTH, &infoLogLength);
if (infoLogLength > 0) {
char* programErrorMessage = new char[infoLogLength+1];
glGetProgramInfoLog(programID, infoLogLength, NULL, programErrorMessage);
printf("%s\n", programErrorMessage);
delete[] programErrorMessage;
}
glDeleteShader(vertexShaderID);
glDeleteShader(fragmentShaderID);
return programID;
}
static GLuint
CreateTexture(NSSize size, void (^drawCallback)(CGContextRef ctx))
{
int width = size.width;
int height = size.height;
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
CGContextRef imgCtx = CGBitmapContextCreate(NULL, width, height, 8, width * 4,
rgb, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host);
CGColorSpaceRelease(rgb);
drawCallback(imgCtx);
GLuint texture = 0;
glActiveTexture(GL_TEXTURE0);
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture);
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, CGBitmapContextGetData(imgCtx));
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
return texture;
}
static GLuint
CreateTextureThroughIOSurface(NSSize size, CGLContextObj cglContextObj, void (^drawCallback)(CGContextRef ctx))
{
int width = size.width;
int height = size.height;
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:width], kIOSurfaceWidth,
[NSNumber numberWithInt:height], kIOSurfaceHeight,
[NSNumber numberWithInt:4], kIOSurfaceBytesPerElement,
// [NSNumber numberWithBool:YES], kIOSurfaceIsGlobal,
nil];
IOSurfaceRef surf = IOSurfaceCreate((CFDictionaryRef)dict);
IOSurfaceLock(surf, 0, NULL);
void* data = IOSurfaceGetBaseAddress(surf);
size_t stride = IOSurfaceGetBytesPerRow(surf);
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
CGContextRef imgCtx = CGBitmapContextCreate(data, width, height, 8, stride,
rgb, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host);
CGColorSpaceRelease(rgb);
drawCallback(imgCtx);
IOSurfaceUnlock(surf, 0, NULL);
GLuint texture = 0;
glActiveTexture(GL_TEXTURE0);
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture);
CGLTexImageIOSurface2D(cglContextObj, GL_TEXTURE_RECTANGLE_ARB, GL_RGBA, width, height,
GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, surf, 0);
// glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, CGBitmapContextGetData(imgCtx));
// XXX WE ARE LEAKING 'surf' HERE.
return texture;
}
- (void)_initGL
{
// Create and compile our GLSL program from the shaders.
mProgramID = CompileShaders(
"#version 120\n"
"// Input vertex data, different for all executions of this shader.\n"
"attribute vec2 aPos;\n"
"varying vec2 vPos;\n"
"void main(){\n"
" vPos = aPos;\n"
" gl_Position = vec4(aPos.x * 2.0 - 1.0, 1.0 - aPos.y * 2.0, 0.0, 1.0);\n"
"}\n",
"#version 120\n"
"varying vec2 vPos;\n"
"uniform sampler2DRect uSampler;\n"
"void main()\n"
"{\n"
" gl_FragColor = texture2DRect(uSampler, vPos * vec2(300, 200));\n" // <-- ATTENTION I HARDCODED THE TEXTURE SIZE HERE SORRY ABOUT THAT
"}\n");
// Create a texture
mTexture = CreateTextureThroughIOSurface(NSMakeSize(300, 200), [mContext CGLContextObj], ^(CGContextRef ctx) {
// Clear with white.
CGContextSetRGBFillColor(ctx, 1, 1, 1, 1);
CGContextFillRect(ctx, CGRectMake(0, 0, 300, 200));
// Draw a bunch of circles.
for (int i = 0; i < 30; i++) {
CGFloat radius = 20.0f + 4.0f * i;
CGFloat angle = i * 1.1;
CGPoint circleCenter = { 150 + radius * cos(angle), 100 + radius * sin(angle) };
CGFloat circleRadius = 10;
CGContextSetRGBFillColor(ctx, 0, i % 2, 1 - (i % 2), 1);
CGContextFillEllipseInRect(ctx, CGRectMake(circleCenter.x - circleRadius, circleCenter.y - circleRadius, circleRadius * 2, circleRadius * 2));
}
});
mTextureUniform = glGetUniformLocation(mProgramID, "uSampler");
// Get a handle for our buffers
mPosAttribute = glGetAttribLocation(mProgramID, "aPos");
static const GLfloat g_vertex_buffer_data[] = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
};
glGenBuffers(1, &mVertexbuffer);
glBindBuffer(GL_ARRAY_BUFFER, mVertexbuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);
}
- (void)_cleanupGL
{
glDeleteTextures(1, &mTexture);
glDeleteBuffers(1, &mVertexbuffer);
}
- (void)_surfaceNeedsUpdate:(NSNotification*)notification
{
[mContext update];
}
- (void)drawRect:(NSRect)aRect
{
[mContext setView:self];
[mContext makeCurrentContext];
NSSize backingSize = [self convertSizeToBacking:[self bounds].size];
GLdouble width = backingSize.width;
GLdouble height = backingSize.height;
glViewport(0, 0, width, height);
glClearColor(0.0, 1.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(mProgramID);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mTexture);
glUniform1i(mTextureUniform, 0);
glEnableVertexAttribArray(mPosAttribute);
glBindBuffer(GL_ARRAY_BUFFER, mVertexbuffer);
glVertexAttribPointer(
mPosAttribute, // The attribute we want to configure
2, // size
GL_FLOAT, // type
GL_FALSE, // normalized?
0, // stride
(void*)0 // array buffer offset
);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // 4 indices starting at 0 -> 2 triangles
glDisableVertexAttribArray(mPosAttribute);
[mContext flushBuffer];
}
- (BOOL)wantsBestResolutionOpenGLSurface
{
return YES;
}
@end
@interface TerminateOnClose : NSObject<NSWindowDelegate>
@end
@implementation TerminateOnClose
- (void)windowWillClose:(NSNotification*)notification
{
[NSApp terminate:self];
}
@end
int
main (int argc, char **argv)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
[NSApplication sharedApplication];
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
int style =
NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask;
NSRect contentRect = NSMakeRect(400, 300, 600, 400);
NSWindow* window = [[NSWindow alloc] initWithContentRect:contentRect
styleMask:style
backing:NSBackingStoreBuffered
defer:NO];
NSView* view = [[TestView alloc] initWithFrame:NSMakeRect(0, 0, contentRect.size.width, contentRect.size.height)];
[window setContentView:view];
[window setDelegate:[[TerminateOnClose alloc] autorelease]];
[NSApp activateIgnoringOtherApps:YES];
[window makeKeyAndOrderFront:window];
[NSApp run];
[pool release];
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment