MoltenVKShaderConverter tool add MSL version and platform command-line options.

Add SPIRVToMSLConverterOptions::platform to track platform. Default to build platform.
Update default SPIRVToMSLConverterOptions MSL version to 2.1.
MoltenVKShaderConverter test MSL compilation use same MSL version as conversion.
Default min perf tracking value to 0.0.
SPIRVToMSLConverter.h reference spirv.hpp via SPIRV-Cross framework.
Update What's New document.
diff --git a/Docs/Whats_New.md b/Docs/Whats_New.md
index a4cc831..f35087c 100644
--- a/Docs/Whats_New.md
+++ b/Docs/Whats_New.md
@@ -13,6 +13,17 @@
 
 
 
+MoltenVK 1.0.35
+---------------
+
+Released TBD
+
+- Don't use setVertexBytes() for passing tessellation vertex counts.
+- Fix zero local threadgroup size in indirect tessellated rendering.
+- MoltenVKShaderConverter tool add MSL version and platform command-line options.
+
+
+
 MoltenVK 1.0.34
 ---------------
 
@@ -115,7 +126,7 @@
   runtime environment variable.
   Set MSL version for shader compiling from Metal feature set.
 - Don't warn on identity swizzles when `fullImageViewSwizzle` config setting is enabled.
-- Track version of spvAux buffer struct in SPRIV-Cross and fail build if different
+- Track version of spvAux buffer struct in SPIRV-Cross and fail build if different
   than version expected by MoltenVK.
 - Add static and dynamic libraries to MoltenVKShaderConverter project.
 - Fix crash from use of MTLDevice registryID on early OS versions.
@@ -792,7 +803,7 @@
 - Add build and runtime OS and device requirements to documentation.
 - Add Compliance and Contribution sections to README.md.
 - Remove executable permissions from non-executable files.
-- Update to latest SPRIV-Cross.
+- Update to latest SPIRV-Cross.
 - Update copyright dates to 2018.
 
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
index a791e8e..21edace 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
@@ -1943,7 +1943,9 @@
     lock_guard<mutex> lock(_perfLock);
 
 	double currInterval = mvkGetElapsedMilliseconds(startTime, endTime);
-    shaderCompilationEvent.minimumDuration = min(currInterval, shaderCompilationEvent.minimumDuration);
+	shaderCompilationEvent.minimumDuration = ((shaderCompilationEvent.minimumDuration == 0.0)
+											  ? currInterval :
+											  min(currInterval, shaderCompilationEvent.minimumDuration));
     shaderCompilationEvent.maximumDuration = max(currInterval, shaderCompilationEvent.maximumDuration);
     double totalInterval = (shaderCompilationEvent.averageDuration * shaderCompilationEvent.count++) + currInterval;
     shaderCompilationEvent.averageDuration = totalInterval / shaderCompilationEvent.count;
@@ -2072,7 +2074,7 @@
     MVKPerformanceTracker initPerf;
     initPerf.count = 0;
     initPerf.averageDuration = 0.0;
-    initPerf.minimumDuration = numeric_limits<double>::max();
+    initPerf.minimumDuration = 0.0;
     initPerf.maximumDuration = 0.0;
 
 	_performanceStatistics.shaderCompilation.hashShaderCode = initPerf;
diff --git a/MoltenVKShaderConverter/MoltenVKSPIRVToMSLConverter/SPIRVToMSLConverter.cpp b/MoltenVKShaderConverter/MoltenVKSPIRVToMSLConverter/SPIRVToMSLConverter.cpp
index 305c6b5..a593c25 100644
--- a/MoltenVKShaderConverter/MoltenVKSPIRVToMSLConverter/SPIRVToMSLConverter.cpp
+++ b/MoltenVKShaderConverter/MoltenVKSPIRVToMSLConverter/SPIRVToMSLConverter.cpp
@@ -84,6 +84,15 @@
 	return verStr;
 }
 
+MVK_PUBLIC_SYMBOL mvk::SPIRVToMSLConverterOptions::Platform SPIRVToMSLConverterOptions::getNativePlatform() {
+#if MVK_MACOS
+	return SPIRVToMSLConverterOptions::macOS;
+#endif
+#if MVK_IOS
+	return SPIRVToMSLConverterOptions::iOS;
+#endif
+}
+
 MVK_PUBLIC_SYMBOL bool MSLVertexAttribute::matches(const MSLVertexAttribute& other) const {
     if (location != other.location) { return false; }
     if (mslBuffer != other.mslBuffer) { return false; }
@@ -182,6 +191,9 @@
 #pragma mark -
 #pragma mark SPIRVToMSLConverter
 
+// Return the SPIRV-Cross platform enum corresponding to a SPIRVToMSLConverterOptions platform enum value.
+SPIRV_CROSS_NAMESPACE::CompilerMSL::Options::Platform getCompilerMSLPlatform(SPIRVToMSLConverterOptions::Platform platform);
+
 // Populates the entry point with info extracted from the SPRI-V compiler.
 void populateEntryPoint(SPIRVEntryPoint& entryPoint, SPIRV_CROSS_NAMESPACE::Compiler* pCompiler, SPIRVToMSLConverterOptions& options);
 
@@ -238,14 +250,7 @@
 		// Establish the MSL options for the compiler
 		// This needs to be done in two steps...for CompilerMSL and its superclass.
 		auto mslOpts = pMSLCompiler->get_msl_options();
-
-#if MVK_MACOS
-		mslOpts.platform = SPIRV_CROSS_NAMESPACE::CompilerMSL::Options::macOS;
-#endif
-#if MVK_IOS
-		mslOpts.platform = SPIRV_CROSS_NAMESPACE::CompilerMSL::Options::iOS;
-#endif
-
+		mslOpts.platform = getCompilerMSLPlatform(context.options.platform);
 		mslOpts.msl_version = context.options.mslVersion;
 		mslOpts.texel_buffer_texture_width = context.options.texelBufferTextureWidth;
 		mslOpts.aux_buffer_index = context.options.auxBufferIndex;
@@ -441,6 +446,14 @@
 
 #pragma mark Support functions
 
+// Return the SPIRV-Cross platform enum corresponding to a SPIRVToMSLConverterOptions platform enum value.
+SPIRV_CROSS_NAMESPACE::CompilerMSL::Options::Platform getCompilerMSLPlatform(SPIRVToMSLConverterOptions::Platform platform) {
+	switch (platform) {
+		case SPIRVToMSLConverterOptions::macOS: return SPIRV_CROSS_NAMESPACE::CompilerMSL::Options::macOS;
+		case SPIRVToMSLConverterOptions::iOS: return SPIRV_CROSS_NAMESPACE::CompilerMSL::Options::iOS;
+	}
+}
+
 // Populate a workgroup size dimension.
 void populateWorkgroupDimension(SPIRVWorkgroupSizeDimension& wgDim, uint32_t size, SPIRV_CROSS_NAMESPACE::SpecializationConstant& spvSpecConst) {
 	wgDim.size = max(size, 1u);
diff --git a/MoltenVKShaderConverter/MoltenVKSPIRVToMSLConverter/SPIRVToMSLConverter.h b/MoltenVKShaderConverter/MoltenVKSPIRVToMSLConverter/SPIRVToMSLConverter.h
index 3f8800b..83e9339 100644
--- a/MoltenVKShaderConverter/MoltenVKSPIRVToMSLConverter/SPIRVToMSLConverter.h
+++ b/MoltenVKShaderConverter/MoltenVKSPIRVToMSLConverter/SPIRVToMSLConverter.h
@@ -19,7 +19,7 @@
 #ifndef __SPIRVToMSLConverter_h_
 #define __SPIRVToMSLConverter_h_ 1
 
-#include "../SPIRV-Cross/spirv.hpp"
+#include <SPIRV-Cross/spirv.hpp>
 #include <string>
 #include <vector>
 #include <unordered_map>
@@ -32,11 +32,18 @@
 
 	/** Options for converting SPIR-V to Metal Shading Language */
 	typedef struct SPIRVToMSLConverterOptions {
+
+		enum Platform {
+			iOS = 0,
+			macOS = 1
+		};
+
 		std::string entryPointName;
 		spv::ExecutionModel entryPointStage = spv::ExecutionModelMax;
 		spv::ExecutionMode tessPatchKind = spv::ExecutionModeMax;
 
-        uint32_t mslVersion = makeMSLVersion(2);
+        uint32_t mslVersion = makeMSLVersion(2, 1);
+		Platform platform = getNativePlatform();
 		uint32_t texelBufferTextureWidth = 4096;
 		uint32_t auxBufferIndex = 0;
 		uint32_t indirectParamsBufferIndex = 0;
@@ -81,6 +88,8 @@
 
 		static std::string printMSLVersion(uint32_t mslVersion, bool includePatch = false);
 
+		static Platform getNativePlatform();
+
     } SPIRVToMSLConverterOptions;
 
 	/**
diff --git a/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/project.pbxproj b/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/project.pbxproj
index d9ba4e8..6919f29 100644
--- a/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/project.pbxproj
+++ b/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/project.pbxproj
@@ -920,6 +920,7 @@
 				GCC_WARN_UNUSED_VARIABLE = NO;
 				HEADER_SEARCH_PATHS = (
 					"$(inherited)",
+					"\"$(SRCROOT)\"",
 					"\"$(SRCROOT)/glslang/External/spirv-tools/include\"",
 					"\"$(SRCROOT)/glslang/External/spirv-tools/external/spirv-headers/include\"",
 				);
@@ -973,6 +974,7 @@
 				GCC_WARN_UNUSED_VARIABLE = NO;
 				HEADER_SEARCH_PATHS = (
 					"$(inherited)",
+					"\"$(SRCROOT)\"",
 					"\"$(SRCROOT)/glslang/External/spirv-tools/include\"",
 					"\"$(SRCROOT)/glslang/External/spirv-tools/external/spirv-headers/include\"",
 				);
diff --git a/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/xcshareddata/xcschemes/MoltenVKShaderConverter.xcscheme b/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/xcshareddata/xcschemes/MoltenVKShaderConverter.xcscheme
index e1d765f..0b76225 100644
--- a/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/xcshareddata/xcschemes/MoltenVKShaderConverter.xcscheme
+++ b/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/xcshareddata/xcschemes/MoltenVKShaderConverter.xcscheme
@@ -104,6 +104,22 @@
             isEnabled = "YES">
          </CommandLineArgument>
          <CommandLineArgument
+            argument = "-mv"
+            isEnabled = "NO">
+         </CommandLineArgument>
+         <CommandLineArgument
+            argument = "2.1"
+            isEnabled = "NO">
+         </CommandLineArgument>
+         <CommandLineArgument
+            argument = "-mp"
+            isEnabled = "NO">
+         </CommandLineArgument>
+         <CommandLineArgument
+            argument = "ios"
+            isEnabled = "NO">
+         </CommandLineArgument>
+         <CommandLineArgument
             argument = "-XS"
             isEnabled = "NO">
          </CommandLineArgument>
diff --git a/MoltenVKShaderConverter/MoltenVKShaderConverterTool/MoltenVKShaderConverterTool.cpp b/MoltenVKShaderConverter/MoltenVKShaderConverterTool/MoltenVKShaderConverterTool.cpp
index 505fa83..a193e81 100644
--- a/MoltenVKShaderConverter/MoltenVKShaderConverterTool/MoltenVKShaderConverterTool.cpp
+++ b/MoltenVKShaderConverter/MoltenVKShaderConverterTool/MoltenVKShaderConverterTool.cpp
@@ -45,7 +45,7 @@
 
 void MVKPerformanceTracker::accumulate(uint64_t startTime, uint64_t endTime) {
 	double currInterval = mvkGetElapsedMilliseconds(startTime, endTime);
-	minimumDuration = min(currInterval, minimumDuration);
+	minimumDuration = (minimumDuration == 0.0) ? currInterval : min(currInterval, minimumDuration);
 	maximumDuration = max(currInterval, maximumDuration);
 	double totalInterval = (averageDuration * count++) + currInterval;
 	averageDuration = totalInterval / count;
@@ -57,6 +57,8 @@
 
 
 int MoltenVKShaderConverterTool::run() {
+	if ( !_isActive ) { return EXIT_FAILURE; }
+
 	bool success = false;
 	if ( !_directoryPath.empty() ) {
 		string errMsg;
@@ -203,6 +205,8 @@
 
 	// Derive the context under which conversion will occur
 	SPIRVToMSLConverterContext mslContext;
+	mslContext.options.platform = _mslPlatform;
+	mslContext.options.setMSLVersion(_mslVersionMajor, _mslVersionMinor, _mslVersionPatch);
 	mslContext.options.shouldFlipVertexY = _shouldFlipVertexY;
 
 	SPIRVToMSLConverter spvConverter;
@@ -227,9 +231,9 @@
 	const string& msl = spvConverter.getMSL();
 
 	string compileErrMsg;
-	bool wasCompiled = compile(msl, compileErrMsg);
+	bool wasCompiled = compile(msl, compileErrMsg, _mslVersionMajor, _mslVersionMinor, _mslVersionPatch);
 	if (compileErrMsg.size() > 0) {
-		string preamble = wasCompiled ? "is valid but the validation compilation produced warnings " : "failed a validation compilation ";
+		string preamble = wasCompiled ? "is valid but the validation compilation produced warnings: " : "failed a validation compilation: ";
 		compileErrMsg = "Generated MSL " + preamble + compileErrMsg;
 		log(compileErrMsg.c_str());
 	} else {
@@ -305,6 +309,12 @@
 	log("                       The optional path parameter specifies the path to a single");
 	log("                       file to contain the MSL code. When using the -d option,");
 	log("                       the path parameter is ignored.");
+	log("  -mv mslVersion     - MSL version to output.");
+	log("                       Must be in form n[.n][.n] (eg. 2, 2.1, or 2.1.0).");
+	log("                       Defaults to the most recent MSL version for the platform");
+	log("                       on which this tool is executed.");
+	log("  -mp mslPlatform    - MSL platform. Must be one of macos or ios.");
+	log("                       Defaults to the platform on which this tool is executed (macos).");
 	log("  -t shaderType      - Shader type: vertex or fragment. Must be one of v, f, or c.");
 	log("                       May be omitted to auto-detect.");
 	log("  -c                 - Combine the GLSL and converted Metal Shader source code");
@@ -363,7 +373,6 @@
 	extractTokens(_defaultSPIRVShaderExtns, _spvFileExtns);
 	_origPathExtnSep = "_";
 	_shaderStage = kMVKShaderStageAuto;
-	_isActive = false;
 	_shouldUseDirectoryRecursion = false;
 	_shouldReadGLSL = false;
 	_shouldReadSPIRV = false;
@@ -375,6 +384,11 @@
 	_shouldLogConversions = false;
 	_shouldReportPerformance = false;
 
+	_mslVersionMajor = 2;
+	_mslVersionMinor = 1;
+	_mslVersionPatch = 0;
+	_mslPlatform = SPIRVToMSLConverterOptions::getNativePlatform();
+
 	_isActive = parseArgs(argc, argv);
 	if ( !_isActive ) { showUsage(); }
 }
@@ -427,6 +441,39 @@
 			continue;
 		}
 
+		if (equal(arg, "-mv", true)) {
+			int optIdx = argIdx;
+			string mslVerStr;
+			argIdx = optionalParam(mslVerStr, argIdx, argc, argv);
+			if (argIdx == optIdx || mslVerStr.length() == 0) { return false; }
+			vector<uint32_t> mslVerTokens;
+			extractTokens(mslVerStr, mslVerTokens);
+			auto tknCnt = mslVerTokens.size();
+			_mslVersionMajor = (tknCnt > 0) ? mslVerTokens[0] : 0;
+			_mslVersionMinor = (tknCnt > 1) ? mslVerTokens[1] : 0;
+			_mslVersionPatch = (tknCnt > 2) ? mslVerTokens[2] : 0;
+			continue;
+		}
+
+		if (equal(arg, "-mp", true)) {
+			int optIdx = argIdx;
+			string shdrTypeStr;
+			argIdx = optionalParam(shdrTypeStr, argIdx, argc, argv);
+			if (argIdx == optIdx || shdrTypeStr.length() == 0) { return false; }
+
+			switch (shdrTypeStr.front()) {
+				case 'm':
+					_mslPlatform = SPIRVToMSLConverterOptions::macOS;
+					break;
+				case 'i':
+					_mslPlatform = SPIRVToMSLConverterOptions::iOS;
+					break;
+				default:
+					return false;
+			}
+			continue;
+		}
+
 		if (equal(arg, "-t", true)) {
 			int optIdx = argIdx;
 			string shdrTypeStr;
@@ -444,7 +491,7 @@
 					_shaderStage = kMVKShaderStageCompute;
 					break;
 				default:
-					break;
+					return false;
 			}
 			continue;
 		}
@@ -577,6 +624,14 @@
 	split(tokens, str, " \t\n\f", false);
 }
 
+void mvk::extractTokens(string str, vector<uint32_t>& tokens) {
+	vector<string> stringTokens;
+	split(stringTokens, str, ".", false);
+	for (auto& st : stringTokens) {
+		tokens.push_back((uint32_t)strtol(st.c_str(), nullptr, 0));
+	}
+}
+
 // Compares the specified characters ignoring case.
 static bool compareIgnoringCase(unsigned char a, unsigned char b) {
 	return tolower(a) == tolower(b);
diff --git a/MoltenVKShaderConverter/MoltenVKShaderConverterTool/MoltenVKShaderConverterTool.h b/MoltenVKShaderConverter/MoltenVKShaderConverterTool/MoltenVKShaderConverterTool.h
index 1747b34..1850433 100644
--- a/MoltenVKShaderConverter/MoltenVKShaderConverterTool/MoltenVKShaderConverterTool.h
+++ b/MoltenVKShaderConverter/MoltenVKShaderConverterTool/MoltenVKShaderConverterTool.h
@@ -20,6 +20,7 @@
 
 
 #include "GLSLConversion.h"
+#include "SPIRVToMSLConverter.h"
 #include <string>
 #include <vector>
 
@@ -28,9 +29,9 @@
 
 	typedef struct {
 		uint32_t count = 0;
-		double averageDuration = 0;
-		double minimumDuration = std::numeric_limits<double>::max();
-		double maximumDuration = 0;
+		double averageDuration = 0.0;
+		double minimumDuration = 0.0;
+		double maximumDuration = 0.0;
 
 		uint64_t getTimestamp();
 		void accumulate(uint64_t startTime, uint64_t endTime = 0);
@@ -102,6 +103,10 @@
 		MVKShaderStage _shaderStage;
 		MVKPerformanceTracker _glslConversionPerformance;
 		MVKPerformanceTracker _spvConversionPerformance;
+		uint32_t _mslVersionMajor;
+		uint32_t _mslVersionMinor;
+		uint32_t _mslVersionPatch;
+		SPIRVToMSLConverterOptions::Platform _mslPlatform;
 		bool _isActive;
 		bool _shouldUseDirectoryRecursion;
 		bool _shouldReadGLSL;
@@ -121,10 +126,16 @@
 
 	/**
 	 * Extracts whitespace-delimited tokens from the specified string and
-	 * appends them to the specified vector. The vector is not cleared first.
+	 * appends them to the specified vector. The vector is cleared first.
 	 */
 	void extractTokens(std::string str, std::vector<std::string>& tokens);
 
+	/**
+	 * Extracts period-delimited tokens from the specified string and
+	 * appends them to the specified vector. The vector is cleared first.
+	 */
+	void extractTokens(std::string str, std::vector<uint32_t>& tokens);
+
 	/** Compares the specified strings, with or without sensitivity to case. */
 	bool equal(std::string const& a, std::string const& b, bool checkCase = true);
 
diff --git a/MoltenVKShaderConverter/MoltenVKShaderConverterTool/OSSupport.h b/MoltenVKShaderConverter/MoltenVKShaderConverterTool/OSSupport.h
index 2f27f32..cb5f7bc 100644
--- a/MoltenVKShaderConverter/MoltenVKShaderConverterTool/OSSupport.h
+++ b/MoltenVKShaderConverter/MoltenVKShaderConverterTool/OSSupport.h
@@ -48,6 +48,10 @@
 	 * If unsuccessful, the return value will be false and the errMsg will contain an
 	 * error message. Otherwise the return value will be true and the errMsg will be empty.
 	 */
-	bool compile(const std::string& mslSourceCode, std::string& errMsg);
+	bool compile(const std::string& mslSourceCode,
+				 std::string& errMsg,
+				 uint32_t mslVersionMajor,
+				 uint32_t mslVersionMinor = 0,
+				 uint32_t mslVersionPoint = 0);
 
 }
diff --git a/MoltenVKShaderConverter/MoltenVKShaderConverterTool/OSSupport.mm b/MoltenVKShaderConverter/MoltenVKShaderConverterTool/OSSupport.mm
index aae8edf..69be1a1 100644
--- a/MoltenVKShaderConverter/MoltenVKShaderConverterTool/OSSupport.mm
+++ b/MoltenVKShaderConverter/MoltenVKShaderConverterTool/OSSupport.mm
@@ -55,20 +55,49 @@
 	return success;
 }
 
-/** Concrete template implementation to allow MoltenVKShaderConverterTool to iterate the files in a directory. */
+// Concrete template implementation to allow MoltenVKShaderConverterTool to iterate the files in a directory.
 template bool mvk::iterateDirectory<MoltenVKShaderConverterTool>(const string& dirPath,
 																  MoltenVKShaderConverterTool& fileProcessor,
 																  bool isRecursive,
 																  string& errMsg);
 
-bool mvk::compile(const string& mslSourceCode, string& errMsg) {
+bool mvk::compile(const string& mslSourceCode,
+				  string& errMsg,
+				  uint32_t mslVersionMajor,
+				  uint32_t mslVersionMinor,
+				  uint32_t mslVersionPoint) {
+
+#define mslVer(MJ, MN, PT)	mslVersionMajor == MJ && mslVersionMinor == MN && mslVersionPoint == PT
+
+	MTLLanguageVersion mslVerEnum = (MTLLanguageVersion)0;
+	if (mslVer(2, 1, 0)) {
+		if (@available(macOS 10.14, *)) {
+			mslVerEnum = MTLLanguageVersion2_1;
+		}
+	} else if (mslVer(2, 0, 0)) {
+		if (@available(macOS 10.13, *)) {
+			mslVerEnum = MTLLanguageVersion2_0;
+		}
+	} else if (mslVer(1, 2, 0)) {
+		mslVerEnum = MTLLanguageVersion1_2;
+	} else if (mslVer(1, 1, 0)) {
+		mslVerEnum = MTLLanguageVersion1_1;
+	}
+
+	if ( !mslVerEnum ) {
+		errMsg = [NSString stringWithFormat: @"%d.%d.%d is not a valid MSL version number on this device",
+				  mslVersionMajor, mslVersionMinor, mslVersionPoint].UTF8String;
+		return false;
+	}
+
 	@autoreleasepool {
+		MTLCompileOptions* mtlCompileOptions  = [[MTLCompileOptions new] autorelease];
+		mtlCompileOptions.languageVersion = mslVerEnum;
 		NSError* err = nil;
 		id<MTLLibrary> mtlLib = [[MTLCreateSystemDefaultDevice() newLibraryWithSource: @(mslSourceCode.c_str())
-																			  options: [[MTLCompileOptions new] autorelease]
+																			  options: mtlCompileOptions
 																				error: &err] autorelease];
-		errMsg = err ? [NSString stringWithFormat: @"(Error code %li):\n%@", (long)err.code, err.localizedDescription].UTF8String
-					 : "";
+		errMsg = err ? [NSString stringWithFormat: @"(Error code %li):\n%@", (long)err.code, err.localizedDescription].UTF8String : "";
 		return !!mtlLib;
 	}
 }