diff --git a/ExternalRevisions/SPIRV-Cross_repo_revision b/ExternalRevisions/SPIRV-Cross_repo_revision index ed092981b..2e77e6f04 100644 --- a/ExternalRevisions/SPIRV-Cross_repo_revision +++ b/ExternalRevisions/SPIRV-Cross_repo_revision @@ -1 +1 @@ -61c603f3baa5270e04bcfb6acf83c654e3c57679 +f6ca6178251c3c886d99781c5437df919fc21734 diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h index 88b71b1f5..4b84769b2 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h +++ b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h @@ -293,7 +293,8 @@ class MVKGraphicsPipeline : public MVKPipeline { ~MVKGraphicsPipeline() override; protected: - typedef MVKSmallVector SPIRVShaderOutputs; + typedef MVKSmallVector SPIRVShaderOutputs; + typedef MVKSmallVector SPIRVShaderInputs; id getOrCompilePipeline(MTLRenderPipelineDescriptor* plDesc, id& plState); id getOrCompilePipeline(MTLComputePipelineDescriptor* plDesc, id& plState, const char* compilerType); @@ -302,14 +303,15 @@ class MVKGraphicsPipeline : public MVKPipeline { void initShaderConversionConfig(SPIRVToMSLConversionConfiguration& shaderConfig, const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData); void initReservedVertexAttributeBufferCount(const VkGraphicsPipelineCreateInfo* pCreateInfo); void addVertexInputToShaderConversionConfig(SPIRVToMSLConversionConfiguration& shaderConfig, const VkGraphicsPipelineCreateInfo* pCreateInfo); + void addNextStageInputToShaderConversionConfig(SPIRVToMSLConversionConfiguration& shaderConfig, SPIRVShaderInputs& inputs); void addPrevStageOutputToShaderConversionConfig(SPIRVToMSLConversionConfiguration& shaderConfig, SPIRVShaderOutputs& outputs); MTLRenderPipelineDescriptor* newMTLRenderPipelineDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData); MTLComputePipelineDescriptor* newMTLTessVertexStageDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData, SPIRVToMSLConversionConfiguration& shaderConfig); MTLComputePipelineDescriptor* newMTLTessControlStageDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData, SPIRVToMSLConversionConfiguration& shaderConfig); MTLRenderPipelineDescriptor* newMTLTessRasterStageDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData, SPIRVToMSLConversionConfiguration& shaderConfig); bool addVertexShaderToPipeline(MTLRenderPipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderConfig); - bool addVertexShaderToPipeline(MTLComputePipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderConfig); - bool addTessCtlShaderToPipeline(MTLComputePipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderConfig, SPIRVShaderOutputs& prevOutput); + bool addVertexShaderToPipeline(MTLComputePipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderConfig, SPIRVShaderInputs& nextInputs); + bool addTessCtlShaderToPipeline(MTLComputePipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderConfig, SPIRVShaderOutputs& prevOutput, SPIRVShaderInputs& nextInputs); bool addTessEvalShaderToPipeline(MTLRenderPipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderConfig, SPIRVShaderOutputs& prevOutput); bool addFragmentShaderToPipeline(MTLRenderPipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderConfig, SPIRVShaderOutputs& prevOutput); template diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm index 4f9bc4a9f..2048687e8 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm @@ -669,8 +669,21 @@ SPIRVToMSLConversionConfiguration& shaderConfig) { MTLComputePipelineDescriptor* plDesc = [MTLComputePipelineDescriptor new]; // retained + SPIRVShaderInputs tcInputs; + std::string errorLog; + if (!getShaderInputs(((MVKShaderModule*)_pTessCtlSS->module)->getSPIRV(), spv::ExecutionModelTessellationControl, _pTessCtlSS->pName, tcInputs, errorLog) ) { + setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Failed to get tessellation control inputs: %s", errorLog.c_str())); + return nil; + } + + // Filter out anything but builtins. We couldn't do this before because we needed to make sure + // locations were assigned correctly. + tcInputs.erase(std::remove_if(tcInputs.begin(), tcInputs.end(), [](const SPIRVShaderInterfaceVariable& var) { + return var.builtin != spv::BuiltInPosition && var.builtin != spv::BuiltInPointSize && var.builtin != spv::BuiltInClipDistance && var.builtin != spv::BuiltInCullDistance; + }), tcInputs.end()); + // Add shader stages. - if (!addVertexShaderToPipeline(plDesc, pCreateInfo, shaderConfig)) { return nil; } + if (!addVertexShaderToPipeline(plDesc, pCreateInfo, shaderConfig, tcInputs)) { return nil; } // Vertex input plDesc.stageInputDescriptor = [MTLStageInputOutputDescriptor stageInputOutputDescriptor]; @@ -794,14 +807,25 @@ static MTLVertexFormat mvkAdjustFormatVectorToSize(MTLVertexFormat format, uint3 MTLComputePipelineDescriptor* plDesc = [MTLComputePipelineDescriptor new]; // retained SPIRVShaderOutputs vtxOutputs; + SPIRVShaderInputs teInputs; std::string errorLog; if (!getShaderOutputs(((MVKShaderModule*)_pVertexSS->module)->getSPIRV(), spv::ExecutionModelVertex, _pVertexSS->pName, vtxOutputs, errorLog) ) { setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Failed to get vertex outputs: %s", errorLog.c_str())); return nil; } + if (!getShaderInputs(((MVKShaderModule*)_pTessEvalSS->module)->getSPIRV(), spv::ExecutionModelTessellationEvaluation, _pTessEvalSS->pName, teInputs, errorLog) ) { + setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Failed to get tessellation evaluation inputs: %s", errorLog.c_str())); + return nil; + } + + // Filter out anything but builtins. We couldn't do this before because we needed to make sure + // locations were assigned correctly. + teInputs.erase(std::remove_if(teInputs.begin(), teInputs.end(), [](const SPIRVShaderInterfaceVariable& var) { + return var.builtin != spv::BuiltInPosition && var.builtin != spv::BuiltInPointSize && var.builtin != spv::BuiltInClipDistance && var.builtin != spv::BuiltInCullDistance; + }), teInputs.end()); // Add shader stages. - if (!addTessCtlShaderToPipeline(plDesc, pCreateInfo, shaderConfig, vtxOutputs)) { + if (!addTessCtlShaderToPipeline(plDesc, pCreateInfo, shaderConfig, vtxOutputs, teInputs)) { [plDesc release]; return nil; } @@ -822,11 +846,16 @@ static MTLVertexFormat mvkAdjustFormatVectorToSize(MTLVertexFormat format, uint3 MTLRenderPipelineDescriptor* plDesc = [MTLRenderPipelineDescriptor new]; // retained SPIRVShaderOutputs tcOutputs, teOutputs; + SPIRVShaderInputs teInputs; std::string errorLog; if (!getShaderOutputs(((MVKShaderModule*)_pTessCtlSS->module)->getSPIRV(), spv::ExecutionModelTessellationControl, _pTessCtlSS->pName, tcOutputs, errorLog) ) { setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Failed to get tessellation control outputs: %s", errorLog.c_str())); return nil; } + if (!getShaderInputs(((MVKShaderModule*)_pTessEvalSS->module)->getSPIRV(), spv::ExecutionModelTessellationEvaluation, _pTessEvalSS->pName, teInputs, errorLog) ) { + setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Failed to get tessellation evaluation inputs: %s", errorLog.c_str())); + return nil; + } if (!getShaderOutputs(((MVKShaderModule*)_pTessEvalSS->module)->getSPIRV(), spv::ExecutionModelTessellationEvaluation, _pTessEvalSS->pName, teOutputs, errorLog) ) { setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Failed to get tessellation evaluation outputs: %s", errorLog.c_str())); return nil; @@ -840,13 +869,38 @@ static MTLVertexFormat mvkAdjustFormatVectorToSize(MTLVertexFormat format, uint3 // Tessellation evaluation stage input // This needs to happen before compiling the fragment shader, or we'll lose information on shader inputs. + // First, add extra builtins that are in teInputs but not tcOutputs. They can be read + // even if not written. + teInputs.erase(std::remove_if(teInputs.begin(), teInputs.end(), [&tcOutputs](const SPIRVShaderInterfaceVariable& var) { + return var.builtin != spv::BuiltInPosition && var.builtin != spv::BuiltInPointSize && var.builtin != spv::BuiltInClipDistance && var.builtin != spv::BuiltInCullDistance; + }), teInputs.end()); + std::remove_copy_if(teInputs.begin(), teInputs.end(), std::back_inserter(tcOutputs), [&tcOutputs](const SPIRVShaderInterfaceVariable& input) { + auto iter = std::find_if(tcOutputs.begin(), tcOutputs.end(), [input](const SPIRVShaderInterfaceVariable& oldVar) { + return oldVar.builtin == input.builtin; + }); + if (iter != tcOutputs.end()) { + iter->isUsed = input.isUsed; + } + return iter != tcOutputs.end(); + }); + + auto isBuiltInRead = [&teInputs](spv::BuiltIn builtin) { + for (const auto& input : teInputs) { + if (input.builtin == builtin) { + return input.isUsed; + } + } + return false; + }; + plDesc.vertexDescriptor = [MTLVertexDescriptor vertexDescriptor]; uint32_t offset = 0, patchOffset = 0, outerLoc = -1, innerLoc = -1; bool usedPerVertex = false, usedPerPatch = false; const SPIRVShaderOutput* firstVertex = nullptr, * firstPatch = nullptr; for (const SPIRVShaderOutput& output : tcOutputs) { if (output.builtin == spv::BuiltInPointSize && !reflectData.pointMode) { continue; } - if (!shaderConfig.isShaderInputLocationUsed(output.location)) { + if ((output.builtin != spv::BuiltInMax && !isBuiltInRead(output.builtin)) && + !shaderConfig.isShaderInputLocationUsed(output.location)) { if (output.perPatch && !(output.builtin == spv::BuiltInTessLevelOuter || output.builtin == spv::BuiltInTessLevelInner) ) { if (!firstPatch) { firstPatch = &output; } patchOffset += getShaderOutputSize(output); @@ -1014,7 +1068,8 @@ static MTLVertexFormat mvkAdjustFormatVectorToSize(MTLVertexFormat format, uint3 // Adds a vertex shader compiled as a compute kernel to the pipeline description. bool MVKGraphicsPipeline::addVertexShaderToPipeline(MTLComputePipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, - SPIRVToMSLConversionConfiguration& shaderConfig) { + SPIRVToMSLConversionConfiguration& shaderConfig, + SPIRVShaderInputs& tcInputs) { shaderConfig.options.entryPointStage = spv::ExecutionModelVertex; shaderConfig.options.entryPointName = _pVertexSS->pName; shaderConfig.options.mslOptions.swizzle_buffer_index = _swizzleBufferIndex.stages[kMVKShaderStageVertex]; @@ -1026,6 +1081,7 @@ static MTLVertexFormat mvkAdjustFormatVectorToSize(MTLVertexFormat format, uint3 shaderConfig.options.mslOptions.vertex_for_tessellation = true; shaderConfig.options.mslOptions.disable_rasterization = true; addVertexInputToShaderConversionConfig(shaderConfig, pCreateInfo); + addNextStageInputToShaderConversionConfig(shaderConfig, tcInputs); static const CompilerMSL::Options::IndexType indexTypes[] = { CompilerMSL::Options::IndexType::None, @@ -1078,7 +1134,8 @@ static MTLVertexFormat mvkAdjustFormatVectorToSize(MTLVertexFormat format, uint3 bool MVKGraphicsPipeline::addTessCtlShaderToPipeline(MTLComputePipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderConfig, - SPIRVShaderOutputs& vtxOutputs) { + SPIRVShaderOutputs& vtxOutputs, + SPIRVShaderInputs& teInputs) { shaderConfig.options.entryPointStage = spv::ExecutionModelTessellationControl; shaderConfig.options.entryPointName = _pTessCtlSS->pName; shaderConfig.options.mslOptions.swizzle_buffer_index = _swizzleBufferIndex.stages[kMVKShaderStageTessCtl]; @@ -1093,6 +1150,7 @@ static MTLVertexFormat mvkAdjustFormatVectorToSize(MTLVertexFormat format, uint3 shaderConfig.options.mslOptions.multi_patch_workgroup = true; shaderConfig.options.mslOptions.fixed_subgroup_size = mvkIsAnyFlagEnabled(_pTessCtlSS->flags, VK_PIPELINE_SHADER_STAGE_CREATE_ALLOW_VARYING_SUBGROUP_SIZE_BIT_EXT) ? 0 : _device->_pMetalFeatures->maxSubgroupSize; addPrevStageOutputToShaderConversionConfig(shaderConfig, vtxOutputs); + addNextStageInputToShaderConversionConfig(shaderConfig, teInputs); MVKMTLFunction func = ((MVKShaderModule*)_pTessCtlSS->module)->getMTLFunction(&shaderConfig, _pTessCtlSS->pSpecializationInfo, _pipelineCache); id mtlFunc = func.getMTLFunction(); @@ -1695,7 +1753,7 @@ static MTLVertexFormat mvkAdjustFormatVectorToSize(MTLVertexFormat format, uint3 // Set binding and offset from Vulkan vertex attribute mvk::MSLShaderInput si; - si.shaderInput.location = pVKVA->location; + si.shaderVar.location = pVKVA->location; si.binding = pVKVA->binding; // Metal can't do signedness conversions on vertex buffers (rdar://45922847). If the shader @@ -1705,11 +1763,11 @@ static MTLVertexFormat mvkAdjustFormatVectorToSize(MTLVertexFormat format, uint3 // declared type. Programs that try to invoke undefined behavior are on their own. switch (getPixelFormats()->getFormatType(pVKVA->format) ) { case kMVKFormatColorUInt8: - si.shaderInput.format = MSL_VERTEX_FORMAT_UINT8; + si.shaderVar.format = MSL_VERTEX_FORMAT_UINT8; break; case kMVKFormatColorUInt16: - si.shaderInput.format = MSL_VERTEX_FORMAT_UINT16; + si.shaderVar.format = MSL_VERTEX_FORMAT_UINT16; break; case kMVKFormatDepthStencil: @@ -1719,7 +1777,7 @@ static MTLVertexFormat mvkAdjustFormatVectorToSize(MTLVertexFormat format, uint3 case VK_FORMAT_D16_UNORM_S8_UINT: case VK_FORMAT_D24_UNORM_S8_UINT: case VK_FORMAT_D32_SFLOAT_S8_UINT: - si.shaderInput.format = MSL_VERTEX_FORMAT_UINT8; + si.shaderVar.format = MSL_VERTEX_FORMAT_UINT8; break; default: @@ -1736,6 +1794,49 @@ static MTLVertexFormat mvkAdjustFormatVectorToSize(MTLVertexFormat format, uint3 } } +// Initializes the shader outputs in a shader conversion config from the next stage input. +void MVKGraphicsPipeline::addNextStageInputToShaderConversionConfig(SPIRVToMSLConversionConfiguration& shaderConfig, + SPIRVShaderInputs& shaderInputs) { + // Set the shader conversion configuration output variable information + shaderConfig.shaderOutputs.clear(); + uint32_t soCnt = (uint32_t)shaderInputs.size(); + for (uint32_t soIdx = 0; soIdx < soCnt; soIdx++) { + if (!shaderInputs[soIdx].isUsed) { continue; } + + mvk::MSLShaderInterfaceVariable so; + so.shaderVar.location = shaderInputs[soIdx].location; + so.shaderVar.component = shaderInputs[soIdx].component; + so.shaderVar.builtin = shaderInputs[soIdx].builtin; + so.shaderVar.vecsize = shaderInputs[soIdx].vecWidth; + + switch (getPixelFormats()->getFormatType(mvkFormatFromOutput(shaderInputs[soIdx]) ) ) { + case kMVKFormatColorUInt8: + so.shaderVar.format = MSL_SHADER_INPUT_FORMAT_UINT8; + break; + + case kMVKFormatColorUInt16: + so.shaderVar.format = MSL_SHADER_INPUT_FORMAT_UINT16; + break; + + case kMVKFormatColorHalf: + case kMVKFormatColorInt16: + so.shaderVar.format = MSL_SHADER_INPUT_FORMAT_ANY16; + break; + + case kMVKFormatColorFloat: + case kMVKFormatColorInt32: + case kMVKFormatColorUInt32: + so.shaderVar.format = MSL_SHADER_INPUT_FORMAT_ANY32; + break; + + default: + break; + } + + shaderConfig.shaderOutputs.push_back(so); + } +} + // Initializes the shader inputs in a shader conversion config from the previous stage output. void MVKGraphicsPipeline::addPrevStageOutputToShaderConversionConfig(SPIRVToMSLConversionConfiguration& shaderConfig, SPIRVShaderOutputs& shaderOutputs) { @@ -1746,29 +1847,29 @@ static MTLVertexFormat mvkAdjustFormatVectorToSize(MTLVertexFormat format, uint3 if (!shaderOutputs[siIdx].isUsed) { continue; } mvk::MSLShaderInput si; - si.shaderInput.location = shaderOutputs[siIdx].location; - si.shaderInput.component = shaderOutputs[siIdx].component; - si.shaderInput.builtin = shaderOutputs[siIdx].builtin; - si.shaderInput.vecsize = shaderOutputs[siIdx].vecWidth; + si.shaderVar.location = shaderOutputs[siIdx].location; + si.shaderVar.component = shaderOutputs[siIdx].component; + si.shaderVar.builtin = shaderOutputs[siIdx].builtin; + si.shaderVar.vecsize = shaderOutputs[siIdx].vecWidth; switch (getPixelFormats()->getFormatType(mvkFormatFromOutput(shaderOutputs[siIdx]) ) ) { case kMVKFormatColorUInt8: - si.shaderInput.format = MSL_SHADER_INPUT_FORMAT_UINT8; + si.shaderVar.format = MSL_SHADER_INPUT_FORMAT_UINT8; break; case kMVKFormatColorUInt16: - si.shaderInput.format = MSL_SHADER_INPUT_FORMAT_UINT16; + si.shaderVar.format = MSL_SHADER_INPUT_FORMAT_UINT16; break; case kMVKFormatColorHalf: case kMVKFormatColorInt16: - si.shaderInput.format = MSL_SHADER_INPUT_FORMAT_ANY16; + si.shaderVar.format = MSL_SHADER_INPUT_FORMAT_ANY16; break; case kMVKFormatColorFloat: case kMVKFormatColorInt32: case kMVKFormatColorUInt32: - si.shaderInput.format = MSL_SHADER_INPUT_FORMAT_ANY32; + si.shaderVar.format = MSL_SHADER_INPUT_FORMAT_ANY32; break; default: @@ -2251,7 +2352,7 @@ void serialize(Archive & archive, CompilerMSL::Options& opt) { } template - void serialize(Archive & archive, MSLShaderInput& si) { + void serialize(Archive & archive, MSLShaderInterfaceVariable& si) { archive(si.location, si.component, si.format, @@ -2331,8 +2432,8 @@ void serialize(Archive & archive, SPIRVToMSLConversionOptions& opt) { } template - void serialize(Archive & archive, MSLShaderInput& si) { - archive(si.shaderInput, + void serialize(Archive & archive, MSLShaderInterfaceVariable& si) { + archive(si.shaderVar, si.binding, si.outIsUsedByShader); } @@ -2357,6 +2458,7 @@ void serialize(Archive & archive, DescriptorBinding& db) { void serialize(Archive & archive, SPIRVToMSLConversionConfiguration& ctx) { archive(ctx.options, ctx.shaderInputs, + ctx.shaderOutputs, ctx.resourceBindings, ctx.discreteDescriptorSets); } diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm b/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm index 40b484f59..8170433a1 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm @@ -288,7 +288,7 @@ static uint32_t getWorkgroupDimensionSize(const SPIRVWorkgroupSizeDimension& wgD _device->addActivityPerformance(_device->_performanceStatistics.shaderCompilation.shaderLibraryFromCache, startTime); } else { mvkLib->setEntryPointName(pShaderConfig->options.entryPointName); - pShaderConfig->markAllInputsAndResourcesUsed(); + pShaderConfig->markAllInterfaceVarsAndResourcesUsed(); } return mvkLib ? mvkLib->getMTLFunction(pSpecializationInfo, this) : MVKMTLFunctionNull; diff --git a/MoltenVK/MoltenVK/Utility/MVKSmallVector.h b/MoltenVK/MoltenVK/Utility/MVKSmallVector.h index ca1916979..f2625215b 100755 --- a/MoltenVK/MoltenVK/Utility/MVKSmallVector.h +++ b/MoltenVK/MoltenVK/Utility/MVKSmallVector.h @@ -62,6 +62,7 @@ class MVKSmallVectorImpl Allocator alc; public: + using value_type = Type; class iterator { const MVKSmallVectorImpl *vector; @@ -115,6 +116,7 @@ class MVKSmallVectorImpl bool is_valid() const { return index < vector->alc.size(); } size_t get_position() const { return index; } }; + using reverse_iterator = std::reverse_iterator; private: // this is the growth strategy -> adjust to your needs @@ -293,6 +295,9 @@ class MVKSmallVectorImpl iterator begin() const { return iterator( 0, *this ); } iterator end() const { return iterator( alc.num_elements_used, *this ); } + reverse_iterator rbegin() const { return reverse_iterator( end() ); } + reverse_iterator rend() const { return reverse_iterator( begin() ); } + const MVKArrayRef contents() const { return MVKArrayRef(data(), size()); } MVKArrayRef contents() { return MVKArrayRef(data(), size()); } @@ -521,6 +526,7 @@ class MVKSmallVectorImpl Allocator alc; public: + using value_type = Type*; class iterator { MVKSmallVectorImpl *vector; @@ -572,6 +578,7 @@ class MVKSmallVectorImpl bool is_valid() const { return index < vector->alc.size(); } size_t get_position() const { return index; } }; + using reverse_iterator = std::reverse_iterator; private: // this is the growth strategy -> adjust to your needs @@ -728,6 +735,9 @@ class MVKSmallVectorImpl iterator begin() { return iterator( 0, *this ); } iterator end() { return iterator( alc.num_elements_used, *this ); } + reverse_iterator rbegin() { return reverse_iterator( end() ); } + reverse_iterator rend() { return reverse_iterator( rbegin() ); } + const MVKArrayRef contents() const { return MVKArrayRef(data(), size()); } MVKArrayRef contents() { return MVKArrayRef(data(), size()); } diff --git a/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVReflection.h b/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVReflection.h index 06f78ec84..b9e1e4a16 100644 --- a/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVReflection.h +++ b/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVReflection.h @@ -54,41 +54,42 @@ namespace mvk { }; #pragma mark - -#pragma mark SPIRVShaderOutputData +#pragma mark SPIRVShaderInterfaceVariable /** - * Reflection data on a single output of a shader. + * Reflection data on a single interface variable of a shader. * This contains the information needed to construct a * stage-input descriptor for the next stage of a pipeline. */ - struct SPIRVShaderOutput { - /** The type of the output. */ + struct SPIRVShaderInterfaceVariable { + /** The type of the variable. */ SPIRV_CROSS_NAMESPACE::SPIRType::BaseType baseType; /** The vector size, if a vector. */ uint32_t vecWidth; - /** The location number of the output. */ + /** The location number of the variable. */ uint32_t location; - /** The component index of the output. */ + /** The component index of the variable. */ uint32_t component; /** * If this is the first member of a struct, this will contain the alignment - * of the struct containing this output, otherwise this will be zero. + * of the struct containing this variable, otherwise this will be zero. */ uint32_t firstStructMemberAlignment; /** If this is a builtin, the kind of builtin this is. */ spv::BuiltIn builtin; - /** Whether this is a per-patch or per-vertex output. Only meaningful for tessellation control shaders. */ + /** Whether this is a per-patch or per-vertex variable. Only meaningful for tessellation shaders. */ bool perPatch; - /** Whether this output is actually used (populated) by the shader. */ + /** Whether this variable is actually used (read or written) by the shader. */ bool isUsed; }; + typedef SPIRVShaderInterfaceVariable SPIRVShaderOutput; #pragma mark - @@ -190,13 +191,13 @@ namespace mvk { #endif } - /** Returns the size in bytes of the output. */ - static inline uint32_t getShaderOutputSize(const SPIRVShaderOutput& output) { - if ( !output.isUsed ) { return 0; } // Unused outputs consume no buffer space. + /** Returns the size in bytes of the interface variable. */ + static inline uint32_t getShaderInterfaceVariableSize(const SPIRVShaderInterfaceVariable& var) { + if ( !var.isUsed ) { return 0; } // Unused variables consume no buffer space. - uint32_t vecWidth = output.vecWidth; + uint32_t vecWidth = var.vecWidth; if (vecWidth == 3) { vecWidth = 4; } // Metal 3-vectors consume same as 4-vectors. - switch (output.baseType) { + switch (var.baseType) { case SPIRV_CROSS_NAMESPACE::SPIRType::SByte: case SPIRV_CROSS_NAMESPACE::SPIRType::UByte: return 1 * vecWidth; @@ -211,29 +212,35 @@ namespace mvk { return 4 * vecWidth; } } + static inline uint32_t getShaderOutputSize(const SPIRVShaderOutput& output) { + return getShaderInterfaceVariableSize(output); + } /** - * Returns the alignment of the shader output, which typically matches the size of the output, - * but the first member of a nested output struct may inherit special alignment from the struct. + * Returns the alignment of the shader interface variable, which typically matches the size of the variable, + * but the first member of a nested struct may inherit special alignment from the struct. */ - static inline uint32_t getShaderOutputAlignment(const SPIRVShaderOutput& output) { - if(output.firstStructMemberAlignment && output.isUsed) { - return output.firstStructMemberAlignment; + static inline uint32_t getShaderInterfaceVariableAlignment(const SPIRVShaderInterfaceVariable& var) { + if(var.firstStructMemberAlignment && var.isUsed) { + return var.firstStructMemberAlignment; } else { - return getShaderOutputSize(output); + return getShaderOutputSize(var); } } + static inline uint32_t getShaderOutputAlignment(const SPIRVShaderOutput& output) { + return getShaderInterfaceVariableAlignment(output); + } auto addSat = [](uint32_t a, uint32_t b) { return a == uint32_t(-1) ? a : a + b; }; - template - static inline uint32_t getShaderOutputStructMembers(const SPIRV_CROSS_NAMESPACE::CompilerReflection& reflect, - Vo& outputs, SPIRVShaderOutput* pParentFirstMember, - const SPIRV_CROSS_NAMESPACE::SPIRType* structType, spv::StorageClass storage, - bool patch, uint32_t loc) { + template + static inline uint32_t getShaderInterfaceStructMembers(const SPIRV_CROSS_NAMESPACE::CompilerReflection& reflect, + Vi& vars, SPIRVShaderInterfaceVariable* pParentFirstMember, + const SPIRV_CROSS_NAMESPACE::SPIRType* structType, spv::StorageClass storage, + bool patch, uint32_t loc) { bool isUsed = true; auto biType = spv::BuiltInMax; - SPIRVShaderOutput* pFirstMember = nullptr; + SPIRVShaderInterfaceVariable* pFirstMember = nullptr; size_t mbrCnt = structType->member_types.size(); for (uint32_t mbrIdx = 0; mbrIdx < mbrCnt; mbrIdx++) { // Each member may have a location decoration. If not, each member @@ -252,12 +259,12 @@ namespace mvk { uint32_t elemCnt = (type->array.empty() ? 1 : type->array[0]) * type->columns; for (uint32_t elemIdx = 0; elemIdx < elemCnt; elemIdx++) { if (type->basetype == SPIRV_CROSS_NAMESPACE::SPIRType::Struct) - loc = getShaderOutputStructMembers(reflect, outputs, pFirstMember, type, storage, patch, loc); + loc = getShaderInterfaceStructMembers(reflect, vars, pFirstMember, type, storage, patch, loc); else { // The alignment of a structure is the same as the largest member of the structure. // Consequently, the first flattened member of a structure should align with structure itself. - outputs.push_back({type->basetype, type->vecsize, loc, cmp, 0, biType, patch, isUsed}); - auto& currOutput = outputs.back(); + vars.push_back({type->basetype, type->vecsize, loc, cmp, 0, biType, patch, isUsed}); + auto& currOutput = vars.back(); if ( !pFirstMember ) { pFirstMember = &currOutput; } pFirstMember->firstStructMemberAlignment = std::max(pFirstMember->firstStructMemberAlignment, getShaderOutputSize(currOutput)); loc = addSat(loc, 1); @@ -274,11 +281,18 @@ namespace mvk { return loc; } + template + static inline uint32_t getShaderOutputStructMembers(const SPIRV_CROSS_NAMESPACE::CompilerReflection& reflect, + Vo& outputs, SPIRVShaderOutput* pParentFirstMember, + const SPIRV_CROSS_NAMESPACE::SPIRType* structType, spv::StorageClass storage, + bool patch, uint32_t loc) { + return getShaderInterfaceStructMembers(reflect, outputs, pParentFirstMember, structType, storage, patch, loc); + } - /** Given a shader in SPIR-V format, returns output reflection data. */ - template - static inline bool getShaderOutputs(const Vs& spirv, spv::ExecutionModel model, const std::string& entryName, - Vo& outputs, std::string& errorLog) { + /** Given a shader in SPIR-V format, returns interface reflection data. */ + template + static inline bool getShaderInterfaceVariables(const Vs& spirv, spv::StorageClass storage, spv::ExecutionModel model, + const std::string& entryName, Vi& vars, std::string& errorLog) { #ifndef SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS try { #endif @@ -291,11 +305,10 @@ namespace mvk { reflect.compile(); reflect.update_active_builtins(); - outputs.clear(); + vars.clear(); for (auto varID : reflect.get_active_interface_variables()) { - spv::StorageClass storage = reflect.get_storage_class(varID); - if (storage != spv::StorageClassOutput) { continue; } + if (storage != reflect.get_storage_class(varID)) { continue; } bool isUsed = true; const auto* type = &reflect.get_type(reflect.get_type_from_variable(varID).parent_type); @@ -313,29 +326,33 @@ namespace mvk { if (reflect.has_decoration(varID, spv::DecorationComponent)) { cmp = reflect.get_decoration(varID, spv::DecorationComponent); } - if (model == spv::ExecutionModelTessellationControl && !patch) + // For tessellation shaders, peel away the initial array type. SPIRV-Cross adds the array back automatically. + // Only some builtins will be arrayed here. + if ((model == spv::ExecutionModelTessellationControl || (model == spv::ExecutionModelTessellationEvaluation && storage == spv::StorageClassInput)) && !patch && + (biType == spv::BuiltInMax || biType == spv::BuiltInPosition || biType == spv::BuiltInPointSize || + biType == spv::BuiltInClipDistance || biType == spv::BuiltInCullDistance)) type = &reflect.get_type(type->parent_type); uint32_t elemCnt = (type->array.empty() ? 1 : type->array[0]) * type->columns; for (uint32_t i = 0; i < elemCnt; i++) { if (type->basetype == SPIRV_CROSS_NAMESPACE::SPIRType::Struct) { - SPIRVShaderOutput* pFirstMember = nullptr; - loc = getShaderOutputStructMembers(reflect, outputs, pFirstMember, type, storage, patch, loc); + SPIRVShaderInterfaceVariable* pFirstMember = nullptr; + loc = getShaderInterfaceStructMembers(reflect, vars, pFirstMember, type, storage, patch, loc); } else { - outputs.push_back({type->basetype, type->vecsize, loc, cmp, 0, biType, patch, isUsed}); + vars.push_back({type->basetype, type->vecsize, loc, cmp, 0, biType, patch, isUsed}); loc = addSat(loc, 1); } } } - // Sort outputs by ascending location. - std::stable_sort(outputs.begin(), outputs.end(), [](const SPIRVShaderOutput& a, const SPIRVShaderOutput& b) { + // Sort variables by ascending location. + std::stable_sort(vars.begin(), vars.end(), [](const SPIRVShaderInterfaceVariable& a, const SPIRVShaderInterfaceVariable& b) { return a.location < b.location; }); - // Assign locations to outputs that don't have one. + // Assign locations to variables that don't have one. uint32_t loc = -1; - for (SPIRVShaderOutput& out : outputs) { - if (out.location == uint32_t(-1)) { out.location = loc + 1; } - loc = out.location; + for (SPIRVShaderInterfaceVariable& var : vars) { + if (var.location == uint32_t(-1)) { var.location = loc + 1; } + loc = var.location; } return true; #ifndef SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS @@ -345,6 +362,16 @@ namespace mvk { } #endif } + template + static inline bool getShaderOutputs(const Vs& spirv, spv::ExecutionModel model, const std::string& entryName, + Vo& outputs, std::string& errorLog) { + return getShaderInterfaceVariables(spirv, spv::StorageClassOutput, model, entryName, outputs, errorLog); + } + template + static inline bool getShaderInputs(const Vs& spirv, spv::ExecutionModel model, const std::string& entryName, + Vo& outputs, std::string& errorLog) { + return getShaderInterfaceVariables(spirv, spv::StorageClassInput, model, entryName, outputs, errorLog); + } } #endif diff --git a/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVToMSLConverter.cpp b/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVToMSLConverter.cpp index 41d360147..6853b91c5 100644 --- a/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVToMSLConverter.cpp +++ b/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVToMSLConverter.cpp @@ -94,17 +94,17 @@ MVK_PUBLIC_SYMBOL SPIRVToMSLConversionOptions::SPIRVToMSLConversionOptions() { mslOptions.pad_fragment_output_components = true; } -MVK_PUBLIC_SYMBOL bool mvk::MSLShaderInput::matches(const mvk::MSLShaderInput& other) const { - if (memcmp(&shaderInput, &other.shaderInput, sizeof(shaderInput)) != 0) { return false; } +MVK_PUBLIC_SYMBOL bool mvk::MSLShaderInterfaceVariable::matches(const mvk::MSLShaderInterfaceVariable& other) const { + if (memcmp(&shaderVar, &other.shaderVar, sizeof(shaderVar)) != 0) { return false; } if (binding != other.binding) { return false; } return true; } -MVK_PUBLIC_SYMBOL mvk::MSLShaderInput::MSLShaderInput() { - // Explicitly set shaderInput to defaults over cleared memory to ensure all instances +MVK_PUBLIC_SYMBOL mvk::MSLShaderInterfaceVariable::MSLShaderInterfaceVariable() { + // Explicitly set shaderVar to defaults over cleared memory to ensure all instances // have exactly the same memory layout when using memory comparison in matches(). - memset(&shaderInput, 0, sizeof(shaderInput)); - shaderInput = SPIRV_CROSS_NAMESPACE::MSLShaderInput(); + memset(&shaderVar, 0, sizeof(shaderVar)); + shaderVar = SPIRV_CROSS_NAMESPACE::MSLShaderInterfaceVariable(); } // If requiresConstExprSampler is false, constExprSampler can be ignored @@ -143,7 +143,21 @@ MVK_PUBLIC_SYMBOL bool SPIRVToMSLConversionConfiguration::stageSupportsVertexAtt // Check them all in case inactive VA's duplicate locations used by active VA's. MVK_PUBLIC_SYMBOL bool SPIRVToMSLConversionConfiguration::isShaderInputLocationUsed(uint32_t location) const { for (auto& si : shaderInputs) { - if ((si.shaderInput.location == location) && si.outIsUsedByShader) { return true; } + if ((si.shaderVar.location == location) && si.outIsUsedByShader) { return true; } + } + return false; +} + +MVK_PUBLIC_SYMBOL bool SPIRVToMSLConversionConfiguration::isShaderInputBuiltInUsed(spv::BuiltIn builtin) const { + for (auto& si : shaderInputs) { + if ((si.shaderVar.builtin == builtin) && si.outIsUsedByShader) { return true; } + } + return false; +} + +MVK_PUBLIC_SYMBOL bool SPIRVToMSLConversionConfiguration::isShaderOutputLocationUsed(uint32_t location) const { + for (auto& so : shaderOutputs) { + if ((so.shaderVar.location == location) && so.outIsUsedByShader) { return true; } } return false; } @@ -166,8 +180,9 @@ MVK_PUBLIC_SYMBOL bool SPIRVToMSLConversionConfiguration::isResourceUsed(Executi return false; } -MVK_PUBLIC_SYMBOL void SPIRVToMSLConversionConfiguration::markAllInputsAndResourcesUsed() { +MVK_PUBLIC_SYMBOL void SPIRVToMSLConversionConfiguration::markAllInterfaceVarsAndResourcesUsed() { for (auto& si : shaderInputs) { si.outIsUsedByShader = true; } + for (auto& so : shaderOutputs) { so.outIsUsedByShader = true; } for (auto& rb : resourceBindings) { rb.outIsUsedByShader = true; } } @@ -175,7 +190,7 @@ MVK_PUBLIC_SYMBOL void SPIRVToMSLConversionConfiguration::markAllInputsAndResour // and the resources can be spread across these shader stages. To improve cache hits when using // this function to find a cached shader for a particular shader stage, only consider the resources // that are used in that shader stage. By contrast, discreteDescriptorSet apply across all stages, -// and shaderInputs are populated before each stage, so neither needs to be filtered by stage here. +// and shaderInputs and shaderOutputs are populated before each stage, so neither needs to be filtered by stage here. MVK_PUBLIC_SYMBOL bool SPIRVToMSLConversionConfiguration::matches(const SPIRVToMSLConversionConfiguration& other) const { if ( !options.matches(other.options) ) { return false; } @@ -184,6 +199,10 @@ MVK_PUBLIC_SYMBOL bool SPIRVToMSLConversionConfiguration::matches(const SPIRVToM if (si.outIsUsedByShader && !containsMatching(other.shaderInputs, si)) { return false; } } + for (const auto& so : shaderOutputs) { + if (so.outIsUsedByShader && !containsMatching(other.shaderOutputs, so)) { return false; } + } + for (const auto& rb : resourceBindings) { if (rb.resourceBinding.stage == options.entryPointStage && rb.outIsUsedByShader && @@ -212,6 +231,13 @@ MVK_PUBLIC_SYMBOL void SPIRVToMSLConversionConfiguration::alignWith(const SPIRVT } } + for (auto& so : shaderOutputs) { + so.outIsUsedByShader = false; + for (auto& srcSO : srcContext.shaderOutputs) { + if (so.matches(srcSO)) { so.outIsUsedByShader = srcSO.outIsUsedByShader; } + } + } + for (auto& rb : resourceBindings) { rb.outIsUsedByShader = false; for (auto& srcRB : srcContext.resourceBindings) { @@ -281,9 +307,13 @@ MVK_PUBLIC_SYMBOL bool SPIRVToMSLConverter::convert(SPIRVToMSLConversionConfigur scOpts.vertex.flip_vert_y = shaderConfig.options.shouldFlipVertexY; pMSLCompiler->set_common_options(scOpts); - // Add shader inputs + // Add shader inputs and outputs for (auto& si : shaderConfig.shaderInputs) { - pMSLCompiler->add_msl_shader_input(si.shaderInput); + pMSLCompiler->add_msl_shader_input(si.shaderVar); + } + + for (auto& so : shaderConfig.shaderOutputs) { + pMSLCompiler->add_msl_shader_output(so.shaderVar); } // Add resource bindings and hardcoded constexpr samplers @@ -352,7 +382,18 @@ MVK_PUBLIC_SYMBOL bool SPIRVToMSLConverter::convert(SPIRVToMSLConversionConfigur } for (auto& ctxSI : shaderConfig.shaderInputs) { - ctxSI.outIsUsedByShader = pMSLCompiler->is_msl_shader_input_used(ctxSI.shaderInput.location); + if (ctxSI.shaderVar.builtin != spv::BuiltInMax) { + ctxSI.outIsUsedByShader = pMSLCompiler->has_active_builtin(ctxSI.shaderVar.builtin, spv::StorageClassInput); + } else { + ctxSI.outIsUsedByShader = pMSLCompiler->is_msl_shader_input_used(ctxSI.shaderVar.location); + } + } + for (auto& ctxSO : shaderConfig.shaderOutputs) { + if (ctxSO.shaderVar.builtin != spv::BuiltInMax) { + ctxSO.outIsUsedByShader = pMSLCompiler->has_active_builtin(ctxSO.shaderVar.builtin, spv::StorageClassOutput); + } else { + ctxSO.outIsUsedByShader = pMSLCompiler->is_msl_shader_output_used(ctxSO.shaderVar.location); + } } for (auto& ctxRB : shaderConfig.resourceBindings) { if (ctxRB.resourceBinding.stage == shaderConfig.options.entryPointStage) { diff --git a/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVToMSLConverter.h b/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVToMSLConverter.h index bd0b297ec..5f00e5044 100644 --- a/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVToMSLConverter.h +++ b/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVToMSLConverter.h @@ -61,30 +61,30 @@ namespace mvk { } SPIRVToMSLConversionOptions; /** - * Defines MSL characteristics of a vertex attribute at a particular location. + * Defines MSL characteristics of a shader interface variable at a particular location. * * The outIsUsedByShader flag is set to true during conversion of SPIR-V to MSL if the shader - * makes use of this vertex attribute. This allows a pipeline to be optimized, and for two + * makes use of this interface variable. This allows a pipeline to be optimized, and for two * shader conversion configurations to be compared only against the attributes that are * actually used by the shader. * * THIS STRUCT IS STREAMED OUT AS PART OF THE PIPELINE CACHE. * CHANGES TO THIS STRUCT SHOULD BE CAPTURED IN THE STREAMING LOGIC OF THE PIPELINE CACHE. */ - typedef struct MSLShaderInput { - SPIRV_CROSS_NAMESPACE::MSLShaderInput shaderInput; + typedef struct MSLShaderInterfaceVariable { + SPIRV_CROSS_NAMESPACE::MSLShaderInterfaceVariable shaderVar; uint32_t binding = 0; bool outIsUsedByShader = false; /** - * Returns whether the specified vertex attribute match this one. + * Returns whether the specified interface variable match this one. * It does if all corresponding elements except outIsUsedByShader are equal. */ - bool matches(const MSLShaderInput& other) const; + bool matches(const MSLShaderInterfaceVariable& other) const; - MSLShaderInput(); + MSLShaderInterfaceVariable(); - } MSLShaderInput; + } MSLShaderInterfaceVariable, MSLShaderInput; /** * Matches the binding index of a MSL resource for a binding within a descriptor set. @@ -146,7 +146,8 @@ namespace mvk { */ typedef struct SPIRVToMSLConversionConfiguration { SPIRVToMSLConversionOptions options; - std::vector shaderInputs; + std::vector shaderInputs; + std::vector shaderOutputs; std::vector resourceBindings; std::vector discreteDescriptorSets; std::vector dynamicBufferDescriptors; @@ -157,17 +158,23 @@ namespace mvk { /** Returns whether the shader input variable at the specified location is used by the shader. */ bool isShaderInputLocationUsed(uint32_t location) const; + /** Returns whether the specified built-in shader input variable is used by the shader. */ + bool isShaderInputBuiltInUsed(spv::BuiltIn builtin) const; + /** Returns the number of shader input variables bound to the specified Vulkan buffer binding, and used by the shader. */ uint32_t countShaderInputsAt(uint32_t binding) const; + /** Returns whether the shader output variable at the specified location is used by the shader. */ + bool isShaderOutputLocationUsed(uint32_t location) const; + /** Returns whether the vertex buffer at the specified Vulkan binding is used by the shader. */ bool isVertexBufferUsed(uint32_t binding) const { return countShaderInputsAt(binding) > 0; } /** Returns whether the resource at the specified descriptor set binding is used by the shader. */ bool isResourceUsed(spv::ExecutionModel stage, uint32_t descSet, uint32_t binding) const; - /** Marks all input variables and resources as being used by the shader. */ - void markAllInputsAndResourcesUsed(); + /** Marks all interface variables and resources as being used by the shader. */ + void markAllInterfaceVarsAndResourcesUsed(); /** * Returns whether this configuration matches the other configuration. It does if