c++ 不正确的常量缓冲区大小

wqsoz72f  于 2023-08-09  发布在  其他
关注(0)|答案(1)|浏览(104)

我创建了一个常量缓冲区:

// C++    
struct IndexConstantBuffer
{
    unsigned indexes[32]{};
};

// hlsl
cbuffer IndexConstantBuffer : register(b0)
{
    uint indexes[32];
};

字符串
我收到以下警告:
D3 D11警告:ID3D11DeviceContext::DrawIndexedInstanced:像素着色器单元的插槽0处的常量缓冲区的大小太小(提供了128个字节,预期至少为512个字节)。这是可以的,因为越界读取被定义为返回0。开发人员也可能知道无论如何都不会使用丢失的数据。只有当开发人员实际上打算为着色器所需的内容绑定足够大的常量缓冲区时,这才是一个问题。[执行警告#351:DEVICE_DRAW_CONSTANT_BUFFER_TOO_SMALL]
是什么原因导致此警告?我是否需要添加384字节(512 - 128)的填充,或者有其他方法?

fae0ux8s

fae0ux8s1#

这是一个老问题,但让我们试着提供一个明确的答案。
要解决此错误,您必须确保C数据结构与HLSL数据结构完全一致。C和HLSL的对齐规则并不相同--它们是非常不同的语言,只是碰巧有一些相似之处。尽管可以强制C对齐方式与HLSL完全一样,但这样的示例将导致额外的填充(消耗额外的内存),而不会带来任何性能上的好处。在这种情况下,您将消耗4倍的内存(512字节,而不是所需的最佳128字节)。
数组元素在HLSL中总是封装在16字节边界上,而在C
中封装在4字节边界上(默认)。当元素是4字节整数时,这显然是低效的,但当处理float 4元素(四个分量向量)时是最佳的,这是HLSL中最常见的数据类型。这是因为GPU可以用一条指令访问128位,因此可以一次检索float 4的所有4个分量。
为了避免在数组中插入不必要的填充,C数组需要Map到HLSL中类型为uint 4的8元素数组。换句话说,HLSL中的每个元素都成为uint类型的4分量向量。
此外,在C
中指定元素类型时应该更加精确。unsigned类型(意味着unsigned int)具有实现定义的大小(以chars为单位)。虽然在大多数情况下它是4chars,但您不能保证在所有实现中都是如此。即使是char的长度也不能保证是8位。然而,C标准(从C11开始)定义了一组固定宽度的积分,当你需要一个特定大小的积分时,这些积分应该是首选的,比如在C++中声明一个包含一个或多个积分的HLSL数据结构时。
执行以下修改将解决此错误:

// C++
#include <cstdint> // for uint32_t    
struct IndexConstantBuffer
{
    // unsigned indexes[32]{}; // Implementation-defined size
    uint32_t indexes[32]{}; // Always 128-bytes
};

// hlsl
cbuffer IndexConstantBuffer : register(b0)
{
    // uint indexes[32];  // (4 + 12) * 32 = 512 (bytes) incl. padding
    uint4 indexes[8]; // (4 * 4) * 8 = 128 (bytes) no padding
};

字符串
当然,访问HLSL中的元素现在需要将每个元素视为uint 4(四个uint元素的向量),而不是单个uint,但是这在HLSL中解决起来很简单。

相关问题