如何重置opengl es上下文或重新绑定帧

时间:2014-09-17 05:00:55

标签: ios opengl-es

目前,我有一个地图视图对象,它使用OpenGL ES 1.0来渲染所有内容。这是视图初始化代码:

- (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;
}

当我们在整个应用程序中只有一个地图视图时,此工作正常。但是当我们创建多个地图视图时,例如:我们在主屏幕上创建第一个地图视图。然后我们导航到子屏幕。这里我们创建另一个地图视图。但是当我们导航回主屏幕时,主屏幕的地图视图不再有效。这是因为我们已经创建了另一个上下文并将所有opengl函数与新缓冲区绑定在一起。因此,不是将东西绘制到旧地图视图,而是在屏幕上不再可见的最新图像上绘制所有内容。

有些人对我们说我们应该:

  1. 每个视图只创建1个上下文和重新绑定缓冲区
  2. 为每个屏幕创建多个上下文,并在每次更改屏幕时重置上下文
  3. 所以我想知道在这种情况下我该怎么做?我该如何更改我们在这里更改opengl的init方法的方法?

    非常感谢。

    =============================================== ==

    更新: 这是我在deallocate上发布资源的代码:

    glDeleteRenderbuffersOES(1, &viewRenderbuffer);
    glDeleteRenderbuffersOES(1, &viewFramebuffer);
    glDeleteRenderbuffersOES(1, &sampleFramebuffer);
    context = nil;
    [EAGLContext setCurrentContext:nil];
    

1 个答案:

答案 0 :(得分:2)

设置当前上下文不适合您吗?您应该做的就是在切换到新视图后使用正确的上下文调用[EAGLContext setCurrentContext:context]

例如,如果您创建了一个将绘制openGL内容的自定义视图,您很可能会创建一个帧缓冲区,一个渲染缓冲区和一个上下文。如果您随后创建此视图,则可以正常绘制(如您所见)。然后,如果您要创建此视图的另一个实例并将其添加为子视图,您只需要执行以下操作:

  • 设置第一个上下文
  • 画出场景
  • 设置第二个上下文
  • 画出场景
  • ...

在你的情况下它几乎是一样的,除了它更容易。当您想要绘制到另一个视图时,只需设置其他上下文...

编辑:清理和上下文

所以这里似乎是个大问题。在问题编辑中发布一旦释放视图对象,缓冲区也会被删除,并且上下文设置为nil[EAGLContext setCurrentContext:nil])。这将从当前线程中删除上下文,并且不再执行openGL代码。

在这种情况下,问题本身就是我们根本不知道当这个方法执行时当前设置的上下文是什么,它可能是创建缓冲区的上下文或“其他”上下文。因此,最快的解决方案是修改它:

    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];
    }

尝试使用此代码,看看它是否解决了您的问题。

一般来说,就这一特定问题发表评论:

如前所述,您可以同时使用多个视图,这些视图包含它们自己的上下文,但它们都在同一个线程上(因为它们是可呈现的主线程,您可以不使用其他线程)。此视图上的操作必须以某种方式序列化,但要同时使用这两种操作,您需要将当前上下文设置为您将要使用的上下文。这意味着执行并将使用相对于上下文的某些代码的每个代码块必须先设置正确的上下文。这意味着如果您使用单个视图调用一系列3个方法,则需要在第一次调用之前设置上下文。另一方面,如果您在每个方法上使用performSelector:方法,那么您需要在每个被调用的方法中设置上下文以保证安全:由于可能的多线程,可能会在其间执行另一个方法。在同一个线程上的其他调用。

因此,当您能够理解上面所写的所有内容时,您会问自己“如何以及何时正确清理上下文中的项目?”。你至少有两种方式,一种是上面描述的并且不是一种非常好的方式,它看起来像是一种黑客攻击。另一个不是显式调用清理(例如在dealloc方法中),而是设置一个不再需要此视图的标志。设置此标志后,视图仍应执行绘图,呈现和所有其他内容但是一旦进入其中一个刷新方法,您调用清理而不是刷新,然后从所有所有者(如计时器)中分离视图,显示链接和超级视图。所以这样你保留了所有其他逻辑,没有黑客,这个调用甚至可以从另一个线程中运行。

为什么这通常不是问题,为什么你被告知要使用单个上下文是因为上下文主要用于拥有多个线程。每个线程都需要自己的上下文(但仍然可以在同一个线程上有多个上下文)所以当每个线程处理一个上下文时,你可以只在创建上下文时(在正确的线程上)设置当前上下文然后一旦不再需要上下文(清理),就设置为nil

我希望这有点意义......