【问题标题】:SDL_PollEvent vs SDL_WaitEventSDL_PollEvent 与 SDL_WaitEvent
【发布时间】:2013-09-22 12:07:27
【问题描述】:

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

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

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

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

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

【问题讨论】:

    标签: sdl sdl-2


    【解决方案1】:

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

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

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

    【讨论】:

    • 您好,感谢您的反馈。这几乎就是我的想法,所以只是想检查一下我没有错过一些完全明显的东西。我已经实现了帧速率上限以防止 CPU 满负荷。
    • 以我的拙见,您希望允许系统执行任务(如其他线程等),因此使用 SDL_WaitEvent 是更好的选择。没有人会在接近 10 毫秒的任何地方移动(抽搐)。如果你需要运行一个物理引擎,你可以设置一个定时器来处理它。只是为了以防万一,把处理器的 100% 吃光是个坏主意。
    • 来自 SDL2 文档:“由于此函数隐式调用 SDL_PumpEvents(),因此您只能在初始化视频子系统的线程中调用此函数。”在另一个线程中使用 SDL_Wait 而另一个线程正在执行这幅画,这可能真的很危险。并且 SDL_Poll 不使用 100% 的 cpu。 SDL_Poll 仅检查事件队列中是否有事件,然后将其弹出到提供的指针。
    【解决方案2】:

    如果您的输入不需要子帧精度,并且您的游戏一直在制作动画,那么 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 以外的任何东西。对于子帧率输入精度,您可以做的最好的事情是,如果您有时间在 帧之间进行刻录,那么您可以选择在此期间保持精确,但是您会得到奇怪的渲染持续时间将输入丢失精度的每一帧窗口化,因此最终会出现不同类型的不一致。

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

      • 刚刚仔细检查了 SDL 文档,并查看了源代码。显然,在 SDL 2 中,他们对其进行了更改,以便 SDL_WaitEvent 只是循环直到事件发生,而不是将控制权交还给操作系统。既然如此,我想我看不出有任何理由证明在 SDL_PollEvent 上使用 SDL_WaitEvent 是合理的,至少出于 SDL 2 中的性能原因... :-(
      • 您好,感谢您的回复。是的 SDL_WaitEvent 本质上是一个阻塞函数。这就是为什么我看不出它如何适合游戏循环的原因。我想如果事件处理被分成一个单独的线程,它可能会起作用
      【解决方案4】:

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

      我长期使用这种方法让我的游戏在windows和linux下运行,并且已经能够成功地同时运行上面提到的3个线程。

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

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

      【讨论】:

      • 你是如何实现多线程的?我现在正在努力尝试并行化我的游戏。
      • 抱歉,过段时间再回复。对于您认为将在多个线程中执行的每个类函数,最好使用互斥锁。还要标记任何应该只在特定线程中执行的函数。
      • 您可能需要创建渲染线程,然后更新线程然后启动两者。然后在加入这些线程之前,您需要执行事件循环。一旦其中一个线程将 done 更改为 true,它就会退出循环并加入所有线程并关闭程序。我更喜欢有一个渲染函数来循环调用渲染,然后是一个更新函数,它在循环中调用每个精灵对象的 Update()。我将更新我的答案,其中包括我如何设法完成它的 sn-p。
      【解决方案5】:

      我知道,这是一个很晚的回复。但这是在 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 行为。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-06-17
        • 2023-04-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多