Search code examples
vertex-buffervulkangeometry-shader

Vulkan: VkVertexInputBindingDescription always wrong with geometry shader


I'm trying to implement billboarded quads in a geomerty shader to render particle effects. The geometry shader input is points (vec3), and its output is a triangle strip with position and UV coordinates (vec3, vec2). I've tried two variations of vertex input bindings, but neither work.

If I set up the vertex binding like this:

VkVertexInputBindingDescription binding_desc[2] = {};
binding_desc[0].binding = 0;
binding_desc[0].stride = sizeof(glm::vec3);
binding_desc[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;

binding_desc[1].binding = 1;
binding_desc[1].stride = sizeof(glm::vec2);
binding_desc[1].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;

VkVertexInputAttributeDescription attribute_desc[2] = {};
attribute_desc[0].location = 0;
attribute_desc[0].binding = binding_desc[0].binding;
attribute_desc[0].format = VK_FORMAT_R32G32B32_SFLOAT;
attribute_desc[0].offset = offsetof(vert_shader_vertex, pos);

attribute_desc[1].location = 1;
attribute_desc[1].binding = binding_desc[1].binding;
attribute_desc[1].format = VK_FORMAT_R32G32_SFLOAT;
attribute_desc[1].offset = offsetof(vert_shader_vertex, uv);

I get the following error when calling vkCmdDraw:

ERROR [default] DS: (OBJECT 0) (CODE 24) The Pipeline State Object (0x3c) expects that this Command Buffer's vertex binding Index 1 should be set via vkCmdBindVertexBuffers. This is because VkVertexInputBindingDescription struct at index 1 of pVertexBindingDescriptions has a binding value of 1.

However, if I set it up as this:

VkVertexInputBindingDescription binding_desc[1] = {};
binding_desc[0].binding = 0;
binding_desc[0].stride = sizeof(glm::vec3);
binding_desc[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;

VkVertexInputAttributeDescription attribute_desc[1] = {};
attribute_desc[0].location = 0;
attribute_desc[0].binding = binding_desc[0].binding;
attribute_desc[0].format = VK_FORMAT_R32G32B32_SFLOAT;
attribute_desc[0].offset = offsetof(vert_shader_vertex, pos);

I get this error when calling vkCreateGraphicsPipelines:

ERROR [default] SC: (OBJECT 0) (CODE 3) Vertex shader consumes input at location 1 but not provided

  • Does the VkVertexInputBindingDescription describe the input to the geometry shader, or the vertex shader?
  • Do I need "dummy" UV coordinates in my vertex buffer as a place holder?
  • Is it possible my geometry shader is not activated, and how can I confirm?
  • Which ever of the two approaches is correct, how do I address the corresponding error?
  • As an aside, I'm new to Vulkan so comments on the shaders are welcome.

Geometry shader

#version 450

#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable

layout (points) in;
layout (triangle_strip, max_vertices = 4) out;

layout (location = 0) in vec3 inPos[];

layout (location = 0) out vec3 outPos;
layout (location = 1) out vec2 outUV;

layout (push_constant) uniform constants_t {
    vec3 up;
    vec3 right;
    mat4x4 world;
    mat4x4 projection;
} constants;

void main(void)
{
    const vec3 pos = gl_in[0].gl_Position.xyz;
    const vec3 up = constants.up;
    const vec3 right = constants.right;

    outPos = pos + up - right;
    outUV = vec2(0, 0);
    EmitVertex();

    outPos = pos + up + right;
    outUV = vec2(1, 0);
    EmitVertex();

    outPos = pos - up - right;
    outUV = vec2(0, 1);
    EmitVertex();

    outPos = pos - up + right;
    outUV = vec2(1, 1);
    EmitVertex();

    EndPrimitive();
}

Vertex shader

#version 450

#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable

layout (location = 0) in vec3 inPos;
layout (location = 1) in vec2 inUV;

layout (location = 0) out vec4 outPos;
layout (location = 1) out vec2 outUV;

layout (push_constant) uniform constants_t {
    vec3 up;
    vec3 right;
    mat4x4 world;
    mat4x4 projection;
} constants;

void main(void) {
    outUV = inUV;
    outPos = vec4(inPos.xyz, 1.0) * constants.world * constants.projection;
}

vkCmdBindVertexBuffers

VkBuffer vertex_buffers[1] = {vertexBuffer};
VkDeviceSize vertex_offset[1] = {0};
vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertex_buffers, vertex_offset);

Solution

  • vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertex_buffers, vertex_offset);
    

    This says that you're binding one buffer to index 0. Yet you told the pipeline when you created it that you would have two buffers bound.

    Do not lie to Vulkan; it always knows (when you're using validation layers ;) ).

    It is rather likely that you intended to have both vertex attributes use the same buffer object. I deduce this from the fact that you used offsetof to compute the relative offsets for them. If that is your intent, then you should have two vertex attributes that use the same buffer binding.

    Does the VkVertexInputBindingDescription describe the input to the geometry shader, or the vertex shader?

    It cannot describe the input to the GS because the first pipeline shader stage is the vertex shader. And creating a graphics pipeline without a VS is not possible.