15

在尝试将我的游戏引擎移植到 mac 时,我偶然发现了一个基本(但很大)的问题。在 Windows 上,我的主要代码如下所示(非常简化):

PeekMessage(...) // check for windows messages
switch (msg.message)
{
case WM_QUIT: ...;
case WM_LBUTTONDOWN: ...;
...
}
TranslateMessage(&msg);
DispatchMessage (&msg);

for (std::vector<CMyBackgroundThread*>::iterator it = mythreads.begin(); it != mythreads.end(); ++it)
{
  (*it)->processEvents();
}

... do other useful stuff like look if the window has resized and other stuff...

drawFrame(); // draw a frame using opengl
SwapBuffers(hDC); // swap backbuffer/frontbuffer

if (g_sleep > 0) Sleep(g_sleep);

我已经读到这不是 mac 方式。mac 方式是检查某种事件,例如屏幕的垂直同步以渲染新帧。但正如你所看到的,我想做的不仅仅是渲染,我还希望其他线程也能工作。某些线程的调用速度需要超过每 1/60 秒。(顺便说一句,我的线程基础结构是:线程将事件放入同步队列中,主线程调用 processEvents 处理主线程中同步队列中的项目。这用于网络内容、图像加载/处理内容、柏林噪声代等...等...)

我希望能够以类似的方式做到这一点,但是关于这方面的信息很少。我不介意将渲染放在垂直同步事件上(我也会在 Windows 上实现),但我希望对其余代码有更多的响应能力。

以这种方式看待它:无论如何,我都希望能够在 GPU 做事情的同时处理其余的事情,我不想等待垂直同步然后开始做应该已经被处理的事情然后才开始将东西发送到 GPU。你明白我的意思吗?

如果我需要从完全不同的角度来看待这个问题,请告诉我。

如果我需要为此阅读书籍/指南/教程/任何东西,请告诉我要读什么!

我不是可可开发人员,我不是 object-c 程序员,我的游戏引擎完全使用 C++,但我对 xcode 的了解足以让一个窗口出现并显示我在该窗口中绘制的内容。它只是不像我的 Windows 版本那样更新,因为我不知道如何正确处理它。

更新:我什至相信我需要某种循环,即使我想在垂直回溯上同步。MacOSX 文档上的 OpenGL 编程表明这是通过设置 SwapInterval 来完成的。因此,如果我理解正确,在 Mac 上实时渲染时,我总是需要某种循环,使用 swapInterval 设置来降低功耗。这是真的?

4

2 回答 2

19

虽然我不能成为世界上唯一一个试图实现这一目标的人,但似乎没有人想回答这个问题(不是指着你们 stackoverflow 的家伙,只是指着知道它是如何工作的 mac 开发人员)。这就是为什么我想改变这种典型的行为并帮助那些正在寻找同样东西的人。换句话说:我找到了解决方案!我仍然需要进一步改进这段代码,但基本上就是这样!

1/ 当你需要一个真正自己的循环时,就像在 Windows 中一样,你需要自己处理它。因此,让 cocoa 构建您的标准代码,但使用 main.m(如果需要,将其更改为 c++ 的 .mm,在此示例中是因为我使用了 c++)并删除唯一的一行代码。您将不允许可可为您设置应用程序/窗口/视图。

2/ Cocoa 通常会为你创建一个 AutoreleasePool。它可以帮助您进行内存管理。现在,这将不再发生,因此您需要对其进行初始化。您在 main 函数中的第一行将是:


    [[NSAutoreleasePool alloc] init];

3/ 现在您需要设置您的应用程序委托。XCode 向导已经为您设置了一个 AppDelegate 类,因此您只需要使用该类即可。此外,该向导已经设置了您的主菜单,并且可能将其命名为 MainMenu.xib。默认的就可以开始了。确保 #import "YourAppDelegate.h" 在 #import <Cocoa/Cocoa.h> 行之后。然后在你的 main 函数中添加以下代码:


    [NSApplication sharedApplication];
    [NSApp setDelegate:[[[YourAppDelegate alloc] init] autorelease]];
    [NSBundle loadNibNamed:@"MainMenu" owner:[NSApp delegate]];
    [NSApp finishLaunching];

4/ Mac OS 现在会知道应用程序是关于什么的,会为其添加一个主菜单。无论如何,运行它不会做太多,因为还没有窗口。让我们创建它:


    [Window setDelegate:[NSApp delegate]];
    [Window setAcceptsMouseMovedEvents:TRUE];
    [Window setIsVisible:TRUE];
    [Window makeKeyAndOrderFront:nil];
    [Window setStyleMask:NSTitledWindowMask|NSClosableWindowMask];

为了展示你可以在这里做什么,我添加了一个标题栏并启用了关闭按钮。你可以做更多,但我也需要先研究这个。

5/ 现在,如果你运行它,你可能会很幸运并看到一个微秒的窗口,但你可能什么也看不到,因为……程序还没有进入循环。让我们添加一个循环并监听传入事件:


    bool quit = false;
    while (!quit)
    {
        NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:nil inMode:NSDefaultRunLoopMode dequeue:YES];
        switch([(NSEvent *)event type])
        {
        case NSKeyDown:
            quit = true;
            break;
        default:
            [NSApp sendEvent:event];
            break;
        }
        [event release];
    }

如果你运行它,你会看到这个窗口出现,它会一直可见,直到你按下一个键。当您按下该键时,应用程序将立即退出。

就是这个!但请注意...查看活动监视器。您将看到您的应用程序正在使用 100% 的 CPU。为什么?因为循环不会使 CPU 进入睡眠模式。这取决于你。你可以让自己轻松一点,睡一觉(10000);在此期间。这会做很多事情,但不是最佳的。我可能会使用 opengl 的垂直同步来确保 CPU 没有被过度使用,并且我也会等待来自我的线程的事件。也许我什至会自己检查过去的时间,并计算出睡眠时间来计算每帧的总时间,这样我就有一个不错的帧速率。

有关 Opengl 的帮助:

1/ 将 cocoa.opengl 框架添加到项目中。

2/ 在 [Window...] 之前放置:


    NSOpenGLPixelFormat *format = [[NSOpenGLPixelFormat alloc] initWithAttributes:windowattribs];
    NSOpenGLContext *OGLContext = [[NSOpenGLContext alloc] initWithFormat:format shareContext:NULL];
    [format release];

3/ 在 [Window setDelegate:...] 之后放置:


    [OGLContext setView:[Window contentView]];

4/ 在窗口处理之后的某处,放置:


    [OGLContext makeCurrentContext];

5/ 在[事件发布]之后,放:


    glClearColor(1, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    [OGLContext flushBuffer];

这将在我的 mac book pro 2011 上以 3300 的帧速率将您的窗口清除为完全红色。

同样,这段代码并不完整。当您单击关闭按钮并重新打开它时,它将不再起作用。我可能需要为此挂钩事件,但我还不知道它们(经过进一步研究,这些事情可以在 AppDelegate 中完成)。此外,可能还有很多其他事情要做/考虑。但这是一个开始。请随时纠正我/填写我/...

如果我完全正确,我可能会为此设置一个教程网页,如果没有人击败我的话。

我希望这对大家有帮助!

于 2011-07-12T13:36:58.727 回答
1

您可以改用 Core Foundation 运行循环,并为您的“线程”提供事件源,以便运行循环将唤醒并调用它们的processEvents()方法。这是对轮询代码的改进,因为如果没有事件等待,运行循环将使线程休眠。

请参阅CFRunLoopSourceCreate()CFRunLoopSourceSignal()了解CFRunLoopWakeUp()更多信息。

请注意,如果您的应用程序构建在 Cocoa 框架之上,您可以(并且可能应该)使用 default NSRunLoop,但这不是问题,因为您可以CFRunLoop通过向其发送-getCFRunLoop消息来获取底层 Core Foundation。

于 2011-07-11T13:15:12.163 回答