演练:因顶点着色而缺少对象

本演练演示如何使用 Visual Studio 图形诊断工具调查因顶点着色器阶段出现的错误而丢失的对象。

本演练阐释了以下任务:

  • 正在使用**“图形事件列表”**定位该问题的潜在来源。

  • 正在使用**“图形管线阶段”** 窗口检查 DrawIndexed Direct3D API 调用的影响。

  • 正在使用**“HLSL 调试器”**检查顶点着色器。

  • 使用**“图形事件调用堆栈”**帮助查找不正确 HLSL 常数的来源。

方案

当顶点着色器以不正确或意外的方式变换对象顶点的形状时,会出现三维应用程序中缺少对象的一个常见原因—例如,对象可能缩放到很小的尺寸或变形,出现在照相机后面而不是前面。

在此方案中,当应用程序运行测试时,背景如预期呈现,但是,其中一个对象不会出现。 使用图像诊断,则获取此问题到图像日志,以便您可以调试该应用程序。 该问题如该应用程序中所示:

无法看到对象。

调查

使用图像诊断工具,可以加载图像日志文件签入测试期间捕获的帧。

检查图形记录的帧

  1. 在 Visual Studio 中,加载包含显示缺少对象的帧的图形日志。 新图像日志选项卡在 Visual Studio 中出现。 此选项卡的顶部是呈现选定的帧的目标输出。 在底部是**“帧列表”**中,显示每个捕获的帧为缩略图像。

  2. 在**“帧列表”**中,选择说明该对象不显示的帧。 将呈现目标更新为反映选定的帧。 在此方案中,图形日志选项卡如下所示:

    Visual Studio 中的图形日志文档

在您已选择演示问题的帧后,可以使用**“图像事件列表”**对开始对其诊断。 **“图形事件列表”**包含用来呈现活动帧的每个 Direct3D API 调用,例如,API 调用设置设备状态,创建和更新缓冲区和绘制出现在帧中的对象。 许多类型的调用都很有趣,因为当应用程序按预期工作时,通常(但不总是)呈现目标中有一个对应的更改,例如绘制、调度、复制或清除调用。 绘制调用特别感兴趣,因为每个表示该应用程序呈现的几何图形(预定调用也可以呈现几何图形)。

由于已知不会绘制缺少对象来呈现目标(在此例中),但绘制场景的其余部分,您可以使用**“图像管道阶段”工具和“图像事件列表”**确定对应于缺少对象的几何图形的绘制调用。 “图形管道阶段” 窗口显示已发送给每个绘制调用的几何图形,而不考虑其在呈现目标上的影响。 当通过绘制调用移动时,更新管道阶段显示与该调用相关的几何图形,并在调用完成后更新呈现目标的输出显示呈现目标的状态。

查找缺少几何图形的绘制调用

  1. 打开**“图形事件列表”窗口。 在“图形诊断”工具栏上,选择“事件列表”**。

  2. 打开**“图形管道阶”窗口。 在“图形诊断”工具栏上,选择“管道阶段”**。

  3. 通过在**“图像事件列表”窗口中的每个绘制调用引动,请为缺少对象注意“图像管道阶段”窗口。 为了使此过程更加简单,在“图像事件列表”窗口的右上角的“搜索”**框中输入“绘制”。 这筛选了该列表以便仅包括其标题中含“绘制”的事件。

    在**“图像管道阶段”窗口中,“输入装配器”阶段在其转换之前显示对象的几何图形,并且,“顶点着色器”阶段在转换后显示同一对象。 在此方案中,当在“输入汇编”阶段中显示,并在“顶点着色器”**阶段不会显示时,您知道找到缺少对象。

    备注

    如果其他几何阶段(例如,外壳着色器,域着色器或几何着色器阶段)处理该对象,则这可能是问题的原因。通常,该问题与最早阶段相关,在最早阶段,以意外的方式不显示或显示结果。

  4. 停止,一旦到达对应于缺少对象的绘图调用。 在此方案中,**“图像管道阶段”**窗口指示几何图形问题分配给 GPU(由输入汇编缩略图指示),但是,不会出现在呈现目标,因为它在顶点着色器阶段发生错误(由顶点着色器缩略图指示):

    DrawIndexed 事件及其对管道的影响

在确认该应用程序被分配为缺失对象的几何图形的绘制调用并在顶点着色器阶段期间出现问题之后,可以使用 HLSL 调试器检查顶点着色器并查看对象的几何图形发生了什么。 您可以在执行期间使用 HLSL 调试器检查 HLSL 变量的状态,通过 HLSL 代码分步执行,并且设置断点帮助您诊断问题。

检查顶点着色器

  1. 开始调试顶点着色器阶段。 在**“图像管道阶段”窗口中,在“顶点着色器”阶段下,选择“开始调试”**按钮。

  2. 由于**“输入汇编”阶段将提供有效数据到顶点着色器,并“顶点着色器”**阶段显示不生成输出,要检查顶点着色器输出机制,output。 通过 HLSL 代码单步执行,当修改 output 时仔细查看。

  3. 第一次修改 output,则写入该成员 worldPos。

    “output.worldPos”的值看起来合理

    由于其值应该是合理的,则通过代码继续逐句执行直到修改 output 的下一行。

  4. 下一次修改 output,则写入该成员 pos。

    “output.pos”的值已归零

    此时,pos 成员的值 — 都是零 — 看起来很可疑。 下一步,您想要确定 output.pos 是如何全为零值的。

  5. 您将注意到 output.pos 将采用其在变量中命名为 temp 的值。 在上一行中,您看到 temp 的值是用其以前的值乘以名为 projection 的常数所得到的结果。 怀疑 temp 的可疑值是否是此乘积的结果。 当指针停留在 projection 上时,会注意到其值也全为零。

    投影矩阵包含错误的转换

    在此方案中,检查显示 temp 的可疑值最可能是由其由 projection的乘法引起,并且由于 projection 是被视为包含投影矩阵的常数,您知道它不应包含所有零。

在您确定 HLSL 常量 projection 通过您的应用程序传入着色器可能是问题的根源之后,下一步是查找常数的缓冲区中加载您的应用程序的源代码位置。 您可以使用**“图像事件调用堆栈”**查找此位置。

查找常数在应用程序的源代码中设置的位置

  1. 打开**“图形事件调用堆栈”窗口。 在“图形诊断”工具栏上,选择“图形事件调用堆栈”**。

  2. 向上导航调用堆栈进入您的应用程序源代码。 在**“图像事件调用堆栈”**窗口中,选择最顶层调用可以查看常量缓冲区是否填充到此处。 如果没有,请继续沿着调用堆栈向上,直到找到它的填充位置。 在此方案中,您会发现常量缓冲区是通过使用调用堆栈上的 UpdateSubresource Direct3D API 进一步在名为 MarbleMaze::Render 的功能,因此,其值来自名为 m_marbleConstantBufferData 的常数的缓冲区对象:

    设置对象的常量缓冲区的代码

    提示

    如果同时调试您的应用程序,则您可以在此位置设置断点,并且在下一个帧呈现时,它将被命中。然后,您可以检查 m_marbleConstantBufferData 的成员确认当常量缓冲区加载时 projection 成员的值设置为所有零。

在发现加载常量缓冲区的位置并发现其值来自变量的 m_marbleConstantBufferData 之后,下一步是查找 m_marbleConstantBufferData.projection 成员位置在哪全部设置为零。 您可以使用**“查找所有引用”**快速浏览更改 m_marbleConstantBufferData.projection 的值的代码。

查找在应用程序的源代码中设置常数的位置

  1. 查找对 m_marbleConstantBufferData.projection 的引用。 打开变量 m_marbleConstantBufferData 的快捷菜单,然后选择**“查找所有引用”**。

  2. 若要导航到修改 projection 成员的应用程序的源代码中的位置,在**“查找符号结果”**窗口中选择该行。 由于修改投影成员的第一个结果可能不是问题的原因,您可能需要检查您的应用程序的源代码的许多区域。

在发现 m_marbleConstantBufferData.projection 在哪设置的位置后,可以检查周围的源代码仪确定不正确值的原点。 在此方案中,您会发现 m_marbleConstantBufferData.projection 的值设置为名为 projection 的局部变量,在初始化到由下一行的代码 m_camera->GetProjection(&projection); 度量值之前。

在初始化之前设置大理石投影

若要解决此问题,移动在初始化本地变量 projection 后设置 m_marbleConstantBufferData.projection 的值的代码行。

已更正的 C++ 源代码

在修复代码后,您可以重新生成并再次运行该应用程序发现呈现问题已解决:

现在已显示对象。