Create pipeline

The pipeline object is similar in purpose to the pipeline state object of Direct3D 12: It is an object designed to combine shader data with state data in order to compile the optimal condition for the shader and state combination to render as efficiently as possible with as little runtime driver overhead as possible.

In order to accelerate the construction of pipeline objects, it is recommended to create a VkPipelineCache object. This object will contain a compiled set of pipeline stages, allowing reuse of duplicate or substantially similar pipeline states.

VkPipelineCache cache;
result = vkCreatePipelineCache (
    renderer->device,
    &(VkPipelineCacheCreateInfo){
        .sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
    },
    NULL,
    &cache
);
if ( result != VK_SUCCESS )
    RETURN_ERROR(-1, "vkCreatePipelineCache failed (0x%08X)", (uint32_t)result);

This pipeline cache file can optionally also be written to disk using data obtained from vkGetPipelineCacheData. This is recommended in order to compile the pipeline states once, write the results later on and reduce the amount of time required to get the pipeline states up and running the next time the application is started.

It should be noted, however, that the data from pipeline caches is not machine-portable. The data in the pipeline cache is only guaranteed for the current machine, with the current hardware and the current driver versions for said hardware. A change in graphics hardware or a driver update can invalidate the contents of previous pipeline state compilations. Therefore, it should be noted that this function call could potentially fail, although the specification is unclear on this point.

Furthermore, we will need the pipeline layout to be defined. This serves as a basic input description for use on multiple pipelines, allowing portability of bindings and other information when swapping between the graphics- or compute pipelines.

result = vkCreatePipelineLayout (
    renderer->device,
    &(VkPipelineLayoutCreateInfo){
        .sType          = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
        .pNext          = NULL,
        .flags          = 0,
        .setLayoutCount = 1,
        .pSetLayouts    = (VkDescriptorSetLayout[1]){
            dsLayout,
        },
        .pushConstantRangeCount = 2,
        .pPushConstantRanges    = (VkPushConstantRange[2]){
            [0] = {
                .stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
                .offset     = 0,
                .size       = 16 * sizeof ( float ),
            },
            [1] = {
                .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
                .offset     = 16 * sizeof ( float ),
                .size       = 1 * sizeof ( uint32_t ),
            },
        },
    },
    NULL,
    &renderer->pipelineLayout
);
if ( result != VK_SUCCESS )
    RETURN_ERROR(-1, "vkCreatePipelineLayout failed (0x%08X)", (uint32_t)result);

The pipeline state itself can now be created using the cache. If the pipeline was already in the cache, or can be sufficiently derived from content of the cache, this function call will perform much faster than it normally would. This is because without that information, all shaders would need to be compiled independently with the associated state information. When the data was not found in the cache, and the pipeline state was successfully constructed from scratch, the pipeline state is added to the cache.

result = vkCreateGraphicsPipelines (
	renderer->device,
	cache,
	1,
	(VkGraphicsPipelineCreateInfo[1]){
		[0] = {
			.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
			.pNext = NULL,
			.flags = 0,
			.stageCount = 2,
			.pStages = (VkPipelineShaderStageCreateInfo[2]){
				[0] = {
					.sType               = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
					.pNext               = NULL,
					.flags               = 0,
					.stage               = VK_SHADER_STAGE_VERTEX_BIT,
					.module              = vs,
					.pName               = "main",
				},
				[1] = {
					.sType               = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
					.pNext               = NULL,
					.flags               = 0,
					.stage               = VK_SHADER_STAGE_FRAGMENT_BIT,
					.module              = fs,
					.pName               = "main",
				},
			},
			.pVertexInputState = &(VkPipelineVertexInputStateCreateInfo){
				.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
				.pNext = NULL,
				.flags = 0,
				.vertexBindingDescriptionCount = 2,
				.pVertexBindingDescriptions = (VkVertexInputBindingDescription[2]){
					[0] = {
						.binding   = 0,
						.stride    = 3 * sizeof ( float ),
						.inputRate = VK_VERTEX_INPUT_RATE_VERTEX,
					},
					[1] = {
						.binding   = 1,
						.stride    = 2 * sizeof ( float ),
						.inputRate = VK_VERTEX_INPUT_RATE_VERTEX,
					},
				},
				.vertexAttributeDescriptionCount = 2,
				.pVertexAttributeDescriptions = (VkVertexInputAttributeDescription[2]){
					[0] = {
						.location = 0,
						.binding  = 0,
						.format   = VK_FORMAT_R32G32B32_SFLOAT,
						.offset   = 0,
					},
					[1] = {
						.location = 1,
						.binding  = 1,
						.format   = VK_FORMAT_R32G32_SFLOAT,
						.offset   = 0,
					},
				},
			},
			.pInputAssemblyState = &(VkPipelineInputAssemblyStateCreateInfo){
				.sType                  = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
				.pNext                  = NULL,
				.flags                  = 0,
				.topology               = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
				.primitiveRestartEnable = VK_FALSE,
			},
			.pTessellationState = &(VkPipelineTessellationStateCreateInfo){
				.sType              = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO,
				.pNext              = NULL,
				.flags              = 0,
				.patchControlPoints = 0,
			},
			.pViewportState     = &(VkPipelineViewportStateCreateInfo){
				.sType         = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
				.pNext         = NULL,
				.flags         = 0,
				.viewportCount = 1,
				.pViewports    = (VkViewport[1]){
					[0] = {
						.x        = 0.0f,
						.y        = 0.0f,
						.width    = (float)windowWidth,
						.height   = (float)windowHeight,
						.minDepth = -1.0f,
						.maxDepth =  1.0f,
					},
				},
				.scissorCount = 1,
				.pScissors = (VkRect2D[1]){
					[0] = {
						.offset = { .x     = 0,           .y      = 0 },
						.extent = { .width = windowWidth, .height = windowHeight },
					},
				},
			},
			.pRasterizationState = &(VkPipelineRasterizationStateCreateInfo){
				.sType                   = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
				.pNext                   = NULL,
				.flags                   = 0,
				.depthClampEnable        = VK_FALSE,
				.rasterizerDiscardEnable = VK_FALSE,
				.polygonMode             = VK_POLYGON_MODE_FILL,
				.cullMode                = VK_CULL_MODE_BACK_BIT,
				.frontFace               = VK_FRONT_FACE_COUNTER_CLOCKWISE,
				.depthBiasEnable         = VK_FALSE,
			},
			.pMultisampleState = &(VkPipelineMultisampleStateCreateInfo){
				.sType                = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
				.pNext                = NULL,
				.flags                = 0,
				.pSampleMask          = NULL,
				.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
			},
			.pDepthStencilState = &(VkPipelineDepthStencilStateCreateInfo){
				.sType                 = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
				.pNext                 = NULL,
				.flags                 = 0,
				.depthTestEnable       = VK_TRUE,
				.depthWriteEnable      = VK_TRUE,
				.depthCompareOp        = VK_COMPARE_OP_LESS,
				.depthBoundsTestEnable = VK_FALSE,
				.stencilTestEnable     = VK_FALSE,
				.minDepthBounds        = -1.0f,
				.maxDepthBounds        =  1.0f,
			},
			.pColorBlendState = &(VkPipelineColorBlendStateCreateInfo){
				.sType           = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
				.pNext           = NULL,
				.flags           = 0,
				.logicOpEnable   = VK_FALSE,
				.attachmentCount = 1,
				.pAttachments = (VkPipelineColorBlendAttachmentState[1]){
					[0] = {
						.blendEnable         = VK_FALSE,
						.srcColorBlendFactor = VK_BLEND_FACTOR_ONE,
						.dstColorBlendFactor = VK_BLEND_FACTOR_ONE,
						.colorBlendOp        = VK_BLEND_OP_ADD,
						.colorWriteMask      = 0xF,
					},
				},
			},
			.pDynamicState = &(VkPipelineDynamicStateCreateInfo){
				.sType             = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
				.pNext             = NULL,
				.flags             = 0,
				.dynamicStateCount = 2,
				.pDynamicStates    = (VkDynamicState[2]){
					VK_DYNAMIC_STATE_VIEWPORT,
					VK_DYNAMIC_STATE_SCISSOR,
				},
			},
			.layout     = renderer->pipelineLayout,
			.renderPass = renderer->renderPass,
			.subpass    = 0,
		},
	},
	NULL,
	&renderer->pipeline
);
if ( result != VK_SUCCESS )
	RETURN_ERROR(-1, "vkCreateGraphicsPipelines failed (0x%08X)", (uint32_t)result);

Creating graphics pipeline states require a lot of state and settings, and unfortunately, not that much can easily be left out without breaking or altering functionality or outright causing the function to fail.

Arguments of note include:

Variable Notes
.pStages Specifies all shader stages involved using shader modules, including their entry point (the function in the shader that is called) and special “specialization” properties, allowing compile-time changes to be made on a per-pipeline state basis
.pVertexInputState Specifies the input vertex buffers for the pipeline state. .pVertexBindingDescriptions describes the vertex buffer inputs during render time, whereas .pVertexAttributeDescriptions specifies how these bindings are used to feed the shaders
.pDynamicState Specifies a list of properties to be adjustable during rendering
.layout The pipeline layout to be used, in our case the one we just created
.renderPass The render pass this graphics pipeline is to be optimized for. This is a required argument, and using this graphics pipeline is only allowed in render passes identical or substantially similar to the render pass specified.