Chapter 8. Pipelines and Pipeline State Management

In the previous chapter, we understood the buffer resource in Vulkan and used it to store geometry data information in the form of a vertex buffer on the physical device memory. We implemented a Render Pass and framebuffer object. Also, we learned about SPIR-V, which is a new way of specifying shaders in Vulkan. In addition, we used the SPIR-V tool library to convert GLSL shaders into the SPIR-V intermediate language at the time of compilation.

We will take this chapter one notch up from what we've learned so far—we'll understand the concept of a pipeline and pipeline state management. In this chapter, we will begin describing the types of pipeline supported by the Vulkan API. There are two types of pipeline—compute and graphics. These pipelines are created using pipeline cache objects, which will be the next topic. As we approach the end of this chapter, we will implement the graphics pipeline and thoroughly understand the various types of pipeline state associated with it.

In this chapter, we will cover the following topics:

  • Getting started with pipelines
  • Caching the pipeline objects with pipeline cache object (PCO)
  • Creating the graphics pipeline
  • Understanding the compute pipeline
  • Pipeline state objects in Vulkan
  • Implementing the pipeline

Getting started with pipelines

A pipeline refers to a succession of fixed stages through which a data input flows; each stage processes the incoming data and hands it over to the next stage. The final product will be either a 2D raster drawing image (the graphics pipeline) or updated resources (buffers or images) with computational logic and calculations (the compute pipeline).

Vulkan supports two types of pipeline—graphics and compute.

  • The graphics pipeline: This pipeline takes in a set of Vulkan commands through command buffers and draws a 2D raster image of the 2D/3D scene.
  • The compute pipeline: This pipeline takes in Vulkan commands through command buffers and processes them for computational work.

The following redrawn diagram from the official Vulkan specification (https://www.khronos.org/registry/vulkan/specs/1.0/xhtml/vkspec.html#pipelines-block-diagram) shows the Vulkan graphics and compute pipelines:

Getting started with pipelines

The pipeline journey begins at Input Assembler, where the input vertex data is assembled in the form of a point, line, and triangle based on the primitive topology specified. Using the Vertex Shader programmable stage, the input vertex data is transformed into a clip space. The geometry is tessellated in the Tessellation Control Shader and Tessellation Evaluation Shader assembler. Geometry Shader has the unique capability of producing multiple primitives out of a single incoming primitive.

Next, Primitive Assembler takes all the transformed coordinates from the previous stage and arranges them in line with the specified draw or primitive-type (point, line, and triangle) information provided at the input stage. The primitives are clipped when the associated vertex coordinates fall outside the viewing volume, and when this happens, the clipped fragments (outside the view) are discarded.

Rasterization is the process of converting transformed screen space primitives (point, line, and triangle) into discrete elements called fragments. These fragments are controlled by the next stage, called Fragment Shader. Fragment shaders perform computation on a single fragment. These fragments finally become a part of the framebuffer, which is collectively subjected to a number of conditional updates, such as depth testing, stenciling, and fragment blending.

The buffer and image memory types can be processed in a separate pipeline in the form of a 1D/2D/3D workgroup called the compute pipeline. The compute pipeline is extremely powerful in accomplishing jobs in parallel processing; it is primarily used in the field of image processing and physics computation. Both the buffer and image memory can be modified (read/write) by the compute pipeline.

This pipeline broadly consists of three concepts: pipeline state objects, pipeline cache objects, and pipeline layouts. These can be used to effectively control the underlying pipeline operations:

  • Pipeline state objects (PSO): The physical device or the GPU is capable of doing several types of operation directly in the hardware. Such operations may include rasterizers and conditional updates, such as blending the depth test, stenciling, and so on. Vulkan offers the ability to control these hardware settings with the help of PSOs. The other hardware-base-controlled operations may include the assembly of the primitive topology type (point/line/triangle) on a given geometrical shape, viewport control, and more.
  • Pipeline cache objects (PCOs): The pipeline cache provides a mechanism for faster retrieval and reuse of stored pipelines. This gives an application a better chance of avoiding the redundancy of creating similar or duplicate pipeline objects.
  • Pipeline layouts: The buffer and images are indirectly connected to the shader and can be accessed using shader resource variables. The resource variables are connected to the buffer and image views. These resource variables are managed through descriptors and the descriptor set layout. Within the pipeline, the pipeline layouts manage the sequence of descriptor set layouts.

Note

A descriptor is an interface between the stored resource and the shader stage. The resources are connected to the logical layout bindings defined by the descriptor set layout, and the pipeline layout provides unprecedented access to descriptor sets within the pipeline. Descriptors will be covered in detail in Chapter 10, Descriptors and Push Constant, where we will learn to use uniforms.

VulkanPipeline - the pipeline implementation class

In this chapter, we are introducing a new user-defined class called VulkanPipeline. This class will manage pipeline implementation for Vulkan applications. Pipeline creation is a performance-critical path as it deals with a lot of pipeline state management objects; therefore, the reusability of pipeline objects is highly advisable. The Vulkan pipeline provides a pipeline-caching mechanism through a PCO. This reduces the overhead in creating similar pipelines—the driver looks for a closer match and creates the new pipeline using the base pipeline.

The pipeline implementation must be placed at a level where drawing an object can easily access the PCO in a centralized manner to enhance the chance of pipeline reusability. For this, there are two choices: place the VulkanPipeline class inside VulkanApplication (at the application level, this may be the primary thread) or VulkanRenderer (for each independent renderer thread). Both the options are fine as long as the application manages the pipeline objects correctly with proper handling of memory leaks and thread synchronization. This book follows the latter option, so we place VulkanPipeline in VulkanRenderer to avoid thread synchronization, making it simpler for beginners.

The following block diagram shows a pictorial view that represents the application system with the integration of the user-defined VulkanPipeline class:

VulkanPipeline - the pipeline implementation class

The following is the declaration of the VulkanPipeline header file (VulkanPipeline.h):

/************ VulkanPipeline.h ************/ 
class VulkanPipeline 
{ 
public: 
    // Creates the pipeline cache object and stores pipeline object 
    void createPipelineCache(); 
 
    // Returns the created pipeline object, it takes the drawable

    // object which contains the vertex input rate and data

    // interpretation information, shader files, Boolean flag to

    // check depth is supported or not, and a flag to check if the
 // vertex input are available. 
    bool createPipeline(VulkanDrawable* drawableObj,  
    VkPipeline*    pipeline,   VulkanShader*     shaderObj,  
    VkBool32       includeDepth, VkBool32        includeVi = true); 
 
    // Destruct the pipeline cache object 
    void destroyPipelineCache(); 
    public: 
    // Pipeline cache object 
    VkPipelineCache   pipelineCache; 
 
    // References to other user defined class 
    VulkanApplication*      appObj; 
    VulkanDevice*           deviceObj; 
}; 

The header file declaration contains the helper functions that allow you to create the pipeline cache object (VkPipelineCache) and produce the pipelines (VkPipeline).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.163.175