SDL_shadercross: Add SDL_SHADERCROSS_PROP_MSL_VERSION property (#114)

From db758697661c59718ae02786d667d24f94d3c88b Mon Sep 17 00:00:00 2001
From: Wilson <[EMAIL REDACTED]>
Date: Wed, 5 Mar 2025 20:01:01 +0100
Subject: [PATCH] Add SDL_SHADERCROSS_PROP_MSL_VERSION property (#114)

---
 include/SDL3_shadercross/SDL_shadercross.h |  2 ++
 src/SDL_shadercross.c                      | 20 ++++++++++++++++++++
 src/cli.c                                  | 20 ++++++++++++++++++--
 3 files changed, 40 insertions(+), 2 deletions(-)

diff --git a/include/SDL3_shadercross/SDL_shadercross.h b/include/SDL3_shadercross/SDL_shadercross.h
index 693884d..f5011ff 100644
--- a/include/SDL3_shadercross/SDL_shadercross.h
+++ b/include/SDL3_shadercross/SDL_shadercross.h
@@ -84,6 +84,8 @@ typedef struct SDL_ShaderCross_SPIRV_Info
 
 #define SDL_SHADERCROSS_PROP_SPIRV_PSSL_COMPATIBILITY "SDL.shadercross.spirv.pssl.compatibility"
 
+#define SDL_SHADERCROSS_PROP_SPIRV_MSL_VERSION "SDL.shadercross.spirv.msl.version"
+
 typedef struct SDL_ShaderCross_HLSL_Define
 {
     char *name;   /**< The define name. */
diff --git a/src/SDL_shadercross.c b/src/SDL_shadercross.c
index b7c3316..bd0963b 100644
--- a/src/SDL_shadercross.c
+++ b/src/SDL_shadercross.c
@@ -119,6 +119,15 @@ struct IDxcBlob
     IDxcBlobVtbl *lpVtbl;
 };
 
+static int parse_version_number(const char* str)
+{
+    unsigned major, minor, patch;
+    if (SDL_sscanf(str, "%u.%u.%u", &major, &minor, &patch) == 3) {
+        return (major * 10000) + (minor) * 100 + patch;
+    }
+    return -1;
+}
+
 static Uint8 IID_IDxcBlobUtf8[] = {
     0xC9, 0x36, 0xA6, 0x3D,
     0x71, 0xBA,
@@ -957,6 +966,17 @@ static SPIRVTranspileContext *SDL_ShaderCross_INTERNAL_TranspileFromSPIRV(
         }
     }
 
+    if (backend == SPVC_BACKEND_MSL) {
+        const char *_mslVersion = SDL_GetStringProperty(props, SDL_SHADERCROSS_PROP_SPIRV_MSL_VERSION, "1.2.0");
+        int mslVersion = parse_version_number(_mslVersion);
+        if (mslVersion == - 1) {
+            SDL_SetError("failed to parse MSL version string \"%s\"", _mslVersion);
+            spvc_context_destroy(context);
+            return NULL;
+        }
+        spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_MSL_VERSION, mslVersion);
+    }
+
     // MSL doesn't have descriptor sets, so we have to set up index remapping
     if (backend == SPVC_BACKEND_MSL && shaderStage != SDL_SHADERCROSS_SHADERSTAGE_COMPUTE) {
         spvc_resources resources;
diff --git a/src/cli.c b/src/cli.c
index 05ce89c..6c9ce1b 100644
--- a/src/cli.c
+++ b/src/cli.c
@@ -49,6 +49,7 @@ void print_help(void)
     SDL_Log("  %-*s %s", column_width, "-I | --include <value>", "HLSL include directory. Only used with HLSL source.");
     SDL_Log("  %-*s %s", column_width, "-D<name>[=<value>]", "HLSL define. Only used with HLSL source. Can be repeated.");
     SDL_Log("  %-*s %s", column_width, "", "If =<value> is omitted the define will be treated as equal to 1.");
+    SDL_Log("  %-*s %s", column_width, "--msl-version <value>", "Target MSL version. Only used when transpiling to MSL. The default is 1.2.0.");
     SDL_Log("  %-*s %s", column_width, "-g | --debug", "Generate debug information when possible. Shaders are valid only when graphics debuggers are attached.");
 }
 
@@ -103,6 +104,7 @@ int main(int argc, char *argv[])
     size_t numDefines = 0;
 
     bool enableDebug = false;
+    char *mslVersion = NULL;
 
     for (int i = 1; i < argc; i += 1) {
         char *arg = argv[i];
@@ -224,6 +226,14 @@ int main(int argc, char *argv[])
                     defines[numDefines - 1].name = SDL_malloc(len);
                     SDL_utf8strlcpy(defines[numDefines - 1].name, (const char *)argv[i] + 2, len);
                 }
+            } else if (SDL_strcmp(arg, "--msl-version") == 0) {
+                if (i + 1 >= argc) {
+                    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s requires an argument", arg);
+                    print_help();
+                    return 1;
+                }
+                i += 1;
+                mslVersion = argv[i];
             } else if (SDL_strcmp(argv[i], "-g") == 0 || SDL_strcmp(arg, "--debug") == 0) {
                 enableDebug = true;
             } else if (SDL_strcmp(arg, "--") == 0) {
@@ -334,7 +344,10 @@ int main(int argc, char *argv[])
         spirvInfo.shader_stage = shaderStage;
         spirvInfo.enable_debug = enableDebug;
         spirvInfo.name = filename;
-        spirvInfo.props = 0;
+        spirvInfo.props = SDL_CreateProperties();
+        if (mslVersion) {
+            SDL_SetStringProperty(spirvInfo.props, SDL_SHADERCROSS_PROP_SPIRV_MSL_VERSION, mslVersion);
+        }
 
         switch (destinationFormat) {
             case SHADERFORMAT_DXBC: {
@@ -487,7 +500,10 @@ int main(int argc, char *argv[])
                     spirvInfo.entrypoint = entrypointName;
                     spirvInfo.shader_stage = shaderStage;
                     spirvInfo.enable_debug = enableDebug;
-                    spirvInfo.props = 0;
+                    spirvInfo.props = SDL_CreateProperties();
+                    if (mslVersion) {
+                        SDL_SetStringProperty(spirvInfo.props, SDL_SHADERCROSS_PROP_SPIRV_MSL_VERSION, mslVersion);
+                    }
                     char *buffer = SDL_ShaderCross_TranspileMSLFromSPIRV(
                         &spirvInfo);
                     if (buffer == NULL) {