了解 DirectX 管道优化

Understanding DirectX pipeline optimisations

我正在尝试改进我已经实现的一些简单的 DirectX 渲染代码。我的想法是只在绝对必要时更新渲染管道,因为我的理解是尽可能减少管道修改的数量是有益的。以下伪代码证明了我的意思:

ID3D11VertexShader *t_shader = getVertexShader();
ID3D11DeviceContext->VSSetShader(t_shader, nullptr, 0);
// Do some other processing/pipeline setup without modifying t_shader
ID3D11DeviceContext->VSSetShader(t_shader, nullptr, 0);
ID3D11DeviceContext->Draw(10, 0);

这是低效的,因为我们在着色器未更改时调用 VSSetShader 两次。这是一个过度简化,但希望你明白我的意思,我的基本理解是这些不必要的 binds/calls 是低效的吗?

如果是这种情况,是否可以在两个单独的 ID3D11DeviceContext::Draw 调用之间进行以下优化? (同样是伪代码,所以请原谅遗漏的步骤,并假设我们需要做的就是在绘制之前设置顶点和像素着色器以及拓扑):

void Object1::Draw() {
    ID3D11VertexShader *t_vs = ShaderMgr::vertexShader1();
    ID3D11DeviceContext->VSSetShader(t_vs, nullptr, 0);

    ID3D11PixelShader *t_ps = ShaderMgr::pixelShader1();
    ID3D11DeviceContext->PSSetShader(t_ps, nullptr, 0);

    ID3D11DeviceContext->IASetPrimitiveTopology(ID3D11_PRIMITIVE_TOPOLOGY_LINELIST);
    ID3D11DeviceContext->Draw(m_vertexCount, 0);
}

void Object2::Draw() {
    ID3D11VertexShader *t_vs = ShaderMgr::vertexShader1();
    ID3D11DeviceContext->VSSetShader(t_vs, nullptr, 0);

    // Use a different pixel shader to Object1
    ID3D11PixelShader *t_ps = ShaderMgr::pixelShader2();
    ID3D11DeviceContext->PSSetShader(t_ps, nullptr, 0);

    ID3D11DeviceContext->IASetPrimitiveTopology(ID3D11_PRIMITIVE_TOPOLOGY_LINELIST);
    ID3D11DeviceContext->Draw(m_vertexCount, 0);
}

两个绘图调用之间的唯一区别是使用了不同的像素着色器。那么以下是一种可能的优化还是每次绘制调用都有效地重置管道?

void Object1::Draw() {
    // Removed common set code
    ID3D11PixelShader *t_ps = ShaderMgr::pixelShader1();
    ID3D11DeviceContext->PSSetShader(t_ps, nullptr, 0);
    ID3D11DeviceContext->Draw(m_vertexCount, 0);
}

void Object2::Draw() {   
    // Removed common set code
    ID3D11PixelShader *t_ps = ShaderMgr::pixelShader2();
    ID3D11DeviceContext->PSSetShader(t_ps, nullptr, 0);
    ID3D11DeviceContext->Draw(m_vertexCount, 0);
}

void drawObjects() {
    // Common states amongst object1 and object2
    ID3D11VertexShader *t_vs = ShaderMgr::vertexShader1();
    ID3D11DeviceContext->VSSetShader(t_vs, nullptr, 0);
    ID3D11DeviceContext->IASetPrimitiveTopology(ID3D11_PRIMITIVE_TOPOLOGY_LINELIST);

    m_object1->draw();

    // Don't bother setting the vs or topology here

    m_object2->draw();
}

任何 feedback/info 将不胜感激。

只是发布了我自己的问题的答案,因为我在我的测试代码中发现了一个错误,这个错误使问题变得模糊不清,希望这会对其他看到的人有所帮助。

我感到困惑的是,在我的测试代码中,我只有一个正在渲染的对象,一个简单的平面。它使用的唯一资源是顶点缓冲区、顶点着色器和像素着色器。我尝试添加上面提到的优化,以尝试尽可能减少 ID3D11DeviceContext 调用的次数。对于这个简单的对象,ID3D11DeviceContext 调用对我来说似乎是明智的,例如VSSetShader、PSSetShader 等应该只需要调用一次,因为这是唯一被渲染的对象。然而,情况并非如此,因为网格一旦消失就被渲染,并且再也不会被渲染。

在 RenderDoc 的帮助下,我能够捕获一个渲染帧,并注意到在我只期望一个时进行了两次绘制调用。我忘记了我有一个通过 DIrectXTK 创建的 SpriteFont 和 SpriteBatch class 用于写出我的相机位置以进行调试。这个调用在绕过我的管道 class(控制这些优化)的同时修改了管道状态,而我没有意识到。这意味着当第二次渲染网格时,管道处于不正确的状态。

事实证明,这些优化是可能的,并且管道不会因绘制调用而被清除。因此,如果您有类似上述示例的内容,那么在调用之间调用一次上下文调用就足够了。我还了解到,像 RenderDoc 或内置渲染调试器的 Visual Studios 这样的调试工具对于跟踪此类问题至关重要。