常量缓冲区大小不正确

问题描述 投票:0回答:1

我创建了以下常量缓冲区:

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

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

我收到以下警告:

D3D11 警告:ID3D11DeviceContext::DrawIndexedInstanced:的大小 像素着色器单元插槽 0 处的常量缓冲区太小 (提供 128 字节,预计至少 512 字节)。这没问题,因为 越界读取被定义为返回 0。也有可能 开发人员知道丢失的数据无论如何都不会被使用。这只是 如果开发人员实际上打算充分绑定一个问题 满足着色器期望的大型常量缓冲区。 [ 执行警告 #351:DEVICE_DRAW_CONSTANT_BUFFER_TOO_SMALL]

什么原因导致此警告?我需要添加 384 字节 (512 - 128) 的填充还是有其他方法?

c++ directx hlsl
1个回答
1
投票

这是一个老问题,但让我们尝试提供明确的答案。

要解决此错误,您必须确保 C++ 数据结构与 HLSL 数据结构完全一致。 C++ 和 HLSL 的对齐规则并不相同——它们是非常不同的语言,只是碰巧有一些相似之处。尽管您可以强制 C++ 对齐的行为与 HLSL 完全相同,但此类实例将产生额外的填充(消耗额外的内存),而不会带来任何性能优势。在这种情况下,您将消耗 4 倍的内存(512 字节,而不是所需的最佳 128 字节)。

在 HLSL 中,数组元素始终打包在 16 字节边界上,而在 C++ 中则打包在 4 字节边界上(默认情况下)。当元素是 4 字节整数时,这显然效率低下,但在处理 float4 元素(四个分量向量)时是最佳的,这是迄今为止 HLSL 中最常见的数据类型。这是因为 GPU 可以使用一条指令访问 128 位,因此可以一次检索 float4 的所有 4 个组件。

为了避免在数组中插入不必要的填充,您的 C++ 数组需要映射到 HLSL 中的 uint4 类型的 8 元素数组。换句话说,HLSL 中的每个元素都成为 uint 类型的 4 分量向量。

此外,在 C++ 中指定元素类型时应该更加精确。 unsigned 类型(意味着 unsigned int)具有实现定义的大小(以 char 为单位)。虽然在大多数情况下它是 4 个char,但您不能保证在所有实现中都是如此。即使 char 的长度也不能保证是 8 位。但是,C++ 标准(自 C++11 起)定义了一组固定宽度积分,当您需要特定大小的积分时,例如在 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 中的元素现在需要将每个元素视为 uint4(四个 uint 元素的向量),而不是单个 uint,但是在 HLSL 中解决这个问题很简单。

© www.soinside.com 2019 - 2024. All rights reserved.