Diagrams showing relationships between Vulkan objects and how they're used.
Vulkan Diagrams is a collection of diagrams which are designed to serve as a quick reference for various topics in Vulkan. The diagrams show the Vulkan objects needed to accomplish common tasks (e.g. creating a vertex buffer) and the relationships between these objects.
For clarity, some members of Vulkan objects are omitted and some names are slightly simplified (e.g. changing VKIMAGELAYOUTSHADERREADONLYOPTIMAL to SHADERREADONLY_OPTIMAL).
In this diagram, time progresses from top to bottom. Their names and setup are taken from the rendering and presentation chapter of Vulkan Tutorial.
Note:
vkAcquireNextImageKHRwon't signal the semaphore/fence until the image is ready, and the image won't be ready until enough previously acquired images are released with
vkQueuePresentKHR.
vkAcquireNextImageKHRwill return the code
VK_NOT_READYto indicate that the semaphore/fence hasn't been signaled immediately, but it will signal later once an image is acquired.
If you have vsync enabled,
vkQueuePresentKHRis the function that will block until the next vsync cycle.
NVIDIA has a good article about memory management which I recommend reading. The takeaway is that it's usually preferable to make big memory allocations with big buffers and to sub-allocate resources from those buffers. It includes the following descriptions of memory objects:
Heap - Depending on the hardware and platform, the device will expose a fixed number of heaps, from which you can allocate certain amount of memory in total. Discrete GPUs with dedicated memory will be different to mobile or integrated solutions that share memory with the CPU. Heaps support different memory types which must be queried from the device.Memory type - When creating a resource such as a buffer, Vulkan will provide information about which memory types are compatible with the resource. Depending on additional usage flags, the developer must pick the right type, and based on the type, the appropriate heap.
Memory property flags - These flags encode caching behavior and whether we can map the memory to the host (CPU), or if the GPU has fast access to the memory.
Memory - This object represents an allocation from a certain heap with a user-defined size.
Resource (Buffer/Image) - After querying for the memory requirements and picking a compatible allocation, the memory is associated with the resource at a certain offset. This offset must fulfill the provided alignment requirements. After this we can start using our resource for actual work.
Sub-Resource (Offsets/View) - It is not required to use a resource only in its full extent, just like in OpenGL we can bind ranges (e.g. varying the starting offset of a vertex-buffer) or make use of views (e.g. individual slice and mipmap of a texture array).
Alternatively, Vulkan Memory Allocator (VMA) is a good library that handles memory allocations for you.
In this diagram, a single renderpass is used for each command buffer, and each renderpass has multiple subpasses.
You should use multiple subpasses instead of multiple render passes whenever possible. If a pass only needs to read from the one corresponding fragment in a previous pass, you can use a previous subpass as an input attachment and no additional render passes are needed. Here is an example of how to do that. If you need random access to a previous pass (to implement a guassian blur, for example) then it would be appropriate to use multiple render passes.
In the diagram we see that one of the attachments in the frame buffer has an image which is owned by the swapchain, but this is not mandatory. For example, you could render to a texture by creating your own
VkImagewith the usage flags
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BITso it can be written into as a color attachment and then sampled from in a shader.
The best mental model description I've come across for some of the objects relating to descriptor sets are from a comment in the Vulkan subreddit:
DescriptorPool - A big heap of available UBOs, textures, storage buffers, etc that can be used when instantiating DescriptorSets. This allows you to allocate a big heap of types ahead of time so that later on you don't have to ask the gpu to do expensive allocations.DescriptorSetLayout - Defines the structure of a descriptor set, a template of sorts. Think of a
class
orstruct
in C or C++, it says "I am made out of, 3 UBOs, a texture sampler, etc". It's analogous to goingstruct MyDesc { Buffer MyBuffer[3]; Texture MyTex; } struct MyOtherDesc { Buffer MyBuffer; }
DescriptorSet - An actual instance of a descriptor, as defined by a DescriptorSetLayout. Using the class/struct analogy, it's like going
MyDesc DescInstance();
PipelineLayout - If you treat your entire shader as if it was just one big
void shader(arguments)
function then a PipelineLayout is like describing all the "arguments" passed into your shader such asvoid shader(MyDesc desc, MyOtherDesc otherDesc)
. This generally maps up to statements likelayout(std140,set=0, binding = 0) uniform UBufferInfo{Blah MyBlah;}
andlayout(set=0, binding = 2, rgba32f) uniform image2D MyImage;
in your shader code.vkCmdBindDescriptorSet - This is the mechanism to actually pass a DescriptorSet into a shader(aka pipeline). So basically passing the "arguments" like
shader(DescInstance,OtherDescInstance)
.
Note that
vkUpdateDescriptorSets(...)doesn't copy a buffer into the descriptor set, but rather gives the descriptor set a pointer to the buffer described by
VkDescriptorBufferInfo. So then
vkUpdateDescriptorSets(...)doesn't need to be called more than once for a descriptor set since modifying the buffer that a descriptor set points to will update what the descriptor set sees.
This diagram shows the general use of pipeline barriers and how they create execution dependencies and memory dependencies. The specific example in the diagram shows the pipeline barrier which transfers the image's layout from
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMALto
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL. This is performed after copying the image data from a buffer into the image, to prepare the image to be read from shaders. This example was taken from the texture mapping chapter of Vulkan Tutorial.
The set names and quotes are taken directly from the spec.
(ctrl+f search for PDF version of spec: Execution and Memory Dependencies)
VkPipelineVertexInputStateCreateInfoallows us to specify how our vertices are stored in memory. It is composed of an array of
VkVertexInputAttributeDescriptions and an array of
VkVertexInputBindingDescriptions.
As the name implies, we will have one binding description for each binding. In this example, we see that binding 0 has
VK_VERTEX_INPUT_RATE_VERTEXas its input rate, which means it increments to the next set of data
strideapart for every vertex. Binding 1, on the other hand, has
VK_VERTEX_INPUT_RATE_INSTANCEas its input rate, so we increment to the next set of data
strideapart only for every instance. We specify the number of instances and vertices we draw in
vkCmdDraw.
We have one vertex attribute for each member of the struct associated with that binding. For example, binding 0 has 3 vertex attributes since the vertex buffer bound to binding 0 is a buffer of
Vertexstructs which has members
position,
normaland
texCoord. Binding 1 has only 2 vertex attributes since
InstanceDatahas only 2 members. The format of each vertex attribute is determined by the size and type of that attribute, so some common choices include:
float: VK_FORMAT_R32_SFLOAT vec2: VK_FORMAT_R32G32_SFLOAT vec3: VK_FORMAT_R32G32B32_SFLOAT vec4: VK_FORMAT_R32G32B32A32_SFLOAT ivec2: VK_FORMAT_R32G32_SINT uvec4: VK_FORMAT_R32G32B32A32_UINT double: VK_FORMAT_R64_SFLOAT etc.
In this example we make one call to
vkCmdBindVertexBufferand pass in arrays to bind both buffers at the same time, but note it would have been possible to make two separate calls so long as in one call we specify
firstBinding = 0and in the other
firstBinding = 1.
Lastly, notice that the vertex shader is completely blind to which binding each of its input variables are coming from. The vertex shader only specifies the locations, then the bound buffer the data comes from for each variable is determined by the corresponding
VkVertexInputAttributeDescription.