使用 glBufferData 将向量的向量传递给 SSBO 时出现段错误

Segfault when passing vector of vectors to SSBO using glBufferData

我试图将向量的向量传递给 SSB0,但是在使用 glBufferData 传递它时出现段错误。 C++中的结构是:

const uint16_t MAX_NODE_POOLS = 1927;

union Node
{
    uint32_t childDescriptor;
    uint32_t material;
};

struct NodePool
{
    NodePool() : mNodes({0}) {}
    std::array<Node, 8> mNodes;
};

struct Block
{
    Block(): ID(0) {}
    uint16_t ID;
    std::vector<NodePool> mNodePools;
    std::vector<uint16_t> mNodeMasks;
};

class Octree
{
public:
    ...
    void registerSSBO(GLuint &octreeSSBO) const;
    void generate();
    
    [[nodiscard]] inline uint64_t getMem() const { return mBlocks.size() *
    (
            sizeof(uint16_t) +                      // ID
            (sizeof(NodePool)*MAX_NODE_POOLS) +     // NodePools
            (sizeof(uint16_t)*MAX_NODE_POOLS)       // NodeMasks
            ); }
private:
     ...
    std::vector<Block> mBlocks;
};

...

void Octree::Octree::registerSSBO(GLuint &octreeSSBO) const
{
    glGenBuffers(1, &octreeSSBO);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, octreeSSBO);
    std::cout << getMem() << std::endl;
    glBufferData(GL_SHADER_STORAGE_BUFFER, getMem(), mBlocks.data(), GL_DYNAMIC_DRAW);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, octreeSSBO);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
}

我用数据填充块,然后像这样传入 SSBO

...
octree.generate();
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

octree.registerSSBO(octreeSSBO);
glBindVertexArray(0);
...

在我的着色器中,我的 SSBO 结构如下

#version 430 core
// To use 16 bit integers
#extension GL_NV_gpu_shader5 : enable
#define MAX_NODE_POOLS 1927

struct Node
{
    // Either a child descriptor or material depending on mNodeMasks
    int data;
};

struct NodePool
{
    Node mNodes[8];
};

struct Block
{
    uint16_t ID;
    NodePool mNodePools[MAX_NODE_POOLS];
    uint16_t mNodeMasks[MAX_NODE_POOLS];
};

layout (std430, binding=2) buffer octreeData
{
    Block blocks[];
};

每次它在 registerSSBO

内的 glBufferData 上发生段错误
glBufferData(GL_SHADER_STORAGE_BUFFER, getMem(), mBlocks.data(), GL_DYNAMIC_DRAW);

getMem() 在这种情况下 returns 大小为 35773920 字节,这是我期望的值。我计算不正确吗? mBlocks.size()*sizeof(mBlocks)mBlocks.size()*sizeof(Block) 等较小的值不会导致应用程序出现段错误(但应用程序的行为不符合预期)

运行 valgrind 阻止了段错误的发生,但是在 glBufferData 调用中给了我 20 个警告 Invalid read of size 16,但我无法准确地弄清楚这可能表明什么?

在每个单独的警告中,它都会给我这样的问题:

Invalid read of size 16
...
Address 0x4cd830b0 is 16 bytes before a block of size 61,664 alloc'd
Invalid read of size 16
...
Address 0x4cd83080 is 0 bytes after a block of size 57,344 alloc'd
Invalid read of size 16
...
Address 0x4cd830a0 is 32 bytes before a block of size 61,664 in arena "client"

等等

这是无关的样板还是我遗漏了什么?

编辑:

为了表明向量的大小正确,我已将 getMem() 更改为以下函数,结果相同

inline uint64_t getMem() const 
{
    uint64_t sum = 0;
    for(const auto& b: mBlocks)
    {
        for(const auto& np: b.mNodePools)
            sum += sizeof(np);
        for(const auto& nm: b.mNodeMasks)
            sum += sizeof(nm);
        sum += sizeof(b.ID);
    }
    return sum;
}
uint16_t ID;
std::vector<NodePool> mNodePools;
std::vector<uint16_t> mNodeMasks;
...
glBufferData(GL_SHADER_STORAGE_BUFFER, getMem(), mBlocks.data(), GL_DYNAMIC_DRAW);

你不能那样做。您不能将大多数 C++ 标准库类型按字节复制到 OpenGL(或与此相关的任何其他内容)。作为一般规则,如果一个类型不是 trivially copyable(并且 vector 肯定是 不是 ),它绝对不能像这样在 OpenGL 中抛出(注意: 这并不意味着您可以在 OpenGL 中抛出任何简单的可复制类型。简单的可复制性是必要的但还不够)。

您对 std::array 的使用有效(也许。C++ 标准不保证您认为它对 array 的布局做了什么)因为 array<T> 是在没有显式构造函数的情况下定义的.因此,在 T 可平凡复制的范围内,它将平凡可复制。

如果您要将 C++ 对象复制到 GLSL,则 C++ 类型和布局必须与 GLSL 定义的相匹配。 std::vector 绝不匹配任何 GLSL 数组的布局。如果您的 GLSL 定义了 X 项的数组,那么唯一肯定会匹配的 C++ 类型是实际的 X 项数组(同样,必要但不足够足够)。

不多也不少。