Initialising a Graphics Pipeline

How to create the graphics pipeline object

It is finally time to create the graphics pipeline. Pipelines are incredibly important objects as they perform the work of transforming the collection of vertices stored in the vertex buffer into the pixel colour values of the final rendered image. This image is what is seen on screen.

The pipeline is composed of a series of stages, each of which perform a different task in the rendering process. These stages are divided into two types, fixed function and programmable.

  • Fixed function stages can be configured by the developer but cannot be programmed directly. They perform standard operations which are generally required by any graphics pipeline, such as reading in the vertex data from the buffer at the start of the pipeline, and converting primitives into discrete fragments (rasterization).
  • Programmable stages are under the complete control of the developer through the use of short programs called shaders. In this example, the application only uses a vertex and a fragment shader.

In Vulkan, the entire pipeline is stored in one large immutable object which has to be constructed once, during initialisation. The reason for this is that defining the pipeline is a costly operation, so by doing it beforehand, Vulkan can drastically reduce the overhead during rendering. Also, in models where the pipeline is not explicitly created beforehand, the hardware needs to check for validity just before use, which involves error checking operations. These can have an unpredictable effect on performance which will vary between different graphics hardware. This goes against one of the key missions of Vulkan, which is to achieve consistent performance across different GPUs.

Creating the actual object involves populating the parameters of the fixed function stages of the pipeline and also selecting which shader modules to use from the ones that were defined earlier. Additionally the descriptor set layouts and render passes are needed, but it is not necessary to create all the memory objects beforehand as these can be bound at render time.

The fixed function state information required when creating a pipeline is shown below. As usual, the example code initPipeline() will demonstrate what this actually looks like in an application.

Vertex Input

This part of the pipeline receives the vertices from the vertex buffer.
  • VkVertexInputBindingDescription - the vertex binding description. This tells the GPU how to pass the vertex buffer data to the shader.
  • VkVertexInputAttributeDescription - the vertex input attribute description. This describes the image format of the vertex attributes and where to find them in the buffer. In this example there are two attributes: the position co-ordinates and the texture co-ordinates.
  • VkPipelineVertexInputStateCreateInfo - sets the configurations of this stage by referencing the two other structs mentioned above.
Input Assembler

This part of the pipeline defines how to construct primitives from the collection of vertices coming from the vertex buffer by defining the type of geometry such as triangles, points, or lines from the vertices.

If an index buffer is being used, the input assembler can be set to restart primitive construction if a special character is given in the index buffer.

These settings are chosen in the VkPipelineInputAssemblyStateCreateInfo struct.

Note: In the example code, the assembler should construct triangles and an index buffer is not used.

Rasterizer

This part of the pipeline converts the assembled polygons into fragments.

Setting up this stage involves selecting:
  • Polygon mode - whether the rasterizer should fill the polygon with fragments or just the edges
  • Face culling mode - whether to perform front face, back face or no culling
  • Front face - which winding order (clockwise, counterclockwise) should be considered to be the front face of the triangle
  • Line width - thickness of the rasterized lines if lines are rendered
  • Depth Bias - whether to enable depth bias which is often used with shadow mapping
This is all set in the VkPipelineRasterizationCreateInfo struct.
Note: In the example code, the polygon mode is set to fill, the front face is clockwise, back faces are culled, and lines and depth bias are not used.

Colour Blending

This stage describes how, the pipeline should mix the colour values in the framebuffer when consecutive fragments are rendered on the same pixel of the screen by consecutive fragment shaders.

Blending techniques can be used by setting the various blend factor and blend operation properties for colour and alpha channels.
Note: In the example code, colour blending is disabled so subsequent writes in the framebuffer at the same point overwrite the existing colour values.

Multi-sampling

This stage of the pipeline handles multi-sampling, which is usually used to implement anti-aliasing.

Note: In the example code, multi-sampling is not used so this stage is set to only use one sample per pixel with the rest of the parameters set to default values.

Dynamic States

Vulkan does allow some limited modification of the pipeline during runtime through dynamic states.

The configuration of dynamic states can be modified by special Vulkan commands in the command buffer.

Note: In the example code, the viewport and scissor rectangles are chosen to be dynamic states. They are not actually set until the VkCmdSetViewport(…) and vkCmdSetScissor(…) commands in the command buffer are executed. The viewport and scissor will be explained more fully in Defining the Viewport and Scissor.

Pipeline Layout

A pipeline layout is used to describe what resources are required by the pipeline during rendering. These are the resources which are accessed by the shaders through descriptor sets.

Creating a pipeline layout involves specifying how many descriptor sets are needed and where to find them.

The pipeline layout can also be used to specify push constants. These are a high-speed path to modifying constant data in shaders but are not used in this example.

Note: In the example code, the transformation matrix and the texture are stored in buffers that are accessed via descriptor sets. The creation struct of the pipeline layout references the two descriptor sets which were created earlier.

Pipeline Object

Finally, the actual pipeline object is created (as might be expected) using a creation struct.

All of the state information specified previously is referenced in the pipeline creation struct. The shader stages, render pass, and the subpass where the pipeline will be used, are also referenced.

With all of this state information set, the pipeline can be created using vkCreateGraphicsPipelines(…).