2222#include <SDL3_gpu_shadercross/SDL_gpu_shadercross.h>
2323#include <SDL3/SDL_log.h>
2424#include <SDL3/SDL_iostream.h>
25+ #include <SDL3/SDL_process.h>
2526
2627// We can emit HLSL as a destination, so let's redefine the shader format enum.
2728typedef enum ShaderCross_DestinationFormat {
@@ -30,19 +31,29 @@ typedef enum ShaderCross_DestinationFormat {
3031 SHADERFORMAT_DXBC ,
3132 SHADERFORMAT_DXIL ,
3233 SHADERFORMAT_MSL ,
33- SHADERFORMAT_HLSL
34+ SHADERFORMAT_METALLIB ,
35+ SHADERFORMAT_HLSL ,
3436} ShaderCross_ShaderFormat ;
3537
38+ typedef enum ShaderCross_Platform {
39+ PLATFORM_METAL_MACOS ,
40+ PLATFORM_METAL_IOS ,
41+ } ShaderCross_Platform ;
42+
43+ bool check_for_metal_tools (void );
44+ int compile_metallib (ShaderCross_Platform platform , const char * outputFilename );
45+
3646void print_help (void )
3747{
3848 int column_width = 32 ;
3949 SDL_Log ("Usage: shadercross <input> [options]" );
4050 SDL_Log ("Required options:\n" );
4151 SDL_Log (" %-*s %s" , column_width , "-s | --source <value>" , "Source language format. May be inferred from the filename. Values: [SPIRV, HLSL]" );
42- SDL_Log (" %-*s %s" , column_width , "-d | --dest <value>" , "Destination format. May be inferred from the filename. Values: [DXBC, DXIL, MSL, SPIRV , HLSL]" );
52+ SDL_Log (" %-*s %s" , column_width , "-d | --dest <value>" , "Destination format. May be inferred from the filename. Values: [SPIRV, DXBC, DXIL, MSL, METALLIB , HLSL]" );
4353 SDL_Log (" %-*s %s" , column_width , "-t | --stage <value>" , "Shader stage. May be inferred from the filename. Values: [vertex, fragment, compute]" );
4454 SDL_Log (" %-*s %s" , column_width , "-e | --entrypoint <value>" , "Entrypoint function name. Default: \"main\"." );
4555 SDL_Log (" %-*s %s" , column_width , "-m | --shadermodel <value>" , "HLSL Shader Model. Only used with HLSL destination. Values: [5.0, 6.0]" );
56+ SDL_Log (" %-*s %s" , column_width , "-p | --platform <value>" , "Target platform. Only used with METALLIB destination. Values: [macOS, iOS]" );
4657 SDL_Log (" %-*s %s" , column_width , "-o | --output <value>" , "Output file." );
4758}
4859
@@ -53,11 +64,13 @@ int main(int argc, char *argv[])
5364 bool destinationValid = false;
5465 bool stageValid = false;
5566 bool shaderModelValid = false; // only required for HLSL destination
67+ bool platformValid = false; // only required for METALLIB destination
5668
5769 bool spirvSource = false;
5870 ShaderCross_ShaderFormat destinationFormat = SHADERFORMAT_INVALID ;
5971 SDL_ShaderCross_ShaderStage shaderStage = SDL_SHADERCROSS_SHADERSTAGE_VERTEX ;
6072 SDL_ShaderCross_ShaderModel shaderModel ;
73+ ShaderCross_Platform platform ;
6174 char * outputFilename = NULL ;
6275 char * entrypointName = "main" ;
6376
@@ -113,8 +126,11 @@ int main(int argc, char *argv[])
113126 } else if (SDL_strcasecmp (argv [i ], "HLSL" ) == 0 ) {
114127 destinationFormat = SHADERFORMAT_HLSL ;
115128 destinationValid = true;
129+ } else if (SDL_strcasecmp (argv [i ], "METALLIB" ) == 0 ) {
130+ destinationFormat = SHADERFORMAT_METALLIB ;
131+ destinationValid = true;
116132 } else {
117- SDL_LogError (SDL_LOG_CATEGORY_APPLICATION , "Unrecognized destination input %s, destination must be DXBC, DXIL, MSL or SPIRV !" , argv [i ]);
133+ SDL_LogError (SDL_LOG_CATEGORY_APPLICATION , "Unrecognized destination input %s, destination must be SPIRV, DXBC, DXIL, MSL, METALLIB, or HLSL !" , argv [i ]);
118134 print_help ();
119135 return 1 ;
120136 }
@@ -165,6 +181,24 @@ int main(int argc, char *argv[])
165181 print_help ();
166182 return 1 ;
167183 }
184+ } else if (SDL_strcmp (arg , "-p" ) == 0 || SDL_strcmp (arg , "--platform" ) == 0 ) {
185+ if (i + 1 >= argc ) {
186+ SDL_LogError (SDL_LOG_CATEGORY_APPLICATION , "%s requires an argument" , arg );
187+ print_help ();
188+ return 1 ;
189+ }
190+ i += 1 ;
191+ if (SDL_strcasecmp (argv [i ], "macOS" ) == 0 ) {
192+ platform = PLATFORM_METAL_MACOS ;
193+ platformValid = true;
194+ } else if (SDL_strcasecmp (argv [i ], "iOS" ) == 0 ) {
195+ platform = PLATFORM_METAL_IOS ;
196+ platformValid = true;
197+ } else {
198+ SDL_LogError (SDL_LOG_CATEGORY_APPLICATION , "%s is not a recognized platform!" , argv [i ]);
199+ print_help ();
200+ return 1 ;
201+ }
168202 } else if (SDL_strcmp (arg , "-o" ) == 0 || SDL_strcmp (arg , "--output" ) == 0 ) {
169203 if (i + 1 >= argc ) {
170204 SDL_LogError (SDL_LOG_CATEGORY_APPLICATION , "%s requires an argument" , arg );
@@ -229,6 +263,8 @@ int main(int argc, char *argv[])
229263 destinationFormat = SHADERFORMAT_DXIL ;
230264 } else if (SDL_strstr (outputFilename , ".msl" )) {
231265 destinationFormat = SHADERFORMAT_MSL ;
266+ } else if (SDL_strstr (outputFilename , ".metallib" )) {
267+ destinationFormat = SHADERFORMAT_METALLIB ;
232268 } else if (SDL_strstr (outputFilename , ".spv" )) {
233269 destinationFormat = SHADERFORMAT_SPIRV ;
234270 } else if (SDL_strstr (outputFilename , ".hlsl" )) {
@@ -255,7 +291,6 @@ int main(int argc, char *argv[])
255291 }
256292
257293 SDL_IOStream * outputIO = SDL_IOFromFile (outputFilename , "w" );
258-
259294 if (outputIO == NULL ) {
260295 SDL_LogError (SDL_LOG_CATEGORY_APPLICATION , "%s" , SDL_GetError ());
261296 return 1 ;
@@ -300,6 +335,43 @@ int main(int argc, char *argv[])
300335 break ;
301336 }
302337
338+ case SHADERFORMAT_METALLIB : {
339+ if (!platformValid ) {
340+ SDL_LogError (SDL_LOG_CATEGORY_APPLICATION , "%s" , "METALLIB destination requires a target platform!" );
341+ print_help ();
342+ return 1 ;
343+ }
344+
345+ if (!check_for_metal_tools ())
346+ {
347+ SDL_LogError (SDL_LOG_CATEGORY_APPLICATION , "xcrun not found! Is Xcode installed and activated?" );
348+ return 1 ;
349+ }
350+
351+ // We won't generate the metallib file directly...
352+ SDL_CloseIO (outputIO );
353+ outputIO = NULL ;
354+
355+ // ...instead we'll send the MSL to a temp file and then compile that.
356+ SDL_IOStream * tempFileIO = SDL_IOFromFile ("tmp.metal" , "w" );
357+ char * buffer = SDL_ShaderCross_TranspileMSLFromSPIRV (
358+ fileData ,
359+ fileSize ,
360+ entrypointName ,
361+ shaderStage );
362+ SDL_IOprintf (tempFileIO , "%s" , buffer );
363+ SDL_free (buffer );
364+ SDL_CloseIO (tempFileIO );
365+
366+ int exitcode = compile_metallib (platform , outputFilename );
367+ if (exitcode != 0 ) {
368+ SDL_LogError (SDL_LOG_CATEGORY_APPLICATION , "Metal library creation failed with error code %d" , exitcode );
369+ return 1 ;
370+ }
371+
372+ break ;
373+ }
374+
303375 case SHADERFORMAT_HLSL : {
304376 if (!shaderModelValid ) {
305377 SDL_LogError (SDL_LOG_CATEGORY_APPLICATION , "%s" , "HLSL destination requires a shader model specification!" );
@@ -373,6 +445,54 @@ int main(int argc, char *argv[])
373445 break ;
374446 }
375447
448+ case SHADERFORMAT_METALLIB : {
449+ if (!platformValid ) {
450+ SDL_LogError (SDL_LOG_CATEGORY_APPLICATION , "%s" , "METALLIB destination requires a target platform!" );
451+ print_help ();
452+ return 1 ;
453+ }
454+
455+ if (!check_for_metal_tools ())
456+ {
457+ SDL_LogError (SDL_LOG_CATEGORY_APPLICATION , "xcrun not found! Is Xcode installed and activated?" );
458+ return 1 ;
459+ }
460+
461+ void * spirv = SDL_ShaderCross_CompileSPIRVFromHLSL (
462+ fileData ,
463+ entrypointName ,
464+ shaderStage ,
465+ & bytecodeSize );
466+ if (spirv == NULL ) {
467+ SDL_LogError (SDL_LOG_CATEGORY_APPLICATION , "%s" , "Failed to compile SPIR-V!" );
468+ return 1 ;
469+ }
470+
471+ // We won't generate the metallib file directly...
472+ SDL_CloseIO (outputIO );
473+ outputIO = NULL ;
474+
475+ // ...instead we'll send the MSL to a temp file and then compile that.
476+ SDL_IOStream * tempFileIO = SDL_IOFromFile ("tmp.metal" , "w" );
477+ char * buffer = SDL_ShaderCross_TranspileMSLFromSPIRV (
478+ spirv ,
479+ bytecodeSize ,
480+ entrypointName ,
481+ shaderStage );
482+ SDL_IOprintf (tempFileIO , "%s" , buffer );
483+ SDL_free (spirv );
484+ SDL_free (buffer );
485+ SDL_CloseIO (tempFileIO );
486+
487+ int exitcode = compile_metallib (platform , outputFilename );
488+ if (exitcode != 0 ) {
489+ SDL_LogError (SDL_LOG_CATEGORY_APPLICATION , "Metal library creation failed with error code %d" , exitcode );
490+ return 1 ;
491+ }
492+
493+ break ;
494+ }
495+
376496 case SHADERFORMAT_SPIRV : {
377497 Uint8 * buffer = SDL_ShaderCross_CompileSPIRVFromHLSL (
378498 fileData ,
@@ -427,3 +547,54 @@ int main(int argc, char *argv[])
427547 SDL_ShaderCross_Quit ();
428548 return 0 ;
429549}
550+
551+ bool check_for_metal_tools (void )
552+ {
553+ // Check for the Metal Developer Tools...
554+ // FIXME: All the process calls need their Windows equivalents!
555+ SDL_PropertiesID props = SDL_CreateProperties ();
556+ SDL_SetPointerProperty (props , SDL_PROP_PROCESS_CREATE_ARGS_POINTER , (const char * []){ "xcrun" , "--help" , NULL });
557+ SDL_SetNumberProperty (props , SDL_PROP_PROCESS_CREATE_STDERR_NUMBER , SDL_PROCESS_STDIO_NULL );
558+ SDL_Process * process = SDL_CreateProcessWithProperties (props );
559+ SDL_DestroyProperties (props );
560+
561+ if (process == NULL ) {
562+ return false;
563+ }
564+ SDL_DestroyProcess (process );
565+ return true;
566+ }
567+
568+ int compile_metallib (ShaderCross_Platform platform , const char * outputFilename )
569+ {
570+ const char * sdkString ;
571+ const char * stdString ;
572+ const char * minversion ;
573+ if (platform == PLATFORM_METAL_MACOS ) {
574+ sdkString = "macosx" ;
575+ stdString = "-std=macos-metal2.0" ;
576+ minversion = "-mmacosx-version-min=10.13" ;
577+ } else {
578+ sdkString = "iphoneos" ;
579+ stdString = "-std=ios-metal2.0" ;
580+ minversion = "-miphoneos-version-min=13.0" ;
581+ }
582+
583+ int exitcode ;
584+ SDL_Process * process = SDL_CreateProcess ((const char * []){ "xcrun" , "-sdk" , sdkString , "metal" , stdString , minversion , "-Wall" , "-O3" , "-c" , "tmp.metal" , "-o" , "tmp.ir" , NULL }, true);
585+ SDL_WaitProcess (process , true, & exitcode );
586+ SDL_RemovePath ("tmp.metal" );
587+ if (exitcode != 0 ) {
588+ return exitcode ;
589+ }
590+ SDL_DestroyProcess (process );
591+
592+ process = SDL_CreateProcess ((const char * []){ "xcrun" , "-sdk" , sdkString , "metallib" , "tmp.ir" , "-o" , outputFilename , NULL }, true);
593+ SDL_WaitProcess (process , true, & exitcode );
594+ SDL_RemovePath ("tmp.ir" );
595+ if (exitcode != 0 ) {
596+ return exitcode ;
597+ }
598+ SDL_DestroyProcess (process );
599+ return 0 ;
600+ }
0 commit comments