18

The OpenGL ES rendering loop is placed on a separate thread in my iphone application. Everything goes fine except that the EAGLContext's presentRenderbuffer method fails. The result is a blank white screen. When the same code is run on the main thread, presentRenderbuffer succeeds and the graphics is properly shown. What is the correct way of doing OpenGL on a separate thread?

genpfault
  • 51,148
  • 11
  • 85
  • 139
Klaus Kilhe
  • 183
  • 1
  • 1
  • 9

3 Answers3

15

You need to create an EAGLSharegroup.

Check out this thread on sharing OpenGL contexts between threads.

UPDATE
Previous to iOS5 I shared OpenGL contexts between threads to allow asynchronous loading of textures from disk. But iOS5's CVOpenGLESTextureCaches essentially make texture uploads free so I don't need shareGroups anymore and my code is simpler and faster.

Rhythmic Fistman
  • 34,352
  • 5
  • 87
  • 159
  • 1
    What do you mean by "make texture uploads free" ? I tried calling CVOpenGLESTextureCacheCreateTextureFromImage() with a result from CVPixelBufferCreateWithBytes() and it took just as long as uploading the texture with glTexImage2D(). – MoDJ Aug 01 '13 at 16:48
  • glTexImage2D does the equivalent of memcpy into a texture while TextureFromImage points the texture at your image - no copy needed. You can save a lot of memory bandwidth. Timing alone will not reveal much as much of GL is asynchronous. You need to look at GPU/CPU usage and frame rate. – Rhythmic Fistman Aug 03 '13 at 05:38
  • See http://stackoverflow.com/questions/12813442/cvopenglestexturecache-vs-gltexsubimage2d-on-ios, it does not seem to work the way you describe when the user passes in a buffer. In fact, I am seeing a whole lot of CPU time spent in glTexImage2D in this execution path. – MoDJ Aug 03 '13 at 08:36
  • Maybe you're doing something that gets you the slow path. Start a new question and post some code. – Rhythmic Fistman Aug 06 '13 at 05:30
  • I actually just found out how to get on the "fast path" while going through my code again today (there was an extra framebuffer copy that was slowing things way down). What appears to be going on is that the camera and h264 decoders write into memory that can be used directly by the GPU. When I create the pixel buffer and write to the texture cache that results in the slow path. This API is a little confusing, but using it properly resulted in 50% CPU use going down to 10% for a full screen movie render, so that is quite impressive. Thanks for your input. – MoDJ Aug 06 '13 at 07:07
  • 1
    iOS's GPU&CPU memory are one and the same which glTexImage2D can't take advantage of because it has no idea of the scope of your pixels. Hence texture caches. Proper usage is hard to pin down as there's not much documentation. – Rhythmic Fistman Aug 06 '13 at 12:49
13

Thanks, Fistman. I made it work and got the performance gain that I expected from using a separate thread. EAGLSharegroup solved the problem.

I created the context for the second thread as described here.

Here is the boilerplate code:


#import <UIKit/UIKit.h>
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>


struct OpenGLContext
{
    GLint Width;
    GLint Height;

    GLuint RenderBuffer;
    GLuint FrameBuffer;
    GLuint DepthBuffer;

    UIView* View;
    EAGLContext* MainContext;
    EAGLContext* WorkingContext;
    EAGLSharegroup* Sharegroup; 

    // Trivial constructor.
    OpenGLContext();

    // Call on the main thread before use.
    // I call it in layoutSubviews.
    // view must not be nil.
    void MainInit(UIView* view);

    // Call on the rendering thread before use, but
    // after MainInit();
    void InitOnSecondaryThread();   

    // Call before any OpenGL ES calls, at the
    // beginning of each frame.
    void PrepareBuffers();

    // Present frame. Call at the end of each
    // frame.
    void SwapBuffers();
};

OpenGLContext::OpenGLContext()
{
    Width = 0;
    Height = 0;

    RenderBuffer = 0;
    FrameBuffer = 0;
    DepthBuffer = 0;

    View = 0;
    MainContext = 0;
    WorkingContext = 0;
    Sharegroup = 0; 
}

void OpenGLContext::InitOnSecondaryThread()
{
    EAGLSharegroup* group = MainContext.sharegroup;
    if (!group)
    {
        NSLog(@"Could not get sharegroup from the main context");
    }
    WorkingContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1 sharegroup:group];
    if (!WorkingContext || ![EAGLContext setCurrentContext:WorkingContext]) {
        NSLog(@"Could not create WorkingContext");
    }
}

void OpenGLContext::MainInit(UIView* view)
{
    View = view;
    MainContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];

    if (!MainContext || ![EAGLContext setCurrentContext:MainContext]) {
        NSLog(@"Could not create EAGLContext"); 
        return;
    }
    NSLog(@"Main EAGLContext created");     

    glGenFramebuffersOES(1, &FrameBuffer);
    glGenRenderbuffersOES(1, &RenderBuffer);
    glGenRenderbuffersOES(1, &DepthBuffer);

    glBindFramebufferOES(GL_FRAMEBUFFER_OES, FrameBuffer);
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, RenderBuffer);

    if (![MainContext renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)View.layer])
    {
        NSLog(@"error calling MainContext renderbufferStorage");
        return;
    }

    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, RenderBuffer);

    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &Width);
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &Height);

    glBindRenderbufferOES(GL_RENDERBUFFER_OES, DepthBuffer);
    glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, Width, Height);
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, DepthBuffer);

    glFlush();

    if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
        NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
    }

    WorkingContext = MainContext;
}

void OpenGLContext::PrepareBuffers()
{   
    if (!WorkingContext || [EAGLContext setCurrentContext:WorkingContext] == NO)
    {
        NSLog(@"PrepareBuffers: [EAGLContext setCurrentContext:WorkingContext] failed");
        return;
    }
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, FrameBuffer);  
}

void OpenGLContext::SwapBuffers()
{
    if (!WorkingContext || [EAGLContext setCurrentContext:WorkingContext] == NO)
    {
        NSLog(@"SwapBuffers: [EAGLContext setCurrentContext:WorkingContext] failed");
        return;
    }

    glBindRenderbufferOES(GL_RENDERBUFFER_OES, RenderBuffer);

    if([WorkingContext presentRenderbuffer:GL_RENDERBUFFER_OES] == NO)
    {
        NSLog(@"SwapBuffers: [WorkingContext presentRenderbuffer:GL_RENDERBUFFER_OES] failed");
    }   
}


Klaus Kilhe
  • 183
  • 1
  • 1
  • 9
2

You shouldn't render the context on a different thread. Instead, do all the calculations on a different thread, and then ensure the rendering occurs on the main display thread.

AlBlue
  • 23,254
  • 14
  • 71
  • 91