并行-RDP更新(LLE N64 Emu)-IGPU/Android dev、Chrome Profiler等

2020-06-01 04:39:32

自从并行RDP重写在世界上发布以来,已经做了相当多的工作。主要与性能相关,并绕过各种驱动程序。

不出所料,发现了一些错误,但与我预期的相比非常少。幸运的是,所有的渲染错误本质上都是微不足道的,并且不需要花费太多精力进行调试。我只能数到3个真正的虫子。要成为真正的错误,必须将问题隔离到并行RDP。不幸的是,核心错误相当常见,很多核心错误被误认为RDP错误。

RDP组合器可以将LOD分数值作为组合器的输入。但是,初始实现只考虑到周期0将观察到有效的LODFrac值。然而,这个游戏在第一周期中使用了LODFrac,这种情况被完全忽略了。修复错误就像考虑这种情况一样简单,并且RDP转储针对Angrylion进行了比特精确度验证。我相信这也修复了“星球大战-纳布”中的一些奇怪的小故障。至少在这个修复就位之后,它也通过了一点精确的测试。

一些游戏,特别是马里奥网球,偶尔会尝试上传带有破碎坐标的纹理。这应该以一种干净的方式溢出,但我错过了这个案例,并触发了一个更新了40亿个纹理元素的“无限”循环。不用说,这会触发GPU崩溃,因为我会耗尽VRAM,同时用内存分配给“无限”循环发垃圾邮件。一旦我复制了它,就相当简单地修复了它。我相信我在其他几个游戏中也看到过这些崩溃,可能也是同样的问题。修复后没有发现任何问题。

不是真正的RDP渲染问题,而是VI恶作剧。这是另一个引起问题的游戏的解决办法的一个很好的老案例。当VI接收到垃圾输入时,我们应该将其渲染为黑色,但这会在旧金山快攻中造成疯狂的闪烁,因为它每一帧都会选通无效状态。不完全确定这里发生了什么(并非不可能,这是一个核心错误…)。,但我在变通方法的基础上应用了另一个变通方法。我不喜欢这个🙁,至少VI实现中的默认路径是在这里呈现黑色,而Parallel-N64选择对无效的VI状态使用奇怪的变通方法。

现在,旧的Parallel-N64 Mupen内核是最薄弱的环节,几乎所有人们报告为RDP错误的问题都只是核心错误。我需要将其集成到较新的Mupen内核中,看看效果如何。

正如我在上一篇文章中提到的,需要解决缺少VK_EXT_EXTERNAL_MEMORY_HOST的问题,我实现了一个相当复杂的方案,以一种不会太慢的方式来处理这个问题。实际上,我们现在需要在RDRAM的两个视图(CPU拥有的RDRAM和GPU拥有的RDRAM)之间来回移动内存。该实现相当准确,并且以字节为单位跟踪写入。

提交给GPU的主要工作单元是“渲染过程”(在概念上类似于Vulkan渲染过程)。这是一组基本体,它们全部渲染到RDRAM中的相同地址,并且没有任何反馈效果,其中纹理数据从要渲染到的帧缓冲区区域采样。渲染过程将在渲染过程开始时从RDRAM进行大量读取,其中读取帧缓冲区数据,以及对TMEM的所有相关更新。在渲染之前,所有可能被读取的RDRAM块将被复制到GPU RDRAM。在渲染过程之后,我们还有一堆潜在的写入。这些写入最终必须返回到CPU RDRAM。在我们完全耗尽GPU的工作之前,RDP进行的任何写入都会“战胜”CPU进行的任何写入。在渲染过程的“读取”阶段,我们可以根据我们在GPU上维护的挂起的写掩码选择性地复制字节。如果GPU没有挂起的写入,我们会优化为直接拷贝。

至于性能,我用这个变通方法在NVIDIA上获得了大约10-15%的FPS命中率。很明显,但并不严重。

Android SoC并不总是支持与GPU的缓存一致性,因此增加了复杂性。在写入GPU RDRAM之后和从GPU RDRAM读取之前,我们必须仔细地刷新和使CPU端的缓存无效。我还修复了并行缓存管理的一系列问题-RDP,这在桌面系统上是永远不会发生的,因为所有东西基本上都是缓存一致的。

通过这些修复,Parallel-RDP至少可以在装有Android 10和MariGPU的Galaxy S9/S10以及Shield TV中的Tegra上正常运行。然而,在Android上对8/16位存储的支持仍然非常稀少,而且我找不到一个支持它的骁龙/Adreno GPU,哦,好吧。总有一天Android会迎头赶上。暂时别指望会有什么神奇的事情发生。除了性能之外,还有一些可怕的性能问题,这些问题是Android特有的,不受并行RDP的控制,需要单独调查。

主要的工作是修复在某些情况下会出现的一些性能问题。

为了深入了解这些问题,我需要更好的工具来关联CPU和GPU活动。这是将这种支持添加到Granite的一个很好的借口,Granite是并行的-RDP的渲染后端,Beetle HW Vulkan的后端,也是我个人的Vulkan渲染引擎的基础。Google Chrome实际上在Chrome://Tracking中有一个内置的配置文件UI前端,这对于像这样的特殊用例非常好。只需抛出一些简单的JSON就可以了。

要制作一个简单的CPU<;->;GPU分析器,您只需要Vulkan时间戳查询和VK_EXT_CALIBRATED_TIMESTAMPS来提高CPU<;->;GPU时间戳关联的准确性。我利用跟踪格式的“PID”特性来显示执行过程中相互重叠的不同帧上下文。

任何人现在都可以通过设置环境变量进行这些跟踪:PARALLEL_RDP_BENCH=1 GRANITE_TIMESTAMP_TRACE=mytrace.json,然后将JSON加载到Chrome://Tracking中。

这是我遇到的主要问题之一,我弄明白了为什么要使用这个新工具。在异步模式下,性能根本不会比同步模式提高。这是因为RetroArch中的交换缓冲区会在完成(跟踪中的“刷新”)之前完全停止GPU。我为这件事提交了一个台式窃听器。我需要在RetroArch中找到解决此问题的方法。通过一个老套的本地解决方案,IGPU在这种情况下最终比仅使用CPU有了很大的提升。在我的超高清620超极本上捕捉到的痕迹显示了四驱车司机的行为。在主仿真线程中延迟6毫秒并不好玩。🙁

这实际上又是一个PARALLEL-N64错误。为了管理CPU<;->;GPU重叠,Vulkan后端使用多个帧上下文,其中屏幕上的一个帧应该与一个帧上下文相对应。RDP集成经常收到帧正在启动的通知,因此会过早地等待GPU工作完成。在许多情况下,这基本上会将异步模式转换为同步模式。总体而言,修复此问题在我的桌面系统上获得了约10-15%的FPS。

更聪明地处理我们如何为GPU批量处理工作-修复马里奥网球中的卡顿。

马里奥·网球在渲染某些效果的方式上相当疯狂。朦胧效果是用~50(!)。渲染一遍接一遍,每次只渲染一个基本体。这是一个执行中的病态案例,运行得非常糟糕。

Parallel-RDP的最初设计是将较大的渲染过程与大约1k个基元的最佳点一次批处理在一起,并且每个渲染过程将对应于一个vkQueueSubmit。在这种情况下,这一假设落空了。为了解决这个问题,我重写了整个提交逻辑,试图使提交到GPU更加平衡。不要太大,也不要太小。现在,背靠背的微小渲染过程将一起批处理到一个命令缓冲区中,而大型渲染过程将被拆分。我们的目标是尽早向GPU提交大量有意义的工作,而不是在GPU无所事事的时候囤积成吨的工作。这对于我发现的同步模式至关重要,因为一旦我们命中最终的SyncFull操作码,我们将需要等待GPU完成所有挂起的工作。如果我们已经提交了大部分相关工作,我们就不需要等待那么长时间了。总体而言,对我来说,这完全消除了马里奥网球的性能问题,总体性能得到了相当程度的提高。>;400 VI/s在我的主系统上的各种游戏中并不少见。同步模式下的RDP开销通常为每帧0.1ms-0.2ms或类似的开销,非常微不足道。

我认为并行RDP本身现在在性能方面处于非常稳固的位置,主要问题是深入研究困扰英特尔IGPU和Android的各种WSI问题,我认为这是我们现在损失最大性能的地方。这项工作必须进入RetroArch本身,因为我们就是在那里处理这类事情的。

总而言之,请记住,与逐个像素的HLE渲染相比,精确的LLE渲染是非常繁重的。当目标是比特精确度时,单个像素所需的工作量是荒谬的。然而,剔除愚蠢的、不必要的开销有很大的提升性能的潜力。