Search code examples
glslvulkan

Vulkan VkVertexInputBindingDescription, how does it work?


I am trying to do something that I thought would be simple, but I can't get it work. I have few questions regarding the Vulkan binding.

Q1. What is the difference between VkVertexInputBindingDescription.binding and VkVertexInputAttributeDescription.binding?

According to the specification the difference seems very subtle and I don't quite get it.

https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkVertexInputBindingDescription.html https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkVertexInputAttributeDescription.html

VkVertexInputBindingDescription.binding = binding is the binding number that this structure describes.
VkVertexInputAttributeDescription.binding = is the binding number which this attribute takes its data from.

Q2. Is it the same thing? According to this post, which is more understandable

What is the purpose of `binding` from `VkVertexInputBindingDescription`?

binding is an index into the pBuffers array bound by vkCmdBindVertexBuffers.

If biding is an index to the vertex buffer, here is what I am trying to do: I want to bind two different buffers to my vertex shader using different locations. I have reasons to do that, but not relevant to this post. Here is what I have tried:

  1. Created my structures and vertex buffers: Notice I want one buffer to have all the positions and the other to have all the colors
struct Vertex {glm::vec2 pos;};
struct Color {glm::vec3 color;};
const std::vector<Vertex> verts = {
    {{-1.0f, -1.0f}}, {{0.0f, -1.0f}}, {{-1.0f, 0.0f}}, // tri0 pos
    {{-1.0f, 0.0f}},  {{0.0f, -1.0f}}, {{0.0f, 0.0f}},  // tri1 pos
};
const std::vector<Color> colors = {
    {{1.0f, 1.0f, 0.0f}}, {{1.0f, 1.0f, 0.0f}}, {{1.0f, 1.0f, 0.0f}}, //tri0 color
    {{0.0f, 0.0f, 1.0f}}, {{0.0f, 0.0f, 1.0f}}, {{0.0f, 0.0f, 1.0f}}, //tri1 color
};
  1. Below is my array of attribute and binding descriptions. Don't worry about the strides, I am using vkCmdBindVertexBuffers2 which allows to specify the strides during the command buffer creation.
static std::array <VkVertexInputBindingDescription,2> getBindingDescription() {

    std::array <VkVertexInputBindingDescription, 2> bindingDescription{};
    bindingDescription[0].binding = 0; // index to Vertex buffer?
    bindingDescription[0].stride = 99; //dummy
    bindingDescription[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;

    bindingDescription[1].binding = 1;
    bindingDescription[1].stride = 99; //dummy
    bindingDescription[1].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;

    return bindingDescription;
}

static std::array<VkVertexInputAttributeDescription, 2> getAttributeDescriptions() {
    std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions{};

    attributeDescriptions[0].binding = 0;   // index to Vertex buffer?
    attributeDescriptions[0].location = 0;
    attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
    attributeDescriptions[0].offset = offsetof(Vertex, pos);

    attributeDescriptions[1].binding = 1;
    attributeDescriptions[1].location = 1;
    attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
    attributeDescriptions[1].offset = offsetof(Color, color);

    return attributeDescriptions;
}
  1. I create the vertex buffers (one for position, one for color)
VkBuffer vertexBuffer[2];
float* p1 = (float*)verts.data(); auto s1 = verts.size() * sizeof(Vertex);
float* p2 = (float*)colors.data(); auto s2 = colors.size() * sizeof(Color);
vertexBuffer[0]= createVertexBuffer(p1, s1);
vertexBuffer[1] = createVertexBuffer(p2, s2);

// a bunch of code
VkBuffer createVertexBuffer(float* pVertices, int count) {
    VkBufferCreateInfo bufferInfo{};
    bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    bufferInfo.size = sizeof(float) * count;
    bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
     bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
     // a bunch of code
return vertexBuffer;

  1. I setup the VkPipelineVertexInputStateCreateInfo as such:
        VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
        vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
        auto bindingDescription = getBindingDescription();
        auto attributeDescriptions = getAttributeDescriptions();
        vertexInputInfo.vertexBindingDescriptionCount = static_cast<uint32_t>(bindingDescription.size());;
        vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescriptions.size());
        vertexInputInfo.pVertexBindingDescriptions = bindingDescription.data();
        vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();

At this point things seemed to be working. I don't see errors from the validation layer when I create the pipeline.

  1. Here is the vertex shader
layout(location = 0) in vec2 inPosition; // I want to bind vertexBuffer[0] here
layout(location = 1) in vec3 inColor;    // I want to bind vertexBuffer[1] here
layout(location = 0) out vec3 fragColor;
void main() {
    gl_Position = vec4(inPosition, 0.0, 1.0);
    fragColor = inColor;
}
  1. Finally here is how I create my command buffer.
VkBuffer vertexBuffers[] = { vertexBuffer[0], vertexBuffer[1] };
VkDeviceSize offsets[] = { 0, 0 };
auto numVertices = static_cast<uint32_t>(verts.size());
auto numColors = static_cast<uint32_t>(colors.size());
VkDeviceSize    sizes[] = { sizeof(Vertex) * numVertices, sizeof(Color) * numVertices };
VkDeviceSize    strides[] = { sizeof(Vertex), sizeof(Color) };
vkCmdBindVertexBuffers2(commandBuffer, 0, 1, vertexBuffers, offsets, sizes, strides);

// this fails :(
vkCmdDraw(commandBuffer, numVertices, 1, 0, 0);

It binds without error, but when I issue the draw call the validation layer reports a bunch of errors as below (there is more, but trying to keep the post readable), and nothing gets drawn.

VUID-vkCmdDraw-None-04007(ERROR / SPEC): msgNum: -1719549157 - Validation Error: [ VUID-vkCmdDraw-None-04007 ] Object 0: handle = 0x254738c3e80, type = VK_OBJECT_TYPE_COMMAND_BUFFER; | MessageID = 0x9981c31b | vkCmdDraw: VkPipeline 0x967dd1000000000e[] expects that this Command Buffer's vertex binding Index 1 should be set via vkCmdBindVertexBuffers. This is because pVertexBindingDescriptions[1].binding value is 1. The Vulkan spec states: All vertex input bindings accessed via vertex input variables declared in the vertex shader entry point's interface must have either valid or VK_NULL_HANDLE buffers bound (https://vulkan.lunarg.com/doc/view/1.3.236.0/windows/1.3-extensions/vkspec.html#VUID-vkCmdDraw-None-04007)
    Objects: 1
        [0] 0x254738c3e80, type: 6, name: NULL
validation layer: Validation Error: [ VUID-vkCmdDraw-None-04007 ] Object 0: handle = 0x254738c3e80, type = VK_OBJECT_TYPE_COMMAND_BUFFER; | MessageID = 0x9981c31b | vkCmdDraw: VkPipeline 0x967dd1000000000e[] expects that this Command Buffer's vertex binding Index 1 should be set via vkCmdBindVertexBuffers. This is because pVertexBindingDescriptions[1].binding value is 1. The Vulkan spec states: All vertex input bindings accessed via vertex input variables declared in the vertex shader entry point's interface must have either valid or VK_NULL_HANDLE buffers bound (https://vulkan.lunarg.com/doc/view/1.3.236.0/windows/1.3-extensions/vkspec.html#VUID-vkCmdDraw-None-04007)
VUID-vkCmdDraw-None-02721(ERROR / SPEC): msgNum: -1712364613 - Validation Error: [ VUID-vkCmdDraw-None-02721 ] Object 0: handle = 0x254738c3e80, type = VK_OBJECT_TYPE_COMMAND_BUFFER; Object 1: handle = 0x967dd1000000000e, type = VK_OBJECT_TYPE_PIPELINE; | MessageID = 0x99ef63bb | vkCmdDraw: binding #1 in pVertexAttributeDescriptions[1] of VkPipeline 0x967dd1000000000e[] is an invalid value for command buffer VkCommandBuffer 0x254738c3e80[]. The Vulkan spec states: For a given vertex buffer binding, any attribute data fetched must be entirely contained within the corresponding vertex buffer binding, as described in Vertex Input Description (https://vulkan.lunarg.com/doc/view/1.3.236.0/windows/1.3-extensions/vkspec.html#VUID-vkCmdDraw-None-02721)

Attached is screenshot of the debugger for reference: The objects and arrays look good to me.

I think fundamentally I still don't understand what binding does. Is it an index to the vertex buffer, is it something else? What is it?

Thank you very much for patiently reading. I greatly appreciate any help or explanations why this does not work and what I am doing wrong.

enter image description here


Solution

  • They are the same thing, you define the buffers you want to use through VkVertexInputBindingDescription, and when you create attributes, you define the binding to the buffer you want to use.

    The error you're getting is because when you call vkCmdBindVertexBuffers2, you are telling it to only bind the first buffer, replace the 1 with 2.

    I find it strange that you are using vkCmdBindVertexBuffers2 and not specifying the stride when to create the VkVertexInputBindingDescriptions, is there a reason for that? Also I'd recommend one vertex buffer in this scenario, but you do say you 'have reasons to do that', so I'll leave it be.