本演练演示如何使用 Visual Studio 图形诊断工具调查,是由于未设置像素着色器的支持对象。
本演练阐释了以下任务:
正在使用**“图形事件列表”**定位该问题的潜在来源。
正在使用**“图形管线阶段”** 窗口检查 DrawIndexed Direct3D API 调用的影响。
检查设备上下文来确定着色器阶段尚未设置。
使用 图形管道阶段 窗口与 图形事件调用堆栈 来帮助查找未设置的像素着色器的源。
方案
当在三维应用中对象丢失时,有时是因为在对象呈现之前,其中一个着色器阶段没有设置。 在具有简单呈现的应用对象中,此错误源通常位于对象的绘图调用的调用堆栈。 但是,作为优化,某些应用程序批处理有着色器程序、纹理,或其他数据来共同最小化状态更改开销的对象。 在这些应用,错误的源将隐藏在批处理系统中,而不是在绘制调用的调用堆栈中。 本演练中,方案演示呈现具有简单渲染的应用,因此错误的源可以在调用堆栈中找到。
在此方案中,当应用程序运行测试时,背景如预期呈现,但是,其中一个对象不会出现。 使用图像诊断,则获取此问题到图像日志,以便您可以调试该应用程序。 该问题如该应用程序中所示:
调查
使用图像诊断工具,可以加载图像日志文档签入测试期间捕获的帧。
检查图形记录的帧
在 Visual Studio 中,加载包含显示缺少对象的帧的图形日志文档。 新图像日志选项卡在 Visual Studio 中出现。 此选项卡的顶部是呈现选定的帧的目标输出。 在底部是**“帧列表”**中,显示每个捕获的帧为缩略图像。
在**“帧列表”**中,选择说明该对象不显示的帧。 将呈现目标更新为反映选定的帧。 在此方案中,图形日志选项卡如下所示:
在您选择演示问题的帧后,可以使用**“图像事件列表”**对开始对其诊断。 **“图形事件列表”**包含用来呈现活动帧的每个 Direct3D API 调用,例如,调用设置设备状态,创建和更新缓冲区和绘制出现在帧中的对象。 许多类型的调用(例如绘制、调度、复制或清除调用)都很有趣,因为当应用程序按预期工作时,通常(但不总是)呈现目标中有一个对应的更改。 绘制调用特别有趣,因为每一个字段都表示应用呈现的几何图形。
由于呈现目标不包含丢失对象,但也不出现于其他错误,您可以使用**“图像管道阶段”工具和“图像事件列表”**确定对应于缺少对象的几何图形的绘制调用。 “图形管道阶段” 窗口显示已发送给每个绘制调用的几何图形,而不考虑其在呈现目标上的影响。 当通过绘制调用移动时,当调用流经每个启用的阶段时,更新管道阶段显示与该调用相关的几何图形,并在调用完成后更新呈现目标的输出显示呈现目标的状态。
查找缺少几何图形的绘制调用
打开**“图形事件列表”窗口。 在“图形诊断”工具栏上,选择“事件列表”**。
打开**“图形管道阶”窗口。 在“图形诊断”工具栏上,选择“管道阶段”**。
通过在**“图像事件列表”窗口中的每个绘制调用引动,请为缺少对象注意“图像管道阶段”窗口。 为了使此过程更加简单,在“图像事件列表”窗口的右上角的“搜索”**框中输入“绘制”。 这筛选了该列表以便仅包括其标题中含“绘制”的事件。
在**“图像管道阶段”窗口中,“输入装配器”阶段在其转换之前显示对象的几何图形,并且,“顶点着色器”**阶段在转换后显示同一对象。 在此情况下,请注意 图形管道阶段 窗口显示调用绘制的 输入装配器 和 顶点着色器 阶段,但是,不是 像素着色器 阶段。
备注
如果其他管道阶段(例如,外壳着色器,域着色器或几何着色器阶段)处理该对象,其中任何一个可能是问题的原因。通常,该问题与最早阶段相关,在最早阶段,以意外的方式不显示或显示结果。
停止,一旦到达对应于缺少对象的绘图调用。 在此方案中,图形管道阶段 窗口指示几何被分配给 GPU (由于 输入装配器 阶段出现的) 和转换 (由于 顶点着色器 阶段),但是,未显示在所呈现器目标,因为看起来似乎不存在一个活动像素着色器 (由于缺少 像素着色器 阶段)。 在本方案,甚至可以看到输出合并器 阶段中缺少对象的剪影 :
在确认应用给丢失对象几何分配绘制调用并发现像素着色器阶段停用后,可以检查设备状态来查看发现。 可以使用 图形对象表 检查设备上下文和其他 Direct3D 对象数据。
检查设备上下文
打开 d3d11 设备上下文。 在 图形管道阶段 窗口中,选择 ID3D11DeviceContext 链接,显示在窗口顶部, DrawIndexed 调用的一部分。
检查在 d3d11 设备上下文 选项卡中的设备状态,确认在绘制调用期间,像素着色器不活动。 在这种情况下,着色器常规信息(显示在 像素着色器状态下)指示着色器是 NULL:
在确认应用设置像素着色器为 NULL 后,下一步将查找设置着色器在应用程序的源代码中的位置。 您可以与使用 图形事件调用堆栈 使用 图形事件列表 查找此位置。
查找在应用程序的源代码中设置像素着色器的位置
找到对应丢失对象的 PSSetShader 调用。 在 图形事件列表 窗口中,在图形事件列表 窗口的右上角的 搜索 框输入“Draw;PSSetShader”。 这筛选了该列表以便仅包括"PSSetShader"事件,和其标题中含“绘制”的事件。 选择丢失对象的绘图调用中的第一个出现的 PSSetShader 调用。
备注
如果此帧中尚未设置,PSSetShader 不会显示在 图形事件列表 窗口。只有在泛像素着色器的所有对象使用时,或此帧内 PSSetShader调用无意中未参与时,这通常才发生。在任何情况下,我们都建议您搜索 PSSetShader 调用的应用程序的源代码,并使用调试传统技术检查这些调用行为。
打开**“图形事件调用堆栈”窗口。 在“图形诊断”工具栏上,选择“图形事件调用堆栈”**。
使用调用堆栈来查找在应用程序的源代码中的 PSSetShader 调用的位置。 在 图形事件调用堆栈 窗口,请选择最顶端的调用并检查设置像素着色器的值。 像素着色器可能直接设置为 NULL,否则 null 值可能会由于传递到函数或其他状态的参数而出现。 如果未直接设置,你也许能在某处找到空值的源调用堆栈。 在此方案中,您查看像素着色器直接设置为最顶端的函数 nullptr,名为 CubeRenderer::Render:
备注
如果无法通过检查调用堆栈将空值的源,我们建议在 PSSetShader 调用设置条件断点,这样的程序的执行中断时,像素着色器将设置为 null。然后,重新启动在调试模式下的应用程序,并使用传统的调试技术来查找空值的来源。
即要解决该问题,通过使用 ID3D11DeviceContext::PSSetShader API 调用的第一个参数,请分配正确的像素着色器。
在修复代码后,您可以重新生成并再次运行该应用程序确认呈现问题已解决: