Add support extension VK_EXT_pipeline_creation_cache_control.

- Adjust locks and performance timing on shader cache lookups.
diff --git a/Docs/MoltenVK_Runtime_UserGuide.md b/Docs/MoltenVK_Runtime_UserGuide.md
index 805671a..d61496e 100644
--- a/Docs/MoltenVK_Runtime_UserGuide.md
+++ b/Docs/MoltenVK_Runtime_UserGuide.md
@@ -329,6 +329,7 @@
 - `VK_EXT_memory_budget` *(requires Metal 2.0)*
 - `VK_EXT_metal_objects`
 - `VK_EXT_metal_surface`
+- `VK_EXT_pipeline_creation_cache_control`
 - `VK_EXT_post_depth_coverage` *(iOS and macOS, requires family 4 (A11) or better Apple GPU)*
 - `VK_EXT_private_data `
 - `VK_EXT_robustness2`
diff --git a/Docs/Whats_New.md b/Docs/Whats_New.md
index 4c0d117..5c89570 100644
--- a/Docs/Whats_New.md
+++ b/Docs/Whats_New.md
@@ -19,6 +19,7 @@
 Released TBA
 
 - Add support for extensions:
+	- `VK_EXT_pipeline_creation_cache_control`
 	- `VK_EXT_swapchain_maintenance1`
 	- `VK_EXT_surface_maintenance1`
 - Fix crash when `VkCommandBufferInheritanceInfo::renderPass` is `VK_NULL_HANDLE` during dynamic rendering.
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
index 60bb270..efa11fe 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
@@ -390,6 +390,11 @@
 				swapchainMaintenance1Features->swapchainMaintenance1 = true;
 				break;
 			}
+			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_CREATION_CACHE_CONTROL_FEATURES_EXT: {
+				auto* pipelineCreationCacheControlFeatures = (VkPhysicalDevicePipelineCreationCacheControlFeaturesEXT*)next;
+				pipelineCreationCacheControlFeatures->pipelineCreationCacheControl = true;
+				break;
+			}
 			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXEL_BUFFER_ALIGNMENT_FEATURES_EXT: {
 				auto* texelBuffAlignFeatures = (VkPhysicalDeviceTexelBufferAlignmentFeaturesEXT*)next;
 				texelBuffAlignFeatures->texelBufferAlignment = _metalFeatures.texelBuffers && [_mtlDevice respondsToSelector: @selector(minimumLinearTextureAlignmentForPixelFormat:)];
@@ -3726,10 +3731,16 @@
                                     const PipelineInfoType* pCreateInfos,
                                     const VkAllocationCallbacks* pAllocator,
                                     VkPipeline* pPipelines) {
+	bool ignoreFurtherPipelines = false;
     VkResult rslt = VK_SUCCESS;
     MVKPipelineCache* mvkPLC = (MVKPipelineCache*)pipelineCache;
 
     for (uint32_t plIdx = 0; plIdx < count; plIdx++) {
+
+		// Ensure all slots are purposefully set.
+		pPipelines[plIdx] = VK_NULL_HANDLE;
+		if (ignoreFurtherPipelines) { continue; }
+
         const PipelineInfoType* pCreateInfo = &pCreateInfos[plIdx];
 
         // See if this pipeline has a parent. This can come either directly
@@ -3742,18 +3753,19 @@
             parentPL = vkParentPL ? (MVKPipeline*)vkParentPL : VK_NULL_HANDLE;
         }
 
-        // Create the pipeline and if creation was successful, insert the new pipeline
-        // in the return array and add it to the pipeline cache (if the cache was specified).
-        // If creation was unsuccessful, insert NULL into the return array, change the
-        // result code of this function, and destroy the broken pipeline.
+        // Create the pipeline and if creation was successful, insert the new pipeline in the return array.
         MVKPipeline* mvkPL = new PipelineType(this, mvkPLC, parentPL, pCreateInfo);
         VkResult plRslt = mvkPL->getConfigurationResult();
         if (plRslt == VK_SUCCESS) {
             pPipelines[plIdx] = (VkPipeline)mvkPL;
         } else {
-            rslt = plRslt;
-            pPipelines[plIdx] = VK_NULL_HANDLE;
-            mvkPL->destroy();
+			// If creation was unsuccessful, destroy the broken pipeline, change the result
+			// code of this function, and if the VK_PIPELINE_CREATE_EARLY_RETURN_ON_FAILURE_BIT
+			// flag is set, don't build any further pipelines.
+			mvkPL->destroy();
+			if (rslt == VK_SUCCESS) { rslt = plRslt; }
+			ignoreFurtherPipelines = (_enabledPipelineCreationCacheControlFeatures.pipelineCreationCacheControl &&
+									  mvkIsAnyFlagEnabled(pCreateInfo->flags, VK_PIPELINE_CREATE_EARLY_RETURN_ON_FAILURE_BIT));
         }
     }
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDeviceFeatureStructs.def b/MoltenVK/MoltenVK/GPUObjects/MVKDeviceFeatureStructs.def
index c6de213..0508632 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDeviceFeatureStructs.def
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDeviceFeatureStructs.def
@@ -35,39 +35,40 @@
 #pragma push_macro("INTEL")
 #undef INTEL
 
-MVK_DEVICE_FEATURE(16BitStorage,                   16BIT_STORAGE,                      4)
-MVK_DEVICE_FEATURE(8BitStorage,                    8BIT_STORAGE,                       3)
-MVK_DEVICE_FEATURE(BufferDeviceAddress,            BUFFER_DEVICE_ADDRESS,              3)
-MVK_DEVICE_FEATURE(DescriptorIndexing,             DESCRIPTOR_INDEXING,               20)
-MVK_DEVICE_FEATURE(DynamicRendering,               DYNAMIC_RENDERING,                  1)
-MVK_DEVICE_FEATURE(HostQueryReset,                 HOST_QUERY_RESET,                   1)
-MVK_DEVICE_FEATURE(ImagelessFramebuffer,           IMAGELESS_FRAMEBUFFER,              1)
-MVK_DEVICE_FEATURE(ImageRobustness,                IMAGE_ROBUSTNESS,                   1)
-MVK_DEVICE_FEATURE(InlineUniformBlock,             INLINE_UNIFORM_BLOCK,               2)
-MVK_DEVICE_FEATURE(Multiview,                      MULTIVIEW,                          3)
-MVK_DEVICE_FEATURE(PrivateData,                    PRIVATE_DATA,                       1)
-MVK_DEVICE_FEATURE(ProtectedMemory,                PROTECTED_MEMORY,                   1)
-MVK_DEVICE_FEATURE(SamplerYcbcrConversion,         SAMPLER_YCBCR_CONVERSION,           1)
-MVK_DEVICE_FEATURE(ScalarBlockLayout,              SCALAR_BLOCK_LAYOUT,                1)
-MVK_DEVICE_FEATURE(SeparateDepthStencilLayouts,    SEPARATE_DEPTH_STENCIL_LAYOUTS,     1)
-MVK_DEVICE_FEATURE(ShaderDrawParameters,           SHADER_DRAW_PARAMETERS,             1)
-MVK_DEVICE_FEATURE(ShaderAtomicInt64,              SHADER_ATOMIC_INT64,                2)
-MVK_DEVICE_FEATURE(ShaderFloat16Int8,              SHADER_FLOAT16_INT8,                2)
-MVK_DEVICE_FEATURE(ShaderSubgroupExtendedTypes,    SHADER_SUBGROUP_EXTENDED_TYPES,     1)
-MVK_DEVICE_FEATURE(SubgroupSizeControl,            SUBGROUP_SIZE_CONTROL,              2)
-MVK_DEVICE_FEATURE(TextureCompressionASTCHDR,      TEXTURE_COMPRESSION_ASTC_HDR,       1)
-MVK_DEVICE_FEATURE(TimelineSemaphore,              TIMELINE_SEMAPHORE,                 1)
-MVK_DEVICE_FEATURE(UniformBufferStandardLayout,    UNIFORM_BUFFER_STANDARD_LAYOUT,     1)
-MVK_DEVICE_FEATURE(VariablePointer,                VARIABLE_POINTER,                   2)
-MVK_DEVICE_FEATURE(VulkanMemoryModel,              VULKAN_MEMORY_MODEL,                3)
-MVK_DEVICE_FEATURE_EXTN(FragmentShaderBarycentric, FRAGMENT_SHADER_BARYCENTRIC, KHR,   1)
-MVK_DEVICE_FEATURE_EXTN(PortabilitySubset,         PORTABILITY_SUBSET,          KHR,  15)
-MVK_DEVICE_FEATURE_EXTN(FragmentShaderInterlock,   FRAGMENT_SHADER_INTERLOCK,   EXT,   3)
-MVK_DEVICE_FEATURE_EXTN(Robustness2,               ROBUSTNESS_2,                EXT,   3)
-MVK_DEVICE_FEATURE_EXTN(SwapchainMaintenance1,     SWAPCHAIN_MAINTENANCE_1,     EXT,   1)
-MVK_DEVICE_FEATURE_EXTN(TexelBufferAlignment,      TEXEL_BUFFER_ALIGNMENT,      EXT,   1)
-MVK_DEVICE_FEATURE_EXTN(VertexAttributeDivisor,    VERTEX_ATTRIBUTE_DIVISOR,    EXT,   2)
-MVK_DEVICE_FEATURE_EXTN(ShaderIntegerFunctions2,   SHADER_INTEGER_FUNCTIONS_2,  INTEL, 1)
+MVK_DEVICE_FEATURE(16BitStorage,                      16BIT_STORAGE,                          4)
+MVK_DEVICE_FEATURE(8BitStorage,                       8BIT_STORAGE,                           3)
+MVK_DEVICE_FEATURE(BufferDeviceAddress,               BUFFER_DEVICE_ADDRESS,                  3)
+MVK_DEVICE_FEATURE(DescriptorIndexing,                DESCRIPTOR_INDEXING,                   20)
+MVK_DEVICE_FEATURE(DynamicRendering,                  DYNAMIC_RENDERING,                      1)
+MVK_DEVICE_FEATURE(HostQueryReset,                    HOST_QUERY_RESET,                       1)
+MVK_DEVICE_FEATURE(ImagelessFramebuffer,              IMAGELESS_FRAMEBUFFER,                  1)
+MVK_DEVICE_FEATURE(ImageRobustness,                   IMAGE_ROBUSTNESS,                       1)
+MVK_DEVICE_FEATURE(InlineUniformBlock,                INLINE_UNIFORM_BLOCK,                   2)
+MVK_DEVICE_FEATURE(Multiview,                         MULTIVIEW,                              3)
+MVK_DEVICE_FEATURE(PrivateData,                       PRIVATE_DATA,                           1)
+MVK_DEVICE_FEATURE(ProtectedMemory,                   PROTECTED_MEMORY,                       1)
+MVK_DEVICE_FEATURE(SamplerYcbcrConversion,            SAMPLER_YCBCR_CONVERSION,               1)
+MVK_DEVICE_FEATURE(ScalarBlockLayout,                 SCALAR_BLOCK_LAYOUT,                    1)
+MVK_DEVICE_FEATURE(SeparateDepthStencilLayouts,       SEPARATE_DEPTH_STENCIL_LAYOUTS,         1)
+MVK_DEVICE_FEATURE(ShaderDrawParameters,              SHADER_DRAW_PARAMETERS,                 1)
+MVK_DEVICE_FEATURE(ShaderAtomicInt64,                 SHADER_ATOMIC_INT64,                    2)
+MVK_DEVICE_FEATURE(ShaderFloat16Int8,                 SHADER_FLOAT16_INT8,                    2)
+MVK_DEVICE_FEATURE(ShaderSubgroupExtendedTypes,       SHADER_SUBGROUP_EXTENDED_TYPES,         1)
+MVK_DEVICE_FEATURE(SubgroupSizeControl,               SUBGROUP_SIZE_CONTROL,                  2)
+MVK_DEVICE_FEATURE(TextureCompressionASTCHDR,         TEXTURE_COMPRESSION_ASTC_HDR,           1)
+MVK_DEVICE_FEATURE(TimelineSemaphore,                 TIMELINE_SEMAPHORE,                     1)
+MVK_DEVICE_FEATURE(UniformBufferStandardLayout,       UNIFORM_BUFFER_STANDARD_LAYOUT,         1)
+MVK_DEVICE_FEATURE(VariablePointer,                   VARIABLE_POINTER,                       2)
+MVK_DEVICE_FEATURE(VulkanMemoryModel,                 VULKAN_MEMORY_MODEL,                    3)
+MVK_DEVICE_FEATURE_EXTN(FragmentShaderBarycentric,    FRAGMENT_SHADER_BARYCENTRIC,     KHR,   1)
+MVK_DEVICE_FEATURE_EXTN(PortabilitySubset,            PORTABILITY_SUBSET,              KHR,  15)
+MVK_DEVICE_FEATURE_EXTN(FragmentShaderInterlock,      FRAGMENT_SHADER_INTERLOCK,       EXT,   3)
+MVK_DEVICE_FEATURE_EXTN(Robustness2,                  ROBUSTNESS_2,                    EXT,   3)
+MVK_DEVICE_FEATURE_EXTN(PipelineCreationCacheControl, PIPELINE_CREATION_CACHE_CONTROL, EXT,   1)
+MVK_DEVICE_FEATURE_EXTN(SwapchainMaintenance1,        SWAPCHAIN_MAINTENANCE_1,         EXT,   1)
+MVK_DEVICE_FEATURE_EXTN(TexelBufferAlignment,         TEXEL_BUFFER_ALIGNMENT,          EXT,   1)
+MVK_DEVICE_FEATURE_EXTN(VertexAttributeDivisor,       VERTEX_ATTRIBUTE_DIVISOR,        EXT,   2)
+MVK_DEVICE_FEATURE_EXTN(ShaderIntegerFunctions2,      SHADER_INTEGER_FUNCTIONS_2,      INTEL, 1)
 
 #pragma pop_macro("INTEL")
 #undef MVK_DEVICE_FEATURE_EXTN
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
index f08eb11..caffd5c 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
@@ -155,8 +155,18 @@
 	/** Returns the number of descriptor sets in this pipeline layout. */
 	uint32_t getDescriptorSetCount() { return _descriptorSetCount; }
 
+	/** Returns the pipeline cache used by this pipeline. */
+	MVKPipelineCache* getPipelineCache() { return _pipelineCache; }
+
+	/** Returns whether the pipeline creation fail if a pipeline compile is required. */
+	bool shouldFailOnPipelineCompileRequired() {
+		return (_device->_enabledPipelineCreationCacheControlFeatures.pipelineCreationCacheControl &&
+				mvkIsAnyFlagEnabled(_flags, VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT));
+	}
+
 	/** Constructs an instance for the device. layout, and parent (which may be NULL). */
-	MVKPipeline(MVKDevice* device, MVKPipelineCache* pipelineCache, MVKPipelineLayout* layout, MVKPipeline* parent);
+	MVKPipeline(MVKDevice* device, MVKPipelineCache* pipelineCache, MVKPipelineLayout* layout,
+				VkPipelineCreateFlags flags, MVKPipeline* parent);
 
 protected:
 	void propagateDebugName() override {}
@@ -172,6 +182,7 @@
 	MVKShaderImplicitRezBinding _dynamicOffsetBufferIndex;
 	MVKShaderImplicitRezBinding _indirectParamsIndex;
 	MVKShaderImplicitRezBinding _pushConstantsBufferIndex;
+	VkPipelineCreateFlags _flags;
 	uint32_t _descriptorSetCount;
 	bool _stageUsesPushConstants[kMVKShaderStageCount];
 	bool _fullImageViewSwizzle;
@@ -324,6 +335,9 @@
 	bool verifyImplicitBuffer(bool needsBuffer, MVKShaderImplicitRezBinding& index, MVKShaderStage stage, const char* name);
 	uint32_t getTranslatedVertexBinding(uint32_t binding, uint32_t translationOffset, uint32_t maxBinding);
 	uint32_t getImplicitBufferIndex(MVKShaderStage stage, uint32_t bufferIndexOffset);
+	MVKMTLFunction getMTLFunction(SPIRVToMSLConversionConfiguration& shaderConfig,
+								  const VkPipelineShaderStageCreateInfo* pShaderStage,
+								  const char* pStageName);
 
 	const VkPipelineShaderStageCreateInfo* _pVertexSS = nullptr;
 	const VkPipelineShaderStageCreateInfo* _pTessCtlSS = nullptr;
@@ -456,8 +470,14 @@
 	 */
 	VkResult writeData(size_t* pDataSize, void* pData);
 
-	/** Return a shader library from the shader conversion configuration and sourced from the specified shader module. */
-	MVKShaderLibrary* getShaderLibrary(SPIRVToMSLConversionConfiguration* pContext, MVKShaderModule* shaderModule);
+	/**
+	 * Return a shader library for the shader conversion configuration, from the
+	 * pipeline's pipeline cache, or compiled from source in the shader module.
+	 */
+	MVKShaderLibrary* getShaderLibrary(SPIRVToMSLConversionConfiguration* pContext,
+									   MVKShaderModule* shaderModule,
+									   MVKPipeline* pipeline,
+									   uint64_t startTime = 0);
 
 	/** Merges the contents of the specified number of pipeline caches into this cache. */
 	VkResult mergePipelineCaches(uint32_t srcCacheCount, const VkPipelineCache* pSrcCaches);
@@ -474,11 +494,18 @@
 	MVKShaderLibraryCache* getShaderLibraryCache(MVKShaderModuleKey smKey);
 	void readData(const VkPipelineCacheCreateInfo* pCreateInfo);
 	void writeData(std::ostream& outstream, bool isCounting = false);
+	MVKShaderLibrary* getShaderLibraryImpl(SPIRVToMSLConversionConfiguration* pContext,
+										   MVKShaderModule* shaderModule,
+										   MVKPipeline* pipeline,
+										   uint64_t startTime);
+	VkResult writeDataImpl(size_t* pDataSize, void* pData);
+	VkResult mergePipelineCachesImpl(uint32_t srcCacheCount, const VkPipelineCache* pSrcCaches);
 	void markDirty();
 
 	std::unordered_map<MVKShaderModuleKey, MVKShaderLibraryCache*> _shaderCache;
 	size_t _dataSize = 0;
 	std::mutex _shaderCacheLock;
+	bool _isExternallySynchronized = false;
 };
 
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
index 426d118..c254df9 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
@@ -208,9 +208,11 @@
 	}
 }
 
-MVKPipeline::MVKPipeline(MVKDevice* device, MVKPipelineCache* pipelineCache, MVKPipelineLayout* layout, MVKPipeline* parent) :
+MVKPipeline::MVKPipeline(MVKDevice* device, MVKPipelineCache* pipelineCache, MVKPipelineLayout* layout,
+						 VkPipelineCreateFlags flags, MVKPipeline* parent) :
 	MVKVulkanAPIDeviceObject(device),
 	_pipelineCache(pipelineCache),
+	_flags(flags),
 	_descriptorSetCount(layout->getDescriptorSetCount()),
 	_fullImageViewSwizzle(mvkConfig().fullImageViewSwizzle) {
 
@@ -393,7 +395,7 @@
 										 MVKPipelineCache* pipelineCache,
 										 MVKPipeline* parent,
 										 const VkGraphicsPipelineCreateInfo* pCreateInfo) :
-	MVKPipeline(device, pipelineCache, (MVKPipelineLayout*)pCreateInfo->layout, parent) {
+	MVKPipeline(device, pipelineCache, (MVKPipelineLayout*)pCreateInfo->layout, pCreateInfo->flags, parent) {
 
 	// Determine rasterization early, as various other structs are validated and interpreted in this context.
 	const VkPipelineRenderingCreateInfo* pRendInfo = getRenderingCreateInfo(pCreateInfo);
@@ -431,6 +433,14 @@
 		}
 	}
 
+	// Tessellation - must ignore allowed bad pTessellationState pointer if not tess pipeline
+	_outputControlPointCount = reflectData.numControlPoints;
+	mvkSetOrClear(&_tessInfo, (_pTessCtlSS && _pTessEvalSS) ? pCreateInfo->pTessellationState : nullptr);
+
+	// Render pipeline state. Do this as early as possible, to fail fast if pipeline requires a fail on cache-miss.
+	initMTLRenderPipelineState(pCreateInfo, reflectData);
+	if ( !_hasValidMTLPipelineStates ) { return; }
+
 	// Track dynamic state
 	const VkPipelineDynamicStateCreateInfo* pDS = pCreateInfo->pDynamicState;
 	if (pDS) {
@@ -455,10 +465,6 @@
 		}
 	}
 
-	// Tessellation - must ignore allowed bad pTessellationState pointer if not tess pipeline
-	_outputControlPointCount = reflectData.numControlPoints;
-	mvkSetOrClear(&_tessInfo, (_pTessCtlSS && _pTessEvalSS) ? pCreateInfo->pTessellationState : nullptr);
-
 	// Rasterization
 	_mtlCullMode = MTLCullModeNone;
 	_mtlFrontWinding = MTLWindingCounterClockwise;
@@ -481,9 +487,6 @@
 	// Must run after _isRasterizing and _dynamicState are populated
 	initCustomSamplePositions(pCreateInfo);
 
-	// Render pipeline state
-	initMTLRenderPipelineState(pCreateInfo, reflectData);
-
 	// Depth stencil content - clearing will disable depth and stencil testing
 	// Must ignore allowed bad pDepthStencilState pointer if rasterization disabled or no depth attachment
 	mvkSetOrClear(&_depthStencilInfo, _isRasterizingDepthStencil ? pCreateInfo->pDepthStencilState : nullptr);
@@ -605,8 +608,10 @@
 			} else {
 				getOrCompilePipeline(plDesc, _mtlPipelineState);
 			}
+			[plDesc release];																				// temp release
+		} else {
+			_hasValidMTLPipelineStates = false;
 		}
-		[plDesc release];																				// temp release
 	} else {
 		// In this case, we need to create three render pipelines. But, the way Metal handles
 		// index buffers for compute stage-in means we have to defer creation of stage 1 until
@@ -621,6 +626,8 @@
 			if (getOrCompilePipeline(tcPLDesc, _mtlTessControlStageState, "Tessellation control")) {
 				getOrCompilePipeline(rastPLDesc, _mtlPipelineState);
 			}
+		} else {
+			_hasValidMTLPipelineStates = false;
 		}
 		[tcPLDesc release];		// temp release
 		[rastPLDesc release];	// temp release
@@ -910,13 +917,10 @@
 	shaderConfig.options.mslOptions.disable_rasterization = !_isRasterizing;
     addVertexInputToShaderConversionConfig(shaderConfig, pCreateInfo);
 
-	MVKMTLFunction func = ((MVKShaderModule*)_pVertexSS->module)->getMTLFunction(&shaderConfig, _pVertexSS->pSpecializationInfo, _pipelineCache);
+	MVKMTLFunction func = getMTLFunction(shaderConfig, _pVertexSS, "Vertex");
 	id<MTLFunction> mtlFunc = func.getMTLFunction();
-	if ( !mtlFunc ) {
-		setConfigurationResult(reportError(VK_ERROR_INVALID_SHADER_NV, "Vertex shader function could not be compiled into pipeline. See previous logged error."));
-		return false;
-	}
 	plDesc.vertexFunction = mtlFunc;
+	if ( !mtlFunc ) { return false; }
 
 	auto& funcRslts = func.shaderConversionResults;
 	plDesc.rasterizationEnabled = !funcRslts.isRasterizationDisabled;
@@ -975,22 +979,19 @@
     addVertexInputToShaderConversionConfig(shaderConfig, pCreateInfo);
 	addNextStageInputToShaderConversionConfig(shaderConfig, tcInputs);
 
+	// We need to compile this function three times, with no indexing, 16-bit indices, and 32-bit indices.
 	static const CompilerMSL::Options::IndexType indexTypes[] = {
 		CompilerMSL::Options::IndexType::None,
 		CompilerMSL::Options::IndexType::UInt16,
 		CompilerMSL::Options::IndexType::UInt32,
 	};
-	// We need to compile this function three times, with no indexing, 16-bit indices, and 32-bit indices.
 	MVKMTLFunction func;
 	for (uint32_t i = 0; i < sizeof(indexTypes)/sizeof(indexTypes[0]); i++) {
 		shaderConfig.options.mslOptions.vertex_index_type = indexTypes[i];
-		func = ((MVKShaderModule*)_pVertexSS->module)->getMTLFunction(&shaderConfig, _pVertexSS->pSpecializationInfo, _pipelineCache);
+		func = getMTLFunction(shaderConfig, _pVertexSS, "Vertex");
 		id<MTLFunction> mtlFunc = func.getMTLFunction();
-		if ( !mtlFunc ) {
-			setConfigurationResult(reportError(VK_ERROR_INVALID_SHADER_NV, "Vertex shader function could not be compiled into pipeline. See previous logged error."));
-			return false;
-		}
 		_mtlTessVertexFunctions[i] = [mtlFunc retain];
+		if ( !mtlFunc ) { return false; }
 
 		auto& funcRslts = func.shaderConversionResults;
 		_needsVertexSwizzleBuffer = funcRslts.needsSwizzleBuffer;
@@ -1044,13 +1045,10 @@
 	addPrevStageOutputToShaderConversionConfig(shaderConfig, vtxOutputs);
 	addNextStageInputToShaderConversionConfig(shaderConfig, teInputs);
 
-	MVKMTLFunction func = ((MVKShaderModule*)_pTessCtlSS->module)->getMTLFunction(&shaderConfig, _pTessCtlSS->pSpecializationInfo, _pipelineCache);
+	MVKMTLFunction func = getMTLFunction(shaderConfig, _pTessCtlSS, "Tessellation control");
 	id<MTLFunction> mtlFunc = func.getMTLFunction();
-	if ( !mtlFunc ) {
-		setConfigurationResult(reportError(VK_ERROR_INVALID_SHADER_NV, "Tessellation control shader function could not be compiled into pipeline. See previous logged error."));
-		return false;
-	}
 	plDesc.computeFunction = mtlFunc;
+	if ( !mtlFunc ) { return false; }
 
 	auto& funcRslts = func.shaderConversionResults;
 	_needsTessCtlSwizzleBuffer = funcRslts.needsSwizzleBuffer;
@@ -1105,14 +1103,10 @@
 	shaderConfig.options.mslOptions.disable_rasterization = !_isRasterizing;
 	addPrevStageOutputToShaderConversionConfig(shaderConfig, tcOutputs);
 
-	MVKMTLFunction func = ((MVKShaderModule*)_pTessEvalSS->module)->getMTLFunction(&shaderConfig, _pTessEvalSS->pSpecializationInfo, _pipelineCache);
+	MVKMTLFunction func = getMTLFunction(shaderConfig, _pTessEvalSS, "Tessellation evaluation");
 	id<MTLFunction> mtlFunc = func.getMTLFunction();
-	if ( !mtlFunc ) {
-		setConfigurationResult(reportError(VK_ERROR_INVALID_SHADER_NV, "Tessellation evaluation shader function could not be compiled into pipeline. See previous logged error."));
-		return false;
-	}
-	// Yeah, you read that right. Tess. eval functions are a kind of vertex function in Metal.
-	plDesc.vertexFunction = mtlFunc;
+	plDesc.vertexFunction = mtlFunc;	// Yeah, you read that right. Tess. eval functions are a kind of vertex function in Metal.
+	if ( !mtlFunc ) { return false; }
 
 	auto& funcRslts = func.shaderConversionResults;
 	plDesc.rasterizationEnabled = !funcRslts.isRasterizationDisabled;
@@ -1166,13 +1160,10 @@
 		}
 		addPrevStageOutputToShaderConversionConfig(shaderConfig, shaderOutputs);
 
-		MVKMTLFunction func = ((MVKShaderModule*)_pFragmentSS->module)->getMTLFunction(&shaderConfig, _pFragmentSS->pSpecializationInfo, _pipelineCache);
+		MVKMTLFunction func = getMTLFunction(shaderConfig, _pFragmentSS, "Fragment");
 		id<MTLFunction> mtlFunc = func.getMTLFunction();
-		if ( !mtlFunc ) {
-			setConfigurationResult(reportError(VK_ERROR_INVALID_SHADER_NV, "Fragment shader function could not be compiled into pipeline. See previous logged error."));
-			return false;
-		}
 		plDesc.fragmentFunction = mtlFunc;
+		if ( !mtlFunc ) { return false; }
 
 		auto& funcRslts = func.shaderConversionResults;
 		_needsFragmentSwizzleBuffer = funcRslts.needsSwizzleBuffer;
@@ -1796,6 +1787,23 @@
 			  (mvkMTLPrimitiveTopologyClassFromVkPrimitiveTopology(pCreateInfo->pInputAssemblyState->topology) == MTLPrimitiveTopologyClassTriangle))));
 }
 
+MVKMTLFunction MVKGraphicsPipeline::getMTLFunction(SPIRVToMSLConversionConfiguration& shaderConfig,
+												   const VkPipelineShaderStageCreateInfo* pShaderStage,
+												   const char* pStageName) {
+	MVKShaderModule* shaderModule = (MVKShaderModule*)pShaderStage->module;
+	MVKMTLFunction func = shaderModule->getMTLFunction(&shaderConfig,
+													   pShaderStage->pSpecializationInfo,
+													   this);
+	if ( !func.getMTLFunction() ) {
+		if (shouldFailOnPipelineCompileRequired()) {
+			setConfigurationResult(VK_PIPELINE_COMPILE_REQUIRED);
+		} else {
+			setConfigurationResult(reportError(VK_ERROR_INVALID_SHADER_NV, "%s shader function could not be compiled into pipeline. See previous logged error.", pStageName));
+		}
+	}
+	return func;
+}
+
 MVKGraphicsPipeline::~MVKGraphicsPipeline() {
 	@synchronized (getMTLDevice()) {
 		[_mtlTessVertexStageDesc release];
@@ -1830,7 +1838,7 @@
 									   MVKPipelineCache* pipelineCache,
 									   MVKPipeline* parent,
 									   const VkComputePipelineCreateInfo* pCreateInfo) :
-	MVKPipeline(device, pipelineCache, (MVKPipelineLayout*)pCreateInfo->layout, parent) {
+	MVKPipeline(device, pipelineCache, (MVKPipelineLayout*)pCreateInfo->layout, pCreateInfo->flags, parent) {
 
 	_allowsDispatchBase = mvkAreAllFlagsEnabled(pCreateInfo->flags, VK_PIPELINE_CREATE_DISPATCH_BASE_BIT);
 
@@ -1863,7 +1871,7 @@
 
 		if ( !_mtlPipelineState ) { _hasValidMTLPipelineStates = false; }
 	} else {
-		setConfigurationResult(reportError(VK_ERROR_INVALID_SHADER_NV, "Compute shader function could not be compiled into pipeline. See previous logged error."));
+		_hasValidMTLPipelineStates = false;
 	}
 
 	if (_needsSwizzleBuffer && _swizzleBufferIndex.stages[kMVKShaderStageCompute] > _device->_pMetalFeatures->maxPerStageBufferCount) {
@@ -1931,8 +1939,14 @@
 	shaderConfig.options.mslOptions.dynamic_offsets_buffer_index = _dynamicOffsetBufferIndex.stages[kMVKShaderStageCompute];
     shaderConfig.options.mslOptions.indirect_params_buffer_index = _indirectParamsIndex.stages[kMVKShaderStageCompute];
 
-    MVKMTLFunction func = ((MVKShaderModule*)pSS->module)->getMTLFunction(&shaderConfig, pSS->pSpecializationInfo, _pipelineCache);
-
+    MVKMTLFunction func = ((MVKShaderModule*)pSS->module)->getMTLFunction(&shaderConfig, pSS->pSpecializationInfo, this);
+	if ( !func.getMTLFunction() ) {
+		if (shouldFailOnPipelineCompileRequired()) {
+			setConfigurationResult(VK_PIPELINE_COMPILE_REQUIRED);
+		} else {
+			setConfigurationResult(reportError(VK_ERROR_INVALID_SHADER_NV, "Compute shader function could not be compiled into pipeline. See previous logged error."));
+		}
+	}
 	auto& funcRslts = func.shaderConversionResults;
 	_needsSwizzleBuffer = funcRslts.needsSwizzleBuffer;
     _needsBufferSizeBuffer = funcRslts.needsBufferSizeBuffer;
@@ -1959,12 +1973,25 @@
 #pragma mark MVKPipelineCache
 
 // Return a shader library from the specified shader conversion configuration sourced from the specified shader module.
-MVKShaderLibrary* MVKPipelineCache::getShaderLibrary(SPIRVToMSLConversionConfiguration* pContext, MVKShaderModule* shaderModule) {
-	lock_guard<mutex> lock(_shaderCacheLock);
+MVKShaderLibrary* MVKPipelineCache::getShaderLibrary(SPIRVToMSLConversionConfiguration* pContext,
+													 MVKShaderModule* shaderModule,
+													 MVKPipeline* pipeline,
+													 uint64_t startTime) {
+	if (_isExternallySynchronized) {
+		return getShaderLibraryImpl(pContext, shaderModule, pipeline, startTime);
+	} else {
+		lock_guard<mutex> lock(_shaderCacheLock);
+		return getShaderLibraryImpl(pContext, shaderModule, pipeline, startTime);
+	}
+}
 
+MVKShaderLibrary* MVKPipelineCache::getShaderLibraryImpl(SPIRVToMSLConversionConfiguration* pContext,
+														 MVKShaderModule* shaderModule,
+														 MVKPipeline* pipeline,
+														 uint64_t startTime) {
 	bool wasAdded = false;
 	MVKShaderLibraryCache* slCache = getShaderLibraryCache(shaderModule->getKey());
-	MVKShaderLibrary* shLib = slCache->getShaderLibrary(pContext, shaderModule, &wasAdded);
+	MVKShaderLibrary* shLib = slCache->getShaderLibrary(pContext, shaderModule, pipeline, &wasAdded, startTime);
 	if (wasAdded) { markDirty(); }
 	return shLib;
 }
@@ -2013,14 +2040,21 @@
 	int32_t _index = -1;
 };
 
+VkResult MVKPipelineCache::writeData(size_t* pDataSize, void* pData) {
+	if (_isExternallySynchronized) {
+		return writeDataImpl(pDataSize, pData);
+	} else {
+		lock_guard<mutex> lock(_shaderCacheLock);
+		return writeDataImpl(pDataSize, pData);
+	}
+}
+
 // If pData is not null, serializes at most pDataSize bytes of the contents of the cache into that
 // memory location, and returns the number of bytes serialized in pDataSize. If pData is null,
 // returns the number of bytes required to serialize the contents of this pipeline cache.
 // This is the compliment of the readData() function. The two must be kept aligned.
-VkResult MVKPipelineCache::writeData(size_t* pDataSize, void* pData) {
+VkResult MVKPipelineCache::writeDataImpl(size_t* pDataSize, void* pData) {
 #if MVK_USE_CEREAL
-	lock_guard<mutex> lock(_shaderCacheLock);
-
 	try {
 
 		if ( !pDataSize ) { return VK_SUCCESS; }
@@ -2184,6 +2218,15 @@
 }
 
 VkResult MVKPipelineCache::mergePipelineCaches(uint32_t srcCacheCount, const VkPipelineCache* pSrcCaches) {
+	if (_isExternallySynchronized) {
+		return mergePipelineCachesImpl(srcCacheCount, pSrcCaches);
+	} else {
+		lock_guard<mutex> lock(_shaderCacheLock);
+		return mergePipelineCachesImpl(srcCacheCount, pSrcCaches);
+	}
+}
+
+VkResult MVKPipelineCache::mergePipelineCachesImpl(uint32_t srcCacheCount, const VkPipelineCache* pSrcCaches) {
 	for (uint32_t srcIdx = 0; srcIdx < srcCacheCount; srcIdx++) {
 		MVKPipelineCache* srcPLC = (MVKPipelineCache*)pSrcCaches[srcIdx];
 		for (auto& srcPair : srcPLC->_shaderCache) {
@@ -2406,7 +2449,11 @@
 
 #pragma mark Construction
 
-MVKPipelineCache::MVKPipelineCache(MVKDevice* device, const VkPipelineCacheCreateInfo* pCreateInfo) : MVKVulkanAPIDeviceObject(device) {
+MVKPipelineCache::MVKPipelineCache(MVKDevice* device, const VkPipelineCacheCreateInfo* pCreateInfo) :
+	MVKVulkanAPIDeviceObject(device),
+	_isExternallySynchronized(device->_enabledPipelineCreationCacheControlFeatures.pipelineCreationCacheControl &&
+							  mvkIsAnyFlagEnabled(pCreateInfo->flags, VK_PIPELINE_CACHE_CREATE_EXTERNALLY_SYNCHRONIZED_BIT)) {
+
 	readData(pCreateInfo);
 }
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.h b/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.h
index 0296992..a7e3417 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.h
@@ -134,15 +134,17 @@
 	MVKVulkanAPIObject* getVulkanAPIObject() override { return _owner->getVulkanAPIObject(); };
 
 	/**
-	 * Returns a shader library from the shader conversion configuration sourced from the shader module,
-	 * lazily creating the shader library from source code in the shader module, if needed.
+	 * Returns a shader library from the shader conversion configuration sourced from the
+	 * shader module, lazily creating the shader library from source code in the shader
+	 * module, if needed, and if the pipeline is not configured to fail if a pipeline compile
+	 * is required. In that case, the new shader library is not created, and nil is returned.
 	 *
 	 * If pWasAdded is not nil, this function will set it to true if a new shader library was created,
 	 * and to false if an existing shader library was found and returned.
 	 */
 	MVKShaderLibrary* getShaderLibrary(SPIRVToMSLConversionConfiguration* pShaderConfig,
-									   MVKShaderModule* shaderModule,
-									   bool* pWasAdded = nullptr);
+									   MVKShaderModule* shaderModule, MVKPipeline* pipeline,
+									   bool* pWasAdded, uint64_t startTime = 0);
 
 	MVKShaderLibraryCache(MVKVulkanAPIDeviceObject* owner) : _owner(owner) {};
 
@@ -153,7 +155,7 @@
 	friend MVKPipelineCache;
 	friend MVKShaderModule;
 
-	MVKShaderLibrary* findShaderLibrary(SPIRVToMSLConversionConfiguration* pShaderConfig);
+	MVKShaderLibrary* findShaderLibrary(SPIRVToMSLConversionConfiguration* pShaderConfig, uint64_t startTime = 0);
 	MVKShaderLibrary* addShaderLibrary(const SPIRVToMSLConversionConfiguration* pShaderConfig,
 									   const SPIRVToMSLConversionResult& conversionResult);
 	MVKShaderLibrary* addShaderLibrary(const SPIRVToMSLConversionConfiguration* pShaderConfig,
@@ -205,7 +207,7 @@
 	/** Returns the Metal shader function, possibly specialized. */
 	MVKMTLFunction getMTLFunction(SPIRVToMSLConversionConfiguration* pShaderConfig,
 								  const VkSpecializationInfo* pSpecializationInfo,
-								  MVKPipelineCache* pipelineCache);
+								  MVKPipeline* pipeline);
 
 	/** Convert the SPIR-V to MSL, using the specified shader conversion configuration. */
 	bool convert(SPIRVToMSLConversionConfiguration* pShaderConfig,
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm b/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm
index 7d7f6db..3c6da5d 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm
@@ -241,11 +241,11 @@
 #pragma mark MVKShaderLibraryCache
 
 MVKShaderLibrary* MVKShaderLibraryCache::getShaderLibrary(SPIRVToMSLConversionConfiguration* pShaderConfig,
-														  MVKShaderModule* shaderModule,
-														  bool* pWasAdded) {
+														  MVKShaderModule* shaderModule, MVKPipeline* pipeline,
+														  bool* pWasAdded, uint64_t startTime) {
 	bool wasAdded = false;
-	MVKShaderLibrary* shLib = findShaderLibrary(pShaderConfig);
-	if ( !shLib ) {
+	MVKShaderLibrary* shLib = findShaderLibrary(pShaderConfig, startTime);
+	if ( !shLib && !pipeline->shouldFailOnPipelineCompileRequired() ) {
 		SPIRVToMSLConversionResult conversionResult;
 		if (shaderModule->convert(pShaderConfig, conversionResult)) {
 			shLib = addShaderLibrary(pShaderConfig, conversionResult);
@@ -260,10 +260,13 @@
 
 // Finds and returns a shader library matching the shader config, or returns nullptr if it doesn't exist.
 // If a match is found, the shader config is aligned with the shader config of the matching library.
-MVKShaderLibrary* MVKShaderLibraryCache::findShaderLibrary(SPIRVToMSLConversionConfiguration* pShaderConfig) {
+MVKShaderLibrary* MVKShaderLibraryCache::findShaderLibrary(SPIRVToMSLConversionConfiguration* pShaderConfig,
+														   uint64_t startTime) {
 	for (auto& slPair : _shaderLibraries) {
 		if (slPair.first.matches(*pShaderConfig)) {
 			pShaderConfig->alignWith(slPair.first);
+			MVKDevice* mkvDev = _owner->getDevice();
+			mkvDev->addActivityPerformance(mkvDev->_performanceStatistics.shaderCompilation.shaderLibraryFromCache, startTime);
 			return slPair.second;
 		}
 	}
@@ -308,18 +311,17 @@
 
 MVKMTLFunction MVKShaderModule::getMTLFunction(SPIRVToMSLConversionConfiguration* pShaderConfig,
 											   const VkSpecializationInfo* pSpecializationInfo,
-											   MVKPipelineCache* pipelineCache) {
-	lock_guard<mutex> lock(_accessLock);
-	
+											   MVKPipeline* pipeline) {
 	MVKShaderLibrary* mvkLib = _directMSLLibrary;
 	if ( !mvkLib ) {
 		uint64_t startTime = _device->getPerformanceTimestamp();
+		MVKPipelineCache* pipelineCache = pipeline->getPipelineCache();
 		if (pipelineCache) {
-			mvkLib = pipelineCache->getShaderLibrary(pShaderConfig, this);
+			mvkLib = pipelineCache->getShaderLibrary(pShaderConfig, this, pipeline, startTime);
 		} else {
-			mvkLib = _shaderLibraryCache.getShaderLibrary(pShaderConfig, this);
+			lock_guard<mutex> lock(_accessLock);
+			mvkLib = _shaderLibraryCache.getShaderLibrary(pShaderConfig, this, pipeline, nullptr, startTime);
 		}
-		_device->addActivityPerformance(_device->_performanceStatistics.shaderCompilation.shaderLibraryFromCache, startTime);
 	} else {
 		mvkLib->setEntryPointName(pShaderConfig->options.entryPointName);
 		pShaderConfig->markAllInterfaceVarsAndResourcesUsed();
diff --git a/MoltenVK/MoltenVK/Layers/MVKExtensions.def b/MoltenVK/MoltenVK/Layers/MVKExtensions.def
index 598757c..c619f4b 100644
--- a/MoltenVK/MoltenVK/Layers/MVKExtensions.def
+++ b/MoltenVK/MoltenVK/Layers/MVKExtensions.def
@@ -104,6 +104,7 @@
 MVK_EXTENSION(EXT_memory_budget,                   EXT_MEMORY_BUDGET,                    DEVICE,   10.13, 11.0)
 MVK_EXTENSION(EXT_metal_objects,                   EXT_METAL_OBJECTS,                    DEVICE,   10.11,  8.0)
 MVK_EXTENSION(EXT_metal_surface,                   EXT_METAL_SURFACE,                    INSTANCE, 10.11,  8.0)
+MVK_EXTENSION(EXT_pipeline_creation_cache_control, EXT_PIPELINE_CREATION_CACHE_CONTROL,  DEVICE,   10.11,  8.0)
 MVK_EXTENSION(EXT_post_depth_coverage,             EXT_POST_DEPTH_COVERAGE,              DEVICE,   11.0,  11.0)
 MVK_EXTENSION(EXT_private_data,                    EXT_PRIVATE_DATA,                     DEVICE,   10.11,  8.0)
 MVK_EXTENSION(EXT_robustness2,                     EXT_ROBUSTNESS_2,                     DEVICE,   10.11,  8.0)