Search code examples
iosopengl-esscreenshot

glReadPixels returns null with multi-sampling


I have same problem with this one, however with those tips i sitll can not get the data from glReadPixels.

I paste my sources code, my code is almost same with the previous one.And I set GL_READ_FRAMEBUFFER_APPLE before snapshot, but the data returns null.

Create My Frame Buffer

- (void)createFrameBuffer {
glGenRenderbuffers(1, &colorRenderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderBuffer);
[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];

GLint backingWidth;
GLint backingHeight;
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight);

glGenFramebuffers(1, &defaultFrameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, defaultFrameBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,GL_RENDERBUFFER, colorRenderBuffer);

glGenFramebuffers(1, &sampleFramebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, sampleFramebuffer);

glGenRenderbuffers(1, &sampleColorRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, sampleColorRenderbuffer);
glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER, 4, GL_RGBA8_OES, backingWidth, backingHeight);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, sampleColorRenderbuffer);

glGenRenderbuffers(1, &sampleDepthRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, sampleDepthRenderbuffer);
glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT16, backingWidth, backingHeight);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, sampleDepthRenderbuffer);

if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE){
    NSAssert(true, @"buffer is not complete");
    NSLog(@"Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
}
}

Render Function

- (void)render {
FC_PERFORMANCE_START();
if ([EAGLContext currentContext] !=_context) {
    [EAGLContext setCurrentContext:_context];
}
[self destoryFrameBuffer];
[self createFrameBuffer];
[self.director setup:self.frame.size contentScale:self.contentScaleFactor backgroudColor:self.backgroundColor];
[self.director mainloop];
glBindFramebuffer(GL_READ_FRAMEBUFFER_APPLE, sampleFramebuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER_APPLE, defaultFrameBuffer);
glResolveMultisampleFramebufferAPPLE();
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderBuffer);
const GLenum discards[]  = {GL_COLOR_ATTACHMENT0,GL_DEPTH_ATTACHMENT};
glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER_APPLE,2,discards);
glBindFramebuffer(GL_FRAMEBUFFER, sampleFramebuffer);
[_context presentRenderbuffer:GL_RENDERBUFFER];
FC_PERFORMANCE_END("FCViewOpenGL Render");

}

Screen Shot Function

- (nullable UIImage*)snapShot{
__block UIImage *ret = nil;
dispatch_sync(dispatch_get_main_queue(), ^{
    glBindFramebuffer(GL_READ_FRAMEBUFFER_APPLE, defaultFrameBuffer);

    GLint backingWidth;
    GLint backingHeight;
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth);
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight);

    NSInteger x = 0, y = 0, width2 = backingWidth, height2 = backingHeight;
    NSInteger dataLength = width2 * height2 * 4;
    GLubyte *data = (GLubyte*)malloc(dataLength * sizeof(GLubyte));

    CHECK_GL_ERROR_DEBUG();
    glReadPixels((GLint)x, (GLint)y, (GLsizei)width2, (GLsizei)height2, GL_RGBA, GL_UNSIGNED_BYTE, data);
    GLenum attachments[] = {GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT   };
    glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER_APPLE, 2, attachments);
    CHECK_GL_ERROR_DEBUG();

    CGDataProviderRef ref = CGDataProviderCreateWithData(NULL, data, dataLength, NULL);
    CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
    CGImageRef iref = CGImageCreate(width2, height2, 8, 32, width2 * 4, colorspace, kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast,
                                    ref, NULL, true, kCGRenderingIntentDefault);

    NSInteger widthInPoints, heightInPoints;
    if (NULL != UIGraphicsBeginImageContextWithOptions) {
        CGFloat scale = self.contentScaleFactor;
        widthInPoints = width2 / scale;
        heightInPoints = height2 / scale;
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(widthInPoints, heightInPoints), NO, scale);
    }
    else {
        widthInPoints = width2;
        heightInPoints = height2;
        UIGraphicsBeginImageContext(CGSizeMake(widthInPoints, heightInPoints));
    }

    CGContextRef cgcontext = UIGraphicsGetCurrentContext();

    CGContextSetBlendMode(cgcontext, kCGBlendModeCopy);
    CGContextDrawImage(cgcontext, CGRectMake(0.0, 0.0, widthInPoints, heightInPoints), iref);

    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    free(data);
    CFRelease(ref);
    CFRelease(colorspace);
    CGImageRelease(iref);
    ret = image;
});
return ret;
}

Can anyone please give me some help?


Solution

  • See OpenGL ES 3.2 Specification; 16.1.2 ReadPixels; page 406

    An INVALID_OPERATION error is generated if the value of READ_FRAMEBUFFER_BINDING (see section 9) is non-zero, the read framebuffer is framebuffer complete, and the effective value of SAMPLE_BUFFERS for the read framebuffer is one.

    ....

    If the read framebuffer is multisampled (its effective value of SAMPLE_BUFFERS is one) ...


    See also OpenGL-Refpages; OpenGL ES 3.0; glReadPixels:

    GL_INVALID_OPERATION is generated if GL_READ_FRAMEBUFFER_BINDING is non-zero, the read framebuffer is complete, and the value of GL_SAMPLE_BUFFERS for the read framebuffer is greater than zero.


    This means, that you can't use glReadPixels an a multisample framebuffer. You will gain a GL_INVALID_OPERATION operation error. Use glGetError after glReadPixels to get the error information.


    To solve the issue you have to go the way over a conventional framebuffer. Do the following steps:

    • Create a 2nd framebuffer object, which is conventional (not multisampled).
    • Bind the multisample framebuffer for reading (GL_READ_FRAMEBUFFER_APPLE).
    • Bind the conventional framebuffer for drawing (GL_DRAW_FRAMEBUFFER_APPLE).
    • Use glBlitFramebuffer copy to copy the pixels from the multisample (read) framebuffer to the conventional (draw) framebuffer.
    • After copying, bind the conventional framebuffer for reading (GL_READ_FRAMEBUFFER_APPLE).
    • Finally use glReadPixels to read from the conventional framebuffer.


    As an alternative, of course you can read the pixels directly from the default framebuffer.