本文包含一些有关 Direct3D 10 的常见问题,从将现有应用程序从 Direct3D 9 (D3D9) 移植到 Direct3D 10 (D3D10) 的开发人员的角度来看。
常量缓冲区
更新常量缓冲区的最佳方式是什么?
UpdateSubresource 和 Map with Discard 的速度应大致相同。 根据哪个副本复制最少的内存量在它们之间进行选择。 如果已将数据存储在一个连续块的内存中,请使用 UpdateSubresource。 如果需要从其他地方累积数据,请使用 Map with Discard。
组织常量缓冲区的最差方法是什么?
通过将特定着色器的所有常量放入一个常量缓冲区来实现最差的性能。 虽然这通常是从 D3D9 移植到 D3D10 的最简单方法,但它可能会妨碍性能。 例如,考虑使用以下常量缓冲区的方案:
cbuffer VSGlobalsCB
{
matrix ViewProj;
matrix Bones[100];
matrix World;
float SpecPower;
float4 BDRFCoefficients;
float AppTime;
uint2 RenderTargetSize;
};
缓冲区为 6560 字节。 假设有一个应用程序要呈现 1000 个对象,其中 100 个是皮肤网格,900 个是静态网格。 此外,假设此应用程序使用一个光源的阴影映射。 这意味着有两个通道,一个用于从光线渲染的深度贴图,一个用于向前呈现通道。 这会导致 2000 个绘图调用。 尽管每个绘制调用不需要更新常量缓冲区的每个部分,但整个常量缓冲区仍会更新并发送到卡。 这会导致每帧更新 13 MB 的数据, (2000 绘图调用次数为 6560 KB) 。
组织常量缓冲区的最佳方式是什么?
最佳方法是按更新频率组织常量缓冲区。 以类似频率更新的常量应位于同一缓冲区中。 例如,请考虑在“组织常量缓冲区的最坏方法是什么?”下提供的方案,但具有更好的常量布局:
cbuffer VSGlobalPerFrameCB
{
float AppTime;
};
cbuffer VSPerSkinnedCB
{
matrix Bones[100];
};
cbuffer VSPerStaticCB
{
matrix World;
};
cbuffer VSPerPassCB
{
matrix ViewProj;
uint2 RenderTargetSize;
};
cbuffer VSPerMaterialCB
{
float SpecPower;
float4 BDRFCoefficients;
};
常量缓冲区按更新频率拆分,但这只是解决方案的一半。 应用程序需要正确更新常量缓冲区才能充分利用拆分。 我们将假设上述场景相同:900 个静态网格、100 个皮肤网格、1 个光通和 1 个向前传递。 我们还假设将存储每个对象的一些常量缓冲区。 这意味着每个对象将包含 VSPerSkinnedCB 或 VSPerStaticCB,具体取决于它是皮肤化还是静态的。 我们这样做是为了避免使通过管道发送的矩阵数量翻倍。
我们将帧拆分为三个阶段。 第一阶段是帧的开始,不涉及呈现,只是不断更新。
开始帧
- 将 VSGlobalPerFrameCB 更新为应用程序时间 (4 个字节)
- 更新 100 个皮肤对象的 100 VSPerSkinnedCB (640000 字节)
- 更新 900 个静态对象的 VSPerStaticCB (57600 字节)
接下来是阴影贴图传递。 请注意,实际更新的唯一常量缓冲区是 VSPerPassCB。 在开始帧传递期间更新了所有其他常量缓冲区。 尽管我们仍然需要绑定这些常量缓冲区,但传递给视频卡的信息量很小,因为缓冲区已更新。
阴影通道
- 更新 VSPerPassCB (72 字节)
- 绘制 100 个 (100 个绑定的皮肤网格,没有更新)
- 绘制 900 个静态网格 (100 个绑定,不更新)
同样,前向呈现传递只需更新每个材料的数据,因为它不是按网格存储的。 如果假设场景中使用了 500 种材料:
正向传递
- 更新 VSPerPassCB (72 字节)
- 更新 500 VSPerMaterialCBs (10000 字节)
这导致总共只有 707 KB。 尽管这是一个非常经过操作的方案,但它说明了通过按更新频率对常量进行排序可以减少多少常量更新开销。
如果我没有足够的空间来存储网格、材料等的各个常量缓冲区,该怎么办?
始终可以使用常量缓冲区的分层系统。 创建大小可变的常量缓冲区 (16 字节、32 字节、64 字节等,) 最大所需的最大常量缓冲区大小。 将常量缓冲区绑定到着色器时,请选择可以保存着色器所需数据的最小常量缓冲区。 虽然此方法的效率略低,但它是一个很好的中间步骤。
我在不同的着色器之间共享常量缓冲区。 一个着色器可以使用所有常量,而另一个着色器可能使用某些常量。 更新这些内容的最佳方式是什么?
一种方法是进一步拆分常量缓冲区。 但是,有一个点,即绑定了太多常量缓冲区。 在这种情况下,将多个着色器可能未使用的常量移动到常量缓冲区的末尾。 从着色器获取变量数据时,请使用D3D10_SHADER_VARIABLE_DESC中的D3D10_SVF_USED标志来确定是否使用了变量。 通过将未使用的变量放在常量缓冲区的末尾,可以将较小的缓冲区绑定到不使用这些变量的着色器,从而节省更新成本。
如果我每帧只上传一次角色的骨头,而不是每次通过/绘制一次,我可以提高多少帧速率?
可以根据冗余数据量将帧速率提高 8% 到 50%。 在最坏的情况下,性能不会降低。
我一次应该绑定多少个常量缓冲区?
绑定将所有数据获取到着色器中所需数目的最小常量缓冲区。 在现实方案中,建议使用 5 个常量缓冲区。 在着色器之间共享常量缓冲区 (将同一 CB 绑定到 VS 和 PS) 也可以提高性能。
在不使用常量缓冲区的情况下绑定常量缓冲区是否有费用?
是的,如果你实际上不打算使用缓冲区,则不要调用 VSSetConsantBuffer 或 PSSetConstantBuffer。 在多次绘制调用过程中,这种额外的 API 开销可能会增加。
状态
在 D3D10 中管理状态的最佳方式是什么?
最佳解决方案是提前了解所有状态,并提前创建状态对象。 这意味着在呈现时,状态绑定是唯一需要执行的操作。 D3D10 还会筛选出重复项。
我的游戏已动态加载或包含用户生成的内容。 我无法提前加载所有状态对象。 我该怎么办?
这里有两种解决方案。 第一种是仅动态创建状态对象,并让 D3D10 筛选出重复项。 但是,对于每个帧具有许多状态对象更改的方案,不建议这样做。 更好的解决方案是自行对状态对象进行哈希处理,并且仅在哈希表中找不到符合要求的状态对象时才创建状态对象。 使用自定义哈希表的原因是,应用程序可以根据特定于该应用程序的使用方案选择快速哈希。 例如,如果应用程序仅更改 BlendState 中的 rendertargetwritemask 并保留所有其他值,则应用程序可以从 rendertargetwritemask 而不是整个结构生成哈希。
AlphaTest 状态已消失。 它去哪儿了?
AlphaTest 现在应该是着色器的性能。 请参阅 FixedFuncEMU 示例。
用户剪裁平面发生了什么情况?
用户剪辑平面已移动到着色器中。 有两种方法可以处理此问题。 第一种是从顶点着色器或几何着色器输出SV_ClipDistance。 另一个选项是基于顶点着色器或几何着色器向下传递的某个值在像素着色器中使用弃元。 试验两者,了解哪种方案更适合你的特定方案。 使用 SV_ClipDistance 可能会导致硬件使用基于几何图形的剪裁例程,这可能会导致几何图形绑定绘图调用运行速度变慢。 同样,使用放弃会将工作转移到像素着色器,这可能会导致像素绑定绘制调用运行速度变慢。
清除不遵循任何状态设置,例如我的光栅器状态中的剪刀矩形设置。
清除已与管道状态分离。 若要获取 D3D9 样式的行为,请通过绘制全屏四边形图来模拟清除。
将状态重新设置为默认值,以尝试诊断呈现错误。 现在,我的屏幕只显示黑色,尽管我知道我正在在屏幕上绘制对象。
将状态重新设置为 null) (默认值时,请确保对 OMSetBlendState 的调用中的 SampleMask 永远不会为零。 如果 SampleMask 设置为零,则所有样本都将在逻辑上以零方式与 AND 一起显示。 在此方案中,任何示例都不会通过混合测试。
D3DSAMP\SRGBTEXTURE 状态去了哪里?
SRGB 作为采样器状态的一部分被删除,现在已绑定到纹理格式。 绑定 SRGB 纹理将导致在 Direct3D 9 中指定D3DSAMP_SRGBTEXTURE时得到的相同采样。
格式
哪种 D3D9 格式对应于哪种 D3D10 格式?
有关信息,请参阅 Direct3D 9 到 Direct3D 10 注意事项。
A8R8G8B8纹理格式发生了什么变化?
它们在 D3D10 中已弃用。 可以将纹理重新作为R8G8B8A8,也可以在加载时重排,也可以在着色器中重排。
如何实现使用淡化纹理?
将调色板置于纹理或常量缓冲区中,并将其绑定到管道。 在像素着色器中,使用淡出纹理中的索引执行间接查找。
这些新的 SRGB 格式是什么?
SRGB 作为采样器状态的一部分被删除,现在已绑定到纹理格式。 绑定 SRGB 纹理将导致在 Direct3D 9 中指定D3DSAMP_SRGBTEXTURE时得到的相同采样。
三角形球迷去哪儿了?
D3D10 中已弃用三角形风扇。 需要在内容管道中或在加载时转换三角形风扇。
着色器链接
我的 Direct3D 9 着色器可正常编译为着色器模型 4.0,但在将它们绑定到管道时,使用调试运行时的调试输出中会显示链接错误。
着色器链接在 D3D10 中要严格得多。 必须按照上一阶段输出的顺序读取后续阶段中的元素。 例如:
顶点着色器输出:
float4 Pos : SV_POSITION;
float3 Norm : NORMAL;
float2 Tex : TEXCOORD0;
像素着色器读取:
float3 Norm : NORMAL;
float2 Tex : TEXCOORD0;
虽然像素着色器中不需要位置,但这将导致链接错误,因为位置是从顶点着色器输出的,但像素着色器不会读取位置。 更正确的版本如下所示:
顶点着色器输出:
float3 Norm : NORMAL;
float2 Tex : TEXCOORD0;
float4 Pos : SV_POSITION;
像素着色器读取:
float3 Norm : NORMAL;
float2 Tex : TEXCOORD0;
在这种情况下,顶点着色器输出相同的信息,但现在像素着色器正在按顺序输出读取内容。 由于像素着色器在 Tex 之后未读取任何内容,因此我们不必担心 VS 输出的信息比 PS 读取的要多。
我需要着色器签名才能创建输入布局,但在创建着色器之前,我加载网格并创建布局。 我该怎么办?
一种解决方案是在加载网格之前切换顺序并加载着色器。 然而,这说起来容易做起来难。 应用程序需要时,始终可以按需创建输入布局。 必须保留着色器签名的一个版本。 应基于着色器和缓冲区布局创建哈希,并且仅当不存在匹配的输入布局时才创建。
绘图调用
D3D10 的绘图调用达到 60 Hz 的限制是多少? 30 Hz?
由于每个绘图调用的 CPU 成本,Direct3D 9 对绘图调用数有限制。 在 Direct3D 10 上,每次绘图调用的成本都已降低。 但是,绘制调用和帧速率之间不再存在明确的相关性。 由于绘图调用通常需要许多支持调用 ( 不断的缓冲区更新、纹理绑定、状态设置等) API 的帧速率影响现在更依赖于整体 API 使用情况,而不是简单地绘制调用计数。
资源
应将哪种资源使用类型用于哪些操作?
使用以下备忘单:
- CPU 每帧多次更新资源:D3D10_USAGE_DYNAMIC
- CPU 每帧更新的资源少于一次:D3D10_USAGE_DEFAULT
- CPU 不更新资源:D3D10_USAGE_IMMUTABLE
- CPU 需要读取资源:D3D10_USAGE_STAGING
由于常量缓冲区始终要频繁更新,因此它们不符合“备忘单”。有关用于常量缓冲区的资源类型,请参阅 常量缓冲区 部分。
DrawPrimitiveUP 和 DrawIndexedPrimitiveUP 发生了什么变化?
它们在 D3D10 中消失。 对于动态几何图形,请使用大型D3D10_USAGE_DYNAMIC缓冲区。 在框架的开头,用D3D10_MAP_WRITE_DISCARD映射它。 对于每个后续绘制调用,将写入指针推进到之前绘制的顶点的位置,并使用D3D10_MAP_WRITE_NO_OVERWRITE映射缓冲区。 如果在帧结束前接近缓冲区的末尾,请将写入指针环绕到开头,并使用D3D10_MAP_WRITE_DISCARD映射。
是否可以将 16 位索引和 32 位索引写入同一动态几何缓冲区?
可以,但这可能会导致某些硬件的性能损失。 为动态 16 位索引数据和 32 位索引数据创建单独的缓冲区更安全。
如何实现将数据从 GPU 读回 CPU?
必须使用过渡资源。 使用 CopyResource 将数据从 GPU 资源复制到暂存资源。 映射暂存资源以读取数据。
我的应用程序依赖于 StretchRect 功能。
由于这实质上是 Direct3D 基本功能的包装器,因此它已从 API 中删除。 某些 StretchRect 功能已移动到 D3DX10LoadTextureFromTexture 中。 对于格式转换和复制纹理,D3DX10LoadTextureFromTexture 可以执行该工作。 但是,从一种大小转换为另一种大小之类的操作可能需要在应用程序中执行呈现到纹理的操作。
对资源的映射调用没有偏移量或大小。 这些是在 Direct3D 9 上的锁定呼叫上;他们为什么会改变?
Direct3D 9 上锁调用的偏移量和大小基本上是 API 混乱,驱动程序通常会忽略。 应改为由应用程序从 Map 调用中返回的指针计算偏移量。
深度即纹理
哪个更快? 使用深度作为纹理,还是将深度写入 alpha 并读取该内容?
这是特定于应用程序和硬件的。 使用可节省最多带宽的哪一个。 如果已使用多个呈现目标并具有额外的通道,则从着色器写入深度可能是更好的解决方案。 此外,将深度写入 alpha 或其他呈现目标允许写入线性深度值,从而加快需要访问深度缓冲区的计算速度。
只要我禁用深度写入,我是否可以将纹理绑定为输入,并绑定为深度模具纹理?
不在 D3D10 中。
MSAA
是否可以解析 MSAA 深度模具纹理?
不在 D3D10 中。 但是,可以从 MSAA 纹理中采样单个样本。 有关详细信息,请参阅 HLSL 部分。
为什么启用 MSAA 后,应用程序会崩溃?
确保启用驱动程序实际枚举的 MSAA 样本计数和质量编号。
崩溃
我的应用程序在 D3D10 或驱动程序中崩溃,我不知道原因。
第一步是启用调试 运行时 (传递到D3D10CreateDevice) 的D3D10_CREATE_DEVICE_DEBUG标志。 这会将最常见的错误公开为调试输出。
当我尝试将应用程序用于 PIX 时,PIX 崩溃。
第一步是启用传递到 D3D10CreateDevice) D3D10_CREATE_DEVICE_DEBUG标志 (调试运行时。 如果调试输出不干净,PIX 崩溃的可能性要高得多。
我的游戏在 D3D10 下的 32 位 Vista 上的虚拟地址空间不足。 它在 D3D9 上没有问题。
D3D10 和虚拟地址空间存在一些问题。 此问题已在 KB940105 中修复。 如果这不能解决你的问题,请确保你创建的可映射 (锁定) 在 D3D10 中的资源不会超过在 D3D9 中创建的资源。 还要考虑移植到 64 位,因为这将来会变得更加普遍。
谓词呈现
我根据) 的封闭查询结果使用了 Predicated 呈现 (。 为什么我的应用速度仍然相同?
首先,确保要跳过的呈现实际上是应用程序瓶颈。 如果这不是瓶颈,则跳过呈现将无助于帧速率。
其次,请确保在查询问题和要谓词的呈现之间经过了足够的时间。 如果查询在呈现器调用命中 GPU 时尚未完成,则呈现仍将发生。
第三,预测只跳过某些调用。 跳过的调用包括 Draw、Clear、Copy、Update、ResolveSubresource 和 GenerateMips。 状态设置、IA 设置、映射和创建调用不遵循谓词。 如果围绕要进行谓词的绘图调用有很多状态设置调用,仍将设置这些状态。
几何着色器
我是否应该使用几何着色器来细化 (在此处插入任何内容) ?
否。 不应使用几何着色器进行分割。
是否可以使用几何着色器创建几何图形?
是的,在非常有限的方案中。 当前 D3D10 (2008) 部件中的几何着色器无法处理大量扩展。 在将来,这可能会有变化。 由于现有的点子画面硬件,视频卡供应商可能有一到四个扩展的特殊路径。 任何其他扩张都必须非常有限。 ParticlesGS 和 PipesGS 样本仅通过执行有限的扩展来实现高帧速率。 每个帧仅展开几个点。
应将几何着色器用于什么用途?
需要对整个基元执行操作的任何内容,例如剪影检测、偏心坐标等。 还使用它来选择要将基元发送到的呈现目标数组的哪个切片。
是否可以从几何着色器输出可变数量的几何图形?
是的,但这可能会导致性能问题。 以一次调用输出 1 个点,为另一个调用输出 4 个点的示例。 虽然符合扩展准则,但这可能会导致几何着色器线程以串行方式运行。
D3D10 如何为网格生成相邻索引? 或者,当我指定几何着色器需要相邻信息时,为什么 D3D10 无法正确呈现。
相邻信息不是由 D3D10 创建的,而是由应用程序创建的。 相邻索引由应用程序生成,每个基元必须包含六个索引;在六个中,奇数索引是边缘相邻顶点。 ID3DX10Mesh::GenerateAdjacencyAndPointsReps 可用于生成此数据。
HLSL
整数和按位指令是否慢?
它们可以。 各种 D3D10 卡可能只能对可用 ALU 单位的子集发出整数操作。 这高度依赖于硬件。 有关如何解决该特定硬件上的整数操作的建议,请参阅单个硬件供应商。 此外,请非常小心类型之间的强制转换。
VPOS 发生了什么情况?
如果将像素着色器的输入声明为SV_POSITION,你将获得与声明为 VPOS 相同的行为。
如何实现对 MSAA 纹理采样?
在着色器中,将纹理声明为 Texture2DMS。 然后,可以使用 Texture2DMS 对象的 Sample 方法提取单个样本。
如何实现判断是否实际使用了常量缓冲区中的着色器变量?
查看该变量的反射D3D10_SHADER_VARIABLE_DESC结构。 uFlags 应设置D3D10_SVF_USED标志。
如何实现判断常量缓冲区中的着色器变量是否实际使用 FX10?
目前无法使用 FX10 执行此操作。
我无法控制 FX10 创建的常量缓冲区。 如何创建和更新它们?
所有 FX10 管理的常量缓冲区都创建为D3D10_USAGE_DEFAULT资源,并使用 UpdateSubresource 进行更新。 由于 FX10 保留所有常量数据的后备存储,因此 UpdateSubresource 是更新这些数据的最佳方法。
如何实现使用着色器模拟固定函数管道?
请参阅 FixedFuncEMU 示例。
是否应使用新的 \[unroll\]、\[loop\]、\[branch\]等编译器提示?
一般来说是不可以的。 编译器通常会尝试这两种方法,并选择最快的方法。 在某些情况下,可能需要使用 [展开],例如,当循环内的纹理提取需要访问渐变时。
部分精度对 D3D10 有何影响? 可以在 D3D9 HLSL 中指定部分精度,但在 D3D10 HLSL 中不能指定部分精度。
所有 D3D10 操作都指定为以 32 位浮点精度运行。 因此,部分精度不应在 D3D10 中产生任何差异。
在 D3D9 中,我可以通过将深度缓冲区绑定为纹理并使用常规 tex2d hlsl 指令来执行 HW PCF 阴影筛选。 如何实现 D3D10 上执行此操作?
必须使用比较采样器状态并使用 SampleCmp 指令。
如何在 D3D10 中注册关键字 (keyword) ?
D3D10 中的寄存器关键字 (keyword) 现在适用于特定资源绑定到的槽。 在这种情况下,资源可以是缓冲区 (常量,或者) 、纹理或采样器。
- 对于常量缓冲区,请使用语法:register (bN) ,其中 N 是输入槽 (0-15)
- 对于纹理,请使用语法:register (tN) ,其中 N 是输入槽 (0-127)
- 对于采样器,请使用语法:register (sN) ,其中 N 是输入槽 (0-127)
如果寄存器仅用于指定绑定整个缓冲区的位置,如何实现将变量放置在常量缓冲区内?
使用 packoffset 关键字 (keyword) 。 packoffset 的参数的格式为 c[0-4095]。[x,y,z,w]。 例如:
cbuffer cbLotsOfEmptySpace
{
float IWaste2Floats : packoffset(c0.z);
float4 IWasteMore : packoffset(c13);
};
在此常量缓冲区中,IWaste2Floats 放置在常量缓冲区中的第三个浮点 (第 12 个字节) 。 IWasteMore 放置在常量缓冲区中的第 13 个 float4 或第 52 个浮点。