5
\$\begingroup\$

I'm working on a Cocoa project that uses OpenGL. I'm trying to keep things easily cross-platformable for later (which is the primary reason for my GL singleton; I hope to implement Linux versions of the IRGL methods that currently use NSOpenGL...). When doing Cocoa & OpenGL stuff, though, I'm never sure that I'm doing things the "right" way. What could I be doing better here? I've snipped out methods unrelated to GL or drawing.

Here is my NSView subclass:

//
//  IRLevelViewView
//  Iris
//
//  Created by Andy Van Ness on 3/15/11.
//  Copyright 2011 Andy Van Ness. All rights reserved.
//

#import "IRGameEditView.h"
#import <OpenGL/gl.h>
#import "IRGL.h"
//snip

@implementation IRGameEditView

- (id)initWithFrame:(NSRect)frameRect
{
    self = [super initWithFrame:frameRect];
    if (self != nil)
    {
        //snip

        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(surfaceNeedsUpdate:) name:NSViewGlobalFrameDidChangeNotification object:self];
    }
    return self;
}

- (void)initDisplayLink
{
    GLint swapInt = 1;
    [[[IRGL gl] glContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; 

    CVDisplayLinkCreateWithActiveCGDisplays(&displayLink);
    CVDisplayLinkSetOutputCallback(displayLink, &MyDisplayLinkCallback, self);

    CGLContextObj cglContext = [[[IRGL gl] glContext] CGLContextObj];
    CGLPixelFormatObj cglPixelFormat = [[NSOpenGLView defaultPixelFormat] CGLPixelFormatObj];
    CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(displayLink, cglContext, cglPixelFormat);

    CVDisplayLinkStart(displayLink);
}

- (void)initGL
{   
    glDisable(GL_DEPTH_TEST);
    glDepthMask(GL_FALSE);

    glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);

    [[IRGL gl] initShaders];
    [self reshape];

    [self setReadyForGL:YES];
}

static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
{
    NSAutoreleasePool* pool = [NSAutoreleasePool new];

    [(id)displayLinkContext setNeedsDisplay:YES];

    [pool drain];

    return kCVReturnSuccess;
}

- (void)lockFocus
{
    [super lockFocus];
    [[IRGL gl] activateContextForView:self];
}

- (void)drawRect:(NSRect)dirtyRect
{
    if (![self isReadyForGL]) [self initGL];

    glClearColor(0.0, 0.0, 0.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    [[self clock] updateTime];
    CGFloat animator = (CGFloat)([[self clock] time] % 100000)/100000.0;

    [[IRGL gl] setCurrentShader:IRIrisShader];
    [[IRGL gl] setUniformGLVariable:@"animator" toFloat:animator];
    [[IRGL gl] setUniformGLVariable:@"cameraCenter" toPoint:[[self camera] center]];
    [[IRGL gl] setUniformGLVariable:@"cameraTileAspectRatio" toFloat:[[self camera] tileAspectRatio]];
    [[IRGL gl] setUniformGLVariable:@"cameraZoom" toFloat:[[self camera] zoom]];
    [[IRGL gl] setUniformGLVariable:@"cameraSize" toSize:[[self camera] size]];
    [[self camera] loadCameraMatrix];

    @synchronized([[self level] tileMap])
    {
        for (IRTileStack* currentStack in [[[self level] tileMap] tileStacksInIrisRect:[[self camera] irisFrame]])
        {
            [currentStack draw];
        }
    }

    glFlush();
}

- (void)reshape
{
    glViewport(0, 0, (GLsizei)[self frame].size.width, (GLsizei)[self frame].size.height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, [self frame].size.width, [self frame].size.height, 0, -2, 0);

    glMatrixMode(GL_MODELVIEW);
}

- (BOOL)isOpaque  { return YES; }
- (BOOL)isFlipped { return YES; }

- (void)setFrame:(NSRect)frameRect
{
    [super setFrame:frameRect];

    [[IRGL gl] activateContextForView:self];
    [self reshape];

    [self setNeedsDisplay:YES];
}

@synthesize automaticallyRedraws;
- (void)setAutomaticallyRedraws:(BOOL)newAutomaticallyRedraws
{
    if (newAutomaticallyRedraws != automaticallyRedraws)
    {
        automaticallyRedraws = newAutomaticallyRedraws;
        if (automaticallyRedraws)
        {
            [self initDisplayLink];
        }
        else
        {
            CVDisplayLinkRelease(displayLink);
        }
    }
}

- (void)surfaceNeedsUpdate:(NSNotification*)notification
{
    [[[IRGL gl] glContext] update];
}

//snip

@end

And here is a singleton GL class:

//
//  IRGL.m
//  Iris
//
//  Created by Andy Van Ness on 8/9/11.
//  Copyright 2011 Andy Van Ness. All rights reserved.
//

#import "IRGL.h"

@implementation IRGL

//snip

@synthesize currentShader;
- (void)setCurrentShader:(NSString *)newCurrentShader
{
    if (![currentShader isEqualToString:newCurrentShader])
    {
        [currentShader release];
        currentShader = [newCurrentShader copy];
    }

    if (!newCurrentShader)
    {
        glUseProgramObjectARB(0);
    }
    else
    {
        glUseProgramObjectARB([self shaderForKey:currentShader]);
    }
}

- (void)initShaders
{
    for (NSString* currentID in [NSArray arrayWithObjects:IRAllShaders])
    {
        [self addShaderWithID:currentID];
    }

    [self setInitialized:YES];
}

- (void)addShaderWithID:(NSString*)key
{
    NSString* shaderSource = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:key ofType:@"fs"] encoding:NSUTF8StringEncoding error:nil];

    if ([shaderSource length] > 0)
    {
        GLhandleARB shaderProgram = glCreateProgramObjectARB();

        GLchar const* source = [shaderSource UTF8String];
        GLint const length = [shaderSource length];
        GLhandleARB shader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
        glShaderSourceARB(shader, 1, &source, &length);

        glCompileShaderARB(shader);
        glAttachObjectARB(shaderProgram, shader);

        glLinkProgramARB(shaderProgram);
        MOGLShaderInfoLog(shaderProgram);

        [[self shaders] setObject:[NSValue valueWithPointer:shaderProgram] forKey:key];
        [[self uniformVariablesByShader] setObject:[NSMutableDictionary dictionary] forKey:key];
    }
    else
    {
        MOLogWarning(@"Shader not found.");
    }
}

- (GLhandleARB)shaderForKey:(NSString *)shaderKey
{
    if (shaderKey == nil) return 0;

    NSValue* shaderValue = [[self shaders] objectForKey:shaderKey];

    if (!shaderValue) MOLogError(@"Shader %@ not found.",shaderKey);

    return [shaderValue pointerValue];
}

- (GLint)uniformLocationForName:(NSString*)varName
{
    NSNumber* uniformLocation = [[[self uniformVariablesByShader] objectForKey:[self currentShader]] objectForKey:varName];

    if (!uniformLocation)
    {
        GLint var = glGetUniformLocationARB([self shaderForKey:[self currentShader]],[varName UTF8String]);
        uniformLocation = [NSNumber numberWithInteger:var];
        MOGLError();
        if (var == -1)
        {
            MOLogError(@"Uniform %@ could not be found.",varName);
        }
        else
        {
            [[[self uniformVariablesByShader] objectForKey:[self currentShader]] setObject:uniformLocation forKey:varName]; 
        }
    }

    return (GLint)[uniformLocation integerValue];
}

- (void)setUniformGLVariable:(NSString*)varName toFloat:(CGFloat)varValue
{
    if ([self isInitialized])
    {
        glUniform1f([self uniformLocationForName:varName], varValue);
        MOGLError();
    }
}

//snip; bunches more methods like that one

- (void)activateContext
{
    if (![self glContext])
    {
        NSOpenGLContext* newContext = [[NSOpenGLContext alloc] initWithFormat:[NSOpenGLView defaultPixelFormat] shareContext:nil];
        [self setGLContext:newContext];
        [newContext release];
        MOGLError();
    }

    [[self glContext] makeCurrentContext];
    MOGLError();
}

- (void)activateContextForView:(NSView*)view
{
    [self activateContext];
    [[self glContext] setView:view];
}

@end
\$\endgroup\$
3
\$\begingroup\$
  1. CVDisplayLink is asynchronous and in general AppKit is not threadsafe.

  2. Using setNeedsDisplay, which gets you a refresh at some point in the future, in the draw callback misses the point of display links, which is drawing something in sync with the screen refresh.

  3. You can drop the ARB, right? It's almost 2012.

\$\endgroup\$
  • \$\begingroup\$ Great points--what do I do about #1? If I -lockFocus on my NSView directly and call my GL right in the callback, is that safe? \$\endgroup\$ – andyvn22 Apr 26 '12 at 2:05
  • \$\begingroup\$ I don't think lockFocus is what you want. Try CGLLockContext. See the the GLFullScreen sample code. It correctly uses an OpenGLView with a CVDisplayLink: developer.apple.com/library/mac/#samplecode/GLFullScreen/… \$\endgroup\$ – Rhythmic Fistman Apr 26 '12 at 16:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.