无锁游戏引擎,完全分离更新并渲染

时间:2015-10-03 12:55:26

标签: multithreading render deterministic lockless

我为这篇长篇文章道歉,但你可能已经看到我已经考虑了很长一段时间了,而且我觉得在我爆炸之前我需要其他人的一些意见: - )

我已经尝试了一段时间,以各种方式构建游戏引擎,满足以下所有标准:

  • 完成对象更新和对象渲染的分离
  • 完全决定论
  • 以个别速度更新和呈现
  • 共享资源没有阻止

完成对象更新和对象渲染的分离

对象更新和对象渲染的分离对于确保在将数据发送到图形API和交换缓冲区时最佳地使用资源似乎至关重要。 即使你想确保使用CPU的多个核心的完全并行性,似乎仍然必须管理这个分离。

完全决定论

许多游戏类型,尤其是多人游戏版本,必须确保完全确定性。否则,玩家将体验同一游戏的不同状态,从而有效地破坏游戏逻辑。游戏回放也需要确定性。对于其他目的而言,它非常有用,因为每次运行模拟在给定相同的起始条件和输入的情况下每次运行都会产生相同的结果。

以个别速度更新和渲染

这实际上是完全确定性的先决条件,因为您不能让模拟依赖于渲染速度(即各种显示器刷新率,图形适配器速度等)。在最佳条件期间,更新速度应设置为某个固定间隔(例如,每秒25次更新 - 可能更少,具体取决于更新类型),并且渲染速度应该是客户端的监视器刷新率/图形适配器允许。

这意味着渲染速度应该允许更新速度更高。虽然这听起来像是浪费,但已知的技巧可以确保增加的渲染周期不是浪费(插值/外推),这意味着更快的监视器/适配器将获得更加视觉上令人愉悦的体验。

也必须允许渲染速度低于更新速度,即使这实际上导致浪费的更新周期 - 至少添加的更新周期并非全部呈现给用户。然而,这对于确保平稳的多人游戏体验是必要的,即使其中一个客户端的渲染因某种原因而导致突然爬行的速度变慢。

共享资源无阻止

如果要实现上面提到的其他标准,还必须遵循我们不能允许渲染等待更新,反之亦然。当然,很明显当2个不同的线程共享资源访问权限并且一个线程正在更新其中一些资源时,就不可能保证永远不会发生阻塞。但是,可以将此阻塞保持在绝对最小值 - 例如,在更新对象的队列和先前渲染的对象的队列之间切换指针引用时。

因此...

我对这里所有技术人员的问题是:我要求的太多了吗?

我一直在许多网站上阅读有关这些不同主题的想法。但似乎总是看到我已经看到的建议中遗漏了一部分或另一部分。也许原因是你没有妥协就无法拥有它。

很久以前,当我在这个帖子中提出我的想法时,我开始了这个看似常见的任务: Thoughts about rendering loop strategies

当时我的第一个天真的假设是,如果更新和阅读同时发生并不重要,因为这种变化对象状态非常小,你不应该注意到一个对象偶尔会比其他

现在我有点聪明,但有时仍感到困惑。

最有希望和最详细的描述一种方法可以满足我的所有愿望: http://blog.slapware.eu/game-engine/programming/multithreaded-renderloop-part1/ 一个三态模型,它将确保渲染器始终可以选择一个新队列进行渲染而无需等待(切换指针引用时可能只有微秒)。同时,更新程序总是可以访问构建下一个状态树所需的2个队列(1个用于创建/更新下一个状态的队列,以及1个用于读取previsous的队列 - 即使在渲染器将其读取为好)。

我最近有时间对此进行示例实现,并且效果非常好,但是有两个问题。

  • 一个是必须处理对所有相关对象的多个引用的小问题
  • 另一个更严重(除非我只是太需要)。这就是外推 - 与内插相反 - 用于在给定快速屏幕刷新率的情况下保持视觉上令人愉悦的状态表示的事实。虽然这两种方法都可以显示偏离固定计算对象状态的状态,但是当预测未能表示现实时,外推似乎会产生更多可见的伪像。我的立场似乎得到了支持: http://gafferongames.com/networked-physics/snapshots-and-interpolation/ 据我所知,在三态设计中不可能实现插值,因为它要求渲染器始终对2个队列具有读取访问权限,以计算两个已知状态之间的中间状态。

因此,我正在努力扩展slapware-blog上建议的三态模型,以利用插值而不是外推 - 同时尝试简化多参考结构。虽然在我看来是可能的,但我想知道价格是否过高。为了实现我的所有目标,我需要

  • 渲染器独占的2个队列(或状态)(它们可以由另一个线程用于只读目的,但从不更新,或在渲染期间切换
  • 1个队列(或状态),当完成呈现当前场景时,具有最新更新状态,准备切换到渲染器
  • 1个队列(或状态),更新程序
  • 构建/更新下一帧
  • 包含上次构建/更新的帧副本的1个队列(或状态)。这与上次发送到渲染器的状态相同,因此读取前一状态的更新程序和渲染状态的渲染器都应该可以访问此队列/状态。

这意味着我应该始终保持4个渲染状态副本,以便能够保持这个设计顺利,无锁,确定地运行。

我担心我会过度思考这个问题。因此,如果你们中的任何一个人建议我回到现场,或者建议可以改进什么,批评设计,或者可以参考解释如何实现这些目标的良好资源,或者为什么这样做或不是。 ;好主意 - 请跟他们打我: - )

0 个答案:

没有答案