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

Posted

技术标签:

【中文标题】如何最好地组织常量缓冲区【英文标题】:How to best organize constant buffers 【发布时间】:2020-04-22 17:31:12 【问题描述】:

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

我的主要问题是:最大的性能损失发生在哪里?使用 Map/Unmap 更新缓冲区数据或绑定 cbuffers 时?

目前,我正在为一种“shader-wrapper”类选择以下两种实现:

持有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;

这种方法将使着色器将所有 cbuffers 绑定在一个批次中,每批 14 个。如果着色器的 cbuffers 注册到 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”保存在哈希表中,每个人都知道它绑定到哪个插槽以及如何将自己绑定到管道。我必须为每个 cbuffer 单独调用 VSSetConstantBuffers(),因为 ID3D11Buffer*s 不再是连续的,但组织更友好,浪费的空间也更少。

您通常如何组织 CBuffer、Shader、SRV 等之间的关系?不是在寻找万能的解决方案,而是从希望比我更有经验的人那里了解一些一般性建议和内容

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

谢谢。

【问题讨论】:

“我的主要问题是:最大的性能损失发生在哪里?” - 你应该分析你的代码并向你的分析器询问这个问题。 【参考方案1】:

常量缓冲区是 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 用于设置为“每级别”的内容,另一个用于设置“每帧”的内容,另一个用于“每个对象”,另一个用于“每遍” ' 等等。因此假设如果您更改了 CB 的任何部分,您将上传整个内容。 CPU 和 GPU 之间的带宽是这里真正的瓶颈。

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

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

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

【讨论】:

非常感谢!这些正是我正在寻找的资源类型。还有很多东西要学。

以上是关于如何最好地组织常量缓冲区的主要内容,如果未能解决你的问题,请参考以下文章

如何从 boost log sink 组织线程安全读取?

HLSL 统一变量与常量缓冲区

谈论如何有效地保护你的数据

编辑顶点数组中的缓冲区数据

如何优雅地实现环形缓冲区?

在 C++ 中重新组织缓冲区中数据的有效方法