Chapter 3: the compute pipeline

Warning

This chapter is currently being written

In the previous chapter, we saw how memory works and how resources are managed in Vulkan. This allowed us to understand the simplest pipeline out there: the transfer pipeline, which enables commands related to copying resources or otherwise acting upon them in limited ways.

In this chapter, we arguably meet our first interesting pipeline, namely the compute one. Unlike the transfer pipeline, this one allows us to truly harness the power of GPUs: we will not merely be copying data around but writing custom computations and running them on GPUs.

A. A high-level overview

The compute pipeline is about using the GPU as a general-purpose computing device. We write special programs called shaders for describing the computations (this name reflects the origin of shaders as programs for controlling graphical operations; compute shaders may also be called kernels). Shaders are built using a domain specific language such as GLSL. We then compile these programs into a binary intermediate language called SPIR-V (website). GPUs do not run SPIR-V natively. Instead, their drivers contain a compiler for turning SPIR-V code into the machine language that corresponds to their actual architecture. Although the compilation from GLSL to SPIR-V can be done at compile-time, the compilation from SPIR-V to the machine language is device-specific and has to be done at run-time.

Just like we may call a program with different arguments, we can call a compute shader with different parameters. These parameters come in two forms: push constants and descriptors. Push constants represent small amounts of data directly from the CPU whereas descriptors give us a way of interacting with resources found in GPU memory. A shader has a given prototype that represents what kind of push constants and descriptors it expects. We describe this prototype explicitly in the form of a pipeline layout object.

We build a compute pipeline, an object assembling information required to run our shader. In particular, it regroups the shader itself and its prototype. We run the shader by using this object. The simplest way is to record a new command buffer and to bind the pipeline to it through a special command. Note that we did not yet set the values of shader arguments — push constants or descriptors — If we had set them while building the compute pipeline object, we would have to build a new one every time we would like to run the same shader with different parameters. This would be costly! Instead, we provide values for push constants and bind descriptor sets through special commands that we pass to the same command buffer.

There are special dispatch commands for running a command buffer bound to a compute pipeline.

In summary, we go through the following steps:

  1. We build our compute shader and compile it to SPIR-V (outside of Vulkan)
  2. We create a compute shader object
  3. We describe the interface of the compute shader as a pipeline layout
  4. We create a compute pipeline object that regroups the shader object and the pipeline layout
  5. We register a command buffer:
    1. We bind the pipeline object
    2. We bind the descriptor sets/push constants used by the pipeline object
  6. We run the command buffer

B. The compute pipeline in more detail

B.1. Shader modules

B.1.1. Writing compute shaders

B.1.2. Compiling compute shaders

B.1.3. Building shader modules

B.2. The pipeline object

VkComputePipelineCreateInfo VkPipelineShaderStageCreateInfo VkPipelineLayoutCreateInfo

vkCreateDescriptorSetLayout VkDescriptorSetLayoutCreateInfo VkDescriptorSetLayoutBinding VkPushConstantRange vkCmdBindDescriptorSets

vkCmdBindPipeline vkCmdDispatch vkCmdDispatchIndirect vkCmdDispatchBase vkCmdPushConstants

Derivatives

B.3. Descriptor set layouts

B.4. Pipeline layouts

B.5. Descriptor sets