如何绘制到GLKit';从iOS上的大型中央调度队列异步创建OpenGL ES上下文

如何绘制到GLKit';从iOS上的大型中央调度队列异步创建OpenGL ES上下文,ios,opengl-es,objective-c-blocks,grand-central-dispatch,Ios,Opengl Es,Objective C Blocks,Grand Central Dispatch,我正试图将冗长的OpenGL绘图操作转移到GCD队列中,这样我就可以在GPU运行时完成其他工作。我更愿意用GCD来实现这一点,而不是在我的应用程序中添加真正的线程。实际上,我想做的就是能够 不阻塞glDrawArrays()调用,以便在GL渲染速度非常慢时,UI的其余部分可以保持响应 在我们还没有完成glDrawArrays()调用时删除它们(不要建立一个不断增长的帧队列) 在苹果的网站上,这些文件说: GCD和NSOperationQueue对象可以在其选择的线程上执行任务。他们可以专门为

我正试图将冗长的OpenGL绘图操作转移到GCD队列中,这样我就可以在GPU运行时完成其他工作。我更愿意用GCD来实现这一点,而不是在我的应用程序中添加真正的线程。实际上,我想做的就是能够

  • 不阻塞glDrawArrays()调用,以便在GL渲染速度非常慢时,UI的其余部分可以保持响应
  • 在我们还没有完成glDrawArrays()调用时删除它们(不要建立一个不断增长的帧队列)
在苹果的网站上,这些文件说:

GCD和NSOperationQueue对象可以在其选择的线程上执行任务。他们可以专门为该任务创建线程,也可以重用现有线程。但在这两种情况下,您都无法保证哪个线程执行任务。对于OpenGL ES应用程序,这意味着:

  • 在执行任何OpenGL ES命令之前,每个任务都必须设置上下文
  • 访问同一上下文的两个任务可能永远不会同时执行
  • 每个任务在退出之前都应该清除线程的上下文
听起来很简单

为了简化这个问题,我从“OpenGL ES”游戏的“新建项目”对话框中出现的苹果模板的一个新的骨库版本开始。当您实例化、编译和运行它时,应该会看到两个立方体在灰色字段上旋转

在该代码中,我添加了一个GCD队列。从
ViewController.m
的界面部分开始:

dispatch_queue_t openGLESDrawQueue;
然后在
ViewController
viewDidLoad
中设置它们:

openGLESDrawQueue = dispatch_queue_create("GLDRAWINGQUEUE", NULL);
最后,我对CADisplayLink最终触发的
drawInRect
方法进行了以下非常小的更改:

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
void (^glDrawBlock)(void) = ^{
    [EAGLContext setCurrentContext:self.context];
    glClearColor(0.65f, 0.65f, 0.65f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glBindVertexArrayOES(_vertexArray);

    // Render the object with GLKit
    [self.effect prepareToDraw];

    glDrawArrays(GL_TRIANGLES, 0, 36);

    // Render the object again with ES2
    glUseProgram(_program);

    glUniformMatrix4fv(uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX], 1, 0, _modelViewProjectionMatrix.m);
    glUniformMatrix3fv(uniforms[UNIFORM_NORMAL_MATRIX], 1, 0, _normalMatrix.m);

    glDrawArrays(GL_TRIANGLES, 0, 36);
};
dispatch_async(openGLESDrawQueue, glDrawBlock);
}
这是行不通的。这幅画疯了。但是,使用
dispatch\u sync()
使用相同的块绘制效果很好

让我们仔细检查一下苹果的列表:

  • 在执行任何OpenGL ES命令之前,每个任务都必须设置上下文。
    • 嗯。我正在设置上下文。不管怎么说,Objective-C对象指针的生命周期比块长,所以它们应该很好地关闭。另外,我可以在调试器中检查它们,它们很好。此外,当我从dispatch_sync中提取时,它也可以工作。因此,这似乎不是问题所在
  • 访问同一上下文的两个任务可能永远不会同时执行。
    • 设置GL上下文后,访问该上下文的唯一代码是此方法中的代码,而此方法又位于此块中。由于这是一个串行队列,因此每次只能绘制一个实例。此外,如果我添加一个
      synchronized(self.context){}
      块,它不会修复任何问题。另外,在其他绘制速度非常慢的代码中,我添加了一个信号量,在前一个代码尚未完成时跳过向队列中添加块,它会很好地丢弃帧(根据它发出的
      NSLog()。然而,我看不到的一些GLKit代码可能会以我从主线程无法理解的方式操纵上下文。这是我目前排名第二的理论,尽管synchronized()并没有改变问题,OpenGL Profiler也没有显示任何线程冲突
  • 每个任务在退出之前都应该清除线程的上下文。
    • 我不完全清楚这意味着什么。GCD线程的上下文?那很好。我们没有向队列的上下文添加任何内容,因此没有需要清理的内容。我们正在绘制的EAGLContext?我不知道我们还能做什么。当然不是真的清除它,那只会抹去一切。此外,还有一些代码呈现如下所示:
代码:

这段代码有效,我看不到任何额外的清理。我不知道这段代码和我的代码有什么不同。有一点不同的是,实际上所有涉及GL上下文的操作都是从同一个GCD调度队列中完成的。然而,当我这样编写代码时,它不会修复任何东西

最后一点不同的是,这段代码似乎没有使用GLKit。上面的代码(以及我真正感兴趣的代码)确实使用了GLKit

关于这个问题,我有三种理论: 1.关于块、GCD和OpenGL ES之间的交互,我犯了一个概念错误。 2.GLKit的
GLKViewController
GLKView
在调用
drawInRect
之间对
EAGLContext
进行一些绘图或操作。在处理我的
drawInRect
块时,会发生这种情况,把事情搞砸。 3.我依赖
-(void)glkView:(glkView*)view drawInRect:(CGRect)rect
方法本身就是问题所在。我认为这个方法是,,“嘿,您自动配置了一个
CADisplayLink
,每当它需要一个帧时,它就会点击这个方法。你想干什么就干什么。我的意思是,在这里的普通代码中,您只需发出glDrawArrays命令。这不像我要传回一个帧缓冲区对象或一个CGImageRef,其中包含我想要在屏幕上结束的内容。我正在发布GL命令。然而,这可能是错误的。也许你无论如何都不能在不引起问题的情况下推迟使用这种方法绘制。为了测试这个理论,我将所有的draw代码移动到一个名为
drawStuff
的方法中,然后将
drawRect
方法的主体替换为:

[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(drawStuff) userInfo:nil repeats:NO];
应用程序出现,显示视图的颜色,持续10秒钟,然后像正常一样绘制。所以这个理论看起来也不太有力

有一个答案是经过投票表决并被接受的:

调度块中的代码不起作用。当它被执行时,所有的OpenGL
[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(drawStuff) userInfo:nil repeats:NO];