From cc140ed1ad5fdf31737057470fb6daac1ad07f0d Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Tue, 23 Mar 2021 21:35:54 -0300 Subject: [PATCH] update spirv-cross --- src/libraries/spirv_cross/spirv.h | 108 +- src/libraries/spirv_cross/spirv.hpp | 109 +- src/libraries/spirv_cross/spirv_cfg.cpp | 9 +- src/libraries/spirv_cross/spirv_cfg.hpp | 9 +- src/libraries/spirv_cross/spirv_common.hpp | 131 +- src/libraries/spirv_cross/spirv_cpp.cpp | 11 +- src/libraries/spirv_cross/spirv_cpp.hpp | 9 +- src/libraries/spirv_cross/spirv_cross.cpp | 370 +- src/libraries/spirv_cross/spirv_cross.hpp | 54 +- src/libraries/spirv_cross/spirv_cross_c.cpp | 166 +- src/libraries/spirv_cross/spirv_cross_c.h | 87 +- .../spirv_cross/spirv_cross_containers.hpp | 21 +- .../spirv_cross_error_handling.hpp | 9 +- .../spirv_cross/spirv_cross_parsed_ir.cpp | 264 +- .../spirv_cross/spirv_cross_parsed_ir.hpp | 20 +- .../spirv_cross/spirv_cross_util.cpp | 9 +- .../spirv_cross/spirv_cross_util.hpp | 9 +- src/libraries/spirv_cross/spirv_glsl.cpp | 3087 ++++++++++--- src/libraries/spirv_cross/spirv_glsl.hpp | 212 +- src/libraries/spirv_cross/spirv_hlsl.cpp | 487 ++- src/libraries/spirv_cross/spirv_hlsl.hpp | 24 +- src/libraries/spirv_cross/spirv_msl.cpp | 3819 +++++++++++++---- src/libraries/spirv_cross/spirv_msl.hpp | 232 +- src/libraries/spirv_cross/spirv_parser.cpp | 40 +- src/libraries/spirv_cross/spirv_parser.hpp | 9 +- src/libraries/spirv_cross/spirv_reflect.cpp | 19 +- src/libraries/spirv_cross/spirv_reflect.hpp | 9 +- 27 files changed, 7496 insertions(+), 1837 deletions(-) diff --git a/src/libraries/spirv_cross/spirv.h b/src/libraries/spirv_cross/spirv.h index dd9850dbb..949f1980e 100644 --- a/src/libraries/spirv_cross/spirv.h +++ b/src/libraries/spirv_cross/spirv.h @@ -54,11 +54,11 @@ typedef unsigned int SpvId; #define SPV_VERSION 0x10500 -#define SPV_REVISION 3 +#define SPV_REVISION 4 static const unsigned int SpvMagicNumber = 0x07230203; static const unsigned int SpvVersion = 0x00010500; -static const unsigned int SpvRevision = 3; +static const unsigned int SpvRevision = 4; static const unsigned int SpvOpCodeMask = 0xffff; static const unsigned int SpvWordCountShift = 16; @@ -172,6 +172,10 @@ typedef enum SpvExecutionMode_ { SpvExecutionModeSampleInterlockUnorderedEXT = 5369, SpvExecutionModeShadingRateInterlockOrderedEXT = 5370, SpvExecutionModeShadingRateInterlockUnorderedEXT = 5371, + SpvExecutionModeMaxWorkgroupSizeINTEL = 5893, + SpvExecutionModeMaxWorkDimINTEL = 5894, + SpvExecutionModeNoGlobalOffsetINTEL = 5895, + SpvExecutionModeNumSIMDWorkitemsINTEL = 5896, SpvExecutionModeMax = 0x7fffffff, } SpvExecutionMode; @@ -203,6 +207,7 @@ typedef enum SpvStorageClass_ { SpvStorageClassShaderRecordBufferNV = 5343, SpvStorageClassPhysicalStorageBuffer = 5349, SpvStorageClassPhysicalStorageBufferEXT = 5349, + SpvStorageClassCodeSectionINTEL = 5605, SpvStorageClassMax = 0x7fffffff, } SpvStorageClass; @@ -273,6 +278,8 @@ typedef enum SpvImageFormat_ { SpvImageFormatRg8ui = 37, SpvImageFormatR16ui = 38, SpvImageFormatR8ui = 39, + SpvImageFormatR64ui = 40, + SpvImageFormatR64i = 41, SpvImageFormatMax = 0x7fffffff, } SpvImageFormat; @@ -481,11 +488,24 @@ typedef enum SpvDecoration_ { SpvDecorationRestrictPointerEXT = 5355, SpvDecorationAliasedPointer = 5356, SpvDecorationAliasedPointerEXT = 5356, + SpvDecorationReferencedIndirectlyINTEL = 5602, SpvDecorationCounterBuffer = 5634, SpvDecorationHlslCounterBufferGOOGLE = 5634, SpvDecorationHlslSemanticGOOGLE = 5635, SpvDecorationUserSemantic = 5635, SpvDecorationUserTypeGOOGLE = 5636, + SpvDecorationRegisterINTEL = 5825, + SpvDecorationMemoryINTEL = 5826, + SpvDecorationNumbanksINTEL = 5827, + SpvDecorationBankwidthINTEL = 5828, + SpvDecorationMaxPrivateCopiesINTEL = 5829, + SpvDecorationSinglepumpINTEL = 5830, + SpvDecorationDoublepumpINTEL = 5831, + SpvDecorationMaxReplicatesINTEL = 5832, + SpvDecorationSimpleDualPortINTEL = 5833, + SpvDecorationMergeINTEL = 5834, + SpvDecorationBankBitsINTEL = 5835, + SpvDecorationForcePow2DepthINTEL = 5836, SpvDecorationMax = 0x7fffffff, } SpvDecoration; @@ -544,8 +564,10 @@ typedef enum SpvBuiltIn_ { SpvBuiltInBaseVertex = 4424, SpvBuiltInBaseInstance = 4425, SpvBuiltInDrawIndex = 4426, + SpvBuiltInPrimitiveShadingRateKHR = 4432, SpvBuiltInDeviceIndex = 4438, SpvBuiltInViewIndex = 4440, + SpvBuiltInShadingRateKHR = 4444, SpvBuiltInBaryCoordNoPerspAMD = 4992, SpvBuiltInBaryCoordNoPerspCentroidAMD = 4993, SpvBuiltInBaryCoordNoPerspSampleAMD = 4994, @@ -596,7 +618,6 @@ typedef enum SpvBuiltIn_ { SpvBuiltInObjectToWorldNV = 5330, SpvBuiltInWorldToObjectKHR = 5331, SpvBuiltInWorldToObjectNV = 5331, - SpvBuiltInHitTKHR = 5332, SpvBuiltInHitTNV = 5332, SpvBuiltInHitKindKHR = 5333, SpvBuiltInHitKindNV = 5333, @@ -632,6 +653,13 @@ typedef enum SpvLoopControlShift_ { SpvLoopControlIterationMultipleShift = 6, SpvLoopControlPeelCountShift = 7, SpvLoopControlPartialCountShift = 8, + SpvLoopControlInitiationIntervalINTELShift = 16, + SpvLoopControlMaxConcurrencyINTELShift = 17, + SpvLoopControlDependencyArrayINTELShift = 18, + SpvLoopControlPipelineEnableINTELShift = 19, + SpvLoopControlLoopCoalesceINTELShift = 20, + SpvLoopControlMaxInterleavingINTELShift = 21, + SpvLoopControlSpeculatedIterationsINTELShift = 22, SpvLoopControlMax = 0x7fffffff, } SpvLoopControlShift; @@ -646,6 +674,13 @@ typedef enum SpvLoopControlMask_ { SpvLoopControlIterationMultipleMask = 0x00000040, SpvLoopControlPeelCountMask = 0x00000080, SpvLoopControlPartialCountMask = 0x00000100, + SpvLoopControlInitiationIntervalINTELMask = 0x00010000, + SpvLoopControlMaxConcurrencyINTELMask = 0x00020000, + SpvLoopControlDependencyArrayINTELMask = 0x00040000, + SpvLoopControlPipelineEnableINTELMask = 0x00080000, + SpvLoopControlLoopCoalesceINTELMask = 0x00100000, + SpvLoopControlMaxInterleavingINTELMask = 0x00200000, + SpvLoopControlSpeculatedIterationsINTELMask = 0x00400000, } SpvLoopControlMask; typedef enum SpvFunctionControlShift_ { @@ -842,6 +877,7 @@ typedef enum SpvCapability_ { SpvCapabilityGroupNonUniformQuad = 68, SpvCapabilityShaderLayer = 69, SpvCapabilityShaderViewportIndex = 70, + SpvCapabilityFragmentShadingRateKHR = 4422, SpvCapabilitySubgroupBallotKHR = 4423, SpvCapabilityDrawParameters = 4427, SpvCapabilitySubgroupVoteKHR = 4431, @@ -866,12 +902,15 @@ typedef enum SpvCapability_ { SpvCapabilityRoundingModeRTE = 4467, SpvCapabilityRoundingModeRTZ = 4468, SpvCapabilityRayQueryProvisionalKHR = 4471, - SpvCapabilityRayTraversalPrimitiveCullingProvisionalKHR = 4478, + SpvCapabilityRayQueryKHR = 4472, + SpvCapabilityRayTraversalPrimitiveCullingKHR = 4478, + SpvCapabilityRayTracingKHR = 4479, SpvCapabilityFloat16ImageAMD = 5008, SpvCapabilityImageGatherBiasLodAMD = 5009, SpvCapabilityFragmentMaskAMD = 5010, SpvCapabilityStencilExportEXT = 5013, SpvCapabilityImageReadWriteLodAMD = 5015, + SpvCapabilityInt64ImageEXT = 5016, SpvCapabilityShaderClockKHR = 5055, SpvCapabilitySampleMaskOverrideCoverageNV = 5249, SpvCapabilityGeometryShaderPassthroughNV = 5251, @@ -932,9 +971,20 @@ typedef enum SpvCapability_ { SpvCapabilitySubgroupImageBlockIOINTEL = 5570, SpvCapabilitySubgroupImageMediaBlockIOINTEL = 5579, SpvCapabilityIntegerFunctions2INTEL = 5584, + SpvCapabilityFunctionPointersINTEL = 5603, + SpvCapabilityIndirectReferencesINTEL = 5604, SpvCapabilitySubgroupAvcMotionEstimationINTEL = 5696, SpvCapabilitySubgroupAvcMotionEstimationIntraINTEL = 5697, SpvCapabilitySubgroupAvcMotionEstimationChromaINTEL = 5698, + SpvCapabilityFPGAMemoryAttributesINTEL = 5824, + SpvCapabilityUnstructuredLoopControlsINTEL = 5886, + SpvCapabilityFPGALoopControlsINTEL = 5888, + SpvCapabilityKernelAttributesINTEL = 5892, + SpvCapabilityFPGAKernelAttributesINTEL = 5897, + SpvCapabilityBlockingPipesINTEL = 5945, + SpvCapabilityFPGARegINTEL = 5948, + SpvCapabilityAtomicFloat32AddEXT = 6033, + SpvCapabilityAtomicFloat64AddEXT = 6034, SpvCapabilityMax = 0x7fffffff, } SpvCapability; @@ -985,6 +1035,22 @@ typedef enum SpvRayQueryCandidateIntersectionType_ { SpvRayQueryCandidateIntersectionTypeMax = 0x7fffffff, } SpvRayQueryCandidateIntersectionType; +typedef enum SpvFragmentShadingRateShift_ { + SpvFragmentShadingRateVertical2PixelsShift = 0, + SpvFragmentShadingRateVertical4PixelsShift = 1, + SpvFragmentShadingRateHorizontal2PixelsShift = 2, + SpvFragmentShadingRateHorizontal4PixelsShift = 3, + SpvFragmentShadingRateMax = 0x7fffffff, +} SpvFragmentShadingRateShift; + +typedef enum SpvFragmentShadingRateMask_ { + SpvFragmentShadingRateMaskNone = 0, + SpvFragmentShadingRateVertical2PixelsMask = 0x00000001, + SpvFragmentShadingRateVertical4PixelsMask = 0x00000002, + SpvFragmentShadingRateHorizontal2PixelsMask = 0x00000004, + SpvFragmentShadingRateHorizontal4PixelsMask = 0x00000008, +} SpvFragmentShadingRateMask; + typedef enum SpvOp_ { SpvOpNop = 0, SpvOpUndef = 1, @@ -1330,13 +1396,19 @@ typedef enum SpvOp_ { SpvOpPtrEqual = 401, SpvOpPtrNotEqual = 402, SpvOpPtrDiff = 403, + SpvOpTerminateInvocation = 4416, SpvOpSubgroupBallotKHR = 4421, SpvOpSubgroupFirstInvocationKHR = 4422, SpvOpSubgroupAllKHR = 4428, SpvOpSubgroupAnyKHR = 4429, SpvOpSubgroupAllEqualKHR = 4430, SpvOpSubgroupReadInvocationKHR = 4432, - SpvOpTypeRayQueryProvisionalKHR = 4472, + SpvOpTraceRayKHR = 4445, + SpvOpExecuteCallableKHR = 4446, + SpvOpConvertUToAccelerationStructureKHR = 4447, + SpvOpIgnoreIntersectionKHR = 4448, + SpvOpTerminateRayKHR = 4449, + SpvOpTypeRayQueryKHR = 4472, SpvOpRayQueryInitializeKHR = 4473, SpvOpRayQueryTerminateKHR = 4474, SpvOpRayQueryGenerateIntersectionKHR = 4475, @@ -1359,15 +1431,11 @@ typedef enum SpvOp_ { SpvOpWritePackedPrimitiveIndices4x8NV = 5299, SpvOpReportIntersectionKHR = 5334, SpvOpReportIntersectionNV = 5334, - SpvOpIgnoreIntersectionKHR = 5335, SpvOpIgnoreIntersectionNV = 5335, - SpvOpTerminateRayKHR = 5336, SpvOpTerminateRayNV = 5336, SpvOpTraceNV = 5337, - SpvOpTraceRayKHR = 5337, SpvOpTypeAccelerationStructureKHR = 5341, SpvOpTypeAccelerationStructureNV = 5341, - SpvOpExecuteCallableKHR = 5344, SpvOpExecuteCallableNV = 5344, SpvOpTypeCooperativeMatrixNV = 5358, SpvOpCooperativeMatrixLoadNV = 5359, @@ -1402,6 +1470,8 @@ typedef enum SpvOp_ { SpvOpUSubSatINTEL = 5596, SpvOpIMul32x16INTEL = 5597, SpvOpUMul32x16INTEL = 5598, + SpvOpFunctionPointerINTEL = 5600, + SpvOpFunctionPointerCallINTEL = 5601, SpvOpDecorateString = 5632, SpvOpDecorateStringGOOGLE = 5632, SpvOpMemberDecorateString = 5633, @@ -1524,6 +1594,10 @@ typedef enum SpvOp_ { SpvOpSubgroupAvcSicGetPackedSkcLumaCountThresholdINTEL = 5814, SpvOpSubgroupAvcSicGetPackedSkcLumaSumThresholdINTEL = 5815, SpvOpSubgroupAvcSicGetInterRawSadsINTEL = 5816, + SpvOpLoopControlINTEL = 5887, + SpvOpReadPipeBlockingINTEL = 5946, + SpvOpWritePipeBlockingINTEL = 5947, + SpvOpFPGARegINTEL = 5949, SpvOpRayQueryGetRayTMinKHR = 6016, SpvOpRayQueryGetRayFlagsKHR = 6017, SpvOpRayQueryGetIntersectionTKHR = 6018, @@ -1541,6 +1615,7 @@ typedef enum SpvOp_ { SpvOpRayQueryGetWorldRayOriginKHR = 6030, SpvOpRayQueryGetIntersectionObjectToWorldKHR = 6031, SpvOpRayQueryGetIntersectionWorldToObjectKHR = 6032, + SpvOpAtomicFAddEXT = 6035, SpvOpMax = 0x7fffffff, } SpvOp; @@ -1893,13 +1968,19 @@ inline void SpvHasResultAndType(SpvOp opcode, bool *hasResult, bool *hasResultTy case SpvOpPtrEqual: *hasResult = true; *hasResultType = true; break; case SpvOpPtrNotEqual: *hasResult = true; *hasResultType = true; break; case SpvOpPtrDiff: *hasResult = true; *hasResultType = true; break; + case SpvOpTerminateInvocation: *hasResult = false; *hasResultType = false; break; case SpvOpSubgroupBallotKHR: *hasResult = true; *hasResultType = true; break; case SpvOpSubgroupFirstInvocationKHR: *hasResult = true; *hasResultType = true; break; case SpvOpSubgroupAllKHR: *hasResult = true; *hasResultType = true; break; case SpvOpSubgroupAnyKHR: *hasResult = true; *hasResultType = true; break; case SpvOpSubgroupAllEqualKHR: *hasResult = true; *hasResultType = true; break; case SpvOpSubgroupReadInvocationKHR: *hasResult = true; *hasResultType = true; break; - case SpvOpTypeRayQueryProvisionalKHR: *hasResult = true; *hasResultType = false; break; + case SpvOpTraceRayKHR: *hasResult = false; *hasResultType = false; break; + case SpvOpExecuteCallableKHR: *hasResult = false; *hasResultType = false; break; + case SpvOpConvertUToAccelerationStructureKHR: *hasResult = true; *hasResultType = true; break; + case SpvOpIgnoreIntersectionKHR: *hasResult = false; *hasResultType = false; break; + case SpvOpTerminateRayKHR: *hasResult = false; *hasResultType = false; break; + case SpvOpTypeRayQueryKHR: *hasResult = true; *hasResultType = false; break; case SpvOpRayQueryInitializeKHR: *hasResult = false; *hasResultType = false; break; case SpvOpRayQueryTerminateKHR: *hasResult = false; *hasResultType = false; break; case SpvOpRayQueryGenerateIntersectionKHR: *hasResult = false; *hasResultType = false; break; @@ -1959,6 +2040,8 @@ inline void SpvHasResultAndType(SpvOp opcode, bool *hasResult, bool *hasResultTy case SpvOpUSubSatINTEL: *hasResult = true; *hasResultType = true; break; case SpvOpIMul32x16INTEL: *hasResult = true; *hasResultType = true; break; case SpvOpUMul32x16INTEL: *hasResult = true; *hasResultType = true; break; + case SpvOpFunctionPointerINTEL: *hasResult = true; *hasResultType = true; break; + case SpvOpFunctionPointerCallINTEL: *hasResult = true; *hasResultType = true; break; case SpvOpDecorateString: *hasResult = false; *hasResultType = false; break; case SpvOpMemberDecorateString: *hasResult = false; *hasResultType = false; break; case SpvOpVmeImageINTEL: *hasResult = true; *hasResultType = true; break; @@ -2079,6 +2162,10 @@ inline void SpvHasResultAndType(SpvOp opcode, bool *hasResult, bool *hasResultTy case SpvOpSubgroupAvcSicGetPackedSkcLumaCountThresholdINTEL: *hasResult = true; *hasResultType = true; break; case SpvOpSubgroupAvcSicGetPackedSkcLumaSumThresholdINTEL: *hasResult = true; *hasResultType = true; break; case SpvOpSubgroupAvcSicGetInterRawSadsINTEL: *hasResult = true; *hasResultType = true; break; + case SpvOpLoopControlINTEL: *hasResult = false; *hasResultType = false; break; + case SpvOpReadPipeBlockingINTEL: *hasResult = true; *hasResultType = true; break; + case SpvOpWritePipeBlockingINTEL: *hasResult = true; *hasResultType = true; break; + case SpvOpFPGARegINTEL: *hasResult = true; *hasResultType = true; break; case SpvOpRayQueryGetRayTMinKHR: *hasResult = true; *hasResultType = true; break; case SpvOpRayQueryGetRayFlagsKHR: *hasResult = true; *hasResultType = true; break; case SpvOpRayQueryGetIntersectionTKHR: *hasResult = true; *hasResultType = true; break; @@ -2096,6 +2183,7 @@ inline void SpvHasResultAndType(SpvOp opcode, bool *hasResult, bool *hasResultTy case SpvOpRayQueryGetWorldRayOriginKHR: *hasResult = true; *hasResultType = true; break; case SpvOpRayQueryGetIntersectionObjectToWorldKHR: *hasResult = true; *hasResultType = true; break; case SpvOpRayQueryGetIntersectionWorldToObjectKHR: *hasResult = true; *hasResultType = true; break; + case SpvOpAtomicFAddEXT: *hasResult = true; *hasResultType = true; break; } } #endif /* SPV_ENABLE_UTILITY_CODE */ diff --git a/src/libraries/spirv_cross/spirv.hpp b/src/libraries/spirv_cross/spirv.hpp index dae36cf20..43dd2aaee 100644 --- a/src/libraries/spirv_cross/spirv.hpp +++ b/src/libraries/spirv_cross/spirv.hpp @@ -50,11 +50,11 @@ namespace spv { typedef unsigned int Id; #define SPV_VERSION 0x10500 -#define SPV_REVISION 3 +#define SPV_REVISION 4 static const unsigned int MagicNumber = 0x07230203; static const unsigned int Version = 0x00010500; -static const unsigned int Revision = 3; +static const unsigned int Revision = 4; static const unsigned int OpCodeMask = 0xffff; static const unsigned int WordCountShift = 16; @@ -168,6 +168,10 @@ enum ExecutionMode { ExecutionModeSampleInterlockUnorderedEXT = 5369, ExecutionModeShadingRateInterlockOrderedEXT = 5370, ExecutionModeShadingRateInterlockUnorderedEXT = 5371, + ExecutionModeMaxWorkgroupSizeINTEL = 5893, + ExecutionModeMaxWorkDimINTEL = 5894, + ExecutionModeNoGlobalOffsetINTEL = 5895, + ExecutionModeNumSIMDWorkitemsINTEL = 5896, ExecutionModeMax = 0x7fffffff, }; @@ -199,6 +203,7 @@ enum StorageClass { StorageClassShaderRecordBufferNV = 5343, StorageClassPhysicalStorageBuffer = 5349, StorageClassPhysicalStorageBufferEXT = 5349, + StorageClassCodeSectionINTEL = 5605, StorageClassMax = 0x7fffffff, }; @@ -269,6 +274,8 @@ enum ImageFormat { ImageFormatRg8ui = 37, ImageFormatR16ui = 38, ImageFormatR8ui = 39, + ImageFormatR64ui = 40, + ImageFormatR64i = 41, ImageFormatMax = 0x7fffffff, }; @@ -477,11 +484,24 @@ enum Decoration { DecorationRestrictPointerEXT = 5355, DecorationAliasedPointer = 5356, DecorationAliasedPointerEXT = 5356, + DecorationReferencedIndirectlyINTEL = 5602, DecorationCounterBuffer = 5634, DecorationHlslCounterBufferGOOGLE = 5634, DecorationHlslSemanticGOOGLE = 5635, DecorationUserSemantic = 5635, DecorationUserTypeGOOGLE = 5636, + DecorationRegisterINTEL = 5825, + DecorationMemoryINTEL = 5826, + DecorationNumbanksINTEL = 5827, + DecorationBankwidthINTEL = 5828, + DecorationMaxPrivateCopiesINTEL = 5829, + DecorationSinglepumpINTEL = 5830, + DecorationDoublepumpINTEL = 5831, + DecorationMaxReplicatesINTEL = 5832, + DecorationSimpleDualPortINTEL = 5833, + DecorationMergeINTEL = 5834, + DecorationBankBitsINTEL = 5835, + DecorationForcePow2DepthINTEL = 5836, DecorationMax = 0x7fffffff, }; @@ -540,8 +560,10 @@ enum BuiltIn { BuiltInBaseVertex = 4424, BuiltInBaseInstance = 4425, BuiltInDrawIndex = 4426, + BuiltInPrimitiveShadingRateKHR = 4432, BuiltInDeviceIndex = 4438, BuiltInViewIndex = 4440, + BuiltInShadingRateKHR = 4444, BuiltInBaryCoordNoPerspAMD = 4992, BuiltInBaryCoordNoPerspCentroidAMD = 4993, BuiltInBaryCoordNoPerspSampleAMD = 4994, @@ -592,7 +614,6 @@ enum BuiltIn { BuiltInObjectToWorldNV = 5330, BuiltInWorldToObjectKHR = 5331, BuiltInWorldToObjectNV = 5331, - BuiltInHitTKHR = 5332, BuiltInHitTNV = 5332, BuiltInHitKindKHR = 5333, BuiltInHitKindNV = 5333, @@ -628,6 +649,13 @@ enum LoopControlShift { LoopControlIterationMultipleShift = 6, LoopControlPeelCountShift = 7, LoopControlPartialCountShift = 8, + LoopControlInitiationIntervalINTELShift = 16, + LoopControlMaxConcurrencyINTELShift = 17, + LoopControlDependencyArrayINTELShift = 18, + LoopControlPipelineEnableINTELShift = 19, + LoopControlLoopCoalesceINTELShift = 20, + LoopControlMaxInterleavingINTELShift = 21, + LoopControlSpeculatedIterationsINTELShift = 22, LoopControlMax = 0x7fffffff, }; @@ -642,6 +670,13 @@ enum LoopControlMask { LoopControlIterationMultipleMask = 0x00000040, LoopControlPeelCountMask = 0x00000080, LoopControlPartialCountMask = 0x00000100, + LoopControlInitiationIntervalINTELMask = 0x00010000, + LoopControlMaxConcurrencyINTELMask = 0x00020000, + LoopControlDependencyArrayINTELMask = 0x00040000, + LoopControlPipelineEnableINTELMask = 0x00080000, + LoopControlLoopCoalesceINTELMask = 0x00100000, + LoopControlMaxInterleavingINTELMask = 0x00200000, + LoopControlSpeculatedIterationsINTELMask = 0x00400000, }; enum FunctionControlShift { @@ -838,6 +873,7 @@ enum Capability { CapabilityGroupNonUniformQuad = 68, CapabilityShaderLayer = 69, CapabilityShaderViewportIndex = 70, + CapabilityFragmentShadingRateKHR = 4422, CapabilitySubgroupBallotKHR = 4423, CapabilityDrawParameters = 4427, CapabilitySubgroupVoteKHR = 4431, @@ -862,12 +898,15 @@ enum Capability { CapabilityRoundingModeRTE = 4467, CapabilityRoundingModeRTZ = 4468, CapabilityRayQueryProvisionalKHR = 4471, - CapabilityRayTraversalPrimitiveCullingProvisionalKHR = 4478, + CapabilityRayQueryKHR = 4472, + CapabilityRayTraversalPrimitiveCullingKHR = 4478, + CapabilityRayTracingKHR = 4479, CapabilityFloat16ImageAMD = 5008, CapabilityImageGatherBiasLodAMD = 5009, CapabilityFragmentMaskAMD = 5010, CapabilityStencilExportEXT = 5013, CapabilityImageReadWriteLodAMD = 5015, + CapabilityInt64ImageEXT = 5016, CapabilityShaderClockKHR = 5055, CapabilitySampleMaskOverrideCoverageNV = 5249, CapabilityGeometryShaderPassthroughNV = 5251, @@ -928,9 +967,20 @@ enum Capability { CapabilitySubgroupImageBlockIOINTEL = 5570, CapabilitySubgroupImageMediaBlockIOINTEL = 5579, CapabilityIntegerFunctions2INTEL = 5584, + CapabilityFunctionPointersINTEL = 5603, + CapabilityIndirectReferencesINTEL = 5604, CapabilitySubgroupAvcMotionEstimationINTEL = 5696, CapabilitySubgroupAvcMotionEstimationIntraINTEL = 5697, CapabilitySubgroupAvcMotionEstimationChromaINTEL = 5698, + CapabilityFPGAMemoryAttributesINTEL = 5824, + CapabilityUnstructuredLoopControlsINTEL = 5886, + CapabilityFPGALoopControlsINTEL = 5888, + CapabilityKernelAttributesINTEL = 5892, + CapabilityFPGAKernelAttributesINTEL = 5897, + CapabilityBlockingPipesINTEL = 5945, + CapabilityFPGARegINTEL = 5948, + CapabilityAtomicFloat32AddEXT = 6033, + CapabilityAtomicFloat64AddEXT = 6034, CapabilityMax = 0x7fffffff, }; @@ -981,6 +1031,22 @@ enum RayQueryCandidateIntersectionType { RayQueryCandidateIntersectionTypeMax = 0x7fffffff, }; +enum FragmentShadingRateShift { + FragmentShadingRateVertical2PixelsShift = 0, + FragmentShadingRateVertical4PixelsShift = 1, + FragmentShadingRateHorizontal2PixelsShift = 2, + FragmentShadingRateHorizontal4PixelsShift = 3, + FragmentShadingRateMax = 0x7fffffff, +}; + +enum FragmentShadingRateMask { + FragmentShadingRateMaskNone = 0, + FragmentShadingRateVertical2PixelsMask = 0x00000001, + FragmentShadingRateVertical4PixelsMask = 0x00000002, + FragmentShadingRateHorizontal2PixelsMask = 0x00000004, + FragmentShadingRateHorizontal4PixelsMask = 0x00000008, +}; + enum Op { OpNop = 0, OpUndef = 1, @@ -1326,13 +1392,19 @@ enum Op { OpPtrEqual = 401, OpPtrNotEqual = 402, OpPtrDiff = 403, + OpTerminateInvocation = 4416, OpSubgroupBallotKHR = 4421, OpSubgroupFirstInvocationKHR = 4422, OpSubgroupAllKHR = 4428, OpSubgroupAnyKHR = 4429, OpSubgroupAllEqualKHR = 4430, OpSubgroupReadInvocationKHR = 4432, - OpTypeRayQueryProvisionalKHR = 4472, + OpTraceRayKHR = 4445, + OpExecuteCallableKHR = 4446, + OpConvertUToAccelerationStructureKHR = 4447, + OpIgnoreIntersectionKHR = 4448, + OpTerminateRayKHR = 4449, + OpTypeRayQueryKHR = 4472, OpRayQueryInitializeKHR = 4473, OpRayQueryTerminateKHR = 4474, OpRayQueryGenerateIntersectionKHR = 4475, @@ -1355,15 +1427,11 @@ enum Op { OpWritePackedPrimitiveIndices4x8NV = 5299, OpReportIntersectionKHR = 5334, OpReportIntersectionNV = 5334, - OpIgnoreIntersectionKHR = 5335, OpIgnoreIntersectionNV = 5335, - OpTerminateRayKHR = 5336, OpTerminateRayNV = 5336, OpTraceNV = 5337, - OpTraceRayKHR = 5337, OpTypeAccelerationStructureKHR = 5341, OpTypeAccelerationStructureNV = 5341, - OpExecuteCallableKHR = 5344, OpExecuteCallableNV = 5344, OpTypeCooperativeMatrixNV = 5358, OpCooperativeMatrixLoadNV = 5359, @@ -1398,6 +1466,8 @@ enum Op { OpUSubSatINTEL = 5596, OpIMul32x16INTEL = 5597, OpUMul32x16INTEL = 5598, + OpFunctionPointerINTEL = 5600, + OpFunctionPointerCallINTEL = 5601, OpDecorateString = 5632, OpDecorateStringGOOGLE = 5632, OpMemberDecorateString = 5633, @@ -1520,6 +1590,10 @@ enum Op { OpSubgroupAvcSicGetPackedSkcLumaCountThresholdINTEL = 5814, OpSubgroupAvcSicGetPackedSkcLumaSumThresholdINTEL = 5815, OpSubgroupAvcSicGetInterRawSadsINTEL = 5816, + OpLoopControlINTEL = 5887, + OpReadPipeBlockingINTEL = 5946, + OpWritePipeBlockingINTEL = 5947, + OpFPGARegINTEL = 5949, OpRayQueryGetRayTMinKHR = 6016, OpRayQueryGetRayFlagsKHR = 6017, OpRayQueryGetIntersectionTKHR = 6018, @@ -1537,6 +1611,7 @@ enum Op { OpRayQueryGetWorldRayOriginKHR = 6030, OpRayQueryGetIntersectionObjectToWorldKHR = 6031, OpRayQueryGetIntersectionWorldToObjectKHR = 6032, + OpAtomicFAddEXT = 6035, OpMax = 0x7fffffff, }; @@ -1889,13 +1964,19 @@ inline void HasResultAndType(Op opcode, bool *hasResult, bool *hasResultType) { case OpPtrEqual: *hasResult = true; *hasResultType = true; break; case OpPtrNotEqual: *hasResult = true; *hasResultType = true; break; case OpPtrDiff: *hasResult = true; *hasResultType = true; break; + case OpTerminateInvocation: *hasResult = false; *hasResultType = false; break; case OpSubgroupBallotKHR: *hasResult = true; *hasResultType = true; break; case OpSubgroupFirstInvocationKHR: *hasResult = true; *hasResultType = true; break; case OpSubgroupAllKHR: *hasResult = true; *hasResultType = true; break; case OpSubgroupAnyKHR: *hasResult = true; *hasResultType = true; break; case OpSubgroupAllEqualKHR: *hasResult = true; *hasResultType = true; break; case OpSubgroupReadInvocationKHR: *hasResult = true; *hasResultType = true; break; - case OpTypeRayQueryProvisionalKHR: *hasResult = true; *hasResultType = false; break; + case OpTraceRayKHR: *hasResult = false; *hasResultType = false; break; + case OpExecuteCallableKHR: *hasResult = false; *hasResultType = false; break; + case OpConvertUToAccelerationStructureKHR: *hasResult = true; *hasResultType = true; break; + case OpIgnoreIntersectionKHR: *hasResult = false; *hasResultType = false; break; + case OpTerminateRayKHR: *hasResult = false; *hasResultType = false; break; + case OpTypeRayQueryKHR: *hasResult = true; *hasResultType = false; break; case OpRayQueryInitializeKHR: *hasResult = false; *hasResultType = false; break; case OpRayQueryTerminateKHR: *hasResult = false; *hasResultType = false; break; case OpRayQueryGenerateIntersectionKHR: *hasResult = false; *hasResultType = false; break; @@ -1955,6 +2036,8 @@ inline void HasResultAndType(Op opcode, bool *hasResult, bool *hasResultType) { case OpUSubSatINTEL: *hasResult = true; *hasResultType = true; break; case OpIMul32x16INTEL: *hasResult = true; *hasResultType = true; break; case OpUMul32x16INTEL: *hasResult = true; *hasResultType = true; break; + case OpFunctionPointerINTEL: *hasResult = true; *hasResultType = true; break; + case OpFunctionPointerCallINTEL: *hasResult = true; *hasResultType = true; break; case OpDecorateString: *hasResult = false; *hasResultType = false; break; case OpMemberDecorateString: *hasResult = false; *hasResultType = false; break; case OpVmeImageINTEL: *hasResult = true; *hasResultType = true; break; @@ -2075,6 +2158,10 @@ inline void HasResultAndType(Op opcode, bool *hasResult, bool *hasResultType) { case OpSubgroupAvcSicGetPackedSkcLumaCountThresholdINTEL: *hasResult = true; *hasResultType = true; break; case OpSubgroupAvcSicGetPackedSkcLumaSumThresholdINTEL: *hasResult = true; *hasResultType = true; break; case OpSubgroupAvcSicGetInterRawSadsINTEL: *hasResult = true; *hasResultType = true; break; + case OpLoopControlINTEL: *hasResult = false; *hasResultType = false; break; + case OpReadPipeBlockingINTEL: *hasResult = true; *hasResultType = true; break; + case OpWritePipeBlockingINTEL: *hasResult = true; *hasResultType = true; break; + case OpFPGARegINTEL: *hasResult = true; *hasResultType = true; break; case OpRayQueryGetRayTMinKHR: *hasResult = true; *hasResultType = true; break; case OpRayQueryGetRayFlagsKHR: *hasResult = true; *hasResultType = true; break; case OpRayQueryGetIntersectionTKHR: *hasResult = true; *hasResultType = true; break; @@ -2092,6 +2179,7 @@ inline void HasResultAndType(Op opcode, bool *hasResult, bool *hasResultType) { case OpRayQueryGetWorldRayOriginKHR: *hasResult = true; *hasResultType = true; break; case OpRayQueryGetIntersectionObjectToWorldKHR: *hasResult = true; *hasResultType = true; break; case OpRayQueryGetIntersectionWorldToObjectKHR: *hasResult = true; *hasResultType = true; break; + case OpAtomicFAddEXT: *hasResult = true; *hasResultType = true; break; } } #endif /* SPV_ENABLE_UTILITY_CODE */ @@ -2107,6 +2195,7 @@ inline MemorySemanticsMask operator|(MemorySemanticsMask a, MemorySemanticsMask inline MemoryAccessMask operator|(MemoryAccessMask a, MemoryAccessMask b) { return MemoryAccessMask(unsigned(a) | unsigned(b)); } inline KernelProfilingInfoMask operator|(KernelProfilingInfoMask a, KernelProfilingInfoMask b) { return KernelProfilingInfoMask(unsigned(a) | unsigned(b)); } inline RayFlagsMask operator|(RayFlagsMask a, RayFlagsMask b) { return RayFlagsMask(unsigned(a) | unsigned(b)); } +inline FragmentShadingRateMask operator|(FragmentShadingRateMask a, FragmentShadingRateMask b) { return FragmentShadingRateMask(unsigned(a) | unsigned(b)); } } // end namespace spv diff --git a/src/libraries/spirv_cross/spirv_cfg.cpp b/src/libraries/spirv_cross/spirv_cfg.cpp index 9973fbae1..0a66d60bd 100644 --- a/src/libraries/spirv_cross/spirv_cfg.cpp +++ b/src/libraries/spirv_cross/spirv_cfg.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 Arm Limited + * Copyright 2016-2021 Arm Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #include "spirv_cfg.hpp" #include "spirv_cross.hpp" #include diff --git a/src/libraries/spirv_cross/spirv_cfg.hpp b/src/libraries/spirv_cross/spirv_cfg.hpp index 9f3e62a1b..041526d65 100644 --- a/src/libraries/spirv_cross/spirv_cfg.hpp +++ b/src/libraries/spirv_cross/spirv_cfg.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 Arm Limited + * Copyright 2016-2021 Arm Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #ifndef SPIRV_CROSS_CFG_HPP #define SPIRV_CROSS_CFG_HPP diff --git a/src/libraries/spirv_cross/spirv_common.hpp b/src/libraries/spirv_cross/spirv_common.hpp index af0d0767f..a7a84188f 100644 --- a/src/libraries/spirv_cross/spirv_common.hpp +++ b/src/libraries/spirv_cross/spirv_common.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 Arm Limited + * Copyright 2015-2021 Arm Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #ifndef SPIRV_CROSS_COMMON_HPP #define SPIRV_CROSS_COMMON_HPP @@ -262,6 +269,29 @@ inline std::string convert_to_string(double t, char locale_radix_point) return buf; } +template +struct ValueSaver +{ + explicit ValueSaver(T ¤t_) + : current(current_) + , saved(current_) + { + } + + void release() + { + current = saved; + } + + ~ValueSaver() + { + release(); + } + + T ¤t; + T saved; +}; + #if defined(__clang__) || defined(__GNUC__) #pragma GCC diagnostic pop #elif defined(_MSC_VER) @@ -272,8 +302,20 @@ struct Instruction { uint16_t op = 0; uint16_t count = 0; + // If offset is 0 (not a valid offset into the instruction stream), + // we have an instruction stream which is embedded in the object. uint32_t offset = 0; uint32_t length = 0; + + inline bool is_embedded() const + { + return offset == 0; + } +}; + +struct EmbeddedInstruction : Instruction +{ + SmallVector ops; }; enum Types @@ -334,28 +376,6 @@ public: return TypedID(*this); } - bool operator==(const TypedID &other) const - { - return id == other.id; - } - - bool operator!=(const TypedID &other) const - { - return id != other.id; - } - - template - bool operator==(const TypedID &other) const - { - return id == uint32_t(other); - } - - template - bool operator!=(const TypedID &other) const - { - return id != uint32_t(other); - } - private: uint32_t id = 0; }; @@ -380,26 +400,6 @@ public: return id; } - bool operator==(const TypedID &other) const - { - return id == other.id; - } - - bool operator!=(const TypedID &other) const - { - return id != other.id; - } - - bool operator==(const TypedID &other) const - { - return id == uint32_t(other); - } - - bool operator!=(const TypedID &other) const - { - return id != uint32_t(other); - } - private: uint32_t id = 0; }; @@ -535,6 +535,7 @@ struct SPIRType : IVariant // Keep internal types at the end. ControlPointArray, + Interpolant, Char }; @@ -641,7 +642,7 @@ struct SPIREntryPoint SmallVector interface_variables; Bitset flags; - struct + struct WorkgroupSize { uint32_t x = 0, y = 0, z = 0; uint32_t constant = 0; // Workgroup size can be expressed as a constant/spec-constant instead. @@ -699,6 +700,9 @@ struct SPIRExpression : IVariant // Used by access chain Store and Load since we read multiple expressions in this case. SmallVector implied_read_expressions; + // The expression was emitted at a certain scope. Lets us track when an expression read means multiple reads. + uint32_t emitted_loop_level = 0; + SPIRV_CROSS_DECLARE_CLONE(SPIRExpression) }; @@ -737,7 +741,9 @@ struct SPIRBlock : IVariant Return, // Block ends with return. Unreachable, // Noop - Kill // Discard + Kill, // Discard + IgnoreIntersection, // Ray Tracing + TerminateRay // Ray Tracing }; enum Merge @@ -1069,7 +1075,8 @@ struct SPIRConstant : IVariant type = TypeConstant }; - union Constant { + union Constant + { uint32_t u32; int32_t i32; float f32; @@ -1107,7 +1114,8 @@ struct SPIRConstant : IVariant int e = (u16_value >> 10) & 0x1f; int m = (u16_value >> 0) & 0x3ff; - union { + union + { float f32; uint32_t u32; } u; @@ -1526,6 +1534,7 @@ struct AccessChainMeta bool need_transpose = false; bool storage_is_packed = false; bool storage_is_invariant = false; + bool flattened_struct = false; }; enum ExtendedDecorations @@ -1560,8 +1569,10 @@ enum ExtendedDecorations // Marks a buffer block for using explicit offsets (GLSL/HLSL). SPIRVCrossDecorationExplicitOffset, - // Apply to a variable in the Input storage class; marks it as holding the base group passed to vkCmdDispatchBase(). - // In MSL, this is used to adjust the WorkgroupId and GlobalInvocationId variables. + // Apply to a variable in the Input storage class; marks it as holding the base group passed to vkCmdDispatchBase(), + // or the base vertex and instance indices passed to vkCmdDrawIndexed(). + // In MSL, this is used to adjust the WorkgroupId and GlobalInvocationId variables in compute shaders, + // and to hold the BaseVertex and BaseInstance variables in vertex shaders. SPIRVCrossDecorationBuiltInDispatchBase, // Apply to a variable that is a function parameter; marks it as being a "dynamic" @@ -1570,6 +1581,27 @@ enum ExtendedDecorations // Y'CbCr conversion. SPIRVCrossDecorationDynamicImageSampler, + // Apply to a variable in the Input storage class; marks it as holding the size of the stage + // input grid. + // In MSL, this is used to hold the vertex and instance counts in a tessellation pipeline + // vertex shader. + SPIRVCrossDecorationBuiltInStageInputSize, + + // Apply to any access chain of a tessellation I/O variable; stores the type of the sub-object + // that was chained to, as recorded in the input variable itself. This is used in case the pointer + // is itself used as the base of an access chain, to calculate the original type of the sub-object + // chained to, in case a swizzle needs to be applied. This should not happen normally with valid + // SPIR-V, but the MSL backend can change the type of input variables, necessitating the + // addition of swizzles to keep the generated code compiling. + SPIRVCrossDecorationTessIOOriginalInputTypeID, + + // Apply to any access chain of an interface variable used with pull-model interpolation, where the variable is a + // vector but the resulting pointer is a scalar; stores the component index that is to be accessed by the chain. + // This is used when emitting calls to interpolation functions on the chain in MSL: in this case, the component + // must be applied to the result, since pull-model interpolants in MSL cannot be swizzled directly, but the + // results of interpolation can. + SPIRVCrossDecorationInterpolantComponentExpr, + SPIRVCrossDecorationCount }; @@ -1589,6 +1621,7 @@ struct Meta uint32_t offset = 0; uint32_t xfb_buffer = 0; uint32_t xfb_stride = 0; + uint32_t stream = 0; uint32_t array_stride = 0; uint32_t matrix_stride = 0; uint32_t input_attachment = 0; diff --git a/src/libraries/spirv_cross/spirv_cpp.cpp b/src/libraries/spirv_cross/spirv_cpp.cpp index 53094d4e1..e95fe00ee 100644 --- a/src/libraries/spirv_cross/spirv_cpp.cpp +++ b/src/libraries/spirv_cross/spirv_cpp.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 Arm Limited + * Copyright 2015-2021 Arm Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #include "spirv_cpp.hpp" using namespace spv; @@ -306,6 +313,8 @@ void CompilerCPP::emit_resources() string CompilerCPP::compile() { + ir.fixup_reserved_names(); + // Do not deal with ES-isms like precision, older extensions and such. options.es = false; options.version = 450; diff --git a/src/libraries/spirv_cross/spirv_cpp.hpp b/src/libraries/spirv_cross/spirv_cpp.hpp index e78d0369f..a3bf0edb3 100644 --- a/src/libraries/spirv_cross/spirv_cpp.hpp +++ b/src/libraries/spirv_cross/spirv_cpp.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 Arm Limited + * Copyright 2015-2021 Arm Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #ifndef SPIRV_CROSS_CPP_HPP #define SPIRV_CROSS_CPP_HPP diff --git a/src/libraries/spirv_cross/spirv_cross.cpp b/src/libraries/spirv_cross/spirv_cross.cpp index 7c227a3dc..f024fa78b 100644 --- a/src/libraries/spirv_cross/spirv_cross.cpp +++ b/src/libraries/spirv_cross/spirv_cross.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 Arm Limited + * Copyright 2015-2021 Arm Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #include "spirv_cross.hpp" #include "GLSL.std.450.h" #include "spirv_cfg.hpp" @@ -89,7 +96,9 @@ bool Compiler::variable_storage_is_aliased(const SPIRVariable &v) bool Compiler::block_is_pure(const SPIRBlock &block) { // This is a global side effect of the function. - if (block.terminator == SPIRBlock::Kill) + if (block.terminator == SPIRBlock::Kill || + block.terminator == SPIRBlock::TerminateRay || + block.terminator == SPIRBlock::IgnoreIntersection) return false; for (auto &i : block.ops) @@ -151,11 +160,13 @@ bool Compiler::block_is_pure(const SPIRBlock &block) return false; // Ray tracing builtins are impure. - case OpReportIntersectionNV: + case OpReportIntersectionKHR: case OpIgnoreIntersectionNV: case OpTerminateRayNV: case OpTraceNV: + case OpTraceRayKHR: case OpExecuteCallableNV: + case OpExecuteCallableKHR: return false; // OpExtInst is potentially impure depending on extension, but GLSL builtins are at least pure. @@ -273,11 +284,27 @@ SPIRVariable *Compiler::maybe_get_backing_variable(uint32_t chain) return var; } -StorageClass Compiler::get_backing_variable_storage(uint32_t ptr) +StorageClass Compiler::get_expression_effective_storage_class(uint32_t ptr) { auto *var = maybe_get_backing_variable(ptr); - if (var) - return var->storage; + + // If the expression has been lowered to a temporary, we need to use the Generic storage class. + // We're looking for the effective storage class of a given expression. + // An access chain or forwarded OpLoads from such access chains + // will generally have the storage class of the underlying variable, but if the load was not forwarded + // we have lost any address space qualifiers. + bool forced_temporary = ir.ids[ptr].get_type() == TypeExpression && !get(ptr).access_chain && + (forced_temporaries.count(ptr) != 0 || forwarded_temporaries.count(ptr) == 0); + + if (var && !forced_temporary) + { + // Normalize SSBOs to StorageBuffer here. + if (var->storage == StorageClassUniform && + has_decoration(get(var->basetype).self, DecorationBufferBlock)) + return StorageClassStorageBuffer; + else + return var->storage; + } else return expression_type(ptr).storage; } @@ -519,10 +546,16 @@ bool Compiler::is_hidden_variable(const SPIRVariable &var, bool include_builtins return false; } - bool hidden = false; - if (check_active_interface_variables && storage_class_is_interface(var.storage)) - hidden = active_interface_variables.find(var.self) == end(active_interface_variables); - return hidden; + // In SPIR-V 1.4 and up we must also use the active variable interface to disable global variables + // which are not part of the entry point. + if (ir.get_spirv_version() >= 0x10400 && var.storage != spv::StorageClassGeneric && + var.storage != spv::StorageClassFunction && !interface_variable_exists_in_entry_point(var.self)) + { + return true; + } + + return check_active_interface_variables && storage_class_is_interface(var.storage) && + active_interface_variables.find(var.self) == end(active_interface_variables); } bool Compiler::is_builtin_type(const SPIRType &type) const @@ -683,8 +716,31 @@ bool Compiler::InterfaceVariableAccessHandler::handle(Op opcode, const uint32_t { if (length < 5) return false; - uint32_t extension_set = args[2]; - if (compiler.get(extension_set).ext == SPIRExtension::SPV_AMD_shader_explicit_vertex_parameter) + auto &extension_set = compiler.get(args[2]); + switch (extension_set.ext) + { + case SPIRExtension::GLSL: + { + auto op = static_cast(args[3]); + + switch (op) + { + case GLSLstd450InterpolateAtCentroid: + case GLSLstd450InterpolateAtSample: + case GLSLstd450InterpolateAtOffset: + { + auto *var = compiler.maybe_get(args[4]); + if (var && storage_class_is_interface(var->storage)) + variables.insert(args[4]); + break; + } + + default: + break; + } + break; + } + case SPIRExtension::SPV_AMD_shader_explicit_vertex_parameter: { enum AMDShaderExplicitVertexParameter { @@ -706,6 +762,10 @@ bool Compiler::InterfaceVariableAccessHandler::handle(Op opcode, const uint32_t default: break; } + break; + } + default: + break; } break; } @@ -755,9 +815,17 @@ unordered_set Compiler::get_active_interface_variables() const InterfaceVariableAccessHandler handler(*this, variables); traverse_all_reachable_opcodes(get(ir.default_entry_point), handler); - // Make sure we preserve output variables which are only initialized, but never accessed by any code. ir.for_each_typed_id([&](uint32_t, const SPIRVariable &var) { - if (var.storage == StorageClassOutput && var.initializer != ID(0)) + if (var.storage != StorageClassOutput) + return; + if (!interface_variable_exists_in_entry_point(var.self)) + return; + + // An output variable which is just declared (but uninitialized) might be read by subsequent stages + // so we should force-enable these outputs, + // since compilation will fail if a subsequent stage attempts to read from the variable in question. + // Also, make sure we preserve output variables which are only initialized, but never accessed by any code. + if (var.initializer != ID(0) || get_execution_model() != ExecutionModelFragment) variables.insert(var.self); }); @@ -1636,6 +1704,161 @@ size_t Compiler::get_declared_struct_size_runtime_array(const SPIRType &type, si return size; } +uint32_t Compiler::evaluate_spec_constant_u32(const SPIRConstantOp &spec) const +{ + auto &result_type = get(spec.basetype); + if (result_type.basetype != SPIRType::UInt && result_type.basetype != SPIRType::Int && + result_type.basetype != SPIRType::Boolean) + { + SPIRV_CROSS_THROW( + "Only 32-bit integers and booleans are currently supported when evaluating specialization constants.\n"); + } + + if (!is_scalar(result_type)) + SPIRV_CROSS_THROW("Spec constant evaluation must be a scalar.\n"); + + uint32_t value = 0; + + const auto eval_u32 = [&](uint32_t id) -> uint32_t { + auto &type = expression_type(id); + if (type.basetype != SPIRType::UInt && type.basetype != SPIRType::Int && type.basetype != SPIRType::Boolean) + { + SPIRV_CROSS_THROW("Only 32-bit integers and booleans are currently supported when evaluating " + "specialization constants.\n"); + } + + if (!is_scalar(type)) + SPIRV_CROSS_THROW("Spec constant evaluation must be a scalar.\n"); + if (const auto *c = this->maybe_get(id)) + return c->scalar(); + else + return evaluate_spec_constant_u32(this->get(id)); + }; + +#define binary_spec_op(op, binary_op) \ + case Op##op: \ + value = eval_u32(spec.arguments[0]) binary_op eval_u32(spec.arguments[1]); \ + break +#define binary_spec_op_cast(op, binary_op, type) \ + case Op##op: \ + value = uint32_t(type(eval_u32(spec.arguments[0])) binary_op type(eval_u32(spec.arguments[1]))); \ + break + + // Support the basic opcodes which are typically used when computing array sizes. + switch (spec.opcode) + { + binary_spec_op(IAdd, +); + binary_spec_op(ISub, -); + binary_spec_op(IMul, *); + binary_spec_op(BitwiseAnd, &); + binary_spec_op(BitwiseOr, |); + binary_spec_op(BitwiseXor, ^); + binary_spec_op(LogicalAnd, &); + binary_spec_op(LogicalOr, |); + binary_spec_op(ShiftLeftLogical, <<); + binary_spec_op(ShiftRightLogical, >>); + binary_spec_op_cast(ShiftRightArithmetic, >>, int32_t); + binary_spec_op(LogicalEqual, ==); + binary_spec_op(LogicalNotEqual, !=); + binary_spec_op(IEqual, ==); + binary_spec_op(INotEqual, !=); + binary_spec_op(ULessThan, <); + binary_spec_op(ULessThanEqual, <=); + binary_spec_op(UGreaterThan, >); + binary_spec_op(UGreaterThanEqual, >=); + binary_spec_op_cast(SLessThan, <, int32_t); + binary_spec_op_cast(SLessThanEqual, <=, int32_t); + binary_spec_op_cast(SGreaterThan, >, int32_t); + binary_spec_op_cast(SGreaterThanEqual, >=, int32_t); +#undef binary_spec_op +#undef binary_spec_op_cast + + case OpLogicalNot: + value = uint32_t(!eval_u32(spec.arguments[0])); + break; + + case OpNot: + value = ~eval_u32(spec.arguments[0]); + break; + + case OpSNegate: + value = uint32_t(-int32_t(eval_u32(spec.arguments[0]))); + break; + + case OpSelect: + value = eval_u32(spec.arguments[0]) ? eval_u32(spec.arguments[1]) : eval_u32(spec.arguments[2]); + break; + + case OpUMod: + { + uint32_t a = eval_u32(spec.arguments[0]); + uint32_t b = eval_u32(spec.arguments[1]); + if (b == 0) + SPIRV_CROSS_THROW("Undefined behavior in UMod, b == 0.\n"); + value = a % b; + break; + } + + case OpSRem: + { + auto a = int32_t(eval_u32(spec.arguments[0])); + auto b = int32_t(eval_u32(spec.arguments[1])); + if (b == 0) + SPIRV_CROSS_THROW("Undefined behavior in SRem, b == 0.\n"); + value = a % b; + break; + } + + case OpSMod: + { + auto a = int32_t(eval_u32(spec.arguments[0])); + auto b = int32_t(eval_u32(spec.arguments[1])); + if (b == 0) + SPIRV_CROSS_THROW("Undefined behavior in SMod, b == 0.\n"); + auto v = a % b; + + // Makes sure we match the sign of b, not a. + if ((b < 0 && v > 0) || (b > 0 && v < 0)) + v += b; + value = v; + break; + } + + case OpUDiv: + { + uint32_t a = eval_u32(spec.arguments[0]); + uint32_t b = eval_u32(spec.arguments[1]); + if (b == 0) + SPIRV_CROSS_THROW("Undefined behavior in UDiv, b == 0.\n"); + value = a / b; + break; + } + + case OpSDiv: + { + auto a = int32_t(eval_u32(spec.arguments[0])); + auto b = int32_t(eval_u32(spec.arguments[1])); + if (b == 0) + SPIRV_CROSS_THROW("Undefined behavior in SDiv, b == 0.\n"); + value = a / b; + break; + } + + default: + SPIRV_CROSS_THROW("Unsupported spec constant opcode for evaluation.\n"); + } + + return value; +} + +uint32_t Compiler::evaluate_constant_u32(uint32_t id) const +{ + if (const auto *c = maybe_get(id)) + return c->scalar(); + else + return evaluate_spec_constant_u32(get(id)); +} + size_t Compiler::get_declared_struct_member_size(const SPIRType &struct_type, uint32_t index) const { if (struct_type.member_types.empty()) @@ -1670,7 +1893,7 @@ size_t Compiler::get_declared_struct_member_size(const SPIRType &struct_type, ui { // For arrays, we can use ArrayStride to get an easy check. bool array_size_literal = type.array_size_literal.back(); - uint32_t array_size = array_size_literal ? type.array.back() : get(type.array.back()).scalar(); + uint32_t array_size = array_size_literal ? type.array.back() : evaluate_constant_u32(type.array.back()); return type_struct_member_array_stride(struct_type, index) * array_size; } else if (type.basetype == SPIRType::Struct) @@ -1904,6 +2127,13 @@ bool Compiler::is_tessellation_shader(ExecutionModel model) return model == ExecutionModelTessellationControl || model == ExecutionModelTessellationEvaluation; } +bool Compiler::is_vertex_like_shader() const +{ + auto model = get_execution_model(); + return model == ExecutionModelVertex || model == ExecutionModelGeometry || + model == ExecutionModelTessellationControl || model == ExecutionModelTessellationEvaluation; +} + bool Compiler::is_tessellation_shader() const { return is_tessellation_shader(get_execution_model()); @@ -2067,16 +2297,22 @@ SPIREntryPoint &Compiler::get_entry_point() bool Compiler::interface_variable_exists_in_entry_point(uint32_t id) const { auto &var = get(id); - if (var.storage != StorageClassInput && var.storage != StorageClassOutput && - var.storage != StorageClassUniformConstant) - SPIRV_CROSS_THROW("Only Input, Output variables and Uniform constants are part of a shader linking interface."); - // This is to avoid potential problems with very old glslang versions which did - // not emit input/output interfaces properly. - // We can assume they only had a single entry point, and single entry point - // shaders could easily be assumed to use every interface variable anyways. - if (ir.entry_points.size() <= 1) - return true; + if (ir.get_spirv_version() < 0x10400) + { + if (var.storage != StorageClassInput && var.storage != StorageClassOutput && + var.storage != StorageClassUniformConstant) + SPIRV_CROSS_THROW("Only Input, Output variables and Uniform constants are part of a shader linking interface."); + + // This is to avoid potential problems with very old glslang versions which did + // not emit input/output interfaces properly. + // We can assume they only had a single entry point, and single entry point + // shaders could easily be assumed to use every interface variable anyways. + if (ir.entry_points.size() <= 1) + return true; + } + + // In SPIR-V 1.4 and later, all global resource variables must be present. auto &execution = get_entry_point(); return find(begin(execution.interface_variables), end(execution.interface_variables), VariableID(id)) != @@ -2599,7 +2835,8 @@ const SPIRConstant &Compiler::get_constant(ConstantID id) const return get(id); } -static bool exists_unaccessed_path_to_return(const CFG &cfg, uint32_t block, const unordered_set &blocks) +static bool exists_unaccessed_path_to_return(const CFG &cfg, uint32_t block, const unordered_set &blocks, + unordered_set &visit_cache) { // This block accesses the variable. if (blocks.find(block) != end(blocks)) @@ -2611,8 +2848,14 @@ static bool exists_unaccessed_path_to_return(const CFG &cfg, uint32_t block, con // If any of our successors have a path to the end, there exists a path from block. for (auto &succ : cfg.get_succeeding_edges(block)) - if (exists_unaccessed_path_to_return(cfg, succ, blocks)) - return true; + { + if (visit_cache.count(succ) == 0) + { + if (exists_unaccessed_path_to_return(cfg, succ, blocks, visit_cache)) + return true; + visit_cache.insert(succ); + } + } return false; } @@ -2669,7 +2912,8 @@ void Compiler::analyze_parameter_preservation( // void foo(int &var) { if (cond) var = 10; } // Using read/write counts, we will think it's just an out variable, but it really needs to be inout, // because if we don't write anything whatever we put into the function must return back to the caller. - if (exists_unaccessed_path_to_return(cfg, entry.entry_block, itr->second)) + unordered_set visit_cache; + if (exists_unaccessed_path_to_return(cfg, entry.entry_block, itr->second, visit_cache)) arg.read_count++; } } @@ -3627,23 +3871,55 @@ void Compiler::ActiveBuiltinHandler::handle_builtin(const SPIRType &type, BuiltI } } -bool Compiler::ActiveBuiltinHandler::handle(spv::Op opcode, const uint32_t *args, uint32_t length) +void Compiler::ActiveBuiltinHandler::add_if_builtin(uint32_t id, bool allow_blocks) { - const auto add_if_builtin = [&](uint32_t id) { - // Only handles variables here. - // Builtins which are part of a block are handled in AccessChain. - auto *var = compiler.maybe_get(id); - auto &decorations = compiler.ir.meta[id].decoration; - if (var && decorations.builtin) + // Only handle plain variables here. + // Builtins which are part of a block are handled in AccessChain. + // If allow_blocks is used however, this is to handle initializers of blocks, + // which implies that all members are written to. + + auto *var = compiler.maybe_get(id); + auto *m = compiler.ir.find_meta(id); + if (var && m) + { + auto &type = compiler.get(var->basetype); + auto &decorations = m->decoration; + auto &flags = type.storage == StorageClassInput ? + compiler.active_input_builtins : compiler.active_output_builtins; + if (decorations.builtin) { - auto &type = compiler.get(var->basetype); - auto &flags = - type.storage == StorageClassInput ? compiler.active_input_builtins : compiler.active_output_builtins; flags.set(decorations.builtin_type); handle_builtin(type, decorations.builtin_type, decorations.decoration_flags); } - }; + else if (allow_blocks && compiler.has_decoration(type.self, DecorationBlock)) + { + uint32_t member_count = uint32_t(type.member_types.size()); + for (uint32_t i = 0; i < member_count; i++) + { + if (compiler.has_member_decoration(type.self, i, DecorationBuiltIn)) + { + auto &member_type = compiler.get(type.member_types[i]); + BuiltIn builtin = BuiltIn(compiler.get_member_decoration(type.self, i, DecorationBuiltIn)); + flags.set(builtin); + handle_builtin(member_type, builtin, compiler.get_member_decoration_bitset(type.self, i)); + } + } + } + } +} +void Compiler::ActiveBuiltinHandler::add_if_builtin(uint32_t id) +{ + add_if_builtin(id, false); +} + +void Compiler::ActiveBuiltinHandler::add_if_builtin_or_block(uint32_t id) +{ + add_if_builtin(id, true); +} + +bool Compiler::ActiveBuiltinHandler::handle(spv::Op opcode, const uint32_t *args, uint32_t length) +{ switch (opcode) { case OpStore: @@ -3781,6 +4057,17 @@ void Compiler::update_active_builtins() clip_distance_count = 0; ActiveBuiltinHandler handler(*this); traverse_all_reachable_opcodes(get(ir.default_entry_point), handler); + + ir.for_each_typed_id([&](uint32_t, const SPIRVariable &var) { + if (var.storage != StorageClassOutput) + return; + if (!interface_variable_exists_in_entry_point(var.self)) + return; + + // Also, make sure we preserve output variables which are only initialized, but never accessed by any code. + if (var.initializer != ID(0)) + handler.add_if_builtin_or_block(var.self); + }); } // Returns whether this shader uses a builtin of the storage class @@ -4658,3 +4945,8 @@ bool Compiler::flush_phi_required(BlockID from, BlockID to) const return true; return false; } + +void Compiler::add_loop_level() +{ + current_loop_level++; +} diff --git a/src/libraries/spirv_cross/spirv_cross.hpp b/src/libraries/spirv_cross/spirv_cross.hpp index 98b58f743..84e23ca30 100644 --- a/src/libraries/spirv_cross/spirv_cross.hpp +++ b/src/libraries/spirv_cross/spirv_cross.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 Arm Limited + * Copyright 2015-2021 Arm Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #ifndef SPIRV_CROSS_HPP #define SPIRV_CROSS_HPP @@ -491,6 +498,12 @@ public: // The most common use here is to check if a buffer is readonly or writeonly. Bitset get_buffer_block_flags(VariableID id) const; + // Returns whether the position output is invariant + bool is_position_invariant() const + { + return position_invariant; + } + protected: const uint32_t *stream(const Instruction &instr) const { @@ -500,9 +513,18 @@ protected: if (!instr.length) return nullptr; - if (instr.offset + instr.length > ir.spirv.size()) - SPIRV_CROSS_THROW("Compiler::stream() out of range."); - return &ir.spirv[instr.offset]; + if (instr.is_embedded()) + { + auto &embedded = static_cast(instr); + assert(embedded.ops.size() == instr.length); + return embedded.ops.data(); + } + else + { + if (instr.offset + instr.length > ir.spirv.size()) + SPIRV_CROSS_THROW("Compiler::stream() out of range."); + return &ir.spirv[instr.offset]; + } } ParsedIR ir; @@ -513,9 +535,22 @@ protected: SPIRFunction *current_function = nullptr; SPIRBlock *current_block = nullptr; + uint32_t current_loop_level = 0; std::unordered_set active_interface_variables; bool check_active_interface_variables = false; + void add_loop_level(); + + void set_initializers(SPIRExpression &e) + { + e.emitted_loop_level = current_loop_level; + } + + template + void set_initializers(const T &) + { + } + // If our IDs are out of range here as part of opcodes, throw instead of // undefined behavior. template @@ -524,6 +559,7 @@ protected: ir.add_typed_id(static_cast(T::type), id); auto &var = variant_set(ir.ids[id], std::forward

(args)...); var.self = id; + set_initializers(var); return var; } @@ -611,7 +647,7 @@ protected: bool expression_is_lvalue(uint32_t id) const; bool variable_storage_is_aliased(const SPIRVariable &var); SPIRVariable *maybe_get_backing_variable(uint32_t chain); - spv::StorageClass get_backing_variable_storage(uint32_t ptr); + spv::StorageClass get_expression_effective_storage_class(uint32_t ptr); void register_read(uint32_t expr, uint32_t chain, bool forwarded); void register_write(uint32_t chain); @@ -812,6 +848,9 @@ protected: Compiler &compiler; void handle_builtin(const SPIRType &type, spv::BuiltIn builtin, const Bitset &decoration_flags); + void add_if_builtin(uint32_t id); + void add_if_builtin_or_block(uint32_t id); + void add_if_builtin(uint32_t id, bool allow_blocks); }; bool traverse_all_reachable_opcodes(const SPIRBlock &block, OpcodeHandler &handler) const; @@ -1046,6 +1085,11 @@ protected: bool flush_phi_required(BlockID from, BlockID to) const; + uint32_t evaluate_spec_constant_u32(const SPIRConstantOp &spec) const; + uint32_t evaluate_constant_u32(uint32_t id) const; + + bool is_vertex_like_shader() const; + private: // Used only to implement the old deprecated get_entry_point() interface. const SPIREntryPoint &get_first_entry_point(const std::string &name) const; diff --git a/src/libraries/spirv_cross/spirv_cross_c.cpp b/src/libraries/spirv_cross/spirv_cross_c.cpp index 8cc074267..ace5376bb 100644 --- a/src/libraries/spirv_cross/spirv_cross_c.cpp +++ b/src/libraries/spirv_cross/spirv_cross_c.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 Hans-Kristian Arntzen + * Copyright 2019-2021 Hans-Kristian Arntzen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #include "spirv_cross_c.h" #if SPIRV_CROSS_C_API_CPP @@ -459,6 +466,9 @@ spvc_result spvc_compiler_options_set_uint(spvc_compiler_options options, spvc_c case SPVC_COMPILER_OPTION_GLSL_EMIT_UNIFORM_BUFFER_AS_PLAIN_UNIFORMS: options->glsl.emit_uniform_buffer_as_plain_uniforms = value != 0; break; + case SPVC_COMPILER_OPTION_GLSL_FORCE_FLATTENED_IO_BLOCKS: + options->glsl.force_flattened_io_blocks = value != 0; + break; #endif #if SPIRV_CROSS_C_API_HLSL @@ -489,6 +499,10 @@ spvc_result spvc_compiler_options_set_uint(spvc_compiler_options options, spvc_c case SPVC_COMPILER_OPTION_HLSL_ENABLE_16BIT_TYPES: options->hlsl.enable_16bit_types = value != 0; break; + + case SPVC_COMPILER_OPTION_HLSL_FLATTEN_MATRIX_VERTEX_INPUT_SEMANTICS: + options->hlsl.flatten_matrix_vertex_input_semantics = value != 0; + break; #endif #if SPIRV_CROSS_C_API_MSL @@ -596,8 +610,8 @@ spvc_result spvc_compiler_options_set_uint(spvc_compiler_options options, spvc_c options->msl.enable_base_index_zero = value != 0; break; - case SPVC_COMPILER_OPTION_MSL_IOS_FRAMEBUFFER_FETCH_SUBPASS: - options->msl.ios_use_framebuffer_fetch_subpasses = value != 0; + case SPVC_COMPILER_OPTION_MSL_FRAMEBUFFER_FETCH_SUBPASS: + options->msl.use_framebuffer_fetch_subpasses = value != 0; break; case SPVC_COMPILER_OPTION_MSL_INVARIANT_FP_MATH: @@ -635,6 +649,62 @@ spvc_result spvc_compiler_options_set_uint(spvc_compiler_options options, spvc_c case SPVC_COMPILER_OPTION_MSL_ENABLE_CLIP_DISTANCE_USER_VARYING: options->msl.enable_clip_distance_user_varying = value != 0; break; + + case SPVC_COMPILER_OPTION_MSL_MULTI_PATCH_WORKGROUP: + options->msl.multi_patch_workgroup = value != 0; + break; + + case SPVC_COMPILER_OPTION_MSL_SHADER_INPUT_BUFFER_INDEX: + options->msl.shader_input_buffer_index = value; + break; + + case SPVC_COMPILER_OPTION_MSL_SHADER_INDEX_BUFFER_INDEX: + options->msl.shader_index_buffer_index = value; + break; + + case SPVC_COMPILER_OPTION_MSL_VERTEX_FOR_TESSELLATION: + options->msl.vertex_for_tessellation = value != 0; + break; + + case SPVC_COMPILER_OPTION_MSL_VERTEX_INDEX_TYPE: + options->msl.vertex_index_type = static_cast(value); + break; + + case SPVC_COMPILER_OPTION_MSL_MULTIVIEW_LAYERED_RENDERING: + options->msl.multiview_layered_rendering = value != 0; + break; + + case SPVC_COMPILER_OPTION_MSL_ARRAYED_SUBPASS_INPUT: + options->msl.arrayed_subpass_input = value != 0; + break; + + case SPVC_COMPILER_OPTION_MSL_R32UI_LINEAR_TEXTURE_ALIGNMENT: + options->msl.r32ui_linear_texture_alignment = value; + break; + + case SPVC_COMPILER_OPTION_MSL_R32UI_ALIGNMENT_CONSTANT_ID: + options->msl.r32ui_alignment_constant_id = value; + break; + + case SPVC_COMPILER_OPTION_MSL_IOS_USE_SIMDGROUP_FUNCTIONS: + options->msl.ios_use_simdgroup_functions = value != 0; + break; + + case SPVC_COMPILER_OPTION_MSL_EMULATE_SUBGROUPS: + options->msl.emulate_subgroups = value != 0; + break; + + case SPVC_COMPILER_OPTION_MSL_FIXED_SUBGROUP_SIZE: + options->msl.fixed_subgroup_size = value; + break; + + case SPVC_COMPILER_OPTION_MSL_FORCE_SAMPLE_RATE_SHADING: + options->msl.force_sample_rate_shading = value != 0; + break; + + case SPVC_COMPILER_OPTION_MSL_IOS_SUPPORT_BASE_VERTEX_INSTANCE: + options->msl.ios_support_base_vertex_instance = value != 0; + break; #endif default: @@ -1019,11 +1089,11 @@ spvc_result spvc_compiler_msl_add_vertex_attribute(spvc_compiler compiler, const } auto &msl = *static_cast(compiler->compiler.get()); - MSLVertexAttr attr; + MSLShaderInput attr; attr.location = va->location; - attr.format = static_cast(va->format); + attr.format = static_cast(va->format); attr.builtin = static_cast(va->builtin); - msl.add_msl_vertex_attribute(attr); + msl.add_msl_shader_input(attr); return SPVC_SUCCESS; #else (void)va; @@ -1032,6 +1102,30 @@ spvc_result spvc_compiler_msl_add_vertex_attribute(spvc_compiler compiler, const #endif } +spvc_result spvc_compiler_msl_add_shader_input(spvc_compiler compiler, const spvc_msl_shader_input *si) +{ +#if SPIRV_CROSS_C_API_MSL + if (compiler->backend != SPVC_BACKEND_MSL) + { + compiler->context->report_error("MSL function used on a non-MSL backend."); + return SPVC_ERROR_INVALID_ARGUMENT; + } + + auto &msl = *static_cast(compiler->compiler.get()); + MSLShaderInput input; + input.location = si->location; + input.format = static_cast(si->format); + input.builtin = static_cast(si->builtin); + input.vecsize = si->vecsize; + msl.add_msl_shader_input(input); + return SPVC_SUCCESS; +#else + (void)si; + compiler->context->report_error("MSL function used on a non-MSL backend."); + return SPVC_ERROR_INVALID_ARGUMENT; +#endif +} + spvc_result spvc_compiler_msl_add_resource_binding(spvc_compiler compiler, const spvc_msl_resource_binding *binding) { @@ -1139,7 +1233,7 @@ spvc_result spvc_compiler_msl_set_argument_buffer_device_address_space(spvc_comp #endif } -spvc_bool spvc_compiler_msl_is_vertex_attribute_used(spvc_compiler compiler, unsigned location) +spvc_bool spvc_compiler_msl_is_shader_input_used(spvc_compiler compiler, unsigned location) { #if SPIRV_CROSS_C_API_MSL if (compiler->backend != SPVC_BACKEND_MSL) @@ -1149,7 +1243,7 @@ spvc_bool spvc_compiler_msl_is_vertex_attribute_used(spvc_compiler compiler, uns } auto &msl = *static_cast(compiler->compiler.get()); - return msl.is_msl_vertex_attribute_used(location) ? SPVC_TRUE : SPVC_FALSE; + return msl.is_msl_shader_input_used(location) ? SPVC_TRUE : SPVC_FALSE; #else (void)location; compiler->context->report_error("MSL function used on a non-MSL backend."); @@ -1157,6 +1251,11 @@ spvc_bool spvc_compiler_msl_is_vertex_attribute_used(spvc_compiler compiler, uns #endif } +spvc_bool spvc_compiler_msl_is_vertex_attribute_used(spvc_compiler compiler, unsigned location) +{ + return spvc_compiler_msl_is_shader_input_used(compiler, location); +} + spvc_bool spvc_compiler_msl_is_resource_used(spvc_compiler compiler, SpvExecutionModel model, unsigned set, unsigned binding) { @@ -1179,6 +1278,42 @@ spvc_bool spvc_compiler_msl_is_resource_used(spvc_compiler compiler, SpvExecutio #endif } +spvc_result spvc_compiler_msl_set_combined_sampler_suffix(spvc_compiler compiler, const char *suffix) +{ +#if SPIRV_CROSS_C_API_MSL + if (compiler->backend != SPVC_BACKEND_MSL) + { + compiler->context->report_error("MSL function used on a non-MSL backend."); + return SPVC_ERROR_INVALID_ARGUMENT; + } + + auto &msl = *static_cast(compiler->compiler.get()); + msl.set_combined_sampler_suffix(suffix); + return SPVC_SUCCESS; +#else + (void)suffix; + compiler->context->report_error("MSL function used on a non-MSL backend."); + return SPVC_ERROR_INVALID_ARGUMENT; +#endif +} + +const char *spvc_compiler_msl_get_combined_sampler_suffix(spvc_compiler compiler) +{ +#if SPIRV_CROSS_C_API_MSL + if (compiler->backend != SPVC_BACKEND_MSL) + { + compiler->context->report_error("MSL function used on a non-MSL backend."); + return ""; + } + + auto &msl = *static_cast(compiler->compiler.get()); + return msl.get_combined_sampler_suffix(); +#else + compiler->context->report_error("MSL function used on a non-MSL backend."); + return ""; +#endif +} + #if SPIRV_CROSS_C_API_MSL static void spvc_convert_msl_sampler(MSLConstexprSampler &samp, const spvc_msl_constexpr_sampler *sampler) { @@ -2258,7 +2393,7 @@ void spvc_msl_vertex_attribute_init(spvc_msl_vertex_attribute *attr) { #if SPIRV_CROSS_C_API_MSL // Crude, but works. - MSLVertexAttr attr_default; + MSLShaderInput attr_default; attr->location = attr_default.location; attr->format = static_cast(attr_default.format); attr->builtin = static_cast(attr_default.builtin); @@ -2267,6 +2402,19 @@ void spvc_msl_vertex_attribute_init(spvc_msl_vertex_attribute *attr) #endif } +void spvc_msl_shader_input_init(spvc_msl_shader_input *input) +{ +#if SPIRV_CROSS_C_API_MSL + MSLShaderInput input_default; + input->location = input_default.location; + input->format = static_cast(input_default.format); + input->builtin = static_cast(input_default.builtin); + input->vecsize = input_default.vecsize; +#else + memset(input, 0, sizeof(*input)); +#endif +} + void spvc_msl_resource_binding_init(spvc_msl_resource_binding *binding) { #if SPIRV_CROSS_C_API_MSL diff --git a/src/libraries/spirv_cross/spirv_cross_c.h b/src/libraries/spirv_cross/spirv_cross_c.h index 1ec154ba9..1511a1dbd 100644 --- a/src/libraries/spirv_cross/spirv_cross_c.h +++ b/src/libraries/spirv_cross/spirv_cross_c.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 Hans-Kristian Arntzen + * Copyright 2019-2021 Hans-Kristian Arntzen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #ifndef SPIRV_CROSS_C_API_H #define SPIRV_CROSS_C_API_H @@ -33,7 +40,7 @@ extern "C" { /* Bumped if ABI or API breaks backwards compatibility. */ #define SPVC_C_API_VERSION_MAJOR 0 /* Bumped if APIs or enumerations are added in a backwards compatible way. */ -#define SPVC_C_API_VERSION_MINOR 34 +#define SPVC_C_API_VERSION_MINOR 45 /* Bumped if internal implementation details change. */ #define SPVC_C_API_VERSION_PATCH 0 @@ -259,14 +266,32 @@ typedef enum spvc_msl_platform } spvc_msl_platform; /* Maps to C++ API. */ -typedef enum spvc_msl_vertex_format +typedef enum spvc_msl_index_type { - SPVC_MSL_VERTEX_FORMAT_OTHER = 0, - SPVC_MSL_VERTEX_FORMAT_UINT8 = 1, - SPVC_MSL_VERTEX_FORMAT_UINT16 = 2 -} spvc_msl_vertex_format; + SPVC_MSL_INDEX_TYPE_NONE = 0, + SPVC_MSL_INDEX_TYPE_UINT16 = 1, + SPVC_MSL_INDEX_TYPE_UINT32 = 2, + SPVC_MSL_INDEX_TYPE_MAX_INT = 0x7fffffff +} spvc_msl_index_type; /* Maps to C++ API. */ +typedef enum spvc_msl_shader_input_format +{ + SPVC_MSL_SHADER_INPUT_FORMAT_OTHER = 0, + SPVC_MSL_SHADER_INPUT_FORMAT_UINT8 = 1, + SPVC_MSL_SHADER_INPUT_FORMAT_UINT16 = 2, + SPVC_MSL_SHADER_INPUT_FORMAT_ANY16 = 3, + SPVC_MSL_SHADER_INPUT_FORMAT_ANY32 = 4, + + /* Deprecated names. */ + SPVC_MSL_VERTEX_FORMAT_OTHER = SPVC_MSL_SHADER_INPUT_FORMAT_OTHER, + SPVC_MSL_VERTEX_FORMAT_UINT8 = SPVC_MSL_SHADER_INPUT_FORMAT_UINT8, + SPVC_MSL_VERTEX_FORMAT_UINT16 = SPVC_MSL_SHADER_INPUT_FORMAT_UINT16, + + SPVC_MSL_SHADER_INPUT_FORMAT_INT_MAX = 0x7fffffff +} spvc_msl_shader_input_format, spvc_msl_vertex_format; + +/* Maps to C++ API. Deprecated; use spvc_msl_shader_input. */ typedef struct spvc_msl_vertex_attribute { unsigned location; @@ -289,6 +314,20 @@ typedef struct spvc_msl_vertex_attribute */ SPVC_PUBLIC_API void spvc_msl_vertex_attribute_init(spvc_msl_vertex_attribute *attr); +/* Maps to C++ API. */ +typedef struct spvc_msl_shader_input +{ + unsigned location; + spvc_msl_vertex_format format; + SpvBuiltIn builtin; + unsigned vecsize; +} spvc_msl_shader_input; + +/* + * Initializes the shader input struct. + */ +SPVC_PUBLIC_API void spvc_msl_shader_input_init(spvc_msl_shader_input *input); + /* Maps to C++ API. */ typedef struct spvc_msl_resource_binding { @@ -574,7 +613,11 @@ typedef enum spvc_compiler_option SPVC_COMPILER_OPTION_MSL_DYNAMIC_OFFSETS_BUFFER_INDEX = 43 | SPVC_COMPILER_OPTION_MSL_BIT, SPVC_COMPILER_OPTION_MSL_TEXTURE_1D_AS_2D = 44 | SPVC_COMPILER_OPTION_MSL_BIT, SPVC_COMPILER_OPTION_MSL_ENABLE_BASE_INDEX_ZERO = 45 | SPVC_COMPILER_OPTION_MSL_BIT, + + /* Obsolete. Use MSL_FRAMEBUFFER_FETCH_SUBPASS instead. */ SPVC_COMPILER_OPTION_MSL_IOS_FRAMEBUFFER_FETCH_SUBPASS = 46 | SPVC_COMPILER_OPTION_MSL_BIT, + SPVC_COMPILER_OPTION_MSL_FRAMEBUFFER_FETCH_SUBPASS = 46 | SPVC_COMPILER_OPTION_MSL_BIT, + SPVC_COMPILER_OPTION_MSL_INVARIANT_FP_MATH = 47 | SPVC_COMPILER_OPTION_MSL_BIT, SPVC_COMPILER_OPTION_MSL_EMULATE_CUBEMAP_ARRAY = 48 | SPVC_COMPILER_OPTION_MSL_BIT, SPVC_COMPILER_OPTION_MSL_ENABLE_DECORATION_BINDING = 49 | SPVC_COMPILER_OPTION_MSL_BIT, @@ -596,6 +639,27 @@ typedef enum spvc_compiler_option SPVC_COMPILER_OPTION_HLSL_ENABLE_16BIT_TYPES = 60 | SPVC_COMPILER_OPTION_HLSL_BIT, + SPVC_COMPILER_OPTION_MSL_MULTI_PATCH_WORKGROUP = 61 | SPVC_COMPILER_OPTION_MSL_BIT, + SPVC_COMPILER_OPTION_MSL_SHADER_INPUT_BUFFER_INDEX = 62 | SPVC_COMPILER_OPTION_MSL_BIT, + SPVC_COMPILER_OPTION_MSL_SHADER_INDEX_BUFFER_INDEX = 63 | SPVC_COMPILER_OPTION_MSL_BIT, + SPVC_COMPILER_OPTION_MSL_VERTEX_FOR_TESSELLATION = 64 | SPVC_COMPILER_OPTION_MSL_BIT, + SPVC_COMPILER_OPTION_MSL_VERTEX_INDEX_TYPE = 65 | SPVC_COMPILER_OPTION_MSL_BIT, + + SPVC_COMPILER_OPTION_GLSL_FORCE_FLATTENED_IO_BLOCKS = 66 | SPVC_COMPILER_OPTION_GLSL_BIT, + + SPVC_COMPILER_OPTION_MSL_MULTIVIEW_LAYERED_RENDERING = 67 | SPVC_COMPILER_OPTION_MSL_BIT, + SPVC_COMPILER_OPTION_MSL_ARRAYED_SUBPASS_INPUT = 68 | SPVC_COMPILER_OPTION_MSL_BIT, + SPVC_COMPILER_OPTION_MSL_R32UI_LINEAR_TEXTURE_ALIGNMENT = 69 | SPVC_COMPILER_OPTION_MSL_BIT, + SPVC_COMPILER_OPTION_MSL_R32UI_ALIGNMENT_CONSTANT_ID = 70 | SPVC_COMPILER_OPTION_MSL_BIT, + + SPVC_COMPILER_OPTION_HLSL_FLATTEN_MATRIX_VERTEX_INPUT_SEMANTICS = 71 | SPVC_COMPILER_OPTION_HLSL_BIT, + + SPVC_COMPILER_OPTION_MSL_IOS_USE_SIMDGROUP_FUNCTIONS = 72 | SPVC_COMPILER_OPTION_MSL_BIT, + SPVC_COMPILER_OPTION_MSL_EMULATE_SUBGROUPS = 73 | SPVC_COMPILER_OPTION_MSL_BIT, + SPVC_COMPILER_OPTION_MSL_FIXED_SUBGROUP_SIZE = 74 | SPVC_COMPILER_OPTION_MSL_BIT, + SPVC_COMPILER_OPTION_MSL_FORCE_SAMPLE_RATE_SHADING = 75 | SPVC_COMPILER_OPTION_MSL_BIT, + SPVC_COMPILER_OPTION_MSL_IOS_SUPPORT_BASE_VERTEX_INSTANCE = 76 | SPVC_COMPILER_OPTION_MSL_BIT, + SPVC_COMPILER_OPTION_INT_MAX = 0x7fffffff } spvc_compiler_option; @@ -698,9 +762,15 @@ SPVC_PUBLIC_API spvc_result spvc_compiler_msl_add_vertex_attribute(spvc_compiler const spvc_msl_vertex_attribute *attrs); SPVC_PUBLIC_API spvc_result spvc_compiler_msl_add_resource_binding(spvc_compiler compiler, const spvc_msl_resource_binding *binding); +SPVC_PUBLIC_API spvc_result spvc_compiler_msl_add_shader_input(spvc_compiler compiler, + const spvc_msl_shader_input *input); SPVC_PUBLIC_API spvc_result spvc_compiler_msl_add_discrete_descriptor_set(spvc_compiler compiler, unsigned desc_set); SPVC_PUBLIC_API spvc_result spvc_compiler_msl_set_argument_buffer_device_address_space(spvc_compiler compiler, unsigned desc_set, spvc_bool device_address); + +/* Obsolete, use is_shader_input_used. */ SPVC_PUBLIC_API spvc_bool spvc_compiler_msl_is_vertex_attribute_used(spvc_compiler compiler, unsigned location); +SPVC_PUBLIC_API spvc_bool spvc_compiler_msl_is_shader_input_used(spvc_compiler compiler, unsigned location); + SPVC_PUBLIC_API spvc_bool spvc_compiler_msl_is_resource_used(spvc_compiler compiler, SpvExecutionModel model, unsigned set, @@ -718,6 +788,9 @@ SPVC_PUBLIC_API spvc_result spvc_compiler_msl_add_dynamic_buffer(spvc_compiler c SPVC_PUBLIC_API spvc_result spvc_compiler_msl_add_inline_uniform_block(spvc_compiler compiler, unsigned desc_set, unsigned binding); +SPVC_PUBLIC_API spvc_result spvc_compiler_msl_set_combined_sampler_suffix(spvc_compiler compiler, const char *suffix); +SPVC_PUBLIC_API const char *spvc_compiler_msl_get_combined_sampler_suffix(spvc_compiler compiler); + /* * Reflect resources. * Maps almost 1:1 to C++ API. diff --git a/src/libraries/spirv_cross/spirv_cross_containers.hpp b/src/libraries/spirv_cross/spirv_cross_containers.hpp index f5760a0e0..b9c3737c9 100644 --- a/src/libraries/spirv_cross/spirv_cross_containers.hpp +++ b/src/libraries/spirv_cross/spirv_cross_containers.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 Hans-Kristian Arntzen + * Copyright 2019-2021 Hans-Kristian Arntzen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #ifndef SPIRV_CROSS_CONTAINERS_HPP #define SPIRV_CROSS_CONTAINERS_HPP @@ -63,7 +70,8 @@ public: private: #if defined(_MSC_VER) && _MSC_VER < 1900 // MSVC 2013 workarounds, sigh ... - union { + union + { char aligned_char[sizeof(T) * N]; double dummy_aligner; } u; @@ -211,6 +219,10 @@ public: this->buffer_size = count; } + SmallVector(std::initializer_list init) SPIRV_CROSS_NOEXCEPT : SmallVector(init.begin(), init.end()) + { + } + SmallVector(SmallVector &&other) SPIRV_CROSS_NOEXCEPT : SmallVector() { *this = std::move(other); @@ -327,8 +339,9 @@ public: size_t target_capacity = buffer_capacity; if (target_capacity == 0) target_capacity = 1; - if (target_capacity < N) - target_capacity = N; + + // Weird parens works around macro issues on Windows if NOMINMAX is not used. + target_capacity = (std::max)(target_capacity, N); // Need to ensure there is a POT value of target capacity which is larger than count, // otherwise this will overflow. diff --git a/src/libraries/spirv_cross/spirv_cross_error_handling.hpp b/src/libraries/spirv_cross/spirv_cross_error_handling.hpp index fa90610b5..32af107eb 100644 --- a/src/libraries/spirv_cross/spirv_cross_error_handling.hpp +++ b/src/libraries/spirv_cross/spirv_cross_error_handling.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 Arm Limited + * Copyright 2015-2021 Arm Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #ifndef SPIRV_CROSS_ERROR_HANDLING #define SPIRV_CROSS_ERROR_HANDLING diff --git a/src/libraries/spirv_cross/spirv_cross_parsed_ir.cpp b/src/libraries/spirv_cross/spirv_cross_parsed_ir.cpp index 655713f8c..e1a13c5e0 100644 --- a/src/libraries/spirv_cross/spirv_cross_parsed_ir.cpp +++ b/src/libraries/spirv_cross/spirv_cross_parsed_ir.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 Arm Limited + * Copyright 2018-2021 Arm Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #include "spirv_cross_parsed_ir.hpp" #include #include @@ -74,6 +81,8 @@ ParsedIR &ParsedIR::operator=(ParsedIR &&other) SPIRV_CROSS_NOEXCEPT source = other.source; loop_iteration_depth_hard = other.loop_iteration_depth_hard; loop_iteration_depth_soft = other.loop_iteration_depth_soft; + + meta_needing_name_fixup = std::move(other.meta_needing_name_fixup); } return *this; } @@ -106,6 +115,8 @@ ParsedIR &ParsedIR::operator=(const ParsedIR &other) addressing_model = other.addressing_model; memory_model = other.memory_model; + meta_needing_name_fixup = other.meta_needing_name_fixup; + // Very deliberate copying of IDs. There is no default copy constructor, nor a simple default constructor. // Construct object first so we have the correct allocator set-up, then we can copy object into our new pool group. ids.clear(); @@ -134,42 +145,151 @@ static bool is_alpha(char c) return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } -static bool is_alphanumeric(char c) +static bool is_numeric(char c) { - return is_alpha(c) || (c >= '0' && c <= '9'); + return c >= '0' && c <= '9'; } -static string ensure_valid_identifier(const string &name, bool member) +static bool is_alphanumeric(char c) +{ + return is_alpha(c) || is_numeric(c); +} + +static bool is_valid_identifier(const string &name) +{ + if (name.empty()) + return true; + + if (is_numeric(name[0])) + return false; + + for (auto c : name) + if (!is_alphanumeric(c) && c != '_') + return false; + + bool saw_underscore = false; + // Two underscores in a row is not a valid identifier either. + // Technically reserved, but it's easier to treat it as invalid. + for (auto c : name) + { + bool is_underscore = c == '_'; + if (is_underscore && saw_underscore) + return false; + saw_underscore = is_underscore; + } + + return true; +} + +static bool is_reserved_prefix(const string &name) +{ + // Generic reserved identifiers used by the implementation. + return name.compare(0, 3, "gl_", 3) == 0 || + // Ignore this case for now, might rewrite internal code to always use spv prefix. + //name.compare(0, 11, "SPIRV_Cross", 11) == 0 || + name.compare(0, 3, "spv", 3) == 0; +} + +static bool is_reserved_identifier(const string &name, bool member, bool allow_reserved_prefixes) +{ + if (!allow_reserved_prefixes && is_reserved_prefix(name)) + return true; + + if (member) + { + // Reserved member identifiers come in one form: + // _m[0-9]+$. + if (name.size() < 3) + return false; + + if (name.compare(0, 2, "_m", 2) != 0) + return false; + + size_t index = 2; + while (index < name.size() && is_numeric(name[index])) + index++; + + return index == name.size(); + } + else + { + // Reserved non-member identifiers come in two forms: + // _[0-9]+$, used for temporaries which map directly to a SPIR-V ID. + // _[0-9]+_, used for auxillary temporaries which derived from a SPIR-V ID. + if (name.size() < 2) + return false; + + if (name[0] != '_' || !is_numeric(name[1])) + return false; + + size_t index = 2; + while (index < name.size() && is_numeric(name[index])) + index++; + + return index == name.size() || (index < name.size() && name[index] == '_'); + } +} + +bool ParsedIR::is_globally_reserved_identifier(std::string &str, bool allow_reserved_prefixes) +{ + return is_reserved_identifier(str, false, allow_reserved_prefixes); +} + +uint32_t ParsedIR::get_spirv_version() const +{ + return spirv[1]; +} + +static string make_unreserved_identifier(const string &name) +{ + if (is_reserved_prefix(name)) + return "_RESERVED_IDENTIFIER_FIXUP_" + name; + else + return "_RESERVED_IDENTIFIER_FIXUP" + name; +} + +void ParsedIR::sanitize_underscores(std::string &str) +{ + // Compact adjacent underscores to make it valid. + auto dst = str.begin(); + auto src = dst; + bool saw_underscore = false; + while (src != str.end()) + { + bool is_underscore = *src == '_'; + if (saw_underscore && is_underscore) + { + src++; + } + else + { + if (dst != src) + *dst = *src; + dst++; + src++; + saw_underscore = is_underscore; + } + } + str.erase(dst, str.end()); +} + +static string ensure_valid_identifier(const string &name) { // Functions in glslangValidator are mangled with name( stuff. // Normally, we would never see '(' in any legal identifiers, so just strip them out. auto str = name.substr(0, name.find('(')); - for (uint32_t i = 0; i < str.size(); i++) - { - auto &c = str[i]; + if (str.empty()) + return str; - if (member) - { - // _m variables are reserved by the internal implementation, - // otherwise, make sure the name is a valid identifier. - if (i == 0) - c = is_alpha(c) ? c : '_'; - else if (i == 2 && str[0] == '_' && str[1] == 'm') - c = is_alpha(c) ? c : '_'; - else - c = is_alphanumeric(c) ? c : '_'; - } - else - { - // _ variables are reserved by the internal implementation, - // otherwise, make sure the name is a valid identifier. - if (i == 0 || (str[0] == '_' && i == 1)) - c = is_alpha(c) ? c : '_'; - else - c = is_alphanumeric(c) ? c : '_'; - } - } + if (is_numeric(str[0])) + str[0] = '_'; + + for (auto &c : str) + if (!is_alphanumeric(c) && c != '_') + c = '_'; + + ParsedIR::sanitize_underscores(str); return str; } @@ -195,35 +315,41 @@ const string &ParsedIR::get_member_name(TypeID id, uint32_t index) const return empty_string; } +void ParsedIR::sanitize_identifier(std::string &name, bool member, bool allow_reserved_prefixes) +{ + if (!is_valid_identifier(name)) + name = ensure_valid_identifier(name); + if (is_reserved_identifier(name, member, allow_reserved_prefixes)) + name = make_unreserved_identifier(name); +} + +void ParsedIR::fixup_reserved_names() +{ + for (uint32_t id : meta_needing_name_fixup) + { + auto &m = meta[id]; + sanitize_identifier(m.decoration.alias, false, false); + for (auto &memb : m.members) + sanitize_identifier(memb.alias, true, false); + } + meta_needing_name_fixup.clear(); +} + void ParsedIR::set_name(ID id, const string &name) { - auto &str = meta[id].decoration.alias; - str.clear(); - - if (name.empty()) - return; - - // Reserved for temporaries. - if (name[0] == '_' && name.size() >= 2 && isdigit(name[1])) - return; - - str = ensure_valid_identifier(name, false); + auto &m = meta[id]; + m.decoration.alias = name; + if (!is_valid_identifier(name) || is_reserved_identifier(name, false, false)) + meta_needing_name_fixup.insert(id); } void ParsedIR::set_member_name(TypeID id, uint32_t index, const string &name) { - meta[id].members.resize(max(meta[id].members.size(), size_t(index) + 1)); - - auto &str = meta[id].members[index].alias; - str.clear(); - if (name.empty()) - return; - - // Reserved for unnamed members. - if (name[0] == '_' && name.size() >= 3 && name[1] == 'm' && isdigit(name[2])) - return; - - str = ensure_valid_identifier(name, true); + auto &m = meta[id]; + m.members.resize(max(meta[id].members.size(), size_t(index) + 1)); + m.members[index].alias = name; + if (!is_valid_identifier(name) || is_reserved_identifier(name, true, false)) + meta_needing_name_fixup.insert(id); } void ParsedIR::set_decoration_string(ID id, Decoration decoration, const string &argument) @@ -274,6 +400,10 @@ void ParsedIR::set_decoration(ID id, Decoration decoration, uint32_t argument) dec.xfb_stride = argument; break; + case DecorationStream: + dec.stream = argument; + break; + case DecorationArrayStride: dec.array_stride = argument; break; @@ -353,6 +483,10 @@ void ParsedIR::set_member_decoration(TypeID id, uint32_t index, Decoration decor dec.xfb_stride = argument; break; + case DecorationStream: + dec.stream = argument; + break; + case DecorationSpecId: dec.spec_id = argument; break; @@ -404,6 +538,17 @@ void ParsedIR::mark_used_as_array_length(ID id) } } +Bitset ParsedIR::get_buffer_block_type_flags(const SPIRType &type) const +{ + if (type.member_types.empty()) + return {}; + + Bitset all_members_flags = get_member_decoration_bitset(type.self, 0); + for (uint32_t i = 1; i < uint32_t(type.member_types.size()); i++) + all_members_flags.merge_and(get_member_decoration_bitset(type.self, i)); + return all_members_flags; +} + Bitset ParsedIR::get_buffer_block_flags(const SPIRVariable &var) const { auto &type = get(var.basetype); @@ -420,10 +565,7 @@ Bitset ParsedIR::get_buffer_block_flags(const SPIRVariable &var) const if (type.member_types.empty()) return base_flags; - Bitset all_members_flags = get_member_decoration_bitset(type.self, 0); - for (uint32_t i = 1; i < uint32_t(type.member_types.size()); i++) - all_members_flags.merge_and(get_member_decoration_bitset(type.self, i)); - + auto all_members_flags = get_buffer_block_type_flags(type); base_flags.merge_or(all_members_flags); return base_flags; } @@ -470,6 +612,8 @@ uint32_t ParsedIR::get_decoration(ID id, Decoration decoration) const return dec.xfb_buffer; case DecorationXfbStride: return dec.xfb_stride; + case DecorationStream: + return dec.stream; case DecorationBinding: return dec.binding; case DecorationDescriptorSet: @@ -542,6 +686,10 @@ void ParsedIR::unset_decoration(ID id, Decoration decoration) dec.xfb_stride = 0; break; + case DecorationStream: + dec.stream = 0; + break; + case DecorationBinding: dec.binding = 0; break; @@ -616,6 +764,8 @@ uint32_t ParsedIR::get_member_decoration(TypeID id, uint32_t index, Decoration d return dec.xfb_buffer; case DecorationXfbStride: return dec.xfb_stride; + case DecorationStream: + return dec.stream; case DecorationSpecId: return dec.spec_id; case DecorationIndex: @@ -712,6 +862,10 @@ void ParsedIR::unset_member_decoration(TypeID id, uint32_t index, Decoration dec dec.xfb_stride = 0; break; + case DecorationStream: + dec.stream = 0; + break; + case DecorationSpecId: dec.spec_id = 0; break; diff --git a/src/libraries/spirv_cross/spirv_cross_parsed_ir.hpp b/src/libraries/spirv_cross/spirv_cross_parsed_ir.hpp index 4880c8419..88a069196 100644 --- a/src/libraries/spirv_cross/spirv_cross_parsed_ir.hpp +++ b/src/libraries/spirv_cross/spirv_cross_parsed_ir.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 Arm Limited + * Copyright 2018-2021 Arm Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #ifndef SPIRV_CROSS_PARSED_IR_HPP #define SPIRV_CROSS_PARSED_IR_HPP @@ -139,6 +146,7 @@ public: void mark_used_as_array_length(ID id); uint32_t increase_bound_by(uint32_t count); Bitset get_buffer_block_flags(const SPIRVariable &var) const; + Bitset get_buffer_block_type_flags(const SPIRType &type) const; void add_typed_id(Types type, ID id); void remove_typed_id(Types type, ID id); @@ -208,6 +216,14 @@ public: void make_constant_null(uint32_t id, uint32_t type, bool add_to_typed_id_set); + void fixup_reserved_names(); + + static void sanitize_underscores(std::string &str); + static void sanitize_identifier(std::string &str, bool member, bool allow_reserved_prefixes); + static bool is_globally_reserved_identifier(std::string &str, bool allow_reserved_prefixes); + + uint32_t get_spirv_version() const; + private: template T &get(uint32_t id) @@ -225,6 +241,8 @@ private: mutable uint32_t loop_iteration_depth_soft = 0; std::string empty_string; Bitset cleared_bitset; + + std::unordered_set meta_needing_name_fixup; }; } // namespace SPIRV_CROSS_NAMESPACE diff --git a/src/libraries/spirv_cross/spirv_cross_util.cpp b/src/libraries/spirv_cross/spirv_cross_util.cpp index cfad676ec..9ce6d4fb7 100644 --- a/src/libraries/spirv_cross/spirv_cross_util.cpp +++ b/src/libraries/spirv_cross/spirv_cross_util.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 Arm Limited + * Copyright 2015-2021 Arm Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #include "spirv_cross_util.hpp" #include "spirv_common.hpp" diff --git a/src/libraries/spirv_cross/spirv_cross_util.hpp b/src/libraries/spirv_cross/spirv_cross_util.hpp index 708b7cb56..daa4a7900 100644 --- a/src/libraries/spirv_cross/spirv_cross_util.hpp +++ b/src/libraries/spirv_cross/spirv_cross_util.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 Arm Limited + * Copyright 2015-2021 Arm Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #ifndef SPIRV_CROSS_UTIL_HPP #define SPIRV_CROSS_UTIL_HPP diff --git a/src/libraries/spirv_cross/spirv_glsl.cpp b/src/libraries/spirv_cross/spirv_glsl.cpp index fb9f37cb0..31ff85042 100644 --- a/src/libraries/spirv_cross/spirv_glsl.cpp +++ b/src/libraries/spirv_cross/spirv_glsl.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 Arm Limited + * Copyright 2015-2021 Arm Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #include "spirv_glsl.hpp" #include "GLSL.std.450.h" #include "spirv_common.hpp" @@ -33,6 +40,13 @@ using namespace spv; using namespace SPIRV_CROSS_NAMESPACE; using namespace std; +enum ExtraSubExpressionType +{ + // Create masks above any legal ID range to allow multiple address spaces into the extra_sub_expressions map. + EXTRA_SUB_EXPRESSION_TYPE_STREAM_OFFSET = 0x10000000, + EXTRA_SUB_EXPRESSION_TYPE_AUX = 0x20000000 +}; + static bool is_unsigned_opcode(Op op) { // Don't have to be exhaustive, only relevant for legacy target checking ... @@ -145,32 +159,6 @@ static BufferPackingStandard packing_to_substruct_packing(BufferPackingStandard } } -// Sanitizes underscores for GLSL where multiple underscores in a row are not allowed. -string CompilerGLSL::sanitize_underscores(const string &str) -{ - string res; - res.reserve(str.size()); - - bool last_underscore = false; - for (auto c : str) - { - if (c == '_') - { - if (last_underscore) - continue; - - res += c; - last_underscore = true; - } - else - { - res += c; - last_underscore = false; - } - } - return res; -} - void CompilerGLSL::init() { if (ir.source.known) @@ -341,6 +329,7 @@ void CompilerGLSL::reset() statement_count = 0; indent = 0; + current_loop_level = 0; } void CompilerGLSL::remap_pls_variables() @@ -445,15 +434,37 @@ void CompilerGLSL::find_static_extensions() require_extension_internal("GL_ARB_tessellation_shader"); break; - case ExecutionModelRayGenerationNV: - case ExecutionModelIntersectionNV: - case ExecutionModelAnyHitNV: - case ExecutionModelClosestHitNV: - case ExecutionModelMissNV: - case ExecutionModelCallableNV: + case ExecutionModelRayGenerationKHR: + case ExecutionModelIntersectionKHR: + case ExecutionModelAnyHitKHR: + case ExecutionModelClosestHitKHR: + case ExecutionModelMissKHR: + case ExecutionModelCallableKHR: + // NV enums are aliases. if (options.es || options.version < 460) SPIRV_CROSS_THROW("Ray tracing shaders require non-es profile with version 460 or above."); - require_extension_internal("GL_NV_ray_tracing"); + if (!options.vulkan_semantics) + SPIRV_CROSS_THROW("Ray tracing requires Vulkan semantics."); + + // Need to figure out if we should target KHR or NV extension based on capabilities. + for (auto &cap : ir.declared_capabilities) + { + if (cap == CapabilityRayTracingKHR || cap == CapabilityRayQueryKHR) + { + ray_tracing_is_khr = true; + break; + } + } + + if (ray_tracing_is_khr) + { + // In KHR ray tracing we pass payloads by pointer instead of location, + // so make sure we assign locations properly. + ray_tracing_khr_fixup_locations(); + require_extension_internal("GL_EXT_ray_tracing"); + } + else + require_extension_internal("GL_NV_ray_tracing"); break; default: @@ -520,25 +531,49 @@ void CompilerGLSL::find_static_extensions() } break; + case CapabilityVariablePointers: + case CapabilityVariablePointersStorageBuffer: + SPIRV_CROSS_THROW("VariablePointers capability is not supported in GLSL."); + default: break; } } } +void CompilerGLSL::ray_tracing_khr_fixup_locations() +{ + uint32_t location = 0; + ir.for_each_typed_id([&](uint32_t, SPIRVariable &var) { + // Incoming payload storage can also be used for tracing. + if (var.storage != StorageClassRayPayloadKHR && var.storage != StorageClassCallableDataKHR && + var.storage != StorageClassIncomingRayPayloadKHR && var.storage != StorageClassIncomingCallableDataKHR) + return; + if (is_hidden_variable(var)) + return; + set_decoration(var.self, DecorationLocation, location++); + }); +} + string CompilerGLSL::compile() { + ir.fixup_reserved_names(); + if (options.vulkan_semantics) backend.allow_precision_qualifiers = true; else { // only NV_gpu_shader5 supports divergent indexing on OpenGL, and it does so without extra qualifiers backend.nonuniform_qualifier = ""; + backend.needs_row_major_load_workaround = true; } backend.force_gl_in_out_block = true; backend.supports_extensions = true; backend.use_array_constructor = true; + if (is_legacy_es()) + backend.support_case_fallthrough = false; + // Scan the SPIR-V to find trivial uses of extensions. fixup_type_alias(); reorder_type_alias(); @@ -568,6 +603,7 @@ string CompilerGLSL::compile() emit_header(); emit_resources(); + emit_extension_workarounds(get_execution_model()); emit_function(get(ir.default_entry_point), Bitset()); @@ -640,6 +676,21 @@ void CompilerGLSL::build_workgroup_size(SmallVector &arguments, const Sp arguments.push_back(join("local_size_z = ", execution.workgroup_size.z)); } +void CompilerGLSL::request_subgroup_feature(ShaderSubgroupSupportHelper::Feature feature) +{ + if (options.vulkan_semantics) + { + auto khr_extension = ShaderSubgroupSupportHelper::get_KHR_extension_for_feature(feature); + require_extension_internal(ShaderSubgroupSupportHelper::get_extension_name(khr_extension)); + } + else + { + if (!shader_subgroup_supporter.is_feature_requested(feature)) + force_recompile(); + shader_subgroup_supporter.request_feature(feature); + } +} + void CompilerGLSL::emit_header() { auto &execution = get_entry_point(); @@ -742,6 +793,45 @@ void CompilerGLSL::emit_header() statement("#extension ", ext, " : require"); } + if (!options.vulkan_semantics) + { + using Supp = ShaderSubgroupSupportHelper; + auto result = shader_subgroup_supporter.resolve(); + + for (uint32_t feature_index = 0; feature_index < Supp::FeatureCount; feature_index++) + { + auto feature = static_cast(feature_index); + if (!shader_subgroup_supporter.is_feature_requested(feature)) + continue; + + auto exts = Supp::get_candidates_for_feature(feature, result); + if (exts.empty()) + continue; + + statement(""); + + for (auto &ext : exts) + { + const char *name = Supp::get_extension_name(ext); + const char *extra_predicate = Supp::get_extra_required_extension_predicate(ext); + auto extra_names = Supp::get_extra_required_extension_names(ext); + statement(&ext != &exts.front() ? "#elif" : "#if", " defined(", name, ")", + (*extra_predicate != '\0' ? " && " : ""), extra_predicate); + for (const auto &e : extra_names) + statement("#extension ", e, " : enable"); + statement("#extension ", name, " : require"); + } + + if (!Supp::can_feature_be_implemented_without_extensions(feature)) + { + statement("#else"); + statement("#error No extensions available to emulate requested subgroup feature."); + } + + statement("#endif"); + } + } + for (auto &header : header_lines) statement(header); @@ -1290,24 +1380,22 @@ uint32_t CompilerGLSL::type_to_packed_array_stride(const SPIRType &type, const B auto &tmp = get(parent); uint32_t size = type_to_packed_size(tmp, flags, packing); - if (tmp.array.empty()) - { - uint32_t alignment = type_to_packed_alignment(type, flags, packing); - return (size + alignment - 1) & ~(alignment - 1); - } - else - { - // For multidimensional arrays, array stride always matches size of subtype. - // The alignment cannot change because multidimensional arrays are basically N * M array elements. - return size; - } + uint32_t alignment = type_to_packed_alignment(type, flags, packing); + return (size + alignment - 1) & ~(alignment - 1); } uint32_t CompilerGLSL::type_to_packed_size(const SPIRType &type, const Bitset &flags, BufferPackingStandard packing) { if (!type.array.empty()) { - return to_array_size_literal(type) * type_to_packed_array_stride(type, flags, packing); + uint32_t packed_size = to_array_size_literal(type) * type_to_packed_array_stride(type, flags, packing); + + // For arrays of vectors and matrices in HLSL, the last element has a size which depends on its vector size, + // so that it is possible to pack other vectors into the last element. + if (packing_is_hlsl(packing) && type.basetype != SPIRType::Struct) + packed_size -= (4 - type.vecsize) * (type.width / 8); + + return packed_size; } // If using PhysicalStorageBufferEXT storage class, this is a pointer, @@ -1380,6 +1468,11 @@ uint32_t CompilerGLSL::type_to_packed_size(const SPIRType &type, const Bitset &f else size = type.vecsize * type.columns * base_alignment; } + + // For matrices in HLSL, the last element has a size which depends on its vector size, + // so that it is possible to pack other vectors into the last element. + if (packing_is_hlsl(packing) && type.columns > 1) + size -= (4 - type.vecsize) * (type.width / 8); } } @@ -1432,7 +1525,7 @@ bool CompilerGLSL::buffer_is_packing_standard(const SPIRType &type, BufferPackin is_top_level_block && size_t(i + 1) == type.member_types.size() && !memb_type.array.empty(); uint32_t packed_size = 0; - if (!member_can_be_unsized) + if (!member_can_be_unsized || packing_is_hlsl(packing)) packed_size = type_to_packed_size(memb_type, member_flags, packing); // We only need to care about this if we have non-array types which can straddle the vec4 boundary. @@ -1445,13 +1538,14 @@ bool CompilerGLSL::buffer_is_packing_standard(const SPIRType &type, BufferPackin packed_alignment = max(packed_alignment, 16u); } + uint32_t actual_offset = type_struct_member_offset(type, i); + // Field is not in the specified range anymore and we can ignore any further fields. + if (actual_offset >= end_offset) + break; + uint32_t alignment = max(packed_alignment, pad_alignment); offset = (offset + alignment - 1) & ~(alignment - 1); - // Field is not in the specified range anymore and we can ignore any further fields. - if (offset >= end_offset) - break; - // The next member following a struct member is aligned to the base alignment of the struct that came before. // GL 4.5 spec, 7.6.2.2. if (memb_type.basetype == SPIRType::Struct && !memb_type.pointer) @@ -1460,10 +1554,8 @@ bool CompilerGLSL::buffer_is_packing_standard(const SPIRType &type, BufferPackin pad_alignment = 1; // Only care about packing if we are in the given range - if (offset >= start_offset) + if (actual_offset >= start_offset) { - uint32_t actual_offset = type_struct_member_offset(type, i); - // We only care about offsets in std140, std430, etc ... // For EnhancedLayout variants, we have the flexibility to choose our own offsets. if (!packing_has_flexible_offset(packing)) @@ -1506,7 +1598,7 @@ bool CompilerGLSL::buffer_is_packing_standard(const SPIRType &type, BufferPackin } // Bump size. - offset += packed_size; + offset = actual_offset + packed_size; } return true; @@ -1571,8 +1663,8 @@ string CompilerGLSL::layout_for_variable(const SPIRVariable &var) if (options.vulkan_semantics && var.storage == StorageClassPushConstant) attr.push_back("push_constant"); - else if (var.storage == StorageClassShaderRecordBufferNV) - attr.push_back("shaderRecordNV"); + else if (var.storage == StorageClassShaderRecordBufferKHR) + attr.push_back(ray_tracing_is_khr ? "shaderRecordEXT" : "shaderRecordNV"); if (flags.get(DecorationRowMajor)) attr.push_back("row_major"); @@ -1608,7 +1700,8 @@ string CompilerGLSL::layout_for_variable(const SPIRVariable &var) uint32_t member_count = uint32_t(type.member_types.size()); bool have_xfb_buffer_stride = false; bool have_any_xfb_offset = false; - uint32_t xfb_stride = 0, xfb_buffer = 0; + bool have_geom_stream = false; + uint32_t xfb_stride = 0, xfb_buffer = 0, geom_stream = 0; if (flags.get(DecorationXfbBuffer) && flags.get(DecorationXfbStride)) { @@ -1617,9 +1710,24 @@ string CompilerGLSL::layout_for_variable(const SPIRVariable &var) xfb_stride = get_decoration(var.self, DecorationXfbStride); } + if (flags.get(DecorationStream)) + { + have_geom_stream = true; + geom_stream = get_decoration(var.self, DecorationStream); + } + // Verify that none of the members violate our assumption. for (uint32_t i = 0; i < member_count; i++) { + if (has_member_decoration(type.self, i, DecorationStream)) + { + uint32_t member_geom_stream = get_member_decoration(type.self, i, DecorationStream); + if (have_geom_stream && member_geom_stream != geom_stream) + SPIRV_CROSS_THROW("IO block member Stream mismatch."); + have_geom_stream = true; + geom_stream = member_geom_stream; + } + // Only members with an Offset decoration participate in XFB. if (!has_member_decoration(type.self, i, DecorationOffset)) continue; @@ -1650,15 +1758,39 @@ string CompilerGLSL::layout_for_variable(const SPIRVariable &var) attr.push_back(join("xfb_stride = ", xfb_stride)); uses_enhanced_layouts = true; } + + if (have_geom_stream) + { + if (get_execution_model() != ExecutionModelGeometry) + SPIRV_CROSS_THROW("Geometry streams can only be used in geometry shaders."); + if (options.es) + SPIRV_CROSS_THROW("Multiple geometry streams not supported in ESSL."); + if (options.version < 400) + require_extension_internal("GL_ARB_transform_feedback3"); + attr.push_back(join("stream = ", get_decoration(var.self, DecorationStream))); + } } - else if (var.storage == StorageClassOutput && flags.get(DecorationXfbBuffer) && flags.get(DecorationXfbStride) && - flags.get(DecorationOffset)) + else if (var.storage == StorageClassOutput) { - // XFB for standalone variables, we can emit all decorations. - attr.push_back(join("xfb_buffer = ", get_decoration(var.self, DecorationXfbBuffer))); - attr.push_back(join("xfb_stride = ", get_decoration(var.self, DecorationXfbStride))); - attr.push_back(join("xfb_offset = ", get_decoration(var.self, DecorationOffset))); - uses_enhanced_layouts = true; + if (flags.get(DecorationXfbBuffer) && flags.get(DecorationXfbStride) && flags.get(DecorationOffset)) + { + // XFB for standalone variables, we can emit all decorations. + attr.push_back(join("xfb_buffer = ", get_decoration(var.self, DecorationXfbBuffer))); + attr.push_back(join("xfb_stride = ", get_decoration(var.self, DecorationXfbStride))); + attr.push_back(join("xfb_offset = ", get_decoration(var.self, DecorationOffset))); + uses_enhanced_layouts = true; + } + + if (flags.get(DecorationStream)) + { + if (get_execution_model() != ExecutionModelGeometry) + SPIRV_CROSS_THROW("Geometry streams can only be used in geometry shaders."); + if (options.es) + SPIRV_CROSS_THROW("Multiple geometry streams not supported in ESSL."); + if (options.version < 400) + require_extension_internal("GL_ARB_transform_feedback3"); + attr.push_back(join("stream = ", get_decoration(var.self, DecorationStream))); + } } // Can only declare Component if we can declare location. @@ -1688,14 +1820,14 @@ string CompilerGLSL::layout_for_variable(const SPIRVariable &var) // Do not emit set = decoration in regular GLSL output, but // we need to preserve it in Vulkan GLSL mode. - if (var.storage != StorageClassPushConstant && var.storage != StorageClassShaderRecordBufferNV) + if (var.storage != StorageClassPushConstant && var.storage != StorageClassShaderRecordBufferKHR) { if (flags.get(DecorationDescriptorSet) && options.vulkan_semantics) attr.push_back(join("set = ", get_decoration(var.self, DecorationDescriptorSet))); } bool push_constant_block = options.vulkan_semantics && var.storage == StorageClassPushConstant; - bool ssbo_block = var.storage == StorageClassStorageBuffer || var.storage == StorageClassShaderRecordBufferNV || + bool ssbo_block = var.storage == StorageClassStorageBuffer || var.storage == StorageClassShaderRecordBufferKHR || (var.storage == StorageClassUniform && typeflags.get(DecorationBufferBlock)); bool emulated_ubo = var.storage == StorageClassPushConstant && options.emit_push_constant_as_uniform_buffer; bool ubo_block = var.storage == StorageClassUniform && typeflags.get(DecorationBlock); @@ -1717,7 +1849,7 @@ string CompilerGLSL::layout_for_variable(const SPIRVariable &var) if (!can_use_buffer_blocks && var.storage == StorageClassUniform) can_use_binding = false; - if (var.storage == StorageClassShaderRecordBufferNV) + if (var.storage == StorageClassShaderRecordBufferKHR) can_use_binding = false; if (can_use_binding && flags.get(DecorationBinding)) @@ -1934,6 +2066,9 @@ void CompilerGLSL::emit_buffer_reference_block(SPIRType &type, bool forward_decl block_names.insert(buffer_name); block_ssbo_names.insert(buffer_name); + + // Ensure we emit the correct name when emitting non-forward pointer type. + ir.meta[type.self].decoration.alias = buffer_name; } else if (type.basetype != SPIRType::Struct) buffer_name = type_to_glsl(type); @@ -1943,7 +2078,20 @@ void CompilerGLSL::emit_buffer_reference_block(SPIRType &type, bool forward_decl if (!forward_declaration) { if (type.basetype == SPIRType::Struct) - statement("layout(buffer_reference, ", buffer_to_packing_standard(type, true), ") buffer ", buffer_name); + { + auto flags = ir.get_buffer_block_type_flags(type); + string decorations; + if (flags.get(DecorationRestrict)) + decorations += " restrict"; + if (flags.get(DecorationCoherent)) + decorations += " coherent"; + if (flags.get(DecorationNonReadable)) + decorations += " writeonly"; + if (flags.get(DecorationNonWritable)) + decorations += " readonly"; + statement("layout(buffer_reference, ", buffer_to_packing_standard(type, true), + ")", decorations, " buffer ", buffer_name); + } else statement("layout(buffer_reference) buffer ", buffer_name); @@ -1981,7 +2129,7 @@ void CompilerGLSL::emit_buffer_block_native(const SPIRVariable &var) auto &type = get(var.basetype); Bitset flags = ir.get_buffer_block_flags(var); - bool ssbo = var.storage == StorageClassStorageBuffer || var.storage == StorageClassShaderRecordBufferNV || + bool ssbo = var.storage == StorageClassStorageBuffer || var.storage == StorageClassShaderRecordBufferKHR || ir.meta[type.self].decoration.decoration_flags.get(DecorationBufferBlock); bool is_restrict = ssbo && flags.get(DecorationRestrict); bool is_writeonly = ssbo && flags.get(DecorationNonReadable); @@ -2096,74 +2244,143 @@ const char *CompilerGLSL::to_storage_qualifiers_glsl(const SPIRVariable &var) { return "uniform "; } - else if (var.storage == StorageClassRayPayloadNV) + else if (var.storage == StorageClassRayPayloadKHR) { - return "rayPayloadNV "; + return ray_tracing_is_khr ? "rayPayloadEXT " : "rayPayloadNV "; } - else if (var.storage == StorageClassIncomingRayPayloadNV) + else if (var.storage == StorageClassIncomingRayPayloadKHR) { - return "rayPayloadInNV "; + return ray_tracing_is_khr ? "rayPayloadInEXT " : "rayPayloadInNV "; } - else if (var.storage == StorageClassHitAttributeNV) + else if (var.storage == StorageClassHitAttributeKHR) { - return "hitAttributeNV "; + return ray_tracing_is_khr ? "hitAttributeEXT " : "hitAttributeNV "; } - else if (var.storage == StorageClassCallableDataNV) + else if (var.storage == StorageClassCallableDataKHR) { - return "callableDataNV "; + return ray_tracing_is_khr ? "callableDataEXT " : "callableDataNV "; } - else if (var.storage == StorageClassIncomingCallableDataNV) + else if (var.storage == StorageClassIncomingCallableDataKHR) { - return "callableDataInNV "; + return ray_tracing_is_khr ? "callableDataInEXT " : "callableDataInNV "; } return ""; } +void CompilerGLSL::emit_flattened_io_block_member(const std::string &basename, const SPIRType &type, const char *qual, + const SmallVector &indices) +{ + uint32_t member_type_id = type.self; + const SPIRType *member_type = &type; + const SPIRType *parent_type = nullptr; + auto flattened_name = basename; + for (auto &index : indices) + { + flattened_name += "_"; + flattened_name += to_member_name(*member_type, index); + parent_type = member_type; + member_type_id = member_type->member_types[index]; + member_type = &get(member_type_id); + } + + assert(member_type->basetype != SPIRType::Struct); + + // We're overriding struct member names, so ensure we do so on the primary type. + if (parent_type->type_alias) + parent_type = &get(parent_type->type_alias); + + // Sanitize underscores because joining the two identifiers might create more than 1 underscore in a row, + // which is not allowed. + ParsedIR::sanitize_underscores(flattened_name); + + uint32_t last_index = indices.back(); + + // Pass in the varying qualifier here so it will appear in the correct declaration order. + // Replace member name while emitting it so it encodes both struct name and member name. + auto backup_name = get_member_name(parent_type->self, last_index); + auto member_name = to_member_name(*parent_type, last_index); + set_member_name(parent_type->self, last_index, flattened_name); + emit_struct_member(*parent_type, member_type_id, last_index, qual); + // Restore member name. + set_member_name(parent_type->self, last_index, member_name); +} + +void CompilerGLSL::emit_flattened_io_block_struct(const std::string &basename, const SPIRType &type, const char *qual, + const SmallVector &indices) +{ + auto sub_indices = indices; + sub_indices.push_back(0); + + const SPIRType *member_type = &type; + for (auto &index : indices) + member_type = &get(member_type->member_types[index]); + + assert(member_type->basetype == SPIRType::Struct); + + if (!member_type->array.empty()) + SPIRV_CROSS_THROW("Cannot flatten array of structs in I/O blocks."); + + for (uint32_t i = 0; i < uint32_t(member_type->member_types.size()); i++) + { + sub_indices.back() = i; + if (get(member_type->member_types[i]).basetype == SPIRType::Struct) + emit_flattened_io_block_struct(basename, type, qual, sub_indices); + else + emit_flattened_io_block_member(basename, type, qual, sub_indices); + } +} + void CompilerGLSL::emit_flattened_io_block(const SPIRVariable &var, const char *qual) { - auto &type = get(var.basetype); - if (!type.array.empty()) + auto &var_type = get(var.basetype); + if (!var_type.array.empty()) SPIRV_CROSS_THROW("Array of varying structs cannot be flattened to legacy-compatible varyings."); + // Emit flattened types based on the type alias. Normally, we are never supposed to emit + // struct declarations for aliased types. + auto &type = var_type.type_alias ? get(var_type.type_alias) : var_type; + auto old_flags = ir.meta[type.self].decoration.decoration_flags; // Emit the members as if they are part of a block to get all qualifiers. ir.meta[type.self].decoration.decoration_flags.set(DecorationBlock); type.member_name_cache.clear(); + SmallVector member_indices; + member_indices.push_back(0); + auto basename = to_name(var.self); + uint32_t i = 0; for (auto &member : type.member_types) { add_member_name(type, i); auto &membertype = get(member); + member_indices.back() = i; if (membertype.basetype == SPIRType::Struct) - SPIRV_CROSS_THROW("Cannot flatten struct inside structs in I/O variables."); - - // Pass in the varying qualifier here so it will appear in the correct declaration order. - // Replace member name while emitting it so it encodes both struct name and member name. - // Sanitize underscores because joining the two identifiers might create more than 1 underscore in a row, - // which is not allowed. - auto backup_name = get_member_name(type.self, i); - auto member_name = to_member_name(type, i); - set_member_name(type.self, i, sanitize_underscores(join(to_name(var.self), "_", member_name))); - emit_struct_member(type, member, i, qual); - // Restore member name. - set_member_name(type.self, i, member_name); + emit_flattened_io_block_struct(basename, type, qual, member_indices); + else + emit_flattened_io_block_member(basename, type, qual, member_indices); i++; } ir.meta[type.self].decoration.decoration_flags = old_flags; - // Treat this variable as flattened from now on. - flattened_structs.insert(var.self); + // Treat this variable as fully flattened from now on. + flattened_structs[var.self] = true; } void CompilerGLSL::emit_interface_block(const SPIRVariable &var) { auto &type = get(var.basetype); + if (var.storage == StorageClassInput && type.basetype == SPIRType::Double && + !options.es && options.version < 410) + { + require_extension_internal("GL_ARB_vertex_attrib_64bit"); + } + // Either make it plain in/out or in/out blocks depending on what shader is doing ... bool block = ir.meta[type.self].decoration.decoration_flags.get(DecorationBlock); const char *qual = to_storage_qualifiers_glsl(var); @@ -2173,7 +2390,8 @@ void CompilerGLSL::emit_interface_block(const SPIRVariable &var) // ESSL earlier than 310 and GLSL earlier than 150 did not support // I/O variables which are struct types. // To support this, flatten the struct into separate varyings instead. - if ((options.es && options.version < 310) || (!options.es && options.version < 150)) + if (options.force_flattened_io_blocks || (options.es && options.version < 310) || + (!options.es && options.version < 150)) { // I/O blocks on ES require version 310 with Android Extension Pack extensions, or core version 320. // On desktop, I/O blocks were introduced with geometry shaders in GL 3.2 (GLSL 150). @@ -2188,6 +2406,9 @@ void CompilerGLSL::emit_interface_block(const SPIRVariable &var) require_extension_internal("GL_EXT_shader_io_blocks"); } + // Workaround to make sure we can emit "patch in/out" correctly. + fixup_io_block_patch_qualifiers(var); + // Block names should never alias. auto block_name = to_name(type.self, false); @@ -2209,7 +2430,8 @@ void CompilerGLSL::emit_interface_block(const SPIRVariable &var) // Instance names cannot alias block names. resource_names.insert(block_name); - statement(layout_for_variable(var), qual, block_name); + bool is_patch = has_decoration(var.self, DecorationPatch); + statement(layout_for_variable(var), (is_patch ? "patch " : ""), qual, block_name); begin_scope(); type.member_name_cache.clear(); @@ -2233,7 +2455,8 @@ void CompilerGLSL::emit_interface_block(const SPIRVariable &var) // I/O variables which are struct types. // To support this, flatten the struct into separate varyings instead. if (type.basetype == SPIRType::Struct && - ((options.es && options.version < 310) || (!options.es && options.version < 150))) + (options.force_flattened_io_blocks || (options.es && options.version < 310) || + (!options.es && options.version < 150))) { emit_flattened_io_block(var, qual); } @@ -2265,14 +2488,6 @@ void CompilerGLSL::emit_interface_block(const SPIRVariable &var) swap(type.array.back(), old_array_size); swap(type.array_size_literal.back(), old_array_size_literal); } - - // If a StorageClassOutput variable has an initializer, we need to initialize it in main(). - if (var.storage == StorageClassOutput && var.initializer) - { - auto &entry_func = this->get(ir.default_entry_point); - entry_func.fixup_hooks_in.push_back( - [&]() { statement(to_name(var.self), " = ", to_expression(var.initializer), ";"); }); - } } } } @@ -2375,7 +2590,17 @@ void CompilerGLSL::replace_illegal_names(const unordered_set &keywords) return; auto &m = meta->decoration; - if (m.alias.compare(0, 3, "gl_") == 0 || keywords.find(m.alias) != end(keywords)) + if (keywords.find(m.alias) != end(keywords)) + m.alias = join("_", m.alias); + }); + + ir.for_each_typed_id([&](uint32_t, const SPIRFunction &func) { + auto *meta = ir.find_meta(func.self); + if (!meta) + return; + + auto &m = meta->decoration; + if (keywords.find(m.alias) != end(keywords)) m.alias = join("_", m.alias); }); @@ -2385,11 +2610,11 @@ void CompilerGLSL::replace_illegal_names(const unordered_set &keywords) return; auto &m = meta->decoration; - if (m.alias.compare(0, 3, "gl_") == 0 || keywords.find(m.alias) != end(keywords)) + if (keywords.find(m.alias) != end(keywords)) m.alias = join("_", m.alias); for (auto &memb : meta->members) - if (memb.alias.compare(0, 3, "gl_") == 0 || keywords.find(memb.alias) != end(keywords)) + if (keywords.find(memb.alias) != end(keywords)) memb.alias = join("_", memb.alias); }); } @@ -2619,9 +2844,33 @@ bool CompilerGLSL::should_force_emit_builtin_block(StorageClass storage) } }); + // If we're declaring clip/cull planes with control points we need to force block declaration. + if (get_execution_model() == ExecutionModelTessellationControl && + (clip_distance_count || cull_distance_count)) + { + should_force = true; + } + return should_force; } +void CompilerGLSL::fixup_implicit_builtin_block_names() +{ + ir.for_each_typed_id([&](uint32_t, SPIRVariable &var) { + auto &type = this->get(var.basetype); + bool block = has_decoration(type.self, DecorationBlock); + if ((var.storage == StorageClassOutput || var.storage == StorageClassInput) && block && + is_builtin_variable(var)) + { + // Make sure the array has a supported name in the code. + if (var.storage == StorageClassOutput) + set_name(var.self, "gl_out"); + else if (var.storage == StorageClassInput) + set_name(var.self, "gl_in"); + } + }); +} + void CompilerGLSL::emit_declared_builtin_block(StorageClass storage, ExecutionModel model) { Bitset emitted_builtins; @@ -2636,8 +2885,9 @@ void CompilerGLSL::emit_declared_builtin_block(StorageClass storage, ExecutionMo uint32_t clip_distance_size = 0; bool have_xfb_buffer_stride = false; + bool have_geom_stream = false; bool have_any_xfb_offset = false; - uint32_t xfb_stride = 0, xfb_buffer = 0; + uint32_t xfb_stride = 0, xfb_buffer = 0, geom_stream = 0; std::unordered_map builtin_xfb_offsets; ir.for_each_typed_id([&](uint32_t, SPIRVariable &var) { @@ -2654,15 +2904,24 @@ void CompilerGLSL::emit_declared_builtin_block(StorageClass storage, ExecutionMo { builtins.set(m.builtin_type); if (m.builtin_type == BuiltInCullDistance) - cull_distance_size = this->get(type.member_types[index]).array.front(); + cull_distance_size = to_array_size_literal(this->get(type.member_types[index])); else if (m.builtin_type == BuiltInClipDistance) - clip_distance_size = this->get(type.member_types[index]).array.front(); + clip_distance_size = to_array_size_literal(this->get(type.member_types[index])); if (is_block_builtin(m.builtin_type) && m.decoration_flags.get(DecorationOffset)) { have_any_xfb_offset = true; builtin_xfb_offsets[m.builtin_type] = m.offset; } + + if (is_block_builtin(m.builtin_type) && m.decoration_flags.get(DecorationStream)) + { + uint32_t stream = m.stream; + if (have_geom_stream && geom_stream != stream) + SPIRV_CROSS_THROW("IO block member Stream mismatch."); + have_geom_stream = true; + geom_stream = stream; + } } index++; } @@ -2680,6 +2939,15 @@ void CompilerGLSL::emit_declared_builtin_block(StorageClass storage, ExecutionMo xfb_buffer = buffer_index; xfb_stride = stride; } + + if (storage == StorageClassOutput && has_decoration(var.self, DecorationStream)) + { + uint32_t stream = get_decoration(var.self, DecorationStream); + if (have_geom_stream && geom_stream != stream) + SPIRV_CROSS_THROW("IO block member Stream mismatch."); + have_geom_stream = true; + geom_stream = stream; + } } else if (var.storage == storage && !block && is_builtin_variable(var)) { @@ -2689,9 +2957,9 @@ void CompilerGLSL::emit_declared_builtin_block(StorageClass storage, ExecutionMo { global_builtins.set(m.builtin_type); if (m.builtin_type == BuiltInCullDistance) - cull_distance_size = type.array.front(); + cull_distance_size = to_array_size_literal(type); else if (m.builtin_type == BuiltInClipDistance) - clip_distance_size = type.array.front(); + clip_distance_size = to_array_size_literal(type); if (is_block_builtin(m.builtin_type) && m.decoration_flags.get(DecorationXfbStride) && m.decoration_flags.get(DecorationXfbBuffer) && m.decoration_flags.get(DecorationOffset)) @@ -2708,6 +2976,15 @@ void CompilerGLSL::emit_declared_builtin_block(StorageClass storage, ExecutionMo xfb_buffer = buffer_index; xfb_stride = stride; } + + if (is_block_builtin(m.builtin_type) && m.decoration_flags.get(DecorationStream)) + { + uint32_t stream = get_decoration(var.self, DecorationStream); + if (have_geom_stream && geom_stream != stream) + SPIRV_CROSS_THROW("IO block member Stream mismatch."); + have_geom_stream = true; + geom_stream = stream; + } } } @@ -2737,9 +3014,9 @@ void CompilerGLSL::emit_declared_builtin_block(StorageClass storage, ExecutionMo if (storage == StorageClassOutput) { + SmallVector attr; if (have_xfb_buffer_stride && have_any_xfb_offset) { - statement("layout(xfb_buffer = ", xfb_buffer, ", xfb_stride = ", xfb_stride, ") out gl_PerVertex"); if (!options.es) { if (options.version < 440 && options.version >= 140) @@ -2751,7 +3028,22 @@ void CompilerGLSL::emit_declared_builtin_block(StorageClass storage, ExecutionMo } else if (options.es) SPIRV_CROSS_THROW("Need GL_ARB_enhanced_layouts for xfb_stride or xfb_buffer."); + attr.push_back(join("xfb_buffer = ", xfb_buffer, ", xfb_stride = ", xfb_stride)); } + + if (have_geom_stream) + { + if (get_execution_model() != ExecutionModelGeometry) + SPIRV_CROSS_THROW("Geometry streams can only be used in geometry shaders."); + if (options.es) + SPIRV_CROSS_THROW("Multiple geometry streams not supported in ESSL."); + if (options.version < 400) + require_extension_internal("GL_ARB_transform_feedback3"); + attr.push_back(join("stream = ", geom_stream)); + } + + if (!attr.empty()) + statement("layout(", merge(attr), ") out gl_PerVertex"); else statement("out gl_PerVertex"); } @@ -2803,12 +3095,6 @@ void CompilerGLSL::emit_declared_builtin_block(StorageClass storage, ExecutionMo if (builtin_array) { - // Make sure the array has a supported name in the code. - if (storage == StorageClassOutput) - set_name(block_var->self, "gl_out"); - else if (storage == StorageClassInput) - set_name(block_var->self, "gl_in"); - if (model == ExecutionModelTessellationControl && storage == StorageClassOutput) end_scope_decl(join(to_name(block_var->self), "[", get_entry_point().output_vertices, "]")); else @@ -2823,12 +3109,16 @@ void CompilerGLSL::declare_undefined_values() { bool emitted = false; ir.for_each_typed_id([&](uint32_t, const SPIRUndef &undef) { + auto &type = this->get(undef.basetype); + // OpUndef can be void for some reason ... + if (type.basetype == SPIRType::Void) + return; + string initializer; - if (options.force_zero_initialized_variables && type_can_zero_initialize(this->get(undef.basetype))) + if (options.force_zero_initialized_variables && type_can_zero_initialize(type)) initializer = join(" = ", to_zero_initialized_expression(undef.basetype)); - statement(variable_decl(this->get(undef.basetype), to_name(undef.self), undef.self), initializer, - ";"); + statement(variable_decl(type, to_name(undef.self), undef.self), initializer, ";"); emitted = true; }); @@ -2865,6 +3155,18 @@ void CompilerGLSL::emit_resources() if (!pls_inputs.empty() || !pls_outputs.empty()) emit_pls(); + switch (execution.model) + { + case ExecutionModelGeometry: + case ExecutionModelTessellationControl: + case ExecutionModelTessellationEvaluation: + fixup_implicit_builtin_block_names(); + break; + + default: + break; + } + // Emit custom gl_PerVertex for SSO compatibility. if (options.separate_shader_objects && !options.es && execution.model != ExecutionModelFragment) { @@ -2952,14 +3254,15 @@ void CompilerGLSL::emit_resources() { auto *type = &id.get(); - bool is_natural_struct = - type->basetype == SPIRType::Struct && type->array.empty() && !type->pointer && - (!has_decoration(type->self, DecorationBlock) && !has_decoration(type->self, DecorationBufferBlock)); + bool is_natural_struct = type->basetype == SPIRType::Struct && type->array.empty() && !type->pointer && + (!has_decoration(type->self, DecorationBlock) && + !has_decoration(type->self, DecorationBufferBlock)); // Special case, ray payload and hit attribute blocks are not really blocks, just regular structs. - if (type->basetype == SPIRType::Struct && type->pointer && has_decoration(type->self, DecorationBlock) && - (type->storage == StorageClassRayPayloadNV || type->storage == StorageClassIncomingRayPayloadNV || - type->storage == StorageClassHitAttributeNV)) + if (type->basetype == SPIRType::Struct && type->pointer && + has_decoration(type->self, DecorationBlock) && + (type->storage == StorageClassRayPayloadKHR || type->storage == StorageClassIncomingRayPayloadKHR || + type->storage == StorageClassHitAttributeKHR)) { type = &get(type->parent_type); is_natural_struct = true; @@ -3035,7 +3338,7 @@ void CompilerGLSL::emit_resources() auto &type = this->get(var.basetype); bool is_block_storage = type.storage == StorageClassStorageBuffer || type.storage == StorageClassUniform || - type.storage == StorageClassShaderRecordBufferNV; + type.storage == StorageClassShaderRecordBufferKHR; bool has_block_flags = ir.meta[type.self].decoration.decoration_flags.get(DecorationBlock) || ir.meta[type.self].decoration.decoration_flags.get(DecorationBufferBlock); @@ -3075,9 +3378,9 @@ void CompilerGLSL::emit_resources() if (var.storage != StorageClassFunction && type.pointer && (type.storage == StorageClassUniformConstant || type.storage == StorageClassAtomicCounter || - type.storage == StorageClassRayPayloadNV || type.storage == StorageClassIncomingRayPayloadNV || - type.storage == StorageClassCallableDataNV || type.storage == StorageClassIncomingCallableDataNV || - type.storage == StorageClassHitAttributeNV) && + type.storage == StorageClassRayPayloadKHR || type.storage == StorageClassIncomingRayPayloadKHR || + type.storage == StorageClassCallableDataKHR || type.storage == StorageClassIncomingCallableDataKHR || + type.storage == StorageClassHitAttributeKHR) && !is_hidden_variable(var)) { emit_uniform(var); @@ -3157,6 +3460,9 @@ void CompilerGLSL::emit_resources() for (auto global : global_variables) { auto &var = get(global); + if (is_hidden_variable(var, true)) + continue; + if (var.storage != StorageClassOutput) { if (!variable_is_lut(var)) @@ -3174,6 +3480,10 @@ void CompilerGLSL::emit_resources() emitted = true; } } + else if (var.initializer && maybe_get(var.initializer) != nullptr) + { + emit_output_variable_initializer(var); + } } if (emitted) @@ -3182,6 +3492,600 @@ void CompilerGLSL::emit_resources() declare_undefined_values(); } +void CompilerGLSL::emit_output_variable_initializer(const SPIRVariable &var) +{ + // If a StorageClassOutput variable has an initializer, we need to initialize it in main(). + auto &entry_func = this->get(ir.default_entry_point); + auto &type = get(var.basetype); + bool is_patch = has_decoration(var.self, DecorationPatch); + bool is_block = has_decoration(type.self, DecorationBlock); + bool is_control_point = get_execution_model() == ExecutionModelTessellationControl && !is_patch; + + if (is_block) + { + uint32_t member_count = uint32_t(type.member_types.size()); + bool type_is_array = type.array.size() == 1; + uint32_t array_size = 1; + if (type_is_array) + array_size = to_array_size_literal(type); + uint32_t iteration_count = is_control_point ? 1 : array_size; + + // If the initializer is a block, we must initialize each block member one at a time. + for (uint32_t i = 0; i < member_count; i++) + { + // These outputs might not have been properly declared, so don't initialize them in that case. + if (has_member_decoration(type.self, i, DecorationBuiltIn)) + { + if (get_member_decoration(type.self, i, DecorationBuiltIn) == BuiltInCullDistance && + !cull_distance_count) + continue; + + if (get_member_decoration(type.self, i, DecorationBuiltIn) == BuiltInClipDistance && + !clip_distance_count) + continue; + } + + // We need to build a per-member array first, essentially transposing from AoS to SoA. + // This code path hits when we have an array of blocks. + string lut_name; + if (type_is_array) + { + lut_name = join("_", var.self, "_", i, "_init"); + uint32_t member_type_id = get(var.basetype).member_types[i]; + auto &member_type = get(member_type_id); + auto array_type = member_type; + array_type.parent_type = member_type_id; + array_type.array.push_back(array_size); + array_type.array_size_literal.push_back(true); + + SmallVector exprs; + exprs.reserve(array_size); + auto &c = get(var.initializer); + for (uint32_t j = 0; j < array_size; j++) + exprs.push_back(to_expression(get(c.subconstants[j]).subconstants[i])); + statement("const ", type_to_glsl(array_type), " ", lut_name, type_to_array_glsl(array_type), " = ", + type_to_glsl_constructor(array_type), "(", merge(exprs, ", "), ");"); + } + + for (uint32_t j = 0; j < iteration_count; j++) + { + entry_func.fixup_hooks_in.push_back([=, &var]() { + AccessChainMeta meta; + auto &c = this->get(var.initializer); + + uint32_t invocation_id = 0; + uint32_t member_index_id = 0; + if (is_control_point) + { + uint32_t ids = ir.increase_bound_by(3); + SPIRType uint_type; + uint_type.basetype = SPIRType::UInt; + uint_type.width = 32; + set(ids, uint_type); + set(ids + 1, builtin_to_glsl(BuiltInInvocationId, StorageClassInput), ids, true); + set(ids + 2, ids, i, false); + invocation_id = ids + 1; + member_index_id = ids + 2; + } + + if (is_patch) + { + statement("if (gl_InvocationID == 0)"); + begin_scope(); + } + + if (type_is_array && !is_control_point) + { + uint32_t indices[2] = { j, i }; + auto chain = access_chain_internal(var.self, indices, 2, ACCESS_CHAIN_INDEX_IS_LITERAL_BIT, &meta); + statement(chain, " = ", lut_name, "[", j, "];"); + } + else if (is_control_point) + { + uint32_t indices[2] = { invocation_id, member_index_id }; + auto chain = access_chain_internal(var.self, indices, 2, 0, &meta); + statement(chain, " = ", lut_name, "[", builtin_to_glsl(BuiltInInvocationId, StorageClassInput), "];"); + } + else + { + auto chain = + access_chain_internal(var.self, &i, 1, ACCESS_CHAIN_INDEX_IS_LITERAL_BIT, &meta); + statement(chain, " = ", to_expression(c.subconstants[i]), ";"); + } + + if (is_patch) + end_scope(); + }); + } + } + } + else if (is_control_point) + { + auto lut_name = join("_", var.self, "_init"); + statement("const ", type_to_glsl(type), " ", lut_name, type_to_array_glsl(type), + " = ", to_expression(var.initializer), ";"); + entry_func.fixup_hooks_in.push_back([&, lut_name]() { + statement(to_expression(var.self), "[gl_InvocationID] = ", lut_name, "[gl_InvocationID];"); + }); + } + else if (has_decoration(var.self, DecorationBuiltIn) && + BuiltIn(get_decoration(var.self, DecorationBuiltIn)) == BuiltInSampleMask) + { + // We cannot copy the array since gl_SampleMask is unsized in GLSL. Unroll time! <_< + entry_func.fixup_hooks_in.push_back([&] { + auto &c = this->get(var.initializer); + uint32_t num_constants = uint32_t(c.subconstants.size()); + for (uint32_t i = 0; i < num_constants; i++) + { + // Don't use to_expression on constant since it might be uint, just fish out the raw int. + statement(to_expression(var.self), "[", i, "] = ", + convert_to_string(this->get(c.subconstants[i]).scalar_i32()), ";"); + } + }); + } + else + { + auto lut_name = join("_", var.self, "_init"); + statement("const ", type_to_glsl(type), " ", lut_name, + type_to_array_glsl(type), " = ", to_expression(var.initializer), ";"); + entry_func.fixup_hooks_in.push_back([&, lut_name, is_patch]() { + if (is_patch) + { + statement("if (gl_InvocationID == 0)"); + begin_scope(); + } + statement(to_expression(var.self), " = ", lut_name, ";"); + if (is_patch) + end_scope(); + }); + } +} + +void CompilerGLSL::emit_extension_workarounds(spv::ExecutionModel model) +{ + static const char *workaround_types[] = { "int", "ivec2", "ivec3", "ivec4", "uint", "uvec2", "uvec3", "uvec4", + "float", "vec2", "vec3", "vec4", "double", "dvec2", "dvec3", "dvec4" }; + + if (!options.vulkan_semantics) + { + using Supp = ShaderSubgroupSupportHelper; + auto result = shader_subgroup_supporter.resolve(); + + if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupMask)) + { + auto exts = Supp::get_candidates_for_feature(Supp::SubgroupMask, result); + + for (auto &e : exts) + { + const char *name = Supp::get_extension_name(e); + statement(&e == &exts.front() ? "#if" : "#elif", " defined(", name, ")"); + + switch (e) + { + case Supp::NV_shader_thread_group: + statement("#define gl_SubgroupEqMask uvec4(gl_ThreadEqMaskNV, 0u, 0u, 0u)"); + statement("#define gl_SubgroupGeMask uvec4(gl_ThreadGeMaskNV, 0u, 0u, 0u)"); + statement("#define gl_SubgroupGtMask uvec4(gl_ThreadGtMaskNV, 0u, 0u, 0u)"); + statement("#define gl_SubgroupLeMask uvec4(gl_ThreadLeMaskNV, 0u, 0u, 0u)"); + statement("#define gl_SubgroupLtMask uvec4(gl_ThreadLtMaskNV, 0u, 0u, 0u)"); + break; + case Supp::ARB_shader_ballot: + statement("#define gl_SubgroupEqMask uvec4(unpackUint2x32(gl_SubGroupEqMaskARB), 0u, 0u)"); + statement("#define gl_SubgroupGeMask uvec4(unpackUint2x32(gl_SubGroupGeMaskARB), 0u, 0u)"); + statement("#define gl_SubgroupGtMask uvec4(unpackUint2x32(gl_SubGroupGtMaskARB), 0u, 0u)"); + statement("#define gl_SubgroupLeMask uvec4(unpackUint2x32(gl_SubGroupLeMaskARB), 0u, 0u)"); + statement("#define gl_SubgroupLtMask uvec4(unpackUint2x32(gl_SubGroupLtMaskARB), 0u, 0u)"); + break; + default: + break; + } + } + statement("#endif"); + statement(""); + } + + if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupSize)) + { + auto exts = Supp::get_candidates_for_feature(Supp::SubgroupSize, result); + + for (auto &e : exts) + { + const char *name = Supp::get_extension_name(e); + statement(&e == &exts.front() ? "#if" : "#elif", " defined(", name, ")"); + + switch (e) + { + case Supp::NV_shader_thread_group: + statement("#define gl_SubgroupSize gl_WarpSizeNV"); + break; + case Supp::ARB_shader_ballot: + statement("#define gl_SubgroupSize gl_SubGroupSizeARB"); + break; + case Supp::AMD_gcn_shader: + statement("#define gl_SubgroupSize uint(gl_SIMDGroupSizeAMD)"); + break; + default: + break; + } + } + statement("#endif"); + statement(""); + } + + if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupInvocationID)) + { + auto exts = Supp::get_candidates_for_feature(Supp::SubgroupInvocationID, result); + + for (auto &e : exts) + { + const char *name = Supp::get_extension_name(e); + statement(&e == &exts.front() ? "#if" : "#elif", " defined(", name, ")"); + + switch (e) + { + case Supp::NV_shader_thread_group: + statement("#define gl_SubgroupInvocationID gl_ThreadInWarpNV"); + break; + case Supp::ARB_shader_ballot: + statement("#define gl_SubgroupInvocationID gl_SubGroupInvocationARB"); + break; + default: + break; + } + } + statement("#endif"); + statement(""); + } + + if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupID)) + { + auto exts = Supp::get_candidates_for_feature(Supp::SubgroupID, result); + + for (auto &e : exts) + { + const char *name = Supp::get_extension_name(e); + statement(&e == &exts.front() ? "#if" : "#elif", " defined(", name, ")"); + + switch (e) + { + case Supp::NV_shader_thread_group: + statement("#define gl_SubgroupID gl_WarpIDNV"); + break; + default: + break; + } + } + statement("#endif"); + statement(""); + } + + if (shader_subgroup_supporter.is_feature_requested(Supp::NumSubgroups)) + { + auto exts = Supp::get_candidates_for_feature(Supp::NumSubgroups, result); + + for (auto &e : exts) + { + const char *name = Supp::get_extension_name(e); + statement(&e == &exts.front() ? "#if" : "#elif", " defined(", name, ")"); + + switch (e) + { + case Supp::NV_shader_thread_group: + statement("#define gl_NumSubgroups gl_WarpsPerSMNV"); + break; + default: + break; + } + } + statement("#endif"); + statement(""); + } + + if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupBroadcast_First)) + { + auto exts = Supp::get_candidates_for_feature(Supp::SubgroupBroadcast_First, result); + + for (auto &e : exts) + { + const char *name = Supp::get_extension_name(e); + statement(&e == &exts.front() ? "#if" : "#elif", " defined(", name, ")"); + + switch (e) + { + case Supp::NV_shader_thread_shuffle: + for (const char *t : workaround_types) + { + statement(t, " subgroupBroadcastFirst(", t, + " value) { return shuffleNV(value, findLSB(ballotThreadNV(true)), gl_WarpSizeNV); }"); + } + for (const char *t : workaround_types) + { + statement(t, " subgroupBroadcast(", t, + " value, uint id) { return shuffleNV(value, id, gl_WarpSizeNV); }"); + } + break; + case Supp::ARB_shader_ballot: + for (const char *t : workaround_types) + { + statement(t, " subgroupBroadcastFirst(", t, + " value) { return readFirstInvocationARB(value); }"); + } + for (const char *t : workaround_types) + { + statement(t, " subgroupBroadcast(", t, + " value, uint id) { return readInvocationARB(value, id); }"); + } + break; + default: + break; + } + } + statement("#endif"); + statement(""); + } + + if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupBallotFindLSB_MSB)) + { + auto exts = Supp::get_candidates_for_feature(Supp::SubgroupBallotFindLSB_MSB, result); + + for (auto &e : exts) + { + const char *name = Supp::get_extension_name(e); + statement(&e == &exts.front() ? "#if" : "#elif", " defined(", name, ")"); + + switch (e) + { + case Supp::NV_shader_thread_group: + statement("uint subgroupBallotFindLSB(uvec4 value) { return findLSB(value.x); }"); + statement("uint subgroupBallotFindMSB(uvec4 value) { return findMSB(value.x); }"); + break; + default: + break; + } + } + statement("#else"); + statement("uint subgroupBallotFindLSB(uvec4 value)"); + begin_scope(); + statement("int firstLive = findLSB(value.x);"); + statement("return uint(firstLive != -1 ? firstLive : (findLSB(value.y) + 32));"); + end_scope(); + statement("uint subgroupBallotFindMSB(uvec4 value)"); + begin_scope(); + statement("int firstLive = findMSB(value.y);"); + statement("return uint(firstLive != -1 ? (firstLive + 32) : findMSB(value.x));"); + end_scope(); + statement("#endif"); + statement(""); + } + + if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupAll_Any_AllEqualBool)) + { + auto exts = Supp::get_candidates_for_feature(Supp::SubgroupAll_Any_AllEqualBool, result); + + for (auto &e : exts) + { + const char *name = Supp::get_extension_name(e); + statement(&e == &exts.front() ? "#if" : "#elif", " defined(", name, ")"); + + switch (e) + { + case Supp::NV_gpu_shader_5: + statement("bool subgroupAll(bool value) { return allThreadsNV(value); }"); + statement("bool subgroupAny(bool value) { return anyThreadNV(value); }"); + statement("bool subgroupAllEqual(bool value) { return allThreadsEqualNV(value); }"); + break; + case Supp::ARB_shader_group_vote: + statement("bool subgroupAll(bool v) { return allInvocationsARB(v); }"); + statement("bool subgroupAny(bool v) { return anyInvocationARB(v); }"); + statement("bool subgroupAllEqual(bool v) { return allInvocationsEqualARB(v); }"); + break; + case Supp::AMD_gcn_shader: + statement("bool subgroupAll(bool value) { return ballotAMD(value) == ballotAMD(true); }"); + statement("bool subgroupAny(bool value) { return ballotAMD(value) != 0ull; }"); + statement("bool subgroupAllEqual(bool value) { uint64_t b = ballotAMD(value); return b == 0ull || " + "b == ballotAMD(true); }"); + break; + default: + break; + } + } + statement("#endif"); + statement(""); + } + + if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupAllEqualT)) + { + statement("#ifndef GL_KHR_shader_subgroup_vote"); + statement( + "#define _SPIRV_CROSS_SUBGROUP_ALL_EQUAL_WORKAROUND(type) bool subgroupAllEqual(type value) { return " + "subgroupAllEqual(subgroupBroadcastFirst(value) == value); }"); + for (const char *t : workaround_types) + statement("_SPIRV_CROSS_SUBGROUP_ALL_EQUAL_WORKAROUND(", t, ")"); + statement("#undef _SPIRV_CROSS_SUBGROUP_ALL_EQUAL_WORKAROUND"); + statement("#endif"); + statement(""); + } + + if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupBallot)) + { + auto exts = Supp::get_candidates_for_feature(Supp::SubgroupBallot, result); + + for (auto &e : exts) + { + const char *name = Supp::get_extension_name(e); + statement(&e == &exts.front() ? "#if" : "#elif", " defined(", name, ")"); + + switch (e) + { + case Supp::NV_shader_thread_group: + statement("uvec4 subgroupBallot(bool v) { return uvec4(ballotThreadNV(v), 0u, 0u, 0u); }"); + break; + case Supp::ARB_shader_ballot: + statement("uvec4 subgroupBallot(bool v) { return uvec4(unpackUint2x32(ballotARB(v)), 0u, 0u); }"); + break; + default: + break; + } + } + statement("#endif"); + statement(""); + } + + if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupElect)) + { + statement("#ifndef GL_KHR_shader_subgroup_basic"); + statement("bool subgroupElect()"); + begin_scope(); + statement("uvec4 activeMask = subgroupBallot(true);"); + statement("uint firstLive = subgroupBallotFindLSB(activeMask);"); + statement("return gl_SubgroupInvocationID == firstLive;"); + end_scope(); + statement("#endif"); + statement(""); + } + + if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupBarrier)) + { + // Extensions we're using in place of GL_KHR_shader_subgroup_basic state + // that subgroup execute in lockstep so this barrier is implicit. + // However the GL 4.6 spec also states that `barrier` implies a shared memory barrier, + // and a specific test of optimizing scans by leveraging lock-step invocation execution, + // has shown that a `memoryBarrierShared` is needed in place of a `subgroupBarrier`. + // https://github.com/buildaworldnet/IrrlichtBAW/commit/d8536857991b89a30a6b65d29441e51b64c2c7ad#diff-9f898d27be1ea6fc79b03d9b361e299334c1a347b6e4dc344ee66110c6aa596aR19 + statement("#ifndef GL_KHR_shader_subgroup_basic"); + statement("void subgroupBarrier() { memoryBarrierShared(); }"); + statement("#endif"); + statement(""); + } + + if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupMemBarrier)) + { + if (model == spv::ExecutionModelGLCompute) + { + statement("#ifndef GL_KHR_shader_subgroup_basic"); + statement("void subgroupMemoryBarrier() { groupMemoryBarrier(); }"); + statement("void subgroupMemoryBarrierBuffer() { groupMemoryBarrier(); }"); + statement("void subgroupMemoryBarrierShared() { memoryBarrierShared(); }"); + statement("void subgroupMemoryBarrierImage() { groupMemoryBarrier(); }"); + statement("#endif"); + } + else + { + statement("#ifndef GL_KHR_shader_subgroup_basic"); + statement("void subgroupMemoryBarrier() { memoryBarrier(); }"); + statement("void subgroupMemoryBarrierBuffer() { memoryBarrierBuffer(); }"); + statement("void subgroupMemoryBarrierImage() { memoryBarrierImage(); }"); + statement("#endif"); + } + statement(""); + } + + if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupInverseBallot_InclBitCount_ExclBitCout)) + { + statement("#ifndef GL_KHR_shader_subgroup_ballot"); + statement("bool subgroupInverseBallot(uvec4 value)"); + begin_scope(); + statement("return any(notEqual(value.xy & gl_SubgroupEqMask.xy, uvec2(0u)));"); + end_scope(); + + statement("uint subgroupBallotInclusiveBitCount(uvec4 value)"); + begin_scope(); + statement("uvec2 v = value.xy & gl_SubgroupLeMask.xy;"); + statement("ivec2 c = bitCount(v);"); + statement_no_indent("#ifdef GL_NV_shader_thread_group"); + statement("return uint(c.x);"); + statement_no_indent("#else"); + statement("return uint(c.x + c.y);"); + statement_no_indent("#endif"); + end_scope(); + + statement("uint subgroupBallotExclusiveBitCount(uvec4 value)"); + begin_scope(); + statement("uvec2 v = value.xy & gl_SubgroupLtMask.xy;"); + statement("ivec2 c = bitCount(v);"); + statement_no_indent("#ifdef GL_NV_shader_thread_group"); + statement("return uint(c.x);"); + statement_no_indent("#else"); + statement("return uint(c.x + c.y);"); + statement_no_indent("#endif"); + end_scope(); + statement("#endif"); + statement(""); + } + + if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupBallotBitCount)) + { + statement("#ifndef GL_KHR_shader_subgroup_ballot"); + statement("uint subgroupBallotBitCount(uvec4 value)"); + begin_scope(); + statement("ivec2 c = bitCount(value.xy);"); + statement_no_indent("#ifdef GL_NV_shader_thread_group"); + statement("return uint(c.x);"); + statement_no_indent("#else"); + statement("return uint(c.x + c.y);"); + statement_no_indent("#endif"); + end_scope(); + statement("#endif"); + statement(""); + } + + if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupBallotBitExtract)) + { + statement("#ifndef GL_KHR_shader_subgroup_ballot"); + statement("bool subgroupBallotBitExtract(uvec4 value, uint index)"); + begin_scope(); + statement_no_indent("#ifdef GL_NV_shader_thread_group"); + statement("uint shifted = value.x >> index;"); + statement_no_indent("#else"); + statement("uint shifted = value[index >> 5u] >> (index & 0x1fu);"); + statement_no_indent("#endif"); + statement("return (shifted & 1u) != 0u;"); + end_scope(); + statement("#endif"); + statement(""); + } + } + + if (!workaround_ubo_load_overload_types.empty()) + { + for (auto &type_id : workaround_ubo_load_overload_types) + { + auto &type = get(type_id); + statement(type_to_glsl(type), " spvWorkaroundRowMajor(", type_to_glsl(type), + " wrap) { return wrap; }"); + } + statement(""); + } + + if (requires_transpose_2x2) + { + statement("mat2 spvTranspose(mat2 m)"); + begin_scope(); + statement("return mat2(m[0][0], m[1][0], m[0][1], m[1][1]);"); + end_scope(); + statement(""); + } + + if (requires_transpose_3x3) + { + statement("mat3 spvTranspose(mat3 m)"); + begin_scope(); + statement("return mat3(m[0][0], m[1][0], m[2][0], m[0][1], m[1][1], m[2][1], m[0][2], m[1][2], m[2][2]);"); + end_scope(); + statement(""); + } + + if (requires_transpose_4x4) + { + statement("mat4 spvTranspose(mat4 m)"); + begin_scope(); + statement("return mat4(m[0][0], m[1][0], m[2][0], m[3][0], m[0][1], m[1][1], m[2][1], m[3][1], m[0][2], " + "m[1][2], m[2][2], m[3][2], m[0][3], m[1][3], m[2][3], m[3][3]);"); + end_scope(); + statement(""); + } +} + // Returns a string representation of the ID, usable as a function arg. // Default is to simply return the expression representation fo the arg ID. // Subclasses may override to modify the return value. @@ -3406,6 +4310,44 @@ string CompilerGLSL::to_extract_component_expression(uint32_t id, uint32_t index return join(expr, ".", index_to_swizzle(index)); } +string CompilerGLSL::to_extract_constant_composite_expression(uint32_t result_type, const SPIRConstant &c, + const uint32_t *chain, uint32_t length) +{ + // It is kinda silly if application actually enter this path since they know the constant up front. + // It is useful here to extract the plain constant directly. + SPIRConstant tmp; + tmp.constant_type = result_type; + auto &composite_type = get(c.constant_type); + assert(composite_type.basetype != SPIRType::Struct && composite_type.array.empty()); + assert(!c.specialization); + + if (is_matrix(composite_type)) + { + if (length == 2) + { + tmp.m.c[0].vecsize = 1; + tmp.m.columns = 1; + tmp.m.c[0].r[0] = c.m.c[chain[0]].r[chain[1]]; + } + else + { + assert(length == 1); + tmp.m.c[0].vecsize = composite_type.vecsize; + tmp.m.columns = 1; + tmp.m.c[0] = c.m.c[chain[0]]; + } + } + else + { + assert(length == 1); + tmp.m.c[0].vecsize = 1; + tmp.m.columns = 1; + tmp.m.c[0].r[0] = c.m.c[0].r[chain[0]]; + } + + return constant_expression(tmp); +} + string CompilerGLSL::to_rerolled_array_expression(const string &base_expr, const SPIRType &type) { uint32_t size = to_array_size_literal(type); @@ -3428,10 +4370,14 @@ string CompilerGLSL::to_rerolled_array_expression(const string &base_expr, const return expr; } -string CompilerGLSL::to_composite_constructor_expression(uint32_t id) +string CompilerGLSL::to_composite_constructor_expression(uint32_t id, bool uses_buffer_offset) { auto &type = expression_type(id); - if (!backend.array_is_value_type && !type.array.empty()) + + bool reroll_array = !type.array.empty() && (!backend.array_is_value_type || + (uses_buffer_offset && !backend.buffer_offset_array_is_value_type)); + + if (reroll_array) { // For this case, we need to "re-roll" an array initializer from a temporary. // We cannot simply pass the array directly, since it decays to a pointer and it cannot @@ -3493,6 +4439,10 @@ string CompilerGLSL::to_expression(uint32_t id, bool register_expression_read) return convert_row_major_matrix(e.expression, get(e.expression_type), physical_type_id, is_packed); } + else if (flattened_structs.count(id)) + { + return load_flattened_struct(e.expression, get(e.expression_type)); + } else { if (is_forcing_recompilation()) @@ -3545,7 +4495,7 @@ string CompilerGLSL::to_expression(uint32_t id, bool register_expression_read) } else if (flattened_structs.count(id)) { - return load_flattened_struct(var); + return load_flattened_struct(to_name(id), get(var.basetype)); } else { @@ -4281,8 +5231,8 @@ string CompilerGLSL::constant_expression_vector(const SPIRConstant &c, uint32_t // Fake unsigned constant literals with signed ones if possible. // Things like array sizes, etc, tend to be unsigned even though they could just as easily be signed. if (c.scalar_i32(vector, i) < 0) - SPIRV_CROSS_THROW( - "Tried to convert uint literal into int, but this made the literal negative."); + SPIRV_CROSS_THROW("Tried to convert uint literal into int, but this made " + "the literal negative."); } else if (backend.uint32_t_literal_suffix) res += "u"; @@ -4542,6 +5492,17 @@ bool CompilerGLSL::expression_suppresses_usage_tracking(uint32_t id) const return suppressed_usage_tracking.count(id) != 0; } +bool CompilerGLSL::expression_read_implies_multiple_reads(uint32_t id) const +{ + auto *expr = maybe_get(id); + if (!expr) + return false; + + // If we're emitting code at a deeper loop level than when we emitted the expression, + // we're probably reading the same expression over and over. + return current_loop_level > expr->emitted_loop_level; +} + SPIRExpression &CompilerGLSL::emit_op(uint32_t result_type, uint32_t result_id, const string &rhs, bool forwarding, bool suppress_usage_tracking) { @@ -4697,7 +5658,8 @@ bool CompilerGLSL::emit_complex_bitcast(uint32_t result_type, uint32_t id, uint3 if (output_type.basetype == SPIRType::Half && input_type.basetype == SPIRType::Float && input_type.vecsize == 1) expr = join("unpackFloat2x16(floatBitsToUint(", to_unpacked_expression(op0), "))"); - else if (output_type.basetype == SPIRType::Float && input_type.basetype == SPIRType::Half && input_type.vecsize == 2) + else if (output_type.basetype == SPIRType::Float && input_type.basetype == SPIRType::Half && + input_type.vecsize == 2) expr = join("uintBitsToFloat(packFloat2x16(", to_unpacked_expression(op0), "))"); else return false; @@ -4760,14 +5722,27 @@ void CompilerGLSL::emit_unary_func_op_cast(uint32_t result_type, uint32_t result // Bit-widths might be different in unary cases because we use it for SConvert/UConvert and friends. expected_type.basetype = input_type; expected_type.width = expr_type.width; - string cast_op = expr_type.basetype != input_type ? bitcast_glsl(expected_type, op0) : to_unpacked_expression(op0); + + string cast_op; + if (expr_type.basetype != input_type) + { + if (expr_type.basetype == SPIRType::Boolean) + cast_op = join(type_to_glsl(expected_type), "(", to_unpacked_expression(op0), ")"); + else + cast_op = bitcast_glsl(expected_type, op0); + } + else + cast_op = to_unpacked_expression(op0); string expr; if (out_type.basetype != expected_result_type) { expected_type.basetype = expected_result_type; expected_type.width = out_type.width; - expr = bitcast_glsl_op(out_type, expected_type); + if (out_type.basetype == SPIRType::Boolean) + expr = type_to_glsl(out_type); + else + expr = bitcast_glsl_op(out_type, expected_type); expr += '('; expr += join(op, "(", cast_op, ")"); expr += ')'; @@ -4987,26 +5962,7 @@ void CompilerGLSL::emit_bitfield_insert_op(uint32_t result_type, uint32_t result inherit_expression_dependencies(result_id, op3); } -// EXT_shader_texture_lod only concerns fragment shaders so lod tex functions -// are not allowed in ES 2 vertex shaders. But SPIR-V only supports lod tex -// functions in vertex shaders so we revert those back to plain calls when -// the lod is a constant value of zero. -bool CompilerGLSL::check_explicit_lod_allowed(uint32_t lod) -{ - auto &execution = get_entry_point(); - bool allowed = !is_legacy_es() || execution.model == ExecutionModelFragment; - if (!allowed && lod != 0) - { - auto *lod_constant = maybe_get(lod); - if (!lod_constant || lod_constant->scalar_f32() != 0.0f) - { - SPIRV_CROSS_THROW("Explicit lod not allowed in legacy ES non-fragment shaders."); - } - } - return allowed; -} - -string CompilerGLSL::legacy_tex_op(const std::string &op, const SPIRType &imgtype, uint32_t lod, uint32_t tex) +string CompilerGLSL::legacy_tex_op(const std::string &op, const SPIRType &imgtype, uint32_t tex) { const char *type; switch (imgtype.image.dim) @@ -5037,16 +5993,19 @@ string CompilerGLSL::legacy_tex_op(const std::string &op, const SPIRType &imgtyp break; } - bool use_explicit_lod = check_explicit_lod_allowed(lod); - - if (op == "textureLod" || op == "textureProjLod" || op == "textureGrad" || op == "textureProjGrad") + // In legacy GLSL, an extension is required for textureLod in the fragment + // shader or textureGrad anywhere. + bool legacy_lod_ext = false; + auto &execution = get_entry_point(); + if (op == "textureGrad" || op == "textureProjGrad" || + ((op == "textureLod" || op == "textureProjLod") && execution.model != ExecutionModelVertex)) { if (is_legacy_es()) { - if (use_explicit_lod) - require_extension_internal("GL_EXT_shader_texture_lod"); + legacy_lod_ext = true; + require_extension_internal("GL_EXT_shader_texture_lod"); } - else if (is_legacy()) + else if (is_legacy_desktop()) require_extension_internal("GL_ARB_shader_texture_lod"); } @@ -5061,7 +6020,8 @@ string CompilerGLSL::legacy_tex_op(const std::string &op, const SPIRType &imgtyp // GLES has very limited support for shadow samplers. // Basically shadow2D and shadow2DProj work through EXT_shadow_samplers, // everything else can just throw - if (image_is_comparison(imgtype, tex) && is_legacy_es()) + bool is_comparison = image_is_comparison(imgtype, tex); + if (is_comparison && is_legacy_es()) { if (op == "texture" || op == "textureProj") require_extension_internal("GL_EXT_shadow_samplers"); @@ -5069,46 +6029,42 @@ string CompilerGLSL::legacy_tex_op(const std::string &op, const SPIRType &imgtyp SPIRV_CROSS_THROW(join(op, " not allowed on depth samplers in legacy ES")); } - bool is_es_and_depth = is_legacy_es() && image_is_comparison(imgtype, tex); - std::string type_prefix = image_is_comparison(imgtype, tex) ? "shadow" : "texture"; + if (op == "textureSize") + { + if (is_legacy_es()) + SPIRV_CROSS_THROW("textureSize not supported in legacy ES"); + if (is_comparison) + SPIRV_CROSS_THROW("textureSize not supported on shadow sampler in legacy GLSL"); + require_extension_internal("GL_EXT_gpu_shader4"); + } + + if (op == "texelFetch" && is_legacy_es()) + SPIRV_CROSS_THROW("texelFetch not supported in legacy ES"); + + bool is_es_and_depth = is_legacy_es() && is_comparison; + std::string type_prefix = is_comparison ? "shadow" : "texture"; if (op == "texture") return is_es_and_depth ? join(type_prefix, type, "EXT") : join(type_prefix, type); else if (op == "textureLod") - { - if (use_explicit_lod) - return join(type_prefix, type, is_legacy_es() ? "LodEXT" : "Lod"); - else - return join(type_prefix, type); - } + return join(type_prefix, type, legacy_lod_ext ? "LodEXT" : "Lod"); else if (op == "textureProj") return join(type_prefix, type, is_es_and_depth ? "ProjEXT" : "Proj"); else if (op == "textureGrad") return join(type_prefix, type, is_legacy_es() ? "GradEXT" : is_legacy_desktop() ? "GradARB" : "Grad"); else if (op == "textureProjLod") - { - if (use_explicit_lod) - return join(type_prefix, type, is_legacy_es() ? "ProjLodEXT" : "ProjLod"); - else - return join(type_prefix, type, "Proj"); - } + return join(type_prefix, type, legacy_lod_ext ? "ProjLodEXT" : "ProjLod"); else if (op == "textureLodOffset") - { - if (use_explicit_lod) - return join(type_prefix, type, "LodOffset"); - else - return join(type_prefix, type); - } + return join(type_prefix, type, "LodOffset"); else if (op == "textureProjGrad") return join(type_prefix, type, is_legacy_es() ? "ProjGradEXT" : is_legacy_desktop() ? "ProjGradARB" : "ProjGrad"); else if (op == "textureProjLodOffset") - { - if (use_explicit_lod) - return join(type_prefix, type, "ProjLodOffset"); - else - return join(type_prefix, type, "ProjOffset"); - } + return join(type_prefix, type, "ProjLodOffset"); + else if (op == "textureSize") + return join("textureSize", type); + else if (op == "texelFetch") + return join("texelFetch", type); else { SPIRV_CROSS_THROW(join("Unsupported legacy texture op: ", op)); @@ -5299,9 +6255,9 @@ string CompilerGLSL::to_combined_image_sampler(VariableID image_id, VariableID s return to_expression(itr->id) + array_expr; else { - SPIRV_CROSS_THROW( - "Cannot find mapping for combined sampler parameter, was build_combined_image_samplers() used " - "before compile() was called?"); + SPIRV_CROSS_THROW("Cannot find mapping for combined sampler parameter, was " + "build_combined_image_samplers() used " + "before compile() was called?"); } } else @@ -5322,6 +6278,30 @@ string CompilerGLSL::to_combined_image_sampler(VariableID image_id, VariableID s } } +bool CompilerGLSL::is_supported_subgroup_op_in_opengl(spv::Op op) +{ + switch (op) + { + case OpGroupNonUniformElect: + case OpGroupNonUniformBallot: + case OpGroupNonUniformBallotFindLSB: + case OpGroupNonUniformBallotFindMSB: + case OpGroupNonUniformBroadcast: + case OpGroupNonUniformBroadcastFirst: + case OpGroupNonUniformAll: + case OpGroupNonUniformAny: + case OpGroupNonUniformAllEqual: + case OpControlBarrier: + case OpMemoryBarrier: + case OpGroupNonUniformBallotBitCount: + case OpGroupNonUniformBallotBitExtract: + case OpGroupNonUniformInverseBallot: + return true; + default: + return false; + } +} + void CompilerGLSL::emit_sampled_image_op(uint32_t result_type, uint32_t result_id, uint32_t image_id, uint32_t samp_id) { if (options.vulkan_semantics && combined_image_samplers.empty()) @@ -5363,7 +6343,38 @@ static inline bool image_opcode_is_sample_no_dref(Op op) } } -void CompilerGLSL::emit_texture_op(const Instruction &i) +void CompilerGLSL::emit_sparse_feedback_temporaries(uint32_t result_type_id, uint32_t id, uint32_t &feedback_id, + uint32_t &texel_id) +{ + // Need to allocate two temporaries. + if (options.es) + SPIRV_CROSS_THROW("Sparse texture feedback is not supported on ESSL."); + require_extension_internal("GL_ARB_sparse_texture2"); + + auto &temps = extra_sub_expressions[id]; + if (temps == 0) + temps = ir.increase_bound_by(2); + + feedback_id = temps + 0; + texel_id = temps + 1; + + auto &return_type = get(result_type_id); + if (return_type.basetype != SPIRType::Struct || return_type.member_types.size() != 2) + SPIRV_CROSS_THROW("Invalid return type for sparse feedback."); + emit_uninitialized_temporary(return_type.member_types[0], feedback_id); + emit_uninitialized_temporary(return_type.member_types[1], texel_id); +} + +uint32_t CompilerGLSL::get_sparse_feedback_texel_id(uint32_t id) const +{ + auto itr = extra_sub_expressions.find(id); + if (itr == extra_sub_expressions.end()) + return 0; + else + return itr->second + 1; +} + +void CompilerGLSL::emit_texture_op(const Instruction &i, bool sparse) { auto *ops = stream(i); auto op = static_cast(i.op); @@ -5372,13 +6383,30 @@ void CompilerGLSL::emit_texture_op(const Instruction &i) uint32_t result_type_id = ops[0]; uint32_t id = ops[1]; + auto &return_type = get(result_type_id); + + uint32_t sparse_code_id = 0; + uint32_t sparse_texel_id = 0; + if (sparse) + emit_sparse_feedback_temporaries(result_type_id, id, sparse_code_id, sparse_texel_id); bool forward = false; - string expr = to_texture_op(i, &forward, inherited_expressions); + string expr = to_texture_op(i, sparse, &forward, inherited_expressions); + + if (sparse) + { + statement(to_expression(sparse_code_id), " = ", expr, ";"); + expr = join(type_to_glsl(return_type), "(", to_expression(sparse_code_id), ", ", to_expression(sparse_texel_id), + ")"); + forward = true; + inherited_expressions.clear(); + } + emit_op(result_type_id, id, expr, forward); for (auto &inherit : inherited_expressions) inherit_expression_dependencies(id, inherit); + // Do not register sparse ops as control dependent as they are always lowered to a temporary. switch (op) { case OpImageSampleDrefImplicitLod: @@ -5393,7 +6421,7 @@ void CompilerGLSL::emit_texture_op(const Instruction &i) } } -std::string CompilerGLSL::to_texture_op(const Instruction &i, bool *forward, +std::string CompilerGLSL::to_texture_op(const Instruction &i, bool sparse, bool *forward, SmallVector &inherited_expressions) { auto *ops = stream(i); @@ -5408,6 +6436,7 @@ std::string CompilerGLSL::to_texture_op(const Instruction &i, bool *forward, bool gather = false; bool proj = false; bool fetch = false; + bool nonuniform_expression = false; const uint32_t *opt = nullptr; auto &result_type = get(result_type_id); @@ -5416,12 +6445,24 @@ std::string CompilerGLSL::to_texture_op(const Instruction &i, bool *forward, // Make sure non-uniform decoration is back-propagated to where it needs to be. if (has_decoration(img, DecorationNonUniformEXT)) - propagate_nonuniform_qualifier(img); + { + // In Vulkan GLSL, we cannot back-propgate nonuniform qualifiers if we + // use a combined image sampler constructor. + // We're only interested in back-propagating if we can trace back through access chains. + // If not, we will apply nonuniform to the sampled image expression itself. + auto *backing = maybe_get_backing_variable(img); + if (backing) + propagate_nonuniform_qualifier(img); + else + nonuniform_expression = true; + } switch (op) { case OpImageSampleDrefImplicitLod: case OpImageSampleDrefExplicitLod: + case OpImageSparseSampleDrefImplicitLod: + case OpImageSparseSampleDrefExplicitLod: dref = ops[4]; opt = &ops[5]; length -= 5; @@ -5429,6 +6470,8 @@ std::string CompilerGLSL::to_texture_op(const Instruction &i, bool *forward, case OpImageSampleProjDrefImplicitLod: case OpImageSampleProjDrefExplicitLod: + case OpImageSparseSampleProjDrefImplicitLod: + case OpImageSparseSampleProjDrefExplicitLod: dref = ops[4]; opt = &ops[5]; length -= 5; @@ -5436,20 +6479,35 @@ std::string CompilerGLSL::to_texture_op(const Instruction &i, bool *forward, break; case OpImageDrefGather: + case OpImageSparseDrefGather: dref = ops[4]; opt = &ops[5]; length -= 5; gather = true; + if (options.es && options.version < 310) + SPIRV_CROSS_THROW("textureGather requires ESSL 310."); + else if (!options.es && options.version < 400) + SPIRV_CROSS_THROW("textureGather with depth compare requires GLSL 400."); break; case OpImageGather: + case OpImageSparseGather: comp = ops[4]; opt = &ops[5]; length -= 5; gather = true; + if (options.es && options.version < 310) + SPIRV_CROSS_THROW("textureGather requires ESSL 310."); + else if (!options.es && options.version < 400) + { + if (!expression_is_constant_null(comp)) + SPIRV_CROSS_THROW("textureGather with component requires GLSL 400."); + require_extension_internal("GL_ARB_texture_gather"); + } break; case OpImageFetch: + case OpImageSparseFetch: case OpImageRead: // Reads == fetches in Metal (other langs will not get here) opt = &ops[4]; length -= 4; @@ -5458,6 +6516,8 @@ std::string CompilerGLSL::to_texture_op(const Instruction &i, bool *forward, case OpImageSampleProjImplicitLod: case OpImageSampleProjExplicitLod: + case OpImageSparseSampleProjImplicitLod: + case OpImageSparseSampleProjExplicitLod: opt = &ops[4]; length -= 4; proj = true; @@ -5540,12 +6600,48 @@ std::string CompilerGLSL::to_texture_op(const Instruction &i, bool *forward, test(sample, ImageOperandsSampleMask); test(minlod, ImageOperandsMinLodMask); + TextureFunctionBaseArguments base_args = {}; + base_args.img = img; + base_args.imgtype = &imgtype; + base_args.is_fetch = fetch != 0; + base_args.is_gather = gather != 0; + base_args.is_proj = proj != 0; + string expr; - expr += to_function_name(img, imgtype, !!fetch, !!gather, !!proj, !!coffsets, (!!coffset || !!offset), - (!!grad_x || !!grad_y), !!dref, lod, minlod); + TextureFunctionNameArguments name_args = {}; + + name_args.base = base_args; + name_args.has_array_offsets = coffsets != 0; + name_args.has_offset = coffset != 0 || offset != 0; + name_args.has_grad = grad_x != 0 || grad_y != 0; + name_args.has_dref = dref != 0; + name_args.is_sparse_feedback = sparse; + name_args.has_min_lod = minlod != 0; + name_args.lod = lod; + expr += to_function_name(name_args); expr += "("; - expr += to_function_args(img, imgtype, fetch, gather, proj, coord, coord_components, dref, grad_x, grad_y, lod, - coffset, offset, bias, comp, sample, minlod, forward); + + uint32_t sparse_texel_id = 0; + if (sparse) + sparse_texel_id = get_sparse_feedback_texel_id(ops[1]); + + TextureFunctionArguments args = {}; + args.base = base_args; + args.coord = coord; + args.coord_components = coord_components; + args.dref = dref; + args.grad_x = grad_x; + args.grad_y = grad_y; + args.lod = lod; + args.coffset = coffset; + args.offset = offset; + args.bias = bias; + args.component = comp; + args.sample = sample; + args.sparse_texel = sparse_texel_id; + args.min_lod = minlod; + args.nonuniform_expression = nonuniform_expression; + expr += to_function_args(args, forward); expr += ")"; // texture(samplerXShadow) returns float. shadowX() returns vec4. Swizzle here. @@ -5576,7 +6672,7 @@ std::string CompilerGLSL::to_texture_op(const Instruction &i, bool *forward, expr = remap_swizzle(result_type, 1, expr); } - if (!backend.support_small_type_sampling_result && result_type.width < 32) + if (!sparse && !backend.support_small_type_sampling_result && result_type.width < 32) { // Just value cast (narrowing) to expected type since we cannot rely on narrowing to work automatically. // Hopefully compiler picks this up and converts the texturing instruction to the appropriate precision. @@ -5598,16 +6694,38 @@ bool CompilerGLSL::expression_is_constant_null(uint32_t id) const return c->constant_is_null(); } +bool CompilerGLSL::expression_is_non_value_type_array(uint32_t ptr) +{ + auto &type = expression_type(ptr); + if (type.array.empty()) + return false; + + if (!backend.array_is_value_type) + return true; + + auto *var = maybe_get_backing_variable(ptr); + if (!var) + return false; + + auto &backed_type = get(var->basetype); + return !backend.buffer_offset_array_is_value_type && backed_type.basetype == SPIRType::Struct && + has_member_decoration(backed_type.self, 0, DecorationOffset); +} + // Returns the function name for a texture sampling function for the specified image and sampling characteristics. // For some subclasses, the function is a method on the specified image. -string CompilerGLSL::to_function_name(VariableID tex, const SPIRType &imgtype, bool is_fetch, bool is_gather, - bool is_proj, bool has_array_offsets, bool has_offset, bool has_grad, bool, - uint32_t lod, uint32_t minlod) +string CompilerGLSL::to_function_name(const TextureFunctionNameArguments &args) { - if (minlod != 0) - SPIRV_CROSS_THROW("Sparse texturing not yet supported."); + if (args.has_min_lod) + { + if (options.es) + SPIRV_CROSS_THROW("Sparse residency is not supported in ESSL."); + require_extension_internal("GL_ARB_sparse_texture_clamp"); + } string fname; + auto &imgtype = *args.base.imgtype; + VariableID tex = args.base.img; // textureLod on sampler2DArrayShadow and samplerCubeShadow does not exist in GLSL for some reason. // To emulate this, we will have to use textureGrad with a constant gradient of 0. @@ -5615,38 +6733,47 @@ string CompilerGLSL::to_function_name(VariableID tex, const SPIRType &imgtype, b // This happens for HLSL SampleCmpLevelZero on Texture2DArray and TextureCube. bool workaround_lod_array_shadow_as_grad = false; if (((imgtype.image.arrayed && imgtype.image.dim == Dim2D) || imgtype.image.dim == DimCube) && - image_is_comparison(imgtype, tex) && lod) + image_is_comparison(imgtype, tex) && args.lod) { - if (!expression_is_constant_null(lod)) + if (!expression_is_constant_null(args.lod)) { - SPIRV_CROSS_THROW( - "textureLod on sampler2DArrayShadow is not constant 0.0. This cannot be expressed in GLSL."); + SPIRV_CROSS_THROW("textureLod on sampler2DArrayShadow is not constant 0.0. This cannot be " + "expressed in GLSL."); } workaround_lod_array_shadow_as_grad = true; } - if (is_fetch) - fname += "texelFetch"; + if (args.is_sparse_feedback) + fname += "sparse"; + + if (args.base.is_fetch) + fname += args.is_sparse_feedback ? "TexelFetch" : "texelFetch"; else { - fname += "texture"; + fname += args.is_sparse_feedback ? "Texture" : "texture"; - if (is_gather) + if (args.base.is_gather) fname += "Gather"; - if (has_array_offsets) + if (args.has_array_offsets) fname += "Offsets"; - if (is_proj) + if (args.base.is_proj) fname += "Proj"; - if (has_grad || workaround_lod_array_shadow_as_grad) + if (args.has_grad || workaround_lod_array_shadow_as_grad) fname += "Grad"; - if (!!lod && !workaround_lod_array_shadow_as_grad) + if (args.lod != 0 && !workaround_lod_array_shadow_as_grad) fname += "Lod"; } - if (has_offset) + if (args.has_offset) fname += "Offset"; - return is_legacy() ? legacy_tex_op(fname, imgtype, lod, tex) : fname; + if (args.has_min_lod) + fname += "Clamp"; + + if (args.is_sparse_feedback || args.has_min_lod) + fname += "ARB"; + + return (is_legacy() && !args.base.is_gather) ? legacy_tex_op(fname, imgtype, tex) : fname; } std::string CompilerGLSL::convert_separate_image_to_expression(uint32_t id) @@ -5679,8 +6806,8 @@ std::string CompilerGLSL::convert_separate_image_to_expression(uint32_t id) else { if (!dummy_sampler_id) - SPIRV_CROSS_THROW( - "Cannot find dummy sampler ID. Was build_dummy_sampler_for_combined_images() called?"); + SPIRV_CROSS_THROW("Cannot find dummy sampler ID. Was " + "build_dummy_sampler_for_combined_images() called?"); return to_combined_image_sampler(id, dummy_sampler_id); } @@ -5691,18 +6818,23 @@ std::string CompilerGLSL::convert_separate_image_to_expression(uint32_t id) } // Returns the function args for a texture sampling function for the specified image and sampling characteristics. -string CompilerGLSL::to_function_args(VariableID img, const SPIRType &imgtype, bool is_fetch, bool is_gather, - bool is_proj, uint32_t coord, uint32_t coord_components, uint32_t dref, - uint32_t grad_x, uint32_t grad_y, uint32_t lod, uint32_t coffset, uint32_t offset, - uint32_t bias, uint32_t comp, uint32_t sample, uint32_t /*minlod*/, - bool *p_forward) +string CompilerGLSL::to_function_args(const TextureFunctionArguments &args, bool *p_forward) { + VariableID img = args.base.img; + auto &imgtype = *args.base.imgtype; + string farg_str; - if (is_fetch) + if (args.base.is_fetch) farg_str = convert_separate_image_to_expression(img); else farg_str = to_expression(img); + if (args.nonuniform_expression && farg_str.find_first_of('[') != string::npos) + { + // Only emit nonuniformEXT() wrapper if the underlying expression is arrayed in some way. + farg_str = join(backend.nonuniform_qualifier, "(", farg_str, ")"); + } + bool swizz_func = backend.swizzle_is_function; auto swizzle = [swizz_func](uint32_t comps, uint32_t in_comps) -> const char * { if (comps == in_comps) @@ -5721,19 +6853,20 @@ string CompilerGLSL::to_function_args(VariableID img, const SPIRType &imgtype, b } }; - bool forward = should_forward(coord); + bool forward = should_forward(args.coord); // The IR can give us more components than we need, so chop them off as needed. - auto swizzle_expr = swizzle(coord_components, expression_type(coord).vecsize); + auto swizzle_expr = swizzle(args.coord_components, expression_type(args.coord).vecsize); // Only enclose the UV expression if needed. - auto coord_expr = (*swizzle_expr == '\0') ? to_expression(coord) : (to_enclosed_expression(coord) + swizzle_expr); + auto coord_expr = + (*swizzle_expr == '\0') ? to_expression(args.coord) : (to_enclosed_expression(args.coord) + swizzle_expr); // texelFetch only takes int, not uint. - auto &coord_type = expression_type(coord); + auto &coord_type = expression_type(args.coord); if (coord_type.basetype == SPIRType::UInt) { auto expected_type = coord_type; - expected_type.vecsize = coord_components; + expected_type.vecsize = args.coord_components; expected_type.basetype = SPIRType::Int; coord_expr = bitcast_expression(expected_type, coord_type.basetype, coord_expr); } @@ -5744,21 +6877,22 @@ string CompilerGLSL::to_function_args(VariableID img, const SPIRType &imgtype, b // This happens for HLSL SampleCmpLevelZero on Texture2DArray and TextureCube. bool workaround_lod_array_shadow_as_grad = ((imgtype.image.arrayed && imgtype.image.dim == Dim2D) || imgtype.image.dim == DimCube) && - image_is_comparison(imgtype, img) && lod; + image_is_comparison(imgtype, img) && args.lod != 0; - if (dref) + if (args.dref) { - forward = forward && should_forward(dref); + forward = forward && should_forward(args.dref); // SPIR-V splits dref and coordinate. - if (is_gather || coord_components == 4) // GLSL also splits the arguments in two. Same for textureGather. + if (args.base.is_gather || + args.coord_components == 4) // GLSL also splits the arguments in two. Same for textureGather. { farg_str += ", "; - farg_str += to_expression(coord); + farg_str += to_expression(args.coord); farg_str += ", "; - farg_str += to_expression(dref); + farg_str += to_expression(args.dref); } - else if (is_proj) + else if (args.base.is_proj) { // Have to reshuffle so we get vec4(coord, dref, proj), special case. // Other shading languages splits up the arguments for coord and compare value like SPIR-V. @@ -5768,21 +6902,21 @@ string CompilerGLSL::to_function_args(VariableID img, const SPIRType &imgtype, b if (imgtype.image.dim == Dim1D) { // Could reuse coord_expr, but we will mess up the temporary usage checking. - farg_str += to_enclosed_expression(coord) + ".x"; + farg_str += to_enclosed_expression(args.coord) + ".x"; farg_str += ", "; farg_str += "0.0, "; - farg_str += to_expression(dref); + farg_str += to_expression(args.dref); farg_str += ", "; - farg_str += to_enclosed_expression(coord) + ".y)"; + farg_str += to_enclosed_expression(args.coord) + ".y)"; } else if (imgtype.image.dim == Dim2D) { // Could reuse coord_expr, but we will mess up the temporary usage checking. - farg_str += to_enclosed_expression(coord) + (swizz_func ? ".xy()" : ".xy"); + farg_str += to_enclosed_expression(args.coord) + (swizz_func ? ".xy()" : ".xy"); farg_str += ", "; - farg_str += to_expression(dref); + farg_str += to_expression(args.dref); farg_str += ", "; - farg_str += to_enclosed_expression(coord) + ".z)"; + farg_str += to_enclosed_expression(args.coord) + ".z)"; } else SPIRV_CROSS_THROW("Invalid type for textureProj with shadow."); @@ -5790,14 +6924,14 @@ string CompilerGLSL::to_function_args(VariableID img, const SPIRType &imgtype, b else { // Create a composite which merges coord/dref into a single vector. - auto type = expression_type(coord); - type.vecsize = coord_components + 1; + auto type = expression_type(args.coord); + type.vecsize = args.coord_components + 1; farg_str += ", "; farg_str += type_to_glsl_constructor(type); farg_str += "("; farg_str += coord_expr; farg_str += ", "; - farg_str += to_expression(dref); + farg_str += to_expression(args.dref); farg_str += ")"; } } @@ -5807,17 +6941,17 @@ string CompilerGLSL::to_function_args(VariableID img, const SPIRType &imgtype, b farg_str += coord_expr; } - if (grad_x || grad_y) + if (args.grad_x || args.grad_y) { - forward = forward && should_forward(grad_x); - forward = forward && should_forward(grad_y); + forward = forward && should_forward(args.grad_x); + forward = forward && should_forward(args.grad_y); farg_str += ", "; - farg_str += to_expression(grad_x); + farg_str += to_expression(args.grad_x); farg_str += ", "; - farg_str += to_expression(grad_y); + farg_str += to_expression(args.grad_y); } - if (lod) + if (args.lod) { if (workaround_lod_array_shadow_as_grad) { @@ -5830,63 +6964,73 @@ string CompilerGLSL::to_function_args(VariableID img, const SPIRType &imgtype, b } else { - if (check_explicit_lod_allowed(lod)) + forward = forward && should_forward(args.lod); + farg_str += ", "; + + auto &lod_expr_type = expression_type(args.lod); + + // Lod expression for TexelFetch in GLSL must be int, and only int. + if (args.base.is_fetch && imgtype.image.dim != DimBuffer && !imgtype.image.ms && + lod_expr_type.basetype != SPIRType::Int) { - forward = forward && should_forward(lod); - farg_str += ", "; - - auto &lod_expr_type = expression_type(lod); - - // Lod expression for TexelFetch in GLSL must be int, and only int. - if (is_fetch && imgtype.image.dim != DimBuffer && !imgtype.image.ms && - lod_expr_type.basetype != SPIRType::Int) - { - farg_str += join("int(", to_expression(lod), ")"); - } - else - { - farg_str += to_expression(lod); - } + farg_str += join("int(", to_expression(args.lod), ")"); + } + else + { + farg_str += to_expression(args.lod); } } } - else if (is_fetch && imgtype.image.dim != DimBuffer && !imgtype.image.ms) + else if (args.base.is_fetch && imgtype.image.dim != DimBuffer && !imgtype.image.ms) { // Lod argument is optional in OpImageFetch, but we require a LOD value, pick 0 as the default. farg_str += ", 0"; } - if (coffset) + if (args.coffset) { - forward = forward && should_forward(coffset); + forward = forward && should_forward(args.coffset); farg_str += ", "; - farg_str += to_expression(coffset); + farg_str += to_expression(args.coffset); } - else if (offset) + else if (args.offset) { - forward = forward && should_forward(offset); + forward = forward && should_forward(args.offset); farg_str += ", "; - farg_str += to_expression(offset); + farg_str += to_expression(args.offset); } - if (bias) + if (args.sample) { - forward = forward && should_forward(bias); farg_str += ", "; - farg_str += to_expression(bias); + farg_str += to_expression(args.sample); } - if (comp) + if (args.min_lod) { - forward = forward && should_forward(comp); farg_str += ", "; - farg_str += to_expression(comp); + farg_str += to_expression(args.min_lod); } - if (sample) + if (args.sparse_texel) { + // Sparse texel output parameter comes after everything else, except it's before the optional, component/bias arguments. farg_str += ", "; - farg_str += to_expression(sample); + farg_str += to_expression(args.sparse_texel); + } + + if (args.bias) + { + forward = forward && should_forward(args.bias); + farg_str += ", "; + farg_str += to_expression(args.bias); + } + + if (args.component && !expression_is_constant_null(args.component)) + { + forward = forward && should_forward(args.component); + farg_str += ", "; + farg_str += to_expression(args.component); } *p_forward = forward; @@ -5910,14 +7054,30 @@ void CompilerGLSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, { // FP fiddling case GLSLstd450Round: - emit_unary_func_op(result_type, id, args[0], "round"); + if (!is_legacy()) + emit_unary_func_op(result_type, id, args[0], "round"); + else + { + auto op0 = to_enclosed_expression(args[0]); + auto &op0_type = expression_type(args[0]); + auto expr = join("floor(", op0, " + ", type_to_glsl_constructor(op0_type), "(0.5))"); + bool forward = should_forward(args[0]); + emit_op(result_type, id, expr, forward); + inherit_expression_dependencies(id, args[0]); + } break; case GLSLstd450RoundEven: - if ((options.es && options.version >= 300) || (!options.es && options.version >= 130)) + if (!is_legacy()) emit_unary_func_op(result_type, id, args[0], "roundEven"); + else if (!options.es) + { + // This extension provides round() with round-to-even semantics. + require_extension_internal("GL_EXT_gpu_shader4"); + emit_unary_func_op(result_type, id, args[0], "round"); + } else - SPIRV_CROSS_THROW("roundEven supported only in ESSL 300 and GLSL 130 and up."); + SPIRV_CROSS_THROW("roundEven supported only in ESSL 300."); break; case GLSLstd450Trunc: @@ -6239,7 +7399,7 @@ void CompilerGLSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, { // Make sure we have a unique ID here to avoid aliasing the extra sub-expressions between clamp and NMin sub-op. // IDs cannot exceed 24 bits, so we can make use of the higher bits for some unique flags. - uint32_t &max_id = extra_sub_expressions[id | 0x80000000u]; + uint32_t &max_id = extra_sub_expressions[id | EXTRA_SUB_EXPRESSION_TYPE_AUX]; if (!max_id) max_id = ir.increase_bound_by(1); @@ -6440,8 +7600,8 @@ void CompilerGLSL::emit_subgroup_op(const Instruction &i) const uint32_t *ops = stream(i); auto op = static_cast(i.op); - if (!options.vulkan_semantics) - SPIRV_CROSS_THROW("Can only use subgroup operations in Vulkan semantics."); + if (!options.vulkan_semantics && !is_supported_subgroup_op_in_opengl(op)) + SPIRV_CROSS_THROW("This subgroup operation is only supported in Vulkan semantics."); // If we need to do implicit bitcasts, make sure we do it with the correct type. uint32_t integer_width = get_integer_width_for_instruction(i); @@ -6451,18 +7611,39 @@ void CompilerGLSL::emit_subgroup_op(const Instruction &i) switch (op) { case OpGroupNonUniformElect: - require_extension_internal("GL_KHR_shader_subgroup_basic"); + request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupElect); + break; + + case OpGroupNonUniformBallotBitCount: + { + const GroupOperation operation = static_cast(ops[3]); + if (operation == GroupOperationReduce) + request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupBallotBitCount); + else if (operation == GroupOperationInclusiveScan || operation == GroupOperationExclusiveScan) + request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupInverseBallot_InclBitCount_ExclBitCout); + } + break; + + case OpGroupNonUniformBallotBitExtract: + request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupBallotBitExtract); + break; + + case OpGroupNonUniformInverseBallot: + request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupInverseBallot_InclBitCount_ExclBitCout); + break; + + case OpGroupNonUniformBallot: + request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupBallot); + break; + + case OpGroupNonUniformBallotFindLSB: + case OpGroupNonUniformBallotFindMSB: + request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupBallotFindLSB_MSB); break; case OpGroupNonUniformBroadcast: case OpGroupNonUniformBroadcastFirst: - case OpGroupNonUniformBallot: - case OpGroupNonUniformInverseBallot: - case OpGroupNonUniformBallotBitExtract: - case OpGroupNonUniformBallotBitCount: - case OpGroupNonUniformBallotFindLSB: - case OpGroupNonUniformBallotFindMSB: - require_extension_internal("GL_KHR_shader_subgroup_ballot"); + request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupBroadcast_First); break; case OpGroupNonUniformShuffle: @@ -6478,8 +7659,14 @@ void CompilerGLSL::emit_subgroup_op(const Instruction &i) case OpGroupNonUniformAll: case OpGroupNonUniformAny: case OpGroupNonUniformAllEqual: - require_extension_internal("GL_KHR_shader_subgroup_vote"); - break; + { + const SPIRType &type = expression_type(ops[3]); + if (type.basetype == SPIRType::BaseType::Boolean && type.vecsize == 1u) + request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupAll_Any_AllEqualBool); + else + request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupAllEqualT); + } + break; case OpGroupNonUniformFAdd: case OpGroupNonUniformFMul: @@ -6494,6 +7681,9 @@ void CompilerGLSL::emit_subgroup_op(const Instruction &i) case OpGroupNonUniformBitwiseAnd: case OpGroupNonUniformBitwiseOr: case OpGroupNonUniformBitwiseXor: + case OpGroupNonUniformLogicalAnd: + case OpGroupNonUniformLogicalOr: + case OpGroupNonUniformLogicalXor: { auto operation = static_cast(ops[3]); if (operation == GroupOperationClusteredReduce) @@ -6522,7 +7712,7 @@ void CompilerGLSL::emit_subgroup_op(const Instruction &i) uint32_t result_type = ops[0]; uint32_t id = ops[1]; - auto scope = static_cast(get(ops[2]).scalar()); + auto scope = static_cast(evaluate_constant_u32(ops[2])); if (scope != ScopeSubgroup) SPIRV_CROSS_THROW("Only subgroup scope is supported."); @@ -6650,13 +7840,16 @@ case OpGroupNonUniform##op: \ GLSL_GROUP_OP(BitwiseAnd, And) GLSL_GROUP_OP(BitwiseOr, Or) GLSL_GROUP_OP(BitwiseXor, Xor) + GLSL_GROUP_OP(LogicalAnd, And) + GLSL_GROUP_OP(LogicalOr, Or) + GLSL_GROUP_OP(LogicalXor, Xor) #undef GLSL_GROUP_OP #undef GLSL_GROUP_OP_CAST // clang-format on case OpGroupNonUniformQuadSwap: { - uint32_t direction = get(ops[4]).scalar(); + uint32_t direction = evaluate_constant_u32(ops[4]); if (direction == 0) emit_unary_func_op(result_type, id, ops[3], "subgroupQuadSwapHorizontal"); else if (direction == 1) @@ -6837,8 +8030,8 @@ string CompilerGLSL::builtin_to_glsl(BuiltIn builtin, StorageClass storage) return "gl_CullDistance"; case BuiltInVertexId: if (options.vulkan_semantics) - SPIRV_CROSS_THROW( - "Cannot implement gl_VertexID in Vulkan GLSL. This shader was created with GL semantics."); + SPIRV_CROSS_THROW("Cannot implement gl_VertexID in Vulkan GLSL. This shader was created " + "with GL semantics."); return "gl_VertexID"; case BuiltInInstanceId: if (options.vulkan_semantics) @@ -6853,10 +8046,14 @@ string CompilerGLSL::builtin_to_glsl(BuiltIn builtin, StorageClass storage) break; default: - SPIRV_CROSS_THROW( - "Cannot implement gl_InstanceID in Vulkan GLSL. This shader was created with GL semantics."); + SPIRV_CROSS_THROW("Cannot implement gl_InstanceID in Vulkan GLSL. This shader was " + "created with GL semantics."); } } + if (!options.es && options.version < 140) + { + require_extension_internal("GL_ARB_draw_instanced"); + } return "gl_InstanceID"; case BuiltInVertexIndex: if (options.vulkan_semantics) @@ -6866,7 +8063,13 @@ string CompilerGLSL::builtin_to_glsl(BuiltIn builtin, StorageClass storage) case BuiltInInstanceIndex: if (options.vulkan_semantics) return "gl_InstanceIndex"; - else if (options.vertex.support_nonzero_base_instance) + + if (!options.es && options.version < 140) + { + require_extension_internal("GL_ARB_draw_instanced"); + } + + if (options.vertex.support_nonzero_base_instance) { if (!options.vulkan_semantics) { @@ -7018,87 +8221,70 @@ string CompilerGLSL::builtin_to_glsl(BuiltIn builtin, StorageClass storage) } case BuiltInNumSubgroups: - if (!options.vulkan_semantics) - SPIRV_CROSS_THROW("Need Vulkan semantics for subgroup."); - require_extension_internal("GL_KHR_shader_subgroup_basic"); + request_subgroup_feature(ShaderSubgroupSupportHelper::NumSubgroups); return "gl_NumSubgroups"; case BuiltInSubgroupId: - if (!options.vulkan_semantics) - SPIRV_CROSS_THROW("Need Vulkan semantics for subgroup."); - require_extension_internal("GL_KHR_shader_subgroup_basic"); + request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupID); return "gl_SubgroupID"; case BuiltInSubgroupSize: - if (!options.vulkan_semantics) - SPIRV_CROSS_THROW("Need Vulkan semantics for subgroup."); - require_extension_internal("GL_KHR_shader_subgroup_basic"); + request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupSize); return "gl_SubgroupSize"; case BuiltInSubgroupLocalInvocationId: - if (!options.vulkan_semantics) - SPIRV_CROSS_THROW("Need Vulkan semantics for subgroup."); - require_extension_internal("GL_KHR_shader_subgroup_basic"); + request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupInvocationID); return "gl_SubgroupInvocationID"; case BuiltInSubgroupEqMask: - if (!options.vulkan_semantics) - SPIRV_CROSS_THROW("Need Vulkan semantics for subgroup."); - require_extension_internal("GL_KHR_shader_subgroup_ballot"); + request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupMask); return "gl_SubgroupEqMask"; case BuiltInSubgroupGeMask: - if (!options.vulkan_semantics) - SPIRV_CROSS_THROW("Need Vulkan semantics for subgroup."); - require_extension_internal("GL_KHR_shader_subgroup_ballot"); + request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupMask); return "gl_SubgroupGeMask"; case BuiltInSubgroupGtMask: - if (!options.vulkan_semantics) - SPIRV_CROSS_THROW("Need Vulkan semantics for subgroup."); - require_extension_internal("GL_KHR_shader_subgroup_ballot"); + request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupMask); return "gl_SubgroupGtMask"; case BuiltInSubgroupLeMask: - if (!options.vulkan_semantics) - SPIRV_CROSS_THROW("Need Vulkan semantics for subgroup."); - require_extension_internal("GL_KHR_shader_subgroup_ballot"); + request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupMask); return "gl_SubgroupLeMask"; case BuiltInSubgroupLtMask: - if (!options.vulkan_semantics) - SPIRV_CROSS_THROW("Need Vulkan semantics for subgroup."); - require_extension_internal("GL_KHR_shader_subgroup_ballot"); + request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupMask); return "gl_SubgroupLtMask"; - case BuiltInLaunchIdNV: - return "gl_LaunchIDNV"; - case BuiltInLaunchSizeNV: - return "gl_LaunchSizeNV"; - case BuiltInWorldRayOriginNV: - return "gl_WorldRayOriginNV"; - case BuiltInWorldRayDirectionNV: - return "gl_WorldRayDirectionNV"; - case BuiltInObjectRayOriginNV: - return "gl_ObjectRayOriginNV"; - case BuiltInObjectRayDirectionNV: - return "gl_ObjectRayDirectionNV"; - case BuiltInRayTminNV: - return "gl_RayTminNV"; - case BuiltInRayTmaxNV: - return "gl_RayTmaxNV"; - case BuiltInInstanceCustomIndexNV: - return "gl_InstanceCustomIndexNV"; - case BuiltInObjectToWorldNV: - return "gl_ObjectToWorldNV"; - case BuiltInWorldToObjectNV: - return "gl_WorldToObjectNV"; + case BuiltInLaunchIdKHR: + return ray_tracing_is_khr ? "gl_LaunchIDEXT" : "gl_LaunchIDNV"; + case BuiltInLaunchSizeKHR: + return ray_tracing_is_khr ? "gl_LaunchSizeEXT" : "gl_LaunchSizeNV"; + case BuiltInWorldRayOriginKHR: + return ray_tracing_is_khr ? "gl_WorldRayOriginEXT" : "gl_WorldRayOriginNV"; + case BuiltInWorldRayDirectionKHR: + return ray_tracing_is_khr ? "gl_WorldRayDirectionEXT" : "gl_WorldRayDirectionNV"; + case BuiltInObjectRayOriginKHR: + return ray_tracing_is_khr ? "gl_ObjectRayOriginEXT" : "gl_ObjectRayOriginNV"; + case BuiltInObjectRayDirectionKHR: + return ray_tracing_is_khr ? "gl_ObjectRayDirectionEXT" : "gl_ObjectRayDirectionNV"; + case BuiltInRayTminKHR: + return ray_tracing_is_khr ? "gl_RayTminEXT" : "gl_RayTminNV"; + case BuiltInRayTmaxKHR: + return ray_tracing_is_khr ? "gl_RayTmaxEXT" : "gl_RayTmaxNV"; + case BuiltInInstanceCustomIndexKHR: + return ray_tracing_is_khr ? "gl_InstanceCustomIndexEXT" : "gl_InstanceCustomIndexNV"; + case BuiltInObjectToWorldKHR: + return ray_tracing_is_khr ? "gl_ObjectToWorldEXT" : "gl_ObjectToWorldNV"; + case BuiltInWorldToObjectKHR: + return ray_tracing_is_khr ? "gl_WorldToObjectEXT" : "gl_WorldToObjectNV"; case BuiltInHitTNV: + // gl_HitTEXT is an alias of RayTMax in KHR. return "gl_HitTNV"; - case BuiltInHitKindNV: - return "gl_HitKindNV"; - case BuiltInIncomingRayFlagsNV: - return "gl_IncomingRayFlagsNV"; + case BuiltInHitKindKHR: + return ray_tracing_is_khr ? "gl_HitKindEXT" : "gl_HitKindNV"; + case BuiltInIncomingRayFlagsKHR: + return ray_tracing_is_khr ? "gl_IncomingRayFlagsEXT" : "gl_IncomingRayFlagsNV"; case BuiltInBaryCoordNV: { @@ -7199,6 +8385,7 @@ string CompilerGLSL::access_chain_internal(uint32_t base, const uint32_t *indice bool chain_only = (flags & ACCESS_CHAIN_CHAIN_ONLY_BIT) != 0; bool ptr_chain = (flags & ACCESS_CHAIN_PTR_CHAIN_BIT) != 0; bool register_expression_read = (flags & ACCESS_CHAIN_SKIP_REGISTER_EXPRESSION_READ_BIT) == 0; + bool flatten_member_reference = (flags & ACCESS_CHAIN_FLATTEN_ALL_MEMBERS_BIT) != 0; if (!chain_only) { @@ -7391,7 +8578,7 @@ string CompilerGLSL::access_chain_internal(uint32_t base, const uint32_t *indice else if (type->basetype == SPIRType::Struct) { if (!is_literal) - index = get(index).scalar(); + index = evaluate_constant_u32(index); if (index >= type->member_types.size()) SPIRV_CROSS_THROW("Member index is out of bounds!"); @@ -7413,6 +8600,8 @@ string CompilerGLSL::access_chain_internal(uint32_t base, const uint32_t *indice string qual_mbr_name = get_member_qualified_name(type_id, index); if (!qual_mbr_name.empty()) expr = qual_mbr_name; + else if (flatten_member_reference) + expr += join("_", to_member_name(*type, index)); else expr += to_member_reference(base, *type, index, ptr_chain); } @@ -7461,6 +8650,23 @@ string CompilerGLSL::access_chain_internal(uint32_t base, const uint32_t *indice } } + // Internally, access chain implementation can also be used on composites, + // ignore scalar access workarounds in this case. + StorageClass effective_storage; + if (expression_type(base).pointer) + effective_storage = get_expression_effective_storage_class(base); + else + effective_storage = StorageClassGeneric; + + if (!row_major_matrix_needs_conversion) + { + // On some backends, we might not be able to safely access individual scalars in a vector. + // To work around this, we might have to cast the access chain reference to something which can, + // like a pointer to scalar, which we can then index into. + prepare_access_chain_for_scalar_access(expr, get(type->parent_type), effective_storage, + is_packed); + } + if (is_literal && !is_packed && !row_major_matrix_needs_conversion) { expr += "."; @@ -7492,6 +8698,12 @@ string CompilerGLSL::access_chain_internal(uint32_t base, const uint32_t *indice expr += "]"; } + if (row_major_matrix_needs_conversion) + { + prepare_access_chain_for_scalar_access(expr, get(type->parent_type), effective_storage, + is_packed); + } + expr += deferred_index; row_major_matrix_needs_conversion = false; @@ -7522,10 +8734,15 @@ string CompilerGLSL::access_chain_internal(uint32_t base, const uint32_t *indice return expr; } -string CompilerGLSL::to_flattened_struct_member(const SPIRVariable &var, uint32_t index) +void CompilerGLSL::prepare_access_chain_for_scalar_access(std::string &, const SPIRType &, spv::StorageClass, bool &) { - auto &type = get(var.basetype); - return sanitize_underscores(join(to_name(var.self), "_", to_member_name(type, index))); +} + +string CompilerGLSL::to_flattened_struct_member(const string &basename, const SPIRType &type, uint32_t index) +{ + auto ret = join(basename, "_", to_member_name(type, index)); + ParsedIR::sanitize_underscores(ret); + return ret; } string CompilerGLSL::access_chain(uint32_t base, const uint32_t *indices, uint32_t count, const SPIRType &target_type, @@ -7554,13 +8771,24 @@ string CompilerGLSL::access_chain(uint32_t base, const uint32_t *indices, uint32 if (ptr_chain) flags |= ACCESS_CHAIN_PTR_CHAIN_BIT; + if (flattened_structs[base]) + { + flags |= ACCESS_CHAIN_FLATTEN_ALL_MEMBERS_BIT; + if (meta) + meta->flattened_struct = target_type.basetype == SPIRType::Struct; + } + auto chain = access_chain_internal(base, indices, count, flags, nullptr).substr(1); if (meta) { meta->need_transpose = false; meta->storage_is_packed = false; } - return sanitize_underscores(join(to_name(base), "_", chain)); + + auto basename = to_flattened_access_chain_expression(base); + auto ret = join(basename, "_", chain); + ParsedIR::sanitize_underscores(ret); + return ret; } else { @@ -7571,48 +8799,73 @@ string CompilerGLSL::access_chain(uint32_t base, const uint32_t *indices, uint32 } } -string CompilerGLSL::load_flattened_struct(SPIRVariable &var) +string CompilerGLSL::load_flattened_struct(const string &basename, const SPIRType &type) { - auto expr = type_to_glsl_constructor(get(var.basetype)); + auto expr = type_to_glsl_constructor(type); expr += '('; - auto &type = get(var.basetype); for (uint32_t i = 0; i < uint32_t(type.member_types.size()); i++) { if (i) expr += ", "; - // Flatten the varyings. - // Apply name transformation for flattened I/O blocks. - expr += to_flattened_struct_member(var, i); + auto &member_type = get(type.member_types[i]); + if (member_type.basetype == SPIRType::Struct) + expr += load_flattened_struct(to_flattened_struct_member(basename, type, i), member_type); + else + expr += to_flattened_struct_member(basename, type, i); } expr += ')'; return expr; } -void CompilerGLSL::store_flattened_struct(SPIRVariable &var, uint32_t value) +std::string CompilerGLSL::to_flattened_access_chain_expression(uint32_t id) { - // We're trying to store a structure which has been flattened. - // Need to copy members one by one. - auto rhs = to_expression(value); + // Do not use to_expression as that will unflatten access chains. + string basename; + if (const auto *var = maybe_get(id)) + basename = to_name(var->self); + else if (const auto *expr = maybe_get(id)) + basename = expr->expression; + else + basename = to_expression(id); - // Store result locally. - // Since we're declaring a variable potentially multiple times here, - // store the variable in an isolated scope. - begin_scope(); - statement(variable_decl_function_local(var), " = ", rhs, ";"); + return basename; +} - auto &type = get(var.basetype); - for (uint32_t i = 0; i < uint32_t(type.member_types.size()); i++) +void CompilerGLSL::store_flattened_struct(const string &basename, uint32_t rhs_id, const SPIRType &type, + const SmallVector &indices) +{ + SmallVector sub_indices = indices; + sub_indices.push_back(0); + + auto *member_type = &type; + for (auto &index : indices) + member_type = &get(member_type->member_types[index]); + + for (uint32_t i = 0; i < uint32_t(member_type->member_types.size()); i++) { - // Flatten the varyings. - // Apply name transformation for flattened I/O blocks. + sub_indices.back() = i; + auto lhs = join(basename, "_", to_member_name(*member_type, i)); + ParsedIR::sanitize_underscores(lhs); - auto lhs = sanitize_underscores(join(to_name(var.self), "_", to_member_name(type, i))); - rhs = join(to_name(var.self), ".", to_member_name(type, i)); - statement(lhs, " = ", rhs, ";"); + if (get(member_type->member_types[i]).basetype == SPIRType::Struct) + { + store_flattened_struct(lhs, rhs_id, type, sub_indices); + } + else + { + auto rhs = to_expression(rhs_id) + to_multi_member_reference(type, sub_indices); + statement(lhs, " = ", rhs, ";"); + } } - end_scope(); +} + +void CompilerGLSL::store_flattened_struct(uint32_t lhs_id, uint32_t value) +{ + auto &type = expression_type(lhs_id); + auto basename = to_flattened_access_chain_expression(lhs_id); + store_flattened_struct(basename, value, type, {}); } std::string CompilerGLSL::flattened_access_chain(uint32_t base, const uint32_t *indices, uint32_t count, @@ -7798,10 +9051,11 @@ std::pair CompilerGLSL::flattened_access_chain_offset( // Dynamic array access. if (array_stride % word_stride) { - SPIRV_CROSS_THROW( - "Array stride for dynamic indexing must be divisible by the size of a 4-component vector. " - "Likely culprit here is a float or vec2 array inside a push constant block which is std430. " - "This cannot be flattened. Try using std140 layout instead."); + SPIRV_CROSS_THROW("Array stride for dynamic indexing must be divisible by the size " + "of a 4-component vector. " + "Likely culprit here is a float or vec2 array inside a push " + "constant block which is std430. " + "This cannot be flattened. Try using std140 layout instead."); } expr += to_enclosed_expression(index); @@ -7824,10 +9078,11 @@ std::pair CompilerGLSL::flattened_access_chain_offset( // Dynamic array access. if (array_stride % word_stride) { - SPIRV_CROSS_THROW( - "Array stride for dynamic indexing must be divisible by the size of a 4-component vector. " - "Likely culprit here is a float or vec2 array inside a push constant block which is std430. " - "This cannot be flattened. Try using std140 layout instead."); + SPIRV_CROSS_THROW("Array stride for dynamic indexing must be divisible by the size " + "of a 4-component vector. " + "Likely culprit here is a float or vec2 array inside a push " + "constant block which is std430. " + "This cannot be flattened. Try using std140 layout instead."); } expr += to_enclosed_expression(index, false); @@ -7846,7 +9101,7 @@ std::pair CompilerGLSL::flattened_access_chain_offset( // We also check if this member is a builtin, since we then replace the entire expression with the builtin one. else if (type->basetype == SPIRType::Struct) { - index = get(index).scalar(); + index = evaluate_constant_u32(index); if (index >= type->member_types.size()) SPIRV_CROSS_THROW("Member index is out of bounds!"); @@ -7874,7 +9129,7 @@ std::pair CompilerGLSL::flattened_access_chain_offset( auto *constant = maybe_get(index); if (constant) { - index = get(index).scalar(); + index = evaluate_constant_u32(index); offset += index * (row_major_matrix_needs_conversion ? (type->width / 8) : matrix_stride); } else @@ -7883,10 +9138,10 @@ std::pair CompilerGLSL::flattened_access_chain_offset( // Dynamic array access. if (indexing_stride % word_stride) { - SPIRV_CROSS_THROW( - "Matrix stride for dynamic indexing must be divisible by the size of a 4-component vector. " - "Likely culprit here is a row-major matrix being accessed dynamically. " - "This cannot be flattened. Try using std140 layout instead."); + SPIRV_CROSS_THROW("Matrix stride for dynamic indexing must be divisible by the size of a " + "4-component vector. " + "Likely culprit here is a row-major matrix being accessed dynamically. " + "This cannot be flattened. Try using std140 layout instead."); } expr += to_enclosed_expression(index, false); @@ -7903,7 +9158,7 @@ std::pair CompilerGLSL::flattened_access_chain_offset( auto *constant = maybe_get(index); if (constant) { - index = get(index).scalar(); + index = evaluate_constant_u32(index); offset += index * (row_major_matrix_needs_conversion ? matrix_stride : (type->width / 8)); } else @@ -7913,9 +9168,9 @@ std::pair CompilerGLSL::flattened_access_chain_offset( // Dynamic array access. if (indexing_stride % word_stride) { - SPIRV_CROSS_THROW( - "Stride for dynamic vector indexing must be divisible by the size of a 4-component vector. " - "This cannot be flattened in legacy targets."); + SPIRV_CROSS_THROW("Stride for dynamic vector indexing must be divisible by the " + "size of a 4-component vector. " + "This cannot be flattened in legacy targets."); } expr += to_enclosed_expression(index, false); @@ -8019,6 +9274,13 @@ void CompilerGLSL::track_expression_read(uint32_t id) auto &v = expression_usage_counts[id]; v++; + // If we create an expression outside a loop, + // but access it inside a loop, we're implicitly reading it multiple times. + // If the expression in question is expensive, we should hoist it out to avoid relying on loop-invariant code motion + // working inside the backend compiler. + if (expression_read_implies_multiple_reads(id)) + v++; + if (v >= 2) { //if (v == 2) @@ -8284,7 +9546,10 @@ string CompilerGLSL::build_composite_combiner(uint32_t return_type, const uint32 if (i) op += ", "; - subop = to_composite_constructor_expression(elems[i]); + + bool uses_buffer_offset = + type.basetype == SPIRType::Struct && has_member_decoration(type.self, i, DecorationOffset); + subop = to_composite_constructor_expression(elems[i], uses_buffer_offset); } base = e ? e->base_expression : ID(0); @@ -8414,17 +9679,20 @@ void CompilerGLSL::emit_store_statement(uint32_t lhs_expression, uint32_t rhs_ex { handle_store_to_invariant_variable(lhs_expression, rhs_expression); - auto lhs = to_dereferenced_expression(lhs_expression); + if (!unroll_array_to_complex_store(lhs_expression, rhs_expression)) + { + auto lhs = to_dereferenced_expression(lhs_expression); - // We might need to bitcast in order to store to a builtin. - bitcast_to_builtin_store(lhs_expression, rhs, expression_type(rhs_expression)); + // We might need to cast in order to store to a builtin. + cast_to_builtin_store(lhs_expression, rhs, expression_type(rhs_expression)); - // Tries to optimize assignments like " = op expr". - // While this is purely cosmetic, this is important for legacy ESSL where loop - // variable increments must be in either i++ or i += const-expr. - // Without this, we end up with i = i + 1, which is correct GLSL, but not correct GLES 2.0. - if (!optimize_read_modify_write(expression_type(rhs_expression), lhs, rhs)) - statement(lhs, " = ", rhs, ";"); + // Tries to optimize assignments like " = op expr". + // While this is purely cosmetic, this is important for legacy ESSL where loop + // variable increments must be in either i++ or i += const-expr. + // Without this, we end up with i = i + 1, which is correct GLSL, but not correct GLES 2.0. + if (!optimize_read_modify_write(expression_type(rhs_expression), lhs, rhs)) + statement(lhs, " = ", rhs, ";"); + } register_write(lhs_expression); } } @@ -8571,15 +9839,24 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) expr = to_unpacked_expression(ptr); } - // We might need to bitcast in order to load from a builtin. - bitcast_from_builtin_load(ptr, expr, get(result_type)); + auto &type = get(result_type); + auto &expr_type = expression_type(ptr); + + // If the expression has more vector components than the result type, insert + // a swizzle. This shouldn't happen normally on valid SPIR-V, but it might + // happen with e.g. the MSL backend replacing the type of an input variable. + if (expr_type.vecsize > type.vecsize) + expr = enclose_expression(expr + vector_swizzle(type.vecsize, 0)); + + // We might need to cast in order to load from a builtin. + cast_from_builtin_load(ptr, expr, type); // We might be trying to load a gl_Position[N], where we should be // doing float4[](gl_in[i].gl_Position, ...) instead. // Similar workarounds are required for input arrays in tessellation. + // Also, loading from gl_SampleMask array needs special unroll. unroll_array_from_complex_load(id, ptr, expr); - auto &type = get(result_type); // Shouldn't need to check for ID, but current glslang codegen requires it in some cases // when loading Image/Sampler descriptors. It does not hurt to check ID as well. if (has_decoration(id, DecorationNonUniformEXT) || has_decoration(ptr, DecorationNonUniformEXT)) @@ -8591,20 +9868,24 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) if (forward && ptr_expression) ptr_expression->need_transpose = old_need_transpose; + bool flattened = ptr_expression && flattened_buffer_blocks.count(ptr_expression->loaded_from) != 0; + + if (backend.needs_row_major_load_workaround && !is_non_native_row_major_matrix(ptr) && !flattened) + rewrite_load_for_wrapped_row_major(expr, result_type, ptr); + // By default, suppress usage tracking since using same expression multiple times does not imply any extra work. // However, if we try to load a complex, composite object from a flattened buffer, // we should avoid emitting the same code over and over and lower the result to a temporary. - bool usage_tracking = ptr_expression && flattened_buffer_blocks.count(ptr_expression->loaded_from) != 0 && - (type.basetype == SPIRType::Struct || (type.columns > 1)); + bool usage_tracking = flattened && (type.basetype == SPIRType::Struct || (type.columns > 1)); SPIRExpression *e = nullptr; - if (!backend.array_is_value_type && !type.array.empty() && !forward) + if (!forward && expression_is_non_value_type_array(ptr)) { // Complicated load case where we need to make a copy of ptr, but we cannot, because // it is an array, and our backend does not support arrays as value types. // Emit the temporary, and copy it explicitly. e = &emit_uninitialized_temporary_expression(result_type, id); - emit_array_copy(to_expression(id), ptr, StorageClassFunction, get_backing_variable_storage(ptr)); + emit_array_copy(to_expression(id), ptr, StorageClassFunction, get_expression_effective_storage_class(ptr)); } else e = &emit_op(result_type, id, expr, forward, !usage_tracking); @@ -8664,6 +9945,8 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) set_extended_decoration(ops[1], SPIRVCrossDecorationPhysicalTypeID, meta.storage_physical_type); if (meta.storage_is_invariant) set_decoration(ops[1], DecorationInvariant); + if (meta.flattened_struct) + flattened_structs[ops[1]] = true; // If we have some expression dependencies in our access chain, this access chain is technically a forwarded // temporary which could be subject to invalidation. @@ -8701,9 +9984,9 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) { // Skip the write. } - else if (var && flattened_structs.count(ops[0])) + else if (flattened_structs.count(ops[0])) { - store_flattened_struct(*var, ops[1]); + store_flattened_struct(ops[0], ops[1]); register_write(ops[0]); } else @@ -8955,7 +10238,7 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) statement(declare_temporary(result_type, id), to_expression(vec), ";"); set(id, to_name(id), result_type, true); auto chain = access_chain_internal(id, &index, 1, 0, nullptr); - statement(chain, " = ", to_expression(comp), ";"); + statement(chain, " = ", to_unpacked_expression(comp), ";"); break; } @@ -8984,7 +10267,8 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) // Do not allow base expression for struct members. We risk doing "swizzle" optimizations in this case. auto &composite_type = expression_type(ops[2]); - if (composite_type.basetype == SPIRType::Struct || !composite_type.array.empty()) + bool composite_type_is_complex = composite_type.basetype == SPIRType::Struct || !composite_type.array.empty(); + if (composite_type_is_complex) allow_base_expression = false; // Packed expressions or physical ID mapped expressions cannot be split up. @@ -8999,10 +10283,17 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) AccessChainMeta meta; SPIRExpression *e = nullptr; + auto *c = maybe_get(ops[2]); - // Only apply this optimization if result is scalar. - if (allow_base_expression && should_forward(ops[2]) && type.vecsize == 1 && type.columns == 1 && length == 1) + if (c && !c->specialization && !composite_type_is_complex) { + auto expr = to_extract_constant_composite_expression(result_type, *c, ops + 3, length); + e = &emit_op(result_type, id, expr, true, true); + } + else if (allow_base_expression && should_forward(ops[2]) && type.vecsize == 1 && type.columns == 1 && length == 1) + { + // Only apply this optimization if result is scalar. + // We want to split the access chain from the base. // This is so we can later combine different CompositeExtract results // with CompositeConstruct without emitting code like @@ -9057,7 +10348,7 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) statement(declare_temporary(result_type, id), to_expression(composite), ";"); set(id, to_name(id), result_type, true); auto chain = access_chain_internal(id, elems, length, ACCESS_CHAIN_INDEX_IS_LITERAL_BIT, nullptr); - statement(chain, " = ", to_expression(obj), ";"); + statement(chain, " = ", to_unpacked_expression(obj), ";"); break; } @@ -9068,10 +10359,27 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) uint32_t rhs = ops[1]; if (lhs != rhs) { - flush_variable_declaration(lhs); - flush_variable_declaration(rhs); - statement(to_expression(lhs), " = ", to_expression(rhs), ";"); - register_write(lhs); + uint32_t &tmp_id = extra_sub_expressions[instruction.offset | EXTRA_SUB_EXPRESSION_TYPE_STREAM_OFFSET]; + if (!tmp_id) + tmp_id = ir.increase_bound_by(1); + uint32_t tmp_type_id = expression_type(rhs).parent_type; + + EmbeddedInstruction fake_load, fake_store; + fake_load.op = OpLoad; + fake_load.length = 3; + fake_load.ops.push_back(tmp_type_id); + fake_load.ops.push_back(tmp_id); + fake_load.ops.push_back(rhs); + + fake_store.op = OpStore; + fake_store.length = 2; + fake_store.ops.push_back(lhs); + fake_store.ops.push_back(tmp_id); + + // Load and Store do a *lot* of workarounds, and we'd like to reuse them as much as possible. + // Synthesize a fake Load and Store pair for CopyMemory. + emit_instruction(fake_load); + emit_instruction(fake_store); } break; } @@ -9097,12 +10405,22 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) bool pointer = get(result_type).pointer; auto *chain = maybe_get(rhs); + auto *imgsamp = maybe_get(rhs); if (chain) { // Cannot lower to a SPIRExpression, just copy the object. auto &e = set(id, *chain); e.self = id; } + else if (imgsamp) + { + // Cannot lower to a SPIRExpression, just copy the object. + // GLSL does not currently use this type and will never get here, but MSL does. + // Handled here instead of CompilerMSL for better integration and general handling, + // and in case GLSL or other subclasses require it in the future. + auto &e = set(id, *imgsamp); + e.self = id; + } else if (expression_is_lvalue(rhs) && !pointer) { // Need a copy. @@ -9328,7 +10646,32 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) break; case OpTranspose: - GLSL_UFOP(transpose); + if (options.version < 120) // Matches GLSL 1.10 / ESSL 1.00 + { + // transpose() is not available, so instead, flip need_transpose, + // which can later be turned into an emulated transpose op by + // convert_row_major_matrix(), if necessary. + uint32_t result_type = ops[0]; + uint32_t result_id = ops[1]; + uint32_t input = ops[2]; + + // Force need_transpose to false temporarily to prevent + // to_expression() from doing the transpose. + bool need_transpose = false; + auto *input_e = maybe_get(input); + if (input_e) + swap(need_transpose, input_e->need_transpose); + + bool forward = should_forward(input); + auto &e = emit_op(result_type, result_id, to_expression(input), forward); + e.need_transpose = !need_transpose; + + // Restore the old need_transpose flag. + if (input_e) + input_e->need_transpose = need_transpose; + } + else + GLSL_UFOP(transpose); break; case OpSRem: @@ -9522,7 +10865,7 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) { auto &type = get(ops[0]); if (type.vecsize > 1) - GLSL_UFOP(not); + GLSL_UFOP(not ); else GLSL_UOP(!); break; @@ -10081,7 +11424,29 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) case OpImageGather: case OpImageDrefGather: // Gets a bit hairy, so move this to a separate instruction. - emit_texture_op(instruction); + emit_texture_op(instruction, false); + break; + + case OpImageSparseSampleExplicitLod: + case OpImageSparseSampleProjExplicitLod: + case OpImageSparseSampleDrefExplicitLod: + case OpImageSparseSampleProjDrefExplicitLod: + case OpImageSparseSampleImplicitLod: + case OpImageSparseSampleProjImplicitLod: + case OpImageSparseSampleDrefImplicitLod: + case OpImageSparseSampleProjDrefImplicitLod: + case OpImageSparseFetch: + case OpImageSparseGather: + case OpImageSparseDrefGather: + // Gets a bit hairy, so move this to a separate instruction. + emit_texture_op(instruction, true); + break; + + case OpImageSparseTexelsResident: + if (options.es) + SPIRV_CROSS_THROW("Sparse feedback is not supported in GLSL."); + require_extension_internal("GL_ARB_sparse_texture2"); + emit_unary_func_op_cast(ops[0], ops[1], ops[2], "sparseTexelsResidentARB", int_type, SPIRType::Boolean); break; case OpImage: @@ -10163,8 +11528,19 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) { uint32_t result_type = ops[0]; uint32_t id = ops[1]; + uint32_t img = ops[2]; - auto expr = join("textureSize(", convert_separate_image_to_expression(ops[2]), ", ", + std::string fname = "textureSize"; + if (is_legacy_desktop()) + { + auto &type = expression_type(img); + auto &imgtype = get(type.self); + fname = legacy_tex_op(fname, imgtype, img); + } + else if (is_legacy_es()) + SPIRV_CROSS_THROW("textureSize is not supported in ESSL 100."); + + auto expr = join(fname, "(", convert_separate_image_to_expression(img), ", ", bitcast_expression(SPIRType::Int, ops[3]), ")"); auto &restype = get(ops[0]); expr = bitcast_expression(restype, SPIRType::Int, expr); @@ -10174,6 +11550,7 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) // Image load/store case OpImageRead: + case OpImageSparseRead: { // We added Nonreadable speculatively to the OpImage variable due to glslangValidator // not adding the proper qualifiers. @@ -10234,8 +11611,8 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) { uint32_t operands = ops[4]; if (operands != ImageOperandsSampleMask || length != 6) - SPIRV_CROSS_THROW( - "Multisampled image used in OpImageRead, but unexpected operand mask was used."); + SPIRV_CROSS_THROW("Multisampled image used in OpImageRead, but unexpected " + "operand mask was used."); uint32_t samples = ops[5]; imgexpr = join("subpassLoad(", to_expression(ops[2]), ", ", to_expression(samples), ")"); @@ -10249,8 +11626,8 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) { uint32_t operands = ops[4]; if (operands != ImageOperandsSampleMask || length != 6) - SPIRV_CROSS_THROW( - "Multisampled image used in OpImageRead, but unexpected operand mask was used."); + SPIRV_CROSS_THROW("Multisampled image used in OpImageRead, but unexpected " + "operand mask was used."); uint32_t samples = ops[5]; imgexpr = join("texelFetch(", to_expression(ops[2]), ", ivec2(gl_FragCoord.xy), ", @@ -10267,6 +11644,12 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) } else { + bool sparse = opcode == OpImageSparseRead; + uint32_t sparse_code_id = 0; + uint32_t sparse_texel_id = 0; + if (sparse) + emit_sparse_feedback_temporaries(ops[0], ops[1], sparse_code_id, sparse_texel_id); + // imageLoad only accepts int coords, not uint. auto coord_expr = to_expression(ops[3]); auto target_coord_type = expression_type(ops[3]); @@ -10274,20 +11657,46 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) coord_expr = bitcast_expression(target_coord_type, expression_type(ops[3]).basetype, coord_expr); // Plain image load/store. - if (type.image.ms) + if (sparse) { - uint32_t operands = ops[4]; - if (operands != ImageOperandsSampleMask || length != 6) - SPIRV_CROSS_THROW("Multisampled image used in OpImageRead, but unexpected operand mask was used."); + if (type.image.ms) + { + uint32_t operands = ops[4]; + if (operands != ImageOperandsSampleMask || length != 6) + SPIRV_CROSS_THROW("Multisampled image used in OpImageRead, but unexpected " + "operand mask was used."); - uint32_t samples = ops[5]; - imgexpr = - join("imageLoad(", to_expression(ops[2]), ", ", coord_expr, ", ", to_expression(samples), ")"); + uint32_t samples = ops[5]; + statement(to_expression(sparse_code_id), " = sparseImageLoadARB(", to_expression(ops[2]), ", ", + coord_expr, ", ", to_expression(samples), ", ", to_expression(sparse_texel_id), ");"); + } + else + { + statement(to_expression(sparse_code_id), " = sparseImageLoadARB(", to_expression(ops[2]), ", ", + coord_expr, ", ", to_expression(sparse_texel_id), ");"); + } + imgexpr = join(type_to_glsl(get(result_type)), "(", to_expression(sparse_code_id), ", ", + to_expression(sparse_texel_id), ")"); } else - imgexpr = join("imageLoad(", to_expression(ops[2]), ", ", coord_expr, ")"); + { + if (type.image.ms) + { + uint32_t operands = ops[4]; + if (operands != ImageOperandsSampleMask || length != 6) + SPIRV_CROSS_THROW("Multisampled image used in OpImageRead, but unexpected " + "operand mask was used."); - imgexpr = remap_swizzle(get(result_type), 4, imgexpr); + uint32_t samples = ops[5]; + imgexpr = + join("imageLoad(", to_expression(ops[2]), ", ", coord_expr, ", ", to_expression(samples), ")"); + } + else + imgexpr = join("imageLoad(", to_expression(ops[2]), ", ", coord_expr, ")"); + } + + if (!sparse) + imgexpr = remap_swizzle(get(result_type), 4, imgexpr); pure = false; } @@ -10392,13 +11801,24 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) string expr; if (type.image.sampled == 2) { + if (!options.es && options.version < 430) + require_extension_internal("GL_ARB_shader_image_size"); + else if (options.es && options.version < 310) + SPIRV_CROSS_THROW("At least ESSL 3.10 required for imageSize."); + // The size of an image is always constant. expr = join("imageSize(", to_expression(ops[2]), ")"); } else { // This path is hit for samplerBuffers and multisampled images which do not have LOD. - expr = join("textureSize(", convert_separate_image_to_expression(ops[2]), ")"); + std::string fname = "textureSize"; + if (is_legacy()) + { + auto &imgtype = get(type.self); + fname = legacy_tex_op(fname, imgtype, ops[2]); + } + expr = join(fname, "(", convert_separate_image_to_expression(ops[2]), ")"); } auto &restype = get(ops[0]); @@ -10420,21 +11840,27 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) if (opcode == OpMemoryBarrier) { - memory = get(ops[0]).scalar(); - semantics = get(ops[1]).scalar(); + memory = evaluate_constant_u32(ops[0]); + semantics = evaluate_constant_u32(ops[1]); } else { - execution_scope = get(ops[0]).scalar(); - memory = get(ops[1]).scalar(); - semantics = get(ops[2]).scalar(); + execution_scope = evaluate_constant_u32(ops[0]); + memory = evaluate_constant_u32(ops[1]); + semantics = evaluate_constant_u32(ops[2]); } if (execution_scope == ScopeSubgroup || memory == ScopeSubgroup) { - if (!options.vulkan_semantics) - SPIRV_CROSS_THROW("Can only use subgroup operations in Vulkan semantics."); - require_extension_internal("GL_KHR_shader_subgroup_basic"); + // OpControlBarrier with ScopeSubgroup is subgroupBarrier() + if (opcode != OpControlBarrier) + { + request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupMemBarrier); + } + else + { + request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupBarrier); + } } if (execution_scope != ScopeSubgroup && get_entry_point().model == ExecutionModelTessellationControl) @@ -10456,8 +11882,8 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) if (next && next->op == OpControlBarrier) { auto *next_ops = stream(*next); - uint32_t next_memory = get(next_ops[1]).scalar(); - uint32_t next_semantics = get(next_ops[2]).scalar(); + uint32_t next_memory = evaluate_constant_u32(next_ops[1]); + uint32_t next_semantics = evaluate_constant_u32(next_ops[2]); next_semantics = mask_relevant_memory_semantics(next_semantics); bool memory_scope_covered = false; @@ -10784,6 +12210,9 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) case OpGroupNonUniformBitwiseAnd: case OpGroupNonUniformBitwiseOr: case OpGroupNonUniformBitwiseXor: + case OpGroupNonUniformLogicalAnd: + case OpGroupNonUniformLogicalOr: + case OpGroupNonUniformLogicalXor: case OpGroupNonUniformQuadSwap: case OpGroupNonUniformQuadBroadcast: emit_subgroup_op(instruction); @@ -10883,29 +12312,59 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) break; } - case OpReportIntersectionNV: - statement("reportIntersectionNV(", to_expression(ops[0]), ", ", to_expression(ops[1]), ");"); + case OpReportIntersectionKHR: + // NV is same opcode. + forced_temporaries.insert(ops[1]); + if (ray_tracing_is_khr) + GLSL_BFOP(reportIntersectionEXT); + else + GLSL_BFOP(reportIntersectionNV); flush_control_dependent_expressions(current_emitting_block->self); break; case OpIgnoreIntersectionNV: + // KHR variant is a terminator. statement("ignoreIntersectionNV();"); flush_control_dependent_expressions(current_emitting_block->self); break; case OpTerminateRayNV: + // KHR variant is a terminator. statement("terminateRayNV();"); flush_control_dependent_expressions(current_emitting_block->self); break; case OpTraceNV: + if (has_decoration(ops[0], DecorationNonUniformEXT)) + propagate_nonuniform_qualifier(ops[0]); statement("traceNV(", to_expression(ops[0]), ", ", to_expression(ops[1]), ", ", to_expression(ops[2]), ", ", to_expression(ops[3]), ", ", to_expression(ops[4]), ", ", to_expression(ops[5]), ", ", to_expression(ops[6]), ", ", to_expression(ops[7]), ", ", to_expression(ops[8]), ", ", to_expression(ops[9]), ", ", to_expression(ops[10]), ");"); flush_control_dependent_expressions(current_emitting_block->self); break; + case OpTraceRayKHR: + if (!has_decoration(ops[10], DecorationLocation)) + SPIRV_CROSS_THROW("A memory declaration object must be used in TraceRayKHR."); + if (has_decoration(ops[0], DecorationNonUniformEXT)) + propagate_nonuniform_qualifier(ops[0]); + statement("traceRayEXT(", to_expression(ops[0]), ", ", to_expression(ops[1]), ", ", to_expression(ops[2]), ", ", + to_expression(ops[3]), ", ", to_expression(ops[4]), ", ", to_expression(ops[5]), ", ", + to_expression(ops[6]), ", ", to_expression(ops[7]), ", ", to_expression(ops[8]), ", ", + to_expression(ops[9]), ", ", get_decoration(ops[10], DecorationLocation), ");"); + flush_control_dependent_expressions(current_emitting_block->self); + break; case OpExecuteCallableNV: statement("executeCallableNV(", to_expression(ops[0]), ", ", to_expression(ops[1]), ");"); flush_control_dependent_expressions(current_emitting_block->self); break; + case OpExecuteCallableKHR: + if (!has_decoration(ops[1], DecorationLocation)) + SPIRV_CROSS_THROW("A memory declaration object must be used in ExecuteCallableKHR."); + statement("executeCallableEXT(", to_expression(ops[0]), ", ", get_decoration(ops[1], DecorationLocation), ");"); + flush_control_dependent_expressions(current_emitting_block->self); + break; + + case OpConvertUToAccelerationStructureKHR: + GLSL_UFOP(accelerationStructureEXT); + break; case OpConvertUToPtr: { @@ -11038,6 +12497,18 @@ string CompilerGLSL::to_member_reference(uint32_t, const SPIRType &type, uint32_ return join(".", to_member_name(type, index)); } +string CompilerGLSL::to_multi_member_reference(const SPIRType &type, const SmallVector &indices) +{ + string ret; + auto *member_type = &type; + for (auto &index : indices) + { + ret += join(".", to_member_name(*member_type, index)); + member_type = &get(member_type->member_types[index]); + } + return ret; +} + void CompilerGLSL::add_member_name(SPIRType &type, uint32_t index) { auto &memb = ir.meta[type.self].members; @@ -11047,13 +12518,7 @@ void CompilerGLSL::add_member_name(SPIRType &type, uint32_t index) if (name.empty()) return; - // Reserved for temporaries. - if (name[0] == '_' && name.size() >= 2 && isdigit(name[1])) - { - name.clear(); - return; - } - + ParsedIR::sanitize_identifier(name, true, true); update_name_cache(type.member_name_cache, name); } } @@ -11066,18 +12531,11 @@ bool CompilerGLSL::is_non_native_row_major_matrix(uint32_t id) if (backend.native_row_major_matrix && !is_legacy()) return false; - // Non-matrix or column-major matrix types do not need to be converted. - if (!has_decoration(id, DecorationRowMajor)) - return false; - - // Only square row-major matrices can be converted at this time. - // Converting non-square matrices will require defining custom GLSL function that - // swaps matrix elements while retaining the original dimensional form of the matrix. - const auto type = expression_type(id); - if (type.columns != type.vecsize) - SPIRV_CROSS_THROW("Row-major matrices must be square on this platform."); - - return true; + auto *e = maybe_get(id); + if (e) + return e->need_transpose; + else + return has_decoration(id, DecorationRowMajor); } // Checks whether the member is a row_major matrix that requires conversion before use @@ -11143,6 +12601,38 @@ string CompilerGLSL::convert_row_major_matrix(string exp_str, const SPIRType &ex transposed_expr += ")"; return transposed_expr; } + else if (options.version < 120) + { + // GLSL 110, ES 100 do not have transpose(), so emulate it. Note that + // these GLSL versions do not support non-square matrices. + if (exp_type.vecsize == 2 && exp_type.columns == 2) + { + if (!requires_transpose_2x2) + { + requires_transpose_2x2 = true; + force_recompile(); + } + } + else if (exp_type.vecsize == 3 && exp_type.columns == 3) + { + if (!requires_transpose_3x3) + { + requires_transpose_3x3 = true; + force_recompile(); + } + } + else if (exp_type.vecsize == 4 && exp_type.columns == 4) + { + if (!requires_transpose_4x4) + { + requires_transpose_4x4 = true; + force_recompile(); + } + } + else + SPIRV_CROSS_THROW("Non-square matrices are not supported in legacy GLSL, cannot transpose."); + return join("spvTranspose(", exp_str, ")"); + } else return join("transpose(", exp_str, ")"); } @@ -11251,6 +12741,31 @@ const char *CompilerGLSL::to_precision_qualifiers_glsl(uint32_t id) return flags_to_qualifiers_glsl(type, ir.meta[id].decoration.decoration_flags); } +void CompilerGLSL::fixup_io_block_patch_qualifiers(const SPIRVariable &var) +{ + // Works around weird behavior in glslangValidator where + // a patch out block is translated to just block members getting the decoration. + // To make glslang not complain when we compile again, we have to transform this back to a case where + // the variable itself has Patch decoration, and not members. + auto &type = get(var.basetype); + if (has_decoration(type.self, DecorationBlock)) + { + uint32_t member_count = uint32_t(type.member_types.size()); + for (uint32_t i = 0; i < member_count; i++) + { + if (has_member_decoration(type.self, i, DecorationPatch)) + { + set_decoration(var.self, DecorationPatch); + break; + } + } + + if (has_decoration(var.self, DecorationPatch)) + for (uint32_t i = 0; i < member_count; i++) + unset_member_decoration(type.self, i, DecorationPatch); + } +} + string CompilerGLSL::to_qualifiers_glsl(uint32_t id) { auto &flags = ir.meta[id].decoration.decoration_flags; @@ -11341,7 +12856,7 @@ string CompilerGLSL::variable_decl(const SPIRVariable &variable) // Ignore the pointer type since GLSL doesn't have pointers. auto &type = get_variable_data_type(variable); - if (type.pointer_depth > 1) + if (type.pointer_depth > 1 && !backend.support_pointer_to_pointer) SPIRV_CROSS_THROW("Cannot declare pointer-to-pointer types."); auto res = join(to_qualifiers_glsl(variable.self), variable_decl(type, to_name(variable.self), variable.self)); @@ -11404,15 +12919,7 @@ uint32_t CompilerGLSL::to_array_size_literal(const SPIRType &type, uint32_t inde { // Use the default spec constant value. // This is the best we can do. - uint32_t array_size_id = type.array[index]; - - // Explicitly check for this case. The error message you would get (bad cast) makes no sense otherwise. - if (ir.ids[array_size_id].get_type() == TypeConstantOp) - SPIRV_CROSS_THROW("An array size was found to be an OpSpecConstantOp. This is not supported since " - "SPIRV-Cross cannot deduce the actual size here."); - - uint32_t array_size = get(array_size_id).scalar(); - return array_size; + return evaluate_constant_u32(type.array[index]); } } @@ -11596,7 +13103,8 @@ string CompilerGLSL::type_to_glsl_constructor(const SPIRType &type) if (backend.use_array_constructor && type.array.size() > 1) { if (options.flatten_multidimensional_arrays) - SPIRV_CROSS_THROW("Cannot flatten constructors of multidimensional array constructors, e.g. float[][]()."); + SPIRV_CROSS_THROW("Cannot flatten constructors of multidimensional array constructors, " + "e.g. float[][]()."); else if (!options.es && options.version < 430) require_extension_internal("GL_ARB_arrays_of_arrays"); else if (options.es && options.version < 310) @@ -11651,7 +13159,7 @@ string CompilerGLSL::type_to_glsl(const SPIRType &type, uint32_t id) return comparison_ids.count(id) ? "samplerShadow" : "sampler"; case SPIRType::AccelerationStructure: - return "accelerationStructureNV"; + return ray_tracing_is_khr ? "accelerationStructureEXT" : "accelerationStructureNV"; case SPIRType::Void: return "void"; @@ -11779,16 +13287,13 @@ void CompilerGLSL::add_variable(unordered_set &variables_primary, if (name.empty()) return; - // Reserved for temporaries. - if (name[0] == '_' && name.size() >= 2 && isdigit(name[1])) + ParsedIR::sanitize_underscores(name); + if (ParsedIR::is_globally_reserved_identifier(name, true)) { name.clear(); return; } - // Avoid double underscores. - name = sanitize_underscores(name); - update_name_cache(variables_primary, variables_secondary, name); } @@ -12095,7 +13600,8 @@ void CompilerGLSL::emit_function(SPIRFunction &func, const Bitset &return_flags) { add_local_variable_name(var.self); - if (var.initializer) + // Loop variables should never be declared early, they are explicitly emitted in a loop. + if (var.initializer && !var.loop_variable) statement(variable_decl_function_local(var), ";"); else { @@ -12149,8 +13655,7 @@ void CompilerGLSL::emit_function(SPIRFunction &func, const Bitset &return_flags) void CompilerGLSL::emit_fixup() { - auto &execution = get_entry_point(); - if (execution.model == ExecutionModelVertex) + if (is_vertex_like_shader()) { if (options.vertex.fixup_clipspace) { @@ -12296,8 +13801,14 @@ void CompilerGLSL::branch(BlockID from, BlockID to) // and end the chain here. statement("continue;"); } - else if (is_break(to)) + else if (from != to && is_break(to)) { + // We cannot break to ourselves, so check explicitly for from != to. + // This case can trigger if a loop header is all three of these things: + // - Continue block + // - Loop header + // - Break merge target all at once ... + // Very dirty workaround. // Switch constructs are able to break, but they cannot break out of a loop at the same time. // Only sensible solution is to make a ladder variable, which we declare at the top of the switch block, @@ -12351,64 +13862,37 @@ void CompilerGLSL::branch(BlockID from, uint32_t cond, BlockID true_block, Block auto &from_block = get(from); BlockID merge_block = from_block.merge == SPIRBlock::MergeSelection ? from_block.next_block : BlockID(0); - // If we branch directly to a selection merge target, we don't need a code path. - // This covers both merge out of if () / else () as well as a break for switch blocks. - bool true_sub = !is_conditional(true_block); - bool false_sub = !is_conditional(false_block); + // If we branch directly to our selection merge target, we don't need a code path. + bool true_block_needs_code = true_block != merge_block || flush_phi_required(from, true_block); + bool false_block_needs_code = false_block != merge_block || flush_phi_required(from, false_block); - bool true_block_is_selection_merge = true_block == merge_block; - bool false_block_is_selection_merge = false_block == merge_block; + if (!true_block_needs_code && !false_block_needs_code) + return; - if (true_sub) + emit_block_hints(get(from)); + + if (true_block_needs_code) { - emit_block_hints(get(from)); statement("if (", to_expression(cond), ")"); begin_scope(); branch(from, true_block); end_scope(); - // If we merge to continue, we handle that explicitly in emit_block_chain(), - // so there is no need to branch to it directly here. - // break; is required to handle ladder fallthrough cases, so keep that in for now, even - // if we could potentially handle it in emit_block_chain(). - if (false_sub || (!false_block_is_selection_merge && is_continue(false_block)) || is_break(false_block)) + if (false_block_needs_code) { statement("else"); begin_scope(); branch(from, false_block); end_scope(); } - else if (flush_phi_required(from, false_block)) - { - statement("else"); - begin_scope(); - flush_phi(from, false_block); - end_scope(); - } } - else if (false_sub) + else if (false_block_needs_code) { // Only need false path, use negative conditional. - emit_block_hints(get(from)); statement("if (!", to_enclosed_expression(cond), ")"); begin_scope(); branch(from, false_block); end_scope(); - - if ((!true_block_is_selection_merge && is_continue(true_block)) || is_break(true_block)) - { - statement("else"); - begin_scope(); - branch(from, true_block); - end_scope(); - } - else if (flush_phi_required(from, true_block)) - { - statement("else"); - begin_scope(); - flush_phi(from, true_block); - end_scope(); - } } } @@ -12783,6 +14267,10 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) bool skip_direct_branch = false; bool emitted_loop_header_variables = false; bool force_complex_continue_block = false; + ValueSaver loop_level_saver(current_loop_level); + + if (block.merge == SPIRBlock::MergeLoop) + add_loop_level(); emit_hoisted_temporaries(block.declare_temporary); @@ -13055,6 +14543,8 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) // Order does not matter. if (!injected_block) block_declaration_order.push_back(block.default_block); + else if (is_legacy_es()) + SPIRV_CROSS_THROW("Default case label fallthrough to other case label is not supported in ESSL 1.0."); case_constructs[block.default_block] = {}; } @@ -13065,12 +14555,26 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) return is_unsigned_case ? convert_to_string(literal) : convert_to_string(int32_t(literal)); }; + const auto to_legacy_case_label = [&](uint32_t condition, const SmallVector &labels, + const char *suffix) -> string { + string ret; + size_t count = labels.size(); + for (size_t i = 0; i < count; i++) + { + if (i) + ret += " || "; + ret += join(count > 1 ? "(" : "", to_enclosed_expression(condition), " == ", labels[i], suffix, + count > 1 ? ")" : ""); + } + return ret; + }; + // We need to deal with a complex scenario for OpPhi. If we have case-fallthrough and Phi in the picture, // we need to flush phi nodes outside the switch block in a branch, // and skip any Phi handling inside the case label to make fall-through work as expected. // This kind of code-gen is super awkward and it's a last resort. Normally we would want to handle this // inside the case label if at all possible. - for (size_t i = 1; i < num_blocks; i++) + for (size_t i = 1; backend.support_case_fallthrough && i < num_blocks; i++) { if (flush_phi_required(block.self, block_declaration_order[i]) && flush_phi_required(block_declaration_order[i - 1], block_declaration_order[i])) @@ -13119,8 +14623,28 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) } } - emit_block_hints(block); - statement("switch (", to_expression(block.condition), ")"); + // If there is only one default block, and no cases, this is a case where SPIRV-opt decided to emulate + // non-structured exits with the help of a switch block. + // This is buggy on FXC, so just emit the logical equivalent of a do { } while(false), which is more idiomatic. + bool degenerate_switch = block.default_block != block.merge_block && block.cases.empty(); + + if (degenerate_switch || is_legacy_es()) + { + // ESSL 1.0 is not guaranteed to support do/while. + if (is_legacy_es()) + { + uint32_t counter = statement_count; + statement("for (int spvDummy", counter, " = 0; spvDummy", counter, + " < 1; spvDummy", counter, "++)"); + } + else + statement("do"); + } + else + { + emit_block_hints(block); + statement("switch (", to_expression(block.condition), ")"); + } begin_scope(); for (size_t i = 0; i < num_blocks; i++) @@ -13131,14 +14655,28 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) if (literals.empty()) { // Default case. - statement("default:"); + if (!degenerate_switch) + { + if (is_legacy_es()) + statement("else"); + else + statement("default:"); + } } else { - for (auto &case_literal : literals) + if (is_legacy_es()) { - // The case label value must be sign-extended properly in SPIR-V, so we can assume 32-bit values here. - statement("case ", to_case_label(case_literal, unsigned_case), label_suffix, ":"); + statement((i ? "else " : ""), "if (", to_legacy_case_label(block.condition, literals, label_suffix), + ")"); + } + else + { + for (auto &case_literal : literals) + { + // The case label value must be sign-extended properly in SPIR-V, so we can assume 32-bit values here. + statement("case ", to_case_label(case_literal, unsigned_case), label_suffix, ":"); + } } } @@ -13155,9 +14693,11 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) else current_emitting_switch_fallthrough = false; - begin_scope(); + if (!degenerate_switch) + begin_scope(); branch(block.self, target_block); - end_scope(); + if (!degenerate_switch) + end_scope(); current_emitting_switch_fallthrough = false; } @@ -13171,7 +14711,12 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) statement("case ", to_case_label(case_literal, unsigned_case), label_suffix, ":"); if (block.default_block == block.next_block) - statement("default:"); + { + if (is_legacy_es()) + statement("else"); + else + statement("default:"); + } begin_scope(); flush_phi(block.self, block.next_block); @@ -13180,7 +14725,10 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) } } - end_scope(); + if (degenerate_switch && !is_legacy_es()) + end_scope_decl("while(false)"); + else + end_scope(); if (block.need_ladder_break) { @@ -13213,8 +14761,8 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) // The backend is responsible for setting this up, and redirection the return values as appropriate. if (ir.ids[block.return_value].get_type() != TypeUndef) { - emit_array_copy("SPIRV_Cross_return_value", block.return_value, StorageClassFunction, - get_backing_variable_storage(block.return_value)); + emit_array_copy("spvReturnValue", block.return_value, StorageClassFunction, + get_expression_effective_storage_class(block.return_value)); } if (!cfg.node_terminates_control_flow_in_sub_graph(current_function->entry_block, block.self) || @@ -13250,6 +14798,14 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) emit_next_block = false; break; + case SPIRBlock::IgnoreIntersection: + statement("ignoreIntersectionEXT;"); + break; + + case SPIRBlock::TerminateRay: + statement("terminateRayEXT;"); + break; + default: SPIRV_CROSS_THROW("Unimplemented block terminator."); } @@ -13259,7 +14815,11 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) // If we hit this case, we're dealing with an unconditional branch, which means we will output // that block after this. If we had selection merge, we already flushed phi variables. if (block.merge != SPIRBlock::MergeSelection) + { flush_phi(block.self, block.next_block); + // For a direct branch, need to remember to invalidate expressions in the next linear block instead. + get(block.next_block).invalidate_expressions = block.invalidate_expressions; + } // For switch fallthrough cases, we terminate the chain here, but we still need to handle Phi. if (!current_emitting_switch_fallthrough) @@ -13313,6 +14873,8 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) else end_scope(); + loop_level_saver.release(); + // We cannot break out of two loops at once, so don't check for break; here. // Using block.self as the "from" block isn't quite right, but it has the same scope // and dominance structure, so it's fine. @@ -13423,6 +14985,43 @@ void CompilerGLSL::emit_array_copy(const string &lhs, uint32_t rhs_id, StorageCl statement(lhs, " = ", to_expression(rhs_id), ";"); } +bool CompilerGLSL::unroll_array_to_complex_store(uint32_t target_id, uint32_t source_id) +{ + if (!backend.force_gl_in_out_block) + return false; + // This path is only relevant for GL backends. + + auto *var = maybe_get(target_id); + if (!var || var->storage != StorageClassOutput) + return false; + + if (!is_builtin_variable(*var) || BuiltIn(get_decoration(var->self, DecorationBuiltIn)) != BuiltInSampleMask) + return false; + + auto &type = expression_type(source_id); + string array_expr; + if (type.array_size_literal.back()) + { + array_expr = convert_to_string(type.array.back()); + if (type.array.back() == 0) + SPIRV_CROSS_THROW("Cannot unroll an array copy from unsized array."); + } + else + array_expr = to_expression(type.array.back()); + + SPIRType target_type; + target_type.basetype = SPIRType::Int; + + statement("for (int i = 0; i < int(", array_expr, "); i++)"); + begin_scope(); + statement(to_expression(target_id), "[i] = ", + bitcast_expression(target_type, type.basetype, join(to_expression(source_id), "[i]")), + ";"); + end_scope(); + + return true; +} + void CompilerGLSL::unroll_array_from_complex_load(uint32_t target_id, uint32_t source_id, std::string &expr) { if (!backend.force_gl_in_out_block) @@ -13433,7 +15032,7 @@ void CompilerGLSL::unroll_array_from_complex_load(uint32_t target_id, uint32_t s if (!var) return; - if (var->storage != StorageClassInput) + if (var->storage != StorageClassInput && var->storage != StorageClassOutput) return; auto &type = get_variable_data_type(*var); @@ -13441,9 +15040,13 @@ void CompilerGLSL::unroll_array_from_complex_load(uint32_t target_id, uint32_t s return; auto builtin = BuiltIn(get_decoration(var->self, DecorationBuiltIn)); - bool is_builtin = is_builtin_variable(*var) && (builtin == BuiltInPointSize || builtin == BuiltInPosition); + bool is_builtin = is_builtin_variable(*var) && + (builtin == BuiltInPointSize || + builtin == BuiltInPosition || + builtin == BuiltInSampleMask); bool is_tess = is_tessellation_shader(); bool is_patch = has_decoration(var->self, DecorationPatch); + bool is_sample_mask = is_builtin && builtin == BuiltInSampleMask; // Tessellation input arrays are special in that they are unsized, so we cannot directly copy from it. // We must unroll the array load. @@ -13467,8 +15070,14 @@ void CompilerGLSL::unroll_array_from_complex_load(uint32_t target_id, uint32_t s // The array size might be a specialization constant, so use a for-loop instead. statement("for (int i = 0; i < int(", array_expr, "); i++)"); begin_scope(); - if (is_builtin) + if (is_builtin && !is_sample_mask) statement(new_expr, "[i] = gl_in[i].", expr, ";"); + else if (is_sample_mask) + { + SPIRType target_type; + target_type.basetype = SPIRType::Int; + statement(new_expr, "[i] = ", bitcast_expression(target_type, type.basetype, join(expr, "[i]")), ";"); + } else statement(new_expr, "[i] = ", expr, "[i];"); end_scope(); @@ -13477,8 +15086,12 @@ void CompilerGLSL::unroll_array_from_complex_load(uint32_t target_id, uint32_t s } } -void CompilerGLSL::bitcast_from_builtin_load(uint32_t source_id, std::string &expr, const SPIRType &expr_type) +void CompilerGLSL::cast_from_builtin_load(uint32_t source_id, std::string &expr, const SPIRType &expr_type) { + // We will handle array cases elsewhere. + if (!expr_type.array.empty()) + return; + auto *var = maybe_get_backing_variable(source_id); if (var) source_id = var->self; @@ -13506,6 +15119,7 @@ void CompilerGLSL::bitcast_from_builtin_load(uint32_t source_id, std::string &ex case BuiltInDrawIndex: case BuiltInFragStencilRefEXT: case BuiltInInstanceCustomIndexNV: + case BuiltInSampleMask: expected_type = SPIRType::Int; break; @@ -13529,8 +15143,12 @@ void CompilerGLSL::bitcast_from_builtin_load(uint32_t source_id, std::string &ex expr = bitcast_expression(expr_type, expected_type, expr); } -void CompilerGLSL::bitcast_to_builtin_store(uint32_t target_id, std::string &expr, const SPIRType &expr_type) +void CompilerGLSL::cast_to_builtin_store(uint32_t target_id, std::string &expr, const SPIRType &expr_type) { + auto *var = maybe_get_backing_variable(target_id); + if (var) + target_id = var->self; + // Only interested in standalone builtin variables. if (!has_decoration(target_id, DecorationBuiltIn)) return; @@ -13545,6 +15163,7 @@ void CompilerGLSL::bitcast_to_builtin_store(uint32_t target_id, std::string &exp case BuiltInPrimitiveId: case BuiltInViewportIndex: case BuiltInFragStencilRefEXT: + case BuiltInSampleMask: expected_type = SPIRType::Int; break; @@ -13567,7 +15186,7 @@ void CompilerGLSL::convert_non_uniform_expression(const SPIRType &type, std::str // Handle SPV_EXT_descriptor_indexing. if (type.basetype == SPIRType::Sampler || type.basetype == SPIRType::SampledImage || - type.basetype == SPIRType::Image) + type.basetype == SPIRType::Image || type.basetype == SPIRType::AccelerationStructure) { // The image/sampler ID must be declared as non-uniform. // However, it is not legal GLSL to have @@ -13578,7 +15197,35 @@ void CompilerGLSL::convert_non_uniform_expression(const SPIRType &type, std::str // so we might have to fixup the OpLoad-ed expression late. auto start_array_index = expr.find_first_of('['); - auto end_array_index = expr.find_last_of(']'); + + if (start_array_index == string::npos) + return; + + // Check for the edge case that a non-arrayed resource was marked to be nonuniform, + // and the bracket we found is actually part of non-resource related data. + if (expr.find_first_of(',') < start_array_index) + return; + + // We've opened a bracket, track expressions until we can close the bracket. + // This must be our image index. + size_t end_array_index = string::npos; + unsigned bracket_count = 1; + for (size_t index = start_array_index + 1; index < expr.size(); index++) + { + if (expr[index] == ']') + { + if (--bracket_count == 0) + { + end_array_index = index; + break; + } + } + else if (expr[index] == '[') + bracket_count++; + } + + assert(bracket_count == 0); + // Doesn't really make sense to declare a non-arrayed image with nonuniformEXT, but there's // nothing we can do here to express that. if (start_array_index == string::npos || end_array_index == string::npos || end_array_index < start_array_index) @@ -13619,40 +15266,32 @@ void CompilerGLSL::reset_name_caches() void CompilerGLSL::fixup_type_alias() { // Due to how some backends work, the "master" type of type_alias must be a block-like type if it exists. - // FIXME: Multiple alias types which are both block-like will be awkward, for now, it's best to just drop the type - // alias if the slave type is a block type. ir.for_each_typed_id([&](uint32_t self, SPIRType &type) { - if (type.type_alias && type_is_block_like(type)) + if (!type.type_alias) + return; + + if (has_decoration(type.self, DecorationBlock) || has_decoration(type.self, DecorationBufferBlock)) { + // Top-level block types should never alias anything else. + type.type_alias = 0; + } + else if (type_is_block_like(type) && type.self == ID(self)) + { + // A block-like type is any type which contains Offset decoration, but not top-level blocks, + // i.e. blocks which are placed inside buffers. // Become the master. ir.for_each_typed_id([&](uint32_t other_id, SPIRType &other_type) { - if (other_id == type.self) + if (other_id == self) return; if (other_type.type_alias == type.type_alias) - other_type.type_alias = type.self; + other_type.type_alias = self; }); this->get(type.type_alias).type_alias = self; type.type_alias = 0; } }); - - ir.for_each_typed_id([&](uint32_t, SPIRType &type) { - if (type.type_alias && type_is_block_like(type)) - { - // This is not allowed, drop the type_alias. - type.type_alias = 0; - } - else if (type.type_alias && !type_is_block_like(this->get(type.type_alias))) - { - // If the alias master is not a block-like type, there is no reason to use type aliasing. - // This case can happen if two structs are declared with the same name, but they are unrelated. - // Aliases are only used to deal with aliased types for structs which are used in different buffer types - // which all create a variant of the same struct with different DecorationOffset values. - type.type_alias = 0; - } - }); } void CompilerGLSL::reorder_type_alias() @@ -13860,7 +15499,8 @@ void CompilerGLSL::emit_inout_fragment_outputs_copy_to_subpass_inputs() if (!subpass_var) continue; if (!output_var) - SPIRV_CROSS_THROW("Need to declare the corresponding fragment output variable to be able to read from it."); + SPIRV_CROSS_THROW("Need to declare the corresponding fragment output variable to be able " + "to read from it."); if (is_array(get(output_var->basetype))) SPIRV_CROSS_THROW("Cannot use GL_EXT_shader_framebuffer_fetch with arrays of color outputs."); @@ -13885,3 +15525,266 @@ bool CompilerGLSL::variable_is_depth_or_compare(VariableID id) const { return image_is_comparison(get(get(id).basetype), id); } + +const char *CompilerGLSL::ShaderSubgroupSupportHelper::get_extension_name(Candidate c) +{ + static const char *const retval[CandidateCount] = { "GL_KHR_shader_subgroup_ballot", + "GL_KHR_shader_subgroup_basic", + "GL_KHR_shader_subgroup_vote", + "GL_NV_gpu_shader_5", + "GL_NV_shader_thread_group", + "GL_NV_shader_thread_shuffle", + "GL_ARB_shader_ballot", + "GL_ARB_shader_group_vote", + "GL_AMD_gcn_shader" }; + return retval[c]; +} + +SmallVector CompilerGLSL::ShaderSubgroupSupportHelper::get_extra_required_extension_names(Candidate c) +{ + switch (c) + { + case ARB_shader_ballot: + return { "GL_ARB_shader_int64" }; + case AMD_gcn_shader: + return { "GL_AMD_gpu_shader_int64", "GL_NV_gpu_shader5" }; + default: + return {}; + } +} + +const char *CompilerGLSL::ShaderSubgroupSupportHelper::get_extra_required_extension_predicate(Candidate c) +{ + switch (c) + { + case ARB_shader_ballot: + return "defined(GL_ARB_shader_int64)"; + case AMD_gcn_shader: + return "(defined(GL_AMD_gpu_shader_int64) || defined(GL_NV_gpu_shader5))"; + default: + return ""; + } +} + +CompilerGLSL::ShaderSubgroupSupportHelper::FeatureVector CompilerGLSL::ShaderSubgroupSupportHelper:: + get_feature_dependencies(Feature feature) +{ + switch (feature) + { + case SubgroupAllEqualT: + return { SubgroupBroadcast_First, SubgroupAll_Any_AllEqualBool }; + case SubgroupElect: + return { SubgroupBallotFindLSB_MSB, SubgroupBallot, SubgroupInvocationID }; + case SubgroupInverseBallot_InclBitCount_ExclBitCout: + return { SubgroupMask }; + case SubgroupBallotBitCount: + return { SubgroupBallot }; + default: + return {}; + } +} + +CompilerGLSL::ShaderSubgroupSupportHelper::FeatureMask CompilerGLSL::ShaderSubgroupSupportHelper:: + get_feature_dependency_mask(Feature feature) +{ + return build_mask(get_feature_dependencies(feature)); +} + +bool CompilerGLSL::ShaderSubgroupSupportHelper::can_feature_be_implemented_without_extensions(Feature feature) +{ + static const bool retval[FeatureCount] = { false, false, false, false, false, false, + true, // SubgroupBalloFindLSB_MSB + false, false, false, false, + true, // SubgroupMemBarrier - replaced with workgroup memory barriers + false, false, true, false }; + + return retval[feature]; +} + +CompilerGLSL::ShaderSubgroupSupportHelper::Candidate CompilerGLSL::ShaderSubgroupSupportHelper:: + get_KHR_extension_for_feature(Feature feature) +{ + static const Candidate extensions[FeatureCount] = { + KHR_shader_subgroup_ballot, KHR_shader_subgroup_basic, KHR_shader_subgroup_basic, KHR_shader_subgroup_basic, + KHR_shader_subgroup_basic, KHR_shader_subgroup_ballot, KHR_shader_subgroup_ballot, KHR_shader_subgroup_vote, + KHR_shader_subgroup_vote, KHR_shader_subgroup_basic, KHR_shader_subgroup_basic, KHR_shader_subgroup_basic, + KHR_shader_subgroup_ballot, KHR_shader_subgroup_ballot, KHR_shader_subgroup_ballot, KHR_shader_subgroup_ballot + }; + + return extensions[feature]; +} + +void CompilerGLSL::ShaderSubgroupSupportHelper::request_feature(Feature feature) +{ + feature_mask |= (FeatureMask(1) << feature) | get_feature_dependency_mask(feature); +} + +bool CompilerGLSL::ShaderSubgroupSupportHelper::is_feature_requested(Feature feature) const +{ + return (feature_mask & (1u << feature)) != 0; +} + +CompilerGLSL::ShaderSubgroupSupportHelper::Result CompilerGLSL::ShaderSubgroupSupportHelper::resolve() const +{ + Result res; + + for (uint32_t i = 0u; i < FeatureCount; ++i) + { + if (feature_mask & (1u << i)) + { + auto feature = static_cast(i); + std::unordered_set unique_candidates; + + auto candidates = get_candidates_for_feature(feature); + unique_candidates.insert(candidates.begin(), candidates.end()); + + auto deps = get_feature_dependencies(feature); + for (Feature d : deps) + { + candidates = get_candidates_for_feature(d); + if (!candidates.empty()) + unique_candidates.insert(candidates.begin(), candidates.end()); + } + + for (uint32_t c : unique_candidates) + ++res.weights[static_cast(c)]; + } + } + + return res; +} + +CompilerGLSL::ShaderSubgroupSupportHelper::CandidateVector CompilerGLSL::ShaderSubgroupSupportHelper:: + get_candidates_for_feature(Feature ft, const Result &r) +{ + auto c = get_candidates_for_feature(ft); + auto cmp = [&r](Candidate a, Candidate b) { + if (r.weights[a] == r.weights[b]) + return a < b; // Prefer candidates with lower enum value + return r.weights[a] > r.weights[b]; + }; + std::sort(c.begin(), c.end(), cmp); + return c; +} + +CompilerGLSL::ShaderSubgroupSupportHelper::CandidateVector CompilerGLSL::ShaderSubgroupSupportHelper:: + get_candidates_for_feature(Feature feature) +{ + switch (feature) + { + case SubgroupMask: + return { KHR_shader_subgroup_ballot, NV_shader_thread_group, ARB_shader_ballot }; + case SubgroupSize: + return { KHR_shader_subgroup_basic, NV_shader_thread_group, AMD_gcn_shader, ARB_shader_ballot }; + case SubgroupInvocationID: + return { KHR_shader_subgroup_basic, NV_shader_thread_group, ARB_shader_ballot }; + case SubgroupID: + return { KHR_shader_subgroup_basic, NV_shader_thread_group }; + case NumSubgroups: + return { KHR_shader_subgroup_basic, NV_shader_thread_group }; + case SubgroupBroadcast_First: + return { KHR_shader_subgroup_ballot, NV_shader_thread_shuffle, ARB_shader_ballot }; + case SubgroupBallotFindLSB_MSB: + return { KHR_shader_subgroup_ballot, NV_shader_thread_group }; + case SubgroupAll_Any_AllEqualBool: + return { KHR_shader_subgroup_vote, NV_gpu_shader_5, ARB_shader_group_vote, AMD_gcn_shader }; + case SubgroupAllEqualT: + return {}; // depends on other features only + case SubgroupElect: + return {}; // depends on other features only + case SubgroupBallot: + return { KHR_shader_subgroup_ballot, NV_shader_thread_group, ARB_shader_ballot }; + case SubgroupBarrier: + return { KHR_shader_subgroup_basic, NV_shader_thread_group, ARB_shader_ballot, AMD_gcn_shader }; + case SubgroupMemBarrier: + return { KHR_shader_subgroup_basic }; + case SubgroupInverseBallot_InclBitCount_ExclBitCout: + return {}; + case SubgroupBallotBitExtract: + return { NV_shader_thread_group }; + case SubgroupBallotBitCount: + return {}; + default: + return {}; + } +} + +CompilerGLSL::ShaderSubgroupSupportHelper::FeatureMask CompilerGLSL::ShaderSubgroupSupportHelper::build_mask( + const SmallVector &features) +{ + FeatureMask mask = 0; + for (Feature f : features) + mask |= FeatureMask(1) << f; + return mask; +} + +CompilerGLSL::ShaderSubgroupSupportHelper::Result::Result() +{ + for (auto &weight : weights) + weight = 0; + + // Make sure KHR_shader_subgroup extensions are always prefered. + const uint32_t big_num = FeatureCount; + weights[KHR_shader_subgroup_ballot] = big_num; + weights[KHR_shader_subgroup_basic] = big_num; + weights[KHR_shader_subgroup_vote] = big_num; +} + +void CompilerGLSL::request_workaround_wrapper_overload(TypeID id) +{ + // Must be ordered to maintain deterministic output, so vector is appropriate. + if (find(begin(workaround_ubo_load_overload_types), end(workaround_ubo_load_overload_types), id) == + end(workaround_ubo_load_overload_types)) + { + force_recompile(); + workaround_ubo_load_overload_types.push_back(id); + } +} + +void CompilerGLSL::rewrite_load_for_wrapped_row_major(std::string &expr, TypeID loaded_type, ID ptr) +{ + // Loading row-major matrices from UBOs on older AMD Windows OpenGL drivers is problematic. + // To load these types correctly, we must first wrap them in a dummy function which only purpose is to + // ensure row_major decoration is actually respected. + auto *var = maybe_get_backing_variable(ptr); + if (!var) + return; + + auto &backing_type = get(var->basetype); + bool is_ubo = backing_type.basetype == SPIRType::Struct && backing_type.storage == StorageClassUniform && + has_decoration(backing_type.self, DecorationBlock); + if (!is_ubo) + return; + + auto *type = &get(loaded_type); + bool rewrite = false; + + if (is_matrix(*type)) + { + // To avoid adding a lot of unnecessary meta tracking to forward the row_major state, + // we will simply look at the base struct itself. It is exceptionally rare to mix and match row-major/col-major state. + // If there is any row-major action going on, we apply the workaround. + // It is harmless to apply the workaround to column-major matrices, so this is still a valid solution. + // If an access chain occurred, the workaround is not required, so loading vectors or scalars don't need workaround. + type = &backing_type; + } + + if (type->basetype == SPIRType::Struct) + { + // If we're loading a struct where any member is a row-major matrix, apply the workaround. + for (uint32_t i = 0; i < uint32_t(type->member_types.size()); i++) + { + if (combined_decoration_for_member(*type, i).get(DecorationRowMajor)) + { + rewrite = true; + break; + } + } + } + + if (rewrite) + { + request_workaround_wrapper_overload(loaded_type); + expr = join("spvWorkaroundRowMajor(", expr, ")"); + } +} diff --git a/src/libraries/spirv_cross/spirv_glsl.hpp b/src/libraries/spirv_cross/spirv_glsl.hpp index 1eafc2cea..0e0303824 100644 --- a/src/libraries/spirv_cross/spirv_glsl.hpp +++ b/src/libraries/spirv_cross/spirv_glsl.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 Arm Limited + * Copyright 2015-2021 Arm Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #ifndef SPIRV_CROSS_GLSL_HPP #define SPIRV_CROSS_GLSL_HPP @@ -57,7 +64,8 @@ enum AccessChainFlagBits ACCESS_CHAIN_CHAIN_ONLY_BIT = 1 << 1, ACCESS_CHAIN_PTR_CHAIN_BIT = 1 << 2, ACCESS_CHAIN_SKIP_REGISTER_EXPRESSION_READ_BIT = 1 << 3, - ACCESS_CHAIN_LITERAL_MSB_FORCE_ID = 1 << 4 + ACCESS_CHAIN_LITERAL_MSB_FORCE_ID = 1 << 4, + ACCESS_CHAIN_FLATTEN_ALL_MEMBERS_BIT = 1 << 5 }; typedef uint32_t AccessChainFlags; @@ -120,6 +128,10 @@ public: // which would otherwise be uninitialized will now be initialized to 0 instead. bool force_zero_initialized_variables = false; + // In GLSL, force use of I/O block flattening, similar to + // what happens on legacy GLSL targets for blocks and structs. + bool force_flattened_io_blocks = false; + enum Precision { DontCare, @@ -128,14 +140,16 @@ public: Highp }; - struct + struct VertexOptions { - // GLSL: In vertex shaders, rewrite [0, w] depth (Vulkan/D3D style) to [-w, w] depth (GL style). - // MSL: In vertex shaders, rewrite [-w, w] depth (GL style) to [0, w] depth. - // HLSL: In vertex shaders, rewrite [-w, w] depth (GL style) to [0, w] depth. + // "Vertex-like shader" here is any shader stage that can write BuiltInPosition. + + // GLSL: In vertex-like shaders, rewrite [0, w] depth (Vulkan/D3D style) to [-w, w] depth (GL style). + // MSL: In vertex-like shaders, rewrite [-w, w] depth (GL style) to [0, w] depth. + // HLSL: In vertex-like shaders, rewrite [-w, w] depth (GL style) to [0, w] depth. bool fixup_clipspace = false; - // Inverts gl_Position.y or equivalent. + // In vertex-like shaders, inverts gl_Position.y or equivalent. bool flip_vert_y = false; // GLSL only, for HLSL version of this option, see CompilerHLSL. @@ -145,7 +159,7 @@ public: bool support_nonzero_base_instance = true; } vertex; - struct + struct FragmentOptions { // Add precision mediump float in ES targets when emitting GLES source. // Add precision highp int in ES targets when emitting GLES source. @@ -237,6 +251,82 @@ public: bool variable_is_depth_or_compare(VariableID id) const; protected: + struct ShaderSubgroupSupportHelper + { + // lower enum value = greater priority + enum Candidate + { + KHR_shader_subgroup_ballot, + KHR_shader_subgroup_basic, + KHR_shader_subgroup_vote, + NV_gpu_shader_5, + NV_shader_thread_group, + NV_shader_thread_shuffle, + ARB_shader_ballot, + ARB_shader_group_vote, + AMD_gcn_shader, + + CandidateCount + }; + + static const char *get_extension_name(Candidate c); + static SmallVector get_extra_required_extension_names(Candidate c); + static const char *get_extra_required_extension_predicate(Candidate c); + + enum Feature + { + SubgroupMask = 0, + SubgroupSize = 1, + SubgroupInvocationID = 2, + SubgroupID = 3, + NumSubgroups = 4, + SubgroupBroadcast_First = 5, + SubgroupBallotFindLSB_MSB = 6, + SubgroupAll_Any_AllEqualBool = 7, + SubgroupAllEqualT = 8, + SubgroupElect = 9, + SubgroupBarrier = 10, + SubgroupMemBarrier = 11, + SubgroupBallot = 12, + SubgroupInverseBallot_InclBitCount_ExclBitCout = 13, + SubgroupBallotBitExtract = 14, + SubgroupBallotBitCount = 15, + + FeatureCount + }; + + using FeatureMask = uint32_t; + static_assert(sizeof(FeatureMask) * 8u >= FeatureCount, "Mask type needs more bits."); + + using CandidateVector = SmallVector; + using FeatureVector = SmallVector; + + static FeatureVector get_feature_dependencies(Feature feature); + static FeatureMask get_feature_dependency_mask(Feature feature); + static bool can_feature_be_implemented_without_extensions(Feature feature); + static Candidate get_KHR_extension_for_feature(Feature feature); + + struct Result + { + Result(); + uint32_t weights[CandidateCount]; + }; + + void request_feature(Feature feature); + bool is_feature_requested(Feature feature) const; + Result resolve() const; + + static CandidateVector get_candidates_for_feature(Feature ft, const Result &r); + + private: + static CandidateVector get_candidates_for_feature(Feature ft); + static FeatureMask build_mask(const SmallVector &features); + FeatureMask feature_mask = 0; + }; + + // TODO remove this function when all subgroup ops are supported (or make it always return true) + static bool is_supported_subgroup_op_in_opengl(spv::Op op); + void reset(); void emit_function(SPIRFunction &func, const Bitset &return_flags); @@ -267,9 +357,11 @@ protected: void build_workgroup_size(SmallVector &arguments, const SpecializationConstant &x, const SpecializationConstant &y, const SpecializationConstant &z); + void request_subgroup_feature(ShaderSubgroupSupportHelper::Feature feature); + virtual void emit_sampled_image_op(uint32_t result_type, uint32_t result_id, uint32_t image_id, uint32_t samp_id); - virtual void emit_texture_op(const Instruction &i); - virtual std::string to_texture_op(const Instruction &i, bool *forward, + virtual void emit_texture_op(const Instruction &i, bool sparse); + virtual std::string to_texture_op(const Instruction &i, bool sparse, bool *forward, SmallVector &inherited_expressions); virtual void emit_subgroup_op(const Instruction &i); virtual std::string type_to_glsl(const SPIRType &type, uint32_t id = 0); @@ -284,14 +376,42 @@ protected: virtual void emit_fixup(); virtual std::string variable_decl(const SPIRType &type, const std::string &name, uint32_t id = 0); virtual std::string to_func_call_arg(const SPIRFunction::Parameter &arg, uint32_t id); - virtual std::string to_function_name(VariableID img, const SPIRType &imgtype, bool is_fetch, bool is_gather, - bool is_proj, bool has_array_offsets, bool has_offset, bool has_grad, - bool has_dref, uint32_t lod, uint32_t minlod); - virtual std::string to_function_args(VariableID img, const SPIRType &imgtype, bool is_fetch, bool is_gather, - bool is_proj, uint32_t coord, uint32_t coord_components, uint32_t dref, - uint32_t grad_x, uint32_t grad_y, uint32_t lod, uint32_t coffset, - uint32_t offset, uint32_t bias, uint32_t comp, uint32_t sample, - uint32_t minlod, bool *p_forward); + + struct TextureFunctionBaseArguments + { + // GCC 4.8 workarounds, it doesn't understand '{}' constructor here, use explicit default constructor. + TextureFunctionBaseArguments() = default; + VariableID img = 0; + const SPIRType *imgtype = nullptr; + bool is_fetch = false, is_gather = false, is_proj = false; + }; + + struct TextureFunctionNameArguments + { + // GCC 4.8 workarounds, it doesn't understand '{}' constructor here, use explicit default constructor. + TextureFunctionNameArguments() = default; + TextureFunctionBaseArguments base; + bool has_array_offsets = false, has_offset = false, has_grad = false; + bool has_dref = false, is_sparse_feedback = false, has_min_lod = false; + uint32_t lod = 0; + }; + virtual std::string to_function_name(const TextureFunctionNameArguments &args); + + struct TextureFunctionArguments + { + // GCC 4.8 workarounds, it doesn't understand '{}' constructor here, use explicit default constructor. + TextureFunctionArguments() = default; + TextureFunctionBaseArguments base; + uint32_t coord = 0, coord_components = 0, dref = 0; + uint32_t grad_x = 0, grad_y = 0, lod = 0, coffset = 0, offset = 0; + uint32_t bias = 0, component = 0, sample = 0, sparse_texel = 0, min_lod = 0; + bool nonuniform_expression = false; + }; + virtual std::string to_function_args(const TextureFunctionArguments &args, bool *p_forward); + + void emit_sparse_feedback_temporaries(uint32_t result_type_id, uint32_t id, uint32_t &feedback_id, + uint32_t &texel_id); + uint32_t get_sparse_feedback_texel_id(uint32_t id) const; virtual void emit_buffer_block(const SPIRVariable &type); virtual void emit_push_constant_block(const SPIRVariable &var); virtual void emit_uniform(const SPIRVariable &var); @@ -441,25 +561,34 @@ protected: bool supports_extensions = false; bool supports_empty_struct = false; bool array_is_value_type = true; + bool buffer_offset_array_is_value_type = true; bool comparison_image_samples_scalar = false; bool native_pointers = false; bool support_small_type_sampling_result = false; bool support_case_fallthrough = true; bool use_array_constructor = false; + bool needs_row_major_load_workaround = false; + bool support_pointer_to_pointer = false; } backend; void emit_struct(SPIRType &type); void emit_resources(); + void emit_extension_workarounds(spv::ExecutionModel model); void emit_buffer_block_native(const SPIRVariable &var); void emit_buffer_reference_block(SPIRType &type, bool forward_declaration); void emit_buffer_block_legacy(const SPIRVariable &var); void emit_buffer_block_flattened(const SPIRVariable &type); + void fixup_implicit_builtin_block_names(); void emit_declared_builtin_block(spv::StorageClass storage, spv::ExecutionModel model); bool should_force_emit_builtin_block(spv::StorageClass storage); void emit_push_constant_block_vulkan(const SPIRVariable &var); void emit_push_constant_block_glsl(const SPIRVariable &var); void emit_interface_block(const SPIRVariable &type); void emit_flattened_io_block(const SPIRVariable &var, const char *qual); + void emit_flattened_io_block_struct(const std::string &basename, const SPIRType &type, const char *qual, + const SmallVector &indices); + void emit_flattened_io_block_member(const std::string &basename, const SPIRType &type, const char *qual, + const SmallVector &indices); void emit_block_chain(SPIRBlock &block); void emit_hoisted_temporaries(SmallVector> &temporaries); std::string constant_value_macro_name(uint32_t id); @@ -522,6 +651,7 @@ protected: void emit_unary_op(uint32_t result_type, uint32_t result_id, uint32_t op0, const char *op); bool expression_is_forwarded(uint32_t id) const; bool expression_suppresses_usage_tracking(uint32_t id) const; + bool expression_read_implies_multiple_reads(uint32_t id) const; SPIRExpression &emit_op(uint32_t result_type, uint32_t result_id, const std::string &rhs, bool forward_rhs, bool suppress_usage_tracking = false); @@ -531,6 +661,9 @@ protected: std::string access_chain_internal(uint32_t base, const uint32_t *indices, uint32_t count, AccessChainFlags flags, AccessChainMeta *meta); + virtual void prepare_access_chain_for_scalar_access(std::string &expr, const SPIRType &type, + spv::StorageClass storage, bool &is_packed); + std::string access_chain(uint32_t base, const uint32_t *indices, uint32_t count, const SPIRType &target_type, AccessChainMeta *meta = nullptr, bool ptr_chain = false); @@ -559,7 +692,7 @@ protected: SPIRExpression &emit_uninitialized_temporary_expression(uint32_t type, uint32_t id); void append_global_func_args(const SPIRFunction &func, uint32_t index, SmallVector &arglist); std::string to_expression(uint32_t id, bool register_expression_read = true); - std::string to_composite_constructor_expression(uint32_t id); + std::string to_composite_constructor_expression(uint32_t id, bool uses_buffer_offset); std::string to_rerolled_array_expression(const std::string &expr, const SPIRType &type); std::string to_enclosed_expression(uint32_t id, bool register_expression_read = true); std::string to_unpacked_expression(uint32_t id, bool register_expression_read = true); @@ -569,15 +702,20 @@ protected: std::string to_pointer_expression(uint32_t id, bool register_expression_read = true); std::string to_enclosed_pointer_expression(uint32_t id, bool register_expression_read = true); std::string to_extract_component_expression(uint32_t id, uint32_t index); + std::string to_extract_constant_composite_expression(uint32_t result_type, const SPIRConstant &c, + const uint32_t *chain, uint32_t length); std::string enclose_expression(const std::string &expr); std::string dereference_expression(const SPIRType &expression_type, const std::string &expr); std::string address_of_expression(const std::string &expr); void strip_enclosed_expression(std::string &expr); std::string to_member_name(const SPIRType &type, uint32_t index); virtual std::string to_member_reference(uint32_t base, const SPIRType &type, uint32_t index, bool ptr_chain); + std::string to_multi_member_reference(const SPIRType &type, const SmallVector &indices); std::string type_to_glsl_constructor(const SPIRType &type); std::string argument_decl(const SPIRFunction::Parameter &arg); virtual std::string to_qualifiers_glsl(uint32_t id); + void fixup_io_block_patch_qualifiers(const SPIRVariable &var); + void emit_output_variable_initializer(const SPIRVariable &var); const char *to_precision_qualifiers_glsl(uint32_t id); virtual const char *to_storage_qualifiers_glsl(const SPIRVariable &var); const char *flags_to_qualifiers_glsl(const SPIRType &type, const Bitset &flags); @@ -624,8 +762,7 @@ protected: void replace_fragment_output(SPIRVariable &var); void replace_fragment_outputs(); - bool check_explicit_lod_allowed(uint32_t lod); - std::string legacy_tex_op(const std::string &op, const SPIRType &imgtype, uint32_t lod, uint32_t id); + std::string legacy_tex_op(const std::string &op, const SPIRType &imgtype, uint32_t id); uint32_t indent = 0; @@ -635,11 +772,16 @@ protected: std::unordered_set flushed_phi_variables; std::unordered_set flattened_buffer_blocks; - std::unordered_set flattened_structs; + std::unordered_map flattened_structs; - std::string load_flattened_struct(SPIRVariable &var); - std::string to_flattened_struct_member(const SPIRVariable &var, uint32_t index); - void store_flattened_struct(SPIRVariable &var, uint32_t value); + ShaderSubgroupSupportHelper shader_subgroup_supporter; + + std::string load_flattened_struct(const std::string &basename, const SPIRType &type); + std::string to_flattened_struct_member(const std::string &basename, const SPIRType &type, uint32_t index); + void store_flattened_struct(uint32_t lhs_id, uint32_t value); + void store_flattened_struct(const std::string &basename, uint32_t rhs, const SPIRType &type, + const SmallVector &indices); + std::string to_flattened_access_chain_expression(uint32_t id); // Usage tracking. If a temporary is used more than once, use the temporary instead to // avoid AST explosion when SPIRV is generated with pure SSA and doesn't write stuff to variables. @@ -654,6 +796,10 @@ protected: // Currently used by NMin/Max/Clamp implementations. std::unordered_map extra_sub_expressions; + SmallVector workaround_ubo_load_overload_types; + void request_workaround_wrapper_overload(TypeID id); + void rewrite_load_for_wrapped_row_major(std::string &expr, TypeID loaded_type, ID ptr); + uint32_t statement_count = 0; inline bool is_legacy() const @@ -671,6 +817,12 @@ protected: return !options.es && options.version < 130; } + bool requires_transpose_2x2 = false; + bool requires_transpose_3x3 = false; + bool requires_transpose_4x4 = false; + bool ray_tracing_is_khr = false; + void ray_tracing_khr_fixup_locations(); + bool args_will_forward(uint32_t id, const uint32_t *args, uint32_t num_args, bool pure); void register_call_out_argument(uint32_t id); void register_impure_function_call(); @@ -712,8 +864,6 @@ protected: virtual void declare_undefined_values(); - static std::string sanitize_underscores(const std::string &str); - bool can_use_io_location(spv::StorageClass storage, bool block); const Instruction *get_next_instruction_in_block(const Instruction &instr); static uint32_t mask_relevant_memory_semantics(uint32_t semantics); @@ -726,16 +876,18 @@ protected: // Builtins in GLSL are always specific signedness, but the SPIR-V can declare them // as either unsigned or signed. - // Sometimes we will need to automatically perform bitcasts on load and store to make this work. - virtual void bitcast_to_builtin_store(uint32_t target_id, std::string &expr, const SPIRType &expr_type); - virtual void bitcast_from_builtin_load(uint32_t source_id, std::string &expr, const SPIRType &expr_type); + // Sometimes we will need to automatically perform casts on load and store to make this work. + virtual void cast_to_builtin_store(uint32_t target_id, std::string &expr, const SPIRType &expr_type); + virtual void cast_from_builtin_load(uint32_t source_id, std::string &expr, const SPIRType &expr_type); void unroll_array_from_complex_load(uint32_t target_id, uint32_t source_id, std::string &expr); + bool unroll_array_to_complex_store(uint32_t target_id, uint32_t source_id); void convert_non_uniform_expression(const SPIRType &type, std::string &expr); void handle_store_to_invariant_variable(uint32_t store_id, uint32_t value_id); void disallow_forwarding_in_expression_chain(const SPIRExpression &expr); bool expression_is_constant_null(uint32_t id) const; + bool expression_is_non_value_type_array(uint32_t ptr); virtual void emit_store_statement(uint32_t lhs_expression, uint32_t rhs_expression); uint32_t get_integer_width_for_instruction(const Instruction &instr) const; diff --git a/src/libraries/spirv_cross/spirv_hlsl.cpp b/src/libraries/spirv_cross/spirv_hlsl.cpp index 3495564de..c0b6d1d9a 100644 --- a/src/libraries/spirv_cross/spirv_hlsl.cpp +++ b/src/libraries/spirv_cross/spirv_hlsl.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 Robert Konrad + * Copyright 2016-2021 Robert Konrad * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -12,6 +12,14 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * + */ + +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #include "spirv_hlsl.hpp" @@ -310,7 +318,7 @@ string CompilerHLSL::image_type_hlsl_modern(const SPIRType &type, uint32_t id) ">"); } -string CompilerHLSL::image_type_hlsl_legacy(const SPIRType &type, uint32_t id) +string CompilerHLSL::image_type_hlsl_legacy(const SPIRType &type, uint32_t /*id*/) { auto &imagetype = get(type.image.type); string res; @@ -373,8 +381,6 @@ string CompilerHLSL::image_type_hlsl_legacy(const SPIRType &type, uint32_t id) res += "MS"; if (type.image.arrayed) res += "Array"; - if (image_is_comparison(type, id)) - res += "Shadow"; return res; } @@ -568,6 +574,13 @@ void CompilerHLSL::emit_builtin_outputs_in_struct() semantic = legacy ? "POSITION" : "SV_Position"; break; + case BuiltInSampleMask: + if (hlsl_options.shader_model < 41 || execution.model != ExecutionModelFragment) + SPIRV_CROSS_THROW("Sample Mask output is only supported in PS 4.1 or higher."); + type = "uint"; + semantic = "SV_Coverage"; + break; + case BuiltInFragDepth: type = "float"; if (legacy) @@ -673,6 +686,13 @@ void CompilerHLSL::emit_builtin_inputs_in_struct() semantic = "SV_SampleIndex"; break; + case BuiltInSampleMask: + if (hlsl_options.shader_model < 50 || get_entry_point().model != ExecutionModelFragment) + SPIRV_CROSS_THROW("Sample Mask input is only supported in PS 5.0 or higher."); + type = "uint"; + semantic = "SV_Coverage"; + break; + case BuiltInGlobalInvocationId: type = "uint3"; semantic = "SV_DispatchThreadID"; @@ -776,7 +796,7 @@ uint32_t CompilerHLSL::type_to_consumed_locations(const SPIRType &type) const if (type.array_size_literal[i]) array_multiplier *= type.array[i]; else - array_multiplier *= get(type.array[i]).scalar(); + array_multiplier *= evaluate_constant_u32(type.array[i]); } elements += array_multiplier * type.columns; } @@ -819,6 +839,21 @@ std::string CompilerHLSL::to_semantic(uint32_t location, ExecutionModel em, Stor return join("TEXCOORD", location); } +std::string CompilerHLSL::to_initializer_expression(const SPIRVariable &var) +{ + // We cannot emit static const initializer for block constants for practical reasons, + // so just inline the initializer. + // FIXME: There is a theoretical problem here if someone tries to composite extract + // into this initializer since we don't declare it properly, but that is somewhat non-sensical. + auto &type = get(var.basetype); + bool is_block = has_decoration(type.self, DecorationBlock); + auto *c = maybe_get(var.initializer); + if (is_block && c) + return constant_expression(*c); + else + return CompilerGLSL::to_initializer_expression(var); +} + void CompilerHLSL::emit_io_block(const SPIRVariable &var) { auto &execution = get_entry_point(); @@ -921,8 +956,15 @@ void CompilerHLSL::emit_interface_block_in_struct(const SPIRVariable &var, unord { SPIRType newtype = type; newtype.columns = 1; + + string effective_semantic; + if (hlsl_options.flatten_matrix_vertex_input_semantics) + effective_semantic = to_semantic(location_number, execution.model, var.storage); + else + effective_semantic = join(semantic, "_", i); + statement(to_interpolation_qualifiers(get_decoration_bitset(var.self)), - variable_decl(newtype, join(name, "_", i)), " : ", semantic, "_", i, ";"); + variable_decl(newtype, join(name, "_", i)), " : ", effective_semantic, ";"); active_locations.insert(location_number++); } } @@ -957,7 +999,9 @@ std::string CompilerHLSL::builtin_to_glsl(spv::BuiltIn builtin, spv::StorageClas auto &var = get(num_workgroups_builtin); auto &type = get(var.basetype); - return sanitize_underscores(join(to_name(num_workgroups_builtin), "_", get_member_name(type.self, 0))); + auto ret = join(to_name(num_workgroups_builtin), "_", get_member_name(type.self, 0)); + ParsedIR::sanitize_underscores(ret); + return ret; } case BuiltInPointCoord: // Crude hack, but there is no real alternative. This path is only enabled if point_coord_compat is set. @@ -979,12 +1023,43 @@ void CompilerHLSL::emit_builtin_variables() bool need_base_vertex_info = false; + std::unordered_map builtin_to_initializer; + ir.for_each_typed_id([&](uint32_t, SPIRVariable &var) { + if (!is_builtin_variable(var) || var.storage != StorageClassOutput || !var.initializer) + return; + + auto *c = this->maybe_get(var.initializer); + if (!c) + return; + + auto &type = this->get(var.basetype); + if (type.basetype == SPIRType::Struct) + { + uint32_t member_count = uint32_t(type.member_types.size()); + for (uint32_t i = 0; i < member_count; i++) + { + if (has_member_decoration(type.self, i, DecorationBuiltIn)) + { + builtin_to_initializer[get_member_decoration(type.self, i, DecorationBuiltIn)] = + c->subconstants[i]; + } + } + } + else if (has_decoration(var.self, DecorationBuiltIn)) + builtin_to_initializer[get_decoration(var.self, DecorationBuiltIn)] = var.initializer; + }); + // Emit global variables for the interface variables which are statically used by the shader. builtins.for_each_bit([&](uint32_t i) { const char *type = nullptr; auto builtin = static_cast(i); uint32_t array_size = 0; + string init_expr; + auto init_itr = builtin_to_initializer.find(builtin); + if (init_itr != builtin_to_initializer.end()) + init_expr = join(" = ", to_expression(init_itr->second)); + switch (builtin) { case BuiltInFragCoord: @@ -1064,20 +1139,29 @@ void CompilerHLSL::emit_builtin_variables() type = "float"; break; + case BuiltInSampleMask: + type = "int"; + break; + default: SPIRV_CROSS_THROW(join("Unsupported builtin in HLSL: ", unsigned(builtin))); } StorageClass storage = active_input_builtins.get(i) ? StorageClassInput : StorageClassOutput; - // FIXME: SampleMask can be both in and out with sample builtin, - // need to distinguish that when we add support for that. if (type) { if (array_size) - statement("static ", type, " ", builtin_to_glsl(builtin, storage), "[", array_size, "];"); + statement("static ", type, " ", builtin_to_glsl(builtin, storage), "[", array_size, "]", init_expr, ";"); else - statement("static ", type, " ", builtin_to_glsl(builtin, storage), ";"); + statement("static ", type, " ", builtin_to_glsl(builtin, storage), init_expr, ";"); + } + + // SampleMask can be both in and out with sample builtin, in this case we have already + // declared the input variable and we need to add the output one now. + if (builtin == BuiltInSampleMask && storage == StorageClassInput && this->active_output_builtins.get(i)) + { + statement("static ", type, " ", this->builtin_to_glsl(builtin, StorageClassOutput), init_expr, ";"); } }); @@ -1103,7 +1187,11 @@ void CompilerHLSL::emit_composite_constants() return; auto &type = this->get(c.constant_type); - if (type.basetype == SPIRType::Struct || !type.array.empty()) + + // Cannot declare block type constants here. + // We do not have the struct type yet. + bool is_block = has_decoration(type.self, DecorationBlock); + if (!is_block && (type.basetype == SPIRType::Struct || !type.array.empty())) { auto name = to_name(c.self); statement("static const ", variable_decl(type, name), " = ", constant_expression(c), ";"); @@ -1184,7 +1272,7 @@ void CompilerHLSL::replace_illegal_names() { static const unordered_set keywords = { // Additional HLSL specific keywords. - "line", "linear", "matrix", "point", "row_major", "sampler", + "line", "linear", "matrix", "point", "row_major", "sampler", "vector" }; CompilerGLSL::replace_illegal_names(keywords); @@ -1195,12 +1283,16 @@ void CompilerHLSL::declare_undefined_values() { bool emitted = false; ir.for_each_typed_id([&](uint32_t, const SPIRUndef &undef) { + auto &type = this->get(undef.basetype); + // OpUndef can be void for some reason ... + if (type.basetype == SPIRType::Void) + return; + string initializer; - if (options.force_zero_initialized_variables && type_can_zero_initialize(this->get(undef.basetype))) + if (options.force_zero_initialized_variables && type_can_zero_initialize(type)) initializer = join(" = ", to_zero_initialized_expression(undef.basetype)); - statement("static ", variable_decl(this->get(undef.basetype), to_name(undef.self), undef.self), - initializer, ";"); + statement("static ", variable_decl(type, to_name(undef.self), undef.self), initializer, ";"); emitted = true; }); @@ -1270,7 +1362,8 @@ void CompilerHLSL::emit_resources() } if (var.storage != StorageClassFunction && !is_builtin_variable(var) && !var.remapped_variable && - type.pointer && (type.storage == StorageClassUniformConstant || type.storage == StorageClassAtomicCounter)) + type.pointer && (type.storage == StorageClassUniformConstant || type.storage == StorageClassAtomicCounter) && + !is_hidden_variable(var)) { emit_uniform(var); emitted = true; @@ -1424,6 +1517,9 @@ void CompilerHLSL::emit_resources() for (auto global : global_variables) { auto &var = get(global); + if (is_hidden_variable(var, true)) + continue; + if (var.storage != StorageClassOutput) { if (!variable_is_lut(var)) @@ -1486,21 +1582,22 @@ void CompilerHLSL::emit_resources() { static const char *qualifiers[] = { "", "unorm ", "snorm " }; static const char *vecsizes[] = { "", "2", "3", "4" }; - emit_texture_size_variants(required_texture_size_variants.uav[norm][comp], vecsizes[comp], true, qualifiers[norm]); + emit_texture_size_variants(required_texture_size_variants.uav[norm][comp], vecsizes[comp], true, + qualifiers[norm]); } } if (requires_fp16_packing) { // HLSL does not pack into a single word sadly :( - statement("uint SPIRV_Cross_packHalf2x16(float2 value)"); + statement("uint spvPackHalf2x16(float2 value)"); begin_scope(); statement("uint2 Packed = f32tof16(value);"); statement("return Packed.x | (Packed.y << 16);"); end_scope(); statement(""); - statement("float2 SPIRV_Cross_unpackHalf2x16(uint value)"); + statement("float2 spvUnpackHalf2x16(uint value)"); begin_scope(); statement("return f16tof32(uint2(value & 0xffff, value >> 16));"); end_scope(); @@ -1509,13 +1606,13 @@ void CompilerHLSL::emit_resources() if (requires_uint2_packing) { - statement("uint64_t SPIRV_Cross_packUint2x32(uint2 value)"); + statement("uint64_t spvPackUint2x32(uint2 value)"); begin_scope(); statement("return (uint64_t(value.y) << 32) | uint64_t(value.x);"); end_scope(); statement(""); - statement("uint2 SPIRV_Cross_unpackUint2x32(uint64_t value)"); + statement("uint2 spvUnpackUint2x32(uint64_t value)"); begin_scope(); statement("uint2 Unpacked;"); statement("Unpacked.x = uint(value & 0xffffffff);"); @@ -1528,14 +1625,14 @@ void CompilerHLSL::emit_resources() if (requires_explicit_fp16_packing) { // HLSL does not pack into a single word sadly :( - statement("uint SPIRV_Cross_packFloat2x16(min16float2 value)"); + statement("uint spvPackFloat2x16(min16float2 value)"); begin_scope(); statement("uint2 Packed = f32tof16(value);"); statement("return Packed.x | (Packed.y << 16);"); end_scope(); statement(""); - statement("min16float2 SPIRV_Cross_unpackFloat2x16(uint value)"); + statement("min16float2 spvUnpackFloat2x16(uint value)"); begin_scope(); statement("return min16float2(f16tof32(uint2(value & 0xffff, value >> 16)));"); end_scope(); @@ -1545,14 +1642,14 @@ void CompilerHLSL::emit_resources() // HLSL does not seem to have builtins for these operation, so roll them by hand ... if (requires_unorm8_packing) { - statement("uint SPIRV_Cross_packUnorm4x8(float4 value)"); + statement("uint spvPackUnorm4x8(float4 value)"); begin_scope(); statement("uint4 Packed = uint4(round(saturate(value) * 255.0));"); statement("return Packed.x | (Packed.y << 8) | (Packed.z << 16) | (Packed.w << 24);"); end_scope(); statement(""); - statement("float4 SPIRV_Cross_unpackUnorm4x8(uint value)"); + statement("float4 spvUnpackUnorm4x8(uint value)"); begin_scope(); statement("uint4 Packed = uint4(value & 0xff, (value >> 8) & 0xff, (value >> 16) & 0xff, value >> 24);"); statement("return float4(Packed) / 255.0;"); @@ -1562,14 +1659,14 @@ void CompilerHLSL::emit_resources() if (requires_snorm8_packing) { - statement("uint SPIRV_Cross_packSnorm4x8(float4 value)"); + statement("uint spvPackSnorm4x8(float4 value)"); begin_scope(); statement("int4 Packed = int4(round(clamp(value, -1.0, 1.0) * 127.0)) & 0xff;"); statement("return uint(Packed.x | (Packed.y << 8) | (Packed.z << 16) | (Packed.w << 24));"); end_scope(); statement(""); - statement("float4 SPIRV_Cross_unpackSnorm4x8(uint value)"); + statement("float4 spvUnpackSnorm4x8(uint value)"); begin_scope(); statement("int SignedValue = int(value);"); statement("int4 Packed = int4(SignedValue << 24, SignedValue << 16, SignedValue << 8, SignedValue) >> 24;"); @@ -1580,14 +1677,14 @@ void CompilerHLSL::emit_resources() if (requires_unorm16_packing) { - statement("uint SPIRV_Cross_packUnorm2x16(float2 value)"); + statement("uint spvPackUnorm2x16(float2 value)"); begin_scope(); statement("uint2 Packed = uint2(round(saturate(value) * 65535.0));"); statement("return Packed.x | (Packed.y << 16);"); end_scope(); statement(""); - statement("float2 SPIRV_Cross_unpackUnorm2x16(uint value)"); + statement("float2 spvUnpackUnorm2x16(uint value)"); begin_scope(); statement("uint2 Packed = uint2(value & 0xffff, value >> 16);"); statement("return float2(Packed) / 65535.0;"); @@ -1597,14 +1694,14 @@ void CompilerHLSL::emit_resources() if (requires_snorm16_packing) { - statement("uint SPIRV_Cross_packSnorm2x16(float2 value)"); + statement("uint spvPackSnorm2x16(float2 value)"); begin_scope(); statement("int2 Packed = int2(round(clamp(value, -1.0, 1.0) * 32767.0)) & 0xffff;"); statement("return uint(Packed.x | (Packed.y << 16));"); end_scope(); statement(""); - statement("float2 SPIRV_Cross_unpackSnorm2x16(uint value)"); + statement("float2 spvUnpackSnorm2x16(uint value)"); begin_scope(); statement("int SignedValue = int(value);"); statement("int2 Packed = int2(SignedValue << 16, SignedValue) >> 16;"); @@ -1618,7 +1715,7 @@ void CompilerHLSL::emit_resources() static const char *types[] = { "uint", "uint2", "uint3", "uint4" }; for (auto &type : types) { - statement(type, " SPIRV_Cross_bitfieldInsert(", type, " Base, ", type, " Insert, uint Offset, uint Count)"); + statement(type, " spvBitfieldInsert(", type, " Base, ", type, " Insert, uint Offset, uint Count)"); begin_scope(); statement("uint Mask = Count == 32 ? 0xffffffff : (((1u << Count) - 1) << (Offset & 31));"); statement("return (Base & ~Mask) | ((Insert << Offset) & Mask);"); @@ -1632,7 +1729,7 @@ void CompilerHLSL::emit_resources() static const char *unsigned_types[] = { "uint", "uint2", "uint3", "uint4" }; for (auto &type : unsigned_types) { - statement(type, " SPIRV_Cross_bitfieldUExtract(", type, " Base, uint Offset, uint Count)"); + statement(type, " spvBitfieldUExtract(", type, " Base, uint Offset, uint Count)"); begin_scope(); statement("uint Mask = Count == 32 ? 0xffffffff : ((1 << Count) - 1);"); statement("return (Base >> Offset) & Mask;"); @@ -1644,7 +1741,7 @@ void CompilerHLSL::emit_resources() static const char *signed_types[] = { "int", "int2", "int3", "int4" }; for (auto &type : signed_types) { - statement(type, " SPIRV_Cross_bitfieldSExtract(", type, " Base, int Offset, int Count)"); + statement(type, " spvBitfieldSExtract(", type, " Base, int Offset, int Count)"); begin_scope(); statement("int Mask = Count == 32 ? -1 : ((1 << Count) - 1);"); statement(type, " Masked = (Base >> Offset) & Mask;"); @@ -1659,7 +1756,7 @@ void CompilerHLSL::emit_resources() { statement("// Returns the inverse of a matrix, by using the algorithm of calculating the classical"); statement("// adjoint and dividing by the determinant. The contents of the matrix are changed."); - statement("float2x2 SPIRV_Cross_Inverse(float2x2 m)"); + statement("float2x2 spvInverse(float2x2 m)"); begin_scope(); statement("float2x2 adj; // The adjoint matrix (inverse after dividing by determinant)"); statement_no_indent(""); @@ -1683,29 +1780,29 @@ void CompilerHLSL::emit_resources() if (requires_inverse_3x3) { statement("// Returns the determinant of a 2x2 matrix."); - statement("float SPIRV_Cross_Det2x2(float a1, float a2, float b1, float b2)"); + statement("float spvDet2x2(float a1, float a2, float b1, float b2)"); begin_scope(); statement("return a1 * b2 - b1 * a2;"); end_scope(); statement_no_indent(""); statement("// Returns the inverse of a matrix, by using the algorithm of calculating the classical"); statement("// adjoint and dividing by the determinant. The contents of the matrix are changed."); - statement("float3x3 SPIRV_Cross_Inverse(float3x3 m)"); + statement("float3x3 spvInverse(float3x3 m)"); begin_scope(); statement("float3x3 adj; // The adjoint matrix (inverse after dividing by determinant)"); statement_no_indent(""); statement("// Create the transpose of the cofactors, as the classical adjoint of the matrix."); - statement("adj[0][0] = SPIRV_Cross_Det2x2(m[1][1], m[1][2], m[2][1], m[2][2]);"); - statement("adj[0][1] = -SPIRV_Cross_Det2x2(m[0][1], m[0][2], m[2][1], m[2][2]);"); - statement("adj[0][2] = SPIRV_Cross_Det2x2(m[0][1], m[0][2], m[1][1], m[1][2]);"); + statement("adj[0][0] = spvDet2x2(m[1][1], m[1][2], m[2][1], m[2][2]);"); + statement("adj[0][1] = -spvDet2x2(m[0][1], m[0][2], m[2][1], m[2][2]);"); + statement("adj[0][2] = spvDet2x2(m[0][1], m[0][2], m[1][1], m[1][2]);"); statement_no_indent(""); - statement("adj[1][0] = -SPIRV_Cross_Det2x2(m[1][0], m[1][2], m[2][0], m[2][2]);"); - statement("adj[1][1] = SPIRV_Cross_Det2x2(m[0][0], m[0][2], m[2][0], m[2][2]);"); - statement("adj[1][2] = -SPIRV_Cross_Det2x2(m[0][0], m[0][2], m[1][0], m[1][2]);"); + statement("adj[1][0] = -spvDet2x2(m[1][0], m[1][2], m[2][0], m[2][2]);"); + statement("adj[1][1] = spvDet2x2(m[0][0], m[0][2], m[2][0], m[2][2]);"); + statement("adj[1][2] = -spvDet2x2(m[0][0], m[0][2], m[1][0], m[1][2]);"); statement_no_indent(""); - statement("adj[2][0] = SPIRV_Cross_Det2x2(m[1][0], m[1][1], m[2][0], m[2][1]);"); - statement("adj[2][1] = -SPIRV_Cross_Det2x2(m[0][0], m[0][1], m[2][0], m[2][1]);"); - statement("adj[2][2] = SPIRV_Cross_Det2x2(m[0][0], m[0][1], m[1][0], m[1][1]);"); + statement("adj[2][0] = spvDet2x2(m[1][0], m[1][1], m[2][0], m[2][1]);"); + statement("adj[2][1] = -spvDet2x2(m[0][0], m[0][1], m[2][0], m[2][1]);"); + statement("adj[2][2] = spvDet2x2(m[0][0], m[0][1], m[1][0], m[1][1]);"); statement_no_indent(""); statement("// Calculate the determinant as a combination of the cofactors of the first row."); statement("float det = (adj[0][0] * m[0][0]) + (adj[0][1] * m[1][0]) + (adj[0][2] * m[2][0]);"); @@ -1722,7 +1819,7 @@ void CompilerHLSL::emit_resources() if (!requires_inverse_3x3) { statement("// Returns the determinant of a 2x2 matrix."); - statement("float SPIRV_Cross_Det2x2(float a1, float a2, float b1, float b2)"); + statement("float spvDet2x2(float a1, float a2, float b1, float b2)"); begin_scope(); statement("return a1 * b2 - b1 * a2;"); end_scope(); @@ -1730,71 +1827,71 @@ void CompilerHLSL::emit_resources() } statement("// Returns the determinant of a 3x3 matrix."); - statement("float SPIRV_Cross_Det3x3(float a1, float a2, float a3, float b1, float b2, float b3, float c1, " + statement("float spvDet3x3(float a1, float a2, float a3, float b1, float b2, float b3, float c1, " "float c2, float c3)"); begin_scope(); - statement("return a1 * SPIRV_Cross_Det2x2(b2, b3, c2, c3) - b1 * SPIRV_Cross_Det2x2(a2, a3, c2, c3) + c1 * " - "SPIRV_Cross_Det2x2(a2, a3, " + statement("return a1 * spvDet2x2(b2, b3, c2, c3) - b1 * spvDet2x2(a2, a3, c2, c3) + c1 * " + "spvDet2x2(a2, a3, " "b2, b3);"); end_scope(); statement_no_indent(""); statement("// Returns the inverse of a matrix, by using the algorithm of calculating the classical"); statement("// adjoint and dividing by the determinant. The contents of the matrix are changed."); - statement("float4x4 SPIRV_Cross_Inverse(float4x4 m)"); + statement("float4x4 spvInverse(float4x4 m)"); begin_scope(); statement("float4x4 adj; // The adjoint matrix (inverse after dividing by determinant)"); statement_no_indent(""); statement("// Create the transpose of the cofactors, as the classical adjoint of the matrix."); statement( - "adj[0][0] = SPIRV_Cross_Det3x3(m[1][1], m[1][2], m[1][3], m[2][1], m[2][2], m[2][3], m[3][1], m[3][2], " + "adj[0][0] = spvDet3x3(m[1][1], m[1][2], m[1][3], m[2][1], m[2][2], m[2][3], m[3][1], m[3][2], " "m[3][3]);"); statement( - "adj[0][1] = -SPIRV_Cross_Det3x3(m[0][1], m[0][2], m[0][3], m[2][1], m[2][2], m[2][3], m[3][1], m[3][2], " + "adj[0][1] = -spvDet3x3(m[0][1], m[0][2], m[0][3], m[2][1], m[2][2], m[2][3], m[3][1], m[3][2], " "m[3][3]);"); statement( - "adj[0][2] = SPIRV_Cross_Det3x3(m[0][1], m[0][2], m[0][3], m[1][1], m[1][2], m[1][3], m[3][1], m[3][2], " + "adj[0][2] = spvDet3x3(m[0][1], m[0][2], m[0][3], m[1][1], m[1][2], m[1][3], m[3][1], m[3][2], " "m[3][3]);"); statement( - "adj[0][3] = -SPIRV_Cross_Det3x3(m[0][1], m[0][2], m[0][3], m[1][1], m[1][2], m[1][3], m[2][1], m[2][2], " + "adj[0][3] = -spvDet3x3(m[0][1], m[0][2], m[0][3], m[1][1], m[1][2], m[1][3], m[2][1], m[2][2], " "m[2][3]);"); statement_no_indent(""); statement( - "adj[1][0] = -SPIRV_Cross_Det3x3(m[1][0], m[1][2], m[1][3], m[2][0], m[2][2], m[2][3], m[3][0], m[3][2], " + "adj[1][0] = -spvDet3x3(m[1][0], m[1][2], m[1][3], m[2][0], m[2][2], m[2][3], m[3][0], m[3][2], " "m[3][3]);"); statement( - "adj[1][1] = SPIRV_Cross_Det3x3(m[0][0], m[0][2], m[0][3], m[2][0], m[2][2], m[2][3], m[3][0], m[3][2], " + "adj[1][1] = spvDet3x3(m[0][0], m[0][2], m[0][3], m[2][0], m[2][2], m[2][3], m[3][0], m[3][2], " "m[3][3]);"); statement( - "adj[1][2] = -SPIRV_Cross_Det3x3(m[0][0], m[0][2], m[0][3], m[1][0], m[1][2], m[1][3], m[3][0], m[3][2], " + "adj[1][2] = -spvDet3x3(m[0][0], m[0][2], m[0][3], m[1][0], m[1][2], m[1][3], m[3][0], m[3][2], " "m[3][3]);"); statement( - "adj[1][3] = SPIRV_Cross_Det3x3(m[0][0], m[0][2], m[0][3], m[1][0], m[1][2], m[1][3], m[2][0], m[2][2], " + "adj[1][3] = spvDet3x3(m[0][0], m[0][2], m[0][3], m[1][0], m[1][2], m[1][3], m[2][0], m[2][2], " "m[2][3]);"); statement_no_indent(""); statement( - "adj[2][0] = SPIRV_Cross_Det3x3(m[1][0], m[1][1], m[1][3], m[2][0], m[2][1], m[2][3], m[3][0], m[3][1], " + "adj[2][0] = spvDet3x3(m[1][0], m[1][1], m[1][3], m[2][0], m[2][1], m[2][3], m[3][0], m[3][1], " "m[3][3]);"); statement( - "adj[2][1] = -SPIRV_Cross_Det3x3(m[0][0], m[0][1], m[0][3], m[2][0], m[2][1], m[2][3], m[3][0], m[3][1], " + "adj[2][1] = -spvDet3x3(m[0][0], m[0][1], m[0][3], m[2][0], m[2][1], m[2][3], m[3][0], m[3][1], " "m[3][3]);"); statement( - "adj[2][2] = SPIRV_Cross_Det3x3(m[0][0], m[0][1], m[0][3], m[1][0], m[1][1], m[1][3], m[3][0], m[3][1], " + "adj[2][2] = spvDet3x3(m[0][0], m[0][1], m[0][3], m[1][0], m[1][1], m[1][3], m[3][0], m[3][1], " "m[3][3]);"); statement( - "adj[2][3] = -SPIRV_Cross_Det3x3(m[0][0], m[0][1], m[0][3], m[1][0], m[1][1], m[1][3], m[2][0], m[2][1], " + "adj[2][3] = -spvDet3x3(m[0][0], m[0][1], m[0][3], m[1][0], m[1][1], m[1][3], m[2][0], m[2][1], " "m[2][3]);"); statement_no_indent(""); statement( - "adj[3][0] = -SPIRV_Cross_Det3x3(m[1][0], m[1][1], m[1][2], m[2][0], m[2][1], m[2][2], m[3][0], m[3][1], " + "adj[3][0] = -spvDet3x3(m[1][0], m[1][1], m[1][2], m[2][0], m[2][1], m[2][2], m[3][0], m[3][1], " "m[3][2]);"); statement( - "adj[3][1] = SPIRV_Cross_Det3x3(m[0][0], m[0][1], m[0][2], m[2][0], m[2][1], m[2][2], m[3][0], m[3][1], " + "adj[3][1] = spvDet3x3(m[0][0], m[0][1], m[0][2], m[2][0], m[2][1], m[2][2], m[3][0], m[3][1], " "m[3][2]);"); statement( - "adj[3][2] = -SPIRV_Cross_Det3x3(m[0][0], m[0][1], m[0][2], m[1][0], m[1][1], m[1][2], m[3][0], m[3][1], " + "adj[3][2] = -spvDet3x3(m[0][0], m[0][1], m[0][2], m[1][0], m[1][1], m[1][2], m[3][0], m[3][1], " "m[3][2]);"); statement( - "adj[3][3] = SPIRV_Cross_Det3x3(m[0][0], m[0][1], m[0][2], m[1][0], m[1][1], m[1][2], m[2][0], m[2][1], " + "adj[3][3] = spvDet3x3(m[0][0], m[0][1], m[0][2], m[1][0], m[1][1], m[1][2], m[2][0], m[2][1], " "m[2][2]);"); statement_no_indent(""); statement("// Calculate the determinant as a combination of the cofactors of the first row."); @@ -1811,7 +1908,7 @@ void CompilerHLSL::emit_resources() if (requires_scalar_reflect) { // FP16/FP64? No templates in HLSL. - statement("float SPIRV_Cross_Reflect(float i, float n)"); + statement("float spvReflect(float i, float n)"); begin_scope(); statement("return i - 2.0 * dot(n, i) * n;"); end_scope(); @@ -1821,7 +1918,7 @@ void CompilerHLSL::emit_resources() if (requires_scalar_refract) { // FP16/FP64? No templates in HLSL. - statement("float SPIRV_Cross_Refract(float i, float n, float eta)"); + statement("float spvRefract(float i, float n, float eta)"); begin_scope(); statement("float NoI = n * i;"); statement("float NoI2 = NoI * NoI;"); @@ -1841,7 +1938,7 @@ void CompilerHLSL::emit_resources() if (requires_scalar_faceforward) { // FP16/FP64? No templates in HLSL. - statement("float SPIRV_Cross_FaceForward(float n, float i, float nref)"); + statement("float spvFaceForward(float n, float i, float nref)"); begin_scope(); statement("return i * nref < 0.0 ? n : -n;"); end_scope(); @@ -1849,15 +1946,16 @@ void CompilerHLSL::emit_resources() } } -void CompilerHLSL::emit_texture_size_variants(uint64_t variant_mask, const char *vecsize_qualifier, bool uav, const char *type_qualifier) +void CompilerHLSL::emit_texture_size_variants(uint64_t variant_mask, const char *vecsize_qualifier, bool uav, + const char *type_qualifier) { if (variant_mask == 0) return; static const char *types[QueryTypeCount] = { "float", "int", "uint" }; static const char *dims[QueryDimCount] = { "Texture1D", "Texture1DArray", "Texture2D", "Texture2DArray", - "Texture3D", "Buffer", "TextureCube", "TextureCubeArray", - "Texture2DMS", "Texture2DMSArray" }; + "Texture3D", "Buffer", "TextureCube", "TextureCubeArray", + "Texture2DMS", "Texture2DMSArray" }; static const bool has_lod[QueryDimCount] = { true, true, true, true, true, false, true, true, false, false }; @@ -1879,9 +1977,9 @@ void CompilerHLSL::emit_texture_size_variants(uint64_t variant_mask, const char if ((variant_mask & mask) == 0) continue; - statement(ret_types[index], " SPIRV_Cross_", (uav ? "image" : "texture"), "Size(", (uav ? "RW" : ""), - dims[index], "<", type_qualifier, types[type_index], vecsize_qualifier, - "> Tex, ", (uav ? "" : "uint Level, "), "out uint Param)"); + statement(ret_types[index], " spv", (uav ? "Image" : "Texture"), "Size(", (uav ? "RW" : ""), + dims[index], "<", type_qualifier, types[type_index], vecsize_qualifier, "> Tex, ", + (uav ? "" : "uint Level, "), "out uint Param)"); begin_scope(); statement(ret_types[index], " ret;"); switch (return_arguments[index]) @@ -2003,7 +2101,7 @@ void CompilerHLSL::emit_buffer_block(const SPIRVariable &var) { // Flatten the top-level struct so we can use packoffset, // this restriction is similar to GLSL where layout(offset) is not possible on sub-structs. - flattened_structs.insert(var.self); + flattened_structs[var.self] = false; // Prefer the block name if possible. auto buffer_name = to_name(type.self, false); @@ -2051,7 +2149,9 @@ void CompilerHLSL::emit_buffer_block(const SPIRVariable &var) add_member_name(type, i); auto backup_name = get_member_name(type.self, i); auto member_name = to_member_name(type, i); - set_member_name(type.self, i, sanitize_underscores(join(to_name(var.self), "_", member_name))); + member_name = join(to_name(var.self), "_", member_name); + ParsedIR::sanitize_underscores(member_name); + set_member_name(type.self, i, member_name); emit_struct_member(type, member, i, ""); set_member_name(type.self, i, backup_name); i++; @@ -2108,7 +2208,7 @@ void CompilerHLSL::emit_push_constant_block(const SPIRVariable &var) ") cannot be expressed with either HLSL packing layout or packoffset.")); } - flattened_structs.insert(var.self); + flattened_structs[var.self] = false; type.member_name_cache.clear(); add_resource_name(var.self); auto &memb = ir.meta[type.self].members; @@ -2132,8 +2232,9 @@ void CompilerHLSL::emit_push_constant_block(const SPIRVariable &var) add_member_name(type, constant_index); auto backup_name = get_member_name(type.self, i); auto member_name = to_member_name(type, i); - set_member_name(type.self, constant_index, - sanitize_underscores(join(to_name(var.self), "_", member_name))); + member_name = join(to_name(var.self), "_", member_name); + ParsedIR::sanitize_underscores(member_name); + set_member_name(type.self, constant_index, member_name); emit_struct_member(type, member, i, "", layout.start); set_member_name(type.self, constant_index, backup_name); @@ -2242,7 +2343,7 @@ void CompilerHLSL::emit_function_prototype(SPIRFunction &func, const Bitset &ret out_argument += "out "; out_argument += type_to_glsl(type); out_argument += " "; - out_argument += "SPIRV_Cross_return_value"; + out_argument += "spvReturnValue"; out_argument += type_to_array_glsl(type); arglist.push_back(move(out_argument)); } @@ -2371,7 +2472,11 @@ void CompilerHLSL::emit_hlsl_entry_point() if (legacy) statement(builtin, " = stage_input.", builtin, " + float4(0.5f, 0.5f, 0.0f, 0.0f);"); else + { statement(builtin, " = stage_input.", builtin, ";"); + // ZW are undefined in D3D9, only do this fixup here. + statement(builtin, ".w = 1.0 / ", builtin, ".w;"); + } break; case BuiltInVertexId: @@ -2610,7 +2715,7 @@ void CompilerHLSL::emit_hlsl_entry_point() void CompilerHLSL::emit_fixup() { - if (get_entry_point().model == ExecutionModelVertex) + if (is_vertex_like_shader()) { // Do various mangling on the gl_Position. if (hlsl_options.shader_model <= 30) @@ -2628,8 +2733,11 @@ void CompilerHLSL::emit_fixup() } } -void CompilerHLSL::emit_texture_op(const Instruction &i) +void CompilerHLSL::emit_texture_op(const Instruction &i, bool sparse) { + if (sparse) + SPIRV_CROSS_THROW("Sparse feedback not yet supported in HLSL."); + auto *ops = stream(i); auto op = static_cast(i.op); uint32_t length = i.length; @@ -2818,7 +2926,7 @@ void CompilerHLSL::emit_texture_op(const Instruction &i) } else if (gather) { - uint32_t comp_num = get(comp).scalar(); + uint32_t comp_num = evaluate_constant_u32(comp); if (hlsl_options.shader_model >= 50) { switch (comp_num) @@ -2884,14 +2992,15 @@ void CompilerHLSL::emit_texture_op(const Instruction &i) SPIRV_CROSS_THROW("textureGather is not supported in HLSL shader model 2/3."); if (offset || coffset) SPIRV_CROSS_THROW("textureOffset is not supported in HLSL shader model 2/3."); - if (proj) - texop += "proj"; + if (grad_x || grad_y) texop += "grad"; - if (lod) + else if (lod) texop += "lod"; - if (bias) + else if (bias) texop += "bias"; + else if (proj || dref) + texop += "proj"; } } @@ -2943,24 +3052,52 @@ void CompilerHLSL::emit_texture_op(const Instruction &i) if (proj && hlsl_options.shader_model >= 40) // Legacy HLSL has "proj" operations which do this for us. coord_expr = coord_expr + " / " + to_extract_component_expression(coord, coord_components); - if (hlsl_options.shader_model < 40 && lod) + if (hlsl_options.shader_model < 40) { - string coord_filler; - for (uint32_t size = coord_components; size < 3; ++size) + if (dref) { - coord_filler += ", 0.0"; - } - coord_expr = "float4(" + coord_expr + coord_filler + ", " + to_expression(lod) + ")"; - } + if (imgtype.image.dim != spv::Dim1D && imgtype.image.dim != spv::Dim2D) + { + SPIRV_CROSS_THROW( + "Depth comparison is only supported for 1D and 2D textures in HLSL shader model 2/3."); + } - if (hlsl_options.shader_model < 40 && bias) - { - string coord_filler; - for (uint32_t size = coord_components; size < 3; ++size) - { - coord_filler += ", 0.0"; + if (grad_x || grad_y) + SPIRV_CROSS_THROW("Depth comparison is not supported for grad sampling in HLSL shader model 2/3."); + + for (uint32_t size = coord_components; size < 2; ++size) + coord_expr += ", 0.0"; + + forward = forward && should_forward(dref); + coord_expr += ", " + to_expression(dref); } - coord_expr = "float4(" + coord_expr + coord_filler + ", " + to_expression(bias) + ")"; + else if (lod || bias || proj) + { + for (uint32_t size = coord_components; size < 3; ++size) + coord_expr += ", 0.0"; + } + + if (lod) + { + coord_expr = "float4(" + coord_expr + ", " + to_expression(lod) + ")"; + } + else if (bias) + { + coord_expr = "float4(" + coord_expr + ", " + to_expression(bias) + ")"; + } + else if (proj) + { + coord_expr = "float4(" + coord_expr + ", " + to_extract_component_expression(coord, coord_components) + ")"; + } + else if (dref) + { + // A "normal" sample gets fed into tex2Dproj as well, because the + // regular tex2D accepts only two coordinates. + coord_expr = "float4(" + coord_expr + ", 1.0)"; + } + + if (!!lod + !!bias + !!proj > 1) + SPIRV_CROSS_THROW("Legacy HLSL can only use one of lod/bias/proj modifiers."); } if (op == OpImageFetch) @@ -2973,11 +3110,8 @@ void CompilerHLSL::emit_texture_op(const Instruction &i) expr += ", "; expr += coord_expr; - if (dref) + if (dref && hlsl_options.shader_model >= 40) { - if (hlsl_options.shader_model < 40) - SPIRV_CROSS_THROW("Legacy HLSL does not support comparison sampling."); - forward = forward && should_forward(dref); expr += ", "; @@ -3032,6 +3166,9 @@ void CompilerHLSL::emit_texture_op(const Instruction &i) expr += ")"; + if (dref && hlsl_options.shader_model < 40) + expr += ".x"; + if (op == OpImageQueryLod) { // This is rather awkward. @@ -3335,7 +3472,7 @@ string CompilerHLSL::bitcast_glsl_op(const SPIRType &out_type, const SPIRType &i requires_explicit_fp16_packing = true; force_recompile(); } - return "SPIRV_Cross_unpackFloat2x16"; + return "spvUnpackFloat2x16"; } else if (out_type.basetype == SPIRType::UInt && in_type.basetype == SPIRType::Half && in_type.vecsize == 2) { @@ -3344,7 +3481,7 @@ string CompilerHLSL::bitcast_glsl_op(const SPIRType &out_type, const SPIRType &i requires_explicit_fp16_packing = true; force_recompile(); } - return "SPIRV_Cross_packFloat2x16"; + return "spvPackFloat2x16"; } else return ""; @@ -3370,7 +3507,10 @@ void CompilerHLSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, break; case GLSLstd450RoundEven: - SPIRV_CROSS_THROW("roundEven is not supported on HLSL."); + if (hlsl_options.shader_model < 40) + SPIRV_CROSS_THROW("roundEven is not supported in HLSL shader model 2/3."); + emit_unary_func_op(result_type, id, args[0], "round"); + break; case GLSLstd450Acosh: case GLSLstd450Asinh: @@ -3406,7 +3546,7 @@ void CompilerHLSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, requires_fp16_packing = true; force_recompile(); } - emit_unary_func_op(result_type, id, args[0], "SPIRV_Cross_packHalf2x16"); + emit_unary_func_op(result_type, id, args[0], "spvPackHalf2x16"); break; case GLSLstd450UnpackHalf2x16: @@ -3415,7 +3555,7 @@ void CompilerHLSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, requires_fp16_packing = true; force_recompile(); } - emit_unary_func_op(result_type, id, args[0], "SPIRV_Cross_unpackHalf2x16"); + emit_unary_func_op(result_type, id, args[0], "spvUnpackHalf2x16"); break; case GLSLstd450PackSnorm4x8: @@ -3424,7 +3564,7 @@ void CompilerHLSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, requires_snorm8_packing = true; force_recompile(); } - emit_unary_func_op(result_type, id, args[0], "SPIRV_Cross_packSnorm4x8"); + emit_unary_func_op(result_type, id, args[0], "spvPackSnorm4x8"); break; case GLSLstd450UnpackSnorm4x8: @@ -3433,7 +3573,7 @@ void CompilerHLSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, requires_snorm8_packing = true; force_recompile(); } - emit_unary_func_op(result_type, id, args[0], "SPIRV_Cross_unpackSnorm4x8"); + emit_unary_func_op(result_type, id, args[0], "spvUnpackSnorm4x8"); break; case GLSLstd450PackUnorm4x8: @@ -3442,7 +3582,7 @@ void CompilerHLSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, requires_unorm8_packing = true; force_recompile(); } - emit_unary_func_op(result_type, id, args[0], "SPIRV_Cross_packUnorm4x8"); + emit_unary_func_op(result_type, id, args[0], "spvPackUnorm4x8"); break; case GLSLstd450UnpackUnorm4x8: @@ -3451,7 +3591,7 @@ void CompilerHLSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, requires_unorm8_packing = true; force_recompile(); } - emit_unary_func_op(result_type, id, args[0], "SPIRV_Cross_unpackUnorm4x8"); + emit_unary_func_op(result_type, id, args[0], "spvUnpackUnorm4x8"); break; case GLSLstd450PackSnorm2x16: @@ -3460,7 +3600,7 @@ void CompilerHLSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, requires_snorm16_packing = true; force_recompile(); } - emit_unary_func_op(result_type, id, args[0], "SPIRV_Cross_packSnorm2x16"); + emit_unary_func_op(result_type, id, args[0], "spvPackSnorm2x16"); break; case GLSLstd450UnpackSnorm2x16: @@ -3469,7 +3609,7 @@ void CompilerHLSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, requires_snorm16_packing = true; force_recompile(); } - emit_unary_func_op(result_type, id, args[0], "SPIRV_Cross_unpackSnorm2x16"); + emit_unary_func_op(result_type, id, args[0], "spvUnpackSnorm2x16"); break; case GLSLstd450PackUnorm2x16: @@ -3478,7 +3618,7 @@ void CompilerHLSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, requires_unorm16_packing = true; force_recompile(); } - emit_unary_func_op(result_type, id, args[0], "SPIRV_Cross_packUnorm2x16"); + emit_unary_func_op(result_type, id, args[0], "spvPackUnorm2x16"); break; case GLSLstd450UnpackUnorm2x16: @@ -3487,7 +3627,7 @@ void CompilerHLSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, requires_unorm16_packing = true; force_recompile(); } - emit_unary_func_op(result_type, id, args[0], "SPIRV_Cross_unpackUnorm2x16"); + emit_unary_func_op(result_type, id, args[0], "spvUnpackUnorm2x16"); break; case GLSLstd450PackDouble2x32: @@ -3536,7 +3676,7 @@ void CompilerHLSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, force_recompile(); } } - emit_unary_func_op(result_type, id, args[0], "SPIRV_Cross_Inverse"); + emit_unary_func_op(result_type, id, args[0], "spvInverse"); break; } @@ -3559,7 +3699,7 @@ void CompilerHLSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, requires_scalar_reflect = true; force_recompile(); } - emit_binary_func_op(result_type, id, args[0], args[1], "SPIRV_Cross_Reflect"); + emit_binary_func_op(result_type, id, args[0], args[1], "spvReflect"); } else CompilerGLSL::emit_glsl_op(result_type, id, eop, args, count); @@ -3573,7 +3713,7 @@ void CompilerHLSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, requires_scalar_refract = true; force_recompile(); } - emit_trinary_func_op(result_type, id, args[0], args[1], args[2], "SPIRV_Cross_Refract"); + emit_trinary_func_op(result_type, id, args[0], args[1], args[2], "spvRefract"); } else CompilerGLSL::emit_glsl_op(result_type, id, eop, args, count); @@ -3587,7 +3727,7 @@ void CompilerHLSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, requires_scalar_faceforward = true; force_recompile(); } - emit_trinary_func_op(result_type, id, args[0], args[1], args[2], "SPIRV_Cross_FaceForward"); + emit_trinary_func_op(result_type, id, args[0], args[1], args[2], "spvFaceForward"); } else CompilerGLSL::emit_glsl_op(result_type, id, eop, args, count); @@ -3669,7 +3809,8 @@ void CompilerHLSL::read_access_chain(string *expr, const string &lhs, const SPIR return; } else if (type.width != 32 && !hlsl_options.enable_16bit_types) - SPIRV_CROSS_THROW("Reading types other than 32-bit from ByteAddressBuffer not yet supported, unless SM 6.2 and native 16-bit types are enabled."); + SPIRV_CROSS_THROW("Reading types other than 32-bit from ByteAddressBuffer not yet supported, unless SM 6.2 and " + "native 16-bit types are enabled."); bool templated_load = hlsl_options.shader_model >= 62; string load_expr; @@ -3725,8 +3866,8 @@ void CompilerHLSL::read_access_chain(string *expr, const string &lhs, const SPIR for (uint32_t r = 0; r < type.vecsize; r++) { - load_expr += - join(chain.base, ".Load", template_expr, "(", chain.dynamic_index, chain.static_index + r * chain.matrix_stride, ")"); + load_expr += join(chain.base, ".Load", template_expr, "(", chain.dynamic_index, + chain.static_index + r * chain.matrix_stride, ")"); if (r + 1 < type.vecsize) load_expr += ", "; } @@ -4002,7 +4143,8 @@ void CompilerHLSL::write_access_chain(const SPIRAccessChain &chain, uint32_t val return; } else if (type.width != 32 && !hlsl_options.enable_16bit_types) - SPIRV_CROSS_THROW("Writing types other than 32-bit to RWByteAddressBuffer not yet supported, unless SM 6.2 and native 16-bit types are enabled."); + SPIRV_CROSS_THROW("Writing types other than 32-bit to RWByteAddressBuffer not yet supported, unless SM 6.2 and " + "native 16-bit types are enabled."); bool templated_store = hlsl_options.shader_model >= 62; @@ -4041,7 +4183,8 @@ void CompilerHLSL::write_access_chain(const SPIRAccessChain &chain, uint32_t val } else store_op = "Store"; - statement(chain.base, ".", store_op, template_expr, "(", chain.dynamic_index, chain.static_index, ", ", store_expr, ");"); + statement(chain.base, ".", store_op, template_expr, "(", chain.dynamic_index, chain.static_index, ", ", + store_expr, ");"); } else if (type.columns == 1) { @@ -4071,8 +4214,8 @@ void CompilerHLSL::write_access_chain(const SPIRAccessChain &chain, uint32_t val store_expr = join(bitcast_op, "(", store_expr, ")"); } - statement(chain.base, ".Store", template_expr, "(", chain.dynamic_index, chain.static_index + chain.matrix_stride * r, ", ", - store_expr, ");"); + statement(chain.base, ".Store", template_expr, "(", chain.dynamic_index, + chain.static_index + chain.matrix_stride * r, ", ", store_expr, ");"); } } else if (!chain.row_major_matrix) @@ -4115,8 +4258,8 @@ void CompilerHLSL::write_access_chain(const SPIRAccessChain &chain, uint32_t val store_expr = join(bitcast_op, "(", store_expr, ")"); } - statement(chain.base, ".", store_op, template_expr, "(", chain.dynamic_index, chain.static_index + c * chain.matrix_stride, - ", ", store_expr, ");"); + statement(chain.base, ".", store_op, template_expr, "(", chain.dynamic_index, + chain.static_index + c * chain.matrix_stride, ", ", store_expr, ");"); } } else @@ -4343,13 +4486,14 @@ void CompilerHLSL::emit_atomic(const uint32_t *ops, uint32_t length, spv::Op op) if (data_type.storage == StorageClassImage || !chain) { - statement(atomic_op, "(", to_expression(ops[0]), ", ", to_expression(ops[3]), ", ", to_expression(tmp_id), ");"); + statement(atomic_op, "(", to_expression(ops[0]), ", ", to_expression(ops[3]), ", ", to_expression(tmp_id), + ");"); } else { // RWByteAddress buffer is always uint in its underlying type. - statement(chain->base, ".", atomic_op, "(", chain->dynamic_index, chain->static_index, ", ", to_expression(ops[3]), - ", ", to_expression(tmp_id), ");"); + statement(chain->base, ".", atomic_op, "(", chain->dynamic_index, chain->static_index, ", ", + to_expression(ops[3]), ", ", to_expression(tmp_id), ");"); } } else @@ -4394,7 +4538,7 @@ void CompilerHLSL::emit_subgroup_op(const Instruction &i) uint32_t result_type = ops[0]; uint32_t id = ops[1]; - auto scope = static_cast(get(ops[2]).scalar()); + auto scope = static_cast(evaluate_constant_u32(ops[2])); if (scope != ScopeSubgroup) SPIRV_CROSS_THROW("Only subgroup scope is supported."); @@ -4491,12 +4635,8 @@ void CompilerHLSL::emit_subgroup_op(const Instruction &i) break; case OpGroupNonUniformAllEqual: - { - auto &type = get(result_type); - emit_unary_func_op(result_type, id, ops[3], - type.basetype == SPIRType::Boolean ? "WaveActiveAllEqualBool" : "WaveActiveAllEqual"); + emit_unary_func_op(result_type, id, ops[3], "WaveActiveAllEqual"); break; - } // clang-format off #define HLSL_GROUP_OP(op, hlsl_op, supports_scan) \ @@ -4544,6 +4684,9 @@ case OpGroupNonUniform##op: \ HLSL_GROUP_OP(BitwiseAnd, BitAnd, false) HLSL_GROUP_OP(BitwiseOr, BitOr, false) HLSL_GROUP_OP(BitwiseXor, BitXor, false) + HLSL_GROUP_OP_CAST(LogicalAnd, BitAnd, uint_type) + HLSL_GROUP_OP_CAST(LogicalOr, BitOr, uint_type) + HLSL_GROUP_OP_CAST(LogicalXor, BitXor, uint_type) #undef HLSL_GROUP_OP #undef HLSL_GROUP_OP_CAST @@ -4551,7 +4694,7 @@ case OpGroupNonUniform##op: \ case OpGroupNonUniformQuadSwap: { - uint32_t direction = get(ops[4]).scalar(); + uint32_t direction = evaluate_constant_u32(ops[4]); if (direction == 0) emit_unary_func_op(result_type, id, ops[3], "QuadReadAcrossX"); else if (direction == 1) @@ -4620,9 +4763,9 @@ void CompilerHLSL::emit_instruction(const Instruction &instruction) } if (bitcast_type == CompilerHLSL::TypePackUint2x32) - emit_unary_func_op(ops[0], ops[1], ops[2], "SPIRV_Cross_packUint2x32"); + emit_unary_func_op(ops[0], ops[1], ops[2], "spvPackUint2x32"); else - emit_unary_func_op(ops[0], ops[1], ops[2], "SPIRV_Cross_unpackUint2x32"); + emit_unary_func_op(ops[0], ops[1], ops[2], "spvUnpackUint2x32"); } break; @@ -4995,7 +5138,7 @@ void CompilerHLSL::emit_instruction(const Instruction &instruction) } case OpImageQueryLod: - emit_texture_op(instruction); + emit_texture_op(instruction, false); break; case OpImageQuerySizeLod: @@ -5007,7 +5150,7 @@ void CompilerHLSL::emit_instruction(const Instruction &instruction) auto dummy_samples_levels = join(get_fallback_name(id), "_dummy_parameter"); statement("uint ", dummy_samples_levels, ";"); - auto expr = join("SPIRV_Cross_textureSize(", to_expression(ops[2]), ", ", + auto expr = join("spvTextureSize(", to_expression(ops[2]), ", ", bitcast_expression(SPIRType::UInt, ops[3]), ", ", dummy_samples_levels, ")"); auto &restype = get(ops[0]); @@ -5033,9 +5176,9 @@ void CompilerHLSL::emit_instruction(const Instruction &instruction) string expr; if (uav) - expr = join("SPIRV_Cross_imageSize(", to_expression(ops[2]), ", ", dummy_samples_levels, ")"); + expr = join("spvImageSize(", to_expression(ops[2]), ", ", dummy_samples_levels, ")"); else - expr = join("SPIRV_Cross_textureSize(", to_expression(ops[2]), ", 0u, ", dummy_samples_levels, ")"); + expr = join("spvTextureSize(", to_expression(ops[2]), ", 0u, ", dummy_samples_levels, ")"); auto &restype = get(ops[0]); expr = bitcast_expression(restype, SPIRType::UInt, expr); @@ -5065,9 +5208,9 @@ void CompilerHLSL::emit_instruction(const Instruction &instruction) statement(variable_decl(type, to_name(id)), ";"); if (uav) - statement("SPIRV_Cross_imageSize(", to_expression(ops[2]), ", ", to_name(id), ");"); + statement("spvImageSize(", to_expression(ops[2]), ", ", to_name(id), ");"); else - statement("SPIRV_Cross_textureSize(", to_expression(ops[2]), ", 0u, ", to_name(id), ");"); + statement("spvTextureSize(", to_expression(ops[2]), ", 0u, ", to_name(id), ");"); auto &restype = get(ops[0]); auto expr = bitcast_expression(restype, SPIRType::UInt, to_name(id)); @@ -5209,13 +5352,13 @@ void CompilerHLSL::emit_instruction(const Instruction &instruction) if (opcode == OpMemoryBarrier) { - memory = get(ops[0]).scalar(); - semantics = get(ops[1]).scalar(); + memory = evaluate_constant_u32(ops[0]); + semantics = evaluate_constant_u32(ops[1]); } else { - memory = get(ops[1]).scalar(); - semantics = get(ops[2]).scalar(); + memory = evaluate_constant_u32(ops[1]); + semantics = evaluate_constant_u32(ops[2]); } if (memory == ScopeSubgroup) @@ -5235,8 +5378,8 @@ void CompilerHLSL::emit_instruction(const Instruction &instruction) if (next && next->op == OpControlBarrier) { auto *next_ops = stream(*next); - uint32_t next_memory = get(next_ops[1]).scalar(); - uint32_t next_semantics = get(next_ops[2]).scalar(); + uint32_t next_memory = evaluate_constant_u32(next_ops[1]); + uint32_t next_semantics = evaluate_constant_u32(next_ops[2]); next_semantics = mask_relevant_memory_semantics(next_semantics); // There is no "just execution barrier" in HLSL. @@ -5308,7 +5451,7 @@ void CompilerHLSL::emit_instruction(const Instruction &instruction) force_recompile(); } - auto expr = join("SPIRV_Cross_bitfieldInsert(", to_expression(ops[2]), ", ", to_expression(ops[3]), ", ", + auto expr = join("spvBitfieldInsert(", to_expression(ops[2]), ", ", to_expression(ops[3]), ", ", to_expression(ops[4]), ", ", to_expression(ops[5]), ")"); bool forward = @@ -5330,9 +5473,9 @@ void CompilerHLSL::emit_instruction(const Instruction &instruction) } if (opcode == OpBitFieldSExtract) - HLSL_TFOP(SPIRV_Cross_bitfieldSExtract); + HLSL_TFOP(spvBitfieldSExtract); else - HLSL_TFOP(SPIRV_Cross_bitfieldUExtract); + HLSL_TFOP(spvBitfieldUExtract); break; } @@ -5440,8 +5583,9 @@ void CompilerHLSL::require_texture_query_variant(uint32_t var_id) } auto norm_state = image_format_to_normalized_state(type.image.format); - auto &variant = uav ? required_texture_size_variants.uav[uint32_t(norm_state)][image_format_to_components(type.image.format) - 1] : - required_texture_size_variants.srv; + auto &variant = uav ? required_texture_size_variants + .uav[uint32_t(norm_state)][image_format_to_components(type.image.format) - 1] : + required_texture_size_variants.srv; uint64_t mask = 1ull << bit; if ((variant & mask) == 0) @@ -5525,6 +5669,12 @@ void CompilerHLSL::validate_shader_model() if (hlsl_options.shader_model < 51) SPIRV_CROSS_THROW( "Shader model 5.1 or higher is required to use bindless resources or NonUniformResourceIndex."); + break; + + case CapabilityVariablePointers: + case CapabilityVariablePointersStorageBuffer: + SPIRV_CROSS_THROW("VariablePointers capability is not supported in HLSL."); + default: break; } @@ -5539,6 +5689,8 @@ void CompilerHLSL::validate_shader_model() string CompilerHLSL::compile() { + ir.fixup_reserved_names(); + // Do not deal with ES-isms like precision, older extensions and such. options.es = false; options.version = 450; @@ -5667,8 +5819,8 @@ bool CompilerHLSL::is_hlsl_force_storage_buffer_as_uav(ID id) const const uint32_t desc_set = get_decoration(id, spv::DecorationDescriptorSet); const uint32_t binding = get_decoration(id, spv::DecorationBinding); - - return (force_uav_buffer_bindings.find({desc_set, binding}) != force_uav_buffer_bindings.end()); + + return (force_uav_buffer_bindings.find({ desc_set, binding }) != force_uav_buffer_bindings.end()); } void CompilerHLSL::set_hlsl_force_storage_buffer_as_uav(uint32_t desc_set, uint32_t binding) @@ -5676,3 +5828,8 @@ void CompilerHLSL::set_hlsl_force_storage_buffer_as_uav(uint32_t desc_set, uint3 SetBindingPair pair = { desc_set, binding }; force_uav_buffer_bindings.insert(pair); } + +bool CompilerHLSL::builtin_translates_to_nonarray(spv::BuiltIn builtin) const +{ + return (builtin == BuiltInSampleMask); +} diff --git a/src/libraries/spirv_cross/spirv_hlsl.hpp b/src/libraries/spirv_cross/spirv_hlsl.hpp index 946486dc8..f46b1868a 100644 --- a/src/libraries/spirv_cross/spirv_hlsl.hpp +++ b/src/libraries/spirv_cross/spirv_hlsl.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 Robert Konrad + * Copyright 2016-2021 Robert Konrad * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #ifndef SPIRV_HLSL_HPP #define SPIRV_HLSL_HPP @@ -124,6 +131,12 @@ public: // Uses half/int16_t/uint16_t instead of min16* types. // Also adds support for 16-bit load-store from (RW)ByteAddressBuffer. bool enable_16bit_types = false; + + // If matrices are used as IO variables, flatten the attribute declaration to use + // TEXCOORD{N,N+1,N+2,...} rather than TEXCOORDN_{0,1,2,3}. + // If add_vertex_attribute_remap is used and this feature is used, + // the semantic name will be queried once per active location. + bool flatten_matrix_vertex_input_semantics = false; }; explicit CompilerHLSL(std::vector spirv_) @@ -209,7 +222,7 @@ private: void emit_interface_block_in_struct(const SPIRVariable &type, std::unordered_set &active_locations); void emit_builtin_inputs_in_struct(); void emit_builtin_outputs_in_struct(); - void emit_texture_op(const Instruction &i) override; + void emit_texture_op(const Instruction &i, bool sparse) override; void emit_instruction(const Instruction &instruction) override; void emit_glsl_op(uint32_t result_type, uint32_t result_id, uint32_t op, const uint32_t *args, uint32_t count) override; @@ -231,6 +244,7 @@ private: std::string to_resource_binding(const SPIRVariable &var); std::string to_resource_binding_sampler(const SPIRVariable &var); std::string to_resource_register(HLSLBindingFlagBits flag, char space, uint32_t binding, uint32_t set); + std::string to_initializer_expression(const SPIRVariable &var) override; void emit_sampled_image_op(uint32_t result_type, uint32_t result_id, uint32_t image_id, uint32_t samp_id) override; void emit_access_chain(const Instruction &instruction); void emit_load(const Instruction &instruction); @@ -291,7 +305,8 @@ private: } required_texture_size_variants; void require_texture_query_variant(uint32_t var_id); - void emit_texture_size_variants(uint64_t variant_mask, const char *vecsize_qualifier, bool uav, const char *type_qualifier); + void emit_texture_size_variants(uint64_t variant_mask, const char *vecsize_qualifier, bool uav, + const char *type_qualifier); enum TextureQueryVariantDim { @@ -351,6 +366,9 @@ private: void remap_hlsl_resource_binding(HLSLBindingFlagBits type, uint32_t &desc_set, uint32_t &binding); std::unordered_set force_uav_buffer_bindings; + + // Returns true for BuiltInSampleMask because gl_SampleMask[] is an array in SPIR-V, but SV_Coverage is a scalar in HLSL. + bool builtin_translates_to_nonarray(spv::BuiltIn builtin) const override; }; } // namespace SPIRV_CROSS_NAMESPACE diff --git a/src/libraries/spirv_cross/spirv_msl.cpp b/src/libraries/spirv_cross/spirv_msl.cpp index 55968d016..1d19e6df3 100644 --- a/src/libraries/spirv_cross/spirv_msl.cpp +++ b/src/libraries/spirv_cross/spirv_msl.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 The Brenwill Workshop Ltd. + * Copyright 2016-2021 The Brenwill Workshop Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #include "spirv_msl.hpp" #include "GLSL.std.450.h" @@ -49,11 +56,11 @@ CompilerMSL::CompilerMSL(ParsedIR &&ir_) { } -void CompilerMSL::add_msl_vertex_attribute(const MSLVertexAttr &va) +void CompilerMSL::add_msl_shader_input(const MSLShaderInput &si) { - vtx_attrs_by_location[va.location] = va; - if (va.builtin != BuiltInMax && !vtx_attrs_by_builtin.count(va.builtin)) - vtx_attrs_by_builtin[va.builtin] = va; + inputs_by_location[si.location] = si; + if (si.builtin != BuiltInMax && !inputs_by_builtin.count(si.builtin)) + inputs_by_builtin[si.builtin] = si; } void CompilerMSL::add_msl_resource_binding(const MSLResourceBinding &binding) @@ -91,9 +98,18 @@ void CompilerMSL::set_argument_buffer_device_address_space(uint32_t desc_set, bo } } -bool CompilerMSL::is_msl_vertex_attribute_used(uint32_t location) +bool CompilerMSL::is_msl_shader_input_used(uint32_t location) { - return vtx_attrs_in_use.count(location) != 0; + return location_inputs_in_use.count(location) != 0; +} + +uint32_t CompilerMSL::get_automatic_builtin_input_location(spv::BuiltIn builtin) const +{ + auto itr = builtin_to_automatic_input_location.find(builtin); + if (itr == builtin_to_automatic_input_location.end()) + return k_unknown_location; + else + return itr->second; } bool CompilerMSL::is_msl_resource_binding_used(ExecutionModel model, uint32_t desc_set, uint32_t binding) const @@ -103,6 +119,16 @@ bool CompilerMSL::is_msl_resource_binding_used(ExecutionModel model, uint32_t de return itr != end(resource_bindings) && itr->second.second; } +// Returns the size of the array of resources used by the variable with the specified id. +// The returned value is retrieved from the resource binding added using add_msl_resource_binding(). +uint32_t CompilerMSL::get_resource_array_size(uint32_t id) const +{ + StageSetBinding tuple = { get_entry_point().model, get_decoration(id, DecorationDescriptorSet), + get_decoration(id, DecorationBinding) }; + auto itr = resource_bindings.find(tuple); + return itr != end(resource_bindings) ? itr->second.first.count : 0; +} + uint32_t CompilerMSL::get_automatic_msl_resource_binding(uint32_t id) const { return get_extended_decoration(id, SPIRVCrossDecorationResourceIndexPrimary); @@ -136,7 +162,8 @@ bool CompilerMSL::builtin_translates_to_nonarray(spv::BuiltIn builtin) const void CompilerMSL::build_implicit_builtins() { bool need_sample_pos = active_input_builtins.get(BuiltInSamplePosition); - bool need_vertex_params = capture_output_to_buffer && get_execution_model() == ExecutionModelVertex; + bool need_vertex_params = capture_output_to_buffer && get_execution_model() == ExecutionModelVertex && + !msl_options.vertex_for_tessellation; bool need_tesc_params = get_execution_model() == ExecutionModelTessellationControl; bool need_subgroup_mask = active_input_builtins.get(BuiltInSubgroupEqMask) || active_input_builtins.get(BuiltInSubgroupGeMask) || @@ -145,12 +172,25 @@ void CompilerMSL::build_implicit_builtins() bool need_subgroup_ge_mask = !msl_options.is_ios() && (active_input_builtins.get(BuiltInSubgroupGeMask) || active_input_builtins.get(BuiltInSubgroupGtMask)); bool need_multiview = get_execution_model() == ExecutionModelVertex && !msl_options.view_index_from_device_index && + msl_options.multiview_layered_rendering && (msl_options.multiview || active_input_builtins.get(BuiltInViewIndex)); bool need_dispatch_base = msl_options.dispatch_base && get_execution_model() == ExecutionModelGLCompute && (active_input_builtins.get(BuiltInWorkgroupId) || active_input_builtins.get(BuiltInGlobalInvocationId)); + bool need_grid_params = get_execution_model() == ExecutionModelVertex && msl_options.vertex_for_tessellation; + bool need_vertex_base_params = + need_grid_params && + (active_input_builtins.get(BuiltInVertexId) || active_input_builtins.get(BuiltInVertexIndex) || + active_input_builtins.get(BuiltInBaseVertex) || active_input_builtins.get(BuiltInInstanceId) || + active_input_builtins.get(BuiltInInstanceIndex) || active_input_builtins.get(BuiltInBaseInstance)); + bool need_sample_mask = msl_options.additional_fixed_sample_mask != 0xffffffff; + bool need_local_invocation_index = msl_options.emulate_subgroups && active_input_builtins.get(BuiltInSubgroupId); + bool need_workgroup_size = msl_options.emulate_subgroups && active_input_builtins.get(BuiltInNumSubgroups); + if (need_subpass_input || need_sample_pos || need_subgroup_mask || need_vertex_params || need_tesc_params || - need_multiview || need_dispatch_base || needs_subgroup_invocation_id) + need_multiview || need_dispatch_base || need_vertex_base_params || need_grid_params || needs_sample_id || + needs_subgroup_invocation_id || needs_subgroup_size || need_sample_mask || need_local_invocation_index || + need_workgroup_size) { bool has_frag_coord = false; bool has_sample_id = false; @@ -163,23 +203,64 @@ void CompilerMSL::build_implicit_builtins() bool has_subgroup_invocation_id = false; bool has_subgroup_size = false; bool has_view_idx = false; + bool has_layer = false; + bool has_local_invocation_index = false; + bool has_workgroup_size = false; uint32_t workgroup_id_type = 0; ir.for_each_typed_id([&](uint32_t, SPIRVariable &var) { - if (var.storage != StorageClassInput || !ir.meta[var.self].decoration.builtin) + if (var.storage != StorageClassInput && var.storage != StorageClassOutput) + return; + if (!interface_variable_exists_in_entry_point(var.self)) + return; + if (!has_decoration(var.self, DecorationBuiltIn)) + return; + + BuiltIn builtin = ir.meta[var.self].decoration.builtin_type; + + if (var.storage == StorageClassOutput) + { + if (need_sample_mask && builtin == BuiltInSampleMask) + { + builtin_sample_mask_id = var.self; + mark_implicit_builtin(StorageClassOutput, BuiltInSampleMask, var.self); + does_shader_write_sample_mask = true; + } + } + + if (var.storage != StorageClassInput) return; // Use Metal's native frame-buffer fetch API for subpass inputs. - BuiltIn builtin = ir.meta[var.self].decoration.builtin_type; - if (need_subpass_input && (!msl_options.is_ios() || !msl_options.ios_use_framebuffer_fetch_subpasses) && - builtin == BuiltInFragCoord) + if (need_subpass_input && (!msl_options.use_framebuffer_fetch_subpasses)) { - mark_implicit_builtin(StorageClassInput, BuiltInFragCoord, var.self); - builtin_frag_coord_id = var.self; - has_frag_coord = true; + switch (builtin) + { + case BuiltInFragCoord: + mark_implicit_builtin(StorageClassInput, BuiltInFragCoord, var.self); + builtin_frag_coord_id = var.self; + has_frag_coord = true; + break; + case BuiltInLayer: + if (!msl_options.arrayed_subpass_input || msl_options.multiview) + break; + mark_implicit_builtin(StorageClassInput, BuiltInLayer, var.self); + builtin_layer_id = var.self; + has_layer = true; + break; + case BuiltInViewIndex: + if (!msl_options.multiview) + break; + mark_implicit_builtin(StorageClassInput, BuiltInViewIndex, var.self); + builtin_view_idx_id = var.self; + has_view_idx = true; + break; + default: + break; + } } - if (need_sample_pos && builtin == BuiltInSampleId) + if ((need_sample_pos || needs_sample_id) && builtin == BuiltInSampleId) { builtin_sample_id_id = var.self; mark_implicit_builtin(StorageClassInput, BuiltInSampleId, var.self); @@ -241,7 +322,7 @@ void CompilerMSL::build_implicit_builtins() has_subgroup_invocation_id = true; } - if (need_subgroup_ge_mask && builtin == BuiltInSubgroupSize) + if ((need_subgroup_ge_mask || needs_subgroup_size) && builtin == BuiltInSubgroupSize) { builtin_subgroup_size_id = var.self; mark_implicit_builtin(StorageClassInput, BuiltInSubgroupSize, var.self); @@ -258,6 +339,12 @@ void CompilerMSL::build_implicit_builtins() mark_implicit_builtin(StorageClassInput, BuiltInInstanceIndex, var.self); has_instance_idx = true; break; + case BuiltInBaseInstance: + // If a non-zero base instance is used, we need to adjust for it when calculating the view index. + builtin_base_instance_id = var.self; + mark_implicit_builtin(StorageClassInput, BuiltInBaseInstance, var.self); + has_base_instance = true; + break; case BuiltInViewIndex: builtin_view_idx_id = var.self; mark_implicit_builtin(StorageClassInput, BuiltInViewIndex, var.self); @@ -268,6 +355,20 @@ void CompilerMSL::build_implicit_builtins() } } + if (need_local_invocation_index && builtin == BuiltInLocalInvocationIndex) + { + builtin_local_invocation_index_id = var.self; + mark_implicit_builtin(StorageClassInput, BuiltInLocalInvocationIndex, var.self); + has_local_invocation_index = true; + } + + if (need_workgroup_size && builtin == BuiltInLocalInvocationId) + { + builtin_workgroup_size_id = var.self; + mark_implicit_builtin(StorageClassInput, BuiltInWorkgroupSize, var.self); + has_workgroup_size = true; + } + // The base workgroup needs to have the same type and vector size // as the workgroup or invocation ID, so keep track of the type that // was used. @@ -277,55 +378,99 @@ void CompilerMSL::build_implicit_builtins() }); // Use Metal's native frame-buffer fetch API for subpass inputs. - if (!has_frag_coord && (!msl_options.is_ios() || !msl_options.ios_use_framebuffer_fetch_subpasses) && - need_subpass_input) + if ((!has_frag_coord || (msl_options.multiview && !has_view_idx) || + (msl_options.arrayed_subpass_input && !msl_options.multiview && !has_layer)) && + (!msl_options.use_framebuffer_fetch_subpasses) && need_subpass_input) { - uint32_t offset = ir.increase_bound_by(3); - uint32_t type_id = offset; - uint32_t type_ptr_id = offset + 1; - uint32_t var_id = offset + 2; + if (!has_frag_coord) + { + uint32_t offset = ir.increase_bound_by(3); + uint32_t type_id = offset; + uint32_t type_ptr_id = offset + 1; + uint32_t var_id = offset + 2; - // Create gl_FragCoord. - SPIRType vec4_type; - vec4_type.basetype = SPIRType::Float; - vec4_type.width = 32; - vec4_type.vecsize = 4; - set(type_id, vec4_type); + // Create gl_FragCoord. + SPIRType vec4_type; + vec4_type.basetype = SPIRType::Float; + vec4_type.width = 32; + vec4_type.vecsize = 4; + set(type_id, vec4_type); - SPIRType vec4_type_ptr; - vec4_type_ptr = vec4_type; - vec4_type_ptr.pointer = true; - vec4_type_ptr.parent_type = type_id; - vec4_type_ptr.storage = StorageClassInput; - auto &ptr_type = set(type_ptr_id, vec4_type_ptr); - ptr_type.self = type_id; + SPIRType vec4_type_ptr; + vec4_type_ptr = vec4_type; + vec4_type_ptr.pointer = true; + vec4_type_ptr.pointer_depth++; + vec4_type_ptr.parent_type = type_id; + vec4_type_ptr.storage = StorageClassInput; + auto &ptr_type = set(type_ptr_id, vec4_type_ptr); + ptr_type.self = type_id; - set(var_id, type_ptr_id, StorageClassInput); - set_decoration(var_id, DecorationBuiltIn, BuiltInFragCoord); - builtin_frag_coord_id = var_id; - mark_implicit_builtin(StorageClassInput, BuiltInFragCoord, var_id); + set(var_id, type_ptr_id, StorageClassInput); + set_decoration(var_id, DecorationBuiltIn, BuiltInFragCoord); + builtin_frag_coord_id = var_id; + mark_implicit_builtin(StorageClassInput, BuiltInFragCoord, var_id); + } + + if (!has_layer && msl_options.arrayed_subpass_input && !msl_options.multiview) + { + uint32_t offset = ir.increase_bound_by(2); + uint32_t type_ptr_id = offset; + uint32_t var_id = offset + 1; + + // Create gl_Layer. + SPIRType uint_type_ptr; + uint_type_ptr = get_uint_type(); + uint_type_ptr.pointer = true; + uint_type_ptr.pointer_depth++; + uint_type_ptr.parent_type = get_uint_type_id(); + uint_type_ptr.storage = StorageClassInput; + auto &ptr_type = set(type_ptr_id, uint_type_ptr); + ptr_type.self = get_uint_type_id(); + + set(var_id, type_ptr_id, StorageClassInput); + set_decoration(var_id, DecorationBuiltIn, BuiltInLayer); + builtin_layer_id = var_id; + mark_implicit_builtin(StorageClassInput, BuiltInLayer, var_id); + } + + if (!has_view_idx && msl_options.multiview) + { + uint32_t offset = ir.increase_bound_by(2); + uint32_t type_ptr_id = offset; + uint32_t var_id = offset + 1; + + // Create gl_ViewIndex. + SPIRType uint_type_ptr; + uint_type_ptr = get_uint_type(); + uint_type_ptr.pointer = true; + uint_type_ptr.pointer_depth++; + uint_type_ptr.parent_type = get_uint_type_id(); + uint_type_ptr.storage = StorageClassInput; + auto &ptr_type = set(type_ptr_id, uint_type_ptr); + ptr_type.self = get_uint_type_id(); + + set(var_id, type_ptr_id, StorageClassInput); + set_decoration(var_id, DecorationBuiltIn, BuiltInViewIndex); + builtin_view_idx_id = var_id; + mark_implicit_builtin(StorageClassInput, BuiltInViewIndex, var_id); + } } - if (!has_sample_id && need_sample_pos) + if (!has_sample_id && (need_sample_pos || needs_sample_id)) { - uint32_t offset = ir.increase_bound_by(3); - uint32_t type_id = offset; - uint32_t type_ptr_id = offset + 1; - uint32_t var_id = offset + 2; + uint32_t offset = ir.increase_bound_by(2); + uint32_t type_ptr_id = offset; + uint32_t var_id = offset + 1; // Create gl_SampleID. - SPIRType uint_type; - uint_type.basetype = SPIRType::UInt; - uint_type.width = 32; - set(type_id, uint_type); - SPIRType uint_type_ptr; - uint_type_ptr = uint_type; + uint_type_ptr = get_uint_type(); uint_type_ptr.pointer = true; - uint_type_ptr.parent_type = type_id; + uint_type_ptr.pointer_depth++; + uint_type_ptr.parent_type = get_uint_type_id(); uint_type_ptr.storage = StorageClassInput; auto &ptr_type = set(type_ptr_id, uint_type_ptr); - ptr_type.self = type_id; + ptr_type.self = get_uint_type_id(); set(var_id, type_ptr_id, StorageClassInput); set_decoration(var_id, DecorationBuiltIn, BuiltInSampleId); @@ -334,24 +479,18 @@ void CompilerMSL::build_implicit_builtins() } if ((need_vertex_params && (!has_vertex_idx || !has_base_vertex || !has_instance_idx || !has_base_instance)) || - (need_multiview && (!has_instance_idx || !has_view_idx))) + (need_multiview && (!has_instance_idx || !has_base_instance || !has_view_idx))) { - uint32_t offset = ir.increase_bound_by(2); - uint32_t type_id = offset; - uint32_t type_ptr_id = offset + 1; - - SPIRType uint_type; - uint_type.basetype = SPIRType::UInt; - uint_type.width = 32; - set(type_id, uint_type); + uint32_t type_ptr_id = ir.increase_bound_by(1); SPIRType uint_type_ptr; - uint_type_ptr = uint_type; + uint_type_ptr = get_uint_type(); uint_type_ptr.pointer = true; - uint_type_ptr.parent_type = type_id; + uint_type_ptr.pointer_depth++; + uint_type_ptr.parent_type = get_uint_type_id(); uint_type_ptr.storage = StorageClassInput; auto &ptr_type = set(type_ptr_id, uint_type_ptr); - ptr_type.self = type_id; + ptr_type.self = get_uint_type_id(); if (need_vertex_params && !has_vertex_idx) { @@ -386,7 +525,7 @@ void CompilerMSL::build_implicit_builtins() mark_implicit_builtin(StorageClassInput, BuiltInInstanceIndex, var_id); } - if (need_vertex_params && !has_base_instance) + if (!has_base_instance) // Needed by both multiview and tessellation { uint32_t var_id = ir.increase_bound_by(1); @@ -405,12 +544,13 @@ void CompilerMSL::build_implicit_builtins() // gl_Layer is an output in vertex-pipeline shaders. uint32_t type_ptr_out_id = ir.increase_bound_by(2); SPIRType uint_type_ptr_out; - uint_type_ptr_out = uint_type; + uint_type_ptr_out = get_uint_type(); uint_type_ptr_out.pointer = true; - uint_type_ptr_out.parent_type = type_id; + uint_type_ptr_out.pointer_depth++; + uint_type_ptr_out.parent_type = get_uint_type_id(); uint_type_ptr_out.storage = StorageClassOutput; auto &ptr_out_type = set(type_ptr_out_id, uint_type_ptr_out); - ptr_out_type.self = type_id; + ptr_out_type.self = get_uint_type_id(); uint32_t var_id = type_ptr_out_id + 1; set(var_id, type_ptr_out_id, StorageClassOutput); set_decoration(var_id, DecorationBuiltIn, BuiltInLayer); @@ -430,26 +570,31 @@ void CompilerMSL::build_implicit_builtins() } } - if (need_tesc_params && (!has_invocation_id || !has_primitive_id)) + if ((need_tesc_params && (msl_options.multi_patch_workgroup || !has_invocation_id || !has_primitive_id)) || + need_grid_params) { - uint32_t offset = ir.increase_bound_by(2); - uint32_t type_id = offset; - uint32_t type_ptr_id = offset + 1; - - SPIRType uint_type; - uint_type.basetype = SPIRType::UInt; - uint_type.width = 32; - set(type_id, uint_type); + uint32_t type_ptr_id = ir.increase_bound_by(1); SPIRType uint_type_ptr; - uint_type_ptr = uint_type; + uint_type_ptr = get_uint_type(); uint_type_ptr.pointer = true; - uint_type_ptr.parent_type = type_id; + uint_type_ptr.pointer_depth++; + uint_type_ptr.parent_type = get_uint_type_id(); uint_type_ptr.storage = StorageClassInput; auto &ptr_type = set(type_ptr_id, uint_type_ptr); - ptr_type.self = type_id; + ptr_type.self = get_uint_type_id(); - if (!has_invocation_id) + if (msl_options.multi_patch_workgroup || need_grid_params) + { + uint32_t var_id = ir.increase_bound_by(1); + + // Create gl_GlobalInvocationID. + set(var_id, type_ptr_id, StorageClassInput); + set_decoration(var_id, DecorationBuiltIn, BuiltInGlobalInvocationId); + builtin_invocation_id_id = var_id; + mark_implicit_builtin(StorageClassInput, BuiltInGlobalInvocationId, var_id); + } + else if (need_tesc_params && !has_invocation_id) { uint32_t var_id = ir.increase_bound_by(1); @@ -460,7 +605,7 @@ void CompilerMSL::build_implicit_builtins() mark_implicit_builtin(StorageClassInput, BuiltInInvocationId, var_id); } - if (!has_primitive_id) + if (need_tesc_params && !has_primitive_id) { uint32_t var_id = ir.increase_bound_by(1); @@ -470,28 +615,34 @@ void CompilerMSL::build_implicit_builtins() builtin_primitive_id_id = var_id; mark_implicit_builtin(StorageClassInput, BuiltInPrimitiveId, var_id); } + + if (need_grid_params) + { + uint32_t var_id = ir.increase_bound_by(1); + + set(var_id, build_extended_vector_type(get_uint_type_id(), 3), StorageClassInput); + set_extended_decoration(var_id, SPIRVCrossDecorationBuiltInStageInputSize); + get_entry_point().interface_variables.push_back(var_id); + set_name(var_id, "spvStageInputSize"); + builtin_stage_input_size_id = var_id; + } } if (!has_subgroup_invocation_id && (need_subgroup_mask || needs_subgroup_invocation_id)) { - uint32_t offset = ir.increase_bound_by(3); - uint32_t type_id = offset; - uint32_t type_ptr_id = offset + 1; - uint32_t var_id = offset + 2; + uint32_t offset = ir.increase_bound_by(2); + uint32_t type_ptr_id = offset; + uint32_t var_id = offset + 1; // Create gl_SubgroupInvocationID. - SPIRType uint_type; - uint_type.basetype = SPIRType::UInt; - uint_type.width = 32; - set(type_id, uint_type); - SPIRType uint_type_ptr; - uint_type_ptr = uint_type; + uint_type_ptr = get_uint_type(); uint_type_ptr.pointer = true; - uint_type_ptr.parent_type = type_id; + uint_type_ptr.pointer_depth++; + uint_type_ptr.parent_type = get_uint_type_id(); uint_type_ptr.storage = StorageClassInput; auto &ptr_type = set(type_ptr_id, uint_type_ptr); - ptr_type.self = type_id; + ptr_type.self = get_uint_type_id(); set(var_id, type_ptr_id, StorageClassInput); set_decoration(var_id, DecorationBuiltIn, BuiltInSubgroupLocalInvocationId); @@ -499,26 +650,21 @@ void CompilerMSL::build_implicit_builtins() mark_implicit_builtin(StorageClassInput, BuiltInSubgroupLocalInvocationId, var_id); } - if (!has_subgroup_size && need_subgroup_ge_mask) + if (!has_subgroup_size && (need_subgroup_ge_mask || needs_subgroup_size)) { - uint32_t offset = ir.increase_bound_by(3); - uint32_t type_id = offset; - uint32_t type_ptr_id = offset + 1; - uint32_t var_id = offset + 2; + uint32_t offset = ir.increase_bound_by(2); + uint32_t type_ptr_id = offset; + uint32_t var_id = offset + 1; // Create gl_SubgroupSize. - SPIRType uint_type; - uint_type.basetype = SPIRType::UInt; - uint_type.width = 32; - set(type_id, uint_type); - SPIRType uint_type_ptr; - uint_type_ptr = uint_type; + uint_type_ptr = get_uint_type(); uint_type_ptr.pointer = true; - uint_type_ptr.parent_type = type_id; + uint_type_ptr.pointer_depth++; + uint_type_ptr.parent_type = get_uint_type_id(); uint_type_ptr.storage = StorageClassInput; auto &ptr_type = set(type_ptr_id, uint_type_ptr); - ptr_type.self = type_id; + ptr_type.self = get_uint_type_id(); set(var_id, type_ptr_id, StorageClassInput); set_decoration(var_id, DecorationBuiltIn, BuiltInSubgroupSize); @@ -526,8 +672,10 @@ void CompilerMSL::build_implicit_builtins() mark_implicit_builtin(StorageClassInput, BuiltInSubgroupSize, var_id); } - if (need_dispatch_base) + if (need_dispatch_base || need_vertex_base_params) { + if (workgroup_id_type == 0) + workgroup_id_type = build_extended_vector_type(get_uint_type_id(), 3); uint32_t var_id; if (msl_options.supports_msl_version(1, 2)) { @@ -561,6 +709,71 @@ void CompilerMSL::build_implicit_builtins() set_name(var_id, "spvDispatchBase"); builtin_dispatch_base_id = var_id; } + + if (need_sample_mask && !does_shader_write_sample_mask) + { + uint32_t offset = ir.increase_bound_by(2); + uint32_t var_id = offset + 1; + + // Create gl_SampleMask. + SPIRType uint_type_ptr_out; + uint_type_ptr_out = get_uint_type(); + uint_type_ptr_out.pointer = true; + uint_type_ptr_out.pointer_depth++; + uint_type_ptr_out.parent_type = get_uint_type_id(); + uint_type_ptr_out.storage = StorageClassOutput; + + auto &ptr_out_type = set(offset, uint_type_ptr_out); + ptr_out_type.self = get_uint_type_id(); + set(var_id, offset, StorageClassOutput); + set_decoration(var_id, DecorationBuiltIn, BuiltInSampleMask); + builtin_sample_mask_id = var_id; + mark_implicit_builtin(StorageClassOutput, BuiltInSampleMask, var_id); + } + + if (need_local_invocation_index && !has_local_invocation_index) + { + uint32_t offset = ir.increase_bound_by(2); + uint32_t type_ptr_id = offset; + uint32_t var_id = offset + 1; + + // Create gl_LocalInvocationIndex. + SPIRType uint_type_ptr; + uint_type_ptr = get_uint_type(); + uint_type_ptr.pointer = true; + uint_type_ptr.pointer_depth++; + uint_type_ptr.parent_type = get_uint_type_id(); + uint_type_ptr.storage = StorageClassInput; + + auto &ptr_type = set(type_ptr_id, uint_type_ptr); + ptr_type.self = get_uint_type_id(); + set(var_id, type_ptr_id, StorageClassInput); + set_decoration(var_id, DecorationBuiltIn, BuiltInLocalInvocationIndex); + builtin_local_invocation_index_id = var_id; + mark_implicit_builtin(StorageClassInput, BuiltInLocalInvocationIndex, var_id); + } + + if (need_workgroup_size && !has_workgroup_size) + { + uint32_t offset = ir.increase_bound_by(2); + uint32_t type_ptr_id = offset; + uint32_t var_id = offset + 1; + + // Create gl_WorkgroupSize. + uint32_t type_id = build_extended_vector_type(get_uint_type_id(), 3); + SPIRType uint_type_ptr = get(type_id); + uint_type_ptr.pointer = true; + uint_type_ptr.pointer_depth++; + uint_type_ptr.parent_type = type_id; + uint_type_ptr.storage = StorageClassInput; + + auto &ptr_type = set(type_ptr_id, uint_type_ptr); + ptr_type.self = type_id; + set(var_id, type_ptr_id, StorageClassInput); + set_decoration(var_id, DecorationBuiltIn, BuiltInWorkgroupSize); + builtin_workgroup_size_id = var_id; + mark_implicit_builtin(StorageClassInput, BuiltInWorkgroupSize, var_id); + } } if (needs_swizzle_buffer_def) @@ -607,6 +820,52 @@ void CompilerMSL::build_implicit_builtins() msl_options.dynamic_offsets_buffer_index); dynamic_offsets_buffer_id = var_id; } + + // If we're returning a struct from a vertex-like entry point, we must return a position attribute. + bool need_position = + (get_execution_model() == ExecutionModelVertex || + get_execution_model() == ExecutionModelTessellationEvaluation) && + !capture_output_to_buffer && !get_is_rasterization_disabled() && + !active_output_builtins.get(BuiltInPosition); + + if (need_position) + { + // If we can get away with returning void from entry point, we don't need to care. + // If there is at least one other stage output, we need to return [[position]]. + need_position = false; + ir.for_each_typed_id([&](uint32_t, SPIRVariable &var) { + if (var.storage == StorageClassOutput && interface_variable_exists_in_entry_point(var.self)) + need_position = true; + }); + } + + if (need_position) + { + uint32_t offset = ir.increase_bound_by(3); + uint32_t type_id = offset; + uint32_t type_ptr_id = offset + 1; + uint32_t var_id = offset + 2; + + // Create gl_Position. + SPIRType vec4_type; + vec4_type.basetype = SPIRType::Float; + vec4_type.width = 32; + vec4_type.vecsize = 4; + set(type_id, vec4_type); + + SPIRType vec4_type_ptr; + vec4_type_ptr = vec4_type; + vec4_type_ptr.pointer = true; + vec4_type_ptr.pointer_depth++; + vec4_type_ptr.parent_type = type_id; + vec4_type_ptr.storage = StorageClassOutput; + auto &ptr_type = set(type_ptr_id, vec4_type_ptr); + ptr_type.self = type_id; + + set(var_id, type_ptr_id, StorageClassOutput); + set_decoration(var_id, DecorationBuiltIn, BuiltInPosition); + mark_implicit_builtin(StorageClassOutput, BuiltInPosition, var_id); + } } // Checks if the specified builtin variable (e.g. gl_InstanceIndex) is marked as active. @@ -665,22 +924,16 @@ void CompilerMSL::mark_implicit_builtin(StorageClass storage, BuiltIn builtin, u uint32_t CompilerMSL::build_constant_uint_array_pointer() { - uint32_t offset = ir.increase_bound_by(4); - uint32_t type_id = offset; - uint32_t type_ptr_id = offset + 1; - uint32_t type_ptr_ptr_id = offset + 2; - uint32_t var_id = offset + 3; + uint32_t offset = ir.increase_bound_by(3); + uint32_t type_ptr_id = offset; + uint32_t type_ptr_ptr_id = offset + 1; + uint32_t var_id = offset + 2; // Create a buffer to hold extra data, including the swizzle constants. - SPIRType uint_type; - uint_type.basetype = SPIRType::UInt; - uint_type.width = 32; - set(type_id, uint_type); - - SPIRType uint_type_pointer = uint_type; + SPIRType uint_type_pointer = get_uint_type(); uint_type_pointer.pointer = true; - uint_type_pointer.pointer_depth = 1; - uint_type_pointer.parent_type = type_id; + uint_type_pointer.pointer_depth++; + uint_type_pointer.parent_type = get_uint_type_id(); uint_type_pointer.storage = StorageClassUniform; set(type_ptr_id, uint_type_pointer); set_decoration(type_ptr_id, DecorationArrayStride, 4); @@ -744,6 +997,25 @@ std::string CompilerMSL::get_tess_factor_struct_name() return "MTLQuadTessellationFactorsHalf"; } +SPIRType &CompilerMSL::get_uint_type() +{ + return get(get_uint_type_id()); +} + +uint32_t CompilerMSL::get_uint_type_id() +{ + if (uint_type_id != 0) + return uint_type_id; + + uint_type_id = ir.increase_bound_by(1); + + SPIRType type; + type.basetype = SPIRType::UInt; + type.width = 32; + set(uint_type_id, type); + return uint_type_id; +} + void CompilerMSL::emit_entry_point_declarations() { // FIXME: Get test coverage here ... @@ -979,6 +1251,9 @@ void CompilerMSL::emit_entry_point_declarations() string CompilerMSL::compile() { + replace_illegal_entry_point_names(); + ir.fixup_reserved_names(); + // Do not deal with GLES-isms like precision, older extensions and such. options.vulkan_semantics = true; options.es = false; @@ -995,7 +1270,7 @@ string CompilerMSL::compile() backend.basic_int16_type = "short"; backend.basic_uint16_type = "ushort"; backend.discard_literal = "discard_fragment()"; - backend.demote_literal = "unsupported-demote"; + backend.demote_literal = "discard_fragment()"; backend.boolean_mix_function = "select"; backend.swizzle_is_function = false; backend.shared_is_implied = false; @@ -1014,6 +1289,9 @@ string CompilerMSL::compile() // Allow Metal to use the array template unless we force it off. backend.can_return_array = !msl_options.force_native_arrays; backend.array_is_value_type = !msl_options.force_native_arrays; + // Arrays which are part of buffer objects are never considered to be native arrays. + backend.buffer_offset_array_is_value_type = false; + backend.support_pointer_to_pointer = true; capture_output_to_buffer = msl_options.capture_output_to_buffer; is_rasterization_disabled = msl_options.disable_rasterization || capture_output_to_buffer; @@ -1024,6 +1302,7 @@ string CompilerMSL::compile() fixup_type_alias(); replace_illegal_names(); + sync_entry_point_aliases_and_names(); build_function_control_flow_graphs_and_analyze(); update_active_builtins(); @@ -1051,6 +1330,8 @@ string CompilerMSL::compile() active_interface_variables.insert(builtin_layer_id); if (builtin_dispatch_base_id && !msl_options.supports_msl_version(1, 2)) active_interface_variables.insert(builtin_dispatch_base_id); + if (builtin_sample_mask_id) + active_interface_variables.insert(builtin_sample_mask_id); // Create structs to hold input, output and uniform variables. // Do output first to ensure out. is declared at top of entry function. @@ -1136,13 +1417,15 @@ void CompilerMSL::preprocess_op_codes() add_pragma_line("#pragma clang diagnostic ignored \"-Wunused-variable\""); } - // Metal vertex functions that write to resources must disable rasterization and return void. + // Before MSL 2.1 (2.2 for textures), Metal vertex functions that write to + // resources must disable rasterization and return void. if (preproc.uses_resource_write) is_rasterization_disabled = true; // Tessellation control shaders are run as compute functions in Metal, and so // must capture their output to a buffer. - if (get_execution_model() == ExecutionModelTessellationControl) + if (get_execution_model() == ExecutionModelTessellationControl || + (get_execution_model() == ExecutionModelVertex && msl_options.vertex_for_tessellation)) { is_rasterization_disabled = true; capture_output_to_buffer = true; @@ -1150,6 +1433,15 @@ void CompilerMSL::preprocess_op_codes() if (preproc.needs_subgroup_invocation_id) needs_subgroup_invocation_id = true; + if (preproc.needs_subgroup_size) + needs_subgroup_size = true; + // build_implicit_builtins() hasn't run yet, and in fact, this needs to execute + // before then so that gl_SampleID will get added; so we also need to check if + // that function would add gl_FragCoord. + if (preproc.needs_sample_id || msl_options.force_sample_rate_shading || + (is_sample_rate() && (active_input_builtins.get(BuiltInFragCoord) || + (need_subpass_input && !msl_options.use_framebuffer_fetch_subpasses)))) + needs_sample_id = true; } // Move the Private and Workgroup global variables to the entry function. @@ -1242,11 +1534,23 @@ void CompilerMSL::extract_global_variables_from_function(uint32_t func_id, std:: // Use Metal's native frame-buffer fetch API for subpass inputs. auto &type = get(ops[0]); if (type.basetype == SPIRType::Image && type.image.dim == DimSubpassData && - (!msl_options.is_ios() || !msl_options.ios_use_framebuffer_fetch_subpasses)) + (!msl_options.use_framebuffer_fetch_subpasses)) { // Implicitly reads gl_FragCoord. assert(builtin_frag_coord_id != 0); added_arg_ids.insert(builtin_frag_coord_id); + if (msl_options.multiview) + { + // Implicitly reads gl_ViewIndex. + assert(builtin_view_idx_id != 0); + added_arg_ids.insert(builtin_view_idx_id); + } + else if (msl_options.arrayed_subpass_input) + { + // Implicitly reads gl_Layer. + assert(builtin_layer_id != 0); + added_arg_ids.insert(builtin_layer_id); + } } break; @@ -1276,6 +1580,11 @@ void CompilerMSL::extract_global_variables_from_function(uint32_t func_id, std:: uint32_t base_id = ops[0]; if (global_var_ids.find(base_id) != global_var_ids.end()) added_arg_ids.insert(base_id); + + uint32_t rvalue_id = ops[1]; + if (global_var_ids.find(rvalue_id) != global_var_ids.end()) + added_arg_ids.insert(rvalue_id); + break; } @@ -1304,6 +1613,63 @@ void CompilerMSL::extract_global_variables_from_function(uint32_t func_id, std:: break; } + case OpExtInst: + { + uint32_t extension_set = ops[2]; + if (get(extension_set).ext == SPIRExtension::GLSL) + { + auto op_450 = static_cast(ops[3]); + switch (op_450) + { + case GLSLstd450InterpolateAtCentroid: + case GLSLstd450InterpolateAtSample: + case GLSLstd450InterpolateAtOffset: + { + // For these, we really need the stage-in block. It is theoretically possible to pass the + // interpolant object, but a) doing so would require us to create an entirely new variable + // with Interpolant type, and b) if we have a struct or array, handling all the members and + // elements could get unwieldy fast. + added_arg_ids.insert(stage_in_var_id); + break; + } + default: + break; + } + } + break; + } + + case OpGroupNonUniformInverseBallot: + { + added_arg_ids.insert(builtin_subgroup_invocation_id_id); + break; + } + + case OpGroupNonUniformBallotFindLSB: + case OpGroupNonUniformBallotFindMSB: + { + added_arg_ids.insert(builtin_subgroup_size_id); + break; + } + + case OpGroupNonUniformBallotBitCount: + { + auto operation = static_cast(ops[3]); + switch (operation) + { + case GroupOperationReduce: + added_arg_ids.insert(builtin_subgroup_size_id); + break; + case GroupOperationInclusiveScan: + case GroupOperationExclusiveScan: + added_arg_ids.insert(builtin_subgroup_invocation_id_id); + break; + default: + break; + } + break; + } + default: break; } @@ -1344,7 +1710,7 @@ void CompilerMSL::extract_global_variables_from_function(uint32_t func_id, std:: { if (added_in) continue; - name = input_wg_var_name; + name = "gl_in"; arg_id = stage_in_ptr_var_id; added_in = true; } @@ -1387,6 +1753,7 @@ void CompilerMSL::extract_global_variables_from_function(uint32_t func_id, std:: ptr.self = mbr_type_id; ptr.storage = var.storage; ptr.pointer = true; + ptr.pointer_depth++; ptr.parent_type = mbr_type_id; func.add_parameter(mbr_type_id, var_id, true); @@ -1458,11 +1825,28 @@ void CompilerMSL::mark_as_packable(SPIRType &type) } } -// If a vertex attribute exists at the location, it is marked as being used by this shader -void CompilerMSL::mark_location_as_used_by_shader(uint32_t location, StorageClass storage) +uint32_t CompilerMSL::type_to_location_count(const SPIRType &type) const { - if ((get_execution_model() == ExecutionModelVertex || is_tessellation_shader()) && (storage == StorageClassInput)) - vtx_attrs_in_use.insert(location); + // In MSL, we cannot place structs in any context where we need locations. + assert(type.basetype != SPIRType::Struct); + + uint32_t dim = 1; + for (uint32_t i = 0; i < type.array.size(); i++) + dim *= to_array_size_literal(type, i); + + uint32_t count = dim * type.columns; + return count; +} + +// If a shader input exists at the location, it is marked as being used by this shader +void CompilerMSL::mark_location_as_used_by_shader(uint32_t location, const SPIRType &type, StorageClass storage) +{ + if (storage != StorageClassInput) + return; + + uint32_t count = type_to_location_count(type); + for (uint32_t i = 0; i < count; i++) + location_inputs_in_use.insert(location + i); } uint32_t CompilerMSL::get_target_components_for_fragment_location(uint32_t location) const @@ -1474,15 +1858,55 @@ uint32_t CompilerMSL::get_target_components_for_fragment_location(uint32_t locat return itr->second; } -uint32_t CompilerMSL::build_extended_vector_type(uint32_t type_id, uint32_t components) +uint32_t CompilerMSL::build_extended_vector_type(uint32_t type_id, uint32_t components, SPIRType::BaseType basetype) { uint32_t new_type_id = ir.increase_bound_by(1); - auto &type = set(new_type_id, get(type_id)); - type.vecsize = components; - type.self = new_type_id; - type.parent_type = type_id; - type.pointer = false; + auto &old_type = get(type_id); + auto *type = &set(new_type_id, old_type); + type->vecsize = components; + if (basetype != SPIRType::Unknown) + type->basetype = basetype; + type->self = new_type_id; + type->parent_type = type_id; + type->array.clear(); + type->array_size_literal.clear(); + type->pointer = false; + if (is_array(old_type)) + { + uint32_t array_type_id = ir.increase_bound_by(1); + type = &set(array_type_id, *type); + type->parent_type = new_type_id; + type->array = old_type.array; + type->array_size_literal = old_type.array_size_literal; + new_type_id = array_type_id; + } + + if (old_type.pointer) + { + uint32_t ptr_type_id = ir.increase_bound_by(1); + type = &set(ptr_type_id, *type); + type->self = new_type_id; + type->parent_type = new_type_id; + type->storage = old_type.storage; + type->pointer = true; + type->pointer_depth++; + new_type_id = ptr_type_id; + } + + return new_type_id; +} + +uint32_t CompilerMSL::build_msl_interpolant_type(uint32_t type_id, bool is_noperspective) +{ + uint32_t new_type_id = ir.increase_bound_by(1); + SPIRType &type = set(new_type_id, get(type_id)); + type.basetype = SPIRType::Interpolant; + type.parent_type = type_id; + // In Metal, the pull-model interpolant type encodes perspective-vs-no-perspective in the type itself. + // Add this decoration so we know which argument to pass to the template. + if (is_noperspective) + set_decoration(new_type_id, DecorationNoPerspective); return new_type_id; } @@ -1584,7 +2008,10 @@ void CompilerMSL::add_plain_variable_to_interface_block(StorageClass storage, co } } - ib_type.member_types.push_back(type_id); + if (storage == StorageClassInput && pull_model_inputs.count(var.self)) + ib_type.member_types.push_back(build_msl_interpolant_type(type_id, is_noperspective)); + else + ib_type.member_types.push_back(type_id); // Give the member a name string mbr_name = ensure_valid_name(to_expression(var.self), "m"); @@ -1592,6 +2019,16 @@ void CompilerMSL::add_plain_variable_to_interface_block(StorageClass storage, co // Update the original variable reference to include the structure reference string qual_var_name = ib_var_ref + "." + mbr_name; + // If using pull-model interpolation, need to add a call to the correct interpolation method. + if (storage == StorageClassInput && pull_model_inputs.count(var.self)) + { + if (is_centroid) + qual_var_name += ".interpolate_at_centroid()"; + else if (is_sample) + qual_var_name += join(".interpolate_at_sample(", to_expression(builtin_sample_id_id), ")"); + else + qual_var_name += ".interpolate_at_center()"; + } if (padded_output || padded_input) { @@ -1625,8 +2062,22 @@ void CompilerMSL::add_plain_variable_to_interface_block(StorageClass storage, co } else { - entry_func.fixup_hooks_in.push_back( - [=, &var]() { statement(qual_var_name, " = ", to_expression(var.initializer), ";"); }); + if (meta.strip_array) + { + entry_func.fixup_hooks_in.push_back([=, &var]() { + uint32_t index = get_extended_decoration(var.self, SPIRVCrossDecorationInterfaceMemberIndex); + statement(to_expression(stage_out_ptr_var_id), "[", + builtin_to_glsl(BuiltInInvocationId, StorageClassInput), "].", + to_member_name(ib_type, index), " = ", to_expression(var.initializer), "[", + builtin_to_glsl(BuiltInInvocationId, StorageClassInput), "];"); + }); + } + else + { + entry_func.fixup_hooks_in.push_back([=, &var]() { + statement(qual_var_name, " = ", to_expression(var.initializer), ";"); + }); + } } } @@ -1634,27 +2085,28 @@ void CompilerMSL::add_plain_variable_to_interface_block(StorageClass storage, co if (get_decoration_bitset(var.self).get(DecorationLocation)) { uint32_t locn = get_decoration(var.self, DecorationLocation); - if (storage == StorageClassInput && (get_execution_model() == ExecutionModelVertex || is_tessellation_shader())) + if (storage == StorageClassInput) { - type_id = ensure_correct_attribute_type(var.basetype, locn, - location_meta ? location_meta->num_components : type.vecsize); - + type_id = ensure_correct_input_type(var.basetype, locn, location_meta ? location_meta->num_components : 0); if (!location_meta) var.basetype = type_id; type_id = get_pointee_type_id(type_id); if (meta.strip_array && is_array(get(type_id))) type_id = get(type_id).parent_type; - ib_type.member_types[ib_mbr_idx] = type_id; + if (pull_model_inputs.count(var.self)) + ib_type.member_types[ib_mbr_idx] = build_msl_interpolant_type(type_id, is_noperspective); + else + ib_type.member_types[ib_mbr_idx] = type_id; } set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); - mark_location_as_used_by_shader(locn, storage); + mark_location_as_used_by_shader(locn, get(type_id), storage); } - else if (is_builtin && is_tessellation_shader() && vtx_attrs_by_builtin.count(builtin)) + else if (is_builtin && is_tessellation_shader() && inputs_by_builtin.count(builtin)) { - uint32_t locn = vtx_attrs_by_builtin[builtin].location; + uint32_t locn = inputs_by_builtin[builtin].location; set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); - mark_location_as_used_by_shader(locn, storage); + mark_location_as_used_by_shader(locn, type, storage); } if (!location_meta) @@ -1681,14 +2133,17 @@ void CompilerMSL::add_plain_variable_to_interface_block(StorageClass storage, co } // Copy interpolation decorations if needed - if (is_flat) - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationFlat); - if (is_noperspective) - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationNoPerspective); - if (is_centroid) - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationCentroid); - if (is_sample) - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationSample); + if (storage != StorageClassInput || !pull_model_inputs.count(var.self)) + { + if (is_flat) + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationFlat); + if (is_noperspective) + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationNoPerspective); + if (is_centroid) + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationCentroid); + if (is_sample) + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationSample); + } // If we have location meta, there is no unique OrigID. We won't need it, since we flatten/unflatten // the variable to stack anyways here. @@ -1787,7 +2242,10 @@ void CompilerMSL::add_composite_variable_to_interface_block(StorageClass storage } } - ib_type.member_types.push_back(get_pointee_type_id(type_id)); + if (storage == StorageClassInput && pull_model_inputs.count(var.self)) + ib_type.member_types.push_back(build_msl_interpolant_type(get_pointee_type_id(type_id), is_noperspective)); + else + ib_type.member_types.push_back(get_pointee_type_id(type_id)); // Give the member a name string mbr_name = ensure_valid_name(join(to_expression(var.self), "_", i), "m"); @@ -1797,21 +2255,23 @@ void CompilerMSL::add_composite_variable_to_interface_block(StorageClass storage if (get_decoration_bitset(var.self).get(DecorationLocation)) { uint32_t locn = get_decoration(var.self, DecorationLocation) + i; - if (storage == StorageClassInput && - (get_execution_model() == ExecutionModelVertex || is_tessellation_shader())) + if (storage == StorageClassInput) { - var.basetype = ensure_correct_attribute_type(var.basetype, locn); - uint32_t mbr_type_id = ensure_correct_attribute_type(usable_type->self, locn); - ib_type.member_types[ib_mbr_idx] = mbr_type_id; + var.basetype = ensure_correct_input_type(var.basetype, locn); + uint32_t mbr_type_id = ensure_correct_input_type(usable_type->self, locn); + if (storage == StorageClassInput && pull_model_inputs.count(var.self)) + ib_type.member_types[ib_mbr_idx] = build_msl_interpolant_type(mbr_type_id, is_noperspective); + else + ib_type.member_types[ib_mbr_idx] = mbr_type_id; } set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); - mark_location_as_used_by_shader(locn, storage); + mark_location_as_used_by_shader(locn, *usable_type, storage); } - else if (is_builtin && is_tessellation_shader() && vtx_attrs_by_builtin.count(builtin)) + else if (is_builtin && is_tessellation_shader() && inputs_by_builtin.count(builtin)) { - uint32_t locn = vtx_attrs_by_builtin[builtin].location + i; + uint32_t locn = inputs_by_builtin[builtin].location + i; set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); - mark_location_as_used_by_shader(locn, storage); + mark_location_as_used_by_shader(locn, *usable_type, storage); } else if (is_builtin && builtin == BuiltInClipDistance) { @@ -1826,15 +2286,18 @@ void CompilerMSL::add_composite_variable_to_interface_block(StorageClass storage set_member_decoration(ib_type.self, ib_mbr_idx, DecorationIndex, index); } - // Copy interpolation decorations if needed - if (is_flat) - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationFlat); - if (is_noperspective) - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationNoPerspective); - if (is_centroid) - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationCentroid); - if (is_sample) - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationSample); + if (storage != StorageClassInput || !pull_model_inputs.count(var.self)) + { + // Copy interpolation decorations if needed + if (is_flat) + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationFlat); + if (is_noperspective) + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationNoPerspective); + if (is_centroid) + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationCentroid); + if (is_sample) + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationSample); + } set_extended_member_decoration(ib_type.self, ib_mbr_idx, SPIRVCrossDecorationInterfaceOrigID, var.self); @@ -1844,8 +2307,23 @@ void CompilerMSL::add_composite_variable_to_interface_block(StorageClass storage switch (storage) { case StorageClassInput: - entry_func.fixup_hooks_in.push_back( - [=, &var]() { statement(to_name(var.self), "[", i, "] = ", ib_var_ref, ".", mbr_name, ";"); }); + entry_func.fixup_hooks_in.push_back([=, &var]() { + if (pull_model_inputs.count(var.self)) + { + string lerp_call; + if (is_centroid) + lerp_call = ".interpolate_at_centroid()"; + else if (is_sample) + lerp_call = join(".interpolate_at_sample(", to_expression(builtin_sample_id_id), ")"); + else + lerp_call = ".interpolate_at_center()"; + statement(to_name(var.self), "[", i, "] = ", ib_var_ref, ".", mbr_name, lerp_call, ";"); + } + else + { + statement(to_name(var.self), "[", i, "] = ", ib_var_ref, ".", mbr_name, ";"); + } + }); break; case StorageClassOutput: @@ -1969,7 +2447,10 @@ void CompilerMSL::add_composite_member_variable_to_interface_block(StorageClass { // Add a reference to the variable type to the interface struct. uint32_t ib_mbr_idx = uint32_t(ib_type.member_types.size()); - ib_type.member_types.push_back(usable_type->self); + if (storage == StorageClassInput && pull_model_inputs.count(var.self)) + ib_type.member_types.push_back(build_msl_interpolant_type(usable_type->self, is_noperspective)); + else + ib_type.member_types.push_back(usable_type->self); // Give the member a name string mbr_name = ensure_valid_name(join(to_qualified_member_name(var_type, mbr_idx), "_", i), "m"); @@ -1979,19 +2460,19 @@ void CompilerMSL::add_composite_member_variable_to_interface_block(StorageClass { uint32_t locn = get_member_decoration(var_type.self, mbr_idx, DecorationLocation) + i; set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); - mark_location_as_used_by_shader(locn, storage); + mark_location_as_used_by_shader(locn, *usable_type, storage); } else if (has_decoration(var.self, DecorationLocation)) { uint32_t locn = get_accumulated_member_location(var, mbr_idx, meta.strip_array) + i; set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); - mark_location_as_used_by_shader(locn, storage); + mark_location_as_used_by_shader(locn, *usable_type, storage); } - else if (is_builtin && is_tessellation_shader() && vtx_attrs_by_builtin.count(builtin)) + else if (is_builtin && is_tessellation_shader() && inputs_by_builtin.count(builtin)) { - uint32_t locn = vtx_attrs_by_builtin[builtin].location + i; + uint32_t locn = inputs_by_builtin[builtin].location + i; set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); - mark_location_as_used_by_shader(locn, storage); + mark_location_as_used_by_shader(locn, *usable_type, storage); } else if (is_builtin && builtin == BuiltInClipDistance) { @@ -2003,15 +2484,18 @@ void CompilerMSL::add_composite_member_variable_to_interface_block(StorageClass if (has_member_decoration(var_type.self, mbr_idx, DecorationComponent)) SPIRV_CROSS_THROW("DecorationComponent on matrices and arrays make little sense."); - // Copy interpolation decorations if needed - if (is_flat) - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationFlat); - if (is_noperspective) - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationNoPerspective); - if (is_centroid) - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationCentroid); - if (is_sample) - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationSample); + if (storage != StorageClassInput || !pull_model_inputs.count(var.self)) + { + // Copy interpolation decorations if needed + if (is_flat) + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationFlat); + if (is_noperspective) + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationNoPerspective); + if (is_centroid) + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationCentroid); + if (is_sample) + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationSample); + } set_extended_member_decoration(ib_type.self, ib_mbr_idx, SPIRVCrossDecorationInterfaceOrigID, var.self); set_extended_member_decoration(ib_type.self, ib_mbr_idx, SPIRVCrossDecorationInterfaceMemberIndex, mbr_idx); @@ -2023,8 +2507,23 @@ void CompilerMSL::add_composite_member_variable_to_interface_block(StorageClass { case StorageClassInput: entry_func.fixup_hooks_in.push_back([=, &var, &var_type]() { - statement(to_name(var.self), ".", to_member_name(var_type, mbr_idx), "[", i, "] = ", ib_var_ref, - ".", mbr_name, ";"); + if (pull_model_inputs.count(var.self)) + { + string lerp_call; + if (is_centroid) + lerp_call = ".interpolate_at_centroid()"; + else if (is_sample) + lerp_call = join(".interpolate_at_sample(", to_expression(builtin_sample_id_id), ")"); + else + lerp_call = ".interpolate_at_center()"; + statement(to_name(var.self), ".", to_member_name(var_type, mbr_idx), "[", i, "] = ", ib_var_ref, + ".", mbr_name, lerp_call, ";"); + } + else + { + statement(to_name(var.self), ".", to_member_name(var_type, mbr_idx), "[", i, "] = ", ib_var_ref, + ".", mbr_name, ";"); + } }); break; @@ -2073,7 +2572,10 @@ void CompilerMSL::add_plain_member_variable_to_interface_block(StorageClass stor uint32_t ib_mbr_idx = uint32_t(ib_type.member_types.size()); mbr_type_id = ensure_correct_builtin_type(mbr_type_id, builtin); var_type.member_types[mbr_idx] = mbr_type_id; - ib_type.member_types.push_back(mbr_type_id); + if (storage == StorageClassInput && pull_model_inputs.count(var.self)) + ib_type.member_types.push_back(build_msl_interpolant_type(mbr_type_id, is_noperspective)); + else + ib_type.member_types.push_back(mbr_type_id); // Give the member a name string mbr_name = ensure_valid_name(to_qualified_member_name(var_type, mbr_idx), "m"); @@ -2081,6 +2583,18 @@ void CompilerMSL::add_plain_member_variable_to_interface_block(StorageClass stor // Update the original variable reference to include the structure reference string qual_var_name = ib_var_ref + "." + mbr_name; + // If using pull-model interpolation, need to add a call to the correct interpolation method. + if (storage == StorageClassInput && pull_model_inputs.count(var.self)) + { + if (is_centroid) + qual_var_name += ".interpolate_at_centroid()"; + else if (is_sample) + qual_var_name += join(".interpolate_at_sample(", to_expression(builtin_sample_id_id), ")"); + else + qual_var_name += ".interpolate_at_center()"; + } + + bool flatten_stage_out = false; if (is_builtin && !meta.strip_array) { @@ -2100,6 +2614,7 @@ void CompilerMSL::add_plain_member_variable_to_interface_block(StorageClass stor break; case StorageClassOutput: + flatten_stage_out = true; entry_func.fixup_hooks_out.push_back([=, &var, &var_type]() { statement(qual_var_name, " = ", to_name(var.self), ".", to_member_name(var_type, mbr_idx), ";"); }); @@ -2114,37 +2629,43 @@ void CompilerMSL::add_plain_member_variable_to_interface_block(StorageClass stor if (has_member_decoration(var_type.self, mbr_idx, DecorationLocation)) { uint32_t locn = get_member_decoration(var_type.self, mbr_idx, DecorationLocation); - if (storage == StorageClassInput && (get_execution_model() == ExecutionModelVertex || is_tessellation_shader())) + if (storage == StorageClassInput) { - mbr_type_id = ensure_correct_attribute_type(mbr_type_id, locn); + mbr_type_id = ensure_correct_input_type(mbr_type_id, locn); var_type.member_types[mbr_idx] = mbr_type_id; - ib_type.member_types[ib_mbr_idx] = mbr_type_id; + if (storage == StorageClassInput && pull_model_inputs.count(var.self)) + ib_type.member_types[ib_mbr_idx] = build_msl_interpolant_type(mbr_type_id, is_noperspective); + else + ib_type.member_types[ib_mbr_idx] = mbr_type_id; } set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); - mark_location_as_used_by_shader(locn, storage); + mark_location_as_used_by_shader(locn, get(mbr_type_id), storage); } else if (has_decoration(var.self, DecorationLocation)) { // The block itself might have a location and in this case, all members of the block // receive incrementing locations. uint32_t locn = get_accumulated_member_location(var, mbr_idx, meta.strip_array); - if (storage == StorageClassInput && (get_execution_model() == ExecutionModelVertex || is_tessellation_shader())) + if (storage == StorageClassInput) { - mbr_type_id = ensure_correct_attribute_type(mbr_type_id, locn); + mbr_type_id = ensure_correct_input_type(mbr_type_id, locn); var_type.member_types[mbr_idx] = mbr_type_id; - ib_type.member_types[ib_mbr_idx] = mbr_type_id; + if (storage == StorageClassInput && pull_model_inputs.count(var.self)) + ib_type.member_types[ib_mbr_idx] = build_msl_interpolant_type(mbr_type_id, is_noperspective); + else + ib_type.member_types[ib_mbr_idx] = mbr_type_id; } set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); - mark_location_as_used_by_shader(locn, storage); + mark_location_as_used_by_shader(locn, get(mbr_type_id), storage); } - else if (is_builtin && is_tessellation_shader() && vtx_attrs_by_builtin.count(builtin)) + else if (is_builtin && is_tessellation_shader() && inputs_by_builtin.count(builtin)) { uint32_t locn = 0; - auto builtin_itr = vtx_attrs_by_builtin.find(builtin); - if (builtin_itr != end(vtx_attrs_by_builtin)) + auto builtin_itr = inputs_by_builtin.find(builtin); + if (builtin_itr != end(inputs_by_builtin)) locn = builtin_itr->second.location; set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); - mark_location_as_used_by_shader(locn, storage); + mark_location_as_used_by_shader(locn, get(mbr_type_id), storage); } // Copy the component location, if present. @@ -2162,15 +2683,47 @@ void CompilerMSL::add_plain_member_variable_to_interface_block(StorageClass stor qual_pos_var_name = qual_var_name; } - // Copy interpolation decorations if needed - if (is_flat) - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationFlat); - if (is_noperspective) - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationNoPerspective); - if (is_centroid) - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationCentroid); - if (is_sample) - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationSample); + const SPIRConstant *c = nullptr; + if (!flatten_stage_out && var.storage == StorageClassOutput && + var.initializer != ID(0) && (c = maybe_get(var.initializer))) + { + if (meta.strip_array) + { + entry_func.fixup_hooks_in.push_back([=, &var]() { + auto &type = this->get(var.basetype); + uint32_t index = get_extended_decoration(var.self, SPIRVCrossDecorationInterfaceMemberIndex); + index += mbr_idx; + + AccessChainMeta chain_meta; + auto constant_chain = access_chain_internal(var.initializer, &builtin_invocation_id_id, 1, 0, &chain_meta); + + statement(to_expression(stage_out_ptr_var_id), "[", + builtin_to_glsl(BuiltInInvocationId, StorageClassInput), "].", + to_member_name(ib_type, index), " = ", + constant_chain, ".", to_member_name(type, mbr_idx), ";"); + }); + } + else + { + entry_func.fixup_hooks_in.push_back([=]() { + statement(qual_var_name, " = ", constant_expression( + this->get(c->subconstants[mbr_idx])), ";"); + }); + } + } + + if (storage != StorageClassInput || !pull_model_inputs.count(var.self)) + { + // Copy interpolation decorations if needed + if (is_flat) + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationFlat); + if (is_noperspective) + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationNoPerspective); + if (is_centroid) + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationCentroid); + if (is_sample) + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationSample); + } set_extended_member_decoration(ib_type.self, ib_mbr_idx, SPIRVCrossDecorationInterfaceOrigID, var.self); set_extended_member_decoration(ib_type.self, ib_mbr_idx, SPIRVCrossDecorationInterfaceMemberIndex, mbr_idx); @@ -2192,7 +2745,7 @@ void CompilerMSL::add_tess_level_input_to_interface_block(const std::string &ib_ // Force the variable to have the proper name. set_name(var.self, builtin_to_glsl(builtin, StorageClassFunction)); - if (get_entry_point().flags.get(ExecutionModeTriangles)) + if (get_execution_mode_bitset().get(ExecutionModeTriangles)) { // Triangles are tricky, because we want only one member in the struct. @@ -2215,18 +2768,22 @@ void CompilerMSL::add_tess_level_input_to_interface_block(const std::string &ib_ // Give the member a name set_member_name(ib_type.self, ib_mbr_idx, mbr_name); + // We cannot decorate both, but the important part is that + // it's marked as builtin so we can get automatic attribute assignment if needed. + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationBuiltIn, builtin); + // There is no qualified alias since we need to flatten the internal array on return. if (get_decoration_bitset(var.self).get(DecorationLocation)) { uint32_t locn = get_decoration(var.self, DecorationLocation); set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); - mark_location_as_used_by_shader(locn, StorageClassInput); + mark_location_as_used_by_shader(locn, var_type, StorageClassInput); } - else if (vtx_attrs_by_builtin.count(builtin)) + else if (inputs_by_builtin.count(builtin)) { - uint32_t locn = vtx_attrs_by_builtin[builtin].location; + uint32_t locn = inputs_by_builtin[builtin].location; set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); - mark_location_as_used_by_shader(locn, StorageClassInput); + mark_location_as_used_by_shader(locn, var_type, StorageClassInput); } added_builtin_tess_level = true; @@ -2262,6 +2819,7 @@ void CompilerMSL::add_tess_level_input_to_interface_block(const std::string &ib_ uint32_t ptr_type_id = ir.increase_bound_by(1); auto &new_var_type = set(ptr_type_id, get(type_id)); new_var_type.pointer = true; + new_var_type.pointer_depth++; new_var_type.storage = StorageClassInput; new_var_type.parent_type = type_id; var.basetype = ptr_type_id; @@ -2277,17 +2835,19 @@ void CompilerMSL::add_tess_level_input_to_interface_block(const std::string &ib_ string qual_var_name = ib_var_ref + "." + mbr_name; ir.meta[var.self].decoration.qualified_alias = qual_var_name; + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationBuiltIn, builtin); + if (get_decoration_bitset(var.self).get(DecorationLocation)) { uint32_t locn = get_decoration(var.self, DecorationLocation); set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); - mark_location_as_used_by_shader(locn, StorageClassInput); + mark_location_as_used_by_shader(locn, new_var_type, StorageClassInput); } - else if (vtx_attrs_by_builtin.count(builtin)) + else if (inputs_by_builtin.count(builtin)) { - uint32_t locn = vtx_attrs_by_builtin[builtin].location; + uint32_t locn = inputs_by_builtin[builtin].location; set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); - mark_location_as_used_by_shader(locn, StorageClassInput); + mark_location_as_used_by_shader(locn, new_var_type, StorageClassInput); } } } @@ -2341,7 +2901,10 @@ void CompilerMSL::add_variable_to_interface_block(StorageClass storage, const st bool is_composite_type = is_matrix(mbr_type) || is_array(mbr_type); bool attribute_load_store = storage == StorageClassInput && get_execution_model() != ExecutionModelFragment; - bool storage_is_stage_io = storage == StorageClassInput || storage == StorageClassOutput; + bool storage_is_stage_io = + (storage == StorageClassInput && !(get_execution_model() == ExecutionModelTessellationControl && + msl_options.multi_patch_workgroup)) || + storage == StorageClassOutput; // ClipDistance always needs to be declared as user attributes. if (builtin == BuiltInClipDistance) @@ -2366,13 +2929,15 @@ void CompilerMSL::add_variable_to_interface_block(StorageClass storage, const st add_tess_level_input_to_interface_block(ib_var_ref, ib_type, var); } else if (var_type.basetype == SPIRType::Boolean || var_type.basetype == SPIRType::Char || - type_is_integral(var_type) || type_is_floating_point(var_type) || var_type.basetype == SPIRType::Boolean) + type_is_integral(var_type) || type_is_floating_point(var_type)) { if (!is_builtin || has_active_builtin(builtin, storage)) { bool is_composite_type = is_matrix(var_type) || is_array(var_type); bool storage_is_stage_io = - storage == StorageClassInput || (storage == StorageClassOutput && !capture_output_to_buffer); + (storage == StorageClassInput && + !(get_execution_model() == ExecutionModelTessellationControl && msl_options.multi_patch_workgroup)) || + (storage == StorageClassOutput && !capture_output_to_buffer); bool attribute_load_store = storage == StorageClassInput && get_execution_model() != ExecutionModelFragment; // ClipDistance always needs to be declared as user attributes. @@ -2396,11 +2961,13 @@ void CompilerMSL::add_variable_to_interface_block(StorageClass storage, const st // for per-vertex variables in a tessellation control shader. void CompilerMSL::fix_up_interface_member_indices(StorageClass storage, uint32_t ib_type_id) { - // Only needed for tessellation shaders. + // Only needed for tessellation shaders and pull-model interpolants. // Need to redirect interface indices back to variables themselves. // For structs, each member of the struct need a separate instance. if (get_execution_model() != ExecutionModelTessellationControl && - !(get_execution_model() == ExecutionModelTessellationEvaluation && storage == StorageClassInput)) + !(get_execution_model() == ExecutionModelTessellationEvaluation && storage == StorageClassInput) && + !(get_execution_model() == ExecutionModelFragment && storage == StorageClassInput && + !pull_model_inputs.empty())) return; auto mbr_cnt = uint32_t(ir.meta[ib_type_id].members.size()); @@ -2488,8 +3055,8 @@ uint32_t CompilerMSL::add_interface_block(StorageClass storage, bool patch) // accept them. We can't put them in the struct at all, or otherwise the compiler // complains that the outputs weren't explicitly marked. if (get_execution_model() == ExecutionModelFragment && storage == StorageClassOutput && !patch && - ((is_builtin && ((bi_type == BuiltInFragDepth && !msl_options.enable_frag_depth_builtin) || - (bi_type == BuiltInFragStencilRefEXT && !msl_options.enable_frag_stencil_ref_builtin))) || + ((is_builtin && ((bi_type == BuiltInFragDepth && !msl_options.enable_frag_depth_builtin) || + (bi_type == BuiltInFragStencilRefEXT && !msl_options.enable_frag_stencil_ref_builtin))) || (!is_builtin && !(msl_options.enable_frag_output_mask & (1 << location))))) { hidden = true; @@ -2564,17 +3131,30 @@ uint32_t CompilerMSL::add_interface_block(StorageClass storage, bool patch) ib_var_ref = patch ? patch_stage_in_var_name : stage_in_var_name; if (get_execution_model() == ExecutionModelTessellationControl) { - // Add a hook to populate the shared workgroup memory containing - // the gl_in array. + // Add a hook to populate the shared workgroup memory containing the gl_in array. entry_func.fixup_hooks_in.push_back([=]() { - // Can't use PatchVertices yet; the hook for that may not have run yet. - statement("if (", to_expression(builtin_invocation_id_id), " < ", "spvIndirectParams[0])"); - statement(" ", input_wg_var_name, "[", to_expression(builtin_invocation_id_id), "] = ", ib_var_ref, - ";"); - statement("threadgroup_barrier(mem_flags::mem_threadgroup);"); - statement("if (", to_expression(builtin_invocation_id_id), " >= ", get_entry_point().output_vertices, - ")"); - statement(" return;"); + // Can't use PatchVertices, PrimitiveId, or InvocationId yet; the hooks for those may not have run yet. + if (msl_options.multi_patch_workgroup) + { + // n.b. builtin_invocation_id_id here is the dispatch global invocation ID, + // not the TC invocation ID. + statement("device ", to_name(ir.default_entry_point), "_", ib_var_ref, "* gl_in = &", + input_buffer_var_name, "[min(", to_expression(builtin_invocation_id_id), ".x / ", + get_entry_point().output_vertices, + ", spvIndirectParams[1] - 1) * spvIndirectParams[0]];"); + } + else + { + // It's safe to use InvocationId here because it's directly mapped to a + // Metal builtin, and therefore doesn't need a hook. + statement("if (", to_expression(builtin_invocation_id_id), " < spvIndirectParams[0])"); + statement(" ", input_wg_var_name, "[", to_expression(builtin_invocation_id_id), + "] = ", ib_var_ref, ";"); + statement("threadgroup_barrier(mem_flags::mem_threadgroup);"); + statement("if (", to_expression(builtin_invocation_id_id), + " >= ", get_entry_point().output_vertices, ")"); + statement(" return;"); + } }); } break; @@ -2616,7 +3196,14 @@ uint32_t CompilerMSL::add_interface_block(StorageClass storage, bool patch) // The first member of the indirect buffer is always the number of vertices // to draw. // We zero-base the InstanceID & VertexID variables for HLSL emulation elsewhere, so don't do it twice - if (msl_options.enable_base_index_zero) + if (get_execution_model() == ExecutionModelVertex && msl_options.vertex_for_tessellation) + { + statement("device ", to_name(ir.default_entry_point), "_", ib_var_ref, "& ", ib_var_ref, + " = ", output_buffer_var_name, "[", to_expression(builtin_invocation_id_id), + ".y * ", to_expression(builtin_stage_input_size_id), ".x + ", + to_expression(builtin_invocation_id_id), ".x];"); + } + else if (msl_options.enable_base_index_zero) { statement("device ", to_name(ir.default_entry_point), "_", ib_var_ref, "& ", ib_var_ref, " = ", output_buffer_var_name, "[", to_expression(builtin_instance_idx_id), @@ -2634,17 +3221,46 @@ uint32_t CompilerMSL::add_interface_block(StorageClass storage, bool patch) }); break; case ExecutionModelTessellationControl: - if (patch) - entry_func.fixup_hooks_in.push_back([=]() { - statement("device ", to_name(ir.default_entry_point), "_", ib_var_ref, "& ", ib_var_ref, " = ", - patch_output_buffer_var_name, "[", to_expression(builtin_primitive_id_id), "];"); - }); + if (msl_options.multi_patch_workgroup) + { + // We cannot use PrimitiveId here, because the hook may not have run yet. + if (patch) + { + entry_func.fixup_hooks_in.push_back([=]() { + statement("device ", to_name(ir.default_entry_point), "_", ib_var_ref, "& ", ib_var_ref, + " = ", patch_output_buffer_var_name, "[", to_expression(builtin_invocation_id_id), + ".x / ", get_entry_point().output_vertices, "];"); + }); + } + else + { + entry_func.fixup_hooks_in.push_back([=]() { + statement("device ", to_name(ir.default_entry_point), "_", ib_var_ref, "* gl_out = &", + output_buffer_var_name, "[", to_expression(builtin_invocation_id_id), ".x - ", + to_expression(builtin_invocation_id_id), ".x % ", + get_entry_point().output_vertices, "];"); + }); + } + } else - entry_func.fixup_hooks_in.push_back([=]() { - statement("device ", to_name(ir.default_entry_point), "_", ib_var_ref, "* gl_out = &", - output_buffer_var_name, "[", to_expression(builtin_primitive_id_id), " * ", - get_entry_point().output_vertices, "];"); - }); + { + if (patch) + { + entry_func.fixup_hooks_in.push_back([=]() { + statement("device ", to_name(ir.default_entry_point), "_", ib_var_ref, "& ", ib_var_ref, + " = ", patch_output_buffer_var_name, "[", to_expression(builtin_primitive_id_id), + "];"); + }); + } + else + { + entry_func.fixup_hooks_in.push_back([=]() { + statement("device ", to_name(ir.default_entry_point), "_", ib_var_ref, "* gl_out = &", + output_buffer_var_name, "[", to_expression(builtin_primitive_id_id), " * ", + get_entry_point().output_vertices, "];"); + }); + } + } break; default: break; @@ -2671,6 +3287,59 @@ uint32_t CompilerMSL::add_interface_block(StorageClass storage, bool patch) add_variable_to_interface_block(storage, ib_var_ref, ib_type, *p_var, meta); } + if (get_execution_model() == ExecutionModelTessellationControl && msl_options.multi_patch_workgroup && + storage == StorageClassInput) + { + // For tessellation control inputs, add all outputs from the vertex shader to ensure + // the struct containing them is the correct size and layout. + for (auto &input : inputs_by_location) + { + if (is_msl_shader_input_used(input.first)) + continue; + + // Create a fake variable to put at the location. + uint32_t offset = ir.increase_bound_by(4); + uint32_t type_id = offset; + uint32_t array_type_id = offset + 1; + uint32_t ptr_type_id = offset + 2; + uint32_t var_id = offset + 3; + + SPIRType type; + switch (input.second.format) + { + case MSL_SHADER_INPUT_FORMAT_UINT16: + case MSL_SHADER_INPUT_FORMAT_ANY16: + type.basetype = SPIRType::UShort; + type.width = 16; + break; + case MSL_SHADER_INPUT_FORMAT_ANY32: + default: + type.basetype = SPIRType::UInt; + type.width = 32; + break; + } + type.vecsize = input.second.vecsize; + set(type_id, type); + + type.array.push_back(0); + type.array_size_literal.push_back(true); + type.parent_type = type_id; + set(array_type_id, type); + + type.pointer = true; + type.pointer_depth++; + type.parent_type = array_type_id; + type.storage = storage; + auto &ptr_type = set(ptr_type_id, type); + ptr_type.self = array_type_id; + + auto &fake_var = set(var_id, ptr_type_id, storage); + set_decoration(var_id, DecorationLocation, input.first); + meta.strip_array = true; + add_variable_to_interface_block(storage, ib_var_ref, ib_type, fake_var, meta); + } + } + // Sort the members of the structure by their locations. MemberSorter member_sorter(ib_type, ir.meta[ib_type_id], MemberSorter::Location); member_sorter.sort(); @@ -2714,7 +3383,11 @@ uint32_t CompilerMSL::add_interface_block_pointer(uint32_t ib_var_id, StorageCla auto &ib_ptr_type = set(ib_ptr_type_id, ib_type); ib_ptr_type.parent_type = ib_ptr_type.type_alias = ib_type.self; ib_ptr_type.pointer = true; - ib_ptr_type.storage = storage == StorageClassInput ? StorageClassWorkgroup : StorageClassStorageBuffer; + ib_ptr_type.pointer_depth++; + ib_ptr_type.storage = + storage == StorageClassInput ? + (msl_options.multi_patch_workgroup ? StorageClassStorageBuffer : StorageClassWorkgroup) : + StorageClassStorageBuffer; ir.meta[ib_ptr_type_id] = ir.meta[ib_type.self]; // To ensure that get_variable_data_type() doesn't strip off the pointer, // which we need, use another pointer. @@ -2727,7 +3400,7 @@ uint32_t CompilerMSL::add_interface_block_pointer(uint32_t ib_var_id, StorageCla ib_ptr_var_id = next_id; set(ib_ptr_var_id, ib_ptr_ptr_type_id, StorageClassFunction, 0); - set_name(ib_ptr_var_id, storage == StorageClassInput ? input_wg_var_name : "gl_out"); + set_name(ib_ptr_var_id, storage == StorageClassInput ? "gl_in" : "gl_out"); } else { @@ -2776,6 +3449,7 @@ uint32_t CompilerMSL::ensure_correct_builtin_type(uint32_t type_id, BuiltIn buil auto &ptr_type = set(ptr_type_id); ptr_type = base_type; ptr_type.pointer = true; + ptr_type.pointer_depth++; ptr_type.storage = type.storage; ptr_type.parent_type = base_type_id; return ptr_type_id; @@ -2784,106 +3458,73 @@ uint32_t CompilerMSL::ensure_correct_builtin_type(uint32_t type_id, BuiltIn buil return type_id; } -// Ensure that the type is compatible with the vertex attribute. +// Ensure that the type is compatible with the shader input. // If it is, simply return the given type ID. // Otherwise, create a new type, and return its ID. -uint32_t CompilerMSL::ensure_correct_attribute_type(uint32_t type_id, uint32_t location, uint32_t num_components) +uint32_t CompilerMSL::ensure_correct_input_type(uint32_t type_id, uint32_t location, uint32_t num_components) { auto &type = get(type_id); - auto p_va = vtx_attrs_by_location.find(location); - if (p_va == end(vtx_attrs_by_location)) + auto p_va = inputs_by_location.find(location); + if (p_va == end(inputs_by_location)) { - if (num_components != 0 && type.vecsize != num_components) + if (num_components > type.vecsize) return build_extended_vector_type(type_id, num_components); else return type_id; } + if (num_components == 0) + num_components = p_va->second.vecsize; + switch (p_va->second.format) { - case MSL_VERTEX_FORMAT_UINT8: + case MSL_SHADER_INPUT_FORMAT_UINT8: { switch (type.basetype) { case SPIRType::UByte: case SPIRType::UShort: case SPIRType::UInt: - if (num_components != 0 && type.vecsize != num_components) + if (num_components > type.vecsize) return build_extended_vector_type(type_id, num_components); else return type_id; case SPIRType::Short: + return build_extended_vector_type(type_id, num_components > type.vecsize ? num_components : type.vecsize, + SPIRType::UShort); case SPIRType::Int: - break; + return build_extended_vector_type(type_id, num_components > type.vecsize ? num_components : type.vecsize, + SPIRType::UInt); default: SPIRV_CROSS_THROW("Vertex attribute type mismatch between host and shader"); } - - uint32_t next_id = ir.increase_bound_by(type.pointer ? 2 : 1); - uint32_t base_type_id = next_id++; - auto &base_type = set(base_type_id); - base_type = type; - base_type.basetype = type.basetype == SPIRType::Short ? SPIRType::UShort : SPIRType::UInt; - base_type.pointer = false; - if (num_components != 0) - base_type.vecsize = num_components; - - if (!type.pointer) - return base_type_id; - - uint32_t ptr_type_id = next_id++; - auto &ptr_type = set(ptr_type_id); - ptr_type = base_type; - ptr_type.pointer = true; - ptr_type.storage = type.storage; - ptr_type.parent_type = base_type_id; - return ptr_type_id; } - case MSL_VERTEX_FORMAT_UINT16: + case MSL_SHADER_INPUT_FORMAT_UINT16: { switch (type.basetype) { case SPIRType::UShort: case SPIRType::UInt: - if (num_components != 0 && type.vecsize != num_components) + if (num_components > type.vecsize) return build_extended_vector_type(type_id, num_components); else return type_id; case SPIRType::Int: - break; + return build_extended_vector_type(type_id, num_components > type.vecsize ? num_components : type.vecsize, + SPIRType::UInt); default: SPIRV_CROSS_THROW("Vertex attribute type mismatch between host and shader"); } - - uint32_t next_id = ir.increase_bound_by(type.pointer ? 2 : 1); - uint32_t base_type_id = next_id++; - auto &base_type = set(base_type_id); - base_type = type; - base_type.basetype = SPIRType::UInt; - base_type.pointer = false; - if (num_components != 0) - base_type.vecsize = num_components; - - if (!type.pointer) - return base_type_id; - - uint32_t ptr_type_id = next_id++; - auto &ptr_type = set(ptr_type_id); - ptr_type = base_type; - ptr_type.pointer = true; - ptr_type.storage = type.storage; - ptr_type.parent_type = base_type_id; - return ptr_type_id; } default: - if (num_components != 0 && type.vecsize != num_components) + if (num_components > type.vecsize) type_id = build_extended_vector_type(type_id, num_components); break; } @@ -3367,15 +4008,10 @@ void CompilerMSL::emit_store_statement(uint32_t lhs_expression, uint32_t rhs_exp auto &physical_type = get(physical_type_id); - static const char *swizzle_lut[] = { - ".x", - ".xy", - ".xyz", - "", - }; - if (is_matrix(type)) { + const char *packed_pfx = lhs_packed_type ? "packed_" : ""; + // Packed matrices are stored as arrays of packed vectors, so we need // to assign the vectors one at a time. // For row-major matrices, we need to transpose the *right-hand* side, @@ -3384,6 +4020,8 @@ void CompilerMSL::emit_store_statement(uint32_t lhs_expression, uint32_t rhs_exp // Lots of cases to cover here ... bool rhs_transpose = rhs_e && rhs_e->need_transpose; + SPIRType write_type = type; + string cast_expr; // We're dealing with transpose manually. if (rhs_transpose) @@ -3393,17 +4031,18 @@ void CompilerMSL::emit_store_statement(uint32_t lhs_expression, uint32_t rhs_exp { // We're dealing with transpose manually. lhs_e->need_transpose = false; + write_type.vecsize = type.columns; + write_type.columns = 1; - const char *store_swiz = ""; if (physical_type.columns != type.columns) - store_swiz = swizzle_lut[type.columns - 1]; + cast_expr = join("(device ", packed_pfx, type_to_glsl(write_type), "&)"); if (rhs_transpose) { // If RHS is also transposed, we can just copy row by row. for (uint32_t i = 0; i < type.vecsize; i++) { - statement(to_enclosed_expression(lhs_expression), "[", i, "]", store_swiz, " = ", + statement(cast_expr, to_enclosed_expression(lhs_expression), "[", i, "]", " = ", to_unpacked_row_major_matrix_expression(rhs_expression), "[", i, "];"); } } @@ -3426,7 +4065,7 @@ void CompilerMSL::emit_store_statement(uint32_t lhs_expression, uint32_t rhs_exp } rhs_row += ")"; - statement(to_enclosed_expression(lhs_expression), "[", i, "]", store_swiz, " = ", rhs_row, ";"); + statement(cast_expr, to_enclosed_expression(lhs_expression), "[", i, "]", " = ", rhs_row, ";"); } } @@ -3435,9 +4074,10 @@ void CompilerMSL::emit_store_statement(uint32_t lhs_expression, uint32_t rhs_exp } else { - const char *store_swiz = ""; + write_type.columns = 1; + if (physical_type.vecsize != type.vecsize) - store_swiz = swizzle_lut[type.vecsize - 1]; + cast_expr = join("(device ", packed_pfx, type_to_glsl(write_type), "&)"); if (rhs_transpose) { @@ -3459,7 +4099,7 @@ void CompilerMSL::emit_store_statement(uint32_t lhs_expression, uint32_t rhs_exp } rhs_row += ")"; - statement(to_enclosed_expression(lhs_expression), "[", i, "]", store_swiz, " = ", rhs_row, ";"); + statement(cast_expr, to_enclosed_expression(lhs_expression), "[", i, "]", " = ", rhs_row, ";"); } } else @@ -3467,7 +4107,7 @@ void CompilerMSL::emit_store_statement(uint32_t lhs_expression, uint32_t rhs_exp // Copy column-by-column. for (uint32_t i = 0; i < type.columns; i++) { - statement(to_enclosed_expression(lhs_expression), "[", i, "]", store_swiz, " = ", + statement(cast_expr, to_enclosed_expression(lhs_expression), "[", i, "]", " = ", to_enclosed_unpacked_expression(rhs_expression), "[", i, "];"); } } @@ -3481,6 +4121,10 @@ void CompilerMSL::emit_store_statement(uint32_t lhs_expression, uint32_t rhs_exp { lhs_e->need_transpose = false; + SPIRType write_type = type; + write_type.vecsize = 1; + write_type.columns = 1; + // Storing a column to a row-major matrix. Unroll the write. for (uint32_t c = 0; c < type.vecsize; c++) { @@ -3488,7 +4132,8 @@ void CompilerMSL::emit_store_statement(uint32_t lhs_expression, uint32_t rhs_exp auto column_index = lhs_expr.find_last_of('['); if (column_index != string::npos) { - statement(lhs_expr.insert(column_index, join('[', c, ']')), " = ", + statement("((device ", type_to_glsl(write_type), "*)&", + lhs_expr.insert(column_index, join('[', c, ']', ")")), " = ", to_extract_component_expression(rhs_expression, c), ";"); } } @@ -3510,7 +4155,7 @@ void CompilerMSL::emit_store_statement(uint32_t lhs_expression, uint32_t rhs_exp // Unpack the expression so we can store to it with a float or float2. // It's still an l-value, so it's fine. Most other unpacking of expressions turn them into r-values instead. - lhs = enclose_expression(lhs) + swizzle_lut[type.vecsize - 1]; + lhs = join("(device ", type_to_glsl(type), "&)", enclose_expression(lhs)); if (!optimize_read_modify_write(expression_type(rhs_expression), lhs, rhs)) statement(lhs, " = ", rhs, ";"); } @@ -3717,7 +4362,7 @@ void CompilerMSL::emit_custom_templates() // otherwise they will cause problems when linked together in a single Metallib. void CompilerMSL::emit_custom_functions() { - for (uint32_t i = SPVFuncImplArrayCopyMultidimMax; i >= 2; i--) + for (uint32_t i = kArrayCopyMultidimMax; i >= 2; i--) if (spv_function_implementations.count(static_cast(SPVFuncImplArrayCopyMultidimBase + i))) spv_function_implementations.insert(static_cast(SPVFuncImplArrayCopyMultidimBase + i - 1)); @@ -3838,19 +4483,24 @@ void CompilerMSL::emit_custom_functions() { // Unfortunately we cannot template on the address space, so combinatorial explosion it is. static const char *function_name_tags[] = { - "FromConstantToStack", "FromConstantToThreadGroup", "FromStackToStack", - "FromStackToThreadGroup", "FromThreadGroupToStack", "FromThreadGroupToThreadGroup", + "FromConstantToStack", "FromConstantToThreadGroup", "FromStackToStack", + "FromStackToThreadGroup", "FromThreadGroupToStack", "FromThreadGroupToThreadGroup", + "FromDeviceToDevice", "FromConstantToDevice", "FromStackToDevice", + "FromThreadGroupToDevice", "FromDeviceToStack", "FromDeviceToThreadGroup", }; static const char *src_address_space[] = { - "constant", "constant", "thread const", "thread const", "threadgroup const", "threadgroup const", + "constant", "constant", "thread const", "thread const", + "threadgroup const", "threadgroup const", "device const", "constant", + "thread const", "threadgroup const", "device const", "device const", }; static const char *dst_address_space[] = { "thread", "threadgroup", "thread", "threadgroup", "thread", "threadgroup", + "device", "device", "device", "device", "thread", "threadgroup", }; - for (uint32_t variant = 0; variant < 6; variant++) + for (uint32_t variant = 0; variant < 12; variant++) { uint32_t dimensions = spv_func - SPVFuncImplArrayCopyMultidimBase; string tmp = "template"); + statement("inline T spvSubgroupBroadcast(T value, ushort lane)"); + begin_scope(); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + statement("return quad_broadcast(value, lane);"); + else + statement("return simd_broadcast(value, lane);"); + end_scope(); + statement(""); + statement("template<>"); + statement("inline bool spvSubgroupBroadcast(bool value, ushort lane)"); + begin_scope(); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + statement("return !!quad_broadcast((ushort)value, lane);"); + else + statement("return !!simd_broadcast((ushort)value, lane);"); + end_scope(); + statement(""); + statement("template"); + statement("inline vec spvSubgroupBroadcast(vec value, ushort lane)"); + begin_scope(); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + statement("return (vec)quad_broadcast((vec)value, lane);"); + else + statement("return (vec)simd_broadcast((vec)value, lane);"); + end_scope(); + statement(""); + break; + + case SPVFuncImplSubgroupBroadcastFirst: + statement("template"); + statement("inline T spvSubgroupBroadcastFirst(T value)"); + begin_scope(); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + statement("return quad_broadcast_first(value);"); + else + statement("return simd_broadcast_first(value);"); + end_scope(); + statement(""); + statement("template<>"); + statement("inline bool spvSubgroupBroadcastFirst(bool value)"); + begin_scope(); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + statement("return !!quad_broadcast_first((ushort)value);"); + else + statement("return !!simd_broadcast_first((ushort)value);"); + end_scope(); + statement(""); + statement("template"); + statement("inline vec spvSubgroupBroadcastFirst(vec value)"); + begin_scope(); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + statement("return (vec)quad_broadcast_first((vec)value);"); + else + statement("return (vec)simd_broadcast_first((vec)value);"); + end_scope(); + statement(""); + break; + case SPVFuncImplSubgroupBallot: statement("inline uint4 spvSubgroupBallot(bool value)"); begin_scope(); - statement("simd_vote vote = simd_ballot(value);"); - statement("// simd_ballot() returns a 64-bit integer-like object, but"); - statement("// SPIR-V callers expect a uint4. We must convert."); - statement("// FIXME: This won't include higher bits if Apple ever supports"); - statement("// 128 lanes in an SIMD-group."); - statement("return uint4((uint)((simd_vote::vote_t)vote & 0xFFFFFFFF), (uint)(((simd_vote::vote_t)vote >> " - "32) & 0xFFFFFFFF), 0, 0);"); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + { + statement("return uint4((quad_vote::vote_t)quad_ballot(value), 0, 0, 0);"); + } + else if (msl_options.is_ios()) + { + // The current simd_vote on iOS uses a 32-bit integer-like object. + statement("return uint4((simd_vote::vote_t)simd_ballot(value), 0, 0, 0);"); + } + else + { + statement("simd_vote vote = simd_ballot(value);"); + statement("// simd_ballot() returns a 64-bit integer-like object, but"); + statement("// SPIR-V callers expect a uint4. We must convert."); + statement("// FIXME: This won't include higher bits if Apple ever supports"); + statement("// 128 lanes in an SIMD-group."); + statement( + "return uint4((uint)((simd_vote::vote_t)vote & 0xFFFFFFFF), (uint)(((simd_vote::vote_t)vote >> " + "32) & 0xFFFFFFFF), 0, 0);"); + } end_scope(); statement(""); break; @@ -4336,8 +5078,18 @@ void CompilerMSL::emit_custom_functions() break; case SPVFuncImplSubgroupBallotFindLSB: - statement("inline uint spvSubgroupBallotFindLSB(uint4 ballot)"); + statement("inline uint spvSubgroupBallotFindLSB(uint4 ballot, uint gl_SubgroupSize)"); begin_scope(); + if (msl_options.is_ios()) + { + statement("uint4 mask = uint4(extract_bits(0xFFFFFFFF, 0, gl_SubgroupSize), uint3(0));"); + } + else + { + statement("uint4 mask = uint4(extract_bits(0xFFFFFFFF, 0, min(gl_SubgroupSize, 32u)), " + "extract_bits(0xFFFFFFFF, 0, (uint)max((int)gl_SubgroupSize - 32, 0)), uint2(0));"); + } + statement("ballot &= mask;"); statement("return select(ctz(ballot.x), select(32 + ctz(ballot.y), select(64 + ctz(ballot.z), select(96 + " "ctz(ballot.w), uint(-1), ballot.w == 0), ballot.z == 0), ballot.y == 0), ballot.x == 0);"); end_scope(); @@ -4345,8 +5097,18 @@ void CompilerMSL::emit_custom_functions() break; case SPVFuncImplSubgroupBallotFindMSB: - statement("inline uint spvSubgroupBallotFindMSB(uint4 ballot)"); + statement("inline uint spvSubgroupBallotFindMSB(uint4 ballot, uint gl_SubgroupSize)"); begin_scope(); + if (msl_options.is_ios()) + { + statement("uint4 mask = uint4(extract_bits(0xFFFFFFFF, 0, gl_SubgroupSize), uint3(0));"); + } + else + { + statement("uint4 mask = uint4(extract_bits(0xFFFFFFFF, 0, min(gl_SubgroupSize, 32u)), " + "extract_bits(0xFFFFFFFF, 0, (uint)max((int)gl_SubgroupSize - 32, 0)), uint2(0));"); + } + statement("ballot &= mask;"); statement("return select(128 - (clz(ballot.w) + 1), select(96 - (clz(ballot.z) + 1), select(64 - " "(clz(ballot.y) + 1), select(32 - (clz(ballot.x) + 1), uint(-1), ballot.x == 0), ballot.y == 0), " "ballot.z == 0), ballot.w == 0);"); @@ -4355,24 +5117,52 @@ void CompilerMSL::emit_custom_functions() break; case SPVFuncImplSubgroupBallotBitCount: - statement("inline uint spvSubgroupBallotBitCount(uint4 ballot)"); + statement("inline uint spvPopCount4(uint4 ballot)"); begin_scope(); statement("return popcount(ballot.x) + popcount(ballot.y) + popcount(ballot.z) + popcount(ballot.w);"); end_scope(); statement(""); + statement("inline uint spvSubgroupBallotBitCount(uint4 ballot, uint gl_SubgroupSize)"); + begin_scope(); + if (msl_options.is_ios()) + { + statement("uint4 mask = uint4(extract_bits(0xFFFFFFFF, 0, gl_SubgroupSize), uint3(0));"); + } + else + { + statement("uint4 mask = uint4(extract_bits(0xFFFFFFFF, 0, min(gl_SubgroupSize, 32u)), " + "extract_bits(0xFFFFFFFF, 0, (uint)max((int)gl_SubgroupSize - 32, 0)), uint2(0));"); + } + statement("return spvPopCount4(ballot & mask);"); + end_scope(); + statement(""); statement("inline uint spvSubgroupBallotInclusiveBitCount(uint4 ballot, uint gl_SubgroupInvocationID)"); begin_scope(); - statement("uint4 mask = uint4(extract_bits(0xFFFFFFFF, 0, min(gl_SubgroupInvocationID + 1, 32u)), " - "extract_bits(0xFFFFFFFF, 0, (uint)max((int)gl_SubgroupInvocationID + 1 - 32, 0)), " - "uint2(0));"); - statement("return spvSubgroupBallotBitCount(ballot & mask);"); + if (msl_options.is_ios()) + { + statement("uint4 mask = uint4(extract_bits(0xFFFFFFFF, 0, gl_SubgroupInvocationID + 1), uint3(0));"); + } + else + { + statement("uint4 mask = uint4(extract_bits(0xFFFFFFFF, 0, min(gl_SubgroupInvocationID + 1, 32u)), " + "extract_bits(0xFFFFFFFF, 0, (uint)max((int)gl_SubgroupInvocationID + 1 - 32, 0)), " + "uint2(0));"); + } + statement("return spvPopCount4(ballot & mask);"); end_scope(); statement(""); statement("inline uint spvSubgroupBallotExclusiveBitCount(uint4 ballot, uint gl_SubgroupInvocationID)"); begin_scope(); - statement("uint4 mask = uint4(extract_bits(0xFFFFFFFF, 0, min(gl_SubgroupInvocationID, 32u)), " - "extract_bits(0xFFFFFFFF, 0, (uint)max((int)gl_SubgroupInvocationID - 32, 0)), uint2(0));"); - statement("return spvSubgroupBallotBitCount(ballot & mask);"); + if (msl_options.is_ios()) + { + statement("uint4 mask = uint4(extract_bits(0xFFFFFFFF, 0, gl_SubgroupInvocationID), uint2(0));"); + } + else + { + statement("uint4 mask = uint4(extract_bits(0xFFFFFFFF, 0, min(gl_SubgroupInvocationID, 32u)), " + "extract_bits(0xFFFFFFFF, 0, (uint)max((int)gl_SubgroupInvocationID - 32, 0)), uint2(0));"); + } + statement("return spvPopCount4(ballot & mask);"); end_scope(); statement(""); break; @@ -4385,13 +5175,200 @@ void CompilerMSL::emit_custom_functions() statement("template"); statement("inline bool spvSubgroupAllEqual(T value)"); begin_scope(); - statement("return simd_all(value == simd_broadcast_first(value));"); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + statement("return quad_all(all(value == quad_broadcast_first(value)));"); + else + statement("return simd_all(all(value == simd_broadcast_first(value)));"); end_scope(); statement(""); statement("template<>"); statement("inline bool spvSubgroupAllEqual(bool value)"); begin_scope(); - statement("return simd_all(value) || !simd_any(value);"); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + statement("return quad_all(value) || !quad_any(value);"); + else + statement("return simd_all(value) || !simd_any(value);"); + end_scope(); + statement(""); + statement("template"); + statement("inline bool spvSubgroupAllEqual(vec value)"); + begin_scope(); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + statement("return quad_all(all(value == (vec)quad_broadcast_first((vec)value)));"); + else + statement("return simd_all(all(value == (vec)simd_broadcast_first((vec)value)));"); + end_scope(); + statement(""); + break; + + case SPVFuncImplSubgroupShuffle: + statement("template"); + statement("inline T spvSubgroupShuffle(T value, ushort lane)"); + begin_scope(); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + statement("return quad_shuffle(value, lane);"); + else + statement("return simd_shuffle(value, lane);"); + end_scope(); + statement(""); + statement("template<>"); + statement("inline bool spvSubgroupShuffle(bool value, ushort lane)"); + begin_scope(); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + statement("return !!quad_shuffle((ushort)value, lane);"); + else + statement("return !!simd_shuffle((ushort)value, lane);"); + end_scope(); + statement(""); + statement("template"); + statement("inline vec spvSubgroupShuffle(vec value, ushort lane)"); + begin_scope(); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + statement("return (vec)quad_shuffle((vec)value, lane);"); + else + statement("return (vec)simd_shuffle((vec)value, lane);"); + end_scope(); + statement(""); + break; + + case SPVFuncImplSubgroupShuffleXor: + statement("template"); + statement("inline T spvSubgroupShuffleXor(T value, ushort mask)"); + begin_scope(); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + statement("return quad_shuffle_xor(value, mask);"); + else + statement("return simd_shuffle_xor(value, mask);"); + end_scope(); + statement(""); + statement("template<>"); + statement("inline bool spvSubgroupShuffleXor(bool value, ushort mask)"); + begin_scope(); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + statement("return !!quad_shuffle_xor((ushort)value, mask);"); + else + statement("return !!simd_shuffle_xor((ushort)value, mask);"); + end_scope(); + statement(""); + statement("template"); + statement("inline vec spvSubgroupShuffleXor(vec value, ushort mask)"); + begin_scope(); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + statement("return (vec)quad_shuffle_xor((vec)value, mask);"); + else + statement("return (vec)simd_shuffle_xor((vec)value, mask);"); + end_scope(); + statement(""); + break; + + case SPVFuncImplSubgroupShuffleUp: + statement("template"); + statement("inline T spvSubgroupShuffleUp(T value, ushort delta)"); + begin_scope(); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + statement("return quad_shuffle_up(value, delta);"); + else + statement("return simd_shuffle_up(value, delta);"); + end_scope(); + statement(""); + statement("template<>"); + statement("inline bool spvSubgroupShuffleUp(bool value, ushort delta)"); + begin_scope(); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + statement("return !!quad_shuffle_up((ushort)value, delta);"); + else + statement("return !!simd_shuffle_up((ushort)value, delta);"); + end_scope(); + statement(""); + statement("template"); + statement("inline vec spvSubgroupShuffleUp(vec value, ushort delta)"); + begin_scope(); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + statement("return (vec)quad_shuffle_up((vec)value, delta);"); + else + statement("return (vec)simd_shuffle_up((vec)value, delta);"); + end_scope(); + statement(""); + break; + + case SPVFuncImplSubgroupShuffleDown: + statement("template"); + statement("inline T spvSubgroupShuffleDown(T value, ushort delta)"); + begin_scope(); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + statement("return quad_shuffle_down(value, delta);"); + else + statement("return simd_shuffle_down(value, delta);"); + end_scope(); + statement(""); + statement("template<>"); + statement("inline bool spvSubgroupShuffleDown(bool value, ushort delta)"); + begin_scope(); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + statement("return !!quad_shuffle_down((ushort)value, delta);"); + else + statement("return !!simd_shuffle_down((ushort)value, delta);"); + end_scope(); + statement(""); + statement("template"); + statement("inline vec spvSubgroupShuffleDown(vec value, ushort delta)"); + begin_scope(); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + statement("return (vec)quad_shuffle_down((vec)value, delta);"); + else + statement("return (vec)simd_shuffle_down((vec)value, delta);"); + end_scope(); + statement(""); + break; + + case SPVFuncImplQuadBroadcast: + statement("template"); + statement("inline T spvQuadBroadcast(T value, uint lane)"); + begin_scope(); + statement("return quad_broadcast(value, lane);"); + end_scope(); + statement(""); + statement("template<>"); + statement("inline bool spvQuadBroadcast(bool value, uint lane)"); + begin_scope(); + statement("return !!quad_broadcast((ushort)value, lane);"); + end_scope(); + statement(""); + statement("template"); + statement("inline vec spvQuadBroadcast(vec value, uint lane)"); + begin_scope(); + statement("return (vec)quad_broadcast((vec)value, lane);"); + end_scope(); + statement(""); + break; + + case SPVFuncImplQuadSwap: + // We can implement this easily based on the following table giving + // the target lane ID from the direction and current lane ID: + // Direction + // | 0 | 1 | 2 | + // ---+---+---+---+ + // L 0 | 1 2 3 + // a 1 | 0 3 2 + // n 2 | 3 0 1 + // e 3 | 2 1 0 + // Notice that target = source ^ (direction + 1). + statement("template"); + statement("inline T spvQuadSwap(T value, uint dir)"); + begin_scope(); + statement("return quad_shuffle_xor(value, dir + 1);"); + end_scope(); + statement(""); + statement("template<>"); + statement("inline bool spvQuadSwap(bool value, uint dir)"); + begin_scope(); + statement("return !!quad_shuffle_xor((ushort)value, dir + 1);"); + end_scope(); + statement(""); + statement("template"); + statement("inline vec spvQuadSwap(vec value, uint dir)"); + begin_scope(); + statement("return (vec)quad_shuffle_xor((vec)value, dir + 1);"); end_scope(); statement(""); break; @@ -5140,6 +6117,30 @@ void CompilerMSL::emit_custom_functions() } } +static string inject_top_level_storage_qualifier(const string &expr, const string &qualifier) +{ + // Easier to do this through text munging since the qualifier does not exist in the type system at all, + // and plumbing in all that information is not very helpful. + size_t last_reference = expr.find_last_of('&'); + size_t last_pointer = expr.find_last_of('*'); + size_t last_significant = string::npos; + + if (last_reference == string::npos) + last_significant = last_pointer; + else if (last_pointer == string::npos) + last_significant = last_reference; + else + last_significant = std::max(last_reference, last_pointer); + + if (last_significant == string::npos) + return join(qualifier, " ", expr); + else + { + return join(expr.substr(0, last_significant + 1), " ", + qualifier, expr.substr(last_significant + 1, string::npos)); + } +} + // Undefined global memory is not allowed in MSL. // Declare constant and init to zeros. Use {}, as global constructors can break Metal. void CompilerMSL::declare_undefined_values() @@ -5147,7 +6148,14 @@ void CompilerMSL::declare_undefined_values() bool emitted = false; ir.for_each_typed_id([&](uint32_t, SPIRUndef &undef) { auto &type = this->get(undef.basetype); - statement("constant ", variable_decl(type, to_name(undef.self), undef.self), " = {};"); + // OpUndef can be void for some reason ... + if (type.basetype == SPIRType::Void) + return; + + statement(inject_top_level_storage_qualifier( + variable_decl(type, to_name(undef.self), undef.self), + "constant"), + " = {};"); emitted = true; }); @@ -5175,7 +6183,8 @@ void CompilerMSL::declare_constant_arrays() if (!type.array.empty() && (!fully_inlined || is_scalar(type) || is_vector(type))) { auto name = to_name(c.self); - statement("constant ", variable_decl(type, name), " = ", constant_expression(c), ";"); + statement(inject_top_level_storage_qualifier(variable_decl(type, name), "constant"), + " = ", constant_expression(c), ";"); emitted = true; } }); @@ -5247,6 +6256,15 @@ void CompilerMSL::emit_specialization_constants_and_structs() mark_scalar_layout_structs(type); }); + bool builtin_block_type_is_required = false; + // Very special case. If gl_PerVertex is initialized as an array (tessellation) + // we have to potentially emit the gl_PerVertex struct type so that we can emit a constant LUT. + ir.for_each_typed_id([&](uint32_t, SPIRConstant &c) { + auto &type = this->get(c.constant_type); + if (is_array(type) && has_decoration(type.self, DecorationBlock) && is_builtin_type(type)) + builtin_block_type_is_required = true; + }); + // Very particular use of the soft loop lock. // align_struct may need to create custom types on the fly, but we don't care about // these types for purpose of iterating over them in ir.ids_for_type and friends. @@ -5326,12 +6344,12 @@ void CompilerMSL::emit_specialization_constants_and_structs() auto &type = id.get(); TypeID type_id = type.self; - bool is_struct = (type.basetype == SPIRType::Struct) && type.array.empty(); + bool is_struct = (type.basetype == SPIRType::Struct) && type.array.empty() && !type.pointer; bool is_block = has_decoration(type.self, DecorationBlock) || has_decoration(type.self, DecorationBufferBlock); bool is_builtin_block = is_block && is_builtin_type(type); - bool is_declarable_struct = is_struct && !is_builtin_block; + bool is_declarable_struct = is_struct && (!is_builtin_block || builtin_block_type_is_required); // We'll declare this later. if (stage_out_var_id && get_stage_out_struct_type().self == type_id) @@ -5388,7 +6406,11 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id if (ptr_type.storage == StorageClassOutput && get_execution_model() == ExecutionModelTessellationEvaluation) return false; - bool flat_data_type = is_matrix(result_type) || is_array(result_type) || result_type.basetype == SPIRType::Struct; + bool multi_patch_tess_ctl = get_execution_model() == ExecutionModelTessellationControl && + msl_options.multi_patch_workgroup && ptr_type.storage == StorageClassInput; + bool flat_matrix = is_matrix(result_type) && ptr_type.storage == StorageClassInput && !multi_patch_tess_ctl; + bool flat_struct = result_type.basetype == SPIRType::Struct && ptr_type.storage == StorageClassInput; + bool flat_data_type = flat_matrix || is_array(result_type) || flat_struct; if (!flat_data_type) return false; @@ -5403,6 +6425,7 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id uint32_t interface_index = get_extended_decoration(ptr, SPIRVCrossDecorationInterfaceMemberIndex); auto *var = maybe_get_backing_variable(ptr); bool ptr_is_io_variable = ir.ids[ptr].get_type() == TypeVariable; + auto &expr_type = get_pointee_type(ptr_type.self); const auto &iface_type = expression_type(stage_in_ptr_var_id); @@ -5416,7 +6439,7 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id SPIRV_CROSS_THROW("Loading an array-of-array must be loaded directly from an IO variable."); if (interface_index == uint32_t(-1)) SPIRV_CROSS_THROW("Interface index is unknown. Cannot continue."); - if (result_type.basetype == SPIRType::Struct || is_matrix(result_type)) + if (result_type.basetype == SPIRType::Struct || flat_matrix) SPIRV_CROSS_THROW("Cannot load array-of-array of composite type in tessellation IO."); expr += type_to_glsl(result_type) + "({ "; @@ -5430,16 +6453,44 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id expr += type_to_glsl(sub_type) + "({ "; interface_index = base_interface_index; uint32_t array_size = to_array_size_literal(result_type, 0); - for (uint32_t j = 0; j < array_size; j++, interface_index++) + if (multi_patch_tess_ctl) { - const uint32_t indices[2] = { i, interface_index }; + for (uint32_t j = 0; j < array_size; j++) + { + const uint32_t indices[3] = { i, interface_index, j }; - AccessChainMeta meta; - expr += access_chain_internal(stage_in_ptr_var_id, indices, 2, - ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT, &meta); + AccessChainMeta meta; + expr += + access_chain_internal(stage_in_ptr_var_id, indices, 3, + ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT, &meta); + // If the expression has more vector components than the result type, insert + // a swizzle. This shouldn't happen normally on valid SPIR-V, but it might + // happen if we replace the type of an input variable. + if (!is_matrix(sub_type) && sub_type.basetype != SPIRType::Struct && + expr_type.vecsize > sub_type.vecsize) + expr += vector_swizzle(sub_type.vecsize, 0); - if (j + 1 < array_size) - expr += ", "; + if (j + 1 < array_size) + expr += ", "; + } + } + else + { + for (uint32_t j = 0; j < array_size; j++, interface_index++) + { + const uint32_t indices[2] = { i, interface_index }; + + AccessChainMeta meta; + expr += + access_chain_internal(stage_in_ptr_var_id, indices, 2, + ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT, &meta); + if (!is_matrix(sub_type) && sub_type.basetype != SPIRType::Struct && + expr_type.vecsize > sub_type.vecsize) + expr += vector_swizzle(sub_type.vecsize, 0); + + if (j + 1 < array_size) + expr += ", "; + } } expr += " })"; if (i + 1 < num_control_points) @@ -5447,7 +6498,7 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id } expr += " })"; } - else if (result_type.basetype == SPIRType::Struct) + else if (flat_struct) { bool is_array_of_struct = is_array(result_type); if (is_array_of_struct && !ptr_is_io_variable) @@ -5479,7 +6530,8 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id SPIRV_CROSS_THROW("Interface index is unknown. Cannot continue."); const auto &mbr_type = get(struct_type.member_types[j]); - if (is_matrix(mbr_type)) + const auto &expr_mbr_type = get(expr_type.member_types[j]); + if (is_matrix(mbr_type) && ptr_type.storage == StorageClassInput && !multi_patch_tess_ctl) { expr += type_to_glsl(mbr_type) + "("; for (uint32_t k = 0; k < mbr_type.columns; k++, interface_index++) @@ -5494,6 +6546,8 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id } else expr += to_expression(ptr) + "." + to_member_name(iface_type, interface_index); + if (expr_mbr_type.vecsize > mbr_type.vecsize) + expr += vector_swizzle(mbr_type.vecsize, 0); if (k + 1 < mbr_type.columns) expr += ", "; @@ -5504,21 +6558,48 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id { expr += type_to_glsl(mbr_type) + "({ "; uint32_t array_size = to_array_size_literal(mbr_type, 0); - for (uint32_t k = 0; k < array_size; k++, interface_index++) + if (multi_patch_tess_ctl) { - if (is_array_of_struct) + for (uint32_t k = 0; k < array_size; k++) { - const uint32_t indices[2] = { i, interface_index }; - AccessChainMeta meta; - expr += access_chain_internal( - stage_in_ptr_var_id, indices, 2, - ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT, &meta); - } - else - expr += to_expression(ptr) + "." + to_member_name(iface_type, interface_index); + if (is_array_of_struct) + { + const uint32_t indices[3] = { i, interface_index, k }; + AccessChainMeta meta; + expr += access_chain_internal( + stage_in_ptr_var_id, indices, 3, + ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT, &meta); + } + else + expr += join(to_expression(ptr), ".", to_member_name(iface_type, interface_index), "[", + k, "]"); + if (expr_mbr_type.vecsize > mbr_type.vecsize) + expr += vector_swizzle(mbr_type.vecsize, 0); - if (k + 1 < array_size) - expr += ", "; + if (k + 1 < array_size) + expr += ", "; + } + } + else + { + for (uint32_t k = 0; k < array_size; k++, interface_index++) + { + if (is_array_of_struct) + { + const uint32_t indices[2] = { i, interface_index }; + AccessChainMeta meta; + expr += access_chain_internal( + stage_in_ptr_var_id, indices, 2, + ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT, &meta); + } + else + expr += to_expression(ptr) + "." + to_member_name(iface_type, interface_index); + if (expr_mbr_type.vecsize > mbr_type.vecsize) + expr += vector_swizzle(mbr_type.vecsize, 0); + + if (k + 1 < array_size) + expr += ", "; + } } expr += " })"; } @@ -5534,6 +6615,8 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id } else expr += to_expression(ptr) + "." + to_member_name(iface_type, interface_index); + if (expr_mbr_type.vecsize > mbr_type.vecsize) + expr += vector_swizzle(mbr_type.vecsize, 0); } if (j + 1 < struct_type.member_types.size()) @@ -5546,7 +6629,7 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id if (is_array_of_struct) expr += " })"; } - else if (is_matrix(result_type)) + else if (flat_matrix) { bool is_array_of_matrix = is_array(result_type); if (is_array_of_matrix && !ptr_is_io_variable) @@ -5575,6 +6658,8 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id expr += access_chain_internal(stage_in_ptr_var_id, indices, 2, ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT, &meta); + if (expr_type.vecsize > result_type.vecsize) + expr += vector_swizzle(result_type.vecsize, 0); if (j + 1 < result_type.columns) expr += ", "; } @@ -5591,6 +6676,8 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id for (uint32_t i = 0; i < result_type.columns; i++, interface_index++) { expr += to_expression(ptr) + "." + to_member_name(iface_type, interface_index); + if (expr_type.vecsize > result_type.vecsize) + expr += vector_swizzle(result_type.vecsize, 0); if (i + 1 < result_type.columns) expr += ", "; } @@ -5616,6 +6703,8 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id AccessChainMeta meta; expr += access_chain_internal(stage_in_ptr_var_id, indices, 2, ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT, &meta); + if (expr_type.vecsize > result_type.vecsize) + expr += vector_swizzle(result_type.vecsize, 0); if (i + 1 < num_control_points) expr += ", "; @@ -5635,6 +6724,8 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id for (uint32_t i = 0; i < array_size; i++, interface_index++) { expr += to_expression(ptr) + "." + to_member_name(iface_type, interface_index); + if (expr_type.vecsize > result_type.vecsize) + expr += vector_swizzle(result_type.vecsize, 0); if (i + 1 < array_size) expr += ", "; } @@ -5657,6 +6748,7 @@ bool CompilerMSL::emit_tessellation_access_chain(const uint32_t *ops, uint32_t l bool patch = false; bool flat_data = false; bool ptr_is_chain = false; + bool multi_patch = get_execution_model() == ExecutionModelTessellationControl && msl_options.multi_patch_workgroup; if (var) { @@ -5680,14 +6772,9 @@ bool CompilerMSL::emit_tessellation_access_chain(const uint32_t *ops, uint32_t l { AccessChainMeta meta; SmallVector indices; - uint32_t next_id = ir.increase_bound_by(2); + uint32_t next_id = ir.increase_bound_by(1); indices.reserve(length - 3 + 1); - uint32_t type_id = next_id++; - SPIRType new_uint_type; - new_uint_type.basetype = SPIRType::UInt; - new_uint_type.width = 32; - set(type_id, new_uint_type); uint32_t first_non_array_index = ptr_is_chain ? 3 : 4; VariableID stage_var_id = var->storage == StorageClassInput ? stage_in_ptr_var_id : stage_out_ptr_var_id; @@ -5722,7 +6809,7 @@ bool CompilerMSL::emit_tessellation_access_chain(const uint32_t *ops, uint32_t l // we're hosed. for (; i < length; ++i) { - if (!is_array(*type) && !is_matrix(*type) && type->basetype != SPIRType::Struct) + if ((multi_patch || (!is_array(*type) && !is_matrix(*type))) && type->basetype != SPIRType::Struct) break; auto *c = maybe_get(ops[i]); @@ -5741,7 +6828,8 @@ bool CompilerMSL::emit_tessellation_access_chain(const uint32_t *ops, uint32_t l type = &get(type->member_types[c->scalar()]); } - if (is_matrix(result_ptr_type) || is_array(result_ptr_type) || result_ptr_type.basetype == SPIRType::Struct) + if ((!multi_patch && (is_matrix(result_ptr_type) || is_array(result_ptr_type))) || + result_ptr_type.basetype == SPIRType::Struct) { // We're not going to emit the actual member name, we let any further OpLoad take care of that. // Tag the access chain with the member index we're referencing. @@ -5750,7 +6838,7 @@ bool CompilerMSL::emit_tessellation_access_chain(const uint32_t *ops, uint32_t l else { // Access the appropriate member of gl_in/gl_out. - set(const_mbr_id, type_id, index, false); + set(const_mbr_id, get_uint_type_id(), index, false); indices.push_back(const_mbr_id); // Append any straggling access chain indices. @@ -5761,7 +6849,7 @@ bool CompilerMSL::emit_tessellation_access_chain(const uint32_t *ops, uint32_t l else { assert(index != uint32_t(-1)); - set(const_mbr_id, type_id, index, false); + set(const_mbr_id, get_uint_type_id(), index, false); indices.push_back(const_mbr_id); indices.insert(indices.end(), ops + 4, ops + length); @@ -5802,6 +6890,24 @@ bool CompilerMSL::emit_tessellation_access_chain(const uint32_t *ops, uint32_t l } } + // Get the actual type of the object that was accessed. If it's a vector type and we changed it, + // then we'll need to add a swizzle. + // For this, we can't necessarily rely on the type of the base expression, because it might be + // another access chain, and it will therefore already have the "correct" type. + auto *expr_type = &get_variable_data_type(*var); + if (has_extended_decoration(ops[2], SPIRVCrossDecorationTessIOOriginalInputTypeID)) + expr_type = &get(get_extended_decoration(ops[2], SPIRVCrossDecorationTessIOOriginalInputTypeID)); + for (uint32_t i = 3; i < length; i++) + { + if (!is_array(*expr_type) && expr_type->basetype == SPIRType::Struct) + expr_type = &get(expr_type->member_types[get(ops[i]).scalar()]); + else + expr_type = &get(expr_type->parent_type); + } + if (!is_array(*expr_type) && !is_matrix(*expr_type) && expr_type->basetype != SPIRType::Struct && + expr_type->vecsize > result_ptr_type.vecsize) + e += vector_swizzle(result_ptr_type.vecsize, 0); + auto &expr = set(ops[1], move(e), ops[0], should_forward(ops[2])); expr.loaded_from = var->self; expr.need_transpose = meta.need_transpose; @@ -5814,6 +6920,8 @@ bool CompilerMSL::emit_tessellation_access_chain(const uint32_t *ops, uint32_t l set_extended_decoration(ops[1], SPIRVCrossDecorationPhysicalTypeID, meta.storage_physical_type); if (meta.storage_is_invariant) set_decoration(ops[1], DecorationInvariant); + // Save the type we found in case the result is used in another access chain. + set_extended_decoration(ops[1], SPIRVCrossDecorationTessIOOriginalInputTypeID, expr_type->self); // If we have some expression dependencies in our access chain, this access chain is technically a forwarded // temporary which could be subject to invalidation. @@ -5886,6 +6994,78 @@ bool CompilerMSL::is_out_of_bounds_tessellation_level(uint32_t id_lhs) (builtin == BuiltInTessLevelOuter && c->scalar() == 3); } +void CompilerMSL::prepare_access_chain_for_scalar_access(std::string &expr, const SPIRType &type, + spv::StorageClass storage, bool &is_packed) +{ + // If there is any risk of writes happening with the access chain in question, + // and there is a risk of concurrent write access to other components, + // we must cast the access chain to a plain pointer to ensure we only access the exact scalars we expect. + // The MSL compiler refuses to allow component-level access for any non-packed vector types. + if (!is_packed && (storage == StorageClassStorageBuffer || storage == StorageClassWorkgroup)) + { + const char *addr_space = storage == StorageClassWorkgroup ? "threadgroup" : "device"; + expr = join("((", addr_space, " ", type_to_glsl(type), "*)&", enclose_expression(expr), ")"); + + // Further indexing should happen with packed rules (array index, not swizzle). + is_packed = true; + } +} + +// Sets the interface member index for an access chain to a pull-model interpolant. +void CompilerMSL::fix_up_interpolant_access_chain(const uint32_t *ops, uint32_t length) +{ + auto *var = maybe_get_backing_variable(ops[2]); + if (!var || !pull_model_inputs.count(var->self)) + return; + // Get the base index. + uint32_t interface_index; + auto &var_type = get_variable_data_type(*var); + auto &result_type = get(ops[0]); + auto *type = &var_type; + if (has_extended_decoration(ops[2], SPIRVCrossDecorationInterfaceMemberIndex)) + { + interface_index = get_extended_decoration(ops[2], SPIRVCrossDecorationInterfaceMemberIndex); + } + else + { + // Assume an access chain into a struct variable. + assert(var_type.basetype == SPIRType::Struct); + auto &c = get(ops[3 + var_type.array.size()]); + interface_index = + get_extended_member_decoration(var->self, c.scalar(), SPIRVCrossDecorationInterfaceMemberIndex); + } + // Accumulate indices. We'll have to skip over the one for the struct, if present, because we already accounted + // for that getting the base index. + for (uint32_t i = 3; i < length; ++i) + { + if (is_vector(*type) && is_scalar(result_type)) + { + // We don't want to combine the next index. Actually, we need to save it + // so we know to apply a swizzle to the result of the interpolation. + set_extended_decoration(ops[1], SPIRVCrossDecorationInterpolantComponentExpr, ops[i]); + break; + } + + auto *c = maybe_get(ops[i]); + if (!c || c->specialization) + SPIRV_CROSS_THROW("Trying to dynamically index into an array interface variable using pull-model " + "interpolation. This is currently unsupported."); + + if (type->parent_type) + type = &get(type->parent_type); + else if (type->basetype == SPIRType::Struct) + type = &get(type->member_types[c->scalar()]); + + if (!has_extended_decoration(ops[2], SPIRVCrossDecorationInterfaceMemberIndex) && + i - 3 == var_type.array.size()) + continue; + + interface_index += c->scalar(); + } + // Save this to the access chain itself so we can recover it later when calling an interpolation function. + set_extended_decoration(ops[1], SPIRVCrossDecorationInterfaceMemberIndex, interface_index); +} + // Override for MSL-specific syntax instructions void CompilerMSL::emit_instruction(const Instruction &instruction) { @@ -6215,7 +7395,7 @@ void CompilerMSL::emit_instruction(const Instruction &instruction) } } - emit_texture_op(instruction); + emit_texture_op(instruction, false); break; } @@ -6301,11 +7481,15 @@ void CompilerMSL::emit_instruction(const Instruction &instruction) auto store_type = texel_type; store_type.vecsize = 4; + TextureFunctionArguments args = {}; + args.base.img = img_id; + args.base.imgtype = &img_type; + args.base.is_fetch = true; + args.coord = coord_id; + args.lod = lod; statement(join(to_expression(img_id), ".write(", remap_swizzle(store_type, texel_type.vecsize, to_expression(texel_id)), ", ", - to_function_args(img_id, img_type, true, false, false, coord_id, 0, 0, 0, 0, lod, 0, 0, 0, 0, 0, - 0, &forward), - ");")); + CompilerMSL::to_function_args(args, &forward), ");")); if (p_var && variable_storage_is_aliased(*p_var)) flush_all_aliased_variables(); @@ -6477,6 +7661,7 @@ void CompilerMSL::emit_instruction(const Instruction &instruction) } else CompilerGLSL::emit_instruction(instruction); + fix_up_interpolant_access_chain(ops, instruction.length); break; case OpStore: @@ -6724,11 +7909,18 @@ void CompilerMSL::emit_instruction(const Instruction &instruction) break; } + // SPV_EXT_demote_to_helper_invocation + case OpDemoteToHelperInvocationEXT: + if (!msl_options.supports_msl_version(2, 3)) + SPIRV_CROSS_THROW("discard_fragment() does not formally have demote semantics until MSL 2.3."); + CompilerGLSL::emit_instruction(instruction); + break; + case OpIsHelperInvocationEXT: - if (msl_options.is_ios()) - SPIRV_CROSS_THROW("simd_is_helper_thread() is only supported on macOS."); + if (msl_options.is_ios() && !msl_options.supports_msl_version(2, 3)) + SPIRV_CROSS_THROW("simd_is_helper_thread() requires MSL 2.3 on iOS."); else if (msl_options.is_macos() && !msl_options.supports_msl_version(2, 1)) - SPIRV_CROSS_THROW("simd_is_helper_thread() requires version 2.1 on macOS."); + SPIRV_CROSS_THROW("simd_is_helper_thread() requires MSL 2.1 on macOS."); emit_op(ops[0], ops[1], "simd_is_helper_thread()", false); break; @@ -6746,9 +7938,12 @@ void CompilerMSL::emit_instruction(const Instruction &instruction) previous_instruction_opcode = opcode; } -void CompilerMSL::emit_texture_op(const Instruction &i) +void CompilerMSL::emit_texture_op(const Instruction &i, bool sparse) { - if (msl_options.is_ios() && msl_options.ios_use_framebuffer_fetch_subpasses) + if (sparse) + SPIRV_CROSS_THROW("Sparse feedback not yet supported in MSL."); + + if (msl_options.use_framebuffer_fetch_subpasses) { auto *ops = stream(i); @@ -6771,7 +7966,7 @@ void CompilerMSL::emit_texture_op(const Instruction &i) } // Fallback to default implementation - CompilerGLSL::emit_texture_op(i); + CompilerGLSL::emit_texture_op(i, sparse); } void CompilerMSL::emit_barrier(uint32_t id_exe_scope, uint32_t id_mem_scope, uint32_t id_mem_sem) @@ -6779,11 +7974,15 @@ void CompilerMSL::emit_barrier(uint32_t id_exe_scope, uint32_t id_mem_scope, uin if (get_execution_model() != ExecutionModelGLCompute && get_execution_model() != ExecutionModelTessellationControl) return; - uint32_t exe_scope = id_exe_scope ? get(id_exe_scope).scalar() : uint32_t(ScopeInvocation); - uint32_t mem_scope = id_mem_scope ? get(id_mem_scope).scalar() : uint32_t(ScopeInvocation); + uint32_t exe_scope = id_exe_scope ? evaluate_constant_u32(id_exe_scope) : uint32_t(ScopeInvocation); + uint32_t mem_scope = id_mem_scope ? evaluate_constant_u32(id_mem_scope) : uint32_t(ScopeInvocation); // Use the wider of the two scopes (smaller value) exe_scope = min(exe_scope, mem_scope); + if (msl_options.emulate_subgroups && exe_scope >= ScopeSubgroup && !id_mem_sem) + // In this case, we assume a "subgroup" size of 1. The barrier, then, is a noop. + return; + string bar_stmt; if ((msl_options.is_ios() && msl_options.supports_msl_version(1, 2)) || msl_options.supports_msl_version(2)) bar_stmt = exe_scope < ScopeSubgroup ? "threadgroup_barrier" : "simdgroup_barrier"; @@ -6791,7 +7990,7 @@ void CompilerMSL::emit_barrier(uint32_t id_exe_scope, uint32_t id_mem_scope, uin bar_stmt = "threadgroup_barrier"; bar_stmt += "("; - uint32_t mem_sem = id_mem_sem ? get(id_mem_sem).scalar() : uint32_t(MemorySemanticsMaskNone); + uint32_t mem_sem = id_mem_sem ? evaluate_constant_u32(id_mem_sem) : uint32_t(MemorySemanticsMaskNone); // Use the | operator to combine flags if we can. if (msl_options.supports_msl_version(1, 2)) @@ -6855,6 +8054,7 @@ void CompilerMSL::emit_array_copy(const string &lhs, uint32_t rhs_id, StorageCla bool lhs_thread = (lhs_storage == StorageClassOutput || lhs_storage == StorageClassFunction || lhs_storage == StorageClassGeneric || lhs_storage == StorageClassPrivate); bool rhs_thread = (rhs_storage == StorageClassInput || rhs_storage == StorageClassFunction || + rhs_storage == StorageClassOutput || rhs_storage == StorageClassGeneric || rhs_storage == StorageClassPrivate); // If threadgroup storage qualifiers are *not* used: @@ -6881,6 +8081,10 @@ void CompilerMSL::emit_array_copy(const string &lhs, uint32_t rhs_id, StorageCla { is_constant = true; } + else if (rhs_storage == StorageClassUniform) + { + is_constant = true; + } // For the case where we have OpLoad triggering an array copy, // we cannot easily detect this case ahead of time since it's @@ -6888,7 +8092,7 @@ void CompilerMSL::emit_array_copy(const string &lhs, uint32_t rhs_id, StorageCla // if this is the only use of array copies in our shader. if (type.array.size() > 1) { - if (type.array.size() > SPVFuncImplArrayCopyMultidimMax) + if (type.array.size() > kArrayCopyMultidimMax) SPIRV_CROSS_THROW("Cannot support this many dimensions for arrays of arrays."); auto func = static_cast(SPVFuncImplArrayCopyMultidimBase + type.array.size()); add_spv_func_and_recompile(func); @@ -6909,6 +8113,18 @@ void CompilerMSL::emit_array_copy(const string &lhs, uint32_t rhs_id, StorageCla tag = "FromThreadGroupToStack"; else if (lhs_storage == StorageClassWorkgroup && rhs_storage == StorageClassWorkgroup) tag = "FromThreadGroupToThreadGroup"; + else if (lhs_storage == StorageClassStorageBuffer && rhs_storage == StorageClassStorageBuffer) + tag = "FromDeviceToDevice"; + else if (lhs_storage == StorageClassStorageBuffer && is_constant) + tag = "FromConstantToDevice"; + else if (lhs_storage == StorageClassStorageBuffer && rhs_storage == StorageClassWorkgroup) + tag = "FromThreadGroupToDevice"; + else if (lhs_storage == StorageClassStorageBuffer && rhs_thread) + tag = "FromStackToDevice"; + else if (lhs_storage == StorageClassWorkgroup && rhs_storage == StorageClassStorageBuffer) + tag = "FromDeviceToThreadGroup"; + else if (lhs_thread && rhs_storage == StorageClassStorageBuffer) + tag = "FromDeviceToStack"; else SPIRV_CROSS_THROW("Unknown storage class used for copying arrays."); @@ -6922,6 +8138,14 @@ void CompilerMSL::emit_array_copy(const string &lhs, uint32_t rhs_id, StorageCla } } +uint32_t CompilerMSL::get_physical_tess_level_array_size(spv::BuiltIn builtin) const +{ + if (get_execution_mode_bitset().get(ExecutionModeTriangles)) + return builtin == BuiltInTessLevelInner ? 1 : 3; + else + return builtin == BuiltInTessLevelInner ? 2 : 4; +} + // Since MSL does not allow arrays to be copied via simple variable assignment, // if the LHS and RHS represent an assignment of an entire array, it must be // implemented by calling an array copy function. @@ -6950,13 +8174,32 @@ bool CompilerMSL::maybe_emit_array_assignment(uint32_t id_lhs, uint32_t id_rhs) return true; } + if (get_execution_model() == ExecutionModelTessellationControl && + has_decoration(id_lhs, DecorationBuiltIn)) + { + auto builtin = BuiltIn(get_decoration(id_lhs, DecorationBuiltIn)); + // Need to manually unroll the array store. + if (builtin == BuiltInTessLevelInner || builtin == BuiltInTessLevelOuter) + { + uint32_t array_size = get_physical_tess_level_array_size(builtin); + if (array_size == 1) + statement(to_expression(id_lhs), " = half(", to_expression(id_rhs), "[0]);"); + else + { + for (uint32_t i = 0; i < array_size; i++) + statement(to_expression(id_lhs), "[", i, "] = half(", to_expression(id_rhs), "[", i, "]);"); + } + return true; + } + } + // Ensure the LHS variable has been declared auto *p_v_lhs = maybe_get_backing_variable(id_lhs); if (p_v_lhs) flush_variable_declaration(p_v_lhs->self); - emit_array_copy(to_expression(id_lhs), id_rhs, get_backing_variable_storage(id_lhs), - get_backing_variable_storage(id_rhs)); + emit_array_copy(to_expression(id_lhs), id_rhs, get_expression_effective_storage_class(id_lhs), + get_expression_effective_storage_class(id_rhs)); register_write(id_lhs); return true; @@ -7210,10 +8453,71 @@ void CompilerMSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, emit_trinary_func_op(result_type, id, args[0], args[1], args[2], "precise::clamp"); break; - // TODO: - // GLSLstd450InterpolateAtCentroid (centroid_no_perspective qualifier) - // GLSLstd450InterpolateAtSample (sample_no_perspective qualifier) - // GLSLstd450InterpolateAtOffset + case GLSLstd450InterpolateAtCentroid: + { + // We can't just emit the expression normally, because the qualified name contains a call to the default + // interpolate method, or refers to a local variable. We saved the interface index we need; use it to construct + // the base for the method call. + uint32_t interface_index = get_extended_decoration(args[0], SPIRVCrossDecorationInterfaceMemberIndex); + string component; + if (has_extended_decoration(args[0], SPIRVCrossDecorationInterpolantComponentExpr)) + { + uint32_t index_expr = get_extended_decoration(args[0], SPIRVCrossDecorationInterpolantComponentExpr); + auto *c = maybe_get(index_expr); + if (!c || c->specialization) + component = join("[", to_expression(index_expr), "]"); + else + component = join(".", index_to_swizzle(c->scalar())); + } + emit_op(result_type, id, + join(to_name(stage_in_var_id), ".", to_member_name(get_stage_in_struct_type(), interface_index), + ".interpolate_at_centroid()", component), + should_forward(args[0])); + break; + } + + case GLSLstd450InterpolateAtSample: + { + uint32_t interface_index = get_extended_decoration(args[0], SPIRVCrossDecorationInterfaceMemberIndex); + string component; + if (has_extended_decoration(args[0], SPIRVCrossDecorationInterpolantComponentExpr)) + { + uint32_t index_expr = get_extended_decoration(args[0], SPIRVCrossDecorationInterpolantComponentExpr); + auto *c = maybe_get(index_expr); + if (!c || c->specialization) + component = join("[", to_expression(index_expr), "]"); + else + component = join(".", index_to_swizzle(c->scalar())); + } + emit_op(result_type, id, + join(to_name(stage_in_var_id), ".", to_member_name(get_stage_in_struct_type(), interface_index), + ".interpolate_at_sample(", to_expression(args[1]), ")", component), + should_forward(args[0]) && should_forward(args[1])); + break; + } + + case GLSLstd450InterpolateAtOffset: + { + uint32_t interface_index = get_extended_decoration(args[0], SPIRVCrossDecorationInterfaceMemberIndex); + string component; + if (has_extended_decoration(args[0], SPIRVCrossDecorationInterpolantComponentExpr)) + { + uint32_t index_expr = get_extended_decoration(args[0], SPIRVCrossDecorationInterpolantComponentExpr); + auto *c = maybe_get(index_expr); + if (!c || c->specialization) + component = join("[", to_expression(index_expr), "]"); + else + component = join(".", index_to_swizzle(c->scalar())); + } + // Like Direct3D, Metal puts the (0, 0) at the upper-left corner, not the center as SPIR-V and GLSL do. + // Offset the offset by (1/2 - 1/16), or 0.4375, to compensate for this. + // It has to be (1/2 - 1/16) and not 1/2, or several CTS tests subtly break on Intel. + emit_op(result_type, id, + join(to_name(stage_in_var_id), ".", to_member_name(get_stage_in_struct_type(), interface_index), + ".interpolate_at_offset(", to_expression(args[1]), " + 0.4375)", component), + should_forward(args[0]) && should_forward(args[1])); + break; + } case GLSLstd450Distance: // MSL does not support scalar versions here. @@ -7221,7 +8525,8 @@ void CompilerMSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, { // Equivalent to length(a - b) -> abs(a - b). emit_op(result_type, id, - join("abs(", to_unpacked_expression(args[0]), " - ", to_unpacked_expression(args[1]), ")"), + join("abs(", to_enclosed_unpacked_expression(args[0]), " - ", + to_enclosed_unpacked_expression(args[1]), ")"), should_forward(args[0]) && should_forward(args[1])); inherit_expression_dependencies(id, args[0]); inherit_expression_dependencies(id, args[1]); @@ -7388,7 +8693,7 @@ void CompilerMSL::emit_function_prototype(SPIRFunction &func, const Bitset &) // Fake arrays returns by writing to an out array instead. decl += "thread "; decl += type_to_glsl(type); - decl += " (&SPIRV_Cross_return_value)"; + decl += " (&spvReturnValue)"; decl += type_to_array_glsl(type); if (!func.arguments.empty()) decl += ", "; @@ -7452,7 +8757,7 @@ void CompilerMSL::emit_function_prototype(SPIRFunction &func, const Bitset &) // Manufacture automatic sampler arg for SampledImage texture if (arg_type.image.dim != DimBuffer) - decl += join(", thread const ", sampler_type(arg_type), " ", to_sampler_expression(arg.id)); + decl += join(", thread const ", sampler_type(arg_type, arg.id), " ", to_sampler_expression(arg.id)); } // Manufacture automatic swizzle arg. @@ -7485,9 +8790,11 @@ static bool needs_chroma_reconstruction(const MSLConstexprSampler *constexpr_sam } // Returns the texture sampling function string for the specified image and sampling characteristics. -string CompilerMSL::to_function_name(VariableID img, const SPIRType &imgtype, bool is_fetch, bool is_gather, bool, bool, - bool, bool, bool has_dref, uint32_t, uint32_t) +string CompilerMSL::to_function_name(const TextureFunctionNameArguments &args) { + VariableID img = args.base.img; + auto &imgtype = *args.base.imgtype; + const MSLConstexprSampler *constexpr_sampler = nullptr; bool is_dynamic_img_sampler = false; if (auto *var = maybe_get_backing_variable(img)) @@ -7498,7 +8805,7 @@ string CompilerMSL::to_function_name(VariableID img, const SPIRType &imgtype, bo // Special-case gather. We have to alter the component being looked up // in the swizzle case. - if (msl_options.swizzle_texture_samples && is_gather && !is_dynamic_img_sampler && + if (msl_options.swizzle_texture_samples && args.base.is_gather && !is_dynamic_img_sampler && (!constexpr_sampler || !constexpr_sampler->ycbcr_conversion_enable)) { add_spv_func_and_recompile(imgtype.image.depth ? SPVFuncImplGatherCompareSwizzle : SPVFuncImplGatherSwizzle); @@ -7618,14 +8925,14 @@ string CompilerMSL::to_function_name(VariableID img, const SPIRType &imgtype, bo fname = to_expression(combined ? combined->image : img) + "."; // Texture function and sampler - if (is_fetch) + if (args.base.is_fetch) fname += "read"; - else if (is_gather) + else if (args.base.is_gather) fname += "gather"; else fname += "sample"; - if (has_dref) + if (args.has_dref) fname += "_compare"; } @@ -7648,11 +8955,15 @@ static inline bool sampling_type_needs_f32_conversion(const SPIRType &type) } // Returns the function args for a texture sampling function for the specified image and sampling characteristics. -string CompilerMSL::to_function_args(VariableID img, const SPIRType &imgtype, bool is_fetch, bool is_gather, - bool is_proj, uint32_t coord, uint32_t, uint32_t dref, uint32_t grad_x, - uint32_t grad_y, uint32_t lod, uint32_t coffset, uint32_t offset, uint32_t bias, - uint32_t comp, uint32_t sample, uint32_t minlod, bool *p_forward) +string CompilerMSL::to_function_args(const TextureFunctionArguments &args, bool *p_forward) { + VariableID img = args.base.img; + auto &imgtype = *args.base.imgtype; + uint32_t lod = args.lod; + uint32_t grad_x = args.grad_x; + uint32_t grad_y = args.grad_y; + uint32_t bias = args.bias; + const MSLConstexprSampler *constexpr_sampler = nullptr; bool is_dynamic_img_sampler = false; if (auto *var = maybe_get_backing_variable(img)) @@ -7675,14 +8986,14 @@ string CompilerMSL::to_function_args(VariableID img, const SPIRType &imgtype, bo farg_str += join(", ", to_expression(img), plane_name_suffix, i); } else if ((!constexpr_sampler || !constexpr_sampler->ycbcr_conversion_enable) && - msl_options.swizzle_texture_samples && is_gather) + msl_options.swizzle_texture_samples && args.base.is_gather) { auto *combined = maybe_get(img); farg_str += to_expression(combined ? combined->image : img); } // Sampler reference - if (!is_fetch) + if (!args.base.is_fetch) { if (!farg_str.empty()) farg_str += ", "; @@ -7690,7 +9001,7 @@ string CompilerMSL::to_function_args(VariableID img, const SPIRType &imgtype, bo } if ((!constexpr_sampler || !constexpr_sampler->ycbcr_conversion_enable) && - msl_options.swizzle_texture_samples && is_gather) + msl_options.swizzle_texture_samples && args.base.is_gather) { // Add the swizzle constant from the swizzle buffer. farg_str += ", " + to_swizzle_expression(img); @@ -7699,17 +9010,17 @@ string CompilerMSL::to_function_args(VariableID img, const SPIRType &imgtype, bo // Swizzled gather puts the component before the other args, to allow template // deduction to work. - if (comp && msl_options.swizzle_texture_samples) + if (args.component && msl_options.swizzle_texture_samples) { - forward = should_forward(comp); - farg_str += ", " + to_component_argument(comp); + forward = should_forward(args.component); + farg_str += ", " + to_component_argument(args.component); } } // Texture coordinates - forward = forward && should_forward(coord); - auto coord_expr = to_enclosed_expression(coord); - auto &coord_type = expression_type(coord); + forward = forward && should_forward(args.coord); + auto coord_expr = to_enclosed_expression(args.coord); + auto &coord_type = expression_type(args.coord); bool coord_is_fp = type_is_floating_point(coord_type); bool is_cube_fetch = false; @@ -7723,14 +9034,14 @@ string CompilerMSL::to_function_args(VariableID img, const SPIRType &imgtype, bo if (coord_type.vecsize > 1) tex_coords = enclose_expression(tex_coords) + ".x"; - if (is_fetch) + if (args.base.is_fetch) tex_coords = "uint(" + round_fp_tex_coords(tex_coords, coord_is_fp) + ")"; else if (sampling_type_needs_f32_conversion(coord_type)) tex_coords = convert_to_f32(tex_coords, 1); if (msl_options.texture_1D_as_2D) { - if (is_fetch) + if (args.base.is_fetch) tex_coords = "uint2(" + tex_coords + ", 0)"; else tex_coords = "float2(" + tex_coords + ", 0.5)"; @@ -7751,7 +9062,7 @@ string CompilerMSL::to_function_args(VariableID img, const SPIRType &imgtype, bo { // Metal texel buffer textures are 2D, so convert 1D coord to 2D. // Support for Metal 2.1's new texture_buffer type. - if (is_fetch) + if (args.base.is_fetch) { if (msl_options.texel_buffer_texture_width > 0) { @@ -7771,17 +9082,15 @@ string CompilerMSL::to_function_args(VariableID img, const SPIRType &imgtype, bo case DimSubpassData: // If we're using Metal's native frame-buffer fetch API for subpass inputs, // this path will not be hit. - if (imgtype.image.ms) - tex_coords = "uint2(gl_FragCoord.xy)"; - else - tex_coords = join("uint2(gl_FragCoord.xy), 0"); + tex_coords = "uint2(gl_FragCoord.xy)"; + alt_coord_component = 2; break; case Dim2D: if (coord_type.vecsize > 2) tex_coords = enclose_expression(tex_coords) + ".xy"; - if (is_fetch) + if (args.base.is_fetch) tex_coords = "uint2(" + round_fp_tex_coords(tex_coords, coord_is_fp) + ")"; else if (sampling_type_needs_f32_conversion(coord_type)) tex_coords = convert_to_f32(tex_coords, 2); @@ -7793,7 +9102,7 @@ string CompilerMSL::to_function_args(VariableID img, const SPIRType &imgtype, bo if (coord_type.vecsize > 3) tex_coords = enclose_expression(tex_coords) + ".xyz"; - if (is_fetch) + if (args.base.is_fetch) tex_coords = "uint3(" + round_fp_tex_coords(tex_coords, coord_is_fp) + ")"; else if (sampling_type_needs_f32_conversion(coord_type)) tex_coords = convert_to_f32(tex_coords, 3); @@ -7802,7 +9111,7 @@ string CompilerMSL::to_function_args(VariableID img, const SPIRType &imgtype, bo break; case DimCube: - if (is_fetch) + if (args.base.is_fetch) { is_cube_fetch = true; tex_coords += ".xy"; @@ -7824,34 +9133,35 @@ string CompilerMSL::to_function_args(VariableID img, const SPIRType &imgtype, bo break; } - if (is_fetch && offset) + if (args.base.is_fetch && (args.offset || args.coffset)) { + uint32_t offset_expr = args.offset ? args.offset : args.coffset; // Fetch offsets must be applied directly to the coordinate. - forward = forward && should_forward(offset); - auto &type = expression_type(offset); - if (type.basetype != SPIRType::UInt) - tex_coords += " + " + bitcast_expression(SPIRType::UInt, offset); + forward = forward && should_forward(offset_expr); + auto &type = expression_type(offset_expr); + if (imgtype.image.dim == Dim1D && msl_options.texture_1D_as_2D) + { + if (type.basetype != SPIRType::UInt) + tex_coords += join(" + uint2(", bitcast_expression(SPIRType::UInt, offset_expr), ", 0)"); + else + tex_coords += join(" + uint2(", to_enclosed_expression(offset_expr), ", 0)"); + } else - tex_coords += " + " + to_enclosed_expression(offset); - } - else if (is_fetch && coffset) - { - // Fetch offsets must be applied directly to the coordinate. - forward = forward && should_forward(coffset); - auto &type = expression_type(coffset); - if (type.basetype != SPIRType::UInt) - tex_coords += " + " + bitcast_expression(SPIRType::UInt, coffset); - else - tex_coords += " + " + to_enclosed_expression(coffset); + { + if (type.basetype != SPIRType::UInt) + tex_coords += " + " + bitcast_expression(SPIRType::UInt, offset_expr); + else + tex_coords += " + " + to_enclosed_expression(offset_expr); + } } // If projection, use alt coord as divisor - if (is_proj) + if (args.base.is_proj) { if (sampling_type_needs_f32_conversion(coord_type)) - tex_coords += " / " + convert_to_f32(to_extract_component_expression(coord, alt_coord_component), 1); + tex_coords += " / " + convert_to_f32(to_extract_component_expression(args.coord, alt_coord_component), 1); else - tex_coords += " / " + to_extract_component_expression(coord, alt_coord_component); + tex_coords += " / " + to_extract_component_expression(args.coord, alt_coord_component); } if (!farg_str.empty()) @@ -7862,11 +9172,12 @@ string CompilerMSL::to_function_args(VariableID img, const SPIRType &imgtype, bo farg_str += "spvCubemapTo2DArrayFace(" + tex_coords + ").xy"; if (is_cube_fetch) - farg_str += ", uint(" + to_extract_component_expression(coord, 2) + ")"; + farg_str += ", uint(" + to_extract_component_expression(args.coord, 2) + ")"; else - farg_str += ", uint(spvCubemapTo2DArrayFace(" + tex_coords + ").z) + (uint(" + - round_fp_tex_coords(to_extract_component_expression(coord, alt_coord_component), coord_is_fp) + - ") * 6u)"; + farg_str += + ", uint(spvCubemapTo2DArrayFace(" + tex_coords + ").z) + (uint(" + + round_fp_tex_coords(to_extract_component_expression(args.coord, alt_coord_component), coord_is_fp) + + ") * 6u)"; add_spv_func_and_recompile(SPVFuncImplCubemapTo2DArrayFace); } @@ -7879,39 +9190,58 @@ string CompilerMSL::to_function_args(VariableID img, const SPIRType &imgtype, bo { // Special case for cube arrays, face and layer are packed in one dimension. if (imgtype.image.arrayed) - farg_str += ", uint(" + to_extract_component_expression(coord, 2) + ") % 6u"; + farg_str += ", uint(" + to_extract_component_expression(args.coord, 2) + ") % 6u"; else farg_str += - ", uint(" + round_fp_tex_coords(to_extract_component_expression(coord, 2), coord_is_fp) + ")"; + ", uint(" + round_fp_tex_coords(to_extract_component_expression(args.coord, 2), coord_is_fp) + ")"; } // If array, use alt coord if (imgtype.image.arrayed) { // Special case for cube arrays, face and layer are packed in one dimension. - if (imgtype.image.dim == DimCube && is_fetch) - farg_str += ", uint(" + to_extract_component_expression(coord, 2) + ") / 6u"; + if (imgtype.image.dim == DimCube && args.base.is_fetch) + { + farg_str += ", uint(" + to_extract_component_expression(args.coord, 2) + ") / 6u"; + } else + { farg_str += ", uint(" + - round_fp_tex_coords(to_extract_component_expression(coord, alt_coord_component), coord_is_fp) + ")"; + round_fp_tex_coords(to_extract_component_expression(args.coord, alt_coord_component), coord_is_fp) + + ")"; + if (imgtype.image.dim == DimSubpassData) + { + if (msl_options.multiview) + farg_str += " + gl_ViewIndex"; + else if (msl_options.arrayed_subpass_input) + farg_str += " + gl_Layer"; + } + } + } + else if (imgtype.image.dim == DimSubpassData) + { + if (msl_options.multiview) + farg_str += ", gl_ViewIndex"; + else if (msl_options.arrayed_subpass_input) + farg_str += ", gl_Layer"; } } // Depth compare reference value - if (dref) + if (args.dref) { - forward = forward && should_forward(dref); + forward = forward && should_forward(args.dref); farg_str += ", "; - auto &dref_type = expression_type(dref); + auto &dref_type = expression_type(args.dref); string dref_expr; - if (is_proj) - dref_expr = - join(to_enclosed_expression(dref), " / ", to_extract_component_expression(coord, alt_coord_component)); + if (args.base.is_proj) + dref_expr = join(to_enclosed_expression(args.dref), " / ", + to_extract_component_expression(args.coord, alt_coord_component)); else - dref_expr = to_expression(dref); + dref_expr = to_expression(args.dref); if (sampling_type_needs_f32_conversion(dref_type)) dref_expr = convert_to_f32(dref_expr, 1); @@ -7933,10 +9263,10 @@ string CompilerMSL::to_function_args(VariableID img, const SPIRType &imgtype, bo grad_y = 0; farg_str += ", level(0)"; } - else + else if (!msl_options.supports_msl_version(2, 3)) { SPIRV_CROSS_THROW("Using non-constant 0.0 gradient() qualifier for sample_compare. This is not " - "supported in MSL macOS."); + "supported on macOS prior to MSL 2.3."); } } @@ -7948,10 +9278,10 @@ string CompilerMSL::to_function_args(VariableID img, const SPIRType &imgtype, bo { bias = 0; } - else + else if (!msl_options.supports_msl_version(2, 3)) { - SPIRV_CROSS_THROW( - "Using non-constant 0.0 bias() qualifier for sample_compare. This is not supported in MSL macOS."); + SPIRV_CROSS_THROW("Using non-constant 0.0 bias() qualifier for sample_compare. This is not supported " + "on macOS prior to MSL 2.3."); } } } @@ -7968,7 +9298,7 @@ string CompilerMSL::to_function_args(VariableID img, const SPIRType &imgtype, bo if (lod && (imgtype.image.dim != Dim1D || msl_options.texture_1D_as_2D)) { forward = forward && should_forward(lod); - if (is_fetch) + if (args.base.is_fetch) { farg_str += ", " + to_expression(lod); } @@ -7977,7 +9307,7 @@ string CompilerMSL::to_function_args(VariableID img, const SPIRType &imgtype, bo farg_str += ", level(" + to_expression(lod) + ")"; } } - else if (is_fetch && !lod && (imgtype.image.dim != Dim1D || msl_options.texture_1D_as_2D) && + else if (args.base.is_fetch && !lod && (imgtype.image.dim != Dim1D || msl_options.texture_1D_as_2D) && imgtype.image.dim != DimBuffer && !imgtype.image.ms && imgtype.image.sampled != 2) { // Lod argument is optional in OpImageFetch, but we require a LOD value, pick 0 as the default. @@ -7993,6 +9323,7 @@ string CompilerMSL::to_function_args(VariableID img, const SPIRType &imgtype, bo string grad_opt; switch (imgtype.image.dim) { + case Dim1D: case Dim2D: grad_opt = "2d"; break; @@ -8012,46 +9343,53 @@ string CompilerMSL::to_function_args(VariableID img, const SPIRType &imgtype, bo farg_str += ", gradient" + grad_opt + "(" + to_expression(grad_x) + ", " + to_expression(grad_y) + ")"; } - if (minlod) + if (args.min_lod) { - if (msl_options.is_macos()) - { - if (!msl_options.supports_msl_version(2, 2)) - SPIRV_CROSS_THROW("min_lod_clamp() is only supported in MSL 2.2+ and up on macOS."); - } - else if (msl_options.is_ios()) - SPIRV_CROSS_THROW("min_lod_clamp() is not supported on iOS."); + if (!msl_options.supports_msl_version(2, 2)) + SPIRV_CROSS_THROW("min_lod_clamp() is only supported in MSL 2.2+ and up."); - forward = forward && should_forward(minlod); - farg_str += ", min_lod_clamp(" + to_expression(minlod) + ")"; + forward = forward && should_forward(args.min_lod); + farg_str += ", min_lod_clamp(" + to_expression(args.min_lod) + ")"; } // Add offsets string offset_expr; - if (coffset && !is_fetch) + const SPIRType *offset_type = nullptr; + if (args.coffset && !args.base.is_fetch) { - forward = forward && should_forward(coffset); - offset_expr = to_expression(coffset); + forward = forward && should_forward(args.coffset); + offset_expr = to_expression(args.coffset); + offset_type = &expression_type(args.coffset); } - else if (offset && !is_fetch) + else if (args.offset && !args.base.is_fetch) { - forward = forward && should_forward(offset); - offset_expr = to_expression(offset); + forward = forward && should_forward(args.offset); + offset_expr = to_expression(args.offset); + offset_type = &expression_type(args.offset); } if (!offset_expr.empty()) { switch (imgtype.image.dim) { + case Dim1D: + if (!msl_options.texture_1D_as_2D) + break; + if (offset_type->vecsize > 1) + offset_expr = enclose_expression(offset_expr) + ".x"; + + farg_str += join(", int2(", offset_expr, ", 0)"); + break; + case Dim2D: - if (coord_type.vecsize > 2) + if (offset_type->vecsize > 2) offset_expr = enclose_expression(offset_expr) + ".xy"; farg_str += ", " + offset_expr; break; case Dim3D: - if (coord_type.vecsize > 3) + if (offset_type->vecsize > 3) offset_expr = enclose_expression(offset_expr) + ".xyz"; farg_str += ", " + offset_expr; @@ -8062,7 +9400,7 @@ string CompilerMSL::to_function_args(VariableID img, const SPIRType &imgtype, bo } } - if (comp) + if (args.component) { // If 2D has gather component, ensure it also has an offset arg if (imgtype.image.dim == Dim2D && offset_expr.empty()) @@ -8070,16 +9408,29 @@ string CompilerMSL::to_function_args(VariableID img, const SPIRType &imgtype, bo if (!msl_options.swizzle_texture_samples || is_dynamic_img_sampler) { - forward = forward && should_forward(comp); - farg_str += ", " + to_component_argument(comp); + forward = forward && should_forward(args.component); + + uint32_t image_var = 0; + if (const auto *combined = maybe_get(img)) + { + if (const auto *img_var = maybe_get_backing_variable(combined->image)) + image_var = img_var->self; + } + else if (const auto *var = maybe_get_backing_variable(img)) + { + image_var = var->self; + } + + if (image_var == 0 || !image_is_comparison(expression_type(image_var), image_var)) + farg_str += ", " + to_component_argument(args.component); } } - if (sample) + if (args.sample) { - forward = forward && should_forward(sample); + forward = forward && should_forward(args.sample); farg_str += ", "; - farg_str += to_expression(sample); + farg_str += to_expression(args.sample); } *p_forward = forward; @@ -8097,13 +9448,7 @@ string CompilerMSL::round_fp_tex_coords(string tex_coords, bool coord_is_fp) // The ID must be a scalar constant. string CompilerMSL::to_component_argument(uint32_t id) { - if (ir.ids[id].get_type() != TypeConstant) - { - SPIRV_CROSS_THROW("ID " + to_string(id) + " is not an OpConstant."); - return "component::x"; - } - - uint32_t component_index = get(id).scalar(); + uint32_t component_index = evaluate_constant_u32(id); switch (component_index) { case 0: @@ -8128,7 +9473,8 @@ void CompilerMSL::emit_sampled_image_op(uint32_t result_type, uint32_t result_id set(result_id, result_type, image_id, samp_id); } -string CompilerMSL::to_texture_op(const Instruction &i, bool *forward, SmallVector &inherited_expressions) +string CompilerMSL::to_texture_op(const Instruction &i, bool sparse, bool *forward, + SmallVector &inherited_expressions) { auto *ops = stream(i); uint32_t result_type_id = ops[0]; @@ -8156,6 +9502,10 @@ string CompilerMSL::to_texture_op(const Instruction &i, bool *forward, SmallVect // processing. switch (constexpr_sampler->ycbcr_model) { + case MSL_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY: + case MSL_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY: + // Default + break; case MSL_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_BT_709: add_spv_func_and_recompile(SPVFuncImplConvertYCbCrBT709); expr += "spvConvertYCbCrBT709("; @@ -8196,7 +9546,7 @@ string CompilerMSL::to_texture_op(const Instruction &i, bool *forward, SmallVect expr += "spvTextureSwizzle("; } - string inner_expr = CompilerGLSL::to_texture_op(i, forward, inherited_expressions); + string inner_expr = CompilerGLSL::to_texture_op(i, sparse, forward, inherited_expressions); if (constexpr_sampler && constexpr_sampler->ycbcr_conversion_enable && !is_dynamic_img_sampler) { @@ -8502,9 +9852,9 @@ string CompilerMSL::to_swizzle_expression(uint32_t id) auto index = expr.find_first_of('['); // If an image is part of an argument buffer translate this to a legal identifier. - for (auto &c : expr) - if (c == '.') - c = '_'; + string::size_type period = 0; + while ((period = expr.find_first_of('.', period)) != string::npos && period < index) + expr[period] = '_'; if (index == string::npos) return expr + swizzle_name_suffix; @@ -8592,9 +9942,7 @@ string CompilerMSL::convert_row_major_matrix(string exp_str, const SPIRType &exp // Called automatically at the end of the entry point function void CompilerMSL::emit_fixup() { - if ((get_execution_model() == ExecutionModelVertex || - get_execution_model() == ExecutionModelTessellationEvaluation) && - stage_out_var_id && !qual_pos_var_name.empty() && !capture_output_to_buffer) + if (is_vertex_like_shader() && stage_out_var_id && !qual_pos_var_name.empty() && !capture_output_to_buffer) { if (options.vertex.fixup_clipspace) statement(qual_pos_var_name, ".z = (", qual_pos_var_name, ".z + ", qual_pos_var_name, @@ -8692,7 +10040,14 @@ string CompilerMSL::to_struct_member(const SPIRType &type, uint32_t member_type_ physical_type.basetype != SPIRType::SampledImage) { BuiltIn builtin = BuiltInMax; - if (is_member_builtin(type, index, &builtin)) + + // Special handling. In [[stage_out]] or [[stage_in]] blocks, + // we need flat arrays, but if we're somehow declaring gl_PerVertex for constant array reasons, we want + // template array types to be declared. + bool is_ib_in_out = + ((stage_out_var_id && get_stage_out_struct_type().self == type.self) || + (stage_in_var_id && get_stage_in_struct_type().self == type.self)); + if (is_ib_in_out && is_member_builtin(type, index, &builtin)) is_using_builtin_array = true; array_type = type_to_array_glsl(physical_type); } @@ -8766,6 +10121,8 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in case BuiltInInstanceId: case BuiltInInstanceIndex: case BuiltInBaseInstance: + if (msl_options.vertex_for_tessellation) + return ""; return string(" [[") + builtin_qualifier(builtin) + "]]"; case BuiltInDrawIndex: @@ -8775,13 +10132,20 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in return ""; } } - uint32_t locn = get_ordered_member_location(type.self, index); + + uint32_t locn; + if (is_builtin) + locn = get_or_allocate_builtin_input_member_location(builtin, type.self, index); + else + locn = get_member_location(type.self, index); + if (locn != k_unknown_location) return string(" [[attribute(") + convert_to_string(locn) + ")]]"; } // Vertex and tessellation evaluation function outputs - if ((execution.model == ExecutionModelVertex || execution.model == ExecutionModelTessellationEvaluation) && + if (((execution.model == ExecutionModelVertex && !msl_options.vertex_for_tessellation) || + execution.model == ExecutionModelTessellationEvaluation) && type.storage == StorageClassOutput) { if (is_builtin) @@ -8814,7 +10178,7 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in } } uint32_t comp; - uint32_t locn = get_ordered_member_location(type.self, index, &comp); + uint32_t locn = get_member_location(type.self, index, &comp); if (locn != k_unknown_location) { if (comp != k_unknown_component) @@ -8833,8 +10197,13 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in { case BuiltInInvocationId: case BuiltInPrimitiveId: + if (msl_options.multi_patch_workgroup) + return ""; + return string(" [[") + builtin_qualifier(builtin) + "]]" + (mbr_type.array.empty() ? "" : " "); case BuiltInSubgroupLocalInvocationId: // FIXME: Should work in any stage case BuiltInSubgroupSize: // FIXME: Should work in any stage + if (msl_options.emulate_subgroups) + return ""; return string(" [[") + builtin_qualifier(builtin) + "]]" + (mbr_type.array.empty() ? "" : " "); case BuiltInPatchVertices: return ""; @@ -8843,7 +10212,15 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in break; } } - uint32_t locn = get_ordered_member_location(type.self, index); + if (msl_options.multi_patch_workgroup) + return ""; + + uint32_t locn; + if (is_builtin) + locn = get_or_allocate_builtin_input_member_location(builtin, type.self, index); + else + locn = get_member_location(type.self, index); + if (locn != k_unknown_location) return string(" [[attribute(") + convert_to_string(locn) + ")]]"; } @@ -8876,7 +10253,13 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in // The special control point array must not be marked with an attribute. if (get_type(type.member_types[index]).basetype == SPIRType::ControlPointArray) return ""; - uint32_t locn = get_ordered_member_location(type.self, index); + + uint32_t locn; + if (is_builtin) + locn = get_or_allocate_builtin_input_member_location(builtin, type.self, index); + else + locn = get_member_location(type.self, index); + if (locn != k_unknown_location) return string(" [[attribute(") + convert_to_string(locn) + ")]]"; } @@ -8892,7 +10275,7 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in switch (builtin) { case BuiltInViewIndex: - if (!msl_options.multiview) + if (!msl_options.multiview || !msl_options.multiview_layered_rendering) break; /* fallthrough */ case BuiltInFrontFacing: @@ -8916,7 +10299,7 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in else { uint32_t comp; - uint32_t locn = get_ordered_member_location(type.self, index, &comp); + uint32_t locn = get_member_location(type.self, index, &comp); if (locn != k_unknown_location) { // For user-defined attributes, this is fine. From Vulkan spec: @@ -9018,7 +10401,7 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in return ""; } } - uint32_t locn = get_ordered_member_location(type.self, index); + uint32_t locn = get_member_location(type.self, index); // Metal will likely complain about missing color attachments, too. if (locn != k_unknown_location && !(msl_options.enable_frag_output_mask & (1 << locn))) return ""; @@ -9040,15 +10423,18 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in { switch (builtin) { + case BuiltInNumSubgroups: + case BuiltInSubgroupId: + case BuiltInSubgroupLocalInvocationId: // FIXME: Should work in any stage + case BuiltInSubgroupSize: // FIXME: Should work in any stage + if (msl_options.emulate_subgroups) + break; + /* fallthrough */ case BuiltInGlobalInvocationId: case BuiltInWorkgroupId: case BuiltInNumWorkgroups: case BuiltInLocalInvocationId: case BuiltInLocalInvocationIndex: - case BuiltInNumSubgroups: - case BuiltInSubgroupId: - case BuiltInSubgroupLocalInvocationId: // FIXME: Should work in any stage - case BuiltInSubgroupSize: // FIXME: Should work in any stage return string(" [[") + builtin_qualifier(builtin) + "]]"; default: @@ -9064,24 +10450,61 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in // If the location of the member has been explicitly set, that location is used. If not, this // function assumes the members are ordered in their location order, and simply returns the // index as the location. -uint32_t CompilerMSL::get_ordered_member_location(uint32_t type_id, uint32_t index, uint32_t *comp) +uint32_t CompilerMSL::get_member_location(uint32_t type_id, uint32_t index, uint32_t *comp) const { - auto &m = ir.meta[type_id]; - if (index < m.members.size()) + if (comp) { - auto &dec = m.members[index]; - if (comp) - { - if (dec.decoration_flags.get(DecorationComponent)) - *comp = dec.component; - else - *comp = k_unknown_component; - } - if (dec.decoration_flags.get(DecorationLocation)) - return dec.location; + if (has_member_decoration(type_id, index, DecorationComponent)) + *comp = get_member_decoration(type_id, index, DecorationComponent); + else + *comp = k_unknown_component; } - return index; + if (has_member_decoration(type_id, index, DecorationLocation)) + return get_member_decoration(type_id, index, DecorationLocation); + else + return k_unknown_location; +} + +uint32_t CompilerMSL::get_or_allocate_builtin_input_member_location(spv::BuiltIn builtin, + uint32_t type_id, uint32_t index, + uint32_t *comp) +{ + uint32_t loc = get_member_location(type_id, index, comp); + if (loc != k_unknown_location) + return loc; + + if (comp) + *comp = k_unknown_component; + + // Late allocation. Find a location which is unused by the application. + // This can happen for built-in inputs in tessellation which are mixed and matched with user inputs. + auto &mbr_type = get(get(type_id).member_types[index]); + uint32_t count = type_to_location_count(mbr_type); + + // This should always be 1. + if (count != 1) + return k_unknown_location; + + loc = 0; + while (location_inputs_in_use.count(loc) != 0) + loc++; + + set_member_decoration(type_id, index, DecorationLocation, loc); + + // Triangle tess level inputs are shared in one packed float4, + // mark both builtins as sharing one location. + if (get_execution_mode_bitset().get(ExecutionModeTriangles) && + (builtin == BuiltInTessLevelInner || builtin == BuiltInTessLevelOuter)) + { + builtin_to_automatic_input_location[BuiltInTessLevelInner] = loc; + builtin_to_automatic_input_location[BuiltInTessLevelOuter] = loc; + } + else + builtin_to_automatic_input_location[builtin] = loc; + + mark_location_as_used_by_shader(loc, mbr_type, StorageClassInput); + return loc; } // Returns the type declaration for a function, including the @@ -9104,7 +10527,9 @@ string CompilerMSL::func_type_decl(SPIRType &type) switch (execution.model) { case ExecutionModelVertex: - entry_type = "vertex"; + if (msl_options.vertex_for_tessellation && !msl_options.supports_msl_version(1, 2)) + SPIRV_CROSS_THROW("Tessellation requires Metal 1.2."); + entry_type = msl_options.vertex_for_tessellation ? "kernel" : "vertex"; break; case ExecutionModelTessellationEvaluation: if (!msl_options.supports_msl_version(1, 2)) @@ -9208,7 +10633,9 @@ string CompilerMSL::get_type_address_space(const SPIRType &type, uint32_t id, bo case StorageClassInput: if (get_execution_model() == ExecutionModelTessellationControl && var && var->basevariable == stage_in_ptr_var_id) - addr_space = "threadgroup"; + addr_space = msl_options.multi_patch_workgroup ? "constant" : "threadgroup"; + if (get_execution_model() == ExecutionModelFragment && var && var->basevariable == stage_in_var_id) + addr_space = "thread"; break; case StorageClassOutput: @@ -9221,8 +10648,10 @@ string CompilerMSL::get_type_address_space(const SPIRType &type, uint32_t id, bo } if (!addr_space) + { // No address space for plain values. addr_space = type.pointer || (argument && type.basetype == SPIRType::ControlPointArray) ? "thread" : ""; + } return join(flags.get(DecorationVolatile) || flags.get(DecorationCoherent) ? "volatile " : "", addr_space); } @@ -9251,6 +10680,9 @@ string CompilerMSL::entry_point_arg_stage_in() { string decl; + if (get_execution_model() == ExecutionModelTessellationControl && msl_options.multi_patch_workgroup) + return decl; + // Stage-in structure uint32_t stage_in_id; if (get_execution_model() == ExecutionModelTessellationEvaluation) @@ -9270,16 +10702,89 @@ string CompilerMSL::entry_point_arg_stage_in() return decl; } +// Returns true if this input builtin should be a direct parameter on a shader function parameter list, +// and false for builtins that should be passed or calculated some other way. +bool CompilerMSL::is_direct_input_builtin(BuiltIn bi_type) +{ + switch (bi_type) + { + // Vertex function in + case BuiltInVertexId: + case BuiltInVertexIndex: + case BuiltInBaseVertex: + case BuiltInInstanceId: + case BuiltInInstanceIndex: + case BuiltInBaseInstance: + return get_execution_model() != ExecutionModelVertex || !msl_options.vertex_for_tessellation; + // Tess. control function in + case BuiltInPosition: + case BuiltInPointSize: + case BuiltInClipDistance: + case BuiltInCullDistance: + case BuiltInPatchVertices: + return false; + case BuiltInInvocationId: + case BuiltInPrimitiveId: + return get_execution_model() != ExecutionModelTessellationControl || !msl_options.multi_patch_workgroup; + // Tess. evaluation function in + case BuiltInTessLevelInner: + case BuiltInTessLevelOuter: + return false; + // Fragment function in + case BuiltInSamplePosition: + case BuiltInHelperInvocation: + case BuiltInBaryCoordNV: + case BuiltInBaryCoordNoPerspNV: + return false; + case BuiltInViewIndex: + return get_execution_model() == ExecutionModelFragment && msl_options.multiview && + msl_options.multiview_layered_rendering; + // Compute function in + case BuiltInSubgroupId: + case BuiltInNumSubgroups: + return !msl_options.emulate_subgroups; + // Any stage function in + case BuiltInDeviceIndex: + case BuiltInSubgroupEqMask: + case BuiltInSubgroupGeMask: + case BuiltInSubgroupGtMask: + case BuiltInSubgroupLeMask: + case BuiltInSubgroupLtMask: + return false; + case BuiltInSubgroupSize: + if (msl_options.fixed_subgroup_size != 0) + return false; + /* fallthrough */ + case BuiltInSubgroupLocalInvocationId: + return !msl_options.emulate_subgroups; + default: + return true; + } +} + +// Returns true if this is a fragment shader that runs per sample, and false otherwise. +bool CompilerMSL::is_sample_rate() const +{ + auto &caps = get_declared_capabilities(); + return get_execution_model() == ExecutionModelFragment && + (msl_options.force_sample_rate_shading || + std::find(caps.begin(), caps.end(), CapabilitySampleRateShading) != caps.end() || + (msl_options.use_framebuffer_fetch_subpasses && need_subpass_input)); +} + void CompilerMSL::entry_point_args_builtin(string &ep_args) { // Builtin variables SmallVector, 8> active_builtins; ir.for_each_typed_id([&](uint32_t var_id, SPIRVariable &var) { + if (var.storage != StorageClassInput) + return; + auto bi_type = BuiltIn(get_decoration(var_id, DecorationBuiltIn)); // Don't emit SamplePosition as a separate parameter. In the entry // point, we get that by calling get_sample_position() on the sample ID. - if (var.storage == StorageClassInput && is_builtin_variable(var) && + if (is_builtin_variable(var) && get_variable_data_type(var).basetype != SPIRType::Struct && get_variable_data_type(var).basetype != SPIRType::ControlPointArray) { @@ -9291,20 +10796,7 @@ void CompilerMSL::entry_point_args_builtin(string &ep_args) // Remember this variable. We may need to correct its type. active_builtins.push_back(make_pair(&var, bi_type)); - // These builtins are emitted specially. If we pass this branch, the builtin directly matches - // a MSL builtin. - if (bi_type != BuiltInSamplePosition && bi_type != BuiltInHelperInvocation && - bi_type != BuiltInPatchVertices && bi_type != BuiltInTessLevelInner && - bi_type != BuiltInTessLevelOuter && bi_type != BuiltInPosition && bi_type != BuiltInPointSize && - bi_type != BuiltInClipDistance && bi_type != BuiltInCullDistance && bi_type != BuiltInSubgroupEqMask && - bi_type != BuiltInBaryCoordNV && bi_type != BuiltInBaryCoordNoPerspNV && - bi_type != BuiltInSubgroupGeMask && bi_type != BuiltInSubgroupGtMask && - bi_type != BuiltInSubgroupLeMask && bi_type != BuiltInSubgroupLtMask && bi_type != BuiltInDeviceIndex && - ((get_execution_model() == ExecutionModelFragment && msl_options.multiview) || - bi_type != BuiltInViewIndex) && - (get_execution_model() == ExecutionModelGLCompute || - (get_execution_model() == ExecutionModelFragment && msl_options.supports_msl_version(2, 2)) || - (bi_type != BuiltInSubgroupLocalInvocationId && bi_type != BuiltInSubgroupSize))) + if (is_direct_input_builtin(bi_type)) { if (!ep_args.empty()) ep_args += ", "; @@ -9316,9 +10808,9 @@ void CompilerMSL::entry_point_args_builtin(string &ep_args) if (bi_type == BuiltInSampleMask && get_entry_point().flags.get(ExecutionModePostDepthCoverage)) { if (!msl_options.supports_msl_version(2)) - SPIRV_CROSS_THROW("Post-depth coverage requires Metal 2.0."); - if (!msl_options.is_ios()) - SPIRV_CROSS_THROW("Post-depth coverage is only supported on iOS."); + SPIRV_CROSS_THROW("Post-depth coverage requires MSL 2.0."); + if (msl_options.is_macos() && !msl_options.supports_msl_version(2, 3)) + SPIRV_CROSS_THROW("Post-depth coverage on Mac requires MSL 2.3."); ep_args += ", post_depth_coverage"; } ep_args += "]]"; @@ -9326,11 +10818,10 @@ void CompilerMSL::entry_point_args_builtin(string &ep_args) } } - if (var.storage == StorageClassInput && - has_extended_decoration(var_id, SPIRVCrossDecorationBuiltInDispatchBase)) + if (has_extended_decoration(var_id, SPIRVCrossDecorationBuiltInDispatchBase)) { // This is a special implicit builtin, not corresponding to any SPIR-V builtin, - // which holds the base that was passed to vkCmdDispatchBase(). If it's present, + // which holds the base that was passed to vkCmdDispatchBase() or vkCmdDrawIndexed(). If it's present, // assume we emitted it for a good reason. assert(msl_options.supports_msl_version(1, 2)); if (!ep_args.empty()) @@ -9338,6 +10829,18 @@ void CompilerMSL::entry_point_args_builtin(string &ep_args) ep_args += type_to_glsl(get_variable_data_type(var)) + " " + to_expression(var_id) + " [[grid_origin]]"; } + + if (has_extended_decoration(var_id, SPIRVCrossDecorationBuiltInStageInputSize)) + { + // This is another special implicit builtin, not corresponding to any SPIR-V builtin, + // which holds the number of vertices and instances to draw. If it's present, + // assume we emitted it for a good reason. + assert(msl_options.supports_msl_version(1, 2)); + if (!ep_args.empty()) + ep_args += ", "; + + ep_args += type_to_glsl(get_variable_data_type(var)) + " " + to_expression(var_id) + " [[grid_size]]"; + } }); // Correct the types of all encountered active builtins. We couldn't do this before @@ -9372,7 +10875,8 @@ void CompilerMSL::entry_point_args_builtin(string &ep_args) ep_args += join("constant uint* spvIndirectParams [[buffer(", msl_options.indirect_params_buffer_index, ")]]"); } - else if (stage_out_var_id) + else if (stage_out_var_id && + !(get_execution_model() == ExecutionModelVertex && msl_options.vertex_for_tessellation)) { if (!ep_args.empty()) ep_args += ", "; @@ -9380,6 +10884,28 @@ void CompilerMSL::entry_point_args_builtin(string &ep_args) join("device uint* spvIndirectParams [[buffer(", msl_options.indirect_params_buffer_index, ")]]"); } + if (get_execution_model() == ExecutionModelVertex && msl_options.vertex_for_tessellation && + (active_input_builtins.get(BuiltInVertexIndex) || active_input_builtins.get(BuiltInVertexId)) && + msl_options.vertex_index_type != Options::IndexType::None) + { + // Add the index buffer so we can set gl_VertexIndex correctly. + if (!ep_args.empty()) + ep_args += ", "; + switch (msl_options.vertex_index_type) + { + case Options::IndexType::None: + break; + case Options::IndexType::UInt16: + ep_args += join("const device ushort* ", index_buffer_var_name, " [[buffer(", + msl_options.shader_index_buffer_index, ")]]"); + break; + case Options::IndexType::UInt32: + ep_args += join("const device uint* ", index_buffer_var_name, " [[buffer(", + msl_options.shader_index_buffer_index, ")]]"); + break; + } + } + // Tessellation control shaders get three additional parameters: // a buffer to hold the per-patch data, a buffer to hold the per-patch // tessellation levels, and a block of workgroup memory to hold the @@ -9398,12 +10924,72 @@ void CompilerMSL::entry_point_args_builtin(string &ep_args) ep_args += ", "; ep_args += join("device ", get_tess_factor_struct_name(), "* ", tess_factor_buffer_var_name, " [[buffer(", convert_to_string(msl_options.shader_tess_factor_buffer_index), ")]]"); + + // Initializer for tess factors must be handled specially since it's never declared as a normal variable. + uint32_t outer_factor_initializer_id = 0; + uint32_t inner_factor_initializer_id = 0; + ir.for_each_typed_id([&](uint32_t, SPIRVariable &var) { + if (!has_decoration(var.self, DecorationBuiltIn) || var.storage != StorageClassOutput || !var.initializer) + return; + + BuiltIn builtin = BuiltIn(get_decoration(var.self, DecorationBuiltIn)); + if (builtin == BuiltInTessLevelInner) + inner_factor_initializer_id = var.initializer; + else if (builtin == BuiltInTessLevelOuter) + outer_factor_initializer_id = var.initializer; + }); + + const SPIRConstant *c = nullptr; + + if (outer_factor_initializer_id && (c = maybe_get(outer_factor_initializer_id))) + { + auto &entry_func = get(ir.default_entry_point); + entry_func.fixup_hooks_in.push_back([=]() { + uint32_t components = get_execution_mode_bitset().get(ExecutionModeTriangles) ? 3 : 4; + for (uint32_t i = 0; i < components; i++) + { + statement(builtin_to_glsl(BuiltInTessLevelOuter, StorageClassOutput), "[", i, "] = ", + "half(", to_expression(c->subconstants[i]), ");"); + } + }); + } + + if (inner_factor_initializer_id && (c = maybe_get(inner_factor_initializer_id))) + { + auto &entry_func = get(ir.default_entry_point); + if (get_execution_mode_bitset().get(ExecutionModeTriangles)) + { + entry_func.fixup_hooks_in.push_back([=]() { + statement(builtin_to_glsl(BuiltInTessLevelInner, StorageClassOutput), " = ", "half(", + to_expression(c->subconstants[0]), ");"); + }); + } + else + { + entry_func.fixup_hooks_in.push_back([=]() { + for (uint32_t i = 0; i < 2; i++) + { + statement(builtin_to_glsl(BuiltInTessLevelInner, StorageClassOutput), "[", i, "] = ", + "half(", to_expression(c->subconstants[i]), ");"); + } + }); + } + } + if (stage_in_var_id) { if (!ep_args.empty()) ep_args += ", "; - ep_args += join("threadgroup ", type_to_glsl(get_stage_in_struct_type()), "* ", input_wg_var_name, - " [[threadgroup(", convert_to_string(msl_options.shader_input_wg_index), ")]]"); + if (msl_options.multi_patch_workgroup) + { + ep_args += join("device ", type_to_glsl(get_stage_in_struct_type()), "* ", input_buffer_var_name, + " [[buffer(", convert_to_string(msl_options.shader_input_buffer_index), ")]]"); + } + else + { + ep_args += join("threadgroup ", type_to_glsl(get_stage_in_struct_type()), "* ", input_wg_var_name, + " [[threadgroup(", convert_to_string(msl_options.shader_input_wg_index), ")]]"); + } } } } @@ -9508,12 +11094,7 @@ void CompilerMSL::entry_point_args_discrete_descriptors(string &ep_args) { auto &type = get_variable_data_type(var); - // Very specifically, image load-store in argument buffers are disallowed on MSL on iOS. - // But we won't know when the argument buffer is encoded whether this image will have - // a NonWritable decoration. So just use discrete arguments for all storage images - // on iOS. - if (!(msl_options.is_ios() && type.basetype == SPIRType::Image && type.image.sampled == 2) && - var.storage != StorageClassPushConstant) + if (is_supported_argument_buffer_type(type) && var.storage != StorageClassPushConstant) { uint32_t desc_set = get_decoration(var_id, DecorationDescriptorSet); if (descriptor_set_is_argument_buffer(desc_set)) @@ -9629,7 +11210,7 @@ void CompilerMSL::entry_point_args_discrete_descriptors(string &ep_args) case SPIRType::Sampler: if (!ep_args.empty()) ep_args += ", "; - ep_args += sampler_type(type) + " " + r.name; + ep_args += sampler_type(type, var_id) + " " + r.name; ep_args += " [[sampler(" + convert_to_string(r.index) + ")]]"; break; case SPIRType::Image: @@ -9651,6 +11232,8 @@ void CompilerMSL::entry_point_args_discrete_descriptors(string &ep_args) } else { + if (msl_options.is_macos() && !msl_options.supports_msl_version(2, 3)) + SPIRV_CROSS_THROW("Framebuffer fetch on Mac is not supported before MSL 2.3."); ep_args += image_type_glsl(type, var_id) + " " + r.name; ep_args += " [[color(" + convert_to_string(r.index) + ")]]"; } @@ -9660,7 +11243,10 @@ void CompilerMSL::entry_point_args_discrete_descriptors(string &ep_args) { ep_args += ", device atomic_" + type_to_glsl(get(basetype.image.type), 0); ep_args += "* " + r.name + "_atomic"; - ep_args += " [[buffer(" + convert_to_string(r.secondary_index) + ")]]"; + ep_args += " [[buffer(" + convert_to_string(r.secondary_index) + ")"; + if (interlocked_resources.count(var_id)) + ep_args += ", raster_order_group(0)"; + ep_args += "]]"; } break; } @@ -9697,6 +11283,21 @@ string CompilerMSL::entry_point_args_classic(bool append_comma) void CompilerMSL::fix_up_shader_inputs_outputs() { + auto &entry_func = this->get(ir.default_entry_point); + + // Emit a guard to ensure we don't execute beyond the last vertex. + // Vertex shaders shouldn't have the problems with barriers in non-uniform control flow that + // tessellation control shaders do, so early returns should be OK. We may need to revisit this + // if it ever becomes possible to use barriers from a vertex shader. + if (get_execution_model() == ExecutionModelVertex && msl_options.vertex_for_tessellation) + { + entry_func.fixup_hooks_in.push_back([this]() { + statement("if (any(", to_expression(builtin_invocation_id_id), + " >= ", to_expression(builtin_stage_input_size_id), "))"); + statement(" return;"); + }); + } + // Look for sampled images and buffer. Add hooks to set up the swizzle constants or array lengths. ir.for_each_typed_id([&](uint32_t, SPIRVariable &var) { auto &type = get_variable_data_type(var); @@ -9707,7 +11308,6 @@ void CompilerMSL::fix_up_shader_inputs_outputs() { if (msl_options.swizzle_texture_samples && has_sampled_images && is_sampled_image_type(type)) { - auto &entry_func = this->get(ir.default_entry_point); entry_func.fixup_hooks_in.push_back([this, &type, &var, var_id]() { bool is_array_type = !type.array.empty(); @@ -9734,7 +11334,6 @@ void CompilerMSL::fix_up_shader_inputs_outputs() { if (buffers_requiring_array_length.count(var.self)) { - auto &entry_func = this->get(ir.default_entry_point); entry_func.fixup_hooks_in.push_back([this, &type, &var, var_id]() { bool is_array_type = !type.array.empty(); @@ -9759,13 +11358,17 @@ void CompilerMSL::fix_up_shader_inputs_outputs() }); // Builtin variables - ir.for_each_typed_id([&](uint32_t, SPIRVariable &var) { + ir.for_each_typed_id([this, &entry_func](uint32_t, SPIRVariable &var) { uint32_t var_id = var.self; BuiltIn bi_type = ir.meta[var_id].decoration.builtin_type; - if (var.storage == StorageClassInput && is_builtin_variable(var)) + if (var.storage != StorageClassInput && var.storage != StorageClassOutput) + return; + if (!interface_variable_exists_in_entry_point(var.self)) + return; + + if (var.storage == StorageClassInput && is_builtin_variable(var) && active_input_builtins.get(bi_type)) { - auto &entry_func = this->get(ir.default_entry_point); switch (bi_type) { case BuiltInSamplePosition: @@ -9774,9 +11377,18 @@ void CompilerMSL::fix_up_shader_inputs_outputs() to_expression(builtin_sample_id_id), ");"); }); break; + case BuiltInFragCoord: + if (is_sample_rate()) + { + entry_func.fixup_hooks_in.push_back([=]() { + statement(to_expression(var_id), ".xy += get_sample_position(", + to_expression(builtin_sample_id_id), ") - 0.5;"); + }); + } + break; case BuiltInHelperInvocation: - if (msl_options.is_ios()) - SPIRV_CROSS_THROW("simd_is_helper_thread() is only supported on macOS."); + if (msl_options.is_ios() && !msl_options.supports_msl_version(2, 3)) + SPIRV_CROSS_THROW("simd_is_helper_thread() requires version 2.3 on iOS."); else if (msl_options.is_macos() && !msl_options.supports_msl_version(2, 1)) SPIRV_CROSS_THROW("simd_is_helper_thread() requires version 2.1 on macOS."); @@ -9784,6 +11396,29 @@ void CompilerMSL::fix_up_shader_inputs_outputs() statement(builtin_type_decl(bi_type), " ", to_expression(var_id), " = simd_is_helper_thread();"); }); break; + case BuiltInInvocationId: + // This is direct-mapped without multi-patch workgroups. + if (get_execution_model() != ExecutionModelTessellationControl || !msl_options.multi_patch_workgroup) + break; + + entry_func.fixup_hooks_in.push_back([=]() { + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), " = ", + to_expression(builtin_invocation_id_id), ".x % ", this->get_entry_point().output_vertices, + ";"); + }); + break; + case BuiltInPrimitiveId: + // This is natively supported by fragment and tessellation evaluation shaders. + // In tessellation control shaders, this is direct-mapped without multi-patch workgroups. + if (get_execution_model() != ExecutionModelTessellationControl || !msl_options.multi_patch_workgroup) + break; + + entry_func.fixup_hooks_in.push_back([=]() { + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), " = min(", + to_expression(builtin_invocation_id_id), ".x / ", this->get_entry_point().output_vertices, + ", spvIndirectParams[1]);"); + }); + break; case BuiltInPatchVertices: if (get_execution_model() == ExecutionModelTessellationEvaluation) entry_func.fixup_hooks_in.push_back([=]() { @@ -9804,136 +11439,236 @@ void CompilerMSL::fix_up_shader_inputs_outputs() entry_func.fixup_hooks_in.push_back([=]() { statement(tc, ".y = 1.0 - ", tc, ".y;"); }); } break; - case BuiltInSubgroupLocalInvocationId: - // This is natively supported in compute shaders. - if (get_execution_model() == ExecutionModelGLCompute) + case BuiltInSubgroupId: + if (!msl_options.emulate_subgroups) break; - - // This is natively supported in fragment shaders in MSL 2.2. - if (get_execution_model() == ExecutionModelFragment && msl_options.supports_msl_version(2, 2)) - break; - - if (msl_options.is_ios()) - SPIRV_CROSS_THROW( - "SubgroupLocalInvocationId cannot be used outside of compute shaders before MSL 2.2 on iOS."); - - if (!msl_options.supports_msl_version(2, 1)) - SPIRV_CROSS_THROW( - "SubgroupLocalInvocationId cannot be used outside of compute shaders before MSL 2.1."); - - // Shaders other than compute shaders don't support the SIMD-group - // builtins directly, but we can emulate them using the SIMD-group - // functions. This might break if some of the subgroup terminated - // before reaching the entry point. + // For subgroup emulation, this is the same as the local invocation index. entry_func.fixup_hooks_in.push_back([=]() { - statement(builtin_type_decl(bi_type), " ", to_expression(var_id), - " = simd_prefix_exclusive_sum(1);"); + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), " = ", + to_expression(builtin_local_invocation_index_id), ";"); }); break; - case BuiltInSubgroupSize: - // This is natively supported in compute shaders. - if (get_execution_model() == ExecutionModelGLCompute) + case BuiltInNumSubgroups: + if (!msl_options.emulate_subgroups) break; - - // This is natively supported in fragment shaders in MSL 2.2. - if (get_execution_model() == ExecutionModelFragment && msl_options.supports_msl_version(2, 2)) + // For subgroup emulation, this is the same as the workgroup size. + entry_func.fixup_hooks_in.push_back([=]() { + auto &type = expression_type(builtin_workgroup_size_id); + string size_expr = to_expression(builtin_workgroup_size_id); + if (type.vecsize >= 3) + size_expr = join(size_expr, ".x * ", size_expr, ".y * ", size_expr, ".z"); + else if (type.vecsize == 2) + size_expr = join(size_expr, ".x * ", size_expr, ".y"); + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), " = ", size_expr, ";"); + }); + break; + case BuiltInSubgroupLocalInvocationId: + if (!msl_options.emulate_subgroups) break; - - if (msl_options.is_ios()) - SPIRV_CROSS_THROW("SubgroupSize cannot be used outside of compute shaders on iOS."); - - if (!msl_options.supports_msl_version(2, 1)) - SPIRV_CROSS_THROW("SubgroupSize cannot be used outside of compute shaders before Metal 2.1."); - + // For subgroup emulation, assume subgroups of size 1. entry_func.fixup_hooks_in.push_back( - [=]() { statement(builtin_type_decl(bi_type), " ", to_expression(var_id), " = simd_sum(1);"); }); + [=]() { statement(builtin_type_decl(bi_type), " ", to_expression(var_id), " = 0;"); }); + break; + case BuiltInSubgroupSize: + if (msl_options.emulate_subgroups) + { + // For subgroup emulation, assume subgroups of size 1. + entry_func.fixup_hooks_in.push_back( + [=]() { statement(builtin_type_decl(bi_type), " ", to_expression(var_id), " = 1;"); }); + } + else if (msl_options.fixed_subgroup_size != 0) + { + entry_func.fixup_hooks_in.push_back([=]() { + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), " = ", + msl_options.fixed_subgroup_size, ";"); + }); + } break; case BuiltInSubgroupEqMask: - if (msl_options.is_ios()) - SPIRV_CROSS_THROW("Subgroup ballot functionality is unavailable on iOS."); + if (msl_options.is_ios() && !msl_options.supports_msl_version(2, 2)) + SPIRV_CROSS_THROW("Subgroup ballot functionality requires Metal 2.2 on iOS."); if (!msl_options.supports_msl_version(2, 1)) SPIRV_CROSS_THROW("Subgroup ballot functionality requires Metal 2.1."); entry_func.fixup_hooks_in.push_back([=]() { - statement(builtin_type_decl(bi_type), " ", to_expression(var_id), " = ", - to_expression(builtin_subgroup_invocation_id_id), " > 32 ? uint4(0, (1 << (", - to_expression(builtin_subgroup_invocation_id_id), " - 32)), uint2(0)) : uint4(1 << ", - to_expression(builtin_subgroup_invocation_id_id), ", uint3(0));"); + if (msl_options.is_ios()) + { + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), " = ", "uint4(1 << ", + to_expression(builtin_subgroup_invocation_id_id), ", uint3(0));"); + } + else + { + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), " = ", + to_expression(builtin_subgroup_invocation_id_id), " >= 32 ? uint4(0, (1 << (", + to_expression(builtin_subgroup_invocation_id_id), " - 32)), uint2(0)) : uint4(1 << ", + to_expression(builtin_subgroup_invocation_id_id), ", uint3(0));"); + } }); break; case BuiltInSubgroupGeMask: - if (msl_options.is_ios()) - SPIRV_CROSS_THROW("Subgroup ballot functionality is unavailable on iOS."); + if (msl_options.is_ios() && !msl_options.supports_msl_version(2, 2)) + SPIRV_CROSS_THROW("Subgroup ballot functionality requires Metal 2.2 on iOS."); if (!msl_options.supports_msl_version(2, 1)) SPIRV_CROSS_THROW("Subgroup ballot functionality requires Metal 2.1."); + if (msl_options.fixed_subgroup_size != 0) + add_spv_func_and_recompile(SPVFuncImplSubgroupBallot); entry_func.fixup_hooks_in.push_back([=]() { // Case where index < 32, size < 32: - // mask0 = bfe(0xFFFFFFFF, index, size - index); - // mask1 = bfe(0xFFFFFFFF, 0, 0); // Gives 0 + // mask0 = bfi(0, 0xFFFFFFFF, index, size - index); + // mask1 = bfi(0, 0xFFFFFFFF, 0, 0); // Gives 0 // Case where index < 32 but size >= 32: - // mask0 = bfe(0xFFFFFFFF, index, 32 - index); - // mask1 = bfe(0xFFFFFFFF, 0, size - 32); + // mask0 = bfi(0, 0xFFFFFFFF, index, 32 - index); + // mask1 = bfi(0, 0xFFFFFFFF, 0, size - 32); // Case where index >= 32: - // mask0 = bfe(0xFFFFFFFF, 32, 0); // Gives 0 - // mask1 = bfe(0xFFFFFFFF, index - 32, size - index); + // mask0 = bfi(0, 0xFFFFFFFF, 32, 0); // Gives 0 + // mask1 = bfi(0, 0xFFFFFFFF, index - 32, size - index); // This is expressed without branches to avoid divergent // control flow--hence the complicated min/max expressions. // This is further complicated by the fact that if you attempt - // to bfe out-of-bounds on Metal, undefined behavior is the + // to bfi/bfe out-of-bounds on Metal, undefined behavior is the // result. - statement(builtin_type_decl(bi_type), " ", to_expression(var_id), - " = uint4(extract_bits(0xFFFFFFFF, min(", - to_expression(builtin_subgroup_invocation_id_id), ", 32u), (uint)max(min((int)", - to_expression(builtin_subgroup_size_id), ", 32) - (int)", - to_expression(builtin_subgroup_invocation_id_id), - ", 0)), extract_bits(0xFFFFFFFF, (uint)max((int)", - to_expression(builtin_subgroup_invocation_id_id), " - 32, 0), (uint)max((int)", - to_expression(builtin_subgroup_size_id), " - (int)max(", - to_expression(builtin_subgroup_invocation_id_id), ", 32u), 0)), uint2(0));"); + if (msl_options.fixed_subgroup_size > 32) + { + // Don't use the subgroup size variable with fixed subgroup sizes, + // since the variables could be defined in the wrong order. + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), + " = uint4(insert_bits(0u, 0xFFFFFFFF, min(", + to_expression(builtin_subgroup_invocation_id_id), ", 32u), (uint)max(32 - (int)", + to_expression(builtin_subgroup_invocation_id_id), + ", 0)), insert_bits(0u, 0xFFFFFFFF," + " (uint)max((int)", + to_expression(builtin_subgroup_invocation_id_id), " - 32, 0), ", + msl_options.fixed_subgroup_size, " - max(", + to_expression(builtin_subgroup_invocation_id_id), + ", 32u)), uint2(0));"); + } + else if (msl_options.fixed_subgroup_size != 0) + { + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), + " = uint4(insert_bits(0u, 0xFFFFFFFF, ", + to_expression(builtin_subgroup_invocation_id_id), ", ", + msl_options.fixed_subgroup_size, " - ", + to_expression(builtin_subgroup_invocation_id_id), + "), uint3(0));"); + } + else if (msl_options.is_ios()) + { + // On iOS, the SIMD-group size will currently never exceed 32. + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), + " = uint4(insert_bits(0u, 0xFFFFFFFF, ", + to_expression(builtin_subgroup_invocation_id_id), ", ", + to_expression(builtin_subgroup_size_id), " - ", + to_expression(builtin_subgroup_invocation_id_id), "), uint3(0));"); + } + else + { + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), + " = uint4(insert_bits(0u, 0xFFFFFFFF, min(", + to_expression(builtin_subgroup_invocation_id_id), ", 32u), (uint)max(min((int)", + to_expression(builtin_subgroup_size_id), ", 32) - (int)", + to_expression(builtin_subgroup_invocation_id_id), + ", 0)), insert_bits(0u, 0xFFFFFFFF, (uint)max((int)", + to_expression(builtin_subgroup_invocation_id_id), " - 32, 0), (uint)max((int)", + to_expression(builtin_subgroup_size_id), " - (int)max(", + to_expression(builtin_subgroup_invocation_id_id), ", 32u), 0)), uint2(0));"); + } }); break; case BuiltInSubgroupGtMask: - if (msl_options.is_ios()) - SPIRV_CROSS_THROW("Subgroup ballot functionality is unavailable on iOS."); + if (msl_options.is_ios() && !msl_options.supports_msl_version(2, 2)) + SPIRV_CROSS_THROW("Subgroup ballot functionality requires Metal 2.2 on iOS."); if (!msl_options.supports_msl_version(2, 1)) SPIRV_CROSS_THROW("Subgroup ballot functionality requires Metal 2.1."); + add_spv_func_and_recompile(SPVFuncImplSubgroupBallot); entry_func.fixup_hooks_in.push_back([=]() { // The same logic applies here, except now the index is one // more than the subgroup invocation ID. - statement(builtin_type_decl(bi_type), " ", to_expression(var_id), - " = uint4(extract_bits(0xFFFFFFFF, min(", - to_expression(builtin_subgroup_invocation_id_id), " + 1, 32u), (uint)max(min((int)", - to_expression(builtin_subgroup_size_id), ", 32) - (int)", - to_expression(builtin_subgroup_invocation_id_id), - " - 1, 0)), extract_bits(0xFFFFFFFF, (uint)max((int)", - to_expression(builtin_subgroup_invocation_id_id), " + 1 - 32, 0), (uint)max((int)", - to_expression(builtin_subgroup_size_id), " - (int)max(", - to_expression(builtin_subgroup_invocation_id_id), " + 1, 32u), 0)), uint2(0));"); + if (msl_options.fixed_subgroup_size > 32) + { + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), + " = uint4(insert_bits(0u, 0xFFFFFFFF, min(", + to_expression(builtin_subgroup_invocation_id_id), " + 1, 32u), (uint)max(32 - (int)", + to_expression(builtin_subgroup_invocation_id_id), + " - 1, 0)), insert_bits(0u, 0xFFFFFFFF, (uint)max((int)", + to_expression(builtin_subgroup_invocation_id_id), " + 1 - 32, 0), ", + msl_options.fixed_subgroup_size, " - max(", + to_expression(builtin_subgroup_invocation_id_id), + " + 1, 32u)), uint2(0));"); + } + else if (msl_options.fixed_subgroup_size != 0) + { + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), + " = uint4(insert_bits(0u, 0xFFFFFFFF, ", + to_expression(builtin_subgroup_invocation_id_id), " + 1, ", + msl_options.fixed_subgroup_size, " - ", + to_expression(builtin_subgroup_invocation_id_id), + " - 1), uint3(0));"); + } + else if (msl_options.is_ios()) + { + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), + " = uint4(insert_bits(0u, 0xFFFFFFFF, ", + to_expression(builtin_subgroup_invocation_id_id), " + 1, ", + to_expression(builtin_subgroup_size_id), " - ", + to_expression(builtin_subgroup_invocation_id_id), " - 1), uint3(0));"); + } + else + { + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), + " = uint4(insert_bits(0u, 0xFFFFFFFF, min(", + to_expression(builtin_subgroup_invocation_id_id), " + 1, 32u), (uint)max(min((int)", + to_expression(builtin_subgroup_size_id), ", 32) - (int)", + to_expression(builtin_subgroup_invocation_id_id), + " - 1, 0)), insert_bits(0u, 0xFFFFFFFF, (uint)max((int)", + to_expression(builtin_subgroup_invocation_id_id), " + 1 - 32, 0), (uint)max((int)", + to_expression(builtin_subgroup_size_id), " - (int)max(", + to_expression(builtin_subgroup_invocation_id_id), " + 1, 32u), 0)), uint2(0));"); + } }); break; case BuiltInSubgroupLeMask: - if (msl_options.is_ios()) - SPIRV_CROSS_THROW("Subgroup ballot functionality is unavailable on iOS."); + if (msl_options.is_ios() && !msl_options.supports_msl_version(2, 2)) + SPIRV_CROSS_THROW("Subgroup ballot functionality requires Metal 2.2 on iOS."); if (!msl_options.supports_msl_version(2, 1)) SPIRV_CROSS_THROW("Subgroup ballot functionality requires Metal 2.1."); + add_spv_func_and_recompile(SPVFuncImplSubgroupBallot); entry_func.fixup_hooks_in.push_back([=]() { - statement(builtin_type_decl(bi_type), " ", to_expression(var_id), - " = uint4(extract_bits(0xFFFFFFFF, 0, min(", - to_expression(builtin_subgroup_invocation_id_id), - " + 1, 32u)), extract_bits(0xFFFFFFFF, 0, (uint)max((int)", - to_expression(builtin_subgroup_invocation_id_id), " + 1 - 32, 0)), uint2(0));"); + if (msl_options.is_ios()) + { + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), + " = uint4(extract_bits(0xFFFFFFFF, 0, ", + to_expression(builtin_subgroup_invocation_id_id), " + 1), uint3(0));"); + } + else + { + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), + " = uint4(extract_bits(0xFFFFFFFF, 0, min(", + to_expression(builtin_subgroup_invocation_id_id), + " + 1, 32u)), extract_bits(0xFFFFFFFF, 0, (uint)max((int)", + to_expression(builtin_subgroup_invocation_id_id), " + 1 - 32, 0)), uint2(0));"); + } }); break; case BuiltInSubgroupLtMask: - if (msl_options.is_ios()) - SPIRV_CROSS_THROW("Subgroup ballot functionality is unavailable on iOS."); + if (msl_options.is_ios() && !msl_options.supports_msl_version(2, 2)) + SPIRV_CROSS_THROW("Subgroup ballot functionality requires Metal 2.2 on iOS."); if (!msl_options.supports_msl_version(2, 1)) SPIRV_CROSS_THROW("Subgroup ballot functionality requires Metal 2.1."); + add_spv_func_and_recompile(SPVFuncImplSubgroupBallot); entry_func.fixup_hooks_in.push_back([=]() { - statement(builtin_type_decl(bi_type), " ", to_expression(var_id), - " = uint4(extract_bits(0xFFFFFFFF, 0, min(", - to_expression(builtin_subgroup_invocation_id_id), - ", 32u)), extract_bits(0xFFFFFFFF, 0, (uint)max((int)", - to_expression(builtin_subgroup_invocation_id_id), " - 32, 0)), uint2(0));"); + if (msl_options.is_ios()) + { + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), + " = uint4(extract_bits(0xFFFFFFFF, 0, ", + to_expression(builtin_subgroup_invocation_id_id), "), uint3(0));"); + } + else + { + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), + " = uint4(extract_bits(0xFFFFFFFF, 0, min(", + to_expression(builtin_subgroup_invocation_id_id), + ", 32u)), extract_bits(0xFFFFFFFF, 0, (uint)max((int)", + to_expression(builtin_subgroup_invocation_id_id), " - 32, 0)), uint2(0));"); + } }); break; case BuiltInViewIndex: @@ -9956,6 +11691,15 @@ void CompilerMSL::fix_up_shader_inputs_outputs() // Since every physical device is rendering a different view, // there's no need for layered rendering here. } + else if (!msl_options.multiview_layered_rendering) + { + // In this case, the views are rendered one at a time. The view index, then, + // is just the first part of the "view mask". + entry_func.fixup_hooks_in.push_back([=]() { + statement("const ", builtin_type_decl(bi_type), " ", to_expression(var_id), " = ", + to_expression(view_mask_buffer_id), "[0];"); + }); + } else if (get_execution_model() == ExecutionModelFragment) { // Because we adjusted the view index in the vertex shader, we have to @@ -9970,10 +11714,13 @@ void CompilerMSL::fix_up_shader_inputs_outputs() // the view index in the instance index. entry_func.fixup_hooks_in.push_back([=]() { statement(builtin_type_decl(bi_type), " ", to_expression(var_id), " = ", - to_expression(view_mask_buffer_id), "[0] + ", to_expression(builtin_instance_idx_id), - " % ", to_expression(view_mask_buffer_id), "[1];"); - statement(to_expression(builtin_instance_idx_id), " /= ", to_expression(view_mask_buffer_id), - "[1];"); + to_expression(view_mask_buffer_id), "[0] + (", to_expression(builtin_instance_idx_id), + " - ", to_expression(builtin_base_instance_id), ") % ", + to_expression(view_mask_buffer_id), "[1];"); + statement(to_expression(builtin_instance_idx_id), " = (", + to_expression(builtin_instance_idx_id), " - ", + to_expression(builtin_base_instance_id), ") / ", to_expression(view_mask_buffer_id), + "[1] + ", to_expression(builtin_base_instance_id), ";"); }); // In addition to setting the variable itself, we also need to // set the render_target_array_index with it on output. We have to @@ -10023,10 +11770,93 @@ void CompilerMSL::fix_up_shader_inputs_outputs() execution.workgroup_size.z, ");"); }); break; + case BuiltInVertexId: + case BuiltInVertexIndex: + // This is direct-mapped normally. + if (!msl_options.vertex_for_tessellation) + break; + + entry_func.fixup_hooks_in.push_back([=]() { + builtin_declaration = true; + switch (msl_options.vertex_index_type) + { + case Options::IndexType::None: + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), " = ", + to_expression(builtin_invocation_id_id), ".x + ", + to_expression(builtin_dispatch_base_id), ".x;"); + break; + case Options::IndexType::UInt16: + case Options::IndexType::UInt32: + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), " = ", index_buffer_var_name, + "[", to_expression(builtin_invocation_id_id), ".x] + ", + to_expression(builtin_dispatch_base_id), ".x;"); + break; + } + builtin_declaration = false; + }); + break; + case BuiltInBaseVertex: + // This is direct-mapped normally. + if (!msl_options.vertex_for_tessellation) + break; + + entry_func.fixup_hooks_in.push_back([=]() { + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), " = ", + to_expression(builtin_dispatch_base_id), ".x;"); + }); + break; + case BuiltInInstanceId: + case BuiltInInstanceIndex: + // This is direct-mapped normally. + if (!msl_options.vertex_for_tessellation) + break; + + entry_func.fixup_hooks_in.push_back([=]() { + builtin_declaration = true; + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), " = ", + to_expression(builtin_invocation_id_id), ".y + ", to_expression(builtin_dispatch_base_id), + ".y;"); + builtin_declaration = false; + }); + break; + case BuiltInBaseInstance: + // This is direct-mapped normally. + if (!msl_options.vertex_for_tessellation) + break; + + entry_func.fixup_hooks_in.push_back([=]() { + statement(builtin_type_decl(bi_type), " ", to_expression(var_id), " = ", + to_expression(builtin_dispatch_base_id), ".y;"); + }); + break; default: break; } } + else if (var.storage == StorageClassOutput && is_builtin_variable(var) && active_output_builtins.get(bi_type)) + { + if (bi_type == BuiltInSampleMask && get_execution_model() == ExecutionModelFragment && + msl_options.additional_fixed_sample_mask != 0xffffffff) + { + // If the additional fixed sample mask was set, we need to adjust the sample_mask + // output to reflect that. If the shader outputs the sample_mask itself too, we need + // to AND the two masks to get the final one. + if (does_shader_write_sample_mask) + { + entry_func.fixup_hooks_out.push_back([=]() { + statement(to_expression(builtin_sample_mask_id), + " &= ", msl_options.additional_fixed_sample_mask, ";"); + }); + } + else + { + entry_func.fixup_hooks_out.push_back([=]() { + statement(to_expression(builtin_sample_mask_id), " = ", + msl_options.additional_fixed_sample_mask, ";"); + }); + } + } + } }); } @@ -10076,10 +11906,18 @@ uint32_t CompilerMSL::get_metal_resource_index(SPIRVariable &var, SPIRType::Base if (has_extended_decoration(var.self, resource_decoration)) return get_extended_decoration(var.self, resource_decoration); - // Allow user to enable decoration binding - if (msl_options.enable_decoration_binding) + auto &type = get(var.basetype); + + if (type_is_msl_framebuffer_fetch(type)) { - // If there is no explicit mapping of bindings to MSL, use the declared binding. + // Frame-buffer fetch gets its fallback resource index from the input attachment index, + // which is then treated as color index. + return get_decoration(var.self, DecorationInputAttachmentIndex); + } + else if (msl_options.enable_decoration_binding) + { + // Allow user to enable decoration binding. + // If there is no explicit mapping of bindings to MSL, use the declared binding as a fallback. if (has_decoration(var.self, DecorationBinding)) { var_binding = get_decoration(var.self, DecorationBinding); @@ -10098,7 +11936,6 @@ uint32_t CompilerMSL::get_metal_resource_index(SPIRVariable &var, SPIRType::Base allocate_argument_buffer_ids = descriptor_set_is_argument_buffer(var_desc_set); uint32_t binding_stride = 1; - auto &type = get(var.basetype); for (uint32_t i = 0; i < uint32_t(type.array.size()); i++) binding_stride *= to_array_size_literal(type, i); @@ -10140,7 +11977,25 @@ uint32_t CompilerMSL::get_metal_resource_index(SPIRVariable &var, SPIRType::Base bool CompilerMSL::type_is_msl_framebuffer_fetch(const SPIRType &type) const { return type.basetype == SPIRType::Image && type.image.dim == DimSubpassData && - msl_options.is_ios() && msl_options.ios_use_framebuffer_fetch_subpasses; + msl_options.use_framebuffer_fetch_subpasses; +} + +bool CompilerMSL::type_is_pointer(const SPIRType &type) const +{ + if (!type.pointer) + return false; + auto &parent_type = get(type.parent_type); + // Safeguards when we forget to set pointer_depth (there is an assert for it in type_to_glsl), + // but the extra check shouldn't hurt. + return (type.pointer_depth > parent_type.pointer_depth) || !parent_type.pointer; +} + +bool CompilerMSL::type_is_pointer_to_pointer(const SPIRType &type) const +{ + if (!type.pointer) + return false; + auto &parent_type = get(type.parent_type); + return type.pointer_depth > parent_type.pointer_depth && type_is_pointer(parent_type); } string CompilerMSL::argument_decl(const SPIRFunction::Parameter &arg) @@ -10169,9 +12024,8 @@ string CompilerMSL::argument_decl(const SPIRFunction::Parameter &arg) if (!type.array.empty() && type_is_image) constref = true; + const char *cv_qualifier = constref ? "const " : ""; string decl; - if (constref) - decl += "const "; // If this is a combined image-sampler for a 2D image with floating-point type, // we emitted the 'spvDynamicImageSampler' type, and this is *not* an alias parameter @@ -10185,27 +12039,38 @@ string CompilerMSL::argument_decl(const SPIRFunction::Parameter &arg) // Allow Metal to use the array template to make arrays a value type string address_space = get_argument_address_space(var); bool builtin = is_builtin_variable(var); + auto builtin_type = BuiltIn(get_decoration(arg.id, DecorationBuiltIn)); is_using_builtin_array = builtin; if (address_space == "threadgroup") is_using_builtin_array = true; if (var.basevariable && (var.basevariable == stage_in_ptr_var_id || var.basevariable == stage_out_ptr_var_id)) - decl += type_to_glsl(type, arg.id); + decl = join(cv_qualifier, type_to_glsl(type, arg.id)); else if (builtin) - decl += builtin_type_decl(static_cast(get_decoration(arg.id, DecorationBuiltIn)), arg.id); + decl = join(cv_qualifier, builtin_type_decl(builtin_type, arg.id)); else if ((storage == StorageClassUniform || storage == StorageClassStorageBuffer) && is_array(type)) { is_using_builtin_array = true; - decl += join(type_to_glsl(type, arg.id), "*"); + decl += join(cv_qualifier, type_to_glsl(type, arg.id), "*"); } else if (is_dynamic_img_sampler) { - decl += join("spvDynamicImageSampler<", type_to_glsl(get(type.image.type)), ">"); + decl = join(cv_qualifier, "spvDynamicImageSampler<", type_to_glsl(get(type.image.type)), ">"); // Mark the variable so that we can handle passing it to another function. set_extended_decoration(arg.id, SPIRVCrossDecorationDynamicImageSampler); } else - decl += type_to_glsl(type, arg.id); + { + // The type is a pointer type we need to emit cv_qualifier late. + if (type_is_pointer(type)) + { + decl = type_to_glsl(type, arg.id); + if (*cv_qualifier != '\0') + decl += join(" ", cv_qualifier); + } + else + decl = join(cv_qualifier, type_to_glsl(type, arg.id)); + } bool opaque_handle = storage == StorageClassUniformConstant; @@ -10273,25 +12138,50 @@ string CompilerMSL::argument_decl(const SPIRFunction::Parameter &arg) } } - decl += " (&"; - const char *restrict_kw = to_restrict(name_id); - if (*restrict_kw) + // Special case, need to override the array size here if we're using tess level as an argument. + if (get_execution_model() == ExecutionModelTessellationControl && builtin && + (builtin_type == BuiltInTessLevelInner || builtin_type == BuiltInTessLevelOuter)) { - decl += " "; - decl += restrict_kw; + uint32_t array_size = get_physical_tess_level_array_size(builtin_type); + if (array_size == 1) + { + decl += " &"; + decl += to_expression(name_id); + } + else + { + decl += " (&"; + decl += to_expression(name_id); + decl += ")"; + decl += join("[", array_size, "]"); + } + } + else + { + decl += " (&"; + const char *restrict_kw = to_restrict(name_id); + if (*restrict_kw) + { + decl += " "; + decl += restrict_kw; + } + decl += to_expression(name_id); + decl += ")"; + decl += type_to_array_glsl(type); } - decl += to_expression(name_id); - decl += ")"; - decl += type_to_array_glsl(type); } - else if (!opaque_handle) + else if (!opaque_handle && (!pull_model_inputs.count(var.basevariable) || type.basetype == SPIRType::Struct)) { // If this is going to be a reference to a variable pointer, the address space // for the reference has to go before the '&', but after the '*'. if (!address_space.empty()) { - if (decl.back() == '*') - decl += join(" ", address_space, " "); + if (type_is_pointer(type)) + { + if (*cv_qualifier == '\0') + decl += ' '; + decl += join(address_space, " "); + } else decl = join(address_space, " ", decl); } @@ -10356,17 +12246,19 @@ string CompilerMSL::ensure_valid_name(string name, string pfx) return (name.size() >= 2 && name[0] == '_' && isdigit(name[1])) ? (pfx + name) : name; } -// Replace all names that match MSL keywords or Metal Standard Library functions. -void CompilerMSL::replace_illegal_names() +const std::unordered_set &CompilerMSL::get_reserved_keyword_set() { - // FIXME: MSL and GLSL are doing two different things here. - // Agree on convention and remove this override. static const unordered_set keywords = { "kernel", "vertex", "fragment", "compute", "bias", + "level", + "gradient2d", + "gradientcube", + "gradient3d", + "min_lod_clamp", "assert", "VARIABLE_TRACEPOINT", "STATIC_DATA_TRACEPOINT", @@ -10489,10 +12381,17 @@ void CompilerMSL::replace_illegal_names() "quad_broadcast", }; + return keywords; +} + +const std::unordered_set &CompilerMSL::get_illegal_func_names() +{ static const unordered_set illegal_func_names = { "main", "saturate", "assert", + "fmin3", + "fmax3", "VARIABLE_TRACEPOINT", "STATIC_DATA_TRACEPOINT", "STATIC_DATA_TRACEPOINT_V", @@ -10613,6 +12512,17 @@ void CompilerMSL::replace_illegal_names() "M_SQRT1_2", }; + return illegal_func_names; +} + +// Replace all names that match MSL keywords or Metal Standard Library functions. +void CompilerMSL::replace_illegal_names() +{ + // FIXME: MSL and GLSL are doing two different things here. + // Agree on convention and remove this override. + auto &keywords = get_reserved_keyword_set(); + auto &illegal_func_names = get_illegal_func_names(); + ir.for_each_typed_id([&](uint32_t self, SPIRVariable &) { auto *meta = ir.find_meta(self); if (!meta) @@ -10643,6 +12553,16 @@ void CompilerMSL::replace_illegal_names() mbr_dec.alias += "0"; }); + CompilerGLSL::replace_illegal_names(); +} + +void CompilerMSL::replace_illegal_entry_point_names() +{ + auto &illegal_func_names = get_illegal_func_names(); + + // It is important to this before we fixup identifiers, + // since if ep_name is reserved, we will need to fix that up, + // and then copy alias back into entry.name after the fixup. for (auto &entry : ir.entry_points) { // Change both the entry point name and the alias, to keep them synced. @@ -10650,11 +12570,14 @@ void CompilerMSL::replace_illegal_names() if (illegal_func_names.find(ep_name) != end(illegal_func_names)) ep_name += "0"; - // Always write this because entry point might have been renamed earlier. ir.meta[entry.first].decoration.alias = ep_name; } +} - CompilerGLSL::replace_illegal_names(); +void CompilerMSL::sync_entry_point_aliases_and_names() +{ + for (auto &entry : ir.entry_points) + entry.second.name = ir.meta[entry.first].decoration.alias; } string CompilerMSL::to_member_reference(uint32_t base, const SPIRType &type, uint32_t index, bool ptr_chain) @@ -10706,8 +12629,22 @@ string CompilerMSL::type_to_glsl(const SPIRType &type, uint32_t id) // Pointer? if (type.pointer) { + assert(type.pointer_depth > 0); + const char *restrict_kw; - type_name = join(get_type_address_space(type, id), " ", type_to_glsl(get(type.parent_type), id)); + + auto type_address_space = get_type_address_space(type, id); + auto type_decl = type_to_glsl(get(type.parent_type), id); + + // Work around C pointer qualifier rules. If glsl_type is a pointer type as well + // we'll need to emit the address space to the right. + // We could always go this route, but it makes the code unnatural. + // Prefer emitting thread T *foo over T thread* foo since it's more readable, + // but we'll have to emit thread T * thread * T constant bar; for example. + if (type_is_pointer_to_pointer(type)) + type_name = join(type_decl, " ", type_address_space, " "); + else + type_name = join(type_address_space, " ", type_decl); switch (type.basetype) { @@ -10743,7 +12680,7 @@ string CompilerMSL::type_to_glsl(const SPIRType &type, uint32_t id) return image_type_glsl(type, id); case SPIRType::Sampler: - return sampler_type(type); + return sampler_type(type, id); case SPIRType::Void: return "void"; @@ -10754,6 +12691,10 @@ string CompilerMSL::type_to_glsl(const SPIRType &type, uint32_t id) case SPIRType::ControlPointArray: return join("patch_control_point<", type_to_glsl(get(type.parent_type), id), ">"); + case SPIRType::Interpolant: + return join("interpolant<", type_to_glsl(get(type.parent_type), id), ", interpolation::", + has_decoration(type.self, DecorationNoPerspective) ? "no_perspective" : "perspective", ">"); + // Scalars case SPIRType::Boolean: type_name = "bool"; @@ -10874,8 +12815,15 @@ std::string CompilerMSL::variable_decl(const SPIRType &type, const std::string & return CompilerGLSL::variable_decl(type, name, id); } -std::string CompilerMSL::sampler_type(const SPIRType &type) +std::string CompilerMSL::sampler_type(const SPIRType &type, uint32_t id) { + auto *var = maybe_get(id); + if (var && var->basevariable) + { + // Check against the base variable, and not a fake ID which might have been generated for this variable. + id = var->basevariable; + } + if (!type.array.empty()) { if (!msl_options.supports_msl_version(2)) @@ -10885,12 +12833,16 @@ std::string CompilerMSL::sampler_type(const SPIRType &type) SPIRV_CROSS_THROW("Arrays of arrays of samplers are not supported in MSL."); // Arrays of samplers in MSL must be declared with a special array syntax ala C++11 std::array. + // If we have a runtime array, it could be a variable-count descriptor set binding. uint32_t array_size = to_array_size_literal(type); + if (array_size == 0) + array_size = get_resource_array_size(id); + if (array_size == 0) SPIRV_CROSS_THROW("Unsized array of samplers is not supported in MSL."); auto &parent = get(get_pointee_type(type).parent_type); - return join("array<", sampler_type(parent), ", ", array_size, ">"); + return join("array<", sampler_type(parent, id), ", ", array_size, ">"); } else return "sampler"; @@ -10927,7 +12879,11 @@ string CompilerMSL::image_type_glsl(const SPIRType &type, uint32_t id) SPIRV_CROSS_THROW("Arrays of arrays of textures are not supported in MSL."); // Arrays of images in MSL must be declared with a special array syntax ala C++11 std::array. + // If we have a runtime array, it could be a variable-count descriptor set binding. uint32_t array_size = to_array_size_literal(type); + if (array_size == 0) + array_size = get_resource_array_size(id); + if (array_size == 0) SPIRV_CROSS_THROW("Unsized array of images is not supported in MSL."); @@ -10999,6 +12955,9 @@ string CompilerMSL::image_type_glsl(const SPIRType &type, uint32_t id) case Dim1D: case Dim2D: case DimSubpassData: + { + bool subpass_array = + img_type.dim == DimSubpassData && (msl_options.multiview || msl_options.arrayed_subpass_input); if (img_type.dim == Dim1D && !msl_options.texture_1D_as_2D) { // Use a native Metal 1D texture @@ -11013,7 +12972,7 @@ string CompilerMSL::image_type_glsl(const SPIRType &type, uint32_t id) img_type_4.vecsize = 4; return type_to_glsl(img_type_4); } - if (img_type.ms && img_type.arrayed) + if (img_type.ms && (img_type.arrayed || subpass_array)) { if (!msl_options.supports_msl_version(2, 1)) SPIRV_CROSS_THROW("Multisampled array textures are supported from 2.1."); @@ -11021,11 +12980,12 @@ string CompilerMSL::image_type_glsl(const SPIRType &type, uint32_t id) } else if (img_type.ms) img_type_name += "texture2d_ms"; - else if (img_type.arrayed) + else if (img_type.arrayed || subpass_array) img_type_name += "texture2d_array"; else img_type_name += "texture2d"; break; + } case Dim3D: img_type_name += "texture3d"; break; @@ -11093,10 +13053,23 @@ void CompilerMSL::emit_subgroup_op(const Instruction &i) const uint32_t *ops = stream(i); auto op = static_cast(i.op); - // Metal 2.0 is required. iOS only supports quad ops. macOS only supports - // broadcast and shuffle on 10.13 (2.0), with full support in 10.14 (2.1). - // Note that iOS makes no distinction between a quad-group and a subgroup; - // all subgroups are quad-groups there. + if (msl_options.emulate_subgroups) + { + // In this mode, only the GroupNonUniform cap is supported. The only op + // we need to handle, then, is OpGroupNonUniformElect. + if (op != OpGroupNonUniformElect) + SPIRV_CROSS_THROW("Subgroup emulation does not support operations other than Elect."); + // In this mode, the subgroup size is assumed to be one, so every invocation + // is elected. + emit_op(ops[0], ops[1], "true", true); + return; + } + + // Metal 2.0 is required. iOS only supports quad ops on 11.0 (2.0), with + // full support in 13.0 (2.2). macOS only supports broadcast and shuffle on + // 10.13 (2.0), with full support in 10.14 (2.1). + // Note that Apple GPUs before A13 make no distinction between a quad-group + // and a SIMD-group; all SIMD-groups are quad-groups on those. if (!msl_options.supports_msl_version(2)) SPIRV_CROSS_THROW("Subgroups are only supported in Metal 2.0 and up."); @@ -11105,12 +13078,32 @@ void CompilerMSL::emit_subgroup_op(const Instruction &i) auto int_type = to_signed_basetype(integer_width); auto uint_type = to_unsigned_basetype(integer_width); - if (msl_options.is_ios()) + if (msl_options.is_ios() && (!msl_options.supports_msl_version(2, 3) || !msl_options.ios_use_simdgroup_functions)) { switch (op) { default: - SPIRV_CROSS_THROW("iOS only supports quad-group operations."); + SPIRV_CROSS_THROW("Subgroup ops beyond broadcast, ballot, and shuffle on iOS require Metal 2.3 and up."); + case OpGroupNonUniformBroadcastFirst: + if (!msl_options.supports_msl_version(2, 2)) + SPIRV_CROSS_THROW("BroadcastFirst on iOS requires Metal 2.2 and up."); + break; + case OpGroupNonUniformElect: + if (!msl_options.supports_msl_version(2, 2)) + SPIRV_CROSS_THROW("Elect on iOS requires Metal 2.2 and up."); + break; + case OpGroupNonUniformAny: + case OpGroupNonUniformAll: + case OpGroupNonUniformAllEqual: + case OpGroupNonUniformBallot: + case OpGroupNonUniformInverseBallot: + case OpGroupNonUniformBallotBitExtract: + case OpGroupNonUniformBallotFindLSB: + case OpGroupNonUniformBallotFindMSB: + case OpGroupNonUniformBallotBitCount: + if (!msl_options.supports_msl_version(2, 2)) + SPIRV_CROSS_THROW("Ballot ops on iOS requires Metal 2.2 and up."); + break; case OpGroupNonUniformBroadcast: case OpGroupNonUniformShuffle: case OpGroupNonUniformShuffleXor: @@ -11140,23 +13133,25 @@ void CompilerMSL::emit_subgroup_op(const Instruction &i) uint32_t result_type = ops[0]; uint32_t id = ops[1]; - auto scope = static_cast(get(ops[2]).scalar()); + auto scope = static_cast(evaluate_constant_u32(ops[2])); if (scope != ScopeSubgroup) SPIRV_CROSS_THROW("Only subgroup scope is supported."); switch (op) { case OpGroupNonUniformElect: - emit_op(result_type, id, "simd_is_first()", true); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + emit_op(result_type, id, "quad_is_first()", false); + else + emit_op(result_type, id, "simd_is_first()", false); break; case OpGroupNonUniformBroadcast: - emit_binary_func_op(result_type, id, ops[3], ops[4], - msl_options.is_ios() ? "quad_broadcast" : "simd_broadcast"); + emit_binary_func_op(result_type, id, ops[3], ops[4], "spvSubgroupBroadcast"); break; case OpGroupNonUniformBroadcastFirst: - emit_unary_func_op(result_type, id, ops[3], "simd_broadcast_first"); + emit_unary_func_op(result_type, id, ops[3], "spvSubgroupBroadcastFirst"); break; case OpGroupNonUniformBallot: @@ -11172,54 +13167,64 @@ void CompilerMSL::emit_subgroup_op(const Instruction &i) break; case OpGroupNonUniformBallotFindLSB: - emit_unary_func_op(result_type, id, ops[3], "spvSubgroupBallotFindLSB"); + emit_binary_func_op(result_type, id, ops[3], builtin_subgroup_size_id, "spvSubgroupBallotFindLSB"); break; case OpGroupNonUniformBallotFindMSB: - emit_unary_func_op(result_type, id, ops[3], "spvSubgroupBallotFindMSB"); + emit_binary_func_op(result_type, id, ops[3], builtin_subgroup_size_id, "spvSubgroupBallotFindMSB"); break; case OpGroupNonUniformBallotBitCount: { auto operation = static_cast(ops[3]); - if (operation == GroupOperationReduce) - emit_unary_func_op(result_type, id, ops[4], "spvSubgroupBallotBitCount"); - else if (operation == GroupOperationInclusiveScan) + switch (operation) + { + case GroupOperationReduce: + emit_binary_func_op(result_type, id, ops[4], builtin_subgroup_size_id, "spvSubgroupBallotBitCount"); + break; + case GroupOperationInclusiveScan: emit_binary_func_op(result_type, id, ops[4], builtin_subgroup_invocation_id_id, "spvSubgroupBallotInclusiveBitCount"); - else if (operation == GroupOperationExclusiveScan) + break; + case GroupOperationExclusiveScan: emit_binary_func_op(result_type, id, ops[4], builtin_subgroup_invocation_id_id, "spvSubgroupBallotExclusiveBitCount"); - else + break; + default: SPIRV_CROSS_THROW("Invalid BitCount operation."); + break; + } break; } case OpGroupNonUniformShuffle: - emit_binary_func_op(result_type, id, ops[3], ops[4], msl_options.is_ios() ? "quad_shuffle" : "simd_shuffle"); + emit_binary_func_op(result_type, id, ops[3], ops[4], "spvSubgroupShuffle"); break; case OpGroupNonUniformShuffleXor: - emit_binary_func_op(result_type, id, ops[3], ops[4], - msl_options.is_ios() ? "quad_shuffle_xor" : "simd_shuffle_xor"); + emit_binary_func_op(result_type, id, ops[3], ops[4], "spvSubgroupShuffleXor"); break; case OpGroupNonUniformShuffleUp: - emit_binary_func_op(result_type, id, ops[3], ops[4], - msl_options.is_ios() ? "quad_shuffle_up" : "simd_shuffle_up"); + emit_binary_func_op(result_type, id, ops[3], ops[4], "spvSubgroupShuffleUp"); break; case OpGroupNonUniformShuffleDown: - emit_binary_func_op(result_type, id, ops[3], ops[4], - msl_options.is_ios() ? "quad_shuffle_down" : "simd_shuffle_down"); + emit_binary_func_op(result_type, id, ops[3], ops[4], "spvSubgroupShuffleDown"); break; case OpGroupNonUniformAll: - emit_unary_func_op(result_type, id, ops[3], "simd_all"); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + emit_unary_func_op(result_type, id, ops[3], "quad_all"); + else + emit_unary_func_op(result_type, id, ops[3], "simd_all"); break; case OpGroupNonUniformAny: - emit_unary_func_op(result_type, id, ops[3], "simd_any"); + if (msl_options.is_ios() && !msl_options.ios_use_simdgroup_functions) + emit_unary_func_op(result_type, id, ops[3], "quad_any"); + else + emit_unary_func_op(result_type, id, ops[3], "simd_any"); break; case OpGroupNonUniformAllEqual: @@ -11240,7 +13245,7 @@ case OpGroupNonUniform##op: \ else if (operation == GroupOperationClusteredReduce) \ { \ /* Only cluster sizes of 4 are supported. */ \ - uint32_t cluster_size = get(ops[5]).scalar(); \ + uint32_t cluster_size = evaluate_constant_u32(ops[5]); \ if (cluster_size != 4) \ SPIRV_CROSS_THROW("Metal only supports quad ClusteredReduce."); \ emit_unary_func_op(result_type, id, ops[4], "quad_" #msl_op); \ @@ -11269,7 +13274,7 @@ case OpGroupNonUniform##op: \ else if (operation == GroupOperationClusteredReduce) \ { \ /* Only cluster sizes of 4 are supported. */ \ - uint32_t cluster_size = get(ops[5]).scalar(); \ + uint32_t cluster_size = evaluate_constant_u32(ops[5]); \ if (cluster_size != 4) \ SPIRV_CROSS_THROW("Metal only supports quad ClusteredReduce."); \ emit_unary_func_op(result_type, id, ops[4], "quad_" #msl_op); \ @@ -11292,7 +13297,7 @@ case OpGroupNonUniform##op: \ else if (operation == GroupOperationClusteredReduce) \ { \ /* Only cluster sizes of 4 are supported. */ \ - uint32_t cluster_size = get(ops[5]).scalar(); \ + uint32_t cluster_size = evaluate_constant_u32(ops[5]); \ if (cluster_size != 4) \ SPIRV_CROSS_THROW("Metal only supports quad ClusteredReduce."); \ emit_unary_func_op_cast(result_type, id, ops[4], "quad_" #msl_op, type, type); \ @@ -11319,26 +13324,11 @@ case OpGroupNonUniform##op: \ #undef MSL_GROUP_OP_CAST case OpGroupNonUniformQuadSwap: - { - // We can implement this easily based on the following table giving - // the target lane ID from the direction and current lane ID: - // Direction - // | 0 | 1 | 2 | - // ---+---+---+---+ - // L 0 | 1 2 3 - // a 1 | 0 3 2 - // n 2 | 3 0 1 - // e 3 | 2 1 0 - // Notice that target = source ^ (direction + 1). - uint32_t mask = get(ops[4]).scalar() + 1; - uint32_t mask_id = ir.increase_bound_by(1); - set(mask_id, expression_type_id(ops[4]), mask, false); - emit_binary_func_op(result_type, id, ops[3], mask_id, "quad_shuffle_xor"); + emit_binary_func_op(result_type, id, ops[3], ops[4], "spvQuadSwap"); break; - } case OpGroupNonUniformQuadBroadcast: - emit_binary_func_op(result_type, id, ops[3], ops[4], "quad_broadcast"); + emit_binary_func_op(result_type, id, ops[3], ops[4], "spvQuadBroadcast"); break; default: @@ -11607,6 +13597,11 @@ string CompilerMSL::builtin_qualifier(BuiltIn builtin) // Tess. control function in case BuiltInInvocationId: + if (msl_options.multi_patch_workgroup) + { + // Shouldn't be reached. + SPIRV_CROSS_THROW("InvocationId is computed manually with multi-patch workgroups in MSL."); + } return "thread_index_in_threadgroup"; case BuiltInPatchVertices: // Shouldn't be reached. @@ -11615,12 +13610,17 @@ string CompilerMSL::builtin_qualifier(BuiltIn builtin) switch (execution.model) { case ExecutionModelTessellationControl: + if (msl_options.multi_patch_workgroup) + { + // Shouldn't be reached. + SPIRV_CROSS_THROW("PrimitiveId is computed manually with multi-patch workgroups in MSL."); + } return "threadgroup_position_in_grid"; case ExecutionModelTessellationEvaluation: return "patch_id"; case ExecutionModelFragment: - if (msl_options.is_ios()) - SPIRV_CROSS_THROW("PrimitiveId is not supported in fragment on iOS."); + if (msl_options.is_ios() && !msl_options.supports_msl_version(2, 3)) + SPIRV_CROSS_THROW("PrimitiveId on iOS requires MSL 2.3."); else if (msl_options.is_macos() && !msl_options.supports_msl_version(2, 2)) SPIRV_CROSS_THROW("PrimitiveId on macOS requires MSL 2.2."); return "primitive_id"; @@ -11688,6 +13688,9 @@ string CompilerMSL::builtin_qualifier(BuiltIn builtin) return "thread_index_in_threadgroup"; case BuiltInSubgroupSize: + if (msl_options.emulate_subgroups || msl_options.fixed_subgroup_size != 0) + // Shouldn't be reached. + SPIRV_CROSS_THROW("Emitting threads_per_simdgroup attribute with fixed subgroup size??"); if (execution.model == ExecutionModelFragment) { if (!msl_options.supports_msl_version(2, 2)) @@ -11702,28 +13705,42 @@ string CompilerMSL::builtin_qualifier(BuiltIn builtin) } case BuiltInNumSubgroups: + if (msl_options.emulate_subgroups) + // Shouldn't be reached. + SPIRV_CROSS_THROW("NumSubgroups is handled specially with emulation."); if (!msl_options.supports_msl_version(2)) SPIRV_CROSS_THROW("Subgroup builtins require Metal 2.0."); return msl_options.is_ios() ? "quadgroups_per_threadgroup" : "simdgroups_per_threadgroup"; case BuiltInSubgroupId: + if (msl_options.emulate_subgroups) + // Shouldn't be reached. + SPIRV_CROSS_THROW("SubgroupId is handled specially with emulation."); if (!msl_options.supports_msl_version(2)) SPIRV_CROSS_THROW("Subgroup builtins require Metal 2.0."); return msl_options.is_ios() ? "quadgroup_index_in_threadgroup" : "simdgroup_index_in_threadgroup"; case BuiltInSubgroupLocalInvocationId: + if (msl_options.emulate_subgroups) + // Shouldn't be reached. + SPIRV_CROSS_THROW("SubgroupLocalInvocationId is handled specially with emulation."); if (execution.model == ExecutionModelFragment) { if (!msl_options.supports_msl_version(2, 2)) SPIRV_CROSS_THROW("thread_index_in_simdgroup requires Metal 2.2 in fragment shaders."); return "thread_index_in_simdgroup"; } - else + else if (execution.model == ExecutionModelKernel || execution.model == ExecutionModelGLCompute || + execution.model == ExecutionModelTessellationControl || + (execution.model == ExecutionModelVertex && msl_options.vertex_for_tessellation)) { + // We are generating a Metal kernel function. if (!msl_options.supports_msl_version(2)) - SPIRV_CROSS_THROW("Subgroup builtins require Metal 2.0."); + SPIRV_CROSS_THROW("Subgroup builtins in kernel functions require Metal 2.0."); return msl_options.is_ios() ? "thread_index_in_quadgroup" : "thread_index_in_simdgroup"; } + else + SPIRV_CROSS_THROW("Subgroup builtins are not available in this type of function."); case BuiltInSubgroupEqMask: case BuiltInSubgroupGeMask: @@ -11735,16 +13752,16 @@ string CompilerMSL::builtin_qualifier(BuiltIn builtin) case BuiltInBaryCoordNV: // TODO: AMD barycentrics as well? Seem to have different swizzle and 2 components rather than 3. - if (msl_options.is_ios()) - SPIRV_CROSS_THROW("Barycentrics not supported on iOS."); + if (msl_options.is_ios() && !msl_options.supports_msl_version(2, 3)) + SPIRV_CROSS_THROW("Barycentrics are only supported in MSL 2.3 and above on iOS."); else if (!msl_options.supports_msl_version(2, 2)) SPIRV_CROSS_THROW("Barycentrics are only supported in MSL 2.2 and above on macOS."); return "barycentric_coord, center_perspective"; case BuiltInBaryCoordNoPerspNV: // TODO: AMD barycentrics as well? Seem to have different swizzle and 2 components rather than 3. - if (msl_options.is_ios()) - SPIRV_CROSS_THROW("Barycentrics not supported on iOS."); + if (msl_options.is_ios() && !msl_options.supports_msl_version(2, 3)) + SPIRV_CROSS_THROW("Barycentrics are only supported in MSL 2.3 and above on iOS."); else if (!msl_options.supports_msl_version(2, 2)) SPIRV_CROSS_THROW("Barycentrics are only supported in MSL 2.2 and above on macOS."); return "barycentric_coord, center_no_perspective"; @@ -11895,6 +13912,18 @@ const SPIRType &CompilerMSL::get_physical_member_type(const SPIRType &type, uint return get(type.member_types[index]); } +SPIRType CompilerMSL::get_presumed_input_type(const SPIRType &ib_type, uint32_t index) const +{ + SPIRType type = get_physical_member_type(ib_type, index); + uint32_t loc = get_member_decoration(ib_type.self, index, DecorationLocation); + if (inputs_by_location.count(loc)) + { + if (inputs_by_location.at(loc).vecsize > type.vecsize) + type.vecsize = inputs_by_location.at(loc).vecsize; + } + return type; +} + uint32_t CompilerMSL::get_declared_type_array_stride_msl(const SPIRType &type, bool is_packed, bool row_major) const { // Array stride in MSL is always size * array_size. sizeof(float3) == 16, @@ -11930,6 +13959,12 @@ uint32_t CompilerMSL::get_declared_struct_member_array_stride_msl(const SPIRType has_member_decoration(type.self, index, DecorationRowMajor)); } +uint32_t CompilerMSL::get_declared_input_array_stride_msl(const SPIRType &type, uint32_t index) const +{ + return get_declared_type_array_stride_msl(get_presumed_input_type(type, index), false, + has_member_decoration(type.self, index, DecorationRowMajor)); +} + uint32_t CompilerMSL::get_declared_type_matrix_stride_msl(const SPIRType &type, bool packed, bool row_major) const { // For packed matrices, we just use the size of the vector type. @@ -11947,6 +13982,12 @@ uint32_t CompilerMSL::get_declared_struct_member_matrix_stride_msl(const SPIRTyp has_member_decoration(type.self, index, DecorationRowMajor)); } +uint32_t CompilerMSL::get_declared_input_matrix_stride_msl(const SPIRType &type, uint32_t index) const +{ + return get_declared_type_matrix_stride_msl(get_presumed_input_type(type, index), false, + has_member_decoration(type.self, index, DecorationRowMajor)); +} + uint32_t CompilerMSL::get_declared_struct_size_msl(const SPIRType &struct_type, bool ignore_alignment, bool ignore_padding) const { @@ -12032,6 +14073,12 @@ uint32_t CompilerMSL::get_declared_struct_member_size_msl(const SPIRType &type, has_member_decoration(type.self, index, DecorationRowMajor)); } +uint32_t CompilerMSL::get_declared_input_size_msl(const SPIRType &type, uint32_t index) const +{ + return get_declared_type_size_msl(get_presumed_input_type(type, index), false, + has_member_decoration(type.self, index, DecorationRowMajor)); +} + // Returns the byte alignment of a type. uint32_t CompilerMSL::get_declared_type_alignment_msl(const SPIRType &type, bool is_packed, bool row_major) const { @@ -12088,6 +14135,12 @@ uint32_t CompilerMSL::get_declared_struct_member_alignment_msl(const SPIRType &t has_member_decoration(type.self, index, DecorationRowMajor)); } +uint32_t CompilerMSL::get_declared_input_alignment_msl(const SPIRType &type, uint32_t index) const +{ + return get_declared_type_alignment_msl(get_presumed_input_type(type, index), false, + has_member_decoration(type.self, index, DecorationRowMajor)); +} + bool CompilerMSL::skip_argument(uint32_t) const { return false; @@ -12185,7 +14238,8 @@ bool CompilerMSL::OpCodePreprocessor::handle(Op opcode, const uint32_t *args, ui } case OpImageWrite: - uses_resource_write = true; + if (!compiler.msl_options.supports_msl_version(2, 2)) + uses_resource_write = true; break; case OpStore: @@ -12245,8 +14299,15 @@ bool CompilerMSL::OpCodePreprocessor::handle(Op opcode, const uint32_t *args, ui needs_subgroup_invocation_id = true; break; + case OpGroupNonUniformBallotFindLSB: + case OpGroupNonUniformBallotFindMSB: + needs_subgroup_size = true; + break; + case OpGroupNonUniformBallotBitCount: - if (args[3] != GroupOperationReduce) + if (args[3] == GroupOperationReduce) + needs_subgroup_size = true; + else needs_subgroup_invocation_id = true; break; @@ -12273,6 +14334,55 @@ bool CompilerMSL::OpCodePreprocessor::handle(Op opcode, const uint32_t *args, ui break; } + case OpExtInst: + { + uint32_t extension_set = args[2]; + if (compiler.get(extension_set).ext == SPIRExtension::GLSL) + { + auto op_450 = static_cast(args[3]); + switch (op_450) + { + case GLSLstd450InterpolateAtCentroid: + case GLSLstd450InterpolateAtSample: + case GLSLstd450InterpolateAtOffset: + { + if (!compiler.msl_options.supports_msl_version(2, 3)) + SPIRV_CROSS_THROW("Pull-model interpolation requires MSL 2.3."); + // Fragment varyings used with pull-model interpolation need special handling, + // due to the way pull-model interpolation works in Metal. + auto *var = compiler.maybe_get_backing_variable(args[4]); + if (var) + { + compiler.pull_model_inputs.insert(var->self); + auto &var_type = compiler.get_variable_element_type(*var); + // In addition, if this variable has a 'Sample' decoration, we need the sample ID + // in order to do default interpolation. + if (compiler.has_decoration(var->self, DecorationSample)) + { + needs_sample_id = true; + } + else if (var_type.basetype == SPIRType::Struct) + { + // Now we need to check each member and see if it has this decoration. + for (uint32_t i = 0; i < var_type.member_types.size(); ++i) + { + if (compiler.has_member_decoration(var_type.self, i, DecorationSample)) + { + needs_sample_id = true; + break; + } + } + } + } + break; + } + default: + break; + } + } + break; + } + default: break; } @@ -12290,7 +14400,8 @@ void CompilerMSL::OpCodePreprocessor::check_resource_write(uint32_t var_id) { auto *p_var = compiler.maybe_get_backing_variable(var_id); StorageClass sc = p_var ? p_var->storage : StorageClassMax; - if (sc == StorageClassUniform || sc == StorageClassStorageBuffer) + if (!compiler.msl_options.supports_msl_version(2, 1) && + (sc == StorageClassUniform || sc == StorageClassStorageBuffer)) uses_resource_write = true; } @@ -12429,6 +14540,12 @@ CompilerMSL::SPVFuncImpl CompilerMSL::OpCodePreprocessor::get_spv_func_impl(Op o break; } + case OpGroupNonUniformBroadcast: + return SPVFuncImplSubgroupBroadcast; + + case OpGroupNonUniformBroadcastFirst: + return SPVFuncImplSubgroupBroadcastFirst; + case OpGroupNonUniformBallot: return SPVFuncImplSubgroupBallot; @@ -12448,6 +14565,24 @@ CompilerMSL::SPVFuncImpl CompilerMSL::OpCodePreprocessor::get_spv_func_impl(Op o case OpGroupNonUniformAllEqual: return SPVFuncImplSubgroupAllEqual; + case OpGroupNonUniformShuffle: + return SPVFuncImplSubgroupShuffle; + + case OpGroupNonUniformShuffleXor: + return SPVFuncImplSubgroupShuffleXor; + + case OpGroupNonUniformShuffleUp: + return SPVFuncImplSubgroupShuffleUp; + + case OpGroupNonUniformShuffleDown: + return SPVFuncImplSubgroupShuffleDown; + + case OpGroupNonUniformQuadBroadcast: + return SPVFuncImplQuadBroadcast; + + case OpGroupNonUniformQuadSwap: + return SPVFuncImplQuadSwap; + default: break; } @@ -12554,7 +14689,7 @@ void CompilerMSL::remap_constexpr_sampler_by_binding(uint32_t desc_set, uint32_t constexpr_samplers_by_binding[{ desc_set, binding }] = sampler; } -void CompilerMSL::bitcast_from_builtin_load(uint32_t source_id, std::string &expr, const SPIRType &expr_type) +void CompilerMSL::cast_from_builtin_load(uint32_t source_id, std::string &expr, const SPIRType &expr_type) { auto *var = maybe_get_backing_variable(source_id); if (var) @@ -12566,6 +14701,7 @@ void CompilerMSL::bitcast_from_builtin_load(uint32_t source_id, std::string &exp auto builtin = static_cast(get_decoration(source_id, DecorationBuiltIn)); auto expected_type = expr_type.basetype; + auto expected_width = expr_type.width; switch (builtin) { case BuiltInGlobalInvocationId: @@ -12586,12 +14722,16 @@ void CompilerMSL::bitcast_from_builtin_load(uint32_t source_id, std::string &exp case BuiltInBaseInstance: case BuiltInBaseVertex: expected_type = SPIRType::UInt; + expected_width = 32; break; case BuiltInTessLevelInner: case BuiltInTessLevelOuter: if (get_execution_model() == ExecutionModelTessellationControl) + { expected_type = SPIRType::Half; + expected_width = 16; + } break; default: @@ -12599,7 +14739,39 @@ void CompilerMSL::bitcast_from_builtin_load(uint32_t source_id, std::string &exp } if (expected_type != expr_type.basetype) - expr = bitcast_expression(expr_type, expected_type, expr); + { + if (!expr_type.array.empty() && (builtin == BuiltInTessLevelInner || builtin == BuiltInTessLevelOuter)) + { + // Triggers when loading TessLevel directly as an array. + // Need explicit padding + cast. + auto wrap_expr = join(type_to_glsl(expr_type), "({ "); + + uint32_t array_size = get_physical_tess_level_array_size(builtin); + for (uint32_t i = 0; i < array_size; i++) + { + if (array_size > 1) + wrap_expr += join("float(", expr, "[", i, "])"); + else + wrap_expr += join("float(", expr, ")"); + if (i + 1 < array_size) + wrap_expr += ", "; + } + + if (get_execution_mode_bitset().get(ExecutionModeTriangles)) + wrap_expr += ", 0.0"; + + wrap_expr += " })"; + expr = std::move(wrap_expr); + } + else + { + // These are of different widths, so we cannot do a straight bitcast. + if (expected_width != expr_type.width) + expr = join(type_to_glsl(expr_type), "(", expr, ")"); + else + expr = bitcast_expression(expr_type, expected_type, expr); + } + } if (builtin == BuiltInTessCoord && get_entry_point().flags.get(ExecutionModeQuads) && expr_type.vecsize == 3) { @@ -12609,7 +14781,7 @@ void CompilerMSL::bitcast_from_builtin_load(uint32_t source_id, std::string &exp } } -void CompilerMSL::bitcast_to_builtin_store(uint32_t target_id, std::string &expr, const SPIRType &expr_type) +void CompilerMSL::cast_to_builtin_store(uint32_t target_id, std::string &expr, const SPIRType &expr_type) { auto *var = maybe_get_backing_variable(target_id); if (var) @@ -12621,6 +14793,7 @@ void CompilerMSL::bitcast_to_builtin_store(uint32_t target_id, std::string &expr auto builtin = static_cast(get_decoration(target_id, DecorationBuiltIn)); auto expected_type = expr_type.basetype; + auto expected_width = expr_type.width; switch (builtin) { case BuiltInLayer: @@ -12629,11 +14802,13 @@ void CompilerMSL::bitcast_to_builtin_store(uint32_t target_id, std::string &expr case BuiltInPrimitiveId: case BuiltInViewIndex: expected_type = SPIRType::UInt; + expected_width = 32; break; case BuiltInTessLevelInner: case BuiltInTessLevelOuter: expected_type = SPIRType::Half; + expected_width = 16; break; default: @@ -12642,10 +14817,13 @@ void CompilerMSL::bitcast_to_builtin_store(uint32_t target_id, std::string &expr if (expected_type != expr_type.basetype) { - if (expected_type == SPIRType::Half && expr_type.basetype == SPIRType::Float) + if (expected_width != expr_type.width) { // These are of different widths, so we cannot do a straight bitcast. - expr = join("half(", expr, ")"); + auto type = expr_type; + type.basetype = expected_type; + type.width = expected_width; + expr = join(type_to_glsl(type), "(", expr, ")"); } else { @@ -12662,11 +14840,18 @@ string CompilerMSL::to_initializer_expression(const SPIRVariable &var) // FIXME: We cannot handle non-constant arrays being initialized. // We will need to inject spvArrayCopy here somehow ... auto &type = get(var.basetype); + string expr; if (ir.ids[var.initializer].get_type() == TypeConstant && (!type.array.empty() || type.basetype == SPIRType::Struct)) - return constant_expression(get(var.initializer)); + expr = constant_expression(get(var.initializer)); else - return CompilerGLSL::to_initializer_expression(var); + expr = CompilerGLSL::to_initializer_expression(var); + // If the initializer has more vector components than the variable, add a swizzle. + // FIXME: This can't handle arrays or structs. + auto &init_type = expression_type(var.initializer); + if (type.array.empty() && type.basetype != SPIRType::Struct && init_type.vecsize > type.vecsize) + expr = enclose_expression(expr + vector_swizzle(type.vecsize, 0)); + return expr; } string CompilerMSL::to_zero_initialized_expression(uint32_t) @@ -12684,6 +14869,17 @@ bool CompilerMSL::descriptor_set_is_argument_buffer(uint32_t desc_set) const return (argument_buffer_discrete_mask & (1u << desc_set)) == 0; } +bool CompilerMSL::is_supported_argument_buffer_type(const SPIRType &type) const +{ + // Very specifically, image load-store in argument buffers are disallowed on MSL on iOS. + // But we won't know when the argument buffer is encoded whether this image will have + // a NonWritable decoration. So just use discrete arguments for all storage images + // on iOS. + bool is_storage_image = type.basetype == SPIRType::Image && type.image.sampled == 2; + bool is_supported_type = !msl_options.is_ios() || !is_storage_image; + return !type_is_msl_framebuffer_fetch(type) && is_supported_type; +} + void CompilerMSL::analyze_argument_buffers() { // Gather all used resources and sort them out into argument buffers. @@ -12766,15 +14962,20 @@ void CompilerMSL::analyze_argument_buffers() { inline_block_vars.push_back(var_id); } - else if (!constexpr_sampler) + else if (!constexpr_sampler && is_supported_argument_buffer_type(type)) { // constexpr samplers are not declared as resources. // Inline uniform blocks are always emitted at the end. - if (!msl_options.is_ios() || type.basetype != SPIRType::Image || type.image.sampled != 2) + add_resource_name(var_id); + resources_in_set[desc_set].push_back( + { &var, to_name(var_id), type.basetype, get_metal_resource_index(var, type.basetype), 0 }); + + // Emulate texture2D atomic operations + if (atomic_image_vars.count(var.self)) { - add_resource_name(var_id); + uint32_t buffer_resource_index = get_metal_resource_index(var, SPIRType::AtomicCounter, 0); resources_in_set[desc_set].push_back( - { &var, to_name(var_id), type.basetype, get_metal_resource_index(var, type.basetype), 0 }); + { &var, to_name(var_id) + "_atomic", SPIRType::Struct, buffer_resource_index, 0 }); } } @@ -12801,20 +15002,13 @@ void CompilerMSL::analyze_argument_buffers() if (uint_ptr_type_id == 0) { - uint32_t offset = ir.increase_bound_by(2); - uint32_t type_id = offset; - uint_ptr_type_id = offset + 1; + uint_ptr_type_id = ir.increase_bound_by(1); // Create a buffer to hold extra data, including the swizzle constants. - SPIRType uint_type; - uint_type.basetype = SPIRType::UInt; - uint_type.width = 32; - set(type_id, uint_type); - - SPIRType uint_type_pointer = uint_type; + SPIRType uint_type_pointer = get_uint_type(); uint_type_pointer.pointer = true; - uint_type_pointer.pointer_depth = 1; - uint_type_pointer.parent_type = type_id; + uint_type_pointer.pointer_depth++; + uint_type_pointer.parent_type = get_uint_type_id(); uint_type_pointer.storage = StorageClassUniform; set(uint_ptr_type_id, uint_type_pointer); set_decoration(uint_ptr_type_id, DecorationArrayStride, 4); @@ -12887,7 +15081,7 @@ void CompilerMSL::analyze_argument_buffers() auto &ptr_type = set(ptr_type_id); ptr_type = buffer_type; ptr_type.pointer = true; - ptr_type.pointer_depth = 1; + ptr_type.pointer_depth++; ptr_type.parent_type = type_id; uint32_t buffer_variable_id = next_id; @@ -12957,6 +15151,31 @@ void CompilerMSL::analyze_argument_buffers() buffer_type.member_types.push_back(get_variable_data_type_id(var)); set_qualified_name(var.self, join(to_name(buffer_variable_id), ".", mbr_name)); } + else if (atomic_image_vars.count(var.self)) + { + // Emulate texture2D atomic operations. + // Don't set the qualified name: it's already set for this variable, + // and the code that references the buffer manually appends "_atomic" + // to the name. + uint32_t offset = ir.increase_bound_by(2); + uint32_t atomic_type_id = offset; + uint32_t type_ptr_id = offset + 1; + + SPIRType atomic_type; + atomic_type.basetype = SPIRType::AtomicCounter; + atomic_type.width = 32; + atomic_type.vecsize = 1; + set(atomic_type_id, atomic_type); + + atomic_type.pointer = true; + atomic_type.pointer_depth++; + atomic_type.parent_type = atomic_type_id; + atomic_type.storage = StorageClassStorageBuffer; + auto &atomic_ptr_type = set(type_ptr_id, atomic_type); + atomic_ptr_type.self = atomic_type_id; + + buffer_type.member_types.push_back(type_ptr_id); + } else { // Resources will be declared as pointers not references, so automatically dereference as appropriate. @@ -12994,3 +15213,13 @@ bool CompilerMSL::using_builtin_array() const { return msl_options.force_native_arrays || is_using_builtin_array; } + +void CompilerMSL::set_combined_sampler_suffix(const char *suffix) +{ + sampler_name_suffix = suffix; +} + +const char *CompilerMSL::get_combined_sampler_suffix() const +{ + return sampler_name_suffix.c_str(); +} diff --git a/src/libraries/spirv_cross/spirv_msl.hpp b/src/libraries/spirv_cross/spirv_msl.hpp index b786a1438..52e96761e 100644 --- a/src/libraries/spirv_cross/spirv_msl.hpp +++ b/src/libraries/spirv_cross/spirv_msl.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 The Brenwill Workshop Ltd. + * Copyright 2016-2021 The Brenwill Workshop Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #ifndef SPIRV_CROSS_MSL_HPP #define SPIRV_CROSS_MSL_HPP @@ -27,29 +34,44 @@ namespace SPIRV_CROSS_NAMESPACE { -// Indicates the format of the vertex attribute. Currently limited to specifying -// if the attribute is an 8-bit unsigned integer, 16-bit unsigned integer, or +// Indicates the format of a shader input. Currently limited to specifying +// if the input is an 8-bit unsigned integer, 16-bit unsigned integer, or // some other format. -enum MSLVertexFormat +enum MSLShaderInputFormat { - MSL_VERTEX_FORMAT_OTHER = 0, - MSL_VERTEX_FORMAT_UINT8 = 1, - MSL_VERTEX_FORMAT_UINT16 = 2, - MSL_VERTEX_FORMAT_INT_MAX = 0x7fffffff + MSL_SHADER_INPUT_FORMAT_OTHER = 0, + MSL_SHADER_INPUT_FORMAT_UINT8 = 1, + MSL_SHADER_INPUT_FORMAT_UINT16 = 2, + MSL_SHADER_INPUT_FORMAT_ANY16 = 3, + MSL_SHADER_INPUT_FORMAT_ANY32 = 4, + + // Deprecated aliases. + MSL_VERTEX_FORMAT_OTHER = MSL_SHADER_INPUT_FORMAT_OTHER, + MSL_VERTEX_FORMAT_UINT8 = MSL_SHADER_INPUT_FORMAT_UINT8, + MSL_VERTEX_FORMAT_UINT16 = MSL_SHADER_INPUT_FORMAT_UINT16, + + MSL_SHADER_INPUT_FORMAT_INT_MAX = 0x7fffffff }; -// Defines MSL characteristics of a vertex attribute at a particular location. +// Defines MSL characteristics of an input variable at a particular location. // After compilation, it is possible to query whether or not this location was used. -struct MSLVertexAttr +// If vecsize is nonzero, it must be greater than or equal to the vecsize declared in the shader, +// or behavior is undefined. +struct MSLShaderInput { uint32_t location = 0; - MSLVertexFormat format = MSL_VERTEX_FORMAT_OTHER; + MSLShaderInputFormat format = MSL_SHADER_INPUT_FORMAT_OTHER; spv::BuiltIn builtin = spv::BuiltInMax; + uint32_t vecsize = 0; }; // Matches the binding index of a MSL resource for a binding within a descriptor set. // Taken together, the stage, desc_set and binding combine to form a reference to a resource -// descriptor used in a particular shading stage. +// descriptor used in a particular shading stage. The count field indicates the number of +// resources consumed by this binding, if the binding represents an array of resources. +// If the resource array is a run-time-sized array, which are legal in GLSL or SPIR-V, this value +// will be used to declare the array size in MSL, which does not support run-time-sized arrays. +// For resources that are not held in a run-time-sized array, the count field does not need to be populated. // If using MSL 2.0 argument buffers, the descriptor set is not marked as a discrete descriptor set, // and (for iOS only) the resource is not a storage image (sampled != 2), the binding reference we // remap to will become an [[id(N)]] attribute within the "descriptor set" argument buffer structure. @@ -60,6 +82,7 @@ struct MSLResourceBinding spv::ExecutionModel stage = spv::ExecutionModelMax; uint32_t desc_set = 0; uint32_t binding = 0; + uint32_t count = 0; uint32_t msl_buffer = 0; uint32_t msl_texture = 0; uint32_t msl_sampler = 0; @@ -238,6 +261,9 @@ static const uint32_t kArgumentBufferBinding = ~(3u); static const uint32_t kMaxArgumentBuffers = 8; +// The arbitrary maximum for the nesting of array of array copies. +static const uint32_t kArrayCopyMultidimMax = 6; + // Decompiles SPIR-V to Metal Shading Language class CompilerMSL : public CompilerGLSL { @@ -254,6 +280,8 @@ public: Platform platform = macOS; uint32_t msl_version = make_msl_version(1, 2); uint32_t texel_buffer_texture_width = 4096; // Width of 2D Metal textures used as 1D texel buffers + uint32_t r32ui_linear_texture_alignment = 4; + uint32_t r32ui_alignment_constant_id = 65535; uint32_t swizzle_buffer_index = 30; uint32_t indirect_params_buffer_index = 29; uint32_t shader_output_buffer_index = 28; @@ -262,9 +290,15 @@ public: uint32_t buffer_size_buffer_index = 25; uint32_t view_mask_buffer_index = 24; uint32_t dynamic_offsets_buffer_index = 23; + uint32_t shader_input_buffer_index = 22; + uint32_t shader_index_buffer_index = 21; uint32_t shader_input_wg_index = 0; uint32_t device_index = 0; uint32_t enable_frag_output_mask = 0xffffffff; + // Metal doesn't allow setting a fixed sample mask directly in the pipeline. + // We can evade this restriction by ANDing the internal sample_mask output + // of the shader with the additional fixed sample mask. + uint32_t additional_fixed_sample_mask = 0xffffffff; bool enable_point_size_builtin = true; bool enable_frag_depth_builtin = true; bool enable_frag_stencil_ref_builtin = true; @@ -273,6 +307,7 @@ public: bool swizzle_texture_samples = false; bool tess_domain_origin_lower_left = false; bool multiview = false; + bool multiview_layered_rendering = true; bool view_index_from_device_index = false; bool dispatch_base = false; bool texture_1D_as_2D = false; @@ -292,7 +327,7 @@ public: bool ios_support_base_vertex_instance = false; // Use Metal's native frame-buffer fetch API for subpass inputs. - bool ios_use_framebuffer_fetch_subpasses = false; + bool use_framebuffer_fetch_subpasses = false; // Enables use of "fma" intrinsic for invariant float math bool invariant_float_math = false; @@ -320,6 +355,64 @@ public: // can be read in subsequent stages. bool enable_clip_distance_user_varying = true; + // In a tessellation control shader, assume that more than one patch can be processed in a + // single workgroup. This requires changes to the way the InvocationId and PrimitiveId + // builtins are processed, but should result in more efficient usage of the GPU. + bool multi_patch_workgroup = false; + + // If set, a vertex shader will be compiled as part of a tessellation pipeline. + // It will be translated as a compute kernel, so it can use the global invocation ID + // to index the output buffer. + bool vertex_for_tessellation = false; + + // Assume that SubpassData images have multiple layers. Layered input attachments + // are addressed relative to the Layer output from the vertex pipeline. This option + // has no effect with multiview, since all input attachments are assumed to be layered + // and will be addressed using the current ViewIndex. + bool arrayed_subpass_input = false; + + // Whether to use SIMD-group or quadgroup functions to implement group nnon-uniform + // operations. Some GPUs on iOS do not support the SIMD-group functions, only the + // quadgroup functions. + bool ios_use_simdgroup_functions = false; + + // If set, the subgroup size will be assumed to be one, and subgroup-related + // builtins and operations will be emitted accordingly. This mode is intended to + // be used by MoltenVK on hardware/software configurations which do not provide + // sufficient support for subgroups. + bool emulate_subgroups = false; + + // If nonzero, a fixed subgroup size to assume. Metal, similarly to VK_EXT_subgroup_size_control, + // allows the SIMD-group size (aka thread execution width) to vary depending on + // register usage and requirements. In certain circumstances--for example, a pipeline + // in MoltenVK without VK_PIPELINE_SHADER_STAGE_CREATE_ALLOW_VARYING_SUBGROUP_SIZE_BIT_EXT-- + // this is undesirable. This fixes the value of the SubgroupSize builtin, instead of + // mapping it to the Metal builtin [[thread_execution_width]]. If the thread + // execution width is reduced, the extra invocations will appear to be inactive. + // If zero, the SubgroupSize will be allowed to vary, and the builtin will be mapped + // to the Metal [[thread_execution_width]] builtin. + uint32_t fixed_subgroup_size = 0; + + enum class IndexType + { + None = 0, + UInt16 = 1, + UInt32 = 2 + }; + + // The type of index in the index buffer, if present. For a compute shader, Metal + // requires specifying the indexing at pipeline creation, rather than at draw time + // as with graphics pipelines. This means we must create three different pipelines, + // for no indexing, 16-bit indices, and 32-bit indices. Each requires different + // handling for the gl_VertexIndex builtin. We may as well, then, create three + // different shaders for these three scenarios. + IndexType vertex_index_type = IndexType::None; + + // If set, a dummy [[sample_id]] input is added to a fragment shader if none is present. + // This will force the shader to run at sample rate, assuming Metal does not optimize + // the extra threads away. + bool force_sample_rate_shading = false; + bool is_ios() const { return platform == iOS; @@ -419,11 +512,10 @@ public: explicit CompilerMSL(const ParsedIR &ir); explicit CompilerMSL(ParsedIR &&ir); - // attr is a vertex attribute binding used to match - // vertex content locations to MSL attributes. If vertex attributes are provided, - // is_msl_vertex_attribute_used() will return true after calling ::compile() if - // the location was used by the MSL code. - void add_msl_vertex_attribute(const MSLVertexAttr &attr); + // input is a shader input description used to fix up shader input variables. + // If shader inputs are provided, is_msl_shader_input_used() will return true after + // calling ::compile() if the location was used by the MSL code. + void add_msl_shader_input(const MSLShaderInput &input); // resource is a resource binding to indicate the MSL buffer, // texture or sampler index to use for a particular SPIR-V description set @@ -455,8 +547,15 @@ public: // constant. Opt-in to this behavior here on a per set basis. void set_argument_buffer_device_address_space(uint32_t desc_set, bool device_storage); - // Query after compilation is done. This allows you to check if a location or set/binding combination was used by the shader. - bool is_msl_vertex_attribute_used(uint32_t location); + // Query after compilation is done. This allows you to check if an input location was used by the shader. + bool is_msl_shader_input_used(uint32_t location); + + // If not using add_msl_shader_input, it's possible + // that certain builtin attributes need to be automatically assigned locations. + // This is typical for tessellation builtin inputs such as tess levels, gl_Position, etc. + // This returns k_unknown_location if the location was explicitly assigned with + // add_msl_shader_input or the builtin is not used, otherwise returns N in [[attribute(N)]]. + uint32_t get_automatic_builtin_input_location(spv::BuiltIn builtin) const; // NOTE: Only resources which are remapped using add_msl_resource_binding will be reported here. // Constexpr samplers are always assumed to be emitted. @@ -475,6 +574,7 @@ public: // Same as get_automatic_msl_resource_binding, but should only be used for combined image samplers, in which case the // sampler's binding is returned instead. For any other resource type, -1 is returned. + // Secondary bindings are also used for the auxillary image atomic buffer. uint32_t get_automatic_msl_resource_binding_secondary(uint32_t id) const; // Same as get_automatic_msl_resource_binding, but should only be used for combined image samplers for multiplanar images, @@ -505,6 +605,9 @@ public: // to use for a particular location. The default is 4 if number of components is not overridden. void set_fragment_output_components(uint32_t location, uint32_t components); + void set_combined_sampler_suffix(const char *suffix); + const char *get_combined_sampler_suffix() const; + protected: // An enum of SPIR-V functions that are implemented in additional // source code that is added to the shader if necessary. @@ -544,12 +647,20 @@ protected: SPVFuncImplTextureSwizzle, SPVFuncImplGatherSwizzle, SPVFuncImplGatherCompareSwizzle, + SPVFuncImplSubgroupBroadcast, + SPVFuncImplSubgroupBroadcastFirst, SPVFuncImplSubgroupBallot, SPVFuncImplSubgroupBallotBitExtract, SPVFuncImplSubgroupBallotFindLSB, SPVFuncImplSubgroupBallotFindMSB, SPVFuncImplSubgroupBallotBitCount, SPVFuncImplSubgroupAllEqual, + SPVFuncImplSubgroupShuffle, + SPVFuncImplSubgroupShuffleXor, + SPVFuncImplSubgroupShuffleUp, + SPVFuncImplSubgroupShuffleDown, + SPVFuncImplQuadBroadcast, + SPVFuncImplQuadSwap, SPVFuncImplReflectScalar, SPVFuncImplRefractScalar, SPVFuncImplFaceForwardScalar, @@ -573,13 +684,11 @@ protected: SPVFuncImplConvertYCbCrBT601, SPVFuncImplConvertYCbCrBT2020, SPVFuncImplDynamicImageSampler, - - SPVFuncImplArrayCopyMultidimMax = 6 }; // If the underlying resource has been used for comparison then duplicate loads of that resource must be too // Use Metal's native frame-buffer fetch API for subpass inputs. - void emit_texture_op(const Instruction &i) override; + void emit_texture_op(const Instruction &i, bool sparse) override; void emit_binary_unord_op(uint32_t result_type, uint32_t result_id, uint32_t op0, uint32_t op1, const char *op); void emit_instruction(const Instruction &instr) override; void emit_glsl_op(uint32_t result_type, uint32_t result_id, uint32_t op, const uint32_t *args, @@ -590,7 +699,7 @@ protected: void emit_function_prototype(SPIRFunction &func, const Bitset &return_flags) override; void emit_sampled_image_op(uint32_t result_type, uint32_t result_id, uint32_t image_id, uint32_t samp_id) override; void emit_subgroup_op(const Instruction &i) override; - std::string to_texture_op(const Instruction &i, bool *forward, + std::string to_texture_op(const Instruction &i, bool sparse, bool *forward, SmallVector &inherited_expressions) override; void emit_fixup() override; std::string to_struct_member(const SPIRType &type, uint32_t member_type_id, uint32_t index, @@ -610,17 +719,12 @@ protected: std::string variable_decl(const SPIRType &type, const std::string &name, uint32_t id = 0) override; std::string image_type_glsl(const SPIRType &type, uint32_t id = 0) override; - std::string sampler_type(const SPIRType &type); + std::string sampler_type(const SPIRType &type, uint32_t id); std::string builtin_to_glsl(spv::BuiltIn builtin, spv::StorageClass storage) override; std::string to_func_call_arg(const SPIRFunction::Parameter &arg, uint32_t id) override; std::string to_name(uint32_t id, bool allow_alias = true) const override; - std::string to_function_name(VariableID img, const SPIRType &imgtype, bool is_fetch, bool is_gather, bool is_proj, - bool has_array_offsets, bool has_offset, bool has_grad, bool has_dref, uint32_t lod, - uint32_t minlod) override; - std::string to_function_args(VariableID img, const SPIRType &imgtype, bool is_fetch, bool is_gather, bool is_proj, - uint32_t coord, uint32_t coord_components, uint32_t dref, uint32_t grad_x, - uint32_t grad_y, uint32_t lod, uint32_t coffset, uint32_t offset, uint32_t bias, - uint32_t comp, uint32_t sample, uint32_t minlod, bool *p_forward) override; + std::string to_function_name(const TextureFunctionNameArguments &args) override; + std::string to_function_args(const TextureFunctionArguments &args, bool *p_forward) override; std::string to_initializer_expression(const SPIRVariable &var) override; std::string to_zero_initialized_expression(uint32_t type_id) override; @@ -639,6 +743,12 @@ protected: void declare_undefined_values() override; void declare_constant_arrays(); + void replace_illegal_entry_point_names(); + void sync_entry_point_aliases_and_names(); + + static const std::unordered_set &get_reserved_keyword_set(); + static const std::unordered_set &get_illegal_func_names(); + // Constant arrays of non-primitive types (i.e. matrices) won't link properly into Metal libraries void declare_complex_constant_arrays(); @@ -689,9 +799,9 @@ protected: void fix_up_interface_member_indices(spv::StorageClass storage, uint32_t ib_type_id); - void mark_location_as_used_by_shader(uint32_t location, spv::StorageClass storage); + void mark_location_as_used_by_shader(uint32_t location, const SPIRType &type, spv::StorageClass storage); uint32_t ensure_correct_builtin_type(uint32_t type_id, spv::BuiltIn builtin); - uint32_t ensure_correct_attribute_type(uint32_t type_id, uint32_t location, uint32_t num_components = 0); + uint32_t ensure_correct_input_type(uint32_t type_id, uint32_t location, uint32_t num_components = 0); void emit_custom_templates(); void emit_custom_functions(); @@ -699,6 +809,7 @@ protected: void emit_specialization_constants_and_structs(); void emit_interface_block(uint32_t ib_var_id); bool maybe_emit_array_assignment(uint32_t id_lhs, uint32_t id_rhs); + uint32_t get_resource_array_size(uint32_t id) const; void fix_up_shader_inputs_outputs(); @@ -713,6 +824,8 @@ protected: std::string to_sampler_expression(uint32_t id); std::string to_swizzle_expression(uint32_t id); std::string to_buffer_size_expression(uint32_t id); + bool is_sample_rate() const; + bool is_direct_input_builtin(spv::BuiltIn builtin); std::string builtin_qualifier(spv::BuiltIn builtin); std::string builtin_type_decl(spv::BuiltIn builtin, uint32_t id = 0); std::string built_in_func_arg(spv::BuiltIn builtin, bool prefix_comma); @@ -720,7 +833,11 @@ protected: std::string argument_decl(const SPIRFunction::Parameter &arg); std::string round_fp_tex_coords(std::string tex_coords, bool coord_is_fp); uint32_t get_metal_resource_index(SPIRVariable &var, SPIRType::BaseType basetype, uint32_t plane = 0); - uint32_t get_ordered_member_location(uint32_t type_id, uint32_t index, uint32_t *comp = nullptr); + uint32_t get_member_location(uint32_t type_id, uint32_t index, uint32_t *comp = nullptr) const; + uint32_t get_or_allocate_builtin_input_member_location(spv::BuiltIn builtin, + uint32_t type_id, uint32_t index, uint32_t *comp = nullptr); + + uint32_t get_physical_tess_level_array_size(spv::BuiltIn builtin) const; // MSL packing rules. These compute the effective packing rules as observed by the MSL compiler in the MSL output. // These values can change depending on various extended decorations which control packing rules. @@ -735,7 +852,13 @@ protected: uint32_t get_declared_struct_member_matrix_stride_msl(const SPIRType &struct_type, uint32_t index) const; uint32_t get_declared_struct_member_alignment_msl(const SPIRType &struct_type, uint32_t index) const; + uint32_t get_declared_input_size_msl(const SPIRType &struct_type, uint32_t index) const; + uint32_t get_declared_input_array_stride_msl(const SPIRType &struct_type, uint32_t index) const; + uint32_t get_declared_input_matrix_stride_msl(const SPIRType &struct_type, uint32_t index) const; + uint32_t get_declared_input_alignment_msl(const SPIRType &struct_type, uint32_t index) const; + const SPIRType &get_physical_member_type(const SPIRType &struct_type, uint32_t index) const; + SPIRType get_presumed_input_type(const SPIRType &struct_type, uint32_t index) const; uint32_t get_declared_struct_size_msl(const SPIRType &struct_type, bool ignore_alignment = false, bool ignore_padding = false) const; @@ -754,6 +877,8 @@ protected: SPIRType &get_patch_stage_in_struct_type(); SPIRType &get_patch_stage_out_struct_type(); std::string get_tess_factor_struct_name(); + SPIRType &get_uint_type(); + uint32_t get_uint_type_id(); void emit_atomic_func_op(uint32_t result_type, uint32_t result_id, const char *op, uint32_t mem_order_1, uint32_t mem_order_2, bool has_mem_order_2, uint32_t op0, uint32_t op1 = 0, bool op1_is_pointer = false, bool op1_is_literal = false, uint32_t op2 = 0); @@ -768,6 +893,7 @@ protected: void emit_entry_point_declarations() override; uint32_t builtin_frag_coord_id = 0; uint32_t builtin_sample_id_id = 0; + uint32_t builtin_sample_mask_id = 0; uint32_t builtin_vertex_idx_id = 0; uint32_t builtin_base_vertex_id = 0; uint32_t builtin_instance_idx_id = 0; @@ -779,17 +905,26 @@ protected: uint32_t builtin_subgroup_invocation_id_id = 0; uint32_t builtin_subgroup_size_id = 0; uint32_t builtin_dispatch_base_id = 0; + uint32_t builtin_stage_input_size_id = 0; + uint32_t builtin_local_invocation_index_id = 0; + uint32_t builtin_workgroup_size_id = 0; uint32_t swizzle_buffer_id = 0; uint32_t buffer_size_buffer_id = 0; uint32_t view_mask_buffer_id = 0; uint32_t dynamic_offsets_buffer_id = 0; + uint32_t uint_type_id = 0; - void bitcast_to_builtin_store(uint32_t target_id, std::string &expr, const SPIRType &expr_type) override; - void bitcast_from_builtin_load(uint32_t source_id, std::string &expr, const SPIRType &expr_type) override; + bool does_shader_write_sample_mask = false; + + void cast_to_builtin_store(uint32_t target_id, std::string &expr, const SPIRType &expr_type) override; + void cast_from_builtin_load(uint32_t source_id, std::string &expr, const SPIRType &expr_type) override; void emit_store_statement(uint32_t lhs_expression, uint32_t rhs_expression) override; void analyze_sampled_image_usage(); + void prepare_access_chain_for_scalar_access(std::string &expr, const SPIRType &type, spv::StorageClass storage, + bool &is_packed) override; + void fix_up_interpolant_access_chain(const uint32_t *ops, uint32_t length); bool emit_tessellation_access_chain(const uint32_t *ops, uint32_t length); bool emit_tessellation_io_load(uint32_t result_type, uint32_t id, uint32_t ptr); bool is_out_of_bounds_tessellation_level(uint32_t id_lhs); @@ -802,15 +937,18 @@ protected: Options msl_options; std::set spv_function_implementations; - std::unordered_map vtx_attrs_by_location; - std::unordered_map vtx_attrs_by_builtin; - std::unordered_set vtx_attrs_in_use; + // Must be ordered to ensure declarations are in a specific order. + std::map inputs_by_location; + std::unordered_map inputs_by_builtin; + std::unordered_set location_inputs_in_use; std::unordered_map fragment_output_components; + std::unordered_map builtin_to_automatic_input_location; std::set pragma_lines; std::set typedef_lines; SmallVector vars_needing_early_declaration; std::unordered_map, InternalHasher> resource_bindings; + uint32_t type_to_location_count(const SPIRType &type) const; uint32_t next_metal_resource_index_buffer = 0; uint32_t next_metal_resource_index_texture = 0; @@ -847,6 +985,8 @@ protected: bool used_swizzle_buffer = false; bool added_builtin_tess_level = false; bool needs_subgroup_invocation_id = false; + bool needs_subgroup_size = false; + bool needs_sample_id = false; std::string qual_pos_var_name; std::string stage_in_var_name = "in"; std::string stage_out_var_name = "out"; @@ -857,9 +997,11 @@ protected: std::string buffer_size_name_suffix = "BufferSize"; std::string plane_name_suffix = "Plane"; std::string input_wg_var_name = "gl_in"; + std::string input_buffer_var_name = "spvIn"; std::string output_buffer_var_name = "spvOut"; std::string patch_output_buffer_var_name = "spvPatchOut"; std::string tess_factor_buffer_var_name = "spvTessLevel"; + std::string index_buffer_var_name = "spvIndices"; spv::Op previous_instruction_opcode = spv::OpNop; // Must be ordered since declaration is in a specific order. @@ -870,6 +1012,7 @@ protected: std::unordered_set buffers_requiring_array_length; SmallVector buffer_arrays; std::unordered_set atomic_image_vars; // Emulate texture2D atomic operations + std::unordered_set pull_model_inputs; // Must be ordered since array is in a specific order. std::map> buffers_requiring_dynamic_offset; @@ -886,7 +1029,9 @@ protected: bool descriptor_set_is_argument_buffer(uint32_t desc_set) const; uint32_t get_target_components_for_fragment_location(uint32_t location) const; - uint32_t build_extended_vector_type(uint32_t type_id, uint32_t components); + uint32_t build_extended_vector_type(uint32_t type_id, uint32_t components, + SPIRType::BaseType basetype = SPIRType::Unknown); + uint32_t build_msl_interpolant_type(uint32_t type_id, bool is_noperspective); bool suppress_missing_prototypes = false; @@ -895,6 +1040,9 @@ protected: void activate_argument_buffer_resources(); bool type_is_msl_framebuffer_fetch(const SPIRType &type) const; + bool type_is_pointer(const SPIRType &type) const; + bool type_is_pointer_to_pointer(const SPIRType &type) const; + bool is_supported_argument_buffer_type(const SPIRType &type) const; // OpcodeHandler that handles several MSL preprocessing operations. struct OpCodePreprocessor : OpcodeHandler @@ -915,6 +1063,8 @@ protected: bool uses_atomics = false; bool uses_resource_write = false; bool needs_subgroup_invocation_id = false; + bool needs_subgroup_size = false; + bool needs_sample_id = false; }; // OpcodeHandler that scans for uses of sampled images diff --git a/src/libraries/spirv_cross/spirv_parser.cpp b/src/libraries/spirv_cross/spirv_parser.cpp index 49c429cd1..86f7fd350 100644 --- a/src/libraries/spirv_cross/spirv_parser.cpp +++ b/src/libraries/spirv_cross/spirv_parser.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 Arm Limited + * Copyright 2018-2021 Arm Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #include "spirv_parser.hpp" #include @@ -133,6 +140,8 @@ void Parser::parse() SPIRV_CROSS_THROW("Function was not terminated."); if (current_block) SPIRV_CROSS_THROW("Block was not terminated."); + if (ir.default_entry_point == 0) + SPIRV_CROSS_THROW("There is no entry point in the SPIR-V module."); } const uint32_t *Parser::stream(const Instruction &instr) const @@ -623,10 +632,15 @@ void Parser::parse(const Instruction &instruction) { uint32_t id = ops[0]; - auto &base = get(ops[2]); + // Very rarely, we might receive a FunctionPrototype here. + // We won't be able to compile it, but we shouldn't crash when parsing. + // We should be able to reflect. + auto *base = maybe_get(ops[2]); auto &ptrbase = set(id); - ptrbase = base; + if (base) + ptrbase = *base; + ptrbase.pointer = true; ptrbase.pointer_depth++; ptrbase.storage = static_cast(ops[1]); @@ -634,7 +648,7 @@ void Parser::parse(const Instruction &instruction) if (ptrbase.storage == StorageClassAtomicCounter) ptrbase.basetype = SPIRType::AtomicCounter; - if (base.forward_pointer) + if (base && base->forward_pointer) forward_pointer_fixups.push_back({ id, ops[2] }); ptrbase.parent_type = ops[2]; @@ -715,7 +729,7 @@ void Parser::parse(const Instruction &instruction) break; } - case OpTypeRayQueryProvisionalKHR: + case OpTypeRayQueryKHR: { uint32_t id = ops[0]; auto &type = set(id); @@ -980,6 +994,22 @@ void Parser::parse(const Instruction &instruction) break; } + case OpTerminateRayKHR: + // NV variant is not a terminator. + if (!current_block) + SPIRV_CROSS_THROW("Trying to end a non-existing block."); + current_block->terminator = SPIRBlock::TerminateRay; + current_block = nullptr; + break; + + case OpIgnoreIntersectionKHR: + // NV variant is not a terminator. + if (!current_block) + SPIRV_CROSS_THROW("Trying to end a non-existing block."); + current_block->terminator = SPIRBlock::IgnoreIntersection; + current_block = nullptr; + break; + case OpReturn: { if (!current_block) diff --git a/src/libraries/spirv_cross/spirv_parser.hpp b/src/libraries/spirv_cross/spirv_parser.hpp index 7b3eddedd..7f4a7d856 100644 --- a/src/libraries/spirv_cross/spirv_parser.hpp +++ b/src/libraries/spirv_cross/spirv_parser.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 Arm Limited + * Copyright 2018-2021 Arm Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #ifndef SPIRV_CROSS_PARSER_HPP #define SPIRV_CROSS_PARSER_HPP diff --git a/src/libraries/spirv_cross/spirv_reflect.cpp b/src/libraries/spirv_cross/spirv_reflect.cpp index 1fa2b2ed5..447bff1c9 100644 --- a/src/libraries/spirv_cross/spirv_reflect.cpp +++ b/src/libraries/spirv_cross/spirv_reflect.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 Bradley Austin Davis + * Copyright 2018-2021 Bradley Austin Davis * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #include "spirv_reflect.hpp" #include "spirv_glsl.hpp" #include @@ -267,7 +274,6 @@ string CompilerReflection::compile() json_stream = std::make_shared(); json_stream->set_current_locale_radix_character(current_locale_radix_character); json_stream->begin_json_object(); - fixup_type_alias(); reorder_type_alias(); emit_entry_points(); emit_types(); @@ -305,8 +311,8 @@ void CompilerReflection::emit_types() else if (type_is_reference(type)) { if (!naturally_emit_type(this->get(type.parent_type)) && - find(physical_pointee_types.begin(), physical_pointee_types.end(), - type.parent_type) == physical_pointee_types.end()) + find(physical_pointee_types.begin(), physical_pointee_types.end(), type.parent_type) == + physical_pointee_types.end()) { physical_pointee_types.push_back(type.parent_type); } @@ -327,9 +333,6 @@ void CompilerReflection::emit_type(uint32_t type_id, bool &emitted_open_tag) auto &type = get(type_id); auto name = type_to_glsl(type); - if (type.type_alias != TypeID(0)) - return; - if (!emitted_open_tag) { json_stream->emit_json_key_object("types"); @@ -651,7 +654,7 @@ void CompilerReflection::emit_specialization_constants() return; json_stream->emit_json_key_array("specialization_constants"); - for (const auto spec_const : specialization_constants) + for (const auto &spec_const : specialization_constants) { auto &c = get(spec_const.id); auto type = get(c.constant_type); diff --git a/src/libraries/spirv_cross/spirv_reflect.hpp b/src/libraries/spirv_cross/spirv_reflect.hpp index 9f60e72ee..8acbd2804 100644 --- a/src/libraries/spirv_cross/spirv_reflect.hpp +++ b/src/libraries/spirv_cross/spirv_reflect.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 Bradley Austin Davis + * Copyright 2018-2021 Bradley Austin Davis * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,13 @@ * limitations under the License. */ +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + * SPDX-License-Identifier: Apache-2.0 OR MIT. + */ + #ifndef SPIRV_CROSS_REFLECT_HPP #define SPIRV_CROSS_REFLECT_HPP