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

C++ 连续使用相同输入的ID3D11DeviceContext状态调用的成本?,c++,performance,graphics,direct3d,C++,Performance,Graphics,Direct3d,这适用于任何DX11状态调用,无论是XXSetConstantBuffers还是IASetVertexBuffers等。。。 基本上,执行以下操作的成本是多少: ctx->VSSetConstantBuffers(0,2,ConstantBuffers); ctx->VSSetConstantBuffers(0,2,constBuffers); ctx->VSSetConstantBuffers(0,2,constBuffers); 这基本上与以下内容相同吗 ctx->VSSetConstan

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

ctx->VSSetConstantBuffers(0,2,ConstantBuffers);
ctx->VSSetConstantBuffers(0,2,constBuffers);
ctx->VSSetConstantBuffers(0,2,constBuffers);
这基本上与以下内容相同吗

ctx->VSSetConstantBuffers(0,2,ConstantBuffers);
以下面的网站为例,表1说明
XXSetConstantBuffers
相当于114个CPU周期。我突然想到,只要检查设置的当前指针值,并且仅在值不同的情况下执行这些操作,几乎没有开销。这似乎也是非常重要的功能,例如,如果我在程序初始化时设置顶点缓冲区,那么我可以在每个帧的开头将该顶点缓冲区重新设置为,以确保,但就GPU而言,顶点缓冲区实际上只在程序初始化时设置

此外,我可以:

//为了举例,假设只设置了一个顶点缓冲区。
ID3D11Buffer*vBuffer;
单位步幅,偏移量;
ctx->IAGetVertexBuffers(0、1和vBuffer、跨步和偏移);
if(vBuffer!=指针ToBufferiWantToSet)
{
UINT newStride=8*sizeof(FLOAT),newOffset=0;
ctx->IASetVertexBuffers(0、1和pointerToBufferIWantToSet、newStride和newOffset);
}//如果已设置顶点缓冲区,则不要设置顶点缓冲区
但这似乎有点可疑,如果这导致避免CPU周期,我觉得所有DX代码都应该这样做,但没有人这样做。因此,我觉得有必要问:

  • 司机会处理吗
  • 调用设备上下文“getter”方法是否有开销

如果以上两个问题的答案都是“否”,那么最好的方法似乎是制作我自己的状态对象包装器,这似乎是不必要的工作量。

对于第一个问题,我能给出的唯一一般性答案是:“视情况而定。” 据我所知,Direct3D11不跟踪其管道的状态,以避免冗余状态更改

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

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

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


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


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


Q:您能详细介绍一下“渲染调用排序”吗

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

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

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个管道状态,它们之间的差别很小。我们可以通过按管道状态对draw调用重新排序来提高性能,以减少它们之间的差异

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堆栈交换中发现了一个老问题 可以发现这是一个非常相似的问题。以下是一段摘录,这是公认的答案:

PSGetShader(通常,D3D中的所有状态getter)不打算以高频率调用。您应该执行脏状态跟踪,并且您应该自己实现它。使用PSGetShader将至少创建AddRef的额外开销,脏状态跟踪不需要这些开销


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

你能详细说明一下“呈现呼叫排序”吗?快速谷歌并没有占上风。你是说抽签吗?@nedb看我的编辑;不适合发表评论。我知道,我并不总是最好地解释(或表达)概念。我找到了一个,这很好地解释了。