diff --git a/include/SDL3_shadercross/SDL_shadercross.h b/include/SDL3_shadercross/SDL_shadercross.h index c1154df..08cc9e0 100644 --- a/include/SDL3_shadercross/SDL_shadercross.h +++ b/include/SDL3_shadercross/SDL_shadercross.h @@ -197,6 +197,34 @@ extern SDL_DECLSPEC SDL_GPUComputePipeline * SDLCALL SDL_ShaderCross_CompileComp const char *entrypoint, SDL_ShaderCross_ComputePipelineInfo *info); +/** + * Reflect graphics shader info from SPIRV code. + * + * \param bytecode the SPIRV bytecode. + * \param bytecodeSize the length of the SPIRV bytecode. + * \param info a pointer filled in with shader metadata. + * + * \threadsafety It is safe to call this function from any thread. + */ +extern SDL_DECLSPEC bool SDLCALL SDL_ShaderCross_ReflectGraphicsSPIRV( + const Uint8 *bytecode, + size_t bytecodeSize, + SDL_ShaderCross_GraphicsShaderInfo *info); + +/** + * Reflect compute pipeline info from SPIRV code. + * + * \param bytecode the SPIRV bytecode. + * \param bytecodeSize the length of the SPIRV bytecode. + * \param info a pointer filled in with compute pipeline metadata. + * + * \threadsafety It is safe to call this function from any thread. + */ +extern SDL_DECLSPEC bool SDLCALL SDL_ShaderCross_ReflectComputeSPIRV( + const Uint8 *bytecode, + size_t bytecodeSize, + SDL_ShaderCross_ComputePipelineInfo *info); + /** * Get the supported shader formats that HLSL cross-compilation can output * diff --git a/src/SDL_shadercross.c b/src/SDL_shadercross.c index b1b03ae..6045689 100644 --- a/src/SDL_shadercross.c +++ b/src/SDL_shadercross.c @@ -1459,12 +1459,12 @@ static SPIRVTranspileContext *SDL_ShaderCross_INTERNAL_TranspileFromSPIRV( return transpileContext; } -// Acquire CreateInfo metadata from SPIRV bytecode. +// Acquire metadata from SPIRV bytecode. // TODO: validate descriptor sets -static bool SDL_ShaderCross_INTERNAL_ReflectGraphicsSPIRV( +bool SDL_ShaderCross_ReflectGraphicsSPIRV( const Uint8 *code, size_t codeSize, - SDL_GPUShaderCreateInfo *createInfo // filled in with reflected data + SDL_ShaderCross_GraphicsShaderInfo *info // filled in with reflected data ) { spvc_result result; spvc_context context = NULL; @@ -1572,17 +1572,17 @@ static bool SDL_ShaderCross_INTERNAL_ReflectGraphicsSPIRV( spvc_context_destroy(context); - createInfo->num_samplers = num_texture_samplers; - createInfo->num_storage_textures = num_storage_textures; - createInfo->num_storage_buffers = num_storage_buffers; - createInfo->num_uniform_buffers = num_uniform_buffers; + info->numSamplers = num_texture_samplers; + info->numStorageTextures = num_storage_textures; + info->numStorageBuffers = num_storage_buffers; + info->numUniformBuffers = num_uniform_buffers; return true; } -static bool SDL_ShaderCross_INTERNAL_ReflectComputeSPIRV( - const Uint8 *code, - size_t codeSize, - SDL_GPUComputePipelineCreateInfo *createInfo // filled in with reflected data +bool SDL_ShaderCross_ReflectComputeSPIRV( + const Uint8 *bytecode, + size_t bytecodeSize, + SDL_ShaderCross_ComputePipelineInfo *info // filled in with reflected data ) { spvc_result result; spvc_context context = NULL; @@ -1606,7 +1606,7 @@ static bool SDL_ShaderCross_INTERNAL_ReflectComputeSPIRV( } /* Parse the SPIR-V into IR */ - result = spvc_context_parse_spirv(context, (const SpvId *)code, codeSize / sizeof(SpvId), &ir); + result = spvc_context_parse_spirv(context, (const SpvId *)bytecode, bytecodeSize / sizeof(SpvId), &ir); if (result < 0) { SPVC_ERROR(spvc_context_parse_spirv); spvc_context_destroy(context); @@ -1740,18 +1740,18 @@ static bool SDL_ShaderCross_INTERNAL_ReflectComputeSPIRV( } // Threadcount - createInfo->threadcount_x = spvc_compiler_get_execution_mode_argument_by_index(compiler, SpvExecutionModeLocalSize, 0); - createInfo->threadcount_y = spvc_compiler_get_execution_mode_argument_by_index(compiler, SpvExecutionModeLocalSize, 1); - createInfo->threadcount_z = spvc_compiler_get_execution_mode_argument_by_index(compiler, SpvExecutionModeLocalSize, 2); + info->threadCountX = spvc_compiler_get_execution_mode_argument_by_index(compiler, SpvExecutionModeLocalSize, 0); + info->threadCountY = spvc_compiler_get_execution_mode_argument_by_index(compiler, SpvExecutionModeLocalSize, 1); + info->threadCountZ = spvc_compiler_get_execution_mode_argument_by_index(compiler, SpvExecutionModeLocalSize, 2); spvc_context_destroy(context); - createInfo->num_samplers = num_texture_samplers; - createInfo->num_readonly_storage_textures = num_readonly_storage_textures; - createInfo->num_readonly_storage_buffers = num_readonly_storage_buffers; - createInfo->num_readwrite_storage_textures = num_readwrite_storage_textures; - createInfo->num_readwrite_storage_buffers = num_readwrite_storage_buffers; - createInfo->num_uniform_buffers = num_uniform_buffers; + info->numSamplers = num_texture_samplers; + info->numReadOnlyStorageTextures = num_readonly_storage_textures; + info->numReadOnlyStorageBuffers = num_readonly_storage_buffers; + info->numReadWriteStorageTextures = num_readwrite_storage_textures; + info->numReadWriteStorageBuffers = num_readwrite_storage_buffers; + info->numUniformBuffers = num_uniform_buffers; return true; } @@ -1761,7 +1761,8 @@ static void *SDL_ShaderCross_INTERNAL_CompileFromSPIRV( size_t bytecodeSize, const char *entrypoint, SDL_ShaderCross_ShaderStage shaderStage, - SDL_GPUShaderFormat targetFormat + SDL_GPUShaderFormat targetFormat, + void *info ) { spvc_backend backend; unsigned shadermodel = 0; @@ -1795,13 +1796,23 @@ static void *SDL_ShaderCross_INTERNAL_CompileFromSPIRV( if (shaderStage == SDL_SHADERCROSS_SHADERSTAGE_COMPUTE) { SDL_GPUComputePipelineCreateInfo createInfo; - SDL_ShaderCross_INTERNAL_ReflectComputeSPIRV( + SDL_ShaderCross_ComputePipelineInfo *pipelineInfo = (SDL_ShaderCross_ComputePipelineInfo *)info; + SDL_ShaderCross_ReflectComputeSPIRV( bytecode, bytecodeSize, - &createInfo); + pipelineInfo); createInfo.entrypoint = transpileContext->cleansed_entrypoint; createInfo.format = targetFormat; createInfo.props = 0; + createInfo.num_samplers = pipelineInfo->numSamplers; + createInfo.num_readonly_storage_textures = pipelineInfo->numReadOnlyStorageTextures; + createInfo.num_readonly_storage_buffers = pipelineInfo->numReadOnlyStorageBuffers; + createInfo.num_readwrite_storage_textures = pipelineInfo->numReadWriteStorageTextures; + createInfo.num_readwrite_storage_buffers = pipelineInfo->numReadWriteStorageBuffers; + createInfo.num_uniform_buffers = pipelineInfo->numUniformBuffers; + createInfo.threadcount_x = pipelineInfo->threadCountX; + createInfo.threadcount_y = pipelineInfo->threadCountY; + createInfo.threadcount_z = pipelineInfo->threadCountZ; if (targetFormat == SDL_GPU_SHADERFORMAT_DXBC) { createInfo.code = SDL_ShaderCross_INTERNAL_CompileDXBCFromHLSL( @@ -1830,14 +1841,19 @@ static void *SDL_ShaderCross_INTERNAL_CompileFromSPIRV( shaderObject = SDL_CreateGPUComputePipeline(device, &createInfo); } else { SDL_GPUShaderCreateInfo createInfo; - SDL_ShaderCross_INTERNAL_ReflectGraphicsSPIRV( + SDL_ShaderCross_GraphicsShaderInfo *shaderInfo = (SDL_ShaderCross_GraphicsShaderInfo *)info; + SDL_ShaderCross_ReflectGraphicsSPIRV( bytecode, bytecodeSize, - &createInfo); + shaderInfo); createInfo.entrypoint = transpileContext->cleansed_entrypoint; createInfo.format = targetFormat; createInfo.stage = (SDL_GPUShaderStage)shaderStage; createInfo.props = 0; + createInfo.num_samplers = shaderInfo->numSamplers; + createInfo.num_storage_textures = shaderInfo->numStorageTextures; + createInfo.num_storage_buffers = shaderInfo->numStorageBuffers; + createInfo.num_uniform_buffers = shaderInfo->numUniformBuffers; if (targetFormat == SDL_GPU_SHADERFORMAT_DXBC) { createInfo.code = SDL_ShaderCross_INTERNAL_CompileDXBCFromHLSL( @@ -2010,42 +2026,42 @@ static void *SDL_ShaderCross_INTERNAL_CreateShaderFromSPIRV( if (shaderStage == SDL_SHADERCROSS_SHADERSTAGE_COMPUTE) { SDL_GPUComputePipelineCreateInfo createInfo; SDL_ShaderCross_ComputePipelineInfo *pipelineInfo = (SDL_ShaderCross_ComputePipelineInfo *)info; - SDL_ShaderCross_INTERNAL_ReflectComputeSPIRV( + SDL_ShaderCross_ReflectComputeSPIRV( bytecode, bytecodeSize, - &createInfo); + pipelineInfo); createInfo.code = bytecode; createInfo.code_size = bytecodeSize; createInfo.entrypoint = entrypoint; createInfo.format = SDL_GPU_SHADERFORMAT_SPIRV; createInfo.props = 0; - pipelineInfo->numSamplers = createInfo.num_samplers; - pipelineInfo->numReadOnlyStorageTextures = createInfo.num_readonly_storage_textures; - pipelineInfo->numReadOnlyStorageBuffers = createInfo.num_readonly_storage_buffers; - pipelineInfo->numReadWriteStorageTextures = createInfo.num_readwrite_storage_textures; - pipelineInfo->numReadWriteStorageBuffers = createInfo.num_readwrite_storage_buffers; - pipelineInfo->numUniformBuffers = createInfo.num_uniform_buffers; - pipelineInfo->threadCountX = createInfo.threadcount_x; - pipelineInfo->threadCountY = createInfo.threadcount_y; - pipelineInfo->threadCountZ = createInfo.threadcount_z; + createInfo.num_samplers = pipelineInfo->numSamplers; + createInfo.num_readonly_storage_textures = pipelineInfo->numReadOnlyStorageTextures; + createInfo.num_readonly_storage_buffers = pipelineInfo->numReadOnlyStorageBuffers; + createInfo.num_readwrite_storage_textures = pipelineInfo->numReadWriteStorageTextures; + createInfo.num_readwrite_storage_buffers = pipelineInfo->numReadWriteStorageBuffers; + createInfo.num_uniform_buffers = pipelineInfo->numUniformBuffers; + createInfo.threadcount_x = pipelineInfo->threadCountX; + createInfo.threadcount_y = pipelineInfo->threadCountY; + createInfo.threadcount_z = pipelineInfo->threadCountZ; return SDL_CreateGPUComputePipeline(device, &createInfo); } else { SDL_GPUShaderCreateInfo createInfo; SDL_ShaderCross_GraphicsShaderInfo *shaderInfo = (SDL_ShaderCross_GraphicsShaderInfo *)info; - SDL_ShaderCross_INTERNAL_ReflectGraphicsSPIRV( + SDL_ShaderCross_ReflectGraphicsSPIRV( bytecode, bytecodeSize, - &createInfo); + shaderInfo); createInfo.code = bytecode; createInfo.code_size = bytecodeSize; createInfo.entrypoint = entrypoint; createInfo.format = SDL_GPU_SHADERFORMAT_SPIRV; createInfo.stage = (SDL_GPUShaderStage)shaderStage; createInfo.props = 0; - shaderInfo->numSamplers = createInfo.num_samplers; - shaderInfo->numStorageTextures = createInfo.num_storage_textures; - shaderInfo->numStorageBuffers = createInfo.num_storage_buffers; - shaderInfo->numUniformBuffers = createInfo.num_uniform_buffers; + createInfo.num_samplers = shaderInfo->numSamplers; + createInfo.num_storage_textures = shaderInfo->numStorageTextures; + createInfo.num_storage_buffers = shaderInfo->numStorageBuffers; + createInfo.num_uniform_buffers = shaderInfo->numUniformBuffers; return SDL_CreateGPUShader(device, &createInfo); } } else if (shader_formats & SDL_GPU_SHADERFORMAT_MSL) { @@ -2071,7 +2087,8 @@ static void *SDL_ShaderCross_INTERNAL_CreateShaderFromSPIRV( bytecodeSize, entrypoint, shaderStage, - format); + format, + info); } SDL_GPUShader *SDL_ShaderCross_CompileGraphicsShaderFromSPIRV( diff --git a/src/SDL_shadercross.sym b/src/SDL_shadercross.sym index d660c0e..619b922 100644 --- a/src/SDL_shadercross.sym +++ b/src/SDL_shadercross.sym @@ -15,5 +15,7 @@ SDL3_shadercross_0.0.0 { SDL_ShaderCross_CompileSPIRVFromHLSL; SDL_ShaderCross_CompileGraphicsShaderFromHLSL; SDL_ShaderCross_CompileComputePipelineFromHLSL; + SDL_ShaderCross_ReflectGraphicsSPIRV; + SDL_ShaderCross_ReflectComputeSPIRV; local: *; }; diff --git a/src/cli.c b/src/cli.c index 1525888..3a89fa1 100644 --- a/src/cli.c +++ b/src/cli.c @@ -23,14 +23,15 @@ #include #include -// We can emit HLSL as a destination, so let's redefine the shader format enum. +// We can emit HLSL and JSON as a destination, so let's redefine the shader format enum. typedef enum ShaderCross_DestinationFormat { SHADERFORMAT_INVALID, SHADERFORMAT_SPIRV, SHADERFORMAT_DXBC, SHADERFORMAT_DXIL, SHADERFORMAT_MSL, - SHADERFORMAT_HLSL + SHADERFORMAT_HLSL, + SHADERFORMAT_JSON } ShaderCross_ShaderFormat; void print_help(void) @@ -39,7 +40,7 @@ void print_help(void) 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: [DXBC, DXIL, MSL, SPIRV, HLSL, JSON]"); 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, "-I | --include ", "HLSL include directory. Only used with HLSL source. Optional."); @@ -47,6 +48,90 @@ void print_help(void) SDL_Log(" %-*s %s", column_width, "-o | --output ", "Output file."); } +void write_string(SDL_IOStream *outputIO, const char *str) +{ + SDL_WriteIO(outputIO, str, SDL_strlen(str)); +} + +void write_graphics_reflect_json(SDL_IOStream *outputIO, SDL_ShaderCross_GraphicsShaderInfo *info) +{ + char buffer[16]; + write_string(outputIO, "{ "); + + write_string(outputIO, "\"samplers\": "); + SDL_itoa(info->numSamplers, buffer, 10); + write_string(outputIO, buffer); + write_string(outputIO, ", "); + + write_string(outputIO, "\"storageTextures\": "); + SDL_itoa(info->numStorageTextures, buffer, 10); + write_string(outputIO, buffer); + write_string(outputIO, ", "); + + write_string(outputIO, "\"storageBuffers\": "); + SDL_itoa(info->numStorageBuffers, buffer, 10); + write_string(outputIO, buffer); + write_string(outputIO, ", "); + + write_string(outputIO, "\"uniformBuffers\": "); + SDL_itoa(info->numUniformBuffers, buffer, 10); + write_string(outputIO, buffer); + + write_string(outputIO, " }\n"); +} + +void write_compute_reflect_json(SDL_IOStream *outputIO, SDL_ShaderCross_ComputePipelineInfo *info) +{ + char buffer[16]; + write_string(outputIO, "{ "); + + write_string(outputIO, "\"samplers\": "); + SDL_itoa(info->numSamplers, buffer, 10); + write_string(outputIO, buffer); + write_string(outputIO, ", "); + + write_string(outputIO, "\"readOnlyStorageTextures\": "); + SDL_itoa(info->numReadOnlyStorageTextures, buffer, 10); + write_string(outputIO, buffer); + write_string(outputIO, ", "); + + write_string(outputIO, "\"readOnlyStorageBuffers\": "); + SDL_itoa(info->numReadOnlyStorageBuffers, buffer, 10); + write_string(outputIO, buffer); + write_string(outputIO, ", "); + + write_string(outputIO, "\"readWriteStorageTextures\": "); + SDL_itoa(info->numReadWriteStorageTextures, buffer, 10); + write_string(outputIO, buffer); + write_string(outputIO, ", "); + + write_string(outputIO, "\"readWriteStorageBuffers\": "); + SDL_itoa(info->numReadWriteStorageBuffers, buffer, 10); + write_string(outputIO, buffer); + write_string(outputIO, ", "); + + write_string(outputIO, "\"uniformBuffers\": "); + SDL_itoa(info->numUniformBuffers, buffer, 10); + write_string(outputIO, buffer); + write_string(outputIO, ", "); + + write_string(outputIO, "\"threadCountX\": "); + SDL_itoa(info->threadCountX, buffer, 10); + write_string(outputIO, buffer); + write_string(outputIO, ", "); + + write_string(outputIO, "\"threadCountY\": "); + SDL_itoa(info->threadCountY, buffer, 10); + write_string(outputIO, buffer); + write_string(outputIO, ", "); + + write_string(outputIO, "\"threadCountZ\": "); + SDL_itoa(info->threadCountZ, buffer, 10); + write_string(outputIO, buffer); + + write_string(outputIO, " }\n"); +} + int main(int argc, char *argv[]) { bool sourceValid = false; @@ -115,6 +200,9 @@ 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], "JSON") == 0) { + destinationFormat = SHADERFORMAT_JSON; + destinationValid = true; } else { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unrecognized destination input %s, destination must be DXBC, DXIL, MSL or SPIRV!", argv[i]); print_help(); @@ -234,6 +322,8 @@ int main(int argc, char *argv[]) destinationFormat = SHADERFORMAT_SPIRV; } else if (SDL_strstr(outputFilename, ".hlsl")) { destinationFormat = SHADERFORMAT_HLSL; + } else if (SDL_strstr(outputFilename, ".json")) { + destinationFormat = SHADERFORMAT_JSON; } else { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", "Could not infer destination format!"); print_help(); @@ -339,6 +429,33 @@ int main(int argc, char *argv[]) break; } + case SHADERFORMAT_JSON: { + if (shaderStage == SDL_SHADERCROSS_SHADERSTAGE_COMPUTE) { + SDL_ShaderCross_ComputePipelineInfo info; + if (SDL_ShaderCross_ReflectComputeSPIRV( + fileData, + fileSize, + &info)) { + write_compute_reflect_json(outputIO, &info); + } else { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to reflect SPIRV: %s", SDL_GetError()); + result = 1; + } + } else { + SDL_ShaderCross_GraphicsShaderInfo info; + if (SDL_ShaderCross_ReflectGraphicsSPIRV( + fileData, + fileSize, + &info)) { + write_graphics_reflect_json(outputIO, &info); + } else { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to reflect SPIRV: %s", SDL_GetError()); + result = 1; + } + } + break; + } + case SHADERFORMAT_INVALID: { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Destination format not provided!"); result = 1; @@ -469,6 +586,55 @@ int main(int argc, char *argv[]) break; } + case SHADERFORMAT_JSON: { + void *spirv = SDL_ShaderCross_CompileSPIRVFromHLSL( + fileData, + entrypointName, + includeDir, + defines, + numDefines, + shaderStage, + &bytecodeSize); + + if (spirv == NULL) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to compile HLSL to SPIRV: %s", SDL_GetError()); + result = 1; + break; + } + + if (shaderStage == SDL_SHADERCROSS_SHADERSTAGE_COMPUTE) { + SDL_ShaderCross_ComputePipelineInfo info; + bool result = SDL_ShaderCross_ReflectComputeSPIRV( + spirv, + bytecodeSize, + &info); + SDL_free(spirv); + + if (result) { + write_compute_reflect_json(outputIO, &info); + } else { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to reflect SPIRV: %s", SDL_GetError()); + result = 1; + } + } else { + SDL_ShaderCross_GraphicsShaderInfo info; + bool result = !SDL_ShaderCross_ReflectGraphicsSPIRV( + spirv, + bytecodeSize, + &info); + SDL_free(spirv); + + if (result) { + write_graphics_reflect_json(outputIO, &info); + } else { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to reflect SPIRV: %s", SDL_GetError()); + result = 1; + } + } + + break; + } + case SHADERFORMAT_INVALID: { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Destination format not provided!"); result = 1;