Reputation: 659
Currently, i have a map view object which use OpenGL ES 1.0 to render everything. Here is the view init code:
- (void)initGLES {
CAEAGLLayer *eaglLayer = (CAEAGLLayer*) self.layer;
eaglLayer.opaque = YES;
eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:FALSE], kEAGLDrawablePropertyRetainedBacking,
kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat,
nil];
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
if(!context || ![EAGLContext setCurrentContext:context] || ![self createFramebuffer])
return;
}
- (BOOL)createFramebuffer {
glGenFramebuffersOES(1, &viewFramebuffer);
glGenRenderbuffersOES(1, &viewRenderbuffer);
glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
[context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self.layer];
glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
// NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
return NO;
}
GLuint sampleColorRenderbuffer, sampleDepthRenderbuffer;
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)
NSLog(@"Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
return YES;
}
This work fine when we only have one map view in whole application. But when we create more than one map view, for example: We create first map view on main screen. Then we navigate to a sub screen. Here we create another map view. But when we navigate back to the main screen, then main screen's map view no longer working. It because we already create another context and bind all opengl function with new buffer there. So instead of drawing thing to old map view, everything got draw on newest one which no longer visible on screen.
Some people comment to us that we should either :
So i want to know what should i do in this case? How should i change how we change our init method for opengl here?
Thank you very much.
=================================================
UPDATE : Here my code for release resource on deallocate:
glDeleteRenderbuffersOES(1, &viewRenderbuffer);
glDeleteRenderbuffersOES(1, &viewFramebuffer);
glDeleteRenderbuffersOES(1, &sampleFramebuffer);
context = nil;
[EAGLContext setCurrentContext:nil];
Upvotes: 2
Views: 2526
Reputation: 16774
Does setting the current context not work for you? All you should do is call [EAGLContext setCurrentContext:context]
with the correct context once switching to a new view.
For instance if you created a custom view that will draw openGL content you will most likely create a frame buffer, a render buffer and a context. If you then create this view you can draw to it normally (as you already did). Then if you were to create another instance of this view and add it as a subview all you needed to do is:
In your case it is pretty much the same thing except it is even easier. Simply set the other context when you want to draw to the other view...
EDIT: the cleanup and the contexts
So here seems to be the big issue. As posted in the question edit once releasing the view object the buffers are deleted as well and the context is set to nil
([EAGLContext setCurrentContext:nil]
). This removes the context from the current thread and no openGL code executes anymore.
As in this case the issue itself is that we do not know what the current set context is when this method executes at all, it might be the context whose buffers were created on or "the other" context. So the quickest solution would be to modify it like this:
EAGLContext *lastContext = [EAGLContext currentContext];
EAGLContext *thisContext = context; // get the context of this view
[EAGLContext setCurrentContext:thisContext];
// do the deletion and cleanup
glDeleteRenderbuffersOES(1, &viewRenderbuffer);
glDeleteRenderbuffersOES(1, &viewFramebuffer);
glDeleteRenderbuffersOES(1, &sampleFramebuffer);
context = nil;
if(lastContext == thisContext)
{
// since there was no other context set just destroy this one
[EAGLContext setCurrentContext:nil];
}
else
{
// there was another context previously set so let us just set it back
[EAGLContext setCurrentContext:lastContext];
}
Try this code and see if it fixes your issue.
So to comment a bit on this particular issue in general:
As already mentioned you can use multiple views at a same time which contain their own contexts but are all on the same thread (since they are presentable that would be the main thread, you can use no other). The operations on this views must be serialized in one way or another but to use both at the same time you would need to set the current context to one you are about to use. That means that every block of code that is executed and will use some code relative to the context must be preceded with setting the correct context. What that means is if you were to call a series of 3 methods working with a single view you would need to set the context before the first call. On the other hand if you used performSelector:
method on each of them then you would need to set the context in each of these called methods just to be safe: Due to the possible multithreading another method might be performed in-between the other calls on the same thread.
So when you are able to understand all written above you will ask yourself "how and when to properly cleanup the items in the context?". You have at least two ways, one is described above and is not a very nice one, it seems like a hack. Another would be not to explicitly call the cleanup (such as in dealloc method) but rather set a certain flag that this view is no longer needed. Once this flag is set the view should still be performing the drawing, presenting and all the other stuff but once it gets into one of these refresh methods you call the cleanup instead of refreshing and then detaching the view from all the owners such as timers, display links and superviews. So this way you keep all other logic, there are no hacks and this call works even from another thread.
Why this is usually not an issue and why you were told to rather use a single context is because the contexts are mostly used to have multiple threads. Each thread needs its own context (but still it is ok to have multiple contexts on the same thread) so when dealing with a single context per thread you may set the current context only when the context is created (on the correct thread) and then set to nil
once the context is no longer needed (the cleanup).
I hope this makes some sense...
Upvotes: 2