[英]How to draw to GLKit's OpenGL ES context asynchronously from a Grand Central Dispatch Queue on iOS
I'm trying to move lengthy OpenGL draw operations into a GCD queue so I can get other stuff done while the GPU grinds along. 我正在尝试将冗长的OpenGL绘制操作移动到GCD队列中,这样我就可以在GPU磨合的同时完成其他工作。 I would much rather prefer to do this with GCD vs. adding real threading to my app.
我宁愿喜欢用GCD与增加实际线程我的应用程序来做到这一点。 Literally all I want to do is be able to
字面上我想做的就是能够
On Apple's website, the docs say: 在Apple的网站上,文档说:
GCD and NSOperationQueue objects can execute your tasks on a thread of their choosing.
GCD和NSOperationQueue对象可以在他们选择的线程上执行您的任务。 They may create a thread specifically for that task, or they may reuse an existing thread.
他们可以专门为该任务创建一个线程,或者他们可以重用现有的线程。 But in either case, you cannot guarantee which thread executes the task.
但在任何一种情况下,您都无法保证哪个线程执行任务。 For an OpenGL ES app, that means:
对于OpenGL ES应用程序,这意味着:
- Each task must set the context before executing any OpenGL ES commands.
每个任务必须在执行任何OpenGL ES命令之前设置上下文。
- Two tasks that access the same context may never execute simultaneously.
访问相同上下文的两个任务可能永远不会同时执行。
- Each task should clear the thread's context before exiting.
每个任务都应该在退出之前清除线程的上下文。
Sounds pretty straightforward. 听起来很简单。
For the sake of simplicity in this question, I'm starting with a new, bone-stock version of the Apple template that comes up in the "New Project" dialog for "OpenGL ES" game. 为了简化这个问题,我首先介绍了一个新的骨架版Apple模板,它出现在“OpenGL ES”游戏的“New Project”对话框中。 When you instantiate it, compile, and run, you should see two cubes rotating on a grey field.
当您实例化它,编译并运行时,您应该看到在灰色字段上旋转的两个立方体。
To that code, I have added a GCD queue. 对于该代码,我添加了一个GCD队列。 Starting with the interface section of
ViewController.m
: 从
ViewController.m
的接口部分开始:
dispatch_queue_t openGLESDrawQueue;
Then setting those up in ViewController
viewDidLoad
: 然后在
ViewController
viewDidLoad
设置它们:
openGLESDrawQueue = dispatch_queue_create("GLDRAWINGQUEUE", NULL);
Finally, I make these very small changes to the drawInRect
method that CADisplayLink ends up triggering: 最后,我做这些非常小的改动
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);
}
This does not work. 这不起作用。 The drawing goes crazy.
绘图变得疯狂。 Drawing with the same block with
dispatch_sync()
works fine, though. 但是使用
dispatch_sync()
绘制相同的块可以正常工作。
Let's double check Apple's list: 让我们仔细检查Apple的清单:
synchronized(self.context){}
block, it doesn't fix anything. synchronized(self.context){}
块,它不会修复任何东西。 Also, in other code with very slow drawing I added a semaphore to skipping adding blocks to the queue when the previous one hadn't finished and it dropped frames fine (according to the NSLog()
messages it was spitting out), but it didn't fix the drawing. NSLog()
消息)但是它没有不修理图纸。 HOWEVER, there is the possibility that some of the GLKit code that I can't see manipulates the context in ways I don't understand from the main thread. Code: 码:
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);
});
This code works, and I don't see any additional cleanup. 此代码有效,我没有看到任何额外的清理。 I can't figure out what this code is doing differently than mine.
我无法弄清楚这段代码的作用与我的不同。 One thing that's different is it looks like literally everything that touches the GL context is being done from the same GCD dispatch queue.
有一点不同的是,从字面上看,触及GL上下文的所有内容都是从同一个GCD调度队列中完成的。 However, when I make my code like this, it doesn't fix anything.
但是,当我像这样编写代码时,它并没有修复任何问题。
The last thing that's different is that this code does not appear to use GLKit. 最后一点不同的是这段代码似乎没有使用GLKit。 The code above (along with the code I'm actually interested in) does use GLKit.
上面的代码(以及我真正感兴趣的代码) 确实使用了GLKit。
At this point, I have three theories about this problem: 1. I am making a conceptual error about the interaction between blocks, GCD, and OpenGL ES. 在这一点上,我有三个关于这个问题的理论:1。我对块,GCD和OpenGL ES之间的交互做了一个概念上的错误。 2. GLKit's
GLKViewController
or GLKView
do some drawing to or manipulation of the EAGLContext
in between calls to drawInRect
. 2. GLKit的
GLKViewController
或GLKView
做一些绘图或操纵EAGLContext
在调用之间drawInRect
。 While my drawInRect
blocks are being worked on, this happens, messing things up. 当我的
drawInRect
块正在处理时,会发生这种情况,弄乱了。 3. The fact that I'm relying on the - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
method is ITSELF the problem. 3.我依赖的事实
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
方法是- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
的问题。 I think of this method as, "Hey, you automatically have a CADisplayLink
configured, and every time it wants a frame, it'll hit this method. Do whatever the hell you want. I mean, in normal code here you just issue glDrawArrays commands. It's not like I'm passing back a framebuffer object or a CGImageRef containing what I want to end up on the screen. I'm issuing GL commands. HOWEVER, this could be wrong. Maybe you just can't defer drawing in this method in anyway without causing problems. To test this theory, I moved all the draw code into a method called drawStuff
and then replaced the body of the drawRect
method with: 我认为这个方法是,“嘿,你自动配置了一个
CADisplayLink
,每次想要一个框架,它都会触及这个方法。不管你想要什么。我的意思是,在普通代码中你只需要发出glDrawArrays这不是说我要传回一个帧缓冲对象或一个CGImageRef,其中包含我想要在屏幕上显示的内容。我正在发出GL命令。但是,这可能是错误的。也许你不能推迟绘图这个方法无论如何都没有引起问题。为了测试这个理论,我将所有绘制代码移动到一个名为drawStuff
的方法中,然后将drawRect
方法的主体替换为:
[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(drawStuff) userInfo:nil repeats:NO];
The app comes up, displays the color the view is glClear
'd to for ten seconds, and then draws like normal. 应用程序出现,显示视图为
glClear
的颜色十秒钟,然后绘制正常。 So that theory doesn't look too strong either. 所以这个理论看起来也不太强大。
There is a similar question posted here that has one answer, which is upvoted and accepted: 这里发布了一个类似的问题 ,其中有一个答案,被赞成并被接受:
The code in the dispatch block isn't going to work.
调度块中的代码不起作用。 By the time it gets executed, all of the OpenGL state for that frame will have long since been destroyed.
当它被执行时,该帧的所有OpenGL状态将很久以来被破坏。 If you were to put a call to glGetError() in that block, I'm sure it would tell you the same.
如果您在该块中调用glGetError(),我相信它会告诉您相同的内容。 You need to make sure that all your drawing code is done in that glkView method for the OpenGL state to be valid.
您需要确保所有绘图代码都在该glkView方法中完成,以使OpenGL状态有效。 When you do that dispatch, you're essentially shunting the execution of that drawing code out of the scope of that method.
当您执行该调度时,您实际上是将该绘图代码的执行分离出该方法的范围。
I don't see why this should be true. 我不明白为什么这应该是真的。 But:
但:
drawInRect
method, I save the block to an ivar and then set a NSTimer to call drawStuff
. drawInRect
方法中,我将块保存到ivar然后设置NSTimer来调用drawStuff
。 In drawStuff
I just invoke the block. drawStuff
我只是调用块。 Draws fine. The NSTimer case draws asynchronously, but it does not involve drawing from another thread since AFAIK NSTimer invocations just get scheduled on the setting thread's runloop. NSTimer情况是异步绘制的,但它不涉及从另一个线程绘制,因为AFAIK NSTimer调用只是在设置线程的runloop上进行了调度。 So it has to do with threads.
所以它与线程有关。
Can anyone clue me in on what I'm missing here? 谁能让我知道我在这里缺少什么?
This is not working because as borrrden says, GLKit is calling presentRenderbuffer:
immediately after - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
completes. 这不起作用,因为正如borrrden所说,GLKit正在调用
presentRenderbuffer:
紧接着- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
完成。
This is working in your case of using a timer, because the drawStuff
method is being called on the main thread at the beginning of a draw cycle. 这适用于使用计时器的情况,因为
drawStuff
方法是在绘制周期开始时在主线程上调用的。 Your - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
effectively does nothing but schedule this to happen on the main thread again in another 10 seconds, and the previously scheduled drawing call is then rendered at the end of the drawInRect:
method. 你的
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
实际上没有做任何事情,只是在另一个10秒内再次在主线程上安排这个,然后在结束时渲染先前安排的绘图调用drawInRect:
方法。 This is doing nothing except delaying the drawing by 10 seconds, everything is still occurring on the main thread. 除了将绘图延迟10秒之外,这没有任何作用,主线程上仍然发生了一切。
If you want to go the route of rendering off the main thread, GLKit is not going to be a good match. 如果你想要渲染主线程的路线,GLKit将不会是一个很好的匹配。 You are going to need to set up your own thread with a runloop, hook up a
CADisplayLink
to this runloop, and then render from this queue. 你将需要建立自己的线程有runloop,挂钩一
CADisplayLink
这个runloop,然后从这个队列中呈现。 GLKViewController
is configured to use the main runloop for this, and will always present the render buffer at the end of each frame, which will cause havoc with whatever you are doing on a different thread. GLKViewController
被配置为使用主runloop,并且将始终在每个帧的末尾呈现渲染缓冲区,这将对您在不同线程上执行的操作造成严重破坏。
Depending on your GL needs you may find it simpler doing all the GL stuff on the main thread, and doing the "other stuff" off the main thread. 根据您的GL需求,您可能会发现在主线程上执行所有GL内容并在主线程上执行“其他内容”更简单。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.