23

所以我正在阅读这篇文章,其中包含“SDL 中多线程编程的提示和建议” - https://vilimpoc.org/research/portmonitorg/sdl-tips-and-tricks.html

它谈到 SDL_PollEvent 效率低下,因为它可能导致 CPU 使用率过高,因此建议使用 SDL_WaitEvent 代替。

它显示了两个循环的示例,但我看不出这将如何与游戏循环一起使用。SDL_WaitEvent 是否应该仅用于不需要不断更新的事物,即如果您正在运行游戏,您将在每一帧执行游戏逻辑。

我认为它可以用于的唯一事情是像绘画程序这样的程序,其中只需要用户输入的操作。

我认为我应该继续使用 SDL_PollEvent 进行通用游戏编程是否正确?

4

5 回答 5

13

如果您的游戏仅根据用户输入更新/重绘,那么您可以使用 SDL_WaitEvent。但是,即使没有用户输入,大多数游戏也会进行动画/物理。所以我认为 SDL_PollEvent 最适合大多数游戏。

SDL_WaitEvent 可能有用的一种情况是,如果您将它放在一个线程中,而将动画/逻辑放在另一个线程中。这样即使 SDL_WaitEvent 等待很长时间,您的游戏也会继续绘制/更新。(编辑:这可能实际上不起作用。请参阅下面的 Henrik 评论)

至于文章指出的使用 100% CPU 的 SDL_PollEvent,当您检测到游戏运行的速度超过所需的每秒帧数时,您可以通过在循环中添加睡眠来缓解这种情况。

于 2013-09-18T03:17:38.250 回答
10

如果您在输入中不需要子帧精度,并且您的游戏不断动画,那么 SDL_PollEvent 是合适的。

例如,子帧精度可能很重要。玩家可能想要非常小的移动增量的游戏 - 如果您使用经典的 keydown 懒惰方法来表示“速度 = 1”并且使用 keyup 来表示“速度 = 0”,那么快速点击和释放一个键会出现不可预测的行为,然后你只每帧更新一次位置。如果您的点击碰巧与帧渲染重叠,那么您将获得一帧持续时间的移动,如果不是,则您没有移动,您真正想要的是小于基于时间戳的帧长度的移动量事件发生的地点。

不幸的是,SDL 的事件不包括来自操作系统的实际事件时间戳,仅包含 PumpEvents 调用的时间戳,并且 WaitEvent 以 10 毫秒的间隔有效轮询,因此即使 WaitEvent 在单独的线程中运行,您将获得的最精确度是10 毫秒(如果您在同一个轮询周期中获得 keydown 和 keyup 则大约为 5 毫秒)。

因此,如果您真的想要对输入进行精确计时,您实际上可能需要编写自己的 SDL_WaitEventTimeout 版本,并使用较小的 SDL_Delay,并在主游戏循环之外的单独线程中运行它。

更不幸的是,SDL_PumpEvents 必须在初始化视频子系统的线程上运行(根据https://wiki.libsdl.org/SDL_PumpEvents),因此在另一个线程上运行输入循环以获得子帧计时的整个想法被否定了通过 SDL 框架。

总之,对于带有动画的 SDL 应用程序,没有理由使用 SDL_PollEvents 以外的任何东西。对于子帧率输入精度,您可以做的最好的事情是,如果您有时间在帧之间进行刻录,您可以选择在那段时间内保持精确,但是您会在输入丢失的每一帧中获得奇怪的渲染持续时间窗口精度,因此您最终会遇到另一种不一致。

于 2016-01-17T21:49:15.017 回答
5

通常,您应该使用 SDL_WaitEvent 而不是 SDL_PollEvent 将 CPU 释放给操作系统以处理其他任务,例如处理用户输入。这将向您的用户显示为对用户输入的反应迟缓,因为这可能会导致他们输入命令和您的应用程序处理事件之间的延迟。通过使用 SDL_WaitEvent,操作系统可以更快地将事件发布到您的应用程序,从而提高感知性能。

附带的好处是,使用电池供电系统(如笔记本电脑和便携式设备)的用户应该会看到电池使用量稍微减少,因为操作系统有机会降低整体 CPU 使用率,因为您的游戏没有 100% 使用它 - 它只会在事件实际发生时使用它。

于 2013-09-18T01:20:17.780 回答
0

您实际上可以在主线程中初始化 SDL 和窗口,然后再创建 2 个线程用于更新(只是随着时间的推移更新游戏状态和变量)和渲染(相应地渲染表面)。然后在完成所有这些之后,在主线程中使用 SDL_WaitEvent 来管理 SDL_Events。这样,您可以确保事件在调用 sdl_init 的同一线程中进行管理。

我一直在使用这种方法来让我的游戏在 windows 和 linux 上运行,并且已经能够成功地同时运行 3 个线程,如上所述。

我必须使用互斥锁来确保纹理/表面也可以通过暂停渲染线程在更新线程中进行转换/更改,并且每 60 帧调用一次锁,因此它不会导致重大性能问题。

此模型最适用于创建事件驱动游戏、运行时游戏或两者兼而有之。

于 2019-07-09T14:26:03.770 回答
0

这是一个很晚的回应,我知道。但这是在 Google 搜索中名列前茅的线程,因此似乎是添加替代建议来处理此问题的地方,有些人可能会觉得有用。

您可以使用 SDL_WaitEvent 编写代码,这样,当您的应用程序没有积极地为任何东西设置动画时,它会阻塞并将 CPU 交还给操作系统。

但是您可以从另一个线程(例如游戏逻辑线程)向队列发送用户定义的消息,以使用该消息唤醒主渲染线程。然后它通过循环渲染一个帧,交换并再次返回到 SDL_WaitEvent。这些用户定义的消息中的另一个可以等待被拾取,告诉它再次循环。

这种结构可能对存在“爆发”动画的应用程序(或游戏)有好处,但除此之外,最好阻止和闲置(并节省笔记本电脑的电池电量)。

例如,当您打开或关闭或移动窗口或将鼠标悬停在按钮上时它会动画的 GUI,但大多数时候它是静态内容。

(或者,对于游戏,虽然它在游戏中一直处于动画状态,但它可能不需要为暂停屏幕或游戏菜单执行此操作。因此,您可以在游戏过程中发送“SDL_ANIMATEEVENT”用户定义的消息,但是然后,在游戏菜单和暂停屏幕中,等待鼠标/键盘事件并实际让 CPU 空闲和冷却。)

事实上,你可以有自触发的动画事件。因为渲染线程被“SDL_ANIMATEEVENT”唤醒,然后又完成了一帧动画。但由于动画未完成,渲染线程本身会向自己的队列发布一个“SDL_ANIMATEEVENT”,当它到达 SDL_WaitEvent 时会触发它再次唤醒。

另一个想法是 SDL 事件也可以携带数据。因此,您可以在事件中提供“data1”中的动画 ID 和“data2”中的“当前帧”计数器。因此,当线程选择“SDL_ANIMATEEVENT”时,事件本身会告诉它要执行哪个动画以及我们当前在哪个帧上。

我觉得这是一个“两全其美”的解决方案。它的行为类似于 SDL_WaitEvent 或 SDL_PollEvent,由应用程序自行决定,只需向自身发送消息即可。

对于游戏而言,这可能不值得,因为您会不断更新帧,所以这并没有太大的优势,也许不值得打扰(尽管即使游戏也可以从暂停屏幕中的 0% CPU 使用率中受益或游戏内菜单,让 CPU 冷却并使用更少的笔记本电脑电池)。

但是对于像 GUI 之类的东西——它有更多的“burst-y”动画——然后鼠标事件可以触发一个动画(例如,打开一个新窗口,它会缩放或滑入视图)将“SDL_ANIMATEEVENT”发送回队列。它会一直这样做,直到动画完成,然后再次回到正常的 SDL_WaitEvent 行为。

这是一个可能适合某些人需要的想法,所以我想我会把它放在这里供一般消费。

于 2022-01-22T13:39:38.403 回答