如何最好地组织常量缓冲区

How to best organize constant buffers

我在思考如何在我正在制作的非常基本的 D3D11 引擎中组织常量缓冲区时遇到了一些麻烦。

我的主要问题是:最大的性能影响发生在什么地方?当使用 Map/Unmap 更新缓冲区数据或绑定 cbuffer 本身时?

目前,我正在为一种“着色器包装器”在以下两种实现之间做出决定class:

持有14个ID3D11Buffer*s的数组

class VertexShader
{
...

public:
    Bind(context)
    {
        // Bind all 14 buffers at once
        context->VSSetConstantBuffers(0, 14, &m_ppCBuffers[0]);
        context->VSSetShader(pVS, nullptr, 0);
    }

    // Set the data for a buffer in a particular slot
    SetData(slot, size, pData)
    {
        D3D11_MAPPED_SUBRESOURCE mappedBuffer = {};
        context->Map(buffers[slot], 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedBuffer);
        memcpy(mappedBuffer.pData, pData, size);
        context->Unmap(buffers[slot], 0);
    }


private:
    ID3D11Buffer*       buffers[14];
    ID3D11VertexShader* pVS;
}

这种方法会让着色器将所有 cbuffer 绑定到一批 14 个中。如果着色器将 cbuffer 注册到 b0、b1、b3,则数组看起来像 -> [cb|cb|0|cb| 0|0|0|0|0|0|0|0|0|0]

知道如何绑定自身的常量缓冲区包装器

class VertexShader
{
...

public:
    Bind(context)
    {
        // all the buffers bind themselves
        for(auto cb : bufferMap)
            cb->Bind(context);

        context->VSSetShader(pVS, nullptr, 0);
    }

    // Set the data for a buffer with a particular ID
    SetData(std::string, size, pData)
    {
        // table lookup into bufferMap, then Map/Unmap
    }


private:
    std::unordered_map<std::string, ConstantBuffer*> bufferMap;
    ID3D11VertexShader* pVS;
}

这种方法会将“ConstantBuffers”保存在散列 table 中,每个人都会知道它绑定到哪个插槽以及如何将自己绑定到管道。我将不得不为每个 cbuffer 单独调用 VSSetConstantBuffers(),因为 ID3D11Buffer*s 不再是连续的,但组织更友好并且浪费更少 space.

您通常如何组织 CBuffer、着色器、SRV 等之间的关系?不是在寻找一个万能的解决方案,而是一些一般性的建议和可以从希望比我更有经验的人那里阅读更多的东西

另外,如果@Chuck Walbourn 看到了这个,我是你工作的粉丝并且在这个项目中使用 DXTK/WiCTextureLoader!

谢谢。

Constant Buffers 是 Direct3D 10 的一项主要功能,因此关于该主题的最佳演讲之一可以追溯到 Gamefest 2007:

Windows to Reality: Getting the Most out of Direct3D 10 Graphics in Your Games

另见 Why Can Updating Constant Buffers be so painfully slow? (NVIDIA)

最初的目的是让 CB 按更新频率组织:比如一个 CB 用于设置的内容 'per level',另一个用于内容 'per frame',另一个用于 'per object' ,另一个 'per pass' 等。因此假设是,如果您更改了 CB 的任何部分,您将上传整个内容。 CPU 和 GPU 之间的带宽是这里真正的瓶颈。

要使这种方法有效,您基本上需要将所有着色器设置为使用相同的方案。这可能很难管理,尤其是当这么多现代 material 系统都是艺术驱动的时候。

CB 的另一种方法是像动态 VB 一样使用它们进行粒子提交,您可以在其中填充短暂的常量,提交工作,然后在每一帧重置事物。在许多情况下,这种方法基本上是人们为 DirectX 12 所做的。问题是没有更新部分 CB 的能力,它太慢了。 DirectX 11.1 中的“部分常量缓冲区更新和偏移”可选功能是实现此功能的一种方式。也就是说,Windows 7 不支持此功能,'optional' 较新版本的 Windows,所以你必须支持两个代码路径才能使用它。

TL;DR: 从技术上讲,您可以一次绑定很多 CB,但关键是要让那些经常变化的 CB 保持较小的个体大小。还假设对 CB 的任何更改都需要在每次更改时将整个内容更新到 GPU。