修复现代AMD处理器上的质量效应图形错误

图片


介绍



质量效应是一种受欢迎的科幻RPG专营权。第一部分由BioWare于2007年底根据与微软的协议专门针对Xbox 360发行。几个月后,即2008年中,该游戏获得了Demiurge Studios开发的PC端口。在AMD于2011年发布其新的Bulldozer架构处理器之前,该端口相当不错且没有明显的缺陷。在两个游戏位置(Noveria和Ilos)上使用装有现代AMD处理器的PC上启动游戏时,会出现严重的图形瑕疵:





是的,看起来很丑。



尽管这不会使游戏无法玩,但这些工件很烦人。幸运的是,有一种解决方案,例如使用控制台命令关闭照明修改游戏地图以消除损坏的照明,但似乎没有人完全了解此问题的原因。一些消息来源声称FPS Counter mod也可以解决此问题,但我找不到有关此信息:该mod的源代码似乎未在线发布,并且没有有关该mod如何解决该错误的文档。



为什么这个问题如此有趣?仅在某些制造商的硬件上发生的错误非常普遍,并且在游戏中已经遇到了数十年。但是,根据我的信息,这是唯一的图形问题是由处理器而不是图形卡引起的情况。在大多数情况下,问题是由某个GPU制造商的产品引起的,并且不会以任何方式影响CPU,但是在这种情况下,情况恰恰相反。因此,此错误是唯一的,因此值得研究。



阅读在线讨论后,我得出的结论是问题似乎出在AMD FX和Ryzen芯片上。与较早的AMD处理器不同,这些芯片缺少3DNow!指令集!... 可能与该错误无关,但总体而言,游戏玩家社区已达成共识,即这是该错误的原因,并且当发现AMD处理器时,游戏会尝试使用这些命令。考虑到在英特尔处理器上尚无此漏洞的已知案例,因此3DNow!仅使用AMD,难怪社区将这一指令集归咎于此。



但是它们是问题所在,还是导致错误的原因完全不同?让我们找出答案!



第一部分-研究



序幕



尽管重现此问题非常容易,但是很长一段时间以来,我都无法评估它,原因很简单-我手头上没有配备AMD处理器的PC!幸运的是,我这次并不是一个人在做研究-拉斐尔·里维拉Rafael Rivera)通过为测试环境提供AMD芯片为我提供了支持,并在我盲目猜测的同时分享了他的假设和想法,这通常是我搜索时的情况此类未知问题的根源。



由于我们现在有了一个测试环境,因此,首先,我们测试了该理论cpuid-如果人们正确地认为应该归咎于3DNow!团队,那么游戏代码中应该有一个位置可以检查其存在,或者至少标识CPU的制造商。但是,这种推理有一个错误。如果游戏确实尝试使用3DNow!在任何AMD芯片上,如果不检查它们是否支持的可能性,那么在尝试执行无效命令时很可能会崩溃。此外,对游戏代码的简要检查表明它没有测试CPU功能。因此,无论错误的原因是什么,它似乎都不是由于错误地确定了处理器的功能而引起的,因为游戏根本对此不感兴趣。



当案件似乎无法调试时,拉斐尔将他的发现通知了我-禁用PSGP(特定于处理器的图形管道)解决了该问题,所有字符均正确点亮!PSGP不是记录最广泛的概念;简而言之,这是一项旧功能(仅适用于DirectX的较早版本),它允许Direct3D对特定处理器执行优化:



在DirectX的早期版本中,存在称为PSGP的代码执行路径,该路径允许进行顶点处理。应用程序必须考虑到该路径,并维护处理器和图形核心进行顶点处理的路径。


通过这种方法,禁用PSGP可以消除AMD上的伪像,这是合乎逻辑的-现代AMD处理器选择的路径存在某种缺陷。如何禁用它?我想到两种方法:



  • 您可以将IDirect3D9::CreateDevice标志传递给函数D3DCREATE_DISABLE_PSGP_THREADING描述如下:



    . , (worker thread), .


    , . , «PSGP», , .
  • DirectX PSGP D3D PSGP D3DX – DisablePSGP DisableD3DXPSGP. . . Direct3D .


看来DisableD3DXPSGP能够解决这个问题。因此,如果您不喜欢下载第三方修补程序/修改或不想在不对游戏进行任何更改的情况下解决问题,那么这是一种完全可行的方法。如果只为质量效应而不是为整个系统设置此标志,那么一切都会好起来的!



PIX



和往常一样,如果您遇到图形问题,则很有可能会帮助诊断PIX。我们在Intel和AMD硬件上拍摄了类似的场景,然后比较了结果。一个差异立即引起了我的注意-与以前的项目不同,在以前的项目中,捕获没有记录错误,并且相同的捕获在不同的PC上可能看起来有所不同(这表明驱动程序有错误或d3d9.dll),这些捕获写下一个错误!换句话说,如果您在装有Intel处理器的PC上打开在AMD硬件上进行的捕获,则将显示该错误



从AMD到Intel的捕获看起来与在所捕获硬件上的捕获完全相同:





这告诉我们什么?



  • PIX « », D3D , , Intel , AMD .
  • , , ( GPU ), , .


换句话说,这几乎肯定不是驱动程序错误。看起来为GPU准备的传入数据在某种程度上已损坏1。这确实是非常罕见的情况!



在此阶段,为了查找错误,有必要查找捕获之间的所有差异。这项工作很无聊,但是没有其他办法。



在对捕获的数据进行长时间的研究之后,我注意到了绘制整个角色身体的呼吁:





在Intel的接管中,此调用将输出角色的大部分身体以及灯光和纹理。在AMD的掌控下,它输出了纯黑色模型。看来我们走对了。



检查的第一个明显候选对象将是相应的纹理,但它们似乎很好,并且在两次捕获中都相同。但是,某些像素着色器常量看起来很奇怪。它们不仅包含NaN(非数字),而且还仅在AMD捕获中找到:





1.#QO代表NaN



看起来很有希望-NaN值通常会导致奇怪的图形工件。有趣的是Mass Effect 2PlayStation 3版本在RPCS3仿真器中也有一个非常相似的问题,也与NaN有关!



但是,暂时不要太高兴-这些可能是先前调用遗留下来的值,当前未使用这些值。幸运的是,在我们的案例中,可以清楚地看到,这些NaN正在为此特定的渲染传递给D3D ...



49652	IDirect3DDevice9::SetVertexShaderConstantF(230, 0x3017FC90, 4)
49653	IDirect3DDevice9::SetVertexShaderConstantF(234, 0x3017FCD0, 3)
49654	IDirect3DDevice9::SetPixelShaderConstantF(10, 0x3017F9D4, 1) // Submits constant c10
49655	IDirect3DDevice9::SetPixelShaderConstantF(11, 0x3017F9C4, 1) // Submits constant c11
49656	IDirect3DDevice9::SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID)
49657	IDirect3DDevice9::SetRenderState(D3DRS_CULLMODE, D3DCULL_CW)
49658	IDirect3DDevice9::SetRenderState(D3DRS_DEPTHBIAS, 0.000f)
49659	IDirect3DDevice9::SetRenderState(D3DRS_SLOPESCALEDEPTHBIAS, 0.000f)
49660	IDirect3DDevice9::TestCooperativeLevel()
49661	IDirect3DDevice9::SetIndices(0x296A5770)
49662	IDirect3DDevice9::DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 2225, 0, 3484) // Draws the character model


...,并且此渲染中使用的像素着色器引用了两个常量:



// Registers:
//
//   Name                     Reg   Size
//   ------------------------ ----- ----
//   UpperSkyColor            c10      1
//   LowerSkyColor            c11      1


看起来这两个常数都是直接从虚幻引擎中获取的,顾名思义,它们会影响光照。答对了!



游戏中的测试证实了我们的理论-在英特尔机器上,四个NaN值的向量从未作为像素着色器常量传递;但是,在AMD机器上,玩家进入灯光中断的地方后,NaN值便开始出现!



这是否意味着工作已经完成?远非如此,因为找到残缺的常数只是成功的一半。问题仍然存在-它们来自哪里并且可以被替换?在游戏中测试中,更改NaN值部分解决了问题-难看的黑点消失了,但角色看起来仍然太暗:





几乎是正确的...但不太正确。



考虑到这些照度值对场景的重要性,我们不能就此止步。但是,我们知道我们走在正确的道路上!



las,任何试图追踪这些常量来源的尝试都指向了看起来像渲染流的东西,而不是实际的目的地。尽管可以进行调试,但很明显,我们需要尝试一种新的方法,然后再花费潜在的无限时间跟踪游戏内和/或UE3相关结构之间的数据流。



第2部分-深入研究D3DX



退后一步,我们意识到我们早先错过了一些东西。回想一下,要“修复”游戏,您需要向注册表添加两个条目之一-DisablePSGPDisableD3DXPSGP。如果我们假设它们的名称表明了它们的用途,那么它DisableD3DXPSGP应该是一个子集DisablePSGP,前者仅在D3DX中禁用PSGP,而后者在D3DX和D3D中均禁用。做出这个假设之后,让我们将目光转向D3DX。



质量效应通过以下方式导入D3DX功能集d3dx9_31.dll



D3DXUVAtlasCreate
D3DXMatrixInverse
D3DXWeldVertices
D3DXSimplifyMesh
D3DXDebugMute
D3DXCleanMesh
D3DXDisassembleShader
D3DXCompileShader
D3DXAssembleShader
D3DXLoadSurfaceFromMemory
D3DXPreprocessShader
D3DXCreateMesh


如果我看到这个名单,不知道我们从捕获得到的信息,我将认为有可能是罪魁祸首可能是D3DXPreprocessShader要么D3DXCompileShader-着色器可能会被错误地优化和/或AMD编译,但是修复他们可能是出奇的困难。



但是,我们已经掌握了知识,因此对我们而言,列表中突出显示了一个功能-它D3DXMatrixInverse是唯一可用于准备像素着色器常量的功能。



仅在游戏中的一个位置调用此函数:



int __thiscall InvertMatrix(void *this, int a2)
{
  D3DXMatrixInverse(a2, 0, this);
  return a2;
}


但是...它没有很好地实现。一项简短的研究d3dx9_31.dll表明,它D3DXMatrixInverse不涉及输出参数,并且如果不可能进行矩阵求逆(因为输入矩阵是简并的),它会返回nullptr,但游戏根本不在乎。输出矩阵可能仍未初始化,嗯!实际上,退化矩阵的反转发生在游戏中(大多数情况下在主菜单中),但是我们为使游戏更好地处理它们所做的一切(例如,将输出归零或为其分配一个单位矩阵),在图形上没有任何变化。就是这样。



在驳斥了这一理论之后,我们回到了PSGP上-PSGP在D3DX中到底做了什么?拉斐尔·里维拉(Rafael Rivera)调查了这个问题,发现该管道背后的逻辑非常简单:



AddFunctions(x86)
if(DisablePSGP || DisableD3DXPSGP) {
  // All optimizations turned off
} else {
  if(IsProcessorFeaturePresent(PF_3DNOW_INSTRUCTIONS_AVAILABLE)) {
    if((GetFeatureFlags() & MMX) && (GetFeatureFlags() & 3DNow!)) {
      AddFunctions(amd_mmx_3dnow)
      if(GetFeatureFlags() & Amd3DNowExtensions) {
        AddFunctions(amd3dnow_amdmmx)
      }
    }
    if(GetFeatureFlags() & SSE) {
      AddFunctions(amdsse)
    }
  } else if(IsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE /* SSE2 */)) {
    AddFunctions(intelsse2)
  } else if(IsProcessorFeaturePresent(PF_XMMI_INSTRUCTIONS_AVAILABLE /* SSE */)) {
    AddFunctions(intelsse)
  }
}


如果未禁用PSGP,则D3DX将选择针对特定指令集优化的功能。这是合乎逻辑的,使我们回到了原始理论。事实证明,D3DX具有针对AMD和3DNow!指令集进行了优化的功能,因此游戏最终会间接使用它们。缺少3DNow!指令的现代AMD处理器遵循与Intel处理器相同的路径-即by intelsse2



总结:



  • 禁用PSGP时,Intel和AMD都遵循正常的代码执行路径x86
  • 英特尔处理器始终通过代码路径intelsse22
  • 配备3DNow的AMD处理器!通过代码执行路径amd_mmx_3dnowamd3dnow_amdmmx,而没有3DNow的处理器通过intelsse2


收到此信息后,我们将提出一个假设-AMD SSE2命令可能存在问题,并且在AMD的过程中,矩阵求反的结果intelsse2要么太不准确,要么完全不正确



我们如何检验这个假设?测试,当然!



PS:您可能会想“已在游戏中使用d3dx9_31.dll,但最新的D3DX9库具有一个版本d3dx9_43.dll,并且很可能已在较新的版本中修复了该错误?” 我们试图“升级”游戏以链接最新的DLL,但没有任何改变。



第3部分-独立测试



我们准备了一个简单的独立程序来测试矩阵求逆的准确性。在简短的游戏环节中,我们将错误记录在错误的位置,并将所有传入和传出的数据记录D3DXMatrixInverse到一个文件中。该文件由独立的测试程序读取,并重新计算结果。将游戏的输出与测试程序计算出的数据进行比较,以验证正确性。



根据从启用PSP和禁用PSGP的Intel和AMD芯片收集的数据进行了几次尝试之后,我们比较了不同计算机的结果。结果如下所示,表示成功(,结果相等)和失败(,结果不相等)运行。最后一列指示游戏是否正在正确处理数据或处于“越野车”状态。我们故意忽略浮点计算的不准确性,并使用memcmp以下方法比较结果



数据源 英特尔SSE2 AMD SSE2 英特尔x86 AMD x86 数据是否被游戏接受?
英特尔SSE2
AMD SSE2
英特尔x86
AMD x86


D3DXMatrixInverse测试结果



奇怪的是,结果表明:



  • 使用SSE2进行计算不能在Intel和AMD计算机之间移植。
  • 不使用SSE2的计算在计算机之间移植
  • 尽管游戏与英特尔SSE2上的计算有所不同,但游戏“不接受”没有SSE2的计算。


因此,出现了一个问题:AMD SSE2的计算到底有什么问题,因为这会导致游戏中出现故障?我们没有确切的答案,但看起来这是两个因素导致的:



  • D3DXMatrixInverse SSE2 — , SSE2 Intel/AMD (, - ), , .
  • , .


在这个阶段,我们准备创建一个修复程序,它将替换D3DXMatrixInverseD3DX函数的重写的x86变体,仅此而已。但是,我有另一个随机想法-D3DX已过时,已被DirectXMath取代。我决定,如果我们真的想替换此矩阵函数,则可以将其更改为XMMatrixInverse,这是该函数的“现代”替换D3DXMatrixInverse。它还XMMatrixInverse使用SSE2命令,也就是说,它将与D3DX中的功能一样最佳,但是我几乎可以确定其中的错误是相同的。



我迅速编写了代码,将其发送给Raphael,并且……



效果很好! (?)



归根结底,由于SSE2团队的细微差异,我们认为存在问题,这可能是一个极端的数字问题。即使它XMMatrixInverse也使用SSE2,它在Intel和AMD上都给出了完美的结果。因此,至少可以说,我们再次运行了相同的测试,结果出乎意料:



数据源 英特尔 AMD公司 数据是否被游戏接受?
英特尔
AMD公司


XMMatrixInverse的基准测试结果



不仅可以很好地运行游戏,而且结果可以完美匹配并在机器之间延续!



考虑到这一点,我们修改了有关错误原因的理论-毫无疑问,应该对问题太敏感的游戏负责;但是,在进行了额外的测试之后,在我们看来D3DX是为快速计算而编写的,而DirectXMath更关心计算的准确性。这似乎是合乎逻辑的,因为D3DX是2000年代的产品,因此将速度作为其首要任务是有意义的。DirectXMath是后来开发的,因此作者可以更加关注精确的确定性计算。



第4部分-全部组合



文章原来很长,希望您不累。让我们总结一下我们所做的:



  • , 3DNow! ( DLL).
  • , PSGP AMD.
  • PIX — NaN .
  • D3DXMatrixInverse.
  • , Intel AMD, SSE2.
  • , XMMatrixInverse .


我们唯一要做的就是正确的替换!这就是SilentPatch for Mass Effect发挥作用的地方我们认为,解决此问题的最简单方法是创建一个spoofer d3dx9_31.dll,它将所有导出的质量效应函数(函数除外)重定向到系统DLL D3DXMatrixInverse为此功能,我们开发了一个XMMatrixInverse



替换的DLL提供了非常干净可靠的安装,并且可以与游戏的Origin和Steam版本一起使用。它可以立即使用,而无需ASI Loader或任何其他第三方软件。



据我们了解,游戏现在看起来应有的样子,而照明却丝毫没有下降:



图片


图片


诺维里亚



图片


图片


伊洛斯



资料下载



可以从Mods&Patches下载该修改单击此处直接转到游戏页面:



下载SilentPatch以产生质量效应



下载后,只需将存档解压缩到游戏文件夹中即可。如果不确定下一步怎么办,请阅读设置说明






该模块的完整源代码已发布在GitHub上,并且可以免费用作起点:



GitHub上的源代码



笔记



  1. 从理论上讲,它也可能是d3d9.dll内部的错误,这会使事情变得有些复杂。幸运的是,事实并非如此。
  2. 当然,假设它们具有SSE2指令集,但是任何没有这些指令的Intel处理器都比Mass Effect的最低系统要求弱得多。


也可以看看:






All Articles