Jeb*_*Jeb 4 opengl-es grand-central-dispatch ios objective-c-blocks
我正在尝试将冗长的OpenGL绘制操作移动到GCD队列中,这样我就可以在GPU磨合的同时完成其他工作.我会更愿意倾向于与GCD与增加实际线程我的应用程序来做到这一点.字面上我想做的就是能够
在Apple的网站上,文档说:
GCD和NSOperationQueue对象可以在他们选择的线程上执行您的任务.他们可以专门为该任务创建一个线程,或者他们可以重用现有的线程.但在任何一种情况下,您都无法保证哪个线程执行任务.对于OpenGL ES应用程序,这意味着:
- 每个任务必须在执行任何OpenGL ES命令之前设置上下文.
- 访问相同上下文的两个任务可能永远不会同时执行.
- 每个任务都应该在退出之前清除线程的上下文.
听起来很简单.
为了简化这个问题,我首先介绍了一个新的骨架版Apple模板,它出现在"OpenGL ES"游戏的"New Project"对话框中.当您实例化它,编译并运行时,您应该看到在灰色字段上旋转的两个立方体.
对于该代码,我添加了一个GCD队列.从接口部分开始ViewController.m:
dispatch_queue_t openGLESDrawQueue;
Run Code Online (Sandbox Code Playgroud)
然后将它们设置为ViewController viewDidLoad:
openGLESDrawQueue = dispatch_queue_create("GLDRAWINGQUEUE", NULL);
Run Code Online (Sandbox Code Playgroud)
最后,我对drawInRectCADisplayLink最终触发的方法进行了这些非常小的更改:
- (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);
}
Run Code Online (Sandbox Code Playgroud)
这不起作用.绘图变得疯狂.但是,使用相同的块绘制dispatch_sync()工作正常.
让我们仔细检查Apple的清单:
synchronized(self.context){}块,它不会修复任何东西.此外,在绘图速度非常慢的其他代码中,我添加了一个信号量,以便在前一个没有完成时跳过向队列添加块并且它丢帧很好(根据NSLog()它正在吐出的消息),但它没有修复图纸.但是,有一些我看不到的GLKit代码可能会以主线程中无法理解的方式操纵上下文.这是我现在评价第二高的理论,尽管synchronized()不会改变问题并且OpenGL Profiler不会显示任何线程冲突.码:
dispatch_async(openGLESContextQueue, ^{
[EAGLContext setCurrentContext:context];
GLfloat currentModelViewMatrix[9];
[self convert3DTransform:¤tCalculatedMatrix 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);
});
Run Code Online (Sandbox Code Playgroud)
此代码有效,我没有看到任何额外的清理.我无法弄清楚这段代码的作用与我的不同.有一点不同的是,从字面上看,触及GL上下文的所有内容都是从同一个GCD调度队列中完成的.但是,当我像这样编写代码时,它并没有修复任何问题.
最后一点不同的是这段代码似乎没有使用GLKit.上面的代码(以及我真正感兴趣的代码)确实使用了GLKit.
在这一点上,我有三个关于这个问题的理论:1.我对块,GCD和OpenGL ES之间的交互做了一个概念上的错误.2. GLKit GLKViewController或者在调用之间GLKView做一些绘制或操纵.当我的积木正在进行时,这种情况发生了,搞砸了.我依赖这种方法的事实是ITSELF的问题.我认为这种方法是,"嘿,你自动拥有一个EAGLContextdrawInRectdrawInRect- (void)glkView:(GLKView *)view drawInRect:(CGRect)rectCADisplayLink配置,每次它想要一个帧,它会点击这个方法.做你想要的任何事情.我的意思是,在普通代码中你只需要发出glDrawArrays命令.这不像是我传回一个帧缓冲对象或一个CGImageRef,其中包含我想要在屏幕上显示的内容.我正在发布GL命令.但是,这可能是错的.也许你无论如何都不能推迟这种方法的绘制而不会引起问题.为了测试这个理论,我将所有绘制代码移动到一个调用的方法中drawStuff,然后用以下drawRect方法替换方法体:
[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(drawStuff) userInfo:nil repeats:NO];
Run Code Online (Sandbox Code Playgroud)
应用程序出现,显示视图的颜色glClear十分钟,然后像平常一样绘制.所以这个理论看起来也不太强大.
这里发布了一个类似的问题,其中有一个答案,被赞成并被接受:
调度块中的代码不起作用.当它被执行时,该帧的所有OpenGL状态将很久以来被破坏.如果您在该块中调用glGetError(),我相信它会告诉您相同的内容.您需要确保所有绘图代码都在该glkView方法中完成,以使OpenGL状态有效.当您执行该调度时,您实际上是将该绘图代码的执行分离出该方法的范围.
我不明白为什么这应该是真的.但:
drawInRect方法中,我将块保存到ivar然后设置NSTimer来调用drawStuff.在drawStuff我只是调用块.画得很好.NSTimer情况是异步绘制的,但它不涉及从另一个线程绘制,因为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被配置为使用主runloop,并且将始终在每个帧的末尾呈现渲染缓冲区,这将对您在不同线程上执行的操作造成严重破坏.
根据您的GL需求,您可能会发现在主线程上执行所有GL内容并在主线程上执行"其他内容"更简单.