Skip to content

Commit 3b9a7cd

Browse files
committed
Add METALLIB output support for the CLI (mac-only)
1 parent 260351b commit 3b9a7cd

File tree

1 file changed

+175
-4
lines changed

1 file changed

+175
-4
lines changed

src/cli.c

Lines changed: 175 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
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.
2728
typedef 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+
3646
void 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

Comments
 (0)