我试图将冗长的OpenGL绘制操作移至GCD队列中,以便在GPU磨削的同时完成其他工作。我会更愿意倾向于与GCD要做到这一点对增加实际线程我的应用程序。从字面上看,我想做的就是
在苹果网站上,文档说:
GCD和NSOperationQueue对象可以在自己选择的线程上执行任务。他们可以创建专门用于该任务的线程,或者可以重用现有线程。但是无论哪种情况,您都不能保证哪个线程执行任务。对于OpenGL ES应用程序,这意味着:
- 每个任务必须在执行任何OpenGL ES命令之前设置上下文。
- 访问同一上下文的两个任务可能永远不会同时执行。
- 每个任务在退出前都应清除线程的上下文。
听起来很简单。
为了简单起见,我首先从新的Apple模板的骨干版本开始,该版本出现在“ OpenGL ES”游戏的“新项目”对话框中。实例化,编译和运行它时,应该看到两个多维数据集在灰色的字段上旋转。
在该代码中,我添加了一个GCD队列。从以下内容的接口部分开始ViewController.m
:
dispatch_queue_t openGLESDrawQueue;
然后在ViewController
viewDidLoad
以下位置进行设置:
openGLESDrawQueue = dispatch_queue_create("GLDRAWINGQUEUE", NULL);
最后,我对drawInRect
CADisplayLink最终触发的方法进行了非常小的更改:
- (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_sync()
可以很好地工作。
让我们仔细检查一下苹果的列表:
synchronized(self.context){}
块,它不会解决任何问题。另外,在其他绘图速度很慢的代码中,我添加了一个信号量,以在前一个尚未完成并且丢弃帧的情况下跳过向队列添加块的操作(根据NSLog()
消息被吐出来了),但它没有修复图形。但是,有些看不见的GLKit代码有可能以我无法从主线程理解的方式操纵上下文。这是我目前评分第二高的理论,尽管sync()并不能解决问题并且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);
});
这段代码有效,而且我看不到任何其他清理方法。我无法弄清楚这段代码与我的代码有何不同。有所不同的是,看起来与GL上下文相关的所有事情实际上都是在同一GCD派遣队列中完成的。但是,当我这样编写代码时,它无法解决任何问题。
最后一点不同是该代码似乎未使用GLKit。上面的代码(以及我实际上感兴趣的代码)确实使用了GLKit。
在这一点上,我有关于该问题的三种理论:1.我在概念上犯了有关块,GCD和OpenGL ES之间交互的错误。2. GLKit的GLKViewController
或GLKView
做一些拉伸或操纵EAGLContext
在调用之间drawInRect
。当我的drawInRect
积木正在加工时,会发生这种情况,使事情变得混乱。3.我依赖该- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
方法的事实就是问题所在。我认为这种方法是:“嘿,您会自动CADisplayLink
配置,并且每次它想要一个框架时,它都会命中此方法。做任何你想做的。我的意思是,在此处的普通代码中,您只需发出glDrawArrays命令即可。这不像我要传回包含要在屏幕上显示的内容的帧缓冲区对象或CGImageRef。我正在发布GL命令。但是,这可能是错误的。也许您无法以任何方式推迟使用该方法进行绘制而不会引起问题。为了验证这一理论,我将所有绘图代码移入了一个名为的方法中drawStuff
,然后将drawRect
方法的主体替换为:
[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(drawStuff) userInfo:nil repeats:NO];
该应用程序启动,显示视图所要显示的颜色glClear
十秒钟,然后像平常一样绘制。因此,该理论看起来也不是太强硬。
这里发布了一个类似的问题,有一个答案,该答案被接受并接受:
调度块中的代码将无法正常工作。到执行时,该帧的所有OpenGL状态早就被销毁了。如果您要在该代码块中调用glGetError(),我相信它会告诉您相同的信息。您需要确保所有绘画代码均在该glkView方法中完成,以使OpenGL状态有效。执行该分派时,实际上是在使该绘图代码的执行脱离该方法的范围。
我不明白为什么这应该是真的。但:
drawInRect
方法中的什么地方,将块保存到ivar中,然后将NSTimer设置为call drawStuff
。在drawStuff
我只是调用块。画得很好。NSTimer情况是异步绘制的,但是它不涉及从另一个线程进行绘制,因为AFAIK NSTimer调用只是在设置线程的运行循环中安排的。因此,它与线程有关。
有人可以告诉我我在这里想念的东西吗?
这是行不通的,因为正如borrrden所说,GLKitpresentRenderbuffer:
在- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
完成后立即调用。
这在使用计时器的情况下有效,因为在drawStuff
绘制周期开始时在主线程上调用了该方法。您- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
实际上没有执行任何操作,只是将其安排在另外10秒内再次在主线程上发生,然后在drawInRect:
方法结束时呈现了先前安排的图形调用。除了将绘图延迟10秒之外,这什么都没做,所有事情仍在主线程上进行。
如果您想采用渲染主线程的方法,则GLKit并不是一个很好的选择。您将需要使用runloop设置自己的线程,将a连接CADisplayLink
到该runloop,然后从此队列进行渲染。GLKViewController
被配置为使用主运行循环,并且将始终在每个帧的末尾呈现渲染缓冲区,这将导致您在其他线程上所做的任何事情都陷入混乱。
根据您的GL需求,您可能会发现在主线程上执行所有GL事务并在主线程上执行“其他事务”更为简单。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句