Support VK_EXT_graphics_pipeline_library

And VK_KHR_pipeline_library as corollary.

Bug: b/245568070
Tests: dEQP-VK.*library*
Change-Id: I10afe16ca19fa0a274a5d8c7887480a7c5b4c129
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/68028
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Kokoro-Result: kokoro <noreply+kokoro@google.com>
Presubmit-Ready: Shahbaz Youssefi <syoussefi@google.com>
Tested-by: Shahbaz Youssefi <syoussefi@google.com>
Commit-Queue: Shahbaz Youssefi <syoussefi@google.com>
diff --git a/src/Device/Context.cpp b/src/Device/Context.cpp
index 9024abf..ef80f03 100644
--- a/src/Device/Context.cpp
+++ b/src/Device/Context.cpp
@@ -17,6 +17,7 @@
 #include "Vulkan/VkBuffer.hpp"
 #include "Vulkan/VkDevice.hpp"
 #include "Vulkan/VkImageView.hpp"
+#include "Vulkan/VkPipeline.hpp"
 #include "Vulkan/VkRenderPass.hpp"
 #include "Vulkan/VkStringify.hpp"
 
@@ -197,7 +198,6 @@
 
 	return dynamicStateFlags;
 }
-
 }  // namespace
 
 namespace vk {
@@ -269,7 +269,7 @@
 	}
 }
 
-Inputs::Inputs(const VkPipelineVertexInputStateCreateInfo *vertexInputState)
+void Inputs::initialize(const VkPipelineVertexInputStateCreateInfo *vertexInputState)
 {
 	if(vertexInputState->flags != 0)
 	{
@@ -528,12 +528,14 @@
 }
 
 void PreRasterizationState::initialize(const vk::Device *device,
+                                       const PipelineLayout *layout,
                                        const VkPipelineViewportStateCreateInfo *viewportState,
                                        const VkPipelineRasterizationStateCreateInfo *rasterizationState,
                                        const vk::RenderPass *renderPass, uint32_t subpassIndex,
                                        const VkPipelineRenderingCreateInfo *rendering,
                                        const DynamicStateFlags &allDynamicStateFlags)
 {
+	pipelineLayout = layout;
 	dynamicStateFlags = allDynamicStateFlags.preRasterization;
 
 	if(rasterizationState->flags != 0)
@@ -717,11 +719,14 @@
 	}
 }
 
-void FragmentState::initialize(const VkPipelineDepthStencilStateCreateInfo *depthStencilState,
-                               const vk::RenderPass *renderPass, uint32_t subpassIndex,
-                               const VkPipelineRenderingCreateInfo *rendering,
-                               const DynamicStateFlags &allDynamicStateFlags)
+void FragmentState::initialize(
+    const PipelineLayout *layout,
+    const VkPipelineDepthStencilStateCreateInfo *depthStencilState,
+    const vk::RenderPass *renderPass, uint32_t subpassIndex,
+    const VkPipelineRenderingCreateInfo *rendering,
+    const DynamicStateFlags &allDynamicStateFlags)
 {
+	pipelineLayout = layout;
 	dynamicStateFlags = allDynamicStateFlags.fragment;
 
 	if(renderPass)
@@ -1198,14 +1203,16 @@
 
 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)
+	      VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT_EXT |
+	      VK_PIPELINE_CREATE_LIBRARY_BIT_KHR |
+	      VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT |
+	      VK_PIPELINE_CREATE_LINK_TIME_OPTIMIZATION_BIT_EXT)) != 0)
 	{
 		UNSUPPORTED("pCreateInfo->flags 0x%08X", int(pCreateInfo->flags));
 	}
@@ -1213,24 +1220,46 @@
 	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);
+	// First, get the subset of state specified in pCreateInfo itself.
+	validSubset = GraphicsPipeline::GetGraphicsPipelineSubset(pCreateInfo);
 
-	if(!pCreateInfo->pRasterizationState->rasterizerDiscardEnable || dynamicStateFlags.preRasterization.dynamicRasterizerDiscardEnable)
+	// If rasterizer discard is enabled (and not dynamically overridable), ignore the fragment
+	// and fragment output subsets, as they will not be used.
+	if((validSubset & VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT) != 0 &&
+	   pCreateInfo->pRasterizationState->rasterizerDiscardEnable &&
+	   !dynamicStateFlags.preRasterization.dynamicRasterizerDiscardEnable)
 	{
-		fragmentState.initialize(pCreateInfo->pDepthStencilState,
+		validSubset &= ~(VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT | VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT);
+	}
+
+	if((validSubset & VK_GRAPHICS_PIPELINE_LIBRARY_VERTEX_INPUT_INTERFACE_BIT_EXT) != 0)
+	{
+		vertexInputInterfaceState.initialize(pCreateInfo->pVertexInputState,
+		                                     pCreateInfo->pInputAssemblyState,
+		                                     dynamicStateFlags);
+	}
+	if((validSubset & VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT) != 0)
+	{
+		preRasterizationState.initialize(device,
+		                                 layout,
+		                                 pCreateInfo->pViewportState,
+		                                 pCreateInfo->pRasterizationState,
+		                                 vk::Cast(pCreateInfo->renderPass),
+		                                 pCreateInfo->subpass,
+		                                 rendering,
+		                                 dynamicStateFlags);
+	}
+	if((validSubset & VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT) != 0)
+	{
+		fragmentState.initialize(layout,
+		                         pCreateInfo->pDepthStencilState,
 		                         vk::Cast(pCreateInfo->renderPass),
 		                         pCreateInfo->subpass,
 		                         rendering,
 		                         dynamicStateFlags);
+	}
+	if((validSubset & VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT) != 0)
+	{
 		fragmentOutputInterfaceState.initialize(pCreateInfo->pColorBlendState,
 		                                        pCreateInfo->pMultisampleState,
 		                                        vk::Cast(pCreateInfo->renderPass),
@@ -1238,6 +1267,48 @@
 		                                        rendering,
 		                                        dynamicStateFlags);
 	}
+
+	// Then, apply state coming from pipeline libraries.
+	const auto *libraryCreateInfo = vk::GetExtendedStruct<VkPipelineLibraryCreateInfoKHR>(pCreateInfo->pNext, VK_STRUCTURE_TYPE_PIPELINE_LIBRARY_CREATE_INFO_KHR);
+	if(libraryCreateInfo)
+	{
+		for(uint32_t i = 0; i < libraryCreateInfo->libraryCount; ++i)
+		{
+			const auto *library = static_cast<const GraphicsPipeline *>(vk::Cast(libraryCreateInfo->pLibraries[i]));
+			const GraphicsState &libraryState = library->getState();
+			const VkGraphicsPipelineLibraryFlagsEXT librarySubset = libraryState.validSubset;
+
+			// The library subsets should be disjoint
+			ASSERT((libraryState.validSubset & validSubset) == 0);
+
+			if((librarySubset & VK_GRAPHICS_PIPELINE_LIBRARY_VERTEX_INPUT_INTERFACE_BIT_EXT) != 0)
+			{
+				vertexInputInterfaceState = libraryState.vertexInputInterfaceState;
+			}
+			if((librarySubset & VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT) != 0)
+			{
+				preRasterizationState = libraryState.preRasterizationState;
+				if (layout)
+				{
+					preRasterizationState.overridePipelineLayout(layout);
+				}
+			}
+			if((librarySubset & VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT) != 0)
+			{
+				fragmentState = libraryState.fragmentState;
+				if (layout)
+				{
+					fragmentState.overridePipelineLayout(layout);
+				}
+			}
+			if((librarySubset & VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT) != 0)
+			{
+				fragmentOutputInterfaceState = libraryState.fragmentOutputInterfaceState;
+			}
+
+			validSubset |= libraryState.validSubset;
+		}
+	}
 }
 
 GraphicsState GraphicsState::combineStates(const DynamicState &dynamicState) const
diff --git a/src/Device/Context.hpp b/src/Device/Context.hpp
index 17a5188..55257d4 100644
--- a/src/Device/Context.hpp
+++ b/src/Device/Context.hpp
@@ -65,7 +65,7 @@
 
 struct Inputs
 {
-	Inputs(const VkPipelineVertexInputStateCreateInfo *vertexInputState);
+	void initialize(const VkPipelineVertexInputStateCreateInfo *vertexInputState);
 
 	void updateDescriptorSets(const DescriptorSet::Array &dso,
 	                          const DescriptorSet::Bindings &ds,
@@ -216,8 +216,8 @@
 struct VertexInputInterfaceState
 {
 	void initialize(const VkPipelineVertexInputStateCreateInfo *vertexInputState,
-	           const VkPipelineInputAssemblyStateCreateInfo *inputAssemblyState,
-	           const DynamicStateFlags &allDynamicStateFlags);
+	                const VkPipelineInputAssemblyStateCreateInfo *inputAssemblyState,
+	                const DynamicStateFlags &allDynamicStateFlags);
 
 	void applyState(const DynamicState &dynamicState);
 
@@ -243,11 +243,15 @@
 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);
+	                const PipelineLayout *layout,
+	                const VkPipelineViewportStateCreateInfo *viewportState,
+	                const VkPipelineRasterizationStateCreateInfo *rasterizationState,
+	                const vk::RenderPass *renderPass, uint32_t subpassIndex,
+	                const VkPipelineRenderingCreateInfo *rendering,
+	                const DynamicStateFlags &allDynamicStateFlags);
+
+	inline const PipelineLayout *getPipelineLayout() const { return pipelineLayout; }
+	inline void overridePipelineLayout(const PipelineLayout *linkedLayout) { pipelineLayout = linkedLayout; }
 
 	void applyState(const DynamicState &dynamicState);
 
@@ -274,6 +278,8 @@
 	inline const VkViewport &getViewport() const { return viewport; }
 
 private:
+	const PipelineLayout *pipelineLayout = nullptr;
+
 	PreRasterizationDynamicStateFlags dynamicStateFlags = {};
 
 	bool rasterizerDiscard = false;
@@ -301,10 +307,14 @@
 
 struct FragmentState
 {
-	void initialize(const VkPipelineDepthStencilStateCreateInfo *depthStencilState,
-	           const vk::RenderPass *renderPass, uint32_t subpassIndex,
-	           const VkPipelineRenderingCreateInfo *rendering,
-	           const DynamicStateFlags &allDynamicStateFlags);
+	void initialize(const PipelineLayout *layout,
+	                const VkPipelineDepthStencilStateCreateInfo *depthStencilState,
+	                const vk::RenderPass *renderPass, uint32_t subpassIndex,
+	                const VkPipelineRenderingCreateInfo *rendering,
+	                const DynamicStateFlags &allDynamicStateFlags);
+
+	inline const PipelineLayout *getPipelineLayout() const { return pipelineLayout; }
+	inline void overridePipelineLayout(const PipelineLayout *linkedLayout) { pipelineLayout = linkedLayout; }
 
 	void applyState(const DynamicState &dynamicState);
 
@@ -324,6 +334,8 @@
 private:
 	void setDepthStencilState(const VkPipelineDepthStencilStateCreateInfo *depthStencilState);
 
+	const PipelineLayout *pipelineLayout = nullptr;
+
 	FragmentDynamicStateFlags dynamicStateFlags = {};
 
 	bool depthTestEnable = false;
@@ -355,10 +367,10 @@
 struct FragmentOutputInterfaceState
 {
 	void initialize(const VkPipelineColorBlendStateCreateInfo *colorBlendState,
-	            const VkPipelineMultisampleStateCreateInfo *multisampleState,
-	            const vk::RenderPass *renderPass, uint32_t subpassIndex,
-	            const VkPipelineRenderingCreateInfo *rendering,
-	            const DynamicStateFlags &allDynamicStateFlags);
+	                const VkPipelineMultisampleStateCreateInfo *multisampleState,
+	                const vk::RenderPass *renderPass, uint32_t subpassIndex,
+	                const VkPipelineRenderingCreateInfo *rendering,
+	                const DynamicStateFlags &allDynamicStateFlags);
 
 	void applyState(const DynamicState &dynamicState);
 
@@ -399,22 +411,53 @@
 
 	GraphicsState combineStates(const DynamicState &dynamicState) const;
 
-	inline const PipelineLayout *getPipelineLayout() const { return pipelineLayout; }
+	bool hasVertexInputInterfaceState() const
+	{
+		return (validSubset & VK_GRAPHICS_PIPELINE_LIBRARY_VERTEX_INPUT_INTERFACE_BIT_EXT) != 0;
+	}
+	bool hasPreRasterizationState() const
+	{
+		return (validSubset & VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT) != 0;
+	}
+	bool hasFragmentState() const
+	{
+		return (validSubset & VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT) != 0;
+	}
+	bool hasFragmentOutputInterfaceState() const
+	{
+		return (validSubset & VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT) != 0;
+	}
 
-	const VertexInputInterfaceState &getVertexInputInterfaceState() const { return vertexInputInterfaceState; }
-	const PreRasterizationState &getPreRasterizationState() const { return preRasterizationState; }
-	const FragmentState &getFragmentState() const { return fragmentState; }
-	const FragmentOutputInterfaceState &getFragmentOutputInterfaceState() const { return fragmentOutputInterfaceState; }
+	const VertexInputInterfaceState &getVertexInputInterfaceState() const
+	{
+		ASSERT(hasVertexInputInterfaceState());
+		return vertexInputInterfaceState;
+	}
+	const PreRasterizationState &getPreRasterizationState() const
+	{
+		ASSERT(hasPreRasterizationState());
+		return preRasterizationState;
+	}
+	const FragmentState &getFragmentState() const
+	{
+		ASSERT(hasFragmentState());
+		return fragmentState;
+	}
+	const FragmentOutputInterfaceState &getFragmentOutputInterfaceState() const
+	{
+		ASSERT(hasFragmentOutputInterfaceState());
+		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;
+
+	VkGraphicsPipelineLibraryFlagsEXT validSubset = 0;
 };
 
 }  // namespace vk
diff --git a/src/Device/PixelProcessor.cpp b/src/Device/PixelProcessor.cpp
index ba61c2f..afedb55 100644
--- a/src/Device/PixelProcessor.cpp
+++ b/src/Device/PixelProcessor.cpp
@@ -86,7 +86,7 @@
 	if(fragmentShader)
 	{
 		state.shaderID = fragmentShader->getIdentifier();
-		state.pipelineLayoutIdentifier = pipelineState.getPipelineLayout()->identifier;
+		state.pipelineLayoutIdentifier = fragmentState.getPipelineLayout()->identifier;
 	}
 	else
 	{
diff --git a/src/Device/Renderer.cpp b/src/Device/Renderer.cpp
index 365cef3..4c3919b 100644
--- a/src/Device/Renderer.cpp
+++ b/src/Device/Renderer.cpp
@@ -196,7 +196,7 @@
 	}
 	draw->id = id;
 
-	const vk::GraphicsState &pipelineState = pipeline->getState(dynamicState);
+	const vk::GraphicsState &pipelineState = pipeline->getCombinedState(dynamicState);
 
 	// 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
@@ -208,13 +208,16 @@
 	// 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 vk::FragmentState *fragmentState = nullptr;
+	const vk::FragmentOutputInterfaceState *fragmentOutputInterfaceState = nullptr;
 
 	const bool hasRasterizerDiscard = preRasterizationState.hasRasterizerDiscard();
 	if(!hasRasterizerDiscard)
 	{
-		pixelProcessor.setBlendConstant(fragmentOutputInterfaceState.getBlendConstants());
+		fragmentState = &pipelineState.getFragmentState();
+		fragmentOutputInterfaceState = &pipelineState.getFragmentOutputInterfaceState();
+
+		pixelProcessor.setBlendConstant(fragmentOutputInterfaceState->getBlendConstants());
 	}
 
 	const vk::Inputs &inputs = pipeline->getInputs();
@@ -229,7 +232,7 @@
 		const vk::Attachments attachments = pipeline->getAttachments();
 
 		vertexState = vertexProcessor.update(pipelineState, vertexShader, inputs);
-		vertexRoutine = vertexProcessor.routine(vertexState, pipelineState.getPipelineLayout(), vertexShader, inputs.getDescriptorSets());
+		vertexRoutine = vertexProcessor.routine(vertexState, preRasterizationState.getPipelineLayout(), vertexShader, inputs.getDescriptorSets());
 
 		if(!hasRasterizerDiscard)
 		{
@@ -237,7 +240,7 @@
 			setupRoutine = setupProcessor.routine(setupState);
 
 			pixelState = pixelProcessor.update(pipelineState, fragmentShader, vertexShader, attachments, hasOcclusionQuery());
-			pixelRoutine = pixelProcessor.routine(pixelState, pipelineState.getPipelineLayout(), fragmentShader, inputs.getDescriptorSets());
+			pixelRoutine = pixelProcessor.routine(pixelState, fragmentState->getPipelineLayout(), fragmentShader, inputs.getDescriptorSets());
 		}
 	}
 
@@ -246,7 +249,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 : fragmentOutputInterfaceState.getSampleCount();
+	int ms = hasRasterizerDiscard ? 1 : fragmentOutputInterfaceState->getSampleCount();
 	ASSERT(ms > 0);
 
 	unsigned int numPrimitivesPerBatch = MaxBatchSize / ms;
@@ -262,7 +265,7 @@
 	draw->indexType = pipeline->getIndexBuffer().getIndexType();
 	draw->lineRasterizationMode = preRasterizationState.getLineRasterizationMode();
 	draw->descriptorSetObjects = inputs.getDescriptorSetObjects();
-	draw->pipelineLayout = pipelineState.getPipelineLayout();
+	draw->preRasterizationPipelineLayout = preRasterizationState.getPipelineLayout();
 	draw->depthClipEnable = preRasterizationState.getDepthClipEnable();
 	draw->depthClipNegativeOneToOne = preRasterizationState.getDepthClipNegativeOneToOne();
 	data->lineWidth = preRasterizationState.getLineWidth();
@@ -286,6 +289,8 @@
 
 	draw->vertexRoutine = vertexRoutine;
 
+	vk::DescriptorSet::PrepareForSampling(draw->descriptorSetObjects, draw->preRasterizationPipelineLayout, device);
+
 	// Viewport
 	{
 		const VkViewport &viewport = preRasterizationState.getViewport();
@@ -372,11 +377,12 @@
 		draw->setupRoutine = setupRoutine;
 		draw->pixelRoutine = pixelRoutine;
 		draw->setupPrimitives = setupPrimitives;
+		draw->fragmentPipelineLayout = fragmentState->getPipelineLayout();
 
 		if(pixelState.stencilActive)
 		{
-			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->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;
@@ -465,6 +471,11 @@
 				data->stencilSliceB = attachments.stencilBuffer->slicePitchBytes(VK_IMAGE_ASPECT_STENCIL_BIT, 0);
 			}
 		}
+
+		if(draw->fragmentPipelineLayout != draw->preRasterizationPipelineLayout)
+		{
+			vk::DescriptorSet::PrepareForSampling(draw->descriptorSetObjects, draw->fragmentPipelineLayout, device);
+		}
 	}
 
 	// Push constants
@@ -474,8 +485,6 @@
 
 	draw->events = events;
 
-	vk::DescriptorSet::PrepareForSampling(draw->descriptorSetObjects, draw->pipelineLayout, device);
-
 	DrawCall::run(device, draw, &drawTickets, clusterQueues);
 }
 
@@ -506,7 +515,7 @@
 
 	if(preRasterizationContainsImageWrite)
 	{
-		vk::DescriptorSet::ContentsChanged(descriptorSetObjects, pipelineLayout, device);
+		vk::DescriptorSet::ContentsChanged(descriptorSetObjects, preRasterizationPipelineLayout, device);
 	}
 
 	if(!data->rasterizerDiscard)
@@ -528,10 +537,12 @@
 			}
 		}
 
-		// If pre-rasterization also contains image writes, don't double-notify the descriptor set.
-		if(fragmentContainsImageWrite && !preRasterizationContainsImageWrite)
+		// If pre-rasterization and fragment use the same pipeline, and pre-rasterization
+		// also contains image writes, don't double-notify the descriptor set.
+		const bool descSetAlreadyNotified = preRasterizationContainsImageWrite && fragmentPipelineLayout == preRasterizationPipelineLayout;
+		if(fragmentContainsImageWrite && !descSetAlreadyNotified)
 		{
-			vk::DescriptorSet::ContentsChanged(descriptorSetObjects, pipelineLayout, device);
+			vk::DescriptorSet::ContentsChanged(descriptorSetObjects, fragmentPipelineLayout, device);
 		}
 	}
 }
diff --git a/src/Device/Renderer.hpp b/src/Device/Renderer.hpp
index 47dd0b1..0e5afb4 100644
--- a/src/Device/Renderer.hpp
+++ b/src/Device/Renderer.hpp
@@ -170,7 +170,8 @@
 	vk::ImageView *depthBuffer;
 	vk::ImageView *stencilBuffer;
 	vk::DescriptorSet::Array descriptorSetObjects;
-	const vk::PipelineLayout *pipelineLayout;
+	const vk::PipelineLayout *preRasterizationPipelineLayout;
+	const vk::PipelineLayout *fragmentPipelineLayout;
 	sw::CountedEvent *events;
 
 	vk::Query *occlusionQuery;
diff --git a/src/Device/VertexProcessor.cpp b/src/Device/VertexProcessor.cpp
index 931b50b..e5562cb 100644
--- a/src/Device/VertexProcessor.cpp
+++ b/src/Device/VertexProcessor.cpp
@@ -73,7 +73,7 @@
 	State state;
 
 	state.shaderID = vertexShader->getIdentifier();
-	state.pipelineLayoutIdentifier = pipelineState.getPipelineLayout()->identifier;
+	state.pipelineLayoutIdentifier = preRasterizationState.getPipelineLayout()->identifier;
 	state.robustBufferAccess = vertexShader->getRobustBufferAccess();
 	state.isPoint = vertexInputInterfaceState.getTopology() == VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
 	state.depthClipEnable = preRasterizationState.getDepthClipEnable();
diff --git a/src/Vulkan/VkPhysicalDevice.cpp b/src/Vulkan/VkPhysicalDevice.cpp
index 598d4b8..24c616c 100644
--- a/src/Vulkan/VkPhysicalDevice.cpp
+++ b/src/Vulkan/VkPhysicalDevice.cpp
@@ -383,6 +383,12 @@
 }
 
 template<typename T>
+static void getPhysicalDeviceGraphicsPipelineLibraryFeatures(T *features)
+{
+	features->graphicsPipelineLibrary = VK_TRUE;
+}
+
+template<typename T>
 static void getPhysicalDeviceVulkan12Features(T *features)
 {
 	features->samplerMirrorClampToEdge = VK_TRUE;
@@ -620,6 +626,9 @@
 		case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_CONTROL_FEATURES_EXT:
 			getPhysicalDeviceDepthClipControlFeaturesExt(reinterpret_cast<struct VkPhysicalDeviceDepthClipControlFeaturesEXT *>(curExtension));
 			break;
+		case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GRAPHICS_PIPELINE_LIBRARY_FEATURES_EXT:
+			getPhysicalDeviceGraphicsPipelineLibraryFeatures(reinterpret_cast<struct VkPhysicalDeviceGraphicsPipelineLibraryFeaturesEXT *>(curExtension));
+			break;
 		case VK_STRUCTURE_TYPE_MAX_ENUM:  // TODO(b/176893525): This may not be legal. dEQP tests that this value is ignored.
 			break;
 		default:
@@ -1324,6 +1333,21 @@
 }
 
 template<typename T>
+static void getGraphicsPipelineLibraryProperties(T *properties)
+{
+	// Library linking is currently fast in SwiftShader, because all the pipeline creation cost
+	// is actually paid at draw time.
+	properties->graphicsPipelineLibraryFastLinking = VK_TRUE;
+	// TODO: check this
+	properties->graphicsPipelineLibraryIndependentInterpolationDecoration = VK_FALSE;
+}
+
+void PhysicalDevice::getProperties(VkPhysicalDeviceGraphicsPipelineLibraryPropertiesEXT *properties) const
+{
+	getGraphicsPipelineLibraryProperties(properties);
+}
+
+template<typename T>
 static void getSamplerFilterMinmaxProperties(T *properties)
 {
 	properties->filterMinmaxSingleComponentFormats = VK_FALSE;
@@ -1620,6 +1644,13 @@
 	       CheckFeature(requested, supported, primitiveTopologyPatchListRestart);
 }
 
+bool PhysicalDevice::hasExtendedFeatures(const VkPhysicalDeviceGraphicsPipelineLibraryFeaturesEXT *requested) const
+{
+	auto supported = getSupportedFeatures(requested);
+
+	return CheckFeature(requested, supported, graphicsPipelineLibrary);
+}
+
 bool PhysicalDevice::hasExtendedFeatures(const VkPhysicalDeviceDescriptorIndexingFeatures *requested) const
 {
 	auto supported = getSupportedFeatures(requested);
diff --git a/src/Vulkan/VkPhysicalDevice.hpp b/src/Vulkan/VkPhysicalDevice.hpp
index 7f68803..8739533 100644
--- a/src/Vulkan/VkPhysicalDevice.hpp
+++ b/src/Vulkan/VkPhysicalDevice.hpp
@@ -58,6 +58,7 @@
 	bool hasExtendedFeatures(const VkPhysicalDeviceDescriptorIndexingFeatures *requested) const;
 	bool hasExtendedFeatures(const VkPhysicalDevicePipelineRobustnessFeaturesEXT *requested) const;
 	bool hasExtendedFeatures(const VkPhysicalDeviceProtectedMemoryFeatures *requested) const;
+	bool hasExtendedFeatures(const VkPhysicalDeviceGraphicsPipelineLibraryFeaturesEXT *requested) const;
 
 	const VkPhysicalDeviceProperties &getProperties() const;
 	void getProperties(VkPhysicalDeviceIDProperties *properties) const;
@@ -93,6 +94,7 @@
 	void getProperties(VkPhysicalDeviceTexelBufferAlignmentProperties *properties) const;
 	void getProperties(VkPhysicalDeviceShaderIntegerDotProductProperties *properties) const;
 	void getProperties(VkPhysicalDevicePipelineRobustnessPropertiesEXT *properties) const;
+	void getProperties(VkPhysicalDeviceGraphicsPipelineLibraryPropertiesEXT *properties) const;
 	void getProperties(VkPhysicalDeviceVulkan11Properties *properties) const;
 	void getProperties(VkPhysicalDeviceVulkan12Properties *properties) const;
 	void getProperties(VkPhysicalDeviceVulkan13Properties *properties) const;
diff --git a/src/Vulkan/VkPipeline.cpp b/src/Vulkan/VkPipeline.cpp
index 5e0902e..99c6580 100644
--- a/src/Vulkan/VkPipeline.cpp
+++ b/src/Vulkan/VkPipeline.cpp
@@ -320,21 +320,54 @@
     , device(device)
     , robustBufferAccess(robustBufferAccess)
 {
-	layout->incRefCount();
+	if(layout)
+	{
+		layout->incRefCount();
+	}
 }
 
 void Pipeline::destroy(const VkAllocationCallbacks *pAllocator)
 {
 	destroyPipeline(pAllocator);
 
-	vk::release(static_cast<VkPipelineLayout>(*layout), pAllocator);
+	if(layout)
+	{
+		vk::release(static_cast<VkPipelineLayout>(*layout), pAllocator);
+	}
 }
 
 GraphicsPipeline::GraphicsPipeline(const VkGraphicsPipelineCreateInfo *pCreateInfo, void *mem, Device *device)
     : Pipeline(vk::Cast(pCreateInfo->layout), device, getPipelineRobustBufferAccess(pCreateInfo->pNext, device))
     , state(device, pCreateInfo, layout)
-    , inputs(pCreateInfo->pVertexInputState)
 {
+	// Either the vertex input interface comes from a pipeline library, or the
+	// VkGraphicsPipelineCreateInfo itself.  Same with shaders.
+	const auto *libraryCreateInfo = GetExtendedStruct<VkPipelineLibraryCreateInfoKHR>(pCreateInfo->pNext, VK_STRUCTURE_TYPE_PIPELINE_LIBRARY_CREATE_INFO_KHR);
+	bool vertexInputInterfaceInLibraries = false;
+	if(libraryCreateInfo)
+	{
+		for(uint32_t i = 0; i < libraryCreateInfo->libraryCount; ++i)
+		{
+			const auto *library = static_cast<const vk::GraphicsPipeline *>(vk::Cast(libraryCreateInfo->pLibraries[i]));
+			if(library->state.hasVertexInputInterfaceState())
+			{
+				inputs = library->inputs;
+				vertexInputInterfaceInLibraries = true;
+			}
+			if(library->state.hasPreRasterizationState())
+			{
+				vertexShader = library->vertexShader;
+			}
+			if(library->state.hasFragmentState())
+			{
+				fragmentShader = library->fragmentShader;
+			}
+		}
+	}
+	if(state.hasVertexInputInterfaceState() && !vertexInputInterfaceInLibraries)
+	{
+		inputs.initialize(pCreateInfo->pVertexInputState);
+	}
 }
 
 void GraphicsPipeline::destroyPipeline(const VkAllocationCallbacks *pAllocator)
@@ -348,6 +381,43 @@
 	return 0;
 }
 
+VkGraphicsPipelineLibraryFlagsEXT GraphicsPipeline::GetGraphicsPipelineSubset(const VkGraphicsPipelineCreateInfo *pCreateInfo)
+{
+	const auto *libraryCreateInfo = vk::GetExtendedStruct<VkPipelineLibraryCreateInfoKHR>(pCreateInfo->pNext, VK_STRUCTURE_TYPE_PIPELINE_LIBRARY_CREATE_INFO_KHR);
+	const auto *graphicsLibraryCreateInfo = vk::GetExtendedStruct<VkGraphicsPipelineLibraryCreateInfoEXT>(pCreateInfo->pNext, VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_LIBRARY_CREATE_INFO_EXT);
+
+	if(graphicsLibraryCreateInfo)
+	{
+		return graphicsLibraryCreateInfo->flags;
+	}
+
+	// > If this structure is omitted, and either VkGraphicsPipelineCreateInfo::flags
+	// > includes VK_PIPELINE_CREATE_LIBRARY_BIT_KHR or the
+	// > VkGraphicsPipelineCreateInfo::pNext chain includes a VkPipelineLibraryCreateInfoKHR
+	// > structure with a libraryCount greater than 0, it is as if flags is 0. Otherwise if
+	// > this structure is omitted, it is as if flags includes all possible subsets of the
+	// > graphics pipeline (i.e. a complete graphics pipeline).
+	//
+	// The above basically says that when a pipeline is created:
+	// - If not a library and not created from libraries, it's a complete pipeline (i.e.
+	//   Vulkan 1.0 pipelines)
+	// - If only created from other libraries, no state is taken from
+	//   VkGraphicsPipelineCreateInfo.
+	//
+	// Otherwise the behavior when creating a library from other libraries is that some
+	// state is taken from VkGraphicsPipelineCreateInfo and some from the libraries.
+	const bool isLibrary = (pCreateInfo->flags & VK_PIPELINE_CREATE_LIBRARY_BIT_KHR) != 0;
+	if(isLibrary || (libraryCreateInfo && libraryCreateInfo->libraryCount > 0))
+	{
+		return 0;
+	}
+
+	return VK_GRAPHICS_PIPELINE_LIBRARY_VERTEX_INPUT_INTERFACE_BIT_EXT |
+	       VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT |
+	       VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT |
+	       VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT;
+}
+
 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 vk::VertexInputInterfaceState &vertexInputInterfaceState = state.getVertexInputInterfaceState();
@@ -404,11 +474,21 @@
 VkResult GraphicsPipeline::compileShaders(const VkAllocationCallbacks *pAllocator, const VkGraphicsPipelineCreateInfo *pCreateInfo, PipelineCache *pPipelineCache)
 {
 	PipelineCreationFeedback pipelineCreationFeedback(pCreateInfo);
+	VkGraphicsPipelineLibraryFlagsEXT pipelineSubset = GetGraphicsPipelineSubset(pCreateInfo);
+	const bool expectVertexShader = (pipelineSubset & VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT) != 0;
+	const bool expectFragmentShader = (pipelineSubset & VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT) != 0;
 
 	for(uint32_t stageIndex = 0; stageIndex < pCreateInfo->stageCount; stageIndex++)
 	{
 		const VkPipelineShaderStageCreateInfo &stageInfo = pCreateInfo->pStages[stageIndex];
 
+		// Ignore stages that don't exist in the pipeline library.
+		if((stageInfo.stage == VK_SHADER_STAGE_VERTEX_BIT && !expectVertexShader) ||
+		   (stageInfo.stage == VK_SHADER_STAGE_FRAGMENT_BIT && !expectFragmentShader))
+		{
+			continue;
+		}
+
 		pipelineCreationFeedback.stageCreationBegins(stageIndex);
 
 		if((stageInfo.flags &
@@ -425,6 +505,25 @@
 		const bool optimize = !dbgctx;
 
 		const ShaderModule *module = vk::Cast(stageInfo.module);
+
+		// VK_EXT_graphics_pipeline_library allows VkShaderModuleCreateInfo to be chained to
+		// VkPipelineShaderStageCreateInfo, which is used if stageInfo.module is
+		// VK_NULL_HANDLE.
+		VkShaderModule tempModule = {};
+		if(stageInfo.module == VK_NULL_HANDLE)
+		{
+			const auto *moduleCreateInfo = vk::GetExtendedStruct<VkShaderModuleCreateInfo>(stageInfo.pNext,
+			                                                                               VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO);
+			ASSERT(moduleCreateInfo);
+			VkResult createResult = vk::ShaderModule::Create(nullptr, moduleCreateInfo, &tempModule);
+			if(createResult != VK_SUCCESS)
+			{
+				return createResult;
+			}
+
+			module = vk::Cast(tempModule);
+		}
+
 		const PipelineCache::SpirvBinaryKey key(module->getBinary(), stageInfo.pSpecializationInfo, optimize);
 
 		if((pCreateInfo->flags & VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT_EXT) &&
@@ -463,6 +562,11 @@
 		setShader(stageInfo.stage, shader);
 
 		pipelineCreationFeedback.stageCreationEnds(stageIndex);
+
+		if(tempModule != VK_NULL_HANDLE)
+		{
+			vk::destroy(tempModule, nullptr);
+		}
 	}
 
 	return VK_SUCCESS;
diff --git a/src/Vulkan/VkPipeline.hpp b/src/Vulkan/VkPipeline.hpp
index 0110d7d..c3322cd 100644
--- a/src/Vulkan/VkPipeline.hpp
+++ b/src/Vulkan/VkPipeline.hpp
@@ -92,10 +92,12 @@
 #endif
 
 	static size_t ComputeRequiredAllocationSize(const VkGraphicsPipelineCreateInfo *pCreateInfo);
+	static VkGraphicsPipelineLibraryFlagsEXT GetGraphicsPipelineSubset(const VkGraphicsPipelineCreateInfo *pCreateInfo);
 
 	VkResult compileShaders(const VkAllocationCallbacks *pAllocator, const VkGraphicsPipelineCreateInfo *pCreateInfo, PipelineCache *pipelineCache);
 
-	const GraphicsState getState(const DynamicState &ds) const { return state.combineStates(ds); }
+	GraphicsState getCombinedState(const DynamicState &ds) const { return state.combineStates(ds); }
+	const GraphicsState &getState() const { return state; }
 
 	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.getVertexInputInterfaceState().hasDynamicVertexStride(); }
diff --git a/src/Vulkan/VkPipelineLayout.cpp b/src/Vulkan/VkPipelineLayout.cpp
index e89cb75..f6d1917 100644
--- a/src/Vulkan/VkPipelineLayout.cpp
+++ b/src/Vulkan/VkPipelineLayout.cpp
@@ -33,6 +33,10 @@
 
 	for(uint32_t i = 0; i < pCreateInfo->setLayoutCount; i++)
 	{
+		if(pCreateInfo->pSetLayouts[i] == VK_NULL_HANDLE)
+		{
+			continue;
+		}
 		const vk::DescriptorSetLayout *setLayout = vk::Cast(pCreateInfo->pSetLayouts[i]);
 		uint32_t bindingsArraySize = setLayout->getBindingsArraySize();
 		descriptorSets[i].bindings = bindingStorage;
@@ -80,6 +84,10 @@
 	uint32_t bindingsCount = 0;
 	for(uint32_t i = 0; i < pCreateInfo->setLayoutCount; i++)
 	{
+		if(pCreateInfo->pSetLayouts[i] == VK_NULL_HANDLE)
+		{
+			continue;
+		}
 		bindingsCount += vk::Cast(pCreateInfo->pSetLayouts[i])->getBindingsArraySize();
 	}
 
diff --git a/src/Vulkan/libVulkan.cpp b/src/Vulkan/libVulkan.cpp
index 65103ca..b55891b 100644
--- a/src/Vulkan/libVulkan.cpp
+++ b/src/Vulkan/libVulkan.cpp
@@ -409,6 +409,8 @@
 	{ { VK_KHR_VULKAN_MEMORY_MODEL_EXTENSION_NAME, VK_KHR_VULKAN_MEMORY_MODEL_SPEC_VERSION } },
 	{ { VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME, VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_SPEC_VERSION } },
 	{ { VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_EXTENSION_NAME, VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_SPEC_VERSION } },
+	{ { VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME, VK_KHR_PIPELINE_LIBRARY_SPEC_VERSION } },
+	{ { VK_EXT_GRAPHICS_PIPELINE_LIBRARY_EXTENSION_NAME, VK_EXT_GRAPHICS_PIPELINE_LIBRARY_SPEC_VERSION } },
 	{ { VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME, VK_EXT_DESCRIPTOR_INDEXING_SPEC_VERSION } },
 	{ { VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME, VK_EXT_DEPTH_CLIP_ENABLE_SPEC_VERSION } },
 	{ { VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME, VK_EXT_CUSTOM_BORDER_COLOR_SPEC_VERSION } },
@@ -1084,6 +1086,7 @@
 		case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_MEMORY_MODEL_FEATURES:
 		case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_4_FEATURES:
 		case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_FEATURES_EXT:
+		case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GRAPHICS_PIPELINE_LIBRARY_FEATURES_EXT:
 		case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_CONTROL_FEATURES_EXT:
 		case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_ROBUSTNESS_FEATURES_EXT:
 			break;
@@ -2283,9 +2286,8 @@
 	TRACE("(VkDevice device = %p, const VkPipelineLayoutCreateInfo* pCreateInfo = %p, const VkAllocationCallbacks* pAllocator = %p, VkPipelineLayout* pPipelineLayout = %p)",
 	      device, pCreateInfo, pAllocator, pPipelineLayout);
 
-	if(pCreateInfo->flags != 0)
+	if(pCreateInfo->flags != 0 && pCreateInfo->flags != VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT)
 	{
-		// Vulkan 1.2: "flags is reserved for future use." "flags must be 0"
 		UNSUPPORTED("pCreateInfo->flags 0x%08X", int(pCreateInfo->flags));
 	}
 
@@ -3701,6 +3703,12 @@
 				vk::Cast(physicalDevice)->getProperties(properties);
 			}
 			break;
+		case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GRAPHICS_PIPELINE_LIBRARY_PROPERTIES_EXT:
+			{
+				auto *properties = reinterpret_cast<VkPhysicalDeviceGraphicsPipelineLibraryPropertiesEXT *>(extensionProperties);
+				vk::Cast(physicalDevice)->getProperties(properties);
+			}
+			break;
 		default:
 			// "the [driver] must skip over, without processing (other than reading the sType and pNext members) any structures in the chain with sType values not defined by [supported extenions]"
 			UNSUPPORTED("pProperties->pNext sType = %s", vk::Stringify(extensionProperties->sType).c_str());