LiquidVR™
LiquidVR™ 提供了一个基于 Direct3D 11 的接口,应用程序可以通过该接口访问以下 GPU 功能,而无论系统是否安装了 VR 设备。
本页内容
不要一上来就试图深入其中!Microsoft DirectX® 12 工作图规范内容庞大、复杂且细致入微。我强烈建议您构建沙箱并尝试各种功能,以确保您的思维模型与 GPU 的行为相匹配,然后再尝试在实际应用中运用这些功能。
围绕工作图的工具也仍在开发中。PIX 对工作图的支持正在取得巨大进展,但仍有一些用例会破坏它。此时,如果您尝试使用工作图执行非常复杂的操作……您可能需要构建自己的基础设施来从 GPU 中获取值,以及/或者一些额外的工具来批量处理或可视化这些结果。
在之前的示例中,我们讨论了如何选择所需的入口点,该入口点是在生成 D3D12_DISPATCH_GRAPH_DESC 时提供的索引。这种基于索引的引用可能会非常令人困惑……尤其是在迭代环境中,其中特定入口点的基于索引的位置可能会随时间变化,或者多个开发人员正在处理某个给定的内容,而只有其中一人真正知道最初打算的入口点。
解决此问题的一种方法是构建一个从入口点名称到入口点索引的映射。然后,您可以封装您的 DispatchGraph 调用,以便您指定所需入口点的名称,并在需要时按需查找相应的索引。这将改善协作和可扩展性。
构建此映射非常简单。以下是一个使用 C++ STL 中常用类型的示例
std::map<std::wstring, UINT> BuildEntrypointIndices(ID3D12StateObject* pStateObject, std::wstring programName){ std::map<std::wstring, UINT> EntrypointIndices;
ID3D12WorkGraphProperties* pWorkGraphProperties; pStateObject->QueryInterface(IID_PPV_ARGS(&pWorkGraphProperties));
UINT WGIndex = pWorkGraphProperties->GetWorkGraphIndex(programName.c_str()); UINT NumEntrypoints = pWorkGraphProperties->GetNumEntrypoints(WGIndex); for (UINT EntrypointIndex = 0; EntrypointIndex < NumEntrypoints; EntrypointIndex++) { D3D12_NODE_ID ID = pWorkGraphProperties->GetEntrypointID(WGIndex, EntrypointIndex); EntrypointIndices.insert({ ID.Name, EntrypointIndex }); }
pWorkGraphProperties->Release(); return EntrypointIndices;}SV_DispatchGrid使用 SV_DispatchGrid 会产生一定的成本。如果您知道常见情况具有固定大小,那么将节点拆分为常见情况并硬编码调度大小,以及根据需要设置调度大小的通用情况,可能会有益。幸运的是,您可以在此处使用相同的记录类型,因为通过指定 [NodeDispatchGrid(x,y,z)],SV_DispatchGrid 条目将被忽略。有关如何将其与 MaxRecordsSharedWith 结合使用,请参见下文。
我在工作图的冒险经历中遇到过的最难诊断的问题类别,最终都是内存踩踏……但被踩踏的内存是由驱动程序分配的,而不是由用户分配的。让我们来谈谈这些情况是如何发生的,以及如果您遇到类似问题,如何识别它们。
在“递归”部分,我们讨论了 API 定义的工作图节点执行深度限制(32);我将其视为工作图的“深度上限”。在该讨论中,我们谈到了 API 如何帮助您检测和避免因超出此上限而导致的错误……但有些情况下,它根本无法做到。工作图规范中还存在其他上限;我需要最频繁交互的上限是 [MaxRecords()] 的上限。
摘自 Microsoft 的 DirectX 12 工作图规范……
输出记录存储有一个限制,该限制是以下变量的函数
…
广播和合并启动节点
Maxrecords <= 256
…
线程启动节点
Maxrecords <= 8
超出这些限制器的声明值通常会导致内存踩踏。有时,这种踩踏是“宽容”的,您会得到一个卡死的 GPU。其他时候,您就不那么幸运了!如果您发现自己盯着屏幕思考“这简直不可能”,那么很可能您已经触及了这些限制之一。
作为一个具体的例子,我进行的一项更复杂的工作图工作,是将一系列计算着色器中的现有算法移植到一个工作图中。在初步理解该算法的过程中,我没有足够关注该算法消耗的数据结构是如何生成的;因此,我未能理解与该数据结构交互的一个关键细节。最终,我得到了一个半功能正常的工作图,但在特定的输入集上却完全失控。
当我终于开始理解发生了什么时,我正在查看我从 GPU 转储到 CPU 的值。我转储的一个值是一个线程局部变量,它存在于我的工作图节点中的“堆栈”上。在将该变量的值转储回 CPU 的五行之前,我将该变量分配了一个硬编码值 0x10。当该值到达 CPU 时,它不再是 0x10(“这简直不可能”)。我遇到了内存踩踏。
这个特定的错误是由无意中调用 OutputComplete() 的记录数超过了 [MaxRecords()] 中指定的数量(该数量本身已达到 "coalescing" 节点的上限 256)引起的。之所以发生这种情况,是因为我通过与我未能完全理解的数据结构交互来决定是否生成输出记录。编译器、运行时、验证层或执行上下文的任何部分都无法警告我发生了这种情况……随后的调试工作演变成了一个详尽的测试过程。如果您发现自己盯着某个东西思考“这简直不可能”,我希望这个故事能让您想起并节省您一些时间!
因此,我强烈建议您在实现新的工作图时,或者特别是移植您可能不完全理解的现有功能时,首先尝试将所有内容都设置为 [MaxRecords(256)](或者如果您在 "Thread" 启动节点中,则设置为 [MaxRecords(8)]),只要可能。一旦图表正常工作,您就可以开始调整正确的限制器使用方式,以提供即时反馈;如果您更改值后出现问题,您就知道从哪里入手了!
考虑以下图结构
在此工作图中,Node B 可以输出两个不同节点的记录:Node B(递归地)或 Node C。正如我们已经看到的,Node B 的声明必须包含多个 NodeOutput<> 参数。每个参数都必须用最大输出记录数进行装饰
[Shader("node")][NodeLaunch("coalescing")][NumThreads(32, 1, 1)]void NodeB( uint GroupIndex : SV_GroupIndex,
[MaxRecords(32)] GroupNodeInputRecords<NodeBInputRecord> inputData,
[MaxRecords(32)] NodeOutput<NodeBInputRecord> NodeB, [MaxRecords(32)] NodeOutput<NodeCInputRecord> NodeC)但是,如果我告诉您,Node B 的单个输入永远无法同时输出到 Node B 和 Node C 呢?如果我告诉您 Node B 必须选择其中之一呢?在此先决条件下,我们声明了最多 64 条输出记录……即使我们只需要 32 条。对于这种情况,[MaxRecordsSharedWith()] 属性代表了一种更清晰、更可扩展的解决方案。
[Shader("node")][NodeLaunch("coalescing")][NumThreads(32, 1, 1)]void NodeB( uint GroupIndex : SV_GroupIndex,
[MaxRecords(32)] GroupNodeInputRecords<NodeBInputRecord> inputData,
[MaxRecords(32)] NodeOutput<NodeBInputRecord> NodeB, [MaxRecordsSharedWith(NodeB)] NodeOutput<NodeCInputRecord> NodeC)虽然内存消耗可能减少总是很有价值……但如果一个节点开始接近“注意您的覆盖区域”部分中描述的“MaxRecords 上限”(或相关的 MaxOutputSize 或 SharedMemorySize 上限),那么识别利用此功能的机会就可能成为成功的关键!
与许多其他 GPU 开发环境一样,打包和解包记录中的数据,以便在记录不被主动使用时消耗更少的内存,可以节省性能。我建议您在开始打包记录之前,先让算法完全正常工作!
在此处了解更多信息
下载
GPUOpen 上的更多工作图内容
第三方网站链接仅为方便用户提供,除非另有明确说明,AMD不对任何此类链接网站的内容负责,且不暗示任何认可。GD-98
Microsoft 是 Microsoft Corporation 在美国和/或其他国家/地区的注册商标。本出版物中使用的其他产品名称仅用于标识目的,并可能为其各自所有者的商标。
DirectX 是 Microsoft Corporation 在美国和/或其他国家/地区的注册商标。