将32位DirectX9应用程序转换为大内存地址应用程序

12

我们遇到了一个老的闭源游戏引擎的问题,当内存接近2GB时,它无法编译着色器。

问题通常出现在D3DXCreateEffect上。通常会返回 "内存不足" 的 HResult,有时d3dx9_25.dll弹出随机错误的窗口或者直接崩溃。

我认为问题是缺乏大地址感知:我注意到其中一个d3dx9_25.dll崩溃做了一些暗示这样的事情。它获取了一个看起来像0x8xxxxxx3的有效指针,检查位0x80000003是否亮起,如果亮起,它将反转位并取消引用。结果指针指向未分配的内存。在编译之前强制引擎malloc 2GB会导致着色器每次都无法编译。

不幸的是,我们对DX9的了解非常有限,我发现DX9有一个标记为D3DXCONSTTABLE_LARGEADDRESSAWARE的标志,但我不确定它应该放在哪里。我能找到的唯一使用它的API调用是D3DXGetShaderConstantTable,但问题发生在它被调用之前。将标志(1 << 17) = 0x20000注入到D3DXCreateEffect中会导致着色器以另一种方式无法编译。

  1. D3DXCreateEffect函数是否可以接受Large Address Aware标志?我找到了一个使用该标志的测试,但在研究DX9汇编时,当flags中的任何一位超出FFFFF800时,它引发的错误是由内部函数返回HResult Invalid Call引起的,这让我认为CreateEffect函数不应该接受此标志。

  2. 在此之前,我是否应该在其他地方注入Large Address Aware标志?我知道需要修复对D3DXGetShaderConstantTable的调用以使用D3DXGetShaderConstantTableEx,但它甚至还没有被执行到。


3
你考虑过迁移吗?听起来你所使用的引擎已经到达了其合理支持的极限。 - Mgetz
1
我认为你误解了。Windows 默认情况下采用保守策略,不会向老旧应用程序传递 >2GB 的指针。这样,像指针取反等花招的老旧应用程序将继续工作。 "Large Address Aware" 是一个标志,告诉 Windows "我没有做任何奇怪的事情,我可以处理 >2GB"。你可以分配2GB,意味着你的应用程序声称自己是LAA。 - MSalters
1
另外,我认为您可能忽略了 0x80000003 中的“3”。这暗示了一个未对齐的指针。取反它不会使其对齐,但是反转所有位可以。 - MSalters
4
x86指令集中的neg表示进行二进制补码取反(从零开始减),在C语言中对应一元运算符-。x86指令集中的not则表示进行二进制反码取反,即将所有位取反,在C语言中对应按位取反运算符~。我们称后者为按位取反,而不是取反操作,以便区分数学/二进制补码的取反操作。 - Peter Cordes
1
你说得对,我记错了。我再次检查,指令确实是“不”。 - user1108591
显示剩余7条评论
2个回答

6

LargeAddressAware(LAA)算是一种小技巧,有时可能会对你的情况有帮助,但也可能没有。它只有在应用程序需要接近2GB VA的空间时才有帮助,而不是需要更多。

遗留的DirectX SDK Direct3D 9时代效果系统的一个关键问题是,它假设效果“句柄”的高位是空闲的,因此可以使用它,而没有该位,则该句柄是指针地址。在使用LargeAddressAware时,这个假设是不成立的。

要启用此功能,请在包含d3dx9.h头文件之前定义D3DXFX_LARGEADDRESS_HANDLE。然后,您必须在创建所有效果时使用D3DXFX_LARGEADDRESSAWARE标志。而且,您不能使用别名技巧,在所有效果方法上都必须使用GetParameterByName获取句柄并使用它。

我记不起来LAA标志是何时添加到Direct3D 9的效果中的。

如果您正在使用d3dx9_25.dll,那么它是DirectX SDK的2005年4月发布版本。如果您正在使用“像素着色器模型1.x”,则不能使用任何比d3dx9_31.dll更新的版本(2006年10月)。DirectX SDK的后续版本允许您在这种情况下使用D3DXSHADER_USE_LEGACY_D3DX9_31_DLL,它只是将着色器编译传递给旧版本。

许多32位游戏失败然后启用LAA后能够工作的一个重要原因是虚拟内存碎片化。改善VA内存布局并使分配更加均匀也可以有所帮助。


1
LargeAddressAware 在 x64 上能够提供约 4GB 的内存,这在今天是相当普遍的。 - MSalters
1
是的,我知道LAA可以让你在Windows x64上分配高达4GB的虚拟地址。顺带一提,我在2004-2008年间大部分时间都在帮助游戏开发者实现这个。但这并不意味着那些遗留的32位游戏能够在接近这个限制的情况下稳定运行。对于一些卡在32位的游戏来说,Windows x64上的LAA能够帮助它们在内存占用超过1.7 GB时不崩溃。 - Chuck Walbourn
就我从标题中所理解的来看,“D3DXFX_LARGEADDRESS_HANDLE”只是帮助你在编译时确保你没有使用别名技巧,对吗? - user1108591
是的,“D3DXFX_LARGEADDRESS_HANDLE”是为了在编译时强制执行行为。 - Chuck Walbourn

1
我们遇到的问题是,CreateEffect不接受LargeAddressAware标志,这在事后看来是非常明显的。引擎正在使用的dx9版本(d3dx9_25.dll)还没有这个功能。
除了优化内存使用之外,我们的选择有:
  1. 将所有像素着色器1.x转换为2.0,并强制引擎加载更新版本的d3dx9,希望引擎不依赖于d3dx9_25.dll或别名技巧的错误,然后在其中注入LargeAddressAware标志位。
  2. 包装malloc,要么避免给句柄分配大地址(我不确定这是否也需要在dll内部进行),要么在大地址中放置足够的其他数据,以使dx9相关的malloc不会达到它。

1
第三个选项是找一个不过时的游戏引擎并将所有东西移植过去;虽然最初听起来可能需要很多工作,但可以显著改善游戏(性能和功能),并且从长远来看可以节省时间(因为一旦你找到了这个问题的快速解决方法,你就会遇到另一个问题,而这个问题可能不那么容易解决)。 - Brendan
这不是一个商业项目。将由*数百名志愿者制作的超过15年的内容移植到我们手中(其中可能只有三个软件人员留下了)对我们来说不是一个选择。 - user1108591
有没有可能将低2GiB的内存留给DX9需要使用的内存,通过提示将其他东西分配到虚拟地址空间的高2GiB中?如果您的进程中有任何不需要DX9兼容地址的大型分配,这可能会有所帮助。(如果Windows分配器有任何方法可以做到这一点;在Linux上,您将使用mmap(0x8000000,...MAP_ANONYMOUS) without MAP_FIXED。但是,提示地址必须针对每个分配进行手动管理,没有“高半部分的任何位置”提示。 - Peter Cordes
是的,我们已经有了一个malloc的包装器,我也有一个解决问题的工作POC。目前这个问题只影响到非常少量的人(可能是那些使用不寻常的驱动程序/反病毒软件需要额外RAM的人),因此我们首先正在检查是否可以欺骗引擎更动态地加载东西,因为这是一个不太hacky的解决方案。 - user1108591

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接