/* * MoltenVKShaderConverterTool.cpp * * Copyright (c) 2015-2024 The Brenwill Workshop Ltd. (http://www.brenwill.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * 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. */ #include "MoltenVKShaderConverterTool.h" #include "FileSupport.h" #include "OSSupport.h" #include "GLSLToSPIRVConverter.h" #include "SPIRVToMSLConverter.h" #include "SPIRVSupport.h" #include "MVKOSExtensions.h" using namespace std; using namespace mvk; // The default list of vertex file extensions. static const char* _defaultVertexShaderExtns = "vs vsh vert vertex"; // The default list of tessellation control file extensions. static const char* _defaultTescShaderExtns = "tcs tcsh tesc"; // The default list of tessellation evaluation file extensions. static const char* _defaultTeseShaderExtns = "tes tesh tese"; // The default list of fragment file extensions. static const char* _defaultFragShaderExtns = "fs fsh frag fragment"; // The default list of compute file extensions. static const char* _defaultCompShaderExtns = "cs csh cp cmp comp compute kn kl krn kern kernel"; // The default list of SPIR-V file extensions. static const char* _defaultSPIRVShaderExtns = "spv spirv"; uint64_t MVKPerformanceTracker::getTimestamp() { return mvkGetTimestamp(); } void MVKPerformanceTracker::accumulate(uint64_t startTime, uint64_t endTime) { double currInterval = mvkGetElapsedMilliseconds(startTime, endTime); minimumDuration = (minimumDuration == 0.0) ? currInterval : min(currInterval, minimumDuration); maximumDuration = max(currInterval, maximumDuration); double totalInterval = (averageDuration * count++) + currInterval; averageDuration = totalInterval / count; } #pragma mark - #pragma mark MoltenVKShaderConverterTool int MoltenVKShaderConverterTool::run() { if ( !_isActive ) { return EXIT_FAILURE; } bool success = false; if ( !_directoryPath.empty() ) { string errMsg; success = iterateDirectory(_directoryPath, *this, _shouldUseDirectoryRecursion, errMsg); if ( !success ) { log(errMsg.data()); } } else { if (_shouldReadGLSL) { success = convertGLSL(_glslInFilePath, _spvOutFilePath, _mslOutFilePath, _shaderStage); } else if (_shouldReadSPIRV) { success = convertSPIRV(_spvInFilePath, _mslOutFilePath); } else { showUsage(); } } reportPerformance(); return success ? EXIT_SUCCESS : EXIT_FAILURE; } bool MoltenVKShaderConverterTool::processFile(string filePath) { string absPath = absolutePath(filePath); string emptyPath; string pathExtn = pathExtension(absPath); if (_shouldReadGLSL && isGLSLFileExtension(pathExtn)) { return convertGLSL(absPath, emptyPath, emptyPath, kMVKGLSLConversionShaderStageAuto); } else if (_shouldReadSPIRV && isSPIRVFileExtension(pathExtn)) { return convertSPIRV(absPath, emptyPath); } return true; } // Read GLSL code from a GLSL file, convert to SPIR-V, and optionally MSL, // and write the SPIR-V and/or MSL code to files. bool MoltenVKShaderConverterTool::convertGLSL(string& glslInFile, string& spvOutFile, string& mslOutFile, MVKGLSLConversionShaderStage shaderStage) { string path; vector fileContents; string glslCode; string errMsg; // Read the GLSL if (glslInFile.empty()) { log("The GLSL file to read from was not specified"); return false; } path = glslInFile; if (readFile(path, fileContents, errMsg)) { string logMsg = "Read GLSL from file: " + fileName(path); log(logMsg.data()); } else { errMsg = "Could not read GLSL file. " + errMsg; log(errMsg.data()); return false; } glslCode.append(fileContents.begin(), fileContents.end()); if (shaderStage == kMVKGLSLConversionShaderStageAuto) { string pathExtn = pathExtension(glslInFile); shaderStage = shaderStageFromFileExtension(pathExtn); } if (shaderStage == kMVKGLSLConversionShaderStageAuto) { errMsg = "Could not determine shader type from GLSL file: " + absolutePath(path); log(errMsg.data()); return false; } // Convert GLSL to SPIR-V GLSLToSPIRVConversionResult conversionResult; GLSLToSPIRVConverter glslConverter; glslConverter.setGLSL(glslCode); uint64_t startTime = _glslConversionPerformance.getTimestamp(); bool wasConverted = glslConverter.convert(shaderStage, conversionResult, _shouldLogConversions, _shouldLogConversions); _glslConversionPerformance.accumulate(startTime); if (wasConverted) { if (_shouldLogConversions) { log(conversionResult.resultLog.data()); } } else { string logMsg = "Could not convert GLSL in file: " + absolutePath(path); log(logMsg.data()); log(conversionResult.resultLog.data()); return false; } // Write the SPIR-V code to a file. // If no file has been supplied, create one from the GLSL file name. if (_shouldWriteSPIRV) { path = spvOutFile; if (path.empty()) { path = pathWithExtension(glslInFile, _shouldOutputAsHeaders ? "h" : "spv", _shouldIncludeOrigPathExtn, _origPathExtnSep); } if (_shouldOutputAsHeaders) { spirvToHeaderBytes(conversionResult.spirv, fileContents, fileName(path, false)); } else { spirvToBytes(conversionResult.spirv, fileContents); } if (writeFile(path, fileContents, errMsg)) { string logMsg = "Saved SPIR-V to file: " + fileName(path); log(logMsg.data()); } else { errMsg = "Could not write SPIR-V file. " + errMsg; log(errMsg.data()); return false; } } return convertSPIRV(conversionResult.spirv, glslInFile, mslOutFile, false); } // Read SPIR-V code from a SPIR-V file, convert to MSL, and write the MSL code to files. bool MoltenVKShaderConverterTool::convertSPIRV(string& spvInFile, string& mslOutFile) { string path; vector fileContents; vector spv; string errMsg; // Read the SPIRV if (spvInFile.empty()) { log("The SPIR-V file to read from was not specified"); return false; } path = spvInFile; if (readFile(path, fileContents, errMsg)) { string logMsg = "Read SPIR-V from file: " + fileName(path); log(logMsg.data()); } else { errMsg = "Could not read SPIR-V file. " + errMsg; log(errMsg.data()); return false; } bytesToSPIRV(fileContents, spv); return convertSPIRV(spv, spvInFile, mslOutFile, _shouldLogConversions); } // Read SPIR-V code from an array, convert to MSL, and write the MSL code to files. bool MoltenVKShaderConverterTool::convertSPIRV(const vector& spv, string& inFile, string& mslOutFile, bool shouldLogSPV) { if ( !_shouldWriteMSL ) { return true; } // Derive the context under which conversion will occur SPIRVToMSLConversionConfiguration mslContext; mslContext.options.mslOptions.platform = _mslPlatform; mslContext.options.mslOptions.set_msl_version(_mslVersionMajor, _mslVersionMinor, _mslVersionPatch); mslContext.options.shouldFlipVertexY = _shouldFlipVertexY; mslContext.options.mslOptions.argument_buffers = _useMetalArgumentBuffers; mslContext.options.mslOptions.force_active_argument_buffer_resources = _useMetalArgumentBuffers; mslContext.options.mslOptions.pad_argument_buffer_resources = false; mslContext.options.mslOptions.argument_buffers_tier = SPIRV_CROSS_NAMESPACE::CompilerMSL::Options::ArgumentBuffersTier::Tier2; mslContext.options.mslOptions.replace_recursive_inputs = mvkOSVersionIsAtLeast(14.0, 17.0, 1.0); SPIRVToMSLConverter spvConverter; spvConverter.setSPIRV(spv); uint64_t startTime = _spvConversionPerformance.getTimestamp(); SPIRVToMSLConversionResult conversionResult; bool wasConverted = spvConverter.convert(mslContext, conversionResult, shouldLogSPV, _shouldLogConversions, (_shouldLogConversions && shouldLogSPV)); _spvConversionPerformance.accumulate(startTime); if (wasConverted) { if (_shouldLogConversions) { log(conversionResult.resultLog.data()); } } else { string errMsg = "Could not convert SPIR-V in file: " + absolutePath(inFile); log(errMsg.data()); log(conversionResult.resultLog.data()); return false; } // Write the MSL to file string path = mslOutFile; if (mslOutFile.empty()) { path = pathWithExtension(inFile, "metal", _shouldIncludeOrigPathExtn, _origPathExtnSep); } string compileErrMsg; bool wasCompiled = compile(conversionResult.msl, compileErrMsg, _mslVersionMajor, _mslVersionMinor, _mslVersionPatch); if (compileErrMsg.size() > 0) { 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 { log("Generated MSL was validated by a successful compilation with no warnings."); } vector fileContents; fileContents.insert(fileContents.end(), conversionResult.msl.begin(), conversionResult.msl.end()); string writeErrMsg; if (writeFile(path, fileContents, writeErrMsg)) { string logMsg = "Saved MSL to file: " + fileName(path); log(logMsg.c_str()); return true; } else { writeErrMsg = "Could not write MSL file. " + writeErrMsg; log(writeErrMsg.c_str()); return false; } } MVKGLSLConversionShaderStage MoltenVKShaderConverterTool::shaderStageFromFileExtension(string& pathExtension) { for (auto& fx : _glslVtxFileExtns) { if (fx == pathExtension) { return kMVKGLSLConversionShaderStageVertex; } } for (auto& fx : _glslTescFileExtns) { if (fx == pathExtension) { return kMVKGLSLConversionShaderStageTessControl; } } for (auto& fx : _glslTeseFileExtns) { if (fx == pathExtension) { return kMVKGLSLConversionShaderStageTessEval; } } for (auto& fx : _glslFragFileExtns) { if (fx == pathExtension) { return kMVKGLSLConversionShaderStageFragment; } } for (auto& fx : _glslCompFileExtns) { if (fx == pathExtension) { return kMVKGLSLConversionShaderStageCompute; } } return kMVKGLSLConversionShaderStageAuto; } bool MoltenVKShaderConverterTool::isGLSLFileExtension(string& pathExtension) { for (auto& fx : _glslVtxFileExtns) { if (fx == pathExtension) { return true; } } for (auto& fx : _glslTescFileExtns) { if (fx == pathExtension) { return true; } } for (auto& fx : _glslTeseFileExtns) { if (fx == pathExtension) { return true; } } for (auto& fx : _glslFragFileExtns) { if (fx == pathExtension) { return true; } } for (auto& fx : _glslCompFileExtns) { if (fx == pathExtension) { return true; } } return false; } bool MoltenVKShaderConverterTool::isSPIRVFileExtension(string& pathExtension) { for (auto& fx : _spvFileExtns) { if (fx == pathExtension) { return true; } } return false; } // Log the specified message to the console. void MoltenVKShaderConverterTool::log(const char* logMsg) { if ( !_quietMode ) { printf("%s\n", logMsg); } } // Display usage information about this application on the console. void MoltenVKShaderConverterTool::showUsage() { bool qm = _quietMode; _quietMode = false; string line = "\n\e[1m" + _processName + "\e[0m converts OpenGL Shading Language (GLSL) source code to"; log((const char*)line.c_str()); log("SPIR-V code, and/or to Metal Shading Language (MSL) source code, or converts"); log("SPIR-V code to Metal Shading Language source code."); log("\nTo convert a single GLSL or SPIR-V file, include a file reference with the -gi"); log("or -si option, respectively. To convert an entire directory of shader files,"); log("use the -d option, along with the -gi or -si option. When using the -d option,"); log("any file name supplied with the -gi or -si option will be ignored."); log("\nUse the -so or -mo option to indicate the desired type of output"); log("(SPIR-V or MSL, respectively)."); log("\nUsage:"); log(" -d [\"dirPath\"] - Path to a directory containing GLSL or SPIR-V shader"); log(" source code files. The dirPath may be omitted to use"); log(" the current working directory."); log(" -r - (when using -d) Process directories recursively."); log(" -gi [\"glslInFile\"] - Indicates that GLSL shader code should be input."); log(" The optional glslInFile parameter specifies the path to a"); log(" single file containing GLSL source code to be converted."); log(" When using the -d option, the glslInFile parameter is ignored."); log(" -si [\"spvInFile\"] - Indicates that SPIR-V shader code should be input."); log(" The optional spvInFile parameter specifies the path to a"); log(" single file containing SPIR-V code to be converted."); log(" When using the -d option, the spvInFile parameter is ignored."); log(" -so [\"spvOutFile\"] - Indicates that SPIR-V shader code should be output."); log(" The optional spvOutFile parameter specifies the path to a single"); log(" file to contain the SPIR-V code. When using the -d option,"); log(" the spvOutFile parameter is ignored."); log(" -mo [\"mslOutFile\"] - Indicates that MSL shader source code should be output."); log(" The optional mslOutFile parameter specifies the path to a single"); log(" file to contain the MSL code. When using the -d option,"); log(" the mslOutFile 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"); log(" into a single ouput file."); log(" -oh [varName] - Save the output as header (.h) files."); log(" Affects the output of the -so option."); log(" The optional varName parameter specifies the name of the"); log(" variable in the header file to which the output code is assigned."); log(" When using the -d option, the varName parameter is ignored."); log(" -Iv - Disable inversion of the vertex coordinate Y-axis"); log(" (default is to invert vertex coordinates)."); log(" -xs \"xtnSep\" - Separator to use when including file extension of original"); log(" code file name in derived converted code file name."); log(" Default is \"_\" (myshdr.vsh -> myshdr_vsh.metal)."); log(" -XS - Disable including file extension of original code"); log(" file name in derived converted code file name"); log(" (myshdr.vsh -> myshdr.metal)."); log(" -vx \"fileExtns\" - List of GLSL vertex shader file extensions."); log(" May be omitted for defaults (\"vs vsh vert vertex\")."); log(" -tcx \"fileExtns\" - List of GLSL tessellation control shader file extensions."); log(" May be omitted for defaults (\"tcs tcsh tesc\")."); log(" -tex \"fileExtns\" - List of GLSL tessellation evaluation shader file extensions."); log(" May be omitted for defaults (\"tes tesh tese\")."); log(" -fx \"fileExtns\" - List of GLSL fragment shader file extensions."); log(" May be omitted for defaults (\"fs fsh frag fragment\")."); log(" -cx \"fileExtns\" - List of GLSL compute shader file extensions."); log(" May be omitted for defaults (\"cp cmp comp compute kn kl krn kern kernel\")."); log(" -sx \"fileExtns\" - List of SPIR-V shader file extensions."); log(" May be omitted for defaults (\"spv spirv\")."); log(" -mab - Use Metal Argument Buffers to hold resources in the shaders."); log(" -l - Log the conversion results to the console (to aid debugging)."); log(" -p - Log the performance of the shader conversions."); log(" -q - Quiet mode. Stops logging of informational messages."); log(""); _quietMode = qm; } void MoltenVKShaderConverterTool::reportPerformance() { if ( !_shouldReportPerformance ) { return; } if (_shouldReadGLSL) { reportPerformance(_glslConversionPerformance, "GLSL to SPIR-V"); } reportPerformance(_spvConversionPerformance, "SPIR-V to MSL"); } void MoltenVKShaderConverterTool::reportPerformance(MVKPerformanceTracker& shaderCompilationEvent, string eventDescription) { string logMsg; logMsg += "Performance to convert "; logMsg += eventDescription; logMsg += " count: "; logMsg += to_string(shaderCompilationEvent.count); logMsg += ", min: "; logMsg += to_string(shaderCompilationEvent.minimumDuration); logMsg += " ms, max: "; logMsg += to_string(shaderCompilationEvent.maximumDuration); logMsg += " ms, avg: "; logMsg += to_string(shaderCompilationEvent.averageDuration); logMsg += " ms.\n"; log(logMsg.c_str()); } #pragma mark Construction MoltenVKShaderConverterTool::MoltenVKShaderConverterTool(int argc, const char* argv[]) { extractTokens(_defaultVertexShaderExtns, _glslVtxFileExtns); extractTokens(_defaultTescShaderExtns, _glslTescFileExtns); extractTokens(_defaultTeseShaderExtns, _glslTeseFileExtns); extractTokens(_defaultFragShaderExtns, _glslFragFileExtns); extractTokens(_defaultCompShaderExtns, _glslCompFileExtns); extractTokens(_defaultSPIRVShaderExtns, _spvFileExtns); _origPathExtnSep = "_"; _shaderStage = kMVKGLSLConversionShaderStageAuto; _shouldUseDirectoryRecursion = false; _shouldReadGLSL = false; _shouldReadSPIRV = false; _shouldWriteSPIRV = false; _shouldWriteMSL = false; _shouldCombineGLSLAndMSL = false; _shouldFlipVertexY = true; _shouldIncludeOrigPathExtn = true; _shouldLogConversions = false; _shouldReportPerformance = false; _shouldOutputAsHeaders = false; _quietMode = false; _useMetalArgumentBuffers = false; if (mvkOSVersionIsAtLeast(14.0)) { _mslVersionMajor = 3; _mslVersionMinor = 1; } else if (mvkOSVersionIsAtLeast(13.0)) { _mslVersionMajor = 3; _mslVersionMinor = 0; } else if (mvkOSVersionIsAtLeast(12.0)) { _mslVersionMajor = 2; _mslVersionMinor = 4; } else if (mvkOSVersionIsAtLeast(11.0)) { _mslVersionMajor = 2; _mslVersionMinor = 3; } else if (mvkOSVersionIsAtLeast(10.15)) { _mslVersionMajor = 2; _mslVersionMinor = 2; } else if (mvkOSVersionIsAtLeast(10.14)) { _mslVersionMajor = 2; _mslVersionMinor = 1; } else if (mvkOSVersionIsAtLeast(10.13)) { _mslVersionMajor = 2; _mslVersionMinor = 0; } else if (mvkOSVersionIsAtLeast(10.12)) { _mslVersionMajor = 1; _mslVersionMinor = 2; } else { _mslVersionMajor = 1; _mslVersionMinor = 1; } _mslVersionPatch = 0; _mslPlatform = SPIRVToMSLConversionOptions().mslOptions.platform; _isActive = parseArgs(argc, argv); if ( !_isActive ) { showUsage(); } } bool MoltenVKShaderConverterTool::parseArgs(int argc, const char* argv[]) { if (argc == 0) { return false; } string execPath(argv[0]); _processName = fileName(execPath, false); for (int argIdx = 1; argIdx < argc; argIdx++) { string arg = argv[argIdx]; if ( !isOptionArg(arg) ) { return false; } if (equal(arg, "-d", false)) { int optIdx = argIdx; argIdx = optionalParam(_directoryPath, argIdx, argc, argv); if (argIdx == optIdx) { return false; } _directoryPath = absolutePath(_directoryPath); continue; } if(equal(arg, "-r", true)) { _shouldUseDirectoryRecursion = true; continue; } if (equal(arg, "-gi", true)) { _shouldReadGLSL = true; argIdx = optionalParam(_glslInFilePath, argIdx, argc, argv); continue; } if (equal(arg, "-si", true)) { _shouldReadSPIRV = true; argIdx = optionalParam(_spvInFilePath, argIdx, argc, argv); continue; } if (equal(arg, "-so", true)) { _shouldWriteSPIRV = true; argIdx = optionalParam(_spvOutFilePath, argIdx, argc, argv); continue; } if (equal(arg, "-mo", true)) { _shouldWriteMSL = true; argIdx = optionalParam(_mslOutFilePath, argIdx, argc, argv); 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 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 = SPIRV_CROSS_NAMESPACE::CompilerMSL::Options::macOS; break; case 'i': _mslPlatform = SPIRV_CROSS_NAMESPACE::CompilerMSL::Options::iOS; break; default: return false; } continue; } if (equal(arg, "-t", true)) { int optIdx = argIdx; string shdrTypeStr; argIdx = optionalParam(shdrTypeStr, argIdx, argc, argv); if (argIdx == optIdx || shdrTypeStr.length() == 0) { return false; } switch (shdrTypeStr.front()) { case 'v': _shaderStage = kMVKGLSLConversionShaderStageVertex; break; case 'f': _shaderStage = kMVKGLSLConversionShaderStageFragment; break; case 'c': _shaderStage = kMVKGLSLConversionShaderStageCompute; break; default: return false; } continue; } if(equal(arg, "-c", true)) { _shouldCombineGLSLAndMSL = true; continue; } if(equal(arg, "-oh", true)) { _shouldOutputAsHeaders = true; argIdx = optionalParam(_hdrOutVarName, argIdx, argc, argv); continue; } if(equal(arg, "-Iv", true)) { _shouldFlipVertexY = false; continue; } if (equal(arg, "-xs", true)) { _shouldIncludeOrigPathExtn = true; argIdx++; if (argIdx < argc) { _origPathExtnSep = argv[argIdx]; } continue; } if(equal(arg, "-XS", true)) { _shouldIncludeOrigPathExtn = false; continue; } if (equal(arg, "-vx", true)) { int optIdx = argIdx; string shdrExtnStr; argIdx = optionalParam(shdrExtnStr, argIdx, argc, argv); if (argIdx == optIdx || shdrExtnStr.length() == 0) { return false; } extractTokens(shdrExtnStr, _glslVtxFileExtns); continue; } if (equal(arg, "-tcx", true)) { int optIdx = argIdx; string shdrExtnStr; argIdx = optionalParam(shdrExtnStr, argIdx, argc, argv); if (argIdx == optIdx || shdrExtnStr.length() == 0) { return false; } extractTokens(shdrExtnStr, _glslTescFileExtns); continue; } if (equal(arg, "-tex", true)) { int optIdx = argIdx; string shdrExtnStr; argIdx = optionalParam(shdrExtnStr, argIdx, argc, argv); if (argIdx == optIdx || shdrExtnStr.length() == 0) { return false; } extractTokens(shdrExtnStr, _glslTeseFileExtns); continue; } if (equal(arg, "-fx", true)) { int optIdx = argIdx; string shdrExtnStr; argIdx = optionalParam(shdrExtnStr, argIdx, argc, argv); if (argIdx == optIdx || shdrExtnStr.length() == 0) { return false; } extractTokens(shdrExtnStr, _glslFragFileExtns); continue; } if (equal(arg, "-cx", true)) { int optIdx = argIdx; string shdrExtnStr; argIdx = optionalParam(shdrExtnStr, argIdx, argc, argv); if (argIdx == optIdx || shdrExtnStr.length() == 0) { return false; } extractTokens(shdrExtnStr, _glslCompFileExtns); continue; } if (equal(arg, "-sx", true)) { int optIdx = argIdx; string shdrExtnStr; argIdx = optionalParam(shdrExtnStr, argIdx, argc, argv); if (argIdx == optIdx || shdrExtnStr.length() == 0) { return false; } extractTokens(shdrExtnStr, _spvFileExtns); continue; } if(equal(arg, "-mab", true)) { _useMetalArgumentBuffers = true; continue; } if(equal(arg, "-l", true)) { _shouldLogConversions = true; continue; } if(equal(arg, "-p", true)) { _shouldReportPerformance = true; continue; } if(equal(arg, "-q", true)) { _quietMode = true; continue; } } return true; } // Returns whether the specified command line arg is an option arg. bool MoltenVKShaderConverterTool::isOptionArg(string& arg) { return (arg.length() > 1 && arg.front() == '-'); } // Sets the contents of the specified string to the parameter part of the option at the // specified arg index, and increments and returns the option index. If no parameter was // provided for the option, the string will be set to an empty string, and the returned // index will be the same as the specified index. int MoltenVKShaderConverterTool::optionalParam(string& optionParamResult, int optionArgIndex, int argc, const char* argv[]) { int optParamIdx = optionArgIndex + 1; if (optParamIdx < argc) { string arg(argv[optParamIdx]); if ( !isOptionArg(arg) ) { optionParamResult = arg; return optParamIdx; } } optionParamResult.clear(); return optionArgIndex; } #pragma mark - #pragma mark Support functions // Template function for tokenizing the components of a string into a vector. template Container& split(Container& result, const typename Container::value_type& s, const typename Container::value_type& delimiters, bool includeEmptyElements) { result.clear(); size_t current; size_t next = -1; do { if (includeEmptyElements) { next = s.find_first_not_of( delimiters, next + 1 ); if (next == Container::value_type::npos) break; next -= 1; } current = next + 1; next = s.find_first_of( delimiters, current ); result.push_back( s.substr( current, next - current ) ); } while (next != Container::value_type::npos); return result; } void mvk::extractTokens(string str, vector& tokens) { split(tokens, str, " \t\n\f", false); } void mvk::extractTokens(string str, vector& tokens) { vector 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); } bool mvk::equal(string const& a, string const& b, bool checkCase) { if (a.length() != b.length()) { return false; } return checkCase ? (a == b) : (equal(b.begin(), b.end(), a.begin(), compareIgnoringCase)); }