Ho avuto un problema simile e ho trovato una soluzione molto più elegante. Fondamentalmente, si sottoclasse CAEAGLLayer e si aggiungerà la propria implementazione di renderInContext che richiede semplicemente alla vista OpenGL di eseguire il rendering dei contenuti usando glReadPixels. Il bello è che ora puoi chiamare renderInContext su qualsiasi livello nella gerarchia, e il risultato è uno screenshot perfettamente composto e dall'aspetto perfetto che include le tue viste OpenGL!
nostro renderInContext nel CAEAGLLayer sottoclasse è:
- (void)renderInContext:(CGContextRef)ctx
{
[super renderInContext: ctx];
[self.delegate renderInContext: ctx];
}
Poi, nella vista OpenGL sostituiamo layerClass in modo che restituisca la nostra sottoclasse invece del CAEAGLLayer plain vanilla:
+ (Class)layerClass
{
return [MyCAEAGLLayer class];
}
Aggiungiamo un metodo nella vista per rendere effettivamente il contenuto della vista nel contesto. Si noti che questo codice DEVE essere eseguito dopo che la vista GL è stata sottoposta a rendering, ma prima di chiamare presentRenderbuffer
in modo che il buffer di rendering contenga il frame. In caso contrario, l'immagine risultante sarà probabilmente vuota (si potrebbe notare un comportamento diverso tra il dispositivo e il simulatore su questo particolare problema).
- (void) renderInContext: (CGContextRef) context
{
GLint backingWidth, backingHeight;
// Bind the color renderbuffer used to render the OpenGL ES view
// If your application only creates a single color renderbuffer which is already bound at this point,
// this call is redundant, but it is needed if you're dealing with multiple renderbuffers.
// Note, replace "_colorRenderbuffer" with the actual name of the renderbuffer object defined in your class.
glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
// Get the size of the backing CAEAGLLayer
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight);
NSInteger x = 0, y = 0, width = backingWidth, height = backingHeight;
NSInteger dataLength = width * height * 4;
GLubyte *data = (GLubyte*)malloc(dataLength * sizeof(GLubyte));
// Read pixel data from the framebuffer
glPixelStorei(GL_PACK_ALIGNMENT, 4);
glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);
// Create a CGImage with the pixel data
// If your OpenGL ES content is opaque, use kCGImageAlphaNoneSkipLast to ignore the alpha channel
// otherwise, use kCGImageAlphaPremultipliedLast
CGDataProviderRef ref = CGDataProviderCreateWithData(NULL, data, dataLength, NULL);
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGImageRef iref = CGImageCreate(width, height, 8, 32, width * 4, colorspace, kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast,
ref, NULL, true, kCGRenderingIntentDefault);
CGFloat scale = self.contentScaleFactor;
NSInteger widthInPoints, heightInPoints;
widthInPoints = width/scale;
heightInPoints = height/scale;
// UIKit coordinate system is upside down to GL/Quartz coordinate system
// Flip the CGImage by rendering it to the flipped bitmap context
// The size of the destination area is measured in POINTS
CGContextSetBlendMode(context, kCGBlendModeCopy);
CGContextDrawImage(context, CGRectMake(0.0, 0.0, widthInPoints, heightInPoints), iref);
// Clean up
free(data);
CFRelease(ref);
CFRelease(colorspace);
CGImageRelease(iref);
}
Infine, per catturare uno screenshot si usa renderInContext nella solita fasion. Ovviamente la bellezza è che non è necessario afferrare direttamente la vista OpenGL. È possibile afferrare uno dei superviews della vista OpenGL e ottenere uno screenshot composto che comprende la vista OpenGL insieme a qualsiasi altra cosa accanto ad essa o su di esso:
UIGraphicsBeginImageContextWithOptions(superviewToGrab.bounds.size, YES, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
[superviewToGrab.layer renderInContext: context]; // This recursively calls renderInContext on all the sublayers, including your OpenGL layer(s)
CGImageRef screenShot = UIGraphicsGetImageFromCurrentImageContext().CGImage;
UIGraphicsEndImageContext();
Sto provando il tuo approccio ma glReadPixels non sembra copiare dati nel mio buffer (la variabile "data" nell'esempio). Qualche idea sul perché questo potrebbe essere il caso? Il mio setup è un normale UIView il cui livello di supporto è un CAEAGLLayer. Quindi aggiungo UIImageView con un'immagine come figlio della vista con supporto GL. Quindi provo ad afferrare il contenuto di renderBuffer e il mio buffer è ancora tutti gli 0 come quando l'ho inizializzato con calloc. Ho anche provato a impostare il livello come memoria renderbuffer ma nulla. Se eseguo il normale renderingInContext, restituisco il contenuto come un'immagine. – SaldaVonSchwartz