Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CARenderer never produces output

I've been stuck for hours trying to render a Core Animation layer tree into an OpenGL context using CARenderer. The OpenGL context is currently provided by a NSOpenGLView subclass set up in Interface Builder, with settings on default.

Here's how I set up the CALayers in my example:

l1 = [[CALayer layer] retain];        // l1 is an instance variable
l1.bounds = CGRectMake(0, 0, 100, 100);
l1.backgroundColor = CGColorCreateGenericRGB(1, 1, 0, 1);
CALayer* l2 = [CALayer layer];
l2.bounds = CGRectMake(0, 0, 20, 20);
l2.backgroundColor = CGColorCreateGenericRGB(1, 0, 0, 1);
l2.position = CGPointMake(50, 50);
[l1 addSublayer:l2];

If I add these to a regular NSView, they show up just fine.

Here's the drawing code from my NSOpenGLView subclass:

- (void) drawRect:(NSRect)dirtyRect
{
    NSRect frame = [self frame];

    // set up context according to CARenderer.h instructions
    glViewport(0, 0, frame.size.width, frame.size.height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, frame.size.width, 0, frame.size.height, -1, 1);

    glClearColor(0, 0, 0, 0);
    glClear(GL_COLOR_BUFFER_BIT);

    if (l1)
    {
        CARenderer* renderer = [CARenderer rendererWithCGLContext:[[self openGLContext] CGLContextObj] options:nil];
        renderer.layer = l1;
        renderer.bounds = NSRectToCGRect(frame);
        [renderer beginFrameAtTime:0.0 timeStamp:NULL];
        [renderer addUpdateRect:renderer.bounds];
        [renderer render];
        [renderer endFrame];
    }

    glFlush();
}

I verified that the view is redrawn after the layers have been created, and the view itself seems to work okay -- OpenGL commands I issue show correctly. The CARenderer does come alive (in fact, if l1 is already attached to another context at the time this code runs, it will dutifully complain). There's just no visible output, ever.

Other things I've tried: wrapping the CARenderer code in a CATransaction, fiddling with the NSOpenGLView setup options in Interface Builder, rendering the whole affair into a manually created OpenGL context offscreen and saving that into an image file... all to the same effect: native OpenGL commands show fine, CARenderer shows nothing.

I suspect that there's something wrong with the way my OpenGL context is set up, or that I might even somehow be rendering outside of my viewport... probably some really stupid minor detail. Unfortunately, both documentation and sample code for CARenderer on the web is somewhat sparse, and while I'm fairly experienced with Core Animation, my OpenGL knowledge is decidedly limited.

So right now, I'm seriously stumped and completely out of ideas, so any pointers are greatly appreciated!

Cheers

like image 891
ig2r Avatar asked Aug 07 '10 09:08

ig2r


1 Answers

Oh wow. So in case anyone is interested, I got this "mostly-working" in the meantime. Here's the relevant code:

- (void) drawRect:(NSRect)dirtyRect
{
    NSRect viewBounds = [self bounds];

    // set up context according to CARenderer.h instructions
    glViewport(0, 0, viewBounds.size.width, viewBounds.size.height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, viewBounds.size.width, 0, viewBounds.size.height, -1, 1);

    glClearColor(0, 0, 0, 0);
    glClear(GL_COLOR_BUFFER_BIT);

    [renderer beginFrameAtTime:0.0 timeStamp:NULL];
    [renderer addUpdateRect:renderer.bounds];
    [renderer render];
    [renderer endFrame];

    glFlush();
}

- (void) prepareOpenGL
{
    renderer = [[CARenderer rendererWithCGLContext:[[self openGLContext] CGLContextObj] options:nil] retain];

    [CATransaction begin];
    CALayer* l = [CALayer layer];
    l.bounds = CGRectMake(0, 0, 100, 100);
    l.backgroundColor = CGColorCreateGenericRGB(1, 1, 0, 1);

    renderer.layer = l;
    renderer.bounds = l.bounds;
    [CATransaction commit];
}

Note the CATransaction block around the layer/renderer creation code. Without this, the layer would either not show up at all (as was my initial problem), or only be rendered after a slight delay, i.e., when the view gets redrawn after some time has passed. As to why this is necessary I'm still unsure, and I'd be more than happy if someone could enlighten me on this point.

Obviously this solution might still be somehow faulty, but I decided to leave it here for the time being considering the lack of sample code for CARenderer.

EDIT: As I've later found out, one rather non-obvious mistake with the solution above is that 0.0 is passed to the beginFrameAtTime method call; instead, this should be the value from CACurrentMediaTime. This will remove the need for the CATransaction blocks, along with correcting a few other bugs along the way. Just as a heads-up for anyone who might stumble upon this answer.

like image 158
ig2r Avatar answered Sep 25 '22 14:09

ig2r