介绍
质量效应是一种受欢迎的科幻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 2的PlayStation 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
退后一步,我们意识到我们早先错过了一些东西。回想一下,要“修复”游戏,您需要向注册表添加两个条目之一-
DisablePSGP
和DisableD3DXPSGP
。如果我们假设它们的名称表明了它们的用途,那么它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
。 - 英特尔处理器始终通过代码路径
intelsse2
2。 - 配备3DNow的AMD处理器!通过代码执行路径
amd_mmx_3dnow
或amd3dnow_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 (, - ), , . - , .
在这个阶段,我们准备创建一个修复程序,它将替换
D3DXMatrixInverse
D3DX函数的重写的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上的源代码
笔记
- 从理论上讲,它也可能是d3d9.dll内部的错误,这会使事情变得有些复杂。幸运的是,事实并非如此。
- 当然,假设它们具有SSE2指令集,但是任何没有这些指令的Intel处理器都比Mass Effect的最低系统要求弱得多。
也可以看看: