使用相同输入连续调用 ID3D11DeviceContext 状态的成本?

Cost of ID3D11DeviceContext state calls with same input consecutively?

这适用于任何 DX11 状态调用,无论是 XXSetConstantBuffers 还是 IASetVertexBuffers 等... 基本上执行以下操作的成本是多少:

ctx->VSSetConstantBuffers(0, 2, constBuffers);
ctx->VSSetConstantBuffers(0, 2, constBuffers);
ctx->VSSetConstantBuffers(0, 2, constBuffers);

这和下面的本质上是一样的吗?

ctx->VSSetConstantBuffers(0, 2, constBuffers);

以下面的网站为例 here table 1 表示 XXSetConstantBuffers 值 114 CPU 周期。令我印象深刻的是,仅检查设置的当前指针值并且仅在值不同时才执行这些操作几乎没有开销。这似乎也是非常重要的功能,例如,如果我在程序初始化时设置顶点缓冲区,那么我可以 re-set 每个帧开头的顶点缓冲区 确保 ,但就 GPU 而言,顶点缓冲区实际上仅在程序初始化时设置。

此外,我可以做到:

//for the sake of example, assuming only one vertex buffer is ever set.
ID3D11Buffer* vBuffer;
UINT stride, offset;
ctx->IAGetVertexBuffers(0, 1, &vBuffer, &stride, &offset);
if (vBuffer != pointerToBufferIWantToSet)
{
    UINT newStride = 8 * sizeof(FLOAT), newOffset = 0;
    ctx->IASetVertexBuffers(0, 1, &pointerToBufferIWantToSet, &newStride, &newOffset);
}//dont set vertex buffer if already set

但这似乎有问题,如果这导致 CPU 循环被避免,我觉得所有 DX 代码都应该这样做,但没有人这样做。所以,因此我觉得重要的是要问:

如果以上两个问题的答案都是 'no' 那么最好的方法似乎是制作我自己的状态 object 包装器,这似乎是一项不必要的工作。

对于你的第一个问题,我唯一能给出的一般答案是:"It depends." 据我所知,Direct3D11 不会跟踪其管道的状态以避免冗余状态更改。

一些供应商可能会选择在他们的驱动程序中实现这样的功能,但我不会指望那样。最好假设,调用同一个方法三次的成本是调用一次的成本的三倍。

关于你的第二个问题:是的,有开销*。每当您调用 DirectX 方法时都会发生上下文切换(从应用程序上下文切换到驱动程序上下文),这需要时间。

我见过的大多数渲染引擎都会记住上下文的当前状态,这与渲染调用排序相结合,证明非常有效。


(*) 这是基于我从有关操作系统和计算机图形学的讲座中学到的知识。它可能不适用于 Direct3D11 and/or 已过时。


编辑:解决评论中的问题,因为它不适合评论。

问:你能详细说明一下吗'render call sorting'?

频繁改变状态会导致惩罚。因此,我们希望减少所需的状态更改量。

让我们考虑一下更广泛的情况。在发出任何绘制调用之前,您通常会绑定缓冲区、着色器等。因此,对于每个绘制调用,我们都为其分配了一个状态(或配置)。我们可以使用状态对象的元组来表示该状态,如下所示:

vertex_buffer a;
index_buffer b;
shader c;
shader d;
render_target e;

pipeline_state state{a, b, c, d, e};

// Then later use...
set_pipeline_state(state);
render_amazing_stuff();

现在假设,我们要绘制 3 个对象,配置略有不同。

vertex_buffer a, b, c; // a stores the first model, b the second, ...
index_buffer f, g;

shader c;
shader d;
render_target e;

pipeline_state state1{a, f, c, d, e};
pipeline_state state2{b, f, c, d, e};
pipeline_state state3{b, g, c, d, e};

我们有 3 个流水线状态,只是略有不同。我们可以通过按管道状态对绘制调用重新排序来提高性能,以减少它们之间的差异。

set_pipeline_state(state1); // Set initial state, with all buffers..
render();
set_pipeline_state(state2); // Binds only the new vertex buffer; since it's the only difference.
render();
set_pipeline_state(state3); // Binds only the new index buffer; since it's the only difference.
render();

所有状态之间的差异很小,减少了状态变化量,有效地提高了每秒帧数。

所以,我在 GameDev stack exchange 上发现了一个老问题 可以发现here这是一个很相似的问题。这是一个摘录,它是公认的答案:

PSGetShader (and in general, all state getters in D3D) are not intended to be called at high frequency. You should do dirty state tracking, and you should implement it yourself. Using PSGetShader will at a minimum create the additional overhead of the AddRef which you don't need for dirty state tracking.

我认为这与 Julian 的回答相结合,产生了一个相当明确的信息,应用程序开发人员应该假设状态跟踪是由驱动程序处理的。