【问题标题】:Rendering and Updating at different rates on OS X with OpenGL使用 OpenGL 在 OS X 上以不同的速率渲染和更新
【发布时间】:2016-06-12 09:06:02
【问题描述】:

我的目标是构建一个面向 OSX 10.4+ 的原生 Cocoa 应用程序,该应用程序使用 OpenGL 进行渲染,以便移植我正在构建的游戏。我的问题是,按照 Apple 的技术说明(请参阅下面的链接),我无法弄清楚如何在 Cocoa 中分离更新和渲染。这就是我的问题:Cocoa 应用程序如何以与其呈现不同的速度更新?换句话说,Cocoa 的渲染是基于事件的,我不想等待一个事件来更新我的模拟。

这是我工作的重点:

    static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
    {
        CVReturn result = [(__bridge GameView*)displayLinkContext getFrameForTime:outputTime];
        return result;
    }

    - (CVReturn)getFrameForTime:(const CVTimeStamp*)outputTime
    {
        //TODO: find somewhere better to put this.
        //TODO: the magic number is not doing what I expect it to.
        float dt = (outputTime->videoTime - lastCapturedTime) / 36000000.0f;
        lastCapturedTime = outputTime->videoTime;
        update(dt);

        NSOpenGLContext *currentContext = [self openGLContext];
        [currentContext makeCurrentContext];

        // must lock GL context because display link is threaded
        CGLLockContext((CGLContextObj)[currentContext CGLContextObj]);

        glClearColor(0, 0, 0, 0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        render();
        glFlush();

        [currentContext flushBuffer];

        CGLUnlockContext((CGLContextObj)[currentContext CGLContextObj]);
        return kCVReturnSuccess;
    }

我不喜欢上面的代码是渲染和更新发生在同一个事件上,而事件的目的是操作系统告诉我的应用程序它应该渲染一个帧。

在 Windows 上,将此代码放在下面的 WinMain() 中会得到我正在寻找的内容。

while(true) {
    process_events();  //PeekMessage, handle WM_ events
    int elapsedTime = GetElapsedSeconds();
    prepare(elapsedTime);
    if (fps_lock(elapsedTime, 60)) {
        render();
    }
    swapBuffers();
}

这是我查看的内容(我会发布不止两个链接,因为我已经完成了大量研究,但我的低代表阻止了我这样做):

提前感谢您指出我可能遗漏的内容,或完全偏离主题的内容。

【问题讨论】:

  • 这里有很多关于基本问题的信息。您可能想要简化它,例如“如何以不同的速率渲染和更新?”并包含您的代码示例。祝你好运。 :-)

标签: macos cocoa opengl


【解决方案1】:

我假设update()prepare() 是类似的,只是关于更新物理等,而不是做任何OpenGL。而您只想尽可能快/频繁地执行此操作?

如果您的代码是线程安全的,您可以基于计时器来执行此操作。您可以在串行队列上使用 Grand Central Dispatch timer source。同样,您可以在运行其运行循环的线程的运行循环上使用NSTimer。现在,应用程序的主线程运行它的运行循环,主调度队列是一个串行队列,但您可能不想使用主线程/队列,因为这会干扰 UI 事件处理。因此,您可能希望使用自定义队列或您自己生成的线程来运行其运行循环。

如果您需要将渲染与更新同步,您可以使用同步原语(如分派信号量),或者您可以执行诸如将渲染同步提交到计时器分派源所针对的同一个串行队列等操作。或使用-performSelector:onThread:withObject:waitUntilDone: 定位您的线程。

顺便说一下,在您的 Windows 伪代码中,为什么要在尚未渲染的通道上交换缓冲区?

【讨论】:

  • Ken,您对更新/准备方法的假设是正确的(它们是类似的)。是的,我不希望游戏只能在鼠标或键盘输入等事件上进行更新。它让我更仔细地查看我的代码,我看到他们正在使用 glBindBuffer() 和 glBufferSubData() 更改动态绘制缓冲区中的一些顶点数据。没有进行其他 GL 调用。 render() 例程再次调用 glBindBuffer(),然后调用着色器程序。另外,关于 windows 伪代码,swap_buffers() 应该在 render() 之后的那个 if 块中。
  • 您可以通过遵循一些规则从update() 执行 OpenGL:一次仅使用来自单个线程的给定上下文,如果您使用线程,请确保该上下文是当前的' 不确定(即调度队列可能在不同的线程上运行每个任务),如果您不拥有线程,最好在返回之前清除线程的上下文。您可以使用共享上下文在一个线程上操作 GL 对象并在另一个线程上渲染。请务必glFlush()glFlushRenderAPPLE() 以确保更改在其他线程上“可见”。
  • 肯,非常感谢您的回答。我现在一切正常,我对计时器的响应能力非常满意。我只有最后一个问题:在使用 GCD 的 Cocoa 应用程序中,是否有最佳实践来启动计时器?我在prepareOpenGL() 方法中从我的子类NSOpenGLView 调用dispatch_resume()。似乎工作正常,但我想知道苹果是否规定了一个特定的事件来响应这对启动线程有好处?我在任何开发人员文档中都没有遇到过这种情况。
  • 不客气。我认为启动计时器或产生线程没有任何特定的最佳点。我不确定您最终做了什么,但如果您使用计时器重复调用update(),那么只要您确定需要开始更新模型,就可以启动它。在prepareOpenGL() 中执行此操作很好,尽管这更多是关于您的视图何时准备就绪,这可能与您的模型何时需要更新有关,也可能无关。这取决于适合您的应用的内容。
猜你喜欢
  • 1970-01-01
  • 2016-03-04
  • 1970-01-01
  • 2012-09-28
  • 2013-11-26
  • 2019-01-20
  • 1970-01-01
  • 1970-01-01
  • 2013-11-15
相关资源
最近更新 更多