Search code examples
rustgpuvulkanrenderdoc

rust vulkan, normals are messed up


I am trying to render a gltf model with ash (vulkan) in rust.

I sent all my data to the gpu and I am seeing this:

enter image description here

Naturally my suspicion is that the normal data is wrong. So I checked with renderdoc:

enter image description here

Those seem ok, maybe the attributes are wrong?

enter image description here

All those normals seem like they add to 1, should be fine. Maybe my pipeline is wrong?

enter image description here

Seems like the correct format and binding (I am sending 3 buffers and binding one to binding 0, one to 1 and the last to 2, binding 1 has the normals).

The only thing I find that is weird is, if I go to the vertex input pipeline stage see the buffers:

enter image description here

This is what the buffer at index 1 shows:

enter image description here

This does not happen for the buffer at index 0 (positions) which also render properly. So Whatever is causing the normals to show up as hex codes here is likely the cause of the bug. But i have no idea why this is happening. As far as I can see the pipeline and buffer were all set properly.


Solution

  • You presumably want to use one separate buffer for each vertex attribute (aka non-interleaved vertex buffer, SoA),
    but your VkVertexInputAttributeDescription::offset values [0, 12, 24] is what you would use for one vertex buffer interleaving all attributes (provided that their binding values point to one and the same VkVertexInputBindingDescription).
    e.g.

    // Interleaved:
    // Buffer 0: |Position: R32G32B32_FLOAT, Normal: R32G32B32_FLOAT, Uv: R32G32B32_FLOAT|, * vertex count
    VkVertexInputBindingDescription {
      .binding = 0,
      .stride  = 12 * 3, // 3 `R32G32B32_FLOAT`s !
      .inputRate = VK_VERTEX_INPUT_RATE_VERTEX
    };
    // All attributes in the same `binding` == `0`
    VkVertexInputAttributeDescription[3] {
      {
        .location = 0,
        .binding  = 0,
        .format   = VK_FORMAT_R32G32G32_SFLOAT,
        .offset   = 0 // [0, 11] portion
      },
      {
        .location = 1,
        .binding  = 0,
        .format   = VK_FORMAT_R32G32G32_SFLOAT,
        .offset   = 12 // [12, 23] portion
      },
      {
        .location = 2,
        .binding  = 0,
        .format   = VK_FORMAT_R32G32G32_SFLOAT,
        .offset   = 24 // [24, 35] portion
      }
    };
    

    Your VkVertexInputBindingDescription[1].stride == 12 tells Vulkan that your vertex buffer 1 uses 12 bytes for each vertex, and your VkVertexInputAttributeDescription[1].offset == 12 says the normal value is at offset 12, which is out of bounds. Same deal with your VkVertexInputAttributeDescription[2].offset == 24 overstepping (by a large amount) VkVertexInputBindingDescription[2].stride == 12.

    For using one tightly-packed buffer for each vertex attribute, you need to correctly set your VkVertexInputAttributeDescription[n].offset values to 0, which looks something like:

    // Non-interleaved:
    // Buffer 0: |Position: R32G32B32_FLOAT|, * vertex count
    // Buffer 1: |Normal: R32G32B32_FLOAT|, * vertex count
    // Buffer 2: |Uv: R32G32B32_FLOAT|, * vertex count
    VkVertexInputBindingDescription[3] {
      {
        .binding = 0,
        .stride  = 12,
        .inputRate = VK_VERTEX_INPUT_RATE_VERTEX
      },
      {
        .binding = 1,
        .stride  = 12,
        .inputRate = VK_VERTEX_INPUT_RATE_VERTEX
      },
      {
        .binding = 2,
        .stride  = 12,
        .inputRate = VK_VERTEX_INPUT_RATE_VERTEX
      }
    };
    // Each attribute in its own `binding` == `location`
    VkVertexInputAttributeDescription[3] {
      {
        .location = 0,
        .binding  = 0,
        .format   = VK_FORMAT_R32G32G32_SFLOAT,
        .offset   = 0 // Whole [0, 11]
      },
      {
        .location = 1,
        .binding  = 1,
        .format   = VK_FORMAT_R32G32G32_SFLOAT,
        .offset   = 0 // Whole [0, 11]
      },
      {
        .location = 2,
        .binding  = 2,
        .format   = VK_FORMAT_R32G32G32_SFLOAT,
        .offset   = 0 // Whole [0, 11]
      }
    };
    

    Worth noting is the comment line // vertex stride 12 less than total data fetched 24 generated by RenderDoc in the Buffer Format section, and how it does so.
    It detects when your vertex attribute description oversteps its binding description's stride:

    if(i + 1 == attrs.size())
    {
      // for the last attribute, ensure the total size doesn't overlap stride
      if(attrs[i].byteOffset + cursz > stride && stride > 0)
        return tr("// vertex stride %1 less than total data fetched %2")
            .arg(stride)
            .arg(attrs[i].byteOffset + cursz);
    }