Split GraphicsState

VK_EXT_graphics_pipeline_library logically divides the graphics state in
four (almost-)disjoint sets.  It then allows each set to be separately
created and further linked.

This change splits GraphicsState correspondingly into four.  The
GraphicsState struct itself then becomes an aggregate of the four
subsets.

Ref: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap10.html#pipeline-graphics-subsets

Bug: b/245568070
Tests: dEQP-VK.*
Change-Id: I6b50444135df4ec9678feaf0755817a614e9a97f
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/68108
Kokoro-Result: kokoro <noreply+kokoro@google.com>
Commit-Queue: Shahbaz Youssefi <syoussefi@google.com>
Presubmit-Ready: Shahbaz Youssefi <syoussefi@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Tested-by: Shahbaz Youssefi <syoussefi@google.com>
diff --git a/src/Device/Context.cpp b/src/Device/Context.cpp
index a72f30e..9024abf 100644
--- a/src/Device/Context.cpp
+++ b/src/Device/Context.cpp
@@ -90,6 +90,114 @@
 	}
 }
 
+vk::DynamicStateFlags ParseDynamicStateFlags(const VkPipelineDynamicStateCreateInfo *dynamicStateCreateInfo)
+{
+	vk::DynamicStateFlags dynamicStateFlags = {};
+
+	if(dynamicStateCreateInfo == nullptr)
+	{
+		return dynamicStateFlags;
+	}
+
+	if(dynamicStateCreateInfo->flags != 0)
+	{
+		// Vulkan 1.3: "flags is reserved for future use." "flags must be 0"
+		UNSUPPORTED("dynamicStateCreateInfo->flags 0x%08X", int(dynamicStateCreateInfo->flags));
+	}
+
+	for(uint32_t i = 0; i < dynamicStateCreateInfo->dynamicStateCount; i++)
+	{
+		VkDynamicState dynamicState = dynamicStateCreateInfo->pDynamicStates[i];
+		switch(dynamicState)
+		{
+		// Vertex input interface:
+		case VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE:
+			dynamicStateFlags.vertexInputInterface.dynamicPrimitiveRestartEnable = true;
+			break;
+		case VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY:
+			dynamicStateFlags.vertexInputInterface.dynamicPrimitiveTopology = true;
+			break;
+		case VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE:
+			dynamicStateFlags.vertexInputInterface.dynamicVertexInputBindingStride = true;
+			break;
+
+		// Pre-rasterization:
+		case VK_DYNAMIC_STATE_LINE_WIDTH:
+			dynamicStateFlags.preRasterization.dynamicLineWidth = true;
+			break;
+		case VK_DYNAMIC_STATE_DEPTH_BIAS:
+			dynamicStateFlags.preRasterization.dynamicDepthBias = true;
+			break;
+		case VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE:
+			dynamicStateFlags.preRasterization.dynamicDepthBiasEnable = true;
+			break;
+		case VK_DYNAMIC_STATE_CULL_MODE:
+			dynamicStateFlags.preRasterization.dynamicCullMode = true;
+			break;
+		case VK_DYNAMIC_STATE_FRONT_FACE:
+			dynamicStateFlags.preRasterization.dynamicFrontFace = true;
+			break;
+		case VK_DYNAMIC_STATE_VIEWPORT:
+			dynamicStateFlags.preRasterization.dynamicViewport = true;
+			break;
+		case VK_DYNAMIC_STATE_SCISSOR:
+			dynamicStateFlags.preRasterization.dynamicScissor = true;
+			break;
+		case VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT:
+			dynamicStateFlags.preRasterization.dynamicViewportWithCount = true;
+			break;
+		case VK_DYNAMIC_STATE_SCISSOR_WITH_COUNT:
+			dynamicStateFlags.preRasterization.dynamicScissorWithCount = true;
+			break;
+		case VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE:
+			dynamicStateFlags.preRasterization.dynamicRasterizerDiscardEnable = true;
+			break;
+
+		// Fragment:
+		case VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE:
+			dynamicStateFlags.fragment.dynamicDepthTestEnable = true;
+			break;
+		case VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE:
+			dynamicStateFlags.fragment.dynamicDepthWriteEnable = true;
+			break;
+		case VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE:
+			dynamicStateFlags.fragment.dynamicDepthBoundsTestEnable = true;
+			break;
+		case VK_DYNAMIC_STATE_DEPTH_BOUNDS:
+			dynamicStateFlags.fragment.dynamicDepthBounds = true;
+			break;
+		case VK_DYNAMIC_STATE_DEPTH_COMPARE_OP:
+			dynamicStateFlags.fragment.dynamicDepthCompareOp = true;
+			break;
+		case VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE:
+			dynamicStateFlags.fragment.dynamicStencilTestEnable = true;
+			break;
+		case VK_DYNAMIC_STATE_STENCIL_OP:
+			dynamicStateFlags.fragment.dynamicStencilOp = true;
+			break;
+		case VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK:
+			dynamicStateFlags.fragment.dynamicStencilCompareMask = true;
+			break;
+		case VK_DYNAMIC_STATE_STENCIL_WRITE_MASK:
+			dynamicStateFlags.fragment.dynamicStencilWriteMask = true;
+			break;
+		case VK_DYNAMIC_STATE_STENCIL_REFERENCE:
+			dynamicStateFlags.fragment.dynamicStencilReference = true;
+			break;
+
+		// Fragment output interface:
+		case VK_DYNAMIC_STATE_BLEND_CONSTANTS:
+			dynamicStateFlags.fragmentOutputInterface.dynamicBlendConstants = true;
+			break;
+
+		default:
+			UNSUPPORTED("VkDynamicState %d", int(dynamicState));
+		}
+	}
+
+	return dynamicStateFlags;
+}
+
 }  // namespace
 
 namespace vk {
@@ -247,104 +355,6 @@
 	}
 }
 
-GraphicsState::DynamicStateFlags GraphicsState::ParseDynamicStateFlags(const VkPipelineDynamicStateCreateInfo *dynamicStateCreateInfo)
-{
-	GraphicsState::DynamicStateFlags dynamicStateFlags = {};
-
-	if(dynamicStateCreateInfo)
-	{
-		if(dynamicStateCreateInfo->flags != 0)
-		{
-			// Vulkan 1.3: "flags is reserved for future use." "flags must be 0"
-			UNSUPPORTED("dynamicStateCreateInfo->flags 0x%08X", int(dynamicStateCreateInfo->flags));
-		}
-
-		for(uint32_t i = 0; i < dynamicStateCreateInfo->dynamicStateCount; i++)
-		{
-			VkDynamicState dynamicState = dynamicStateCreateInfo->pDynamicStates[i];
-			switch(dynamicState)
-			{
-			case VK_DYNAMIC_STATE_VIEWPORT:
-				dynamicStateFlags.dynamicViewport = true;
-				break;
-			case VK_DYNAMIC_STATE_SCISSOR:
-				dynamicStateFlags.dynamicScissor = true;
-				break;
-			case VK_DYNAMIC_STATE_LINE_WIDTH:
-				dynamicStateFlags.dynamicLineWidth = true;
-				break;
-			case VK_DYNAMIC_STATE_DEPTH_BIAS:
-				dynamicStateFlags.dynamicDepthBias = true;
-				break;
-			case VK_DYNAMIC_STATE_BLEND_CONSTANTS:
-				dynamicStateFlags.dynamicBlendConstants = true;
-				break;
-			case VK_DYNAMIC_STATE_DEPTH_BOUNDS:
-				dynamicStateFlags.dynamicDepthBounds = true;
-				break;
-			case VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK:
-				dynamicStateFlags.dynamicStencilCompareMask = true;
-				break;
-			case VK_DYNAMIC_STATE_STENCIL_WRITE_MASK:
-				dynamicStateFlags.dynamicStencilWriteMask = true;
-				break;
-			case VK_DYNAMIC_STATE_STENCIL_REFERENCE:
-				dynamicStateFlags.dynamicStencilReference = true;
-				break;
-			case VK_DYNAMIC_STATE_CULL_MODE:
-				dynamicStateFlags.dynamicCullMode = true;
-				break;
-			case VK_DYNAMIC_STATE_FRONT_FACE:
-				dynamicStateFlags.dynamicFrontFace = true;
-				break;
-			case VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY:
-				dynamicStateFlags.dynamicPrimitiveTopology = true;
-				break;
-			case VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT:
-				dynamicStateFlags.dynamicViewportWithCount = true;
-				break;
-			case VK_DYNAMIC_STATE_SCISSOR_WITH_COUNT:
-				dynamicStateFlags.dynamicScissorWithCount = true;
-				break;
-			case VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE:
-				dynamicStateFlags.dynamicVertexInputBindingStride = true;
-				break;
-			case VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE:
-				dynamicStateFlags.dynamicDepthTestEnable = true;
-				break;
-			case VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE:
-				dynamicStateFlags.dynamicDepthWriteEnable = true;
-				break;
-			case VK_DYNAMIC_STATE_DEPTH_COMPARE_OP:
-				dynamicStateFlags.dynamicDepthCompareOp = true;
-				break;
-			case VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE:
-				dynamicStateFlags.dynamicDepthBoundsTestEnable = true;
-				break;
-			case VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE:
-				dynamicStateFlags.dynamicStencilTestEnable = true;
-				break;
-			case VK_DYNAMIC_STATE_STENCIL_OP:
-				dynamicStateFlags.dynamicStencilOp = true;
-				break;
-			case VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE:
-				dynamicStateFlags.dynamicRasterizerDiscardEnable = true;
-				break;
-			case VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE:
-				dynamicStateFlags.dynamicDepthBiasEnable = true;
-				break;
-			case VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE:
-				dynamicStateFlags.dynamicPrimitiveRestartEnable = true;
-				break;
-			default:
-				UNSUPPORTED("VkDynamicState %d", int(dynamicState));
-			}
-		}
-	}
-
-	return dynamicStateFlags;
-}
-
 VkDeviceSize Inputs::getVertexStride(uint32_t i, bool dynamicVertexStride) const
 {
 	auto &attrib = stream[i];
@@ -381,22 +391,56 @@
 	return 0;
 }
 
-GraphicsState::GraphicsState(const Device *device, const VkGraphicsPipelineCreateInfo *pCreateInfo,
-                             const PipelineLayout *layout)
-    : pipelineLayout(layout)
-    , dynamicStateFlags(ParseDynamicStateFlags(pCreateInfo->pDynamicState))
+void MultisampleState::set(const VkPipelineMultisampleStateCreateInfo *multisampleState)
 {
-	if((pCreateInfo->flags &
-	    ~(VK_PIPELINE_CREATE_DISABLE_OPTIMIZATION_BIT |
-	      VK_PIPELINE_CREATE_DERIVATIVE_BIT |
-	      VK_PIPELINE_CREATE_ALLOW_DERIVATIVES_BIT |
-	      VK_PIPELINE_CREATE_EARLY_RETURN_ON_FAILURE_BIT_EXT |
-	      VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT_EXT)) != 0)
+	if(multisampleState->flags != 0)
 	{
-		UNSUPPORTED("pCreateInfo->flags 0x%08X", int(pCreateInfo->flags));
+		// Vulkan 1.2: "flags is reserved for future use." "flags must be 0"
+		UNSUPPORTED("pCreateInfo->pMultisampleState->flags 0x%08X", int(multisampleState->flags));
 	}
 
-	const VkPipelineVertexInputStateCreateInfo *vertexInputState = pCreateInfo->pVertexInputState;
+	sampleShadingEnable = (multisampleState->sampleShadingEnable != VK_FALSE);
+	if(sampleShadingEnable)
+	{
+		minSampleShading = multisampleState->minSampleShading;
+	}
+
+	if(multisampleState->alphaToOneEnable != VK_FALSE)
+	{
+		UNSUPPORTED("VkPhysicalDeviceFeatures::alphaToOne");
+	}
+
+	switch(multisampleState->rasterizationSamples)
+	{
+	case VK_SAMPLE_COUNT_1_BIT:
+		sampleCount = 1;
+		break;
+	case VK_SAMPLE_COUNT_4_BIT:
+		sampleCount = 4;
+		break;
+	default:
+		UNSUPPORTED("Unsupported sample count");
+	}
+
+	VkSampleMask sampleMask;
+	if(multisampleState->pSampleMask)
+	{
+		sampleMask = multisampleState->pSampleMask[0];
+	}
+	else  // "If pSampleMask is NULL, it is treated as if the mask has all bits set to 1."
+	{
+		sampleMask = ~0;
+	}
+
+	alphaToCoverage = (multisampleState->alphaToCoverageEnable != VK_FALSE);
+	multiSampleMask = sampleMask & ((unsigned)0xFFFFFFFF >> (32 - sampleCount));
+}
+
+void VertexInputInterfaceState::initialize(const VkPipelineVertexInputStateCreateInfo *vertexInputState,
+                                           const VkPipelineInputAssemblyStateCreateInfo *inputAssemblyState,
+                                           const DynamicStateFlags &allDynamicStateFlags)
+{
+	dynamicStateFlags = allDynamicStateFlags.vertexInputInterface;
 
 	if(vertexInputState->flags != 0)
 	{
@@ -404,26 +448,101 @@
 		UNSUPPORTED("vertexInputState->flags");
 	}
 
-	const VkPipelineInputAssemblyStateCreateInfo *inputAssemblyState = pCreateInfo->pInputAssemblyState;
-
 	if(inputAssemblyState->flags != 0)
 	{
 		// Vulkan 1.2: "flags is reserved for future use." "flags must be 0"
-		UNSUPPORTED("pCreateInfo->pInputAssemblyState->flags 0x%08X", int(pCreateInfo->pInputAssemblyState->flags));
+		UNSUPPORTED("pCreateInfo->pInputAssemblyState->flags 0x%08X", int(inputAssemblyState->flags));
 	}
 
 	primitiveRestartEnable = (inputAssemblyState->primitiveRestartEnable != VK_FALSE);
 	topology = inputAssemblyState->topology;
+}
 
-	const VkPipelineRasterizationStateCreateInfo *rasterizationState = pCreateInfo->pRasterizationState;
+void VertexInputInterfaceState::applyState(const DynamicState &dynamicState)
+{
+	if(dynamicStateFlags.dynamicPrimitiveRestartEnable)
+	{
+		primitiveRestartEnable = dynamicState.primitiveRestartEnable;
+	}
+
+	if(dynamicStateFlags.dynamicPrimitiveTopology)
+	{
+		topology = dynamicState.primitiveTopology;
+	}
+}
+
+bool VertexInputInterfaceState::isDrawPoint(bool polygonModeAware, VkPolygonMode polygonMode) const
+{
+	switch(topology)
+	{
+	case VK_PRIMITIVE_TOPOLOGY_POINT_LIST:
+		return true;
+	case VK_PRIMITIVE_TOPOLOGY_LINE_LIST:
+	case VK_PRIMITIVE_TOPOLOGY_LINE_STRIP:
+		return false;
+	case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST:
+	case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP:
+	case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN:
+		return polygonModeAware ? (polygonMode == VK_POLYGON_MODE_POINT) : false;
+	default:
+		UNSUPPORTED("topology %d", int(topology));
+	}
+	return false;
+}
+
+bool VertexInputInterfaceState::isDrawLine(bool polygonModeAware, VkPolygonMode polygonMode) const
+{
+	switch(topology)
+	{
+	case VK_PRIMITIVE_TOPOLOGY_POINT_LIST:
+		return false;
+	case VK_PRIMITIVE_TOPOLOGY_LINE_LIST:
+	case VK_PRIMITIVE_TOPOLOGY_LINE_STRIP:
+		return true;
+	case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST:
+	case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP:
+	case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN:
+		return polygonModeAware ? (polygonMode == VK_POLYGON_MODE_LINE) : false;
+	default:
+		UNSUPPORTED("topology %d", int(topology));
+	}
+	return false;
+}
+
+bool VertexInputInterfaceState::isDrawTriangle(bool polygonModeAware, VkPolygonMode polygonMode) const
+{
+	switch(topology)
+	{
+	case VK_PRIMITIVE_TOPOLOGY_POINT_LIST:
+	case VK_PRIMITIVE_TOPOLOGY_LINE_LIST:
+	case VK_PRIMITIVE_TOPOLOGY_LINE_STRIP:
+		return false;
+	case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST:
+	case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP:
+	case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN:
+		return polygonModeAware ? (polygonMode == VK_POLYGON_MODE_FILL) : true;
+	default:
+		UNSUPPORTED("topology %d", int(topology));
+	}
+	return false;
+}
+
+void PreRasterizationState::initialize(const vk::Device *device,
+                                       const VkPipelineViewportStateCreateInfo *viewportState,
+                                       const VkPipelineRasterizationStateCreateInfo *rasterizationState,
+                                       const vk::RenderPass *renderPass, uint32_t subpassIndex,
+                                       const VkPipelineRenderingCreateInfo *rendering,
+                                       const DynamicStateFlags &allDynamicStateFlags)
+{
+	dynamicStateFlags = allDynamicStateFlags.preRasterization;
 
 	if(rasterizationState->flags != 0)
 	{
 		// Vulkan 1.2: "flags is reserved for future use." "flags must be 0"
-		UNSUPPORTED("pCreateInfo->pRasterizationState->flags 0x%08X", int(pCreateInfo->pRasterizationState->flags));
+		UNSUPPORTED("pCreateInfo->pRasterizationState->flags 0x%08X", int(rasterizationState->flags));
 	}
 
-	rasterizerDiscard = (rasterizationState->rasterizerDiscardEnable != VK_FALSE);
+	rasterizerDiscard = rasterizationState->rasterizerDiscardEnable != VK_FALSE;
 	cullMode = rasterizationState->cullMode;
 	frontFace = rasterizationState->frontFace;
 	polygonMode = rasterizationState->polygonMode;
@@ -493,14 +612,8 @@
 		extensionCreateInfo = extensionCreateInfo->pNext;
 	}
 
-	// Only access rasterization state if rasterization is not disabled.
-	if(rasterizationState->rasterizerDiscardEnable == VK_FALSE || dynamicStateFlags.dynamicRasterizerDiscardEnable)
+	if(!rasterizerDiscard || dynamicStateFlags.dynamicRasterizerDiscardEnable)
 	{
-		const VkPipelineViewportStateCreateInfo *viewportState = pCreateInfo->pViewportState;
-		const VkPipelineMultisampleStateCreateInfo *multisampleState = pCreateInfo->pMultisampleState;
-		const VkPipelineDepthStencilStateCreateInfo *depthStencilState = pCreateInfo->pDepthStencilState;
-		const VkPipelineColorBlendStateCreateInfo *colorBlendState = pCreateInfo->pColorBlendState;
-
 		extensionCreateInfo = reinterpret_cast<const VkBaseInStructure *>(viewportState->pNext);
 		while(extensionCreateInfo != nullptr)
 		{
@@ -525,7 +638,7 @@
 		if(viewportState->flags != 0)
 		{
 			// Vulkan 1.2: "flags is reserved for future use." "flags must be 0"
-			UNSUPPORTED("pCreateInfo->pViewportState->flags 0x%08X", int(pCreateInfo->pViewportState->flags));
+			UNSUPPORTED("pCreateInfo->pViewportState->flags 0x%08X", int(viewportState->flags));
 		}
 
 		if((viewportState->viewportCount > 1) ||
@@ -543,114 +656,206 @@
 		{
 			viewport = viewportState->pViewports[0];
 		}
+	}
+}
 
-		if(multisampleState->flags != 0)
+void PreRasterizationState::applyState(const DynamicState &dynamicState)
+{
+	if(dynamicStateFlags.dynamicLineWidth)
+	{
+		lineWidth = dynamicState.lineWidth;
+	}
+
+	if(dynamicStateFlags.dynamicDepthBias)
+	{
+		constantDepthBias = dynamicState.depthBiasConstantFactor;
+		slopeDepthBias = dynamicState.depthBiasSlopeFactor;
+		depthBiasClamp = dynamicState.depthBiasClamp;
+	}
+
+	if(dynamicStateFlags.dynamicDepthBiasEnable)
+	{
+		depthBiasEnable = dynamicState.depthBiasEnable;
+	}
+
+	if(dynamicStateFlags.dynamicCullMode)
+	{
+		cullMode = dynamicState.cullMode;
+	}
+
+	if(dynamicStateFlags.dynamicFrontFace)
+	{
+		frontFace = dynamicState.frontFace;
+	}
+
+	if(dynamicStateFlags.dynamicViewport)
+	{
+		viewport = dynamicState.viewport;
+	}
+
+	if(dynamicStateFlags.dynamicScissor)
+	{
+		scissor = dynamicState.scissor;
+	}
+
+	if(dynamicStateFlags.dynamicViewportWithCount && dynamicState.viewportCount > 0)
+	{
+		viewport.width = static_cast<float>(dynamicState.viewports[0].extent.width);
+		viewport.height = static_cast<float>(dynamicState.viewports[0].extent.height);
+		viewport.x = static_cast<float>(dynamicState.viewports[0].offset.x);
+		viewport.y = static_cast<float>(dynamicState.viewports[0].offset.y);
+	}
+
+	if(dynamicStateFlags.dynamicScissorWithCount && dynamicState.scissorCount > 0)
+	{
+		scissor = dynamicState.scissors[0];
+	}
+
+	if(dynamicStateFlags.dynamicRasterizerDiscardEnable)
+	{
+		rasterizerDiscard = dynamicState.rasterizerDiscardEnable;
+	}
+}
+
+void FragmentState::initialize(const VkPipelineDepthStencilStateCreateInfo *depthStencilState,
+                               const vk::RenderPass *renderPass, uint32_t subpassIndex,
+                               const VkPipelineRenderingCreateInfo *rendering,
+                               const DynamicStateFlags &allDynamicStateFlags)
+{
+	dynamicStateFlags = allDynamicStateFlags.fragment;
+
+	if(renderPass)
+	{
+		const VkSubpassDescription &subpass = renderPass->getSubpass(subpassIndex);
+
+		// Ignore pDepthStencilState when "the subpass of the render pass the pipeline
+		// is created against does not use a depth/stencil attachment"
+		if(subpass.pDepthStencilAttachment &&
+		   subpass.pDepthStencilAttachment->attachment != VK_ATTACHMENT_UNUSED)
 		{
-			// Vulkan 1.2: "flags is reserved for future use." "flags must be 0"
-			UNSUPPORTED("pCreateInfo->pMultisampleState->flags 0x%08X", int(pCreateInfo->pMultisampleState->flags));
+			setDepthStencilState(depthStencilState);
 		}
+	}
+	else  // No render pass
+	{
+		// When a pipeline is created without a VkRenderPass, if the VkPipelineRenderingCreateInfo structure
+		// is present in the pNext chain of VkGraphicsPipelineCreateInfo, it specifies the view mask and
+		// format of attachments used for rendering. If this structure is not specified, and the pipeline
+		// does not include a VkRenderPass, viewMask and colorAttachmentCount are 0, and
+		// depthAttachmentFormat and stencilAttachmentFormat are VK_FORMAT_UNDEFINED. If a graphics pipeline
+		// is created with a valid VkRenderPass, parameters of this structure are ignored.
 
-		sampleShadingEnable = (multisampleState->sampleShadingEnable != VK_FALSE);
-		if(sampleShadingEnable)
+		if(rendering)
 		{
-			minSampleShading = multisampleState->minSampleShading;
-		}
-
-		if(multisampleState->alphaToOneEnable != VK_FALSE)
-		{
-			UNSUPPORTED("VkPhysicalDeviceFeatures::alphaToOne");
-		}
-
-		switch(multisampleState->rasterizationSamples)
-		{
-		case VK_SAMPLE_COUNT_1_BIT:
-			sampleCount = 1;
-			break;
-		case VK_SAMPLE_COUNT_4_BIT:
-			sampleCount = 4;
-			break;
-		default:
-			UNSUPPORTED("Unsupported sample count");
-		}
-
-		VkSampleMask sampleMask;
-		if(multisampleState->pSampleMask)
-		{
-			sampleMask = multisampleState->pSampleMask[0];
-		}
-		else  // "If pSampleMask is NULL, it is treated as if the mask has all bits set to 1."
-		{
-			sampleMask = ~0;
-		}
-
-		alphaToCoverage = (multisampleState->alphaToCoverageEnable != VK_FALSE);
-		multiSampleMask = sampleMask & ((unsigned)0xFFFFFFFF >> (32 - sampleCount));
-
-		const vk::RenderPass *renderPass = vk::Cast(pCreateInfo->renderPass);
-		if(renderPass)
-		{
-			const VkSubpassDescription &subpass = renderPass->getSubpass(pCreateInfo->subpass);
-
-			// Ignore pDepthStencilState when "the subpass of the render pass the pipeline
-			// is created against does not use a depth/stencil attachment"
-			if(subpass.pDepthStencilAttachment &&
-			   subpass.pDepthStencilAttachment->attachment != VK_ATTACHMENT_UNUSED)
+			if((rendering->depthAttachmentFormat != VK_FORMAT_UNDEFINED) ||
+			   (rendering->stencilAttachmentFormat != VK_FORMAT_UNDEFINED))
 			{
+				// If renderPass is VK_NULL_HANDLE, the pipeline is being created with fragment
+				// shader state, and either of VkPipelineRenderingCreateInfo::depthAttachmentFormat
+				// or VkPipelineRenderingCreateInfo::stencilAttachmentFormat are not
+				// VK_FORMAT_UNDEFINED, pDepthStencilState must be a valid pointer to a valid
+				// VkPipelineDepthStencilStateCreateInfo structure
+				ASSERT(depthStencilState);
+
 				setDepthStencilState(depthStencilState);
 			}
-
-			// Ignore pColorBlendState when "the subpass of the render pass the pipeline
-			// is created against does not use any color attachments"
-			for(uint32_t i = 0; i < subpass.colorAttachmentCount; i++)
-			{
-				if(subpass.pColorAttachments[i].attachment != VK_ATTACHMENT_UNUSED)
-				{
-					setColorBlendState(colorBlendState);
-					break;
-				}
-			}
-		}
-		else  // No render pass
-		{
-			// When a pipeline is created without a VkRenderPass, if the VkPipelineRenderingCreateInfo structure
-			// is present in the pNext chain of VkGraphicsPipelineCreateInfo, it specifies the view mask and
-			// format of attachments used for rendering. If this structure is not specified, and the pipeline
-			// does not include a VkRenderPass, viewMask and colorAttachmentCount are 0, and
-			// depthAttachmentFormat and stencilAttachmentFormat are VK_FORMAT_UNDEFINED. If a graphics pipeline
-			// is created with a valid VkRenderPass, parameters of this structure are ignored.
-
-			const auto *renderingCreateInfo = GetExtendedStruct<VkPipelineRenderingCreateInfo>(pCreateInfo->pNext, VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO);
-			if(renderingCreateInfo)
-			{
-				if((renderingCreateInfo->depthAttachmentFormat != VK_FORMAT_UNDEFINED) ||
-				   (renderingCreateInfo->stencilAttachmentFormat != VK_FORMAT_UNDEFINED))
-				{
-					// If renderPass is VK_NULL_HANDLE, the pipeline is being created with fragment
-					// shader state, and either of VkPipelineRenderingCreateInfo::depthAttachmentFormat
-					// or VkPipelineRenderingCreateInfo::stencilAttachmentFormat are not
-					// VK_FORMAT_UNDEFINED, pDepthStencilState must be a valid pointer to a valid
-					// VkPipelineDepthStencilStateCreateInfo structure
-					ASSERT(depthStencilState);
-
-					setDepthStencilState(depthStencilState);
-				}
-
-				if(renderingCreateInfo->colorAttachmentCount > 0)
-				{
-					// If renderPass is VK_NULL_HANDLE, the pipeline is being created with fragment
-					// output interface state, and VkPipelineRenderingCreateInfo::colorAttachmentCount
-					// is not equal to 0, pColorBlendState must be a valid pointer to a valid
-					// VkPipelineColorBlendStateCreateInfo structure
-					ASSERT(colorBlendState);
-
-					setColorBlendState(colorBlendState);
-				}
-			}
 		}
 	}
 }
 
-void GraphicsState::setDepthStencilState(const VkPipelineDepthStencilStateCreateInfo *depthStencilState)
+void FragmentState::applyState(const DynamicState &dynamicState)
+{
+	if(dynamicStateFlags.dynamicDepthTestEnable)
+	{
+		depthTestEnable = dynamicState.depthTestEnable;
+	}
+
+	if(dynamicStateFlags.dynamicDepthWriteEnable)
+	{
+		depthWriteEnable = dynamicState.depthWriteEnable;
+	}
+
+	if(dynamicStateFlags.dynamicDepthBoundsTestEnable)
+	{
+		depthBoundsTestEnable = dynamicState.depthBoundsTestEnable;
+	}
+
+	if(dynamicStateFlags.dynamicDepthBounds && depthBoundsTestEnable)
+	{
+		minDepthBounds = dynamicState.minDepthBounds;
+		maxDepthBounds = dynamicState.maxDepthBounds;
+	}
+
+	if(dynamicStateFlags.dynamicDepthCompareOp)
+	{
+		depthCompareMode = dynamicState.depthCompareOp;
+	}
+
+	if(dynamicStateFlags.dynamicStencilTestEnable)
+	{
+		stencilEnable = dynamicState.stencilTestEnable;
+	}
+
+	if(dynamicStateFlags.dynamicStencilOp && stencilEnable)
+	{
+		if(dynamicState.faceMask & VK_STENCIL_FACE_FRONT_BIT)
+		{
+			frontStencil.compareOp = dynamicState.frontStencil.compareOp;
+			frontStencil.depthFailOp = dynamicState.frontStencil.depthFailOp;
+			frontStencil.failOp = dynamicState.frontStencil.failOp;
+			frontStencil.passOp = dynamicState.frontStencil.passOp;
+		}
+
+		if(dynamicState.faceMask & VK_STENCIL_FACE_BACK_BIT)
+		{
+			backStencil.compareOp = dynamicState.backStencil.compareOp;
+			backStencil.depthFailOp = dynamicState.backStencil.depthFailOp;
+			backStencil.failOp = dynamicState.backStencil.failOp;
+			backStencil.passOp = dynamicState.backStencil.passOp;
+		}
+	}
+
+	if(dynamicStateFlags.dynamicStencilCompareMask && stencilEnable)
+	{
+		frontStencil.compareMask = dynamicState.frontStencil.compareMask;
+		backStencil.compareMask = dynamicState.backStencil.compareMask;
+	}
+
+	if(dynamicStateFlags.dynamicStencilWriteMask && stencilEnable)
+	{
+		frontStencil.writeMask = dynamicState.frontStencil.writeMask;
+		backStencil.writeMask = dynamicState.backStencil.writeMask;
+	}
+
+	if(dynamicStateFlags.dynamicStencilReference && stencilEnable)
+	{
+		frontStencil.reference = dynamicState.frontStencil.reference;
+		backStencil.reference = dynamicState.backStencil.reference;
+	}
+}
+
+bool FragmentState::depthWriteActive(const Attachments &attachments) const
+{
+	// "Depth writes are always disabled when depthTestEnable is VK_FALSE."
+	return depthTestActive(attachments) && depthWriteEnable;
+}
+
+bool FragmentState::depthTestActive(const Attachments &attachments) const
+{
+	return attachments.depthBuffer && depthTestEnable;
+}
+
+bool FragmentState::stencilActive(const Attachments &attachments) const
+{
+	return attachments.stencilBuffer && stencilEnable;
+}
+
+bool FragmentState::depthBoundsTestActive(const Attachments &attachments) const
+{
+	return attachments.depthBuffer && depthBoundsTestEnable;
+}
+
+void FragmentState::setDepthStencilState(const VkPipelineDepthStencilStateCreateInfo *depthStencilState)
 {
 	if((depthStencilState->flags &
 	    ~(VK_PIPELINE_DEPTH_STENCIL_STATE_CREATE_RASTERIZATION_ORDER_ATTACHMENT_DEPTH_ACCESS_BIT_EXT |
@@ -675,7 +880,65 @@
 	}
 }
 
-void GraphicsState::setColorBlendState(const VkPipelineColorBlendStateCreateInfo *colorBlendState)
+void FragmentOutputInterfaceState::initialize(const VkPipelineColorBlendStateCreateInfo *colorBlendState,
+                                              const VkPipelineMultisampleStateCreateInfo *multisampleState,
+                                              const vk::RenderPass *renderPass, uint32_t subpassIndex,
+                                              const VkPipelineRenderingCreateInfo *rendering,
+                                              const DynamicStateFlags &allDynamicStateFlags)
+{
+	dynamicStateFlags = allDynamicStateFlags.fragmentOutputInterface;
+
+	multisample.set(multisampleState);
+
+	if(renderPass)
+	{
+		const VkSubpassDescription &subpass = renderPass->getSubpass(subpassIndex);
+
+		// Ignore pColorBlendState when "the subpass of the render pass the pipeline
+		// is created against does not use any color attachments"
+		for(uint32_t i = 0; i < subpass.colorAttachmentCount; i++)
+		{
+			if(subpass.pColorAttachments[i].attachment != VK_ATTACHMENT_UNUSED)
+			{
+				setColorBlendState(colorBlendState);
+				break;
+			}
+		}
+	}
+	else  // No render pass
+	{
+		// When a pipeline is created without a VkRenderPass, if the VkPipelineRenderingCreateInfo structure
+		// is present in the pNext chain of VkGraphicsPipelineCreateInfo, it specifies the view mask and
+		// format of attachments used for rendering. If this structure is not specified, and the pipeline
+		// does not include a VkRenderPass, viewMask and colorAttachmentCount are 0, and
+		// depthAttachmentFormat and stencilAttachmentFormat are VK_FORMAT_UNDEFINED. If a graphics pipeline
+		// is created with a valid VkRenderPass, parameters of this structure are ignored.
+
+		if(rendering)
+		{
+			if(rendering->colorAttachmentCount > 0)
+			{
+				// If renderPass is VK_NULL_HANDLE, the pipeline is being created with fragment
+				// output interface state, and VkPipelineRenderingCreateInfo::colorAttachmentCount
+				// is not equal to 0, pColorBlendState must be a valid pointer to a valid
+				// VkPipelineColorBlendStateCreateInfo structure
+				ASSERT(colorBlendState);
+
+				setColorBlendState(colorBlendState);
+			}
+		}
+	}
+}
+
+void FragmentOutputInterfaceState::applyState(const DynamicState &dynamicState)
+{
+	if(dynamicStateFlags.dynamicBlendConstants)
+	{
+		blendConstants = dynamicState.blendConstants;
+	}
+}
+
+void FragmentOutputInterfaceState::setColorBlendState(const VkPipelineColorBlendStateCreateInfo *colorBlendState)
 {
 	if(colorBlendState->flags != 0 &&
 	   colorBlendState->flags != VK_PIPELINE_COLOR_BLEND_STATE_CREATE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_BIT_EXT)
@@ -731,230 +994,7 @@
 	}
 }
 
-bool GraphicsState::isDrawPoint(bool polygonModeAware) const
-{
-	switch(topology)
-	{
-	case VK_PRIMITIVE_TOPOLOGY_POINT_LIST:
-		return true;
-	case VK_PRIMITIVE_TOPOLOGY_LINE_LIST:
-	case VK_PRIMITIVE_TOPOLOGY_LINE_STRIP:
-		return false;
-	case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST:
-	case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP:
-	case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN:
-		return polygonModeAware ? (polygonMode == VK_POLYGON_MODE_POINT) : false;
-	default:
-		UNSUPPORTED("topology %d", int(topology));
-	}
-	return false;
-}
-
-bool GraphicsState::isDrawLine(bool polygonModeAware) const
-{
-	switch(topology)
-	{
-	case VK_PRIMITIVE_TOPOLOGY_POINT_LIST:
-		return false;
-	case VK_PRIMITIVE_TOPOLOGY_LINE_LIST:
-	case VK_PRIMITIVE_TOPOLOGY_LINE_STRIP:
-		return true;
-	case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST:
-	case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP:
-	case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN:
-		return polygonModeAware ? (polygonMode == VK_POLYGON_MODE_LINE) : false;
-	default:
-		UNSUPPORTED("topology %d", int(topology));
-	}
-	return false;
-}
-
-bool GraphicsState::isDrawTriangle(bool polygonModeAware) const
-{
-	switch(topology)
-	{
-	case VK_PRIMITIVE_TOPOLOGY_POINT_LIST:
-	case VK_PRIMITIVE_TOPOLOGY_LINE_LIST:
-	case VK_PRIMITIVE_TOPOLOGY_LINE_STRIP:
-		return false;
-	case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST:
-	case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP:
-	case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN:
-		return polygonModeAware ? (polygonMode == VK_POLYGON_MODE_FILL) : true;
-	default:
-		UNSUPPORTED("topology %d", int(topology));
-	}
-	return false;
-}
-
-bool GraphicsState::depthWriteActive(const Attachments &attachments) const
-{
-	// "Depth writes are always disabled when depthTestEnable is VK_FALSE."
-	return depthTestActive(attachments) && depthWriteEnable;
-}
-
-bool GraphicsState::depthTestActive(const Attachments &attachments) const
-{
-	return attachments.depthBuffer && depthTestEnable;
-}
-
-bool GraphicsState::stencilActive(const Attachments &attachments) const
-{
-	return attachments.stencilBuffer && stencilEnable;
-}
-
-bool GraphicsState::depthBoundsTestActive(const Attachments &attachments) const
-{
-	return attachments.depthBuffer && depthBoundsTestEnable;
-}
-
-const GraphicsState GraphicsState::combineStates(const DynamicState &dynamicState) const
-{
-	GraphicsState combinedState = *this;
-
-	// Apply either pipeline state or dynamic state
-	if(dynamicStateFlags.dynamicDepthTestEnable)
-	{
-		combinedState.depthTestEnable = dynamicState.depthTestEnable;
-	}
-
-	if(dynamicStateFlags.dynamicDepthWriteEnable)
-	{
-		combinedState.depthWriteEnable = dynamicState.depthWriteEnable;
-	}
-
-	if(dynamicStateFlags.dynamicDepthCompareOp)
-	{
-		combinedState.depthCompareMode = dynamicState.depthCompareOp;
-	}
-
-	if(dynamicStateFlags.dynamicDepthBoundsTestEnable)
-	{
-		combinedState.depthBoundsTestEnable = dynamicState.depthBoundsTestEnable;
-	}
-
-	if(dynamicStateFlags.dynamicStencilTestEnable)
-	{
-		combinedState.stencilEnable = dynamicState.stencilTestEnable;
-	}
-
-	if(dynamicStateFlags.dynamicRasterizerDiscardEnable)
-	{
-		combinedState.rasterizerDiscard = dynamicState.rasterizerDiscardEnable;
-	}
-
-	if(dynamicStateFlags.dynamicDepthBiasEnable)
-	{
-		combinedState.depthBiasEnable = dynamicState.depthBiasEnable;
-	}
-
-	if(dynamicStateFlags.dynamicPrimitiveRestartEnable)
-	{
-		combinedState.primitiveRestartEnable = dynamicState.primitiveRestartEnable;
-	}
-
-	if(dynamicStateFlags.dynamicScissor)
-	{
-		combinedState.scissor = dynamicState.scissor;
-	}
-
-	if(dynamicStateFlags.dynamicViewport)
-	{
-		combinedState.viewport = dynamicState.viewport;
-	}
-
-	if(dynamicStateFlags.dynamicLineWidth)
-	{
-		combinedState.lineWidth = dynamicState.lineWidth;
-	}
-
-	if(dynamicStateFlags.dynamicBlendConstants)
-	{
-		combinedState.blendConstants = dynamicState.blendConstants;
-	}
-
-	if(dynamicStateFlags.dynamicDepthBias)
-	{
-		combinedState.constantDepthBias = dynamicState.depthBiasConstantFactor;
-		combinedState.slopeDepthBias = dynamicState.depthBiasSlopeFactor;
-		combinedState.depthBiasClamp = dynamicState.depthBiasClamp;
-	}
-
-	if(dynamicStateFlags.dynamicDepthBounds && combinedState.depthBoundsTestEnable)
-	{
-		combinedState.minDepthBounds = dynamicState.minDepthBounds;
-		combinedState.maxDepthBounds = dynamicState.maxDepthBounds;
-	}
-
-	if(dynamicStateFlags.dynamicStencilCompareMask && combinedState.stencilEnable)
-	{
-		combinedState.frontStencil.compareMask = dynamicState.frontStencil.compareMask;
-		combinedState.backStencil.compareMask = dynamicState.backStencil.compareMask;
-	}
-
-	if(dynamicStateFlags.dynamicStencilWriteMask && combinedState.stencilEnable)
-	{
-		combinedState.frontStencil.writeMask = dynamicState.frontStencil.writeMask;
-		combinedState.backStencil.writeMask = dynamicState.backStencil.writeMask;
-	}
-
-	if(dynamicStateFlags.dynamicStencilReference && combinedState.stencilEnable)
-	{
-		combinedState.frontStencil.reference = dynamicState.frontStencil.reference;
-		combinedState.backStencil.reference = dynamicState.backStencil.reference;
-	}
-
-	if(dynamicStateFlags.dynamicStencilOp && combinedState.stencilEnable)
-	{
-		if(dynamicState.faceMask & VK_STENCIL_FACE_FRONT_BIT)
-		{
-			combinedState.frontStencil.compareOp = dynamicState.frontStencil.compareOp;
-			combinedState.frontStencil.depthFailOp = dynamicState.frontStencil.depthFailOp;
-			combinedState.frontStencil.failOp = dynamicState.frontStencil.failOp;
-			combinedState.frontStencil.passOp = dynamicState.frontStencil.passOp;
-		}
-
-		if(dynamicState.faceMask & VK_STENCIL_FACE_BACK_BIT)
-		{
-			combinedState.backStencil.compareOp = dynamicState.backStencil.compareOp;
-			combinedState.backStencil.depthFailOp = dynamicState.backStencil.depthFailOp;
-			combinedState.backStencil.failOp = dynamicState.backStencil.failOp;
-			combinedState.backStencil.passOp = dynamicState.backStencil.passOp;
-		}
-	}
-
-	if(dynamicStateFlags.dynamicCullMode)
-	{
-		combinedState.cullMode = dynamicState.cullMode;
-	}
-
-	if(dynamicStateFlags.dynamicFrontFace)
-	{
-		combinedState.frontFace = dynamicState.frontFace;
-	}
-
-	if(dynamicStateFlags.dynamicPrimitiveTopology)
-	{
-		combinedState.topology = dynamicState.primitiveTopology;
-	}
-
-	if(dynamicStateFlags.dynamicViewportWithCount && (dynamicState.viewportCount > 0))
-	{
-		combinedState.viewport.width = static_cast<float>(dynamicState.viewports[0].extent.width);
-		combinedState.viewport.height = static_cast<float>(dynamicState.viewports[0].extent.height);
-		combinedState.viewport.x = static_cast<float>(dynamicState.viewports[0].offset.x);
-		combinedState.viewport.y = static_cast<float>(dynamicState.viewports[0].offset.y);
-	}
-
-	if(dynamicStateFlags.dynamicScissorWithCount && (dynamicState.scissorCount > 0))
-	{
-		combinedState.scissor = dynamicState.scissors[0];
-	}
-
-	return combinedState;
-}
-
-BlendState GraphicsState::getBlendState(int index, const Attachments &attachments, bool fragmentContainsKill) const
+BlendState FragmentOutputInterfaceState::getBlendState(int index, const Attachments &attachments, bool fragmentContainsKill) const
 {
 	ASSERT((index >= 0) && (index < sw::MAX_COLOR_BUFFERS));
 	auto &state = blendState[index];
@@ -977,7 +1017,7 @@
 	return activeBlendState;
 }
 
-bool GraphicsState::alphaBlendActive(int index, const Attachments &attachments, bool fragmentContainsKill) const
+bool FragmentOutputInterfaceState::alphaBlendActive(int index, const Attachments &attachments, bool fragmentContainsKill) const
 {
 	ASSERT((index >= 0) && (index < sw::MAX_COLOR_BUFFERS));
 	auto &state = blendState[index];
@@ -999,7 +1039,7 @@
 	return colorBlend || alphaBlend;
 }
 
-VkBlendFactor GraphicsState::blendFactor(VkBlendOp blendOperation, VkBlendFactor blendFactor) const
+VkBlendFactor FragmentOutputInterfaceState::blendFactor(VkBlendOp blendOperation, VkBlendFactor blendFactor) const
 {
 	switch(blendOperation)
 	{
@@ -1031,7 +1071,7 @@
 	}
 }
 
-VkBlendOp GraphicsState::blendOperation(VkBlendOp blendOperation, VkBlendFactor sourceBlendFactor, VkBlendFactor destBlendFactor, vk::Format format) const
+VkBlendOp FragmentOutputInterfaceState::blendOperation(VkBlendOp blendOperation, VkBlendFactor sourceBlendFactor, VkBlendFactor destBlendFactor, vk::Format format) const
 {
 	switch(blendOperation)
 	{
@@ -1122,7 +1162,7 @@
 	return blendOperation;
 }
 
-bool GraphicsState::colorWriteActive(const Attachments &attachments) const
+bool FragmentOutputInterfaceState::colorWriteActive(const Attachments &attachments) const
 {
 	for(int i = 0; i < sw::MAX_COLOR_BUFFERS; i++)
 	{
@@ -1135,7 +1175,7 @@
 	return false;
 }
 
-int GraphicsState::colorWriteActive(int index, const Attachments &attachments) const
+int FragmentOutputInterfaceState::colorWriteActive(int index, const Attachments &attachments) const
 {
 	ASSERT((index >= 0) && (index < sw::MAX_COLOR_BUFFERS));
 	auto &state = blendState[index];
@@ -1156,4 +1196,61 @@
 	return colorWriteMask[index];
 }
 
+GraphicsState::GraphicsState(const Device *device, const VkGraphicsPipelineCreateInfo *pCreateInfo,
+                             const PipelineLayout *layout)
+    : pipelineLayout(layout)
+{
+	if((pCreateInfo->flags &
+	    ~(VK_PIPELINE_CREATE_DISABLE_OPTIMIZATION_BIT |
+	      VK_PIPELINE_CREATE_DERIVATIVE_BIT |
+	      VK_PIPELINE_CREATE_ALLOW_DERIVATIVES_BIT |
+	      VK_PIPELINE_CREATE_EARLY_RETURN_ON_FAILURE_BIT_EXT |
+	      VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT_EXT)) != 0)
+	{
+		UNSUPPORTED("pCreateInfo->flags 0x%08X", int(pCreateInfo->flags));
+	}
+
+	DynamicStateFlags dynamicStateFlags = ParseDynamicStateFlags(pCreateInfo->pDynamicState);
+	const auto *rendering = GetExtendedStruct<VkPipelineRenderingCreateInfo>(pCreateInfo, VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO);
+
+	vertexInputInterfaceState.initialize(pCreateInfo->pVertexInputState,
+	                                     pCreateInfo->pInputAssemblyState,
+	                                     dynamicStateFlags);
+	preRasterizationState.initialize(device,
+	                                 pCreateInfo->pViewportState,
+	                                 pCreateInfo->pRasterizationState,
+	                                 vk::Cast(pCreateInfo->renderPass),
+	                                 pCreateInfo->subpass,
+	                                 rendering,
+	                                 dynamicStateFlags);
+
+	if(!pCreateInfo->pRasterizationState->rasterizerDiscardEnable || dynamicStateFlags.preRasterization.dynamicRasterizerDiscardEnable)
+	{
+		fragmentState.initialize(pCreateInfo->pDepthStencilState,
+		                         vk::Cast(pCreateInfo->renderPass),
+		                         pCreateInfo->subpass,
+		                         rendering,
+		                         dynamicStateFlags);
+		fragmentOutputInterfaceState.initialize(pCreateInfo->pColorBlendState,
+		                                        pCreateInfo->pMultisampleState,
+		                                        vk::Cast(pCreateInfo->renderPass),
+		                                        pCreateInfo->subpass,
+		                                        rendering,
+		                                        dynamicStateFlags);
+	}
+}
+
+GraphicsState GraphicsState::combineStates(const DynamicState &dynamicState) const
+{
+	GraphicsState combinedState = *this;
+
+	// Make a copy of the states for modification, then either keep the pipeline state or apply the dynamic state.
+	combinedState.vertexInputInterfaceState.applyState(dynamicState);
+	combinedState.preRasterizationState.applyState(dynamicState);
+	combinedState.fragmentState.applyState(dynamicState);
+	combinedState.fragmentOutputInterfaceState.applyState(dynamicState);
+
+	return combinedState;
+}
+
 }  // namespace vk
diff --git a/src/Device/Context.hpp b/src/Device/Context.hpp
index 8263477..17a5188 100644
--- a/src/Device/Context.hpp
+++ b/src/Device/Context.hpp
@@ -30,6 +30,7 @@
 class Device;
 class ImageView;
 class PipelineLayout;
+class RenderPass;
 
 struct VertexInputBinding
 {
@@ -88,6 +89,18 @@
 	sw::Stream stream[sw::MAX_INTERFACE_COMPONENTS / 4];
 };
 
+struct MultisampleState
+{
+	bool sampleShadingEnable = false;
+	bool alphaToCoverage = false;
+
+	int sampleCount = 0;
+	unsigned int multiSampleMask = 0;
+	float minSampleShading = 0.0f;
+
+	void set(const VkPipelineMultisampleStateCreateInfo *multisampleState);
+};
+
 struct BlendState : sw::Memset<BlendState>
 {
 	BlendState()
@@ -152,100 +165,216 @@
 	VkBool32 primitiveRestartEnable = VK_FALSE;
 };
 
-struct GraphicsState
+struct VertexInputInterfaceDynamicStateFlags
 {
-	GraphicsState(const Device *device, const VkGraphicsPipelineCreateInfo *pCreateInfo, const PipelineLayout *layout);
+	bool dynamicPrimitiveRestartEnable : 1;
+	bool dynamicPrimitiveTopology : 1;
+	bool dynamicVertexInputBindingStride : 1;
+};
 
-	const GraphicsState combineStates(const DynamicState &dynamicState) const;
+struct PreRasterizationDynamicStateFlags
+{
+	bool dynamicLineWidth : 1;
+	bool dynamicDepthBias : 1;
+	bool dynamicDepthBiasEnable : 1;
+	bool dynamicCullMode : 1;
+	bool dynamicFrontFace : 1;
+	bool dynamicViewport : 1;
+	bool dynamicScissor : 1;
+	bool dynamicViewportWithCount : 1;
+	bool dynamicScissorWithCount : 1;
+	bool dynamicRasterizerDiscardEnable : 1;
+};
 
-	inline const PipelineLayout *getPipelineLayout() const { return pipelineLayout; }
+struct FragmentDynamicStateFlags
+{
+	bool dynamicDepthTestEnable : 1;
+	bool dynamicDepthWriteEnable : 1;
+	bool dynamicDepthBoundsTestEnable : 1;
+	bool dynamicDepthBounds : 1;
+	bool dynamicDepthCompareOp : 1;
+	bool dynamicStencilTestEnable : 1;
+	bool dynamicStencilOp : 1;
+	bool dynamicStencilCompareMask : 1;
+	bool dynamicStencilWriteMask : 1;
+	bool dynamicStencilReference : 1;
+};
+
+struct FragmentOutputInterfaceDynamicStateFlags
+{
+	bool dynamicBlendConstants : 1;
+};
+
+struct DynamicStateFlags
+{
+	VertexInputInterfaceDynamicStateFlags vertexInputInterface;
+	PreRasterizationDynamicStateFlags preRasterization;
+	FragmentDynamicStateFlags fragment;
+	FragmentOutputInterfaceDynamicStateFlags fragmentOutputInterface;
+};
+
+struct VertexInputInterfaceState
+{
+	void initialize(const VkPipelineVertexInputStateCreateInfo *vertexInputState,
+	           const VkPipelineInputAssemblyStateCreateInfo *inputAssemblyState,
+	           const DynamicStateFlags &allDynamicStateFlags);
+
+	void applyState(const DynamicState &dynamicState);
+
 	inline VkPrimitiveTopology getTopology() const { return topology; }
-
-	inline VkProvokingVertexModeEXT getProvokingVertexMode() const { return provokingVertexMode; }
-
-	inline VkStencilOpState getFrontStencil() const { return frontStencil; }
-	inline VkStencilOpState getBackStencil() const { return backStencil; }
-
-	// Pixel processor states
-	inline VkCullModeFlags getCullMode() const { return cullMode; }
-	inline VkFrontFace getFrontFace() const { return frontFace; }
-	inline VkPolygonMode getPolygonMode() const { return polygonMode; }
-	inline VkLineRasterizationModeEXT getLineRasterizationMode() const { return lineRasterizationMode; }
-
-	inline float getConstantDepthBias() const { return depthBiasEnable ? constantDepthBias : 0; }
-	inline float getSlopeDepthBias() const { return depthBiasEnable ? slopeDepthBias : 0; }
-	inline float getDepthBiasClamp() const { return depthBiasEnable ? depthBiasClamp : 0; }
-	inline float getMinDepthBounds() const { return minDepthBounds; }
-	inline float getMaxDepthBounds() const { return maxDepthBounds; }
-	inline bool hasDepthRangeUnrestricted() const { return depthRangeUnrestricted; }
-	inline bool getDepthClampEnable() const { return depthClampEnable; }
-	inline bool getDepthClipEnable() const { return depthClipEnable; }
-	inline bool getDepthClipNegativeOneToOne() const { return depthClipNegativeOneToOne; }
-
-	// Pixel processor states
-	inline bool hasRasterizerDiscard() const { return rasterizerDiscard; }
-	inline VkCompareOp getDepthCompareMode() const { return depthCompareMode; }
-
-	inline float getLineWidth() const { return lineWidth; }
-
-	inline unsigned int getMultiSampleMask() const { return multiSampleMask; }
-	inline int getSampleCount() const { return sampleCount; }
-	inline bool hasSampleShadingEnabled() const { return sampleShadingEnable; }
-	inline float getMinSampleShading() const { return minSampleShading; }
-	inline bool hasAlphaToCoverage() const { return alphaToCoverage; }
-
 	inline bool hasPrimitiveRestartEnable() const { return primitiveRestartEnable; }
-	inline const VkRect2D &getScissor() const { return scissor; }
-	inline const VkViewport &getViewport() const { return viewport; }
-	inline const sw::float4 &getBlendConstants() const { return blendConstants; }
-
-	bool isDrawPoint(bool polygonModeAware) const;
-	bool isDrawLine(bool polygonModeAware) const;
-	bool isDrawTriangle(bool polygonModeAware) const;
-
-	BlendState getBlendState(int index, const Attachments &attachments, bool fragmentContainsKill) const;
-
-	int colorWriteActive(int index, const Attachments &attachments) const;
-	bool depthWriteActive(const Attachments &attachments) const;
-	bool depthTestActive(const Attachments &attachments) const;
-	bool stencilActive(const Attachments &attachments) const;
-	bool depthBoundsTestActive(const Attachments &attachments) const;
 
 	inline bool hasDynamicVertexStride() const { return dynamicStateFlags.dynamicVertexInputBindingStride; }
 	inline bool hasDynamicTopology() const { return dynamicStateFlags.dynamicPrimitiveTopology; }
 	inline bool hasDynamicPrimitiveRestartEnable() const { return dynamicStateFlags.dynamicPrimitiveRestartEnable; }
 
-private:
-	struct DynamicStateFlags
-	{
-		bool dynamicViewport : 1;
-		bool dynamicScissor : 1;
-		bool dynamicLineWidth : 1;
-		bool dynamicDepthBias : 1;
-		bool dynamicBlendConstants : 1;
-		bool dynamicDepthBounds : 1;
-		bool dynamicStencilCompareMask : 1;
-		bool dynamicStencilWriteMask : 1;
-		bool dynamicStencilReference : 1;
-		bool dynamicCullMode : 1;
-		bool dynamicFrontFace : 1;
-		bool dynamicPrimitiveTopology : 1;
-		bool dynamicViewportWithCount : 1;
-		bool dynamicScissorWithCount : 1;
-		bool dynamicVertexInputBindingStride : 1;
-		bool dynamicDepthTestEnable : 1;
-		bool dynamicDepthWriteEnable : 1;
-		bool dynamicDepthCompareOp : 1;
-		bool dynamicDepthBoundsTestEnable : 1;
-		bool dynamicStencilTestEnable : 1;
-		bool dynamicStencilOp : 1;
-		bool dynamicRasterizerDiscardEnable : 1;
-		bool dynamicDepthBiasEnable : 1;
-		bool dynamicPrimitiveRestartEnable : 1;
-	};
+	bool isDrawPoint(bool polygonModeAware, VkPolygonMode polygonMode) const;
+	bool isDrawLine(bool polygonModeAware, VkPolygonMode polygonMode) const;
+	bool isDrawTriangle(bool polygonModeAware, VkPolygonMode polygonMode) const;
 
-	static DynamicStateFlags ParseDynamicStateFlags(const VkPipelineDynamicStateCreateInfo *dynamicStateCreateInfo);
+private:
+	VertexInputInterfaceDynamicStateFlags dynamicStateFlags = {};
+
+	bool primitiveRestartEnable = false;
+
+	VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
+};
+
+struct PreRasterizationState
+{
+	void initialize(const vk::Device *device,
+	           const VkPipelineViewportStateCreateInfo *viewportState,
+	           const VkPipelineRasterizationStateCreateInfo *rasterizationState,
+	           const vk::RenderPass *renderPass, uint32_t subpassIndex,
+	           const VkPipelineRenderingCreateInfo *rendering,
+	           const DynamicStateFlags &allDynamicStateFlags);
+
+	void applyState(const DynamicState &dynamicState);
+
+	inline VkCullModeFlags getCullMode() const { return cullMode; }
+	inline VkFrontFace getFrontFace() const { return frontFace; }
+	inline VkPolygonMode getPolygonMode() const { return polygonMode; }
+	inline VkProvokingVertexModeEXT getProvokingVertexMode() const { return provokingVertexMode; }
+	inline VkLineRasterizationModeEXT getLineRasterizationMode() const { return lineRasterizationMode; }
+
+	inline bool hasRasterizerDiscard() const { return rasterizerDiscard; }
+
+	inline float getConstantDepthBias() const { return depthBiasEnable ? constantDepthBias : 0; }
+	inline float getSlopeDepthBias() const { return depthBiasEnable ? slopeDepthBias : 0; }
+	inline float getDepthBiasClamp() const { return depthBiasEnable ? depthBiasClamp : 0; }
+
+	inline bool hasDepthRangeUnrestricted() const { return depthRangeUnrestricted; }
+	inline bool getDepthClampEnable() const { return depthClampEnable; }
+	inline bool getDepthClipEnable() const { return depthClipEnable; }
+	inline bool getDepthClipNegativeOneToOne() const { return depthClipNegativeOneToOne; }
+
+	inline float getLineWidth() const { return lineWidth; }
+
+	inline const VkRect2D &getScissor() const { return scissor; }
+	inline const VkViewport &getViewport() const { return viewport; }
+
+private:
+	PreRasterizationDynamicStateFlags dynamicStateFlags = {};
+
+	bool rasterizerDiscard = false;
+	bool depthClampEnable = false;
+	bool depthClipEnable = false;
+	bool depthClipNegativeOneToOne = false;
+	bool depthBiasEnable = false;
+	bool depthRangeUnrestricted = false;
+
+	VkCullModeFlags cullMode = 0;
+	VkFrontFace frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
+	VkPolygonMode polygonMode = VK_POLYGON_MODE_FILL;
+	VkProvokingVertexModeEXT provokingVertexMode = VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT;
+	VkLineRasterizationModeEXT lineRasterizationMode = VK_LINE_RASTERIZATION_MODE_DEFAULT_EXT;
+
+	float depthBiasClamp = 0.0f;
+	float constantDepthBias = 0.0f;
+	float slopeDepthBias = 0.0f;
+
+	float lineWidth = 0.0f;
+
+	VkRect2D scissor = {};
+	VkViewport viewport = {};
+};
+
+struct FragmentState
+{
+	void initialize(const VkPipelineDepthStencilStateCreateInfo *depthStencilState,
+	           const vk::RenderPass *renderPass, uint32_t subpassIndex,
+	           const VkPipelineRenderingCreateInfo *rendering,
+	           const DynamicStateFlags &allDynamicStateFlags);
+
+	void applyState(const DynamicState &dynamicState);
+
+	inline VkStencilOpState getFrontStencil() const { return frontStencil; }
+	inline VkStencilOpState getBackStencil() const { return backStencil; }
+
+	inline float getMinDepthBounds() const { return minDepthBounds; }
+	inline float getMaxDepthBounds() const { return maxDepthBounds; }
+
+	inline VkCompareOp getDepthCompareMode() const { return depthCompareMode; }
+
+	bool depthWriteActive(const Attachments &attachments) const;
+	bool depthTestActive(const Attachments &attachments) const;
+	bool stencilActive(const Attachments &attachments) const;
+	bool depthBoundsTestActive(const Attachments &attachments) const;
+
+private:
 	void setDepthStencilState(const VkPipelineDepthStencilStateCreateInfo *depthStencilState);
+
+	FragmentDynamicStateFlags dynamicStateFlags = {};
+
+	bool depthTestEnable = false;
+	bool depthWriteEnable = false;
+	bool depthBoundsTestEnable = false;
+	bool stencilEnable = false;
+
+	float minDepthBounds = 0.0f;
+	float maxDepthBounds = 0.0f;
+
+	VkCompareOp depthCompareMode = VK_COMPARE_OP_NEVER;
+
+	VkStencilOpState frontStencil = {};
+	VkStencilOpState backStencil = {};
+
+	// Note: if a pipeline library is created with the fragment state only, and sample shading
+	// is enabled or a render pass is provided, VkPipelineMultisampleStateCreateInfo must be
+	// provided.  This must identically match with the one provided for the fragment output
+	// interface library.
+	//
+	// Currently, SwiftShader can always use the copy provided and stored in
+	// FragmentOutputInterfaceState.  If a future optimization requires access to this state in
+	// a pipeline library without fragment output interface, a copy of MultisampleState can be
+	// placed here and initialized under the above condition.
+	//
+	// Ref: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap10.html#pipeline-graphics-subsets
+};
+
+struct FragmentOutputInterfaceState
+{
+	void initialize(const VkPipelineColorBlendStateCreateInfo *colorBlendState,
+	            const VkPipelineMultisampleStateCreateInfo *multisampleState,
+	            const vk::RenderPass *renderPass, uint32_t subpassIndex,
+	            const VkPipelineRenderingCreateInfo *rendering,
+	            const DynamicStateFlags &allDynamicStateFlags);
+
+	void applyState(const DynamicState &dynamicState);
+
+	inline unsigned int getMultiSampleMask() const { return multisample.multiSampleMask; }
+	inline int getSampleCount() const { return multisample.sampleCount; }
+	inline bool hasSampleShadingEnabled() const { return multisample.sampleShadingEnable; }
+	inline float getMinSampleShading() const { return multisample.minSampleShading; }
+	inline bool hasAlphaToCoverage() const { return multisample.alphaToCoverage; }
+
+	inline const sw::float4 &getBlendConstants() const { return blendConstants; }
+
+	BlendState getBlendState(int index, const Attachments &attachments, bool fragmentContainsKill) const;
+
+	int colorWriteActive(int index, const Attachments &attachments) const;
+
+private:
 	void setColorBlendState(const VkPipelineColorBlendStateCreateInfo *colorBlendState);
 
 	VkBlendFactor blendFactor(VkBlendOp blendOperation, VkBlendFactor blendFactor) const;
@@ -254,56 +383,38 @@
 	bool alphaBlendActive(int index, const Attachments &attachments, bool fragmentContainsKill) const;
 	bool colorWriteActive(const Attachments &attachments) const;
 
-	const PipelineLayout *pipelineLayout = nullptr;
-	const DynamicStateFlags dynamicStateFlags = {};
-	VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
-
-	VkProvokingVertexModeEXT provokingVertexMode = VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT;
-
-	bool stencilEnable = false;
-	VkStencilOpState frontStencil = {};
-	VkStencilOpState backStencil = {};
-
-	// Pixel processor states
-	VkCullModeFlags cullMode = 0;
-	VkFrontFace frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
-	VkPolygonMode polygonMode = VK_POLYGON_MODE_FILL;
-	VkLineRasterizationModeEXT lineRasterizationMode = VK_LINE_RASTERIZATION_MODE_DEFAULT_EXT;
-
-	bool depthBiasEnable = false;
-	float constantDepthBias = 0.0f;
-	float slopeDepthBias = 0.0f;
-	float depthBiasClamp = 0.0f;
-	float minDepthBounds = 0.0f;
-	float maxDepthBounds = 0.0f;
-	bool depthRangeUnrestricted = false;
-
-	// Pixel processor states
-	bool rasterizerDiscard = false;
-	bool depthBoundsTestEnable = false;
-	bool depthTestEnable = false;
-	VkCompareOp depthCompareMode = VK_COMPARE_OP_NEVER;
-	bool depthWriteEnable = false;
-	bool depthClampEnable = false;
-	bool depthClipEnable = false;
-	bool depthClipNegativeOneToOne = false;
-
-	float lineWidth = 0.0f;
-
 	int colorWriteMask[sw::MAX_COLOR_BUFFERS] = {};  // RGBA
-	unsigned int multiSampleMask = 0;
-	int sampleCount = 0;
-	bool alphaToCoverage = false;
 
-	bool sampleShadingEnable = false;
-	float minSampleShading = 0.0f;
+	FragmentOutputInterfaceDynamicStateFlags dynamicStateFlags = {};
 
-	bool primitiveRestartEnable = false;
-	VkRect2D scissor = {};
-	VkViewport viewport = {};
 	sw::float4 blendConstants = {};
-
 	BlendState blendState[sw::MAX_COLOR_BUFFERS] = {};
+
+	MultisampleState multisample;
+};
+
+struct GraphicsState
+{
+	GraphicsState(const Device *device, const VkGraphicsPipelineCreateInfo *pCreateInfo, const PipelineLayout *layout);
+
+	GraphicsState combineStates(const DynamicState &dynamicState) const;
+
+	inline const PipelineLayout *getPipelineLayout() const { return pipelineLayout; }
+
+	const VertexInputInterfaceState &getVertexInputInterfaceState() const { return vertexInputInterfaceState; }
+	const PreRasterizationState &getPreRasterizationState() const { return preRasterizationState; }
+	const FragmentState &getFragmentState() const { return fragmentState; }
+	const FragmentOutputInterfaceState &getFragmentOutputInterfaceState() const { return fragmentOutputInterfaceState; }
+
+private:
+	const PipelineLayout *pipelineLayout = nullptr;
+
+	// The four subsets of a graphics pipeline as described in the spec.  With
+	// VK_EXT_graphics_pipeline_library, a number of these may be valid.
+	VertexInputInterfaceState vertexInputInterfaceState;
+	PreRasterizationState preRasterizationState;
+	FragmentState fragmentState;
+	FragmentOutputInterfaceState fragmentOutputInterfaceState;
 };
 
 }  // namespace vk
diff --git a/src/Device/PixelProcessor.cpp b/src/Device/PixelProcessor.cpp
index 93848b0..ba61c2f 100644
--- a/src/Device/PixelProcessor.cpp
+++ b/src/Device/PixelProcessor.cpp
@@ -73,6 +73,11 @@
 
 const PixelProcessor::State PixelProcessor::update(const vk::GraphicsState &pipelineState, const sw::SpirvShader *fragmentShader, const sw::SpirvShader *vertexShader, const vk::Attachments &attachments, bool occlusionEnabled) const
 {
+	const vk::VertexInputInterfaceState &vertexInputInterfaceState = pipelineState.getVertexInputInterfaceState();
+	const vk::PreRasterizationState &preRasterizationState = pipelineState.getPreRasterizationState();
+	const vk::FragmentState &fragmentState = pipelineState.getFragmentState();
+	const vk::FragmentOutputInterfaceState &fragmentOutputInterfaceState = pipelineState.getFragmentOutputInterfaceState();
+
 	State state;
 
 	state.numClipDistances = vertexShader->getNumOutputClipDistances();
@@ -89,36 +94,36 @@
 		state.pipelineLayoutIdentifier = 0;
 	}
 
-	state.alphaToCoverage = pipelineState.hasAlphaToCoverage();
-	state.depthWriteEnable = pipelineState.depthWriteActive(attachments);
+	state.alphaToCoverage = fragmentOutputInterfaceState.hasAlphaToCoverage();
+	state.depthWriteEnable = fragmentState.depthWriteActive(attachments);
 
-	if(pipelineState.stencilActive(attachments))
+	if(fragmentState.stencilActive(attachments))
 	{
 		state.stencilActive = true;
-		state.frontStencil = pipelineState.getFrontStencil();
-		state.backStencil = pipelineState.getBackStencil();
+		state.frontStencil = fragmentState.getFrontStencil();
+		state.backStencil = fragmentState.getBackStencil();
 	}
 
 	state.depthFormat = attachments.depthFormat();
-	state.depthBoundsTestActive = pipelineState.depthBoundsTestActive(attachments);
-	state.minDepthBounds = pipelineState.getMinDepthBounds();
-	state.maxDepthBounds = pipelineState.getMaxDepthBounds();
+	state.depthBoundsTestActive = fragmentState.depthBoundsTestActive(attachments);
+	state.minDepthBounds = fragmentState.getMinDepthBounds();
+	state.maxDepthBounds = fragmentState.getMaxDepthBounds();
 
-	if(pipelineState.depthTestActive(attachments))
+	if(fragmentState.depthTestActive(attachments))
 	{
 		state.depthTestActive = true;
-		state.depthCompareMode = pipelineState.getDepthCompareMode();
+		state.depthCompareMode = fragmentState.getDepthCompareMode();
 
-		state.depthBias = (pipelineState.getConstantDepthBias() != 0.0f) || (pipelineState.getSlopeDepthBias() != 0.0f);
+		state.depthBias = preRasterizationState.getConstantDepthBias() != 0.0f || preRasterizationState.getSlopeDepthBias() != 0.0f;
 
-		bool pipelineDepthClamp = pipelineState.getDepthClampEnable();
+		bool pipelineDepthClamp = preRasterizationState.getDepthClampEnable();
 		// "For fixed-point depth buffers, fragment depth values are always limited to the range [0,1] by clamping after depth bias addition is performed.
 		//  Unless the VK_EXT_depth_range_unrestricted extension is enabled, fragment depth values are clamped even when the depth buffer uses a floating-point representation."
-		state.depthClamp = pipelineDepthClamp || !state.depthFormat.isFloatFormat() || !pipelineState.hasDepthRangeUnrestricted();
+		state.depthClamp = pipelineDepthClamp || !state.depthFormat.isFloatFormat() || !preRasterizationState.hasDepthRangeUnrestricted();
 
 		if(pipelineDepthClamp)
 		{
-			const VkViewport viewport = pipelineState.getViewport();
+			const VkViewport viewport = preRasterizationState.getViewport();
 			state.minDepthClamp = min(viewport.minDepth, viewport.maxDepth);
 			state.maxDepthClamp = max(viewport.minDepth, viewport.maxDepth);
 		}
@@ -134,15 +139,17 @@
 	bool fragmentContainsDiscard = (fragmentShader && fragmentShader->getAnalysis().ContainsDiscard);
 	for(int i = 0; i < MAX_COLOR_BUFFERS; i++)
 	{
-		state.colorWriteMask |= pipelineState.colorWriteActive(i, attachments) << (4 * i);
+		state.colorWriteMask |= fragmentOutputInterfaceState.colorWriteActive(i, attachments) << (4 * i);
 		state.colorFormat[i] = attachments.colorFormat(i);
-		state.blendState[i] = pipelineState.getBlendState(i, attachments, fragmentContainsDiscard);
+		state.blendState[i] = fragmentOutputInterfaceState.getBlendState(i, attachments, fragmentContainsDiscard);
 	}
 
-	state.multiSampleCount = static_cast<unsigned int>(pipelineState.getSampleCount());
-	state.multiSampleMask = pipelineState.getMultiSampleMask();
-	state.enableMultiSampling = (state.multiSampleCount > 1) &&
-	                            !(pipelineState.isDrawLine(true) && (pipelineState.getLineRasterizationMode() == VK_LINE_RASTERIZATION_MODE_BRESENHAM_EXT));
+	const bool isBresenhamLine = vertexInputInterfaceState.isDrawLine(true, preRasterizationState.getPolygonMode()) &&
+	                             preRasterizationState.getLineRasterizationMode() == VK_LINE_RASTERIZATION_MODE_BRESENHAM_EXT;
+
+	state.multiSampleCount = static_cast<unsigned int>(fragmentOutputInterfaceState.getSampleCount());
+	state.multiSampleMask = fragmentOutputInterfaceState.getMultiSampleMask();
+	state.enableMultiSampling = state.multiSampleCount > 1 && !isBresenhamLine;
 
 	// SampleId and SamplePosition require per-sample fragment shader invocations, so the Vulkan spec
 	// requires turning on sample shading if either of them is present in the shader:
@@ -160,8 +167,8 @@
 	}
 	else
 	{
-		state.sampleShadingEnabled = pipelineState.hasSampleShadingEnabled();
-		state.minSampleShading = pipelineState.getMinSampleShading();
+		state.sampleShadingEnabled = fragmentOutputInterfaceState.hasSampleShadingEnabled();
+		state.minSampleShading = fragmentOutputInterfaceState.getMinSampleShading();
 	}
 
 	if(state.enableMultiSampling && fragmentShader)
@@ -169,7 +176,7 @@
 		state.centroid = fragmentShader->getAnalysis().NeedsCentroid;
 	}
 
-	state.frontFace = pipelineState.getFrontFace();
+	state.frontFace = preRasterizationState.getFrontFace();
 
 	state.hash = state.computeHash();
 
diff --git a/src/Device/Renderer.cpp b/src/Device/Renderer.cpp
index c0cb963..365cef3 100644
--- a/src/Device/Renderer.cpp
+++ b/src/Device/Renderer.cpp
@@ -197,11 +197,24 @@
 	draw->id = id;
 
 	const vk::GraphicsState &pipelineState = pipeline->getState(dynamicState);
-	const bool hasRasterizerDiscard = pipelineState.hasRasterizerDiscard();
 
+	// A graphics pipeline must always be "complete" before it can be used for drawing.  A
+	// complete graphics pipeline always includes the vertex input interface and
+	// pre-rasterization subsets, but only includes fragment and fragment output interface
+	// subsets if rasterizer discard is not enabled.
+	//
+	// Note that in the following, the setupPrimitives, setupRoutine and pixelRoutine functions
+	// are only called when rasterizer discard is not enabled.  If rasterizer discard is
+	// enabled, these functions and state for the latter two states are not set.
+	const vk::VertexInputInterfaceState &vertexInputInterfaceState = pipelineState.getVertexInputInterfaceState();
+	const vk::PreRasterizationState &preRasterizationState = pipelineState.getPreRasterizationState();
+	const vk::FragmentState &fragmentState = pipelineState.getFragmentState();
+	const vk::FragmentOutputInterfaceState &fragmentOutputInterfaceState = pipelineState.getFragmentOutputInterfaceState();
+
+	const bool hasRasterizerDiscard = preRasterizationState.hasRasterizerDiscard();
 	if(!hasRasterizerDiscard)
 	{
-		pixelProcessor.setBlendConstant(pipelineState.getBlendConstants());
+		pixelProcessor.setBlendConstant(fragmentOutputInterfaceState.getBlendConstants());
 	}
 
 	const vk::Inputs &inputs = pipeline->getInputs();
@@ -233,7 +246,7 @@
 
 	// The sample count affects the batch size even if rasterization is disabled.
 	// TODO(b/147812380): Eliminate the dependency between multisampling and batch size.
-	int ms = hasRasterizerDiscard ? 1 : pipelineState.getSampleCount();
+	int ms = hasRasterizerDiscard ? 1 : fragmentOutputInterfaceState.getSampleCount();
 	ASSERT(ms > 0);
 
 	unsigned int numPrimitivesPerBatch = MaxBatchSize / ms;
@@ -244,15 +257,15 @@
 	draw->numPrimitives = count;
 	draw->numPrimitivesPerBatch = numPrimitivesPerBatch;
 	draw->numBatches = (count + draw->numPrimitivesPerBatch - 1) / draw->numPrimitivesPerBatch;
-	draw->topology = pipelineState.getTopology();
-	draw->provokingVertexMode = pipelineState.getProvokingVertexMode();
+	draw->topology = vertexInputInterfaceState.getTopology();
+	draw->provokingVertexMode = preRasterizationState.getProvokingVertexMode();
 	draw->indexType = pipeline->getIndexBuffer().getIndexType();
-	draw->lineRasterizationMode = pipelineState.getLineRasterizationMode();
+	draw->lineRasterizationMode = preRasterizationState.getLineRasterizationMode();
 	draw->descriptorSetObjects = inputs.getDescriptorSetObjects();
 	draw->pipelineLayout = pipelineState.getPipelineLayout();
-	draw->depthClipEnable = pipelineState.getDepthClipEnable();
-	draw->depthClipNegativeOneToOne = pipelineState.getDepthClipNegativeOneToOne();
-	data->lineWidth = pipelineState.getLineWidth();
+	draw->depthClipEnable = preRasterizationState.getDepthClipEnable();
+	draw->depthClipNegativeOneToOne = preRasterizationState.getDepthClipNegativeOneToOne();
+	data->lineWidth = preRasterizationState.getLineWidth();
 	data->rasterizerDiscard = hasRasterizerDiscard;
 
 	data->descriptorSets = inputs.getDescriptorSets();
@@ -263,7 +276,7 @@
 		const sw::Stream &stream = inputs.getStream(i);
 		data->input[i] = stream.buffer;
 		data->robustnessSize[i] = stream.robustnessSize;
-		data->stride[i] = inputs.getVertexStride(i, pipelineState.hasDynamicVertexStride());
+		data->stride[i] = inputs.getVertexStride(i, vertexInputInterfaceState.hasDynamicVertexStride());
 	}
 
 	data->indices = indexBuffer;
@@ -275,7 +288,7 @@
 
 	// Viewport
 	{
-		const VkViewport &viewport = pipelineState.getViewport();
+		const VkViewport &viewport = preRasterizationState.getViewport();
 
 		float W = 0.5f * viewport.width;
 		float H = 0.5f * viewport.height;
@@ -295,12 +308,12 @@
 		data->viewportHeight = abs(viewport.height);
 		data->depthRange = Z;
 		data->depthNear = N;
-		data->constantDepthBias = pipelineState.getConstantDepthBias();
-		data->slopeDepthBias = pipelineState.getSlopeDepthBias();
-		data->depthBiasClamp = pipelineState.getDepthBiasClamp();
+		data->constantDepthBias = preRasterizationState.getConstantDepthBias();
+		data->slopeDepthBias = preRasterizationState.getSlopeDepthBias();
+		data->depthBiasClamp = preRasterizationState.getDepthBiasClamp();
 
 		// Adjust viewport transform based on the negativeOneToOne state.
-		if(pipelineState.getDepthClipNegativeOneToOne())
+		if(preRasterizationState.getDepthClipNegativeOneToOne())
 		{
 			data->depthRange = Z * 0.5f;
 			data->depthNear = (F + N) * 0.5f;
@@ -309,7 +322,7 @@
 
 	// Scissor
 	{
-		const VkRect2D &scissor = pipelineState.getScissor();
+		const VkRect2D &scissor = preRasterizationState.getScissor();
 
 		int x0 = renderArea.offset.x;
 		int y0 = renderArea.offset.y;
@@ -323,10 +336,12 @@
 
 	if(!hasRasterizerDiscard)
 	{
+		const VkPolygonMode polygonMode = preRasterizationState.getPolygonMode();
+
 		DrawCall::SetupFunction setupPrimitives = nullptr;
-		if(pipelineState.isDrawTriangle(false))
+		if(vertexInputInterfaceState.isDrawTriangle(false, polygonMode))
 		{
-			switch(pipelineState.getPolygonMode())
+			switch(preRasterizationState.getPolygonMode())
 			{
 			case VK_POLYGON_MODE_FILL:
 				setupPrimitives = &DrawCall::setupSolidTriangles;
@@ -340,11 +355,11 @@
 				numPrimitivesPerBatch /= 3;
 				break;
 			default:
-				UNSUPPORTED("polygon mode: %d", int(pipelineState.getPolygonMode()));
+				UNSUPPORTED("polygon mode: %d", int(preRasterizationState.getPolygonMode()));
 				return;
 			}
 		}
-		else if(pipelineState.isDrawLine(false))
+		else if(vertexInputInterfaceState.isDrawLine(false, polygonMode))
 		{
 			setupPrimitives = &DrawCall::setupLines;
 		}
@@ -360,8 +375,8 @@
 
 		if(pixelState.stencilActive)
 		{
-			data->stencil[0].set(pipelineState.getFrontStencil().reference, pipelineState.getFrontStencil().compareMask, pipelineState.getFrontStencil().writeMask);
-			data->stencil[1].set(pipelineState.getBackStencil().reference, pipelineState.getBackStencil().compareMask, pipelineState.getBackStencil().writeMask);
+			data->stencil[0].set(fragmentState.getFrontStencil().reference, fragmentState.getFrontStencil().compareMask, fragmentState.getFrontStencil().writeMask);
+			data->stencil[1].set(fragmentState.getBackStencil().reference, fragmentState.getBackStencil().compareMask, fragmentState.getBackStencil().writeMask);
 		}
 
 		data->factor = pixelProcessor.factor;
diff --git a/src/Device/SetupProcessor.cpp b/src/Device/SetupProcessor.cpp
index 04daa5b..3a42290 100644
--- a/src/Device/SetupProcessor.cpp
+++ b/src/Device/SetupProcessor.cpp
@@ -57,25 +57,34 @@
 
 SetupProcessor::State SetupProcessor::update(const vk::GraphicsState &pipelineState, const sw::SpirvShader *fragmentShader, const sw::SpirvShader *vertexShader, const vk::Attachments &attachments) const
 {
+	const vk::VertexInputInterfaceState &vertexInputInterfaceState = pipelineState.getVertexInputInterfaceState();
+	const vk::PreRasterizationState &preRasterizationState = pipelineState.getPreRasterizationState();
+	const vk::FragmentState &fragmentState = pipelineState.getFragmentState();
+	const vk::FragmentOutputInterfaceState &fragmentOutputInterfaceState = pipelineState.getFragmentOutputInterfaceState();
+
 	State state;
 
 	bool vPosZW = (fragmentShader && fragmentShader->hasBuiltinInput(spv::BuiltInFragCoord));
 
-	state.isDrawPoint = pipelineState.isDrawPoint(true);
-	state.isDrawLine = pipelineState.isDrawLine(true);
-	state.isDrawTriangle = pipelineState.isDrawTriangle(true);
-	state.fixedPointDepthBuffer = attachments.depthBuffer && !attachments.depthBuffer->getFormat(VK_IMAGE_ASPECT_DEPTH_BIT).isFloatFormat();
-	state.applyConstantDepthBias = pipelineState.isDrawTriangle(false) && (pipelineState.getConstantDepthBias() != 0.0f);
-	state.applySlopeDepthBias = pipelineState.isDrawTriangle(false) && (pipelineState.getSlopeDepthBias() != 0.0f);
-	state.applyDepthBiasClamp = pipelineState.isDrawTriangle(false) && (pipelineState.getDepthBiasClamp() != 0.0f);
-	state.interpolateZ = pipelineState.depthTestActive(attachments) || vPosZW;
-	state.interpolateW = fragmentShader != nullptr;
-	state.frontFace = pipelineState.getFrontFace();
-	state.cullMode = pipelineState.getCullMode();
+	const VkPolygonMode polygonMode = preRasterizationState.getPolygonMode();
 
-	state.multiSampleCount = pipelineState.getSampleCount();
-	state.enableMultiSampling = (state.multiSampleCount > 1) &&
-	                            !(pipelineState.isDrawLine(true) && (pipelineState.getLineRasterizationMode() == VK_LINE_RASTERIZATION_MODE_BRESENHAM_EXT));
+	state.isDrawPoint = vertexInputInterfaceState.isDrawPoint(true, polygonMode);
+	state.isDrawLine = vertexInputInterfaceState.isDrawLine(true, polygonMode);
+	state.isDrawTriangle = vertexInputInterfaceState.isDrawTriangle(true, polygonMode);
+	state.fixedPointDepthBuffer = attachments.depthBuffer && !attachments.depthBuffer->getFormat(VK_IMAGE_ASPECT_DEPTH_BIT).isFloatFormat();
+	state.applyConstantDepthBias = vertexInputInterfaceState.isDrawTriangle(false, polygonMode) && (preRasterizationState.getConstantDepthBias() != 0.0f);
+	state.applySlopeDepthBias = vertexInputInterfaceState.isDrawTriangle(false, polygonMode) && (preRasterizationState.getSlopeDepthBias() != 0.0f);
+	state.applyDepthBiasClamp = vertexInputInterfaceState.isDrawTriangle(false, polygonMode) && (preRasterizationState.getDepthBiasClamp() != 0.0f);
+	state.interpolateZ = fragmentState.depthTestActive(attachments) || vPosZW;
+	state.interpolateW = fragmentShader != nullptr;
+	state.frontFace = preRasterizationState.getFrontFace();
+	state.cullMode = preRasterizationState.getCullMode();
+
+	const bool isBresenhamLine = vertexInputInterfaceState.isDrawLine(true, preRasterizationState.getPolygonMode()) &&
+	                             preRasterizationState.getLineRasterizationMode() == VK_LINE_RASTERIZATION_MODE_BRESENHAM_EXT;
+
+	state.multiSampleCount = fragmentOutputInterfaceState.getSampleCount();
+	state.enableMultiSampling = state.multiSampleCount > 1 && !isBresenhamLine;
 
 	state.numClipDistances = vertexShader->getNumOutputClipDistances();
 	state.numCullDistances = vertexShader->getNumOutputCullDistances();
diff --git a/src/Device/VertexProcessor.cpp b/src/Device/VertexProcessor.cpp
index 23e8e9d..931b50b 100644
--- a/src/Device/VertexProcessor.cpp
+++ b/src/Device/VertexProcessor.cpp
@@ -67,14 +67,17 @@
 
 const VertexProcessor::State VertexProcessor::update(const vk::GraphicsState &pipelineState, const sw::SpirvShader *vertexShader, const vk::Inputs &inputs)
 {
+	const vk::VertexInputInterfaceState &vertexInputInterfaceState = pipelineState.getVertexInputInterfaceState();
+	const vk::PreRasterizationState &preRasterizationState = pipelineState.getPreRasterizationState();
+
 	State state;
 
 	state.shaderID = vertexShader->getIdentifier();
 	state.pipelineLayoutIdentifier = pipelineState.getPipelineLayout()->identifier;
 	state.robustBufferAccess = vertexShader->getRobustBufferAccess();
-	state.isPoint = pipelineState.getTopology() == VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
-	state.depthClipEnable = pipelineState.getDepthClipEnable();
-	state.depthClipNegativeOneToOne = pipelineState.getDepthClipNegativeOneToOne();
+	state.isPoint = vertexInputInterfaceState.getTopology() == VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
+	state.depthClipEnable = preRasterizationState.getDepthClipEnable();
+	state.depthClipNegativeOneToOne = preRasterizationState.getDepthClipNegativeOneToOne();
 
 	for(size_t i = 0; i < MAX_INTERFACE_COMPONENTS / 4; i++)
 	{
diff --git a/src/Vulkan/VkPipeline.cpp b/src/Vulkan/VkPipeline.cpp
index ad1b121..01fc82d 100644
--- a/src/Vulkan/VkPipeline.cpp
+++ b/src/Vulkan/VkPipeline.cpp
@@ -350,8 +350,10 @@
 
 void GraphicsPipeline::getIndexBuffers(const vk::DynamicState &dynamicState, uint32_t count, uint32_t first, bool indexed, std::vector<std::pair<uint32_t, void *>> *indexBuffers) const
 {
-	const VkPrimitiveTopology topology = state.hasDynamicTopology() ? dynamicState.primitiveTopology : state.getTopology();
-	const bool hasPrimitiveRestartEnable = state.hasDynamicPrimitiveRestartEnable() ? dynamicState.primitiveRestartEnable : state.hasPrimitiveRestartEnable();
+	const vk::VertexInputInterfaceState &vertexInputInterfaceState = state.getVertexInputInterfaceState();
+
+	const VkPrimitiveTopology topology = vertexInputInterfaceState.hasDynamicTopology() ? dynamicState.primitiveTopology : vertexInputInterfaceState.getTopology();
+	const bool hasPrimitiveRestartEnable = vertexInputInterfaceState.hasDynamicPrimitiveRestartEnable() ? dynamicState.primitiveRestartEnable : vertexInputInterfaceState.hasPrimitiveRestartEnable();
 	indexBuffer.getIndexBuffers(topology, count, first, indexed, hasPrimitiveRestartEnable, indexBuffers);
 }
 
diff --git a/src/Vulkan/VkPipeline.hpp b/src/Vulkan/VkPipeline.hpp
index bc5639b..0110d7d 100644
--- a/src/Vulkan/VkPipeline.hpp
+++ b/src/Vulkan/VkPipeline.hpp
@@ -98,7 +98,7 @@
 	const GraphicsState getState(const DynamicState &ds) const { return state.combineStates(ds); }
 
 	void getIndexBuffers(const vk::DynamicState &dynamicState, uint32_t count, uint32_t first, bool indexed, std::vector<std::pair<uint32_t, void *>> *indexBuffers) const;
-	bool hasDynamicVertexStride() const { return state.hasDynamicVertexStride(); }
+	bool hasDynamicVertexStride() const { return state.getVertexInputInterfaceState().hasDynamicVertexStride(); }
 
 	IndexBuffer &getIndexBuffer() { return indexBuffer; }
 	const IndexBuffer &getIndexBuffer() const { return indexBuffer; }