如何从iOS的Grand Central Dispatch Queueasynchronous绘制到GLKit的OpenGL ES上下文

我正在尝试将冗长的OpenGL绘制操作移动到GCD队列中,以便在GPU磨合时完成其他工作。 我宁愿用GCD来做这件​​事,而不是把真正的线程join到我的应用程序中。 从字面上来说,我想要做的就是能够

  • 不要在glDrawArrays()调用上阻塞,所以当GL渲染速度非常慢时,其余的UI可以保持响应。
  • 当我们还没有完成glDrawArrays()调用时,请放弃glDrawArrays()调用(不要build立一个刚刚增长和增长的帧队列)

在苹果的网站上,文档说:

GCD和NSOperationQueue对象可以在他们select的线程上执行你的任务。 他们可能会专门为这个任务创build一个线程,或者他们可能会重用一个现有的线程。 但无论哪种情况,都不能保证哪个线程执行任务。 对于OpenGL ES应用程序,这意味着:

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

听起来很直接。

为了简单起见,我将从“OpenGL ES”游戏“新build项目”对话框中提供的新版苹果模板开始。 当你实例化它,编译并运行时,你应该看到两个立方体在灰色区域上旋转。

为了这个代码,我添加了一个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); } 

这不起作用。 绘画变得疯狂。 尽pipe使用与dispatch_sync()相同的块绘图工作正常。

让我们仔细检查苹果的名单:

  • 在执行任何OpenGL ES命令之前,每个任务都必须设置上下文。
    • 好。 我正在设置上下文。 它的Objective-C对象指针的生命周期比块要长,所以它们应该被closures。 另外,我可以在debugging器中检查它们,它们都很好。 此外,当我从dispatch_sync绘制,它的作品。 所以这似乎不是问题。
  • 访问相同上下文的两个任务可能永远不会同时执行。
    • 访问GL上下文的唯一代码一旦build立,就是这个方法中的代码,它反过来在这个代码块中。 由于这是一个串行队列,无论如何,这个应该只有一个实例正在绘制。 此外,如果我添加一个synchronized(self.context){}块,它不会解决任何问题。 此外,在其他代码非常缓慢的绘图中,我添加了一个信号量跳过添加块队列时,前一个还没有完成,并丢弃帧(根据它吐出的NSLog()消息),但它didn不修复绘图。 然而,有一些GLKit代码是我看不见的操作上下文的方式,我从主线程不能理解。 尽pipesynchronized()不会改变问题,OpenGL Profiler也不会显示任何线程冲突,但这是我现在第二高的理论。
  • 每个任务都应该在退出之前清除线程的上下文。
    • 我不完全清楚这是什么意思。 GCD线程的上下文? 没关系。 我们没有添加任何东西到队列的上下文,所以没有什么可以清理的。 我们正在绘制的EAGLContext? 我不知道我们还能做什么。 当然不是实际上glClear它,这将只是抹去一切。 此外, 日落湖的分子中有一些代码呈现如下:

码:

 dispatch_async(openGLESContextQueue, ^{ [EAGLContext setCurrentContext:context]; GLfloat currentModelViewMatrix[9]; [self convert3DTransform:&currentCalculatedMatrix to3x3Matrix:currentModelViewMatrix]; CATransform3D inverseMatrix = CATransform3DInvert(currentCalculatedMatrix); GLfloat inverseModelViewMatrix[9]; [self convert3DTransform:&inverseMatrix to3x3Matrix:inverseModelViewMatrix]; GLfloat currentTranslation[3]; currentTranslation[0] = accumulatedModelTranslation[0]; currentTranslation[1] = accumulatedModelTranslation[1]; currentTranslation[2] = accumulatedModelTranslation[2]; GLfloat currentScaleFactor = currentModelScaleFactor; [self precalculateAOLookupTextureForInverseMatrix:inverseModelViewMatrix]; [self renderDepthTextureForModelViewMatrix:currentModelViewMatrix translation:currentTranslation scale:currentScaleFactor]; [self renderRaytracedSceneForModelViewMatrix:currentModelViewMatrix inverseMatrix:inverseModelViewMatrix translation:currentTranslation scale:currentScaleFactor]; const GLenum discards[] = {GL_DEPTH_ATTACHMENT}; glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards); [self presentRenderBuffer]; dispatch_semaphore_signal(frameRenderingSemaphore); }); 

这个代码有效,我没有看到任何额外的清理。 我无法弄清楚这个代码和我的做法有什么不同。 有一点是不同的,看起来像触摸GL上下文的一切都是从同一个GCD调度队列完成的。 但是,当我这样做我的代码,它不解决任何问题。

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

在这一点上,我有三个关于这个问题的理论:1.我正在做一个关于块,GCD和OpenGL ES之间交互的概念错误。 2. GLKit的GLKViewControllerEAGLContext在对drawInRect调用之间执行一些绘制或操作EAGLContext 。 当我的drawInRect块正在工作时,发生这种情况,搞砸了。 3.我依靠- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect方法的- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect的问题。 我想这种方法是:“嘿,你自动configuration了一个CADisplayLink ,每次它需要一个框架,它会打这个方法,做任何你想要的,我的意思是,在这里正常的代码,你只需要发出glDrawArrays命令,这并不像是我传回一个framebuffer对象或一个CGImageRef,它包含我想要在屏幕上显示的内容,而是发出了GL命令,但是这可能是错误的,也许你不能延迟绘图为了testing这个理论,我将所有的绘图代码移动到了一个名为drawStuff的方法中,然后用下面的代码replace了drawRect方法的主体:

 [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(drawStuff) userInfo:nil repeats:NO]; 

该应用程序出现,显示颜色的观点是glClear十秒钟,然后绘制像正常。 所以这个理论也不太强。

这里有一个类似的问题 ,有一个答案,这是upvoted和接受:

调度块中的代码不起作用。 当它被执行的时候,那个帧的所有OpenGL状态早已被破坏了。 如果你要在该块中调用glGetError(),我相信它会告诉你相同的。 你需要确保你所有的绘图代码都是在glgView方法中完成的,以使OpenGL状态有效。 当你做这个调度的时候,你基本上是将那个绘图代码的执行从该方法的范围之外分stream出来。

我不明白为什么这是真的。 但:

  1. 我只closures块中将要活动的块的引用,它们就像封闭对象作用域中的Objective-C指针一样。
  2. 我可以在debugging器中检查他们,他们看起来很好。
  3. 在每个GL操作之后,我插入了一个getGLError()调用,并且它永远不会返回任何东西,而是零。
  4. 使用dispatch_sync从一个块中绘制。
  5. 我试着想一下,在drawInRect方法中,我将块保存为一个ivar,然后设置一个NSTimer来调用drawStuff 。 在drawStuff我只是调用这个块。 画好。

NSTimer案例asynchronous绘制,但不涉及从另一个线程绘制,因为AFAIK NSTimer调用只是在设置线程的runloop上进行调度。 所以它与线程有关。

任何人都可以告诉我在这里失踪的东西吗?

这是不工作的,因为borrrden说,GLKit正在调用presentRenderbuffer: - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect完成。

这对于使用定时器的情况是有效的,因为drawStuff方法在绘制循环开始时在主线程上被调用。 你的- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect除了在另一个10秒钟内再次调度主线程, drawInRect:方法。 除了延迟绘图10秒之外,这没有任何作用,一切仍然在主线程上发生。

如果你想要走主线程渲染的路线,GLKit不会是一个好的匹配。 您将需要使用runloop设置自己的线程,将CADisplayLink连接到此runloop,然后从此队列中进行渲染。 GLKViewController被configuration为使用这个主runloop,并将始终在每帧结束时呈现渲染缓冲区,这将导致无论你在不同的线程上做什么。

根据您的总帐需求,您可能会发现在主线程上执行所有总帐function,并在主线程中执行“其他内容”更简单。