From 3b9a7cdd21208939e05846473e985b4db15bcf27 Mon Sep 17 00:00:00 2001 From: Caleb Cornett Date: Thu, 31 Oct 2024 17:22:41 -0400 Subject: [PATCH 1/4] Add METALLIB output support for the CLI (mac-only) --- src/cli.c | 179 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 175 insertions(+), 4 deletions(-) diff --git a/src/cli.c b/src/cli.c index 493c1e0..bd871e0 100644 --- a/src/cli.c +++ b/src/cli.c @@ -22,6 +22,7 @@ #include #include #include +#include // We can emit HLSL as a destination, so let's redefine the shader format enum. typedef enum ShaderCross_DestinationFormat { @@ -30,19 +31,29 @@ typedef enum ShaderCross_DestinationFormat { SHADERFORMAT_DXBC, SHADERFORMAT_DXIL, SHADERFORMAT_MSL, - SHADERFORMAT_HLSL + SHADERFORMAT_METALLIB, + SHADERFORMAT_HLSL, } ShaderCross_ShaderFormat; +typedef enum ShaderCross_Platform { + PLATFORM_METAL_MACOS, + PLATFORM_METAL_IOS, +} ShaderCross_Platform; + +bool check_for_metal_tools(void); +int compile_metallib(ShaderCross_Platform platform, const char *outputFilename); + void print_help(void) { int column_width = 32; SDL_Log("Usage: shadercross [options]"); SDL_Log("Required options:\n"); SDL_Log(" %-*s %s", column_width, "-s | --source ", "Source language format. May be inferred from the filename. Values: [SPIRV, HLSL]"); - SDL_Log(" %-*s %s", column_width, "-d | --dest ", "Destination format. May be inferred from the filename. Values: [DXBC, DXIL, MSL, SPIRV, HLSL]"); + SDL_Log(" %-*s %s", column_width, "-d | --dest ", "Destination format. May be inferred from the filename. Values: [SPIRV, DXBC, DXIL, MSL, METALLIB, HLSL]"); SDL_Log(" %-*s %s", column_width, "-t | --stage ", "Shader stage. May be inferred from the filename. Values: [vertex, fragment, compute]"); SDL_Log(" %-*s %s", column_width, "-e | --entrypoint ", "Entrypoint function name. Default: \"main\"."); SDL_Log(" %-*s %s", column_width, "-m | --shadermodel ", "HLSL Shader Model. Only used with HLSL destination. Values: [5.0, 6.0]"); + SDL_Log(" %-*s %s", column_width, "-p | --platform ", "Target platform. Only used with METALLIB destination. Values: [macOS, iOS]"); SDL_Log(" %-*s %s", column_width, "-o | --output ", "Output file."); } @@ -53,11 +64,13 @@ int main(int argc, char *argv[]) bool destinationValid = false; bool stageValid = false; bool shaderModelValid = false; // only required for HLSL destination + bool platformValid = false; // only required for METALLIB destination bool spirvSource = false; ShaderCross_ShaderFormat destinationFormat = SHADERFORMAT_INVALID; SDL_ShaderCross_ShaderStage shaderStage = SDL_SHADERCROSS_SHADERSTAGE_VERTEX; SDL_ShaderCross_ShaderModel shaderModel; + ShaderCross_Platform platform; char *outputFilename = NULL; char *entrypointName = "main"; @@ -113,8 +126,11 @@ int main(int argc, char *argv[]) } else if (SDL_strcasecmp(argv[i], "HLSL") == 0) { destinationFormat = SHADERFORMAT_HLSL; destinationValid = true; + } else if (SDL_strcasecmp(argv[i], "METALLIB") == 0) { + destinationFormat = SHADERFORMAT_METALLIB; + destinationValid = true; } else { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unrecognized destination input %s, destination must be DXBC, DXIL, MSL or SPIRV!", argv[i]); + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unrecognized destination input %s, destination must be SPIRV, DXBC, DXIL, MSL, METALLIB, or HLSL!", argv[i]); print_help(); return 1; } @@ -165,6 +181,24 @@ int main(int argc, char *argv[]) print_help(); return 1; } + } else if (SDL_strcmp(arg, "-p") == 0 || SDL_strcmp(arg, "--platform") == 0) { + if (i + 1 >= argc) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s requires an argument", arg); + print_help(); + return 1; + } + i += 1; + if (SDL_strcasecmp(argv[i], "macOS") == 0) { + platform = PLATFORM_METAL_MACOS; + platformValid = true; + } else if (SDL_strcasecmp(argv[i], "iOS") == 0) { + platform = PLATFORM_METAL_IOS; + platformValid = true; + } else { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s is not a recognized platform!", argv[i]); + print_help(); + return 1; + } } else if (SDL_strcmp(arg, "-o") == 0 || SDL_strcmp(arg, "--output") == 0) { if (i + 1 >= argc) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s requires an argument", arg); @@ -229,6 +263,8 @@ int main(int argc, char *argv[]) destinationFormat = SHADERFORMAT_DXIL; } else if (SDL_strstr(outputFilename, ".msl")) { destinationFormat = SHADERFORMAT_MSL; + } else if (SDL_strstr(outputFilename, ".metallib")) { + destinationFormat = SHADERFORMAT_METALLIB; } else if (SDL_strstr(outputFilename, ".spv")) { destinationFormat = SHADERFORMAT_SPIRV; } else if (SDL_strstr(outputFilename, ".hlsl")) { @@ -255,7 +291,6 @@ int main(int argc, char *argv[]) } SDL_IOStream *outputIO = SDL_IOFromFile(outputFilename, "w"); - if (outputIO == NULL) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", SDL_GetError()); return 1; @@ -300,6 +335,43 @@ int main(int argc, char *argv[]) break; } + case SHADERFORMAT_METALLIB: { + if (!platformValid) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", "METALLIB destination requires a target platform!"); + print_help(); + return 1; + } + + if (!check_for_metal_tools()) + { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "xcrun not found! Is Xcode installed and activated?"); + return 1; + } + + // We won't generate the metallib file directly... + SDL_CloseIO(outputIO); + outputIO = NULL; + + // ...instead we'll send the MSL to a temp file and then compile that. + SDL_IOStream *tempFileIO = SDL_IOFromFile("tmp.metal", "w"); + char *buffer = SDL_ShaderCross_TranspileMSLFromSPIRV( + fileData, + fileSize, + entrypointName, + shaderStage); + SDL_IOprintf(tempFileIO, "%s", buffer); + SDL_free(buffer); + SDL_CloseIO(tempFileIO); + + int exitcode = compile_metallib(platform, outputFilename); + if (exitcode != 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Metal library creation failed with error code %d", exitcode); + return 1; + } + + break; + } + case SHADERFORMAT_HLSL: { if (!shaderModelValid) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", "HLSL destination requires a shader model specification!"); @@ -373,6 +445,54 @@ int main(int argc, char *argv[]) break; } + case SHADERFORMAT_METALLIB: { + if (!platformValid) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", "METALLIB destination requires a target platform!"); + print_help(); + return 1; + } + + if (!check_for_metal_tools()) + { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "xcrun not found! Is Xcode installed and activated?"); + return 1; + } + + void *spirv = SDL_ShaderCross_CompileSPIRVFromHLSL( + fileData, + entrypointName, + shaderStage, + &bytecodeSize); + if (spirv == NULL) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", "Failed to compile SPIR-V!"); + return 1; + } + + // We won't generate the metallib file directly... + SDL_CloseIO(outputIO); + outputIO = NULL; + + // ...instead we'll send the MSL to a temp file and then compile that. + SDL_IOStream *tempFileIO = SDL_IOFromFile("tmp.metal", "w"); + char *buffer = SDL_ShaderCross_TranspileMSLFromSPIRV( + spirv, + bytecodeSize, + entrypointName, + shaderStage); + SDL_IOprintf(tempFileIO, "%s", buffer); + SDL_free(spirv); + SDL_free(buffer); + SDL_CloseIO(tempFileIO); + + int exitcode = compile_metallib(platform, outputFilename); + if (exitcode != 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Metal library creation failed with error code %d", exitcode); + return 1; + } + + break; + } + case SHADERFORMAT_SPIRV: { Uint8 *buffer = SDL_ShaderCross_CompileSPIRVFromHLSL( fileData, @@ -427,3 +547,54 @@ int main(int argc, char *argv[]) SDL_ShaderCross_Quit(); return 0; } + +bool check_for_metal_tools(void) +{ + // Check for the Metal Developer Tools... + // FIXME: All the process calls need their Windows equivalents! + SDL_PropertiesID props = SDL_CreateProperties(); + SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, (const char*[]){ "xcrun", "--help", NULL }); + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER, SDL_PROCESS_STDIO_NULL); + SDL_Process *process = SDL_CreateProcessWithProperties(props); + SDL_DestroyProperties(props); + + if (process == NULL) { + return false; + } + SDL_DestroyProcess(process); + return true; +} + +int compile_metallib(ShaderCross_Platform platform, const char *outputFilename) +{ + const char *sdkString; + const char *stdString; + const char *minversion; + if (platform == PLATFORM_METAL_MACOS) { + sdkString = "macosx"; + stdString = "-std=macos-metal2.0"; + minversion = "-mmacosx-version-min=10.13"; + } else { + sdkString = "iphoneos"; + stdString = "-std=ios-metal2.0"; + minversion = "-miphoneos-version-min=13.0"; + } + + int exitcode; + SDL_Process *process = SDL_CreateProcess((const char*[]){ "xcrun", "-sdk", sdkString, "metal", stdString, minversion, "-Wall", "-O3", "-c", "tmp.metal", "-o", "tmp.ir", NULL }, true); + SDL_WaitProcess(process, true, &exitcode); + SDL_RemovePath("tmp.metal"); + if (exitcode != 0) { + return exitcode; + } + SDL_DestroyProcess(process); + + process = SDL_CreateProcess((const char*[]){ "xcrun", "-sdk", sdkString, "metallib", "tmp.ir", "-o", outputFilename, NULL }, true); + SDL_WaitProcess(process, true, &exitcode); + SDL_RemovePath("tmp.ir"); + if (exitcode != 0) { + return exitcode; + } + SDL_DestroyProcess(process); + return 0; +} From 5424cd1ef5fe54faedfb2e968006d5e3ed7838e0 Mon Sep 17 00:00:00 2001 From: Caleb Cornett Date: Thu, 31 Oct 2024 17:34:11 -0400 Subject: [PATCH 2/4] warning fix --- src/cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli.c b/src/cli.c index bd871e0..58e0244 100644 --- a/src/cli.c +++ b/src/cli.c @@ -553,7 +553,7 @@ bool check_for_metal_tools(void) // Check for the Metal Developer Tools... // FIXME: All the process calls need their Windows equivalents! SDL_PropertiesID props = SDL_CreateProperties(); - SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, (const char*[]){ "xcrun", "--help", NULL }); + SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, (char*[]){ "xcrun", "--help", NULL }); SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER, SDL_PROCESS_STDIO_NULL); SDL_Process *process = SDL_CreateProcessWithProperties(props); SDL_DestroyProperties(props); From 9192d40918c9df4023a7fea581a205e83ca341fe Mon Sep 17 00:00:00 2001 From: Caleb Cornett Date: Fri, 1 Nov 2024 19:42:36 -0400 Subject: [PATCH 3/4] Add Windows compiler commands --- src/cli.c | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/cli.c b/src/cli.c index 58e0244..6653a64 100644 --- a/src/cli.c +++ b/src/cli.c @@ -59,7 +59,6 @@ void print_help(void) int main(int argc, char *argv[]) { - bool sourceValid = false; bool destinationValid = false; bool stageValid = false; @@ -344,7 +343,6 @@ int main(int argc, char *argv[]) if (!check_for_metal_tools()) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "xcrun not found! Is Xcode installed and activated?"); return 1; } @@ -454,7 +452,6 @@ int main(int argc, char *argv[]) if (!check_for_metal_tools()) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "xcrun not found! Is Xcode installed and activated?"); return 1; } @@ -550,17 +547,30 @@ int main(int argc, char *argv[]) bool check_for_metal_tools(void) { +#if defined(SDL_PLATFORM_MACOS) + char *compilerName = "xcrun"; + char *cantFindMessage = "Install Xcode or the Xcode Command Line Tools."; +#elif defined(SDL_PLATFORM_WIN32) + char *compilerName = "metal"; + char *cantFindMessage = "Install Metal Developer Tools for Windows 5.0 beta 2 or newer (https://developer.apple.com/download/all/?q=metal%20developer%20tools%20for%20windows) and add \"C:\\Program Files\\Metal Developer Tools\\bin\" to your PATH."; +#else + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Compiling to METALLIB is not supported on this platform!"); + return false; +#endif + // Check for the Metal Developer Tools... - // FIXME: All the process calls need their Windows equivalents! SDL_PropertiesID props = SDL_CreateProperties(); - SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, (char*[]){ "xcrun", "--help", NULL }); + SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, (char*[]){ compilerName, "--help", NULL }); + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_NULL); SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER, SDL_PROCESS_STDIO_NULL); SDL_Process *process = SDL_CreateProcessWithProperties(props); SDL_DestroyProperties(props); if (process == NULL) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s not found! %s", compilerName, cantFindMessage); return false; } + SDL_DestroyProcess(process); return true; } @@ -580,8 +590,16 @@ int compile_metallib(ShaderCross_Platform platform, const char *outputFilename) minversion = "-miphoneos-version-min=13.0"; } +#if defined(SDL_PLATFORM_MACOS) + const char* compileToIRCommand[] = { "xcrun", "-sdk", sdkString, "metal", stdString, minversion, "-Wall", "-O3", "-c", "tmp.metal", "-o", "tmp.ir", NULL }; + const char* compileToMetallibCommand[] = { "xcrun", "-sdk", sdkString, "metallib", "tmp.ir", "-o", outputFilename, NULL }; +#elif defined(SDL_PLATFORM_WINDOWS) + const char* compileToIRCommand[] = { "metal", stdString, minversion, "-Wall", "-O3", "-c", "tmp.metal", "-o", "tmp.ir", NULL}; + const char* compileToMetallibCommand[] = { "metallib", "tmp.ir", "-o", outputFilename, NULL}; +#endif + int exitcode; - SDL_Process *process = SDL_CreateProcess((const char*[]){ "xcrun", "-sdk", sdkString, "metal", stdString, minversion, "-Wall", "-O3", "-c", "tmp.metal", "-o", "tmp.ir", NULL }, true); + SDL_Process *process = SDL_CreateProcess(compileToIRCommand, true); SDL_WaitProcess(process, true, &exitcode); SDL_RemovePath("tmp.metal"); if (exitcode != 0) { @@ -589,7 +607,7 @@ int compile_metallib(ShaderCross_Platform platform, const char *outputFilename) } SDL_DestroyProcess(process); - process = SDL_CreateProcess((const char*[]){ "xcrun", "-sdk", sdkString, "metallib", "tmp.ir", "-o", outputFilename, NULL }, true); + process = SDL_CreateProcess(compileToMetallibCommand, true); SDL_WaitProcess(process, true, &exitcode); SDL_RemovePath("tmp.ir"); if (exitcode != 0) { From 7a5836861c3005317b8356206347f83de61fdf3f Mon Sep 17 00:00:00 2001 From: Caleb Cornett Date: Fri, 1 Nov 2024 19:48:31 -0400 Subject: [PATCH 4/4] warning fixes --- src/cli.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/cli.c b/src/cli.c index 6653a64..4095ec9 100644 --- a/src/cli.c +++ b/src/cli.c @@ -547,15 +547,14 @@ int main(int argc, char *argv[]) bool check_for_metal_tools(void) { +#if defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_WINDOWS) + #if defined(SDL_PLATFORM_MACOS) char *compilerName = "xcrun"; char *cantFindMessage = "Install Xcode or the Xcode Command Line Tools."; -#elif defined(SDL_PLATFORM_WIN32) +#else char *compilerName = "metal"; char *cantFindMessage = "Install Metal Developer Tools for Windows 5.0 beta 2 or newer (https://developer.apple.com/download/all/?q=metal%20developer%20tools%20for%20windows) and add \"C:\\Program Files\\Metal Developer Tools\\bin\" to your PATH."; -#else - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Compiling to METALLIB is not supported on this platform!"); - return false; #endif // Check for the Metal Developer Tools... @@ -573,27 +572,30 @@ bool check_for_metal_tools(void) SDL_DestroyProcess(process); return true; + +#else + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Compiling to METALLIB is not supported on this platform!"); + return false; +#endif } int compile_metallib(ShaderCross_Platform platform, const char *outputFilename) { - const char *sdkString; const char *stdString; const char *minversion; if (platform == PLATFORM_METAL_MACOS) { - sdkString = "macosx"; stdString = "-std=macos-metal2.0"; minversion = "-mmacosx-version-min=10.13"; } else { - sdkString = "iphoneos"; stdString = "-std=ios-metal2.0"; minversion = "-miphoneos-version-min=13.0"; } #if defined(SDL_PLATFORM_MACOS) + const char* sdkString = (platform == PLATFORM_METAL_MACOS) ? "macosx" : "iphoneos"; const char* compileToIRCommand[] = { "xcrun", "-sdk", sdkString, "metal", stdString, minversion, "-Wall", "-O3", "-c", "tmp.metal", "-o", "tmp.ir", NULL }; const char* compileToMetallibCommand[] = { "xcrun", "-sdk", sdkString, "metallib", "tmp.ir", "-o", outputFilename, NULL }; -#elif defined(SDL_PLATFORM_WINDOWS) +#else const char* compileToIRCommand[] = { "metal", stdString, minversion, "-Wall", "-O3", "-c", "tmp.metal", "-o", "tmp.ir", NULL}; const char* compileToMetallibCommand[] = { "metallib", "tmp.ir", "-o", outputFilename, NULL}; #endif