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)