Vulkan

From OSDev Wiki
Jump to navigation Jump to search

This article is a stub! This page or section is a stub. You can help the wiki by accurately contributing to it.

Vulkan is a modern 3D graphics API, very comparable to Direct3D 12.

These APIs are much more multithreading friendly, and do this by offering lower level control of the hardware to applications than previous generation APIs like Open GL or Direct3D 11. In contrast to these APIs, Vulkan avoids global state, state is localised to a command buffer.

Closer mapping to the graphics hardware is able to achieve better performance, at the cost of application complexity. However, this maps to reducing work done by device drivers (and thus, driver complexity).

The existence of projects like ANGLE and DXVK, which implement OpenGL and Direct3D 8-11 respectively, on top of Vulkan, further makes Vulkan an attractive API to target.

Structure of a Vulkan Application

Note that this is not meant to be a tutorial on how to use Vulkan! Rather, it's useful to understand the typical flow of an application.

vkCreateInstance - Creates an application instance. This is the closest thing that Vulkan has to a global state. However, what's passed in is a list of required extensions, and some information about the application (name, required API version...).

vkEnumerateInstanceExtensionProperties can enumerate for optional extensions.

Now with this initial phase of initialization out of the way, the application can now enumerate graphics adapters.

vkEnumeratePhysicalDevices - Returns a count, and optionally, fills an array of, VkPhysicalDevicehandles.

vkGetPhysicalDeviceQueueFamilyProperties - Enumerates the queue families supported by a physical device. An application may well need different queues for Compute vs 3D. This is how it finds out.


Finally, the application is able to create a logical device object.

vkCreateDevice. This takes a physical device to use, a set of queue creation descriptions, and optionally a set of host memory allocator callbacks - allowing e.g. pool allocators instead of default heap.

Render Pipeline

The application now starts to build a render pipeline. There are numerous stages involved here!

// Provided by VK_VERSION_1_0
typedef struct VkComputePipelineCreateInfo {
    VkStructureType                    sType;
    const void*                        pNext;
    VkPipelineCreateFlags              flags;
    VkPipelineShaderStageCreateInfo    stage;
    VkPipelineLayout                   layout;
    VkPipeline                         basePipelineHandle;
    int32_t                            basePipelineIndex;
} VkComputePipelineCreateInfo;
// Provided by VK_VERSION_1_0
typedef struct VkGraphicsPipelineCreateInfo {
    VkStructureType                                  sType;
    const void*                                      pNext;
    VkPipelineCreateFlags                            flags;
    uint32_t                                         stageCount;
    const VkPipelineShaderStageCreateInfo*           pStages;
    const VkPipelineVertexInputStateCreateInfo*      pVertexInputState;
    const VkPipelineInputAssemblyStateCreateInfo*    pInputAssemblyState;
    const VkPipelineTessellationStateCreateInfo*     pTessellationState;
    const VkPipelineViewportStateCreateInfo*         pViewportState;
    const VkPipelineRasterizationStateCreateInfo*    pRasterizationState;
    const VkPipelineMultisampleStateCreateInfo*      pMultisampleState;
    const VkPipelineDepthStencilStateCreateInfo*     pDepthStencilState;
    const VkPipelineColorBlendStateCreateInfo*       pColorBlendState;
    const VkPipelineDynamicStateCreateInfo*          pDynamicState;
    VkPipelineLayout                                 layout;
    VkRenderPass                                     renderPass;
    uint32_t                                         subpass;
    VkPipeline                                       basePipelineHandle;
    int32_t                                          basePipelineIndex;
} VkGraphicsPipelineCreateInfo;

Note the basePipeline fields allow optimisations when deriving from a similar pipeline, they can reasonably be ignored here.

Shaders

An application will create shaders. These are specified in SPIR-V bytecode.

vkCreateShaderModule - Creates a shader module from an array of SPIR-V bytecode. Note that this is simply a loading operation, it's a thin, opaque, wrapper.

Once shader modules are loaded, they can be specified in a VkPipelineShaderStageCreateInfo . Note there can be multiple entry points to a shader, shaders can be linked...

Vertex Input

The format of the vertex buffer is specified. This is how the application will feed raw vertex data to the shaders.

Input Assembly

An initial processing step, where the toplogogy is specified. The vertex buffer may be individual triangles, triangle strips, points, lines... The input assembly builds the basic mesh based on this.

The rest of the pipleine is similar.

Pipleline Layout

vkCreatePipelineLayout - This is where uniforms (cbuffer in HLSL) are specified. A relatively small buffer that effectively paramaterises the shader. For instance, the view transform can be passed to the vertex shader this way.

Render Pass

vkCreateRenderPass - Determines how the result is rendered to a framebuffer.

After all that work, we reach vkCreateGraphicsPipelines

Framebuffer

vkCreateFramebuffer creates a framebuffer, which is pretty straightforward. It can be used as a destination for a graphics pipeline.

Command Queues

Now we're getting somewhere - and this section maps closely to the graphics hardware itself. First, a command pool is created - vkCreateCommandPool

vkAllocateCommandBuffers - Allocate command buffers from the pool.

vkBeginCommandBuffer - Starts a sequence of commands in a buffer.

vkCmdBeginRenderPass

vkCmdBindPipeline

vkCmdDraw

vkCmdEndRenderPass

vkEndCommandBuffer

vkQueueSubmit - submits the recorded command queue asynchronously.

Synchronisation

All synchronisation is explicit, there are semaphores for sync between GPU processes, and fences for the host CPU.