SDL_gpu_shadercross: Automatic resource reflection in Compile functions (#58)

From aab5bd0154572cdc928129b780db09351adc2da9 Mon Sep 17 00:00:00 2001
From: Evan Hemsley <[EMAIL REDACTED]>
Date: Wed, 13 Nov 2024 11:35:08 -0800
Subject: [PATCH] Automatic resource reflection in Compile functions (#58)

---
 .../SDL_gpu_shadercross.h                     |  35 +-
 src/SDL_gpu_shadercross.c                     | 614 +++++++++++-------
 2 files changed, 372 insertions(+), 277 deletions(-)

diff --git a/include/SDL3_gpu_shadercross/SDL_gpu_shadercross.h b/include/SDL3_gpu_shadercross/SDL_gpu_shadercross.h
index 3845275..b89fc09 100644
--- a/include/SDL3_gpu_shadercross/SDL_gpu_shadercross.h
+++ b/include/SDL3_gpu_shadercross/SDL_gpu_shadercross.h
@@ -45,25 +45,6 @@ typedef enum SDL_ShaderCross_ShaderStage
    SDL_SHADERCROSS_SHADERSTAGE_COMPUTE
 } SDL_ShaderCross_ShaderStage;
 
-typedef struct SDL_ShaderCross_ShaderResourceInfo {
-    Uint32 num_samplers;         /**< The number of samplers defined in the shader. */
-    Uint32 num_storage_textures; /**< The number of storage textures defined in the shader. */
-    Uint32 num_storage_buffers;  /**< The number of storage buffers defined in the shader. */
-    Uint32 num_uniform_buffers;  /**< The number of uniform buffers defined in the shader. */
-} SDL_ShaderCross_ShaderResourceInfo;
-
-typedef struct SDL_ShaderCross_ComputeResourceInfo {
-    Uint32 num_samplers;                   /**< The number of samplers defined in the shader. */
-    Uint32 num_readonly_storage_textures;  /**< The number of storage textures defined in the shader. */
-    Uint32 num_readonly_storage_buffers;   /**< The number of storage buffers defined in the shader. */
-    Uint32 num_readwrite_storage_textures; /**< The number of read-write storage textures defined in the shader. */
-    Uint32 num_readwrite_storage_buffers;  /**< The number of read-write storage buffers defined in the shader. */
-    Uint32 num_uniform_buffers;            /**< The number of uniform buffers defined in the shader. */
-    Uint32 threadcount_x;                  /**< The number of threads in the X dimension. This should match the value in the shader. */
-    Uint32 threadcount_y;                  /**< The number of threads in the Y dimension. This should match the value in the shader. */
-    Uint32 threadcount_z;                  /**< The number of threads in the Z dimension. This should match the value in the shader. */
-} SDL_ShaderCross_ComputeResourceInfo;
-
 /**
  * Initializes SDL_gpu_shadercross
  *
@@ -163,7 +144,6 @@ extern SDL_DECLSPEC void * SDLCALL SDL_ShaderCross_CompileDXILFromSPIRV(
  * \param bytecodeSize the length of the SPIRV bytecode.
  * \param entrypoint the entry point function name for the shader in UTF-8.
  * \param shaderStage the shader stage to compile the shader with.
- * \param resourceInfo a pointer to an SDL_ShaderCross_ShaderResourceInfo.
  * \returns a compiled SDL_GPUShader
  *
  * \threadsafety It is safe to call this function from any thread.
@@ -173,8 +153,7 @@ extern SDL_DECLSPEC SDL_GPUShader * SDLCALL SDL_ShaderCross_CompileGraphicsShade
     const Uint8 *bytecode,
     size_t bytecodeSize,
     const char *entrypoint,
-    SDL_GPUShaderStage shaderStage,
-    const SDL_ShaderCross_ShaderResourceInfo *resourceInfo);
+    SDL_GPUShaderStage shaderStage);
 
 /**
  * Compile an SDL GPU compute pipeline from SPIRV code.
@@ -183,7 +162,6 @@ extern SDL_DECLSPEC SDL_GPUShader * SDLCALL SDL_ShaderCross_CompileGraphicsShade
  * \param bytecode the SPIRV bytecode.
  * \param bytecodeSize the length of the SPIRV bytecode.
  * \param entrypoint the entry point function name for the shader in UTF-8.
- * \param resourceInfo a pointer to an SDL_ShaderCross_ShaderResourceInfo.
  * \returns a compiled SDL_GPUComputePipeline
  *
  * \threadsafety It is safe to call this function from any thread.
@@ -192,8 +170,7 @@ extern SDL_DECLSPEC SDL_GPUComputePipeline * SDLCALL SDL_ShaderCross_CompileComp
     SDL_GPUDevice *device,
     const Uint8 *bytecode,
     size_t bytecodeSize,
-    const char *entrypoint,
-    const SDL_ShaderCross_ComputeResourceInfo *resourceInfo);
+    const char *entrypoint);
 
 /**
  * Get the supported shader formats that HLSL cross-compilation can output
@@ -273,7 +250,6 @@ extern SDL_DECLSPEC void * SDLCALL SDL_ShaderCross_CompileSPIRVFromHLSL(
  * \param entrypoint the entry point function name for the shader in UTF-8.
  * \param includeDir the include directory for shader code. Optional, can be NULL.
  * \param graphicsShaderStage the shader stage to compile the shader with.
- * \param resourceInfo a pointer to an SDL_ShaderCross_ShaderResourceInfo.
  * \returns a compiled SDL_GPUShader
  *
  * \threadsafety It is safe to call this function from any thread.
@@ -283,8 +259,7 @@ extern SDL_DECLSPEC SDL_GPUShader * SDLCALL SDL_ShaderCross_CompileGraphicsShade
     const char *hlslSource,
     const char *entrypoint,
     const char *includeDir,
-    SDL_GPUShaderStage graphicsShaderStage,
-    const SDL_ShaderCross_ShaderResourceInfo *resourceInfo);
+    SDL_GPUShaderStage graphicsShaderStage);
 
 /**
  * Compile an SDL GPU compute pipeline from code.
@@ -293,7 +268,6 @@ extern SDL_DECLSPEC SDL_GPUShader * SDLCALL SDL_ShaderCross_CompileGraphicsShade
  * \param hlslSource the HLSL source code for the shader.
  * \param entrypoint the entry point function name for the shader in UTF-8.
  * \param includeDir the include directory for shader code. Optional, can be NULL.
- * \param resourceInfo a pointer to an SDL_ShaderCross_ComputeResourceInfo.
  * \returns a compiled SDL_GPUComputePipeline
  *
  * \threadsafety It is safe to call this function from any thread.
@@ -302,8 +276,7 @@ extern SDL_DECLSPEC SDL_GPUComputePipeline * SDLCALL SDL_ShaderCross_CompileComp
     SDL_GPUDevice *device,
     const char *hlslSource,
     const char *entrypoint,
-    const char *includeDir,
-    const SDL_ShaderCross_ComputeResourceInfo *resourceInfo);
+    const char *includeDir);
 
 #ifdef __cplusplus
 }
diff --git a/src/SDL_gpu_shadercross.c b/src/SDL_gpu_shadercross.c
index 4ac52d9..48420d7 100644
--- a/src/SDL_gpu_shadercross.c
+++ b/src/SDL_gpu_shadercross.c
@@ -585,82 +585,6 @@ void *SDL_ShaderCross_CompileSPIRVFromHLSL(
         size);
 }
 
-static void *SDL_ShaderCross_INTERNAL_CreateShaderFromDXC(
-    SDL_GPUDevice *device,
-    const char *hlslSource,
-    const char *entrypoint,
-    const char *includeDir,
-    SDL_ShaderCross_ShaderStage shaderStage,
-    const void *resourceInfo,
-    bool spirv)
-{
-    void *result;
-    void *bytecode;
-    size_t bytecodeSize;
-
-    if (!spirv) {
-        // If destination is DXIL, force roundtrip through SPIRV-Cross.
-        bytecode = SDL_ShaderCross_CompileDXILFromHLSL(
-            hlslSource,
-            entrypoint,
-            includeDir,
-            shaderStage,
-            &bytecodeSize);
-    } else {
-        // Otherwise just compile straight to SPIRV.
-        bytecode = SDL_ShaderCross_INTERNAL_CompileUsingDXC(
-            hlslSource,
-            entrypoint,
-            includeDir,
-            shaderStage,
-            spirv,
-            &bytecodeSize);
-    }
-
-    if (bytecode == NULL) {
-        return NULL;
-    }
-
-    if (shaderStage == SDL_SHADERCROSS_SHADERSTAGE_COMPUTE) {
-        SDL_ShaderCross_ComputeResourceInfo *info = (SDL_ShaderCross_ComputeResourceInfo *)resourceInfo;
-        SDL_GPUComputePipelineCreateInfo createInfo;
-        createInfo.code = (const Uint8 *)bytecode;
-        createInfo.code_size = bytecodeSize;
-        createInfo.entrypoint = entrypoint;
-        createInfo.format = spirv ? SDL_GPU_SHADERFORMAT_SPIRV : SDL_GPU_SHADERFORMAT_DXIL;
-        createInfo.num_samplers = info->num_samplers;
-        createInfo.num_readonly_storage_textures = info->num_readonly_storage_textures;
-        createInfo.num_readonly_storage_buffers = info->num_readonly_storage_buffers;
-        createInfo.num_readwrite_storage_textures = info->num_readwrite_storage_textures;
-        createInfo.num_readwrite_storage_buffers = info->num_readwrite_storage_buffers;
-        createInfo.num_uniform_buffers = info->num_uniform_buffers;
-        createInfo.threadcount_x = info->threadcount_x;
-        createInfo.threadcount_y = info->threadcount_y;
-        createInfo.threadcount_z = info->threadcount_z;
-        createInfo.props = 0;
-
-        result = SDL_CreateGPUComputePipeline(device, &createInfo);
-    } else {
-        SDL_ShaderCross_ShaderResourceInfo *info = (SDL_ShaderCross_ShaderResourceInfo *)resourceInfo;
-        SDL_GPUShaderCreateInfo createInfo;
-        createInfo.code = (const Uint8 *)bytecode;
-        createInfo.code_size = bytecodeSize;
-        createInfo.entrypoint = entrypoint;
-        createInfo.format = spirv ? SDL_GPU_SHADERFORMAT_SPIRV : SDL_GPU_SHADERFORMAT_DXIL;
-        createInfo.stage = (SDL_GPUShaderStage)shaderStage;
-        createInfo.num_samplers = info->num_samplers;
-        createInfo.num_storage_textures = info->num_storage_textures;
-        createInfo.num_storage_buffers = info->num_storage_buffers;
-        createInfo.num_uniform_buffers = info->num_uniform_buffers;
-        createInfo.props = 0;
-
-        result = SDL_CreateGPUShader(device, &createInfo);
-    }
-
-    SDL_free(bytecode);
-    return result;
-}
-
 /* DXBC via FXC */
 
 /* d3dcompiler Type Definitions */
@@ -856,146 +780,45 @@ void *SDL_ShaderCross_CompileDXBCFromHLSL(
         size);
 }
 
-static void *SDL_ShaderCross_INTERNAL_CreateShaderFromDXBC(
+static void *SDL_ShaderCross_INTERNAL_CreateShaderFromHLSL(
     SDL_GPUDevice *device,
     const char *hlslSource,
     const char *entrypoint,
     const char *includeDir,
-    SDL_ShaderCross_ShaderStage shaderStage,
-    const void *resourceInfo)
+    SDL_ShaderCross_ShaderStage shaderStage)
 {
-    void *result;
     size_t bytecodeSize;
 
-    void *bytecode = SDL_ShaderCross_INTERNAL_CompileDXBCFromHLSL(
+    // We'll go through SPIRV-Cross for all of these to more easily obtain reflection metadata.
+    void *spirv = SDL_ShaderCross_CompileSPIRVFromHLSL(
         hlslSource,
         entrypoint,
         includeDir,
         shaderStage,
-        true,
         &bytecodeSize);
 
-    if (bytecode == NULL) {
+    if (spirv == NULL) {
+        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", "Failed to compile SPIR-V!");
         return NULL;
     }
 
+    void *result;
     if (shaderStage == SDL_SHADERCROSS_SHADERSTAGE_COMPUTE) {
-        SDL_ShaderCross_ComputeResourceInfo *info = (SDL_ShaderCross_ComputeResourceInfo *)resourceInfo;
-        SDL_GPUComputePipelineCreateInfo createInfo;
-        createInfo.code = (const Uint8 *)bytecode;
-        createInfo.code_size = bytecodeSize;
-        createInfo.entrypoint = entrypoint;
-        createInfo.format = SDL_GPU_SHADERFORMAT_DXBC;
-        createInfo.num_samplers = info->num_samplers;
-        createInfo.num_readonly_storage_textures = info->num_readonly_storage_textures;
-        createInfo.num_readonly_storage_buffers = info->num_readonly_storage_buffers;
-        createInfo.num_readwrite_storage_textures = info->num_readwrite_storage_textures;
-        createInfo.num_readwrite_storage_buffers = info->num_readwrite_storage_buffers;
-        createInfo.num_uniform_buffers = info->num_uniform_buffers;
-        createInfo.threadcount_x = info->threadcount_x;
-        createInfo.threadcount_y = info->threadcount_y;
-        createInfo.threadcount_z = info->threadcount_z;
-        createInfo.props = 0;
-
-        result = SDL_CreateGPUComputePipeline(device, &createInfo);
-    } else {
-        SDL_ShaderCross_ShaderResourceInfo *info = (SDL_ShaderCross_ShaderResourceInfo *)resourceInfo;
-        SDL_GPUShaderCreateInfo createInfo;
-        createInfo.code = (const Uint8 *)bytecode;
-        createInfo.code_size = bytecodeSize;
-        createInfo.entrypoint = entrypoint;
-        createInfo.format = SDL_GPU_SHADERFORMAT_DXBC;
-        createInfo.stage = (SDL_GPUShaderStage)shaderStage;
-        createInfo.num_samplers = info->num_samplers;
-        createInfo.num_storage_textures = info->num_storage_textures;
-        createInfo.num_storage_buffers = info->num_storage_buffers;
-        createInfo.num_uniform_buffers = info->num_uniform_buffers;
-        createInfo.props = 0;
-
-        result = SDL_CreateGPUShader(device, &createInfo);
-    }
-
-    SDL_free(bytecode);
-    return result;
-}
-
-static void *SDL_ShaderCross_INTERNAL_CreateShaderFromHLSL(
-    SDL_GPUDevice *device,
-    const char *hlslSource,
-    const char *entrypoint,
-    const char *includeDir,
-    SDL_ShaderCross_ShaderStage shaderStage,
-    const void *resourceInfo)
-{
-    SDL_GPUShaderFormat format = SDL_GetGPUShaderFormats(device);
-
-    if (format & SDL_GPU_SHADERFORMAT_DXIL) {
-        return SDL_ShaderCross_INTERNAL_CreateShaderFromDXC(
+        result = SDL_ShaderCross_CompileComputePipelineFromSPIRV(
             device,
-            hlslSource,
-            entrypoint,
-            includeDir,
-            shaderStage,
-            resourceInfo,
-            false);
-    }
-
-    if (format & SDL_GPU_SHADERFORMAT_DXBC) {
-        return SDL_ShaderCross_INTERNAL_CreateShaderFromDXBC(
-            device,
-            hlslSource,
-            entrypoint,
-            includeDir,
-            shaderStage,
-            resourceInfo);
-    }
-
-    if (format & SDL_GPU_SHADERFORMAT_SPIRV) {
-        return SDL_ShaderCross_INTERNAL_CreateShaderFromDXC(
+            spirv,
+            bytecodeSize,
+            entrypoint);
+    } else {
+        result = SDL_ShaderCross_CompileGraphicsShaderFromSPIRV(
             device,
-            hlslSource,
-            entrypoint,
-            includeDir,
-            shaderStage,
-            resourceInfo,
-            true);
-    }
-
-    if (format & SDL_GPU_SHADERFORMAT_MSL) {
-        size_t bytecodeSize;
-        void *spirv = SDL_ShaderCross_CompileSPIRVFromHLSL(
-            hlslSource,
+            spirv,
+            bytecodeSize,
             entrypoint,
-            includeDir,
-            shaderStage,
-            &bytecodeSize);
-        if (spirv == NULL) {
-            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", "Failed to compile SPIR-V!");
-            return NULL;
-        }
-        void *result;
-        if (shaderStage == SDL_SHADERCROSS_SHADERSTAGE_COMPUTE) {
-            result = SDL_ShaderCross_CompileComputePipelineFromSPIRV(
-                device,
-                spirv,
-                bytecodeSize,
-                entrypoint,
-                (SDL_ShaderCross_ComputeResourceInfo *)resourceInfo);
-        } else {
-            result = SDL_ShaderCross_CompileGraphicsShaderFromSPIRV(
-                device,
-                spirv,
-                bytecodeSize,
-                entrypoint,
-                (SDL_GPUShaderStage)shaderStage,
-                (SDL_ShaderCross_ShaderResourceInfo *)resourceInfo);
-        }
-        SDL_free(spirv);
-        return result;
+            (SDL_GPUShaderStage)shaderStage);
     }
-
-    SDL_SetError("SDL_ShaderCross_INTERNAL_CreateShaderFromHLSL: Unexpected SDL_GPUShaderFormat");
-    return NULL;
+    SDL_free(spirv);
+    return result;
 }
 
 SDL_GPUShader *SDL_ShaderCross_CompileGraphicsShaderFromHLSL(
@@ -1003,32 +826,28 @@ SDL_GPUShader *SDL_ShaderCross_CompileGraphicsShaderFromHLSL(
     const char *hlslSource,
     const char *entrypoint,
     const char *includeDir,
-    SDL_GPUShaderStage graphicsShaderStage,
-    const SDL_ShaderCross_ShaderResourceInfo *resourceInfo)
+    SDL_GPUShaderStage graphicsShaderStage)
 {
     return (SDL_GPUShader *)SDL_ShaderCross_INTERNAL_CreateShaderFromHLSL(
         device,
         hlslSource,
         entrypoint,
         includeDir,
-        (SDL_ShaderCross_ShaderStage)graphicsShaderStage,
-        (const void *)resourceInfo);
+        (SDL_ShaderCross_ShaderStage)graphicsShaderStage);
 }
 
 SDL_GPUComputePipeline *SDL_ShaderCross_CompileComputePipelineFromHLSL(
     SDL_GPUDevice *device,
     const char *hlslSource,
     const char *entrypoint,
-    const char *includeDir,
-    const SDL_ShaderCross_ComputeResourceInfo *resourceInfo)
+    const char *includeDir)
 {
     return (SDL_GPUComputePipeline *)SDL_ShaderCross_INTERNAL_CreateShaderFromHLSL(
         device,
         hlslSource,
         entrypoint,
         includeDir,
-        SDL_SHADERCROSS_SHADERSTAGE_COMPUTE,
-        (const void *)resourceInfo);
+        SDL_SHADERCROSS_SHADERSTAGE_COMPUTE);
 }
 
 #include <spirv_cross_c.h>
@@ -1140,6 +959,20 @@ static SPIRVTranspileContext *SDL_ShaderCross_INTERNAL_TranspileFromSPIRV(
             return NULL;
         }
 
+        // If source is HLSL, we might have separate images and samplers
+        if (num_texture_samplers == 0) {
+            result = spvc_resources_get_resource_list_for_type(
+                resources,
+                SPVC_RESOURCE_TYPE_SEPARATE_SAMPLERS,
+                (const spvc_reflected_resource **)&reflected_resources,
+                &num_texture_samplers);
+            if (result < 0) {
+                SPVC_ERROR(spvc_resources_get_resource_list_for_type);
+                spvc_context_destroy(context);
+                return false;
+            }
+        }
+
         spvc_msl_resource_binding binding;
         for (size_t i = 0; i < num_texture_samplers; i += 1) {
             if (!spvc_compiler_has_decoration(compiler, reflected_resources[i].id, SpvDecorationDescriptorSet) || !spvc_compiler_has_decoration(compiler, reflected_resources[i].id, SpvDecorationBinding)) {
@@ -1322,6 +1155,20 @@ static SPIRVTranspileContext *SDL_ShaderCross_INTERNAL_TranspileFromSPIRV(
             return NULL;
         }
 
+        // If source is HLSL, we might have separate images and samplers
+        if (num_texture_samplers == 0) {
+            result = spvc_resources_get_resource_list_for_type(
+                resources,
+                SPVC_RESOURCE_TYPE_SEPARATE_SAMPLERS,
+                (const spvc_reflected_resource **)&reflected_resources,
+                &num_texture_samplers);
+            if (result < 0) {
+                SPVC_ERROR(spvc_resources_get_resource_list_for_type);
+                spvc_context_destroy(context);
+                return false;
+            }
+        }
+
         for (size_t i = 0; i < num_texture_samplers; i += 1) {
             if (!spvc_compiler_has_decoration(compiler, reflected_resources[i].id, SpvDecorationDescriptorSet) || !spvc_compiler_has_decoration(compiler, reflected_resources[i].id, SpvDecorationBinding)) {
                 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", "Shader resources must have descriptor set and binding index!");
@@ -1561,13 +1408,308 @@ static SPIRVTranspileContext *SDL_ShaderCross_INTERNAL_TranspileFromSPIRV(
     return transpileContext;
 }
 
+// Acquire CreateInfo metadata from SPIRV bytecode.
+// TODO: validate descriptor sets
+static bool SDL_ShaderCross_INTERNAL_ReflectGraphicsSPIRV(
+    const Uint8 *code,
+    size_t codeSize,
+    SDL_GPUShaderCreateInfo *createInfo // filled in with reflected data
+) {
+    spvc_result result;
+    spvc_context context = NULL;
+    spvc_parsed_ir ir = NULL;
+    spvc_compiler compiler = NULL;
+    size_t num_texture_samplers;
+    size_t num_storage_textures;
+    size_t num_storage_buffers;
+    size_t num_uniform_buffers;
+
+    /* Create the SPIRV-Cross context */
+    result = spvc_context_create(&context);
+    if (result < 0) {
+        SDL_SetError("spvc_context_create failed: %X", result);
+        return false;
+    }
+
+    /* Parse the SPIR-V into IR */
+    result = spvc_context_parse_spirv(context, (const SpvId *)code, codeSize / sizeof(SpvId), &ir);
+    if (result < 0) {
+        SPVC_ERROR(spvc_context_parse_spirv);
+        spvc_context_destroy(context);
+        return false;
+    }
+
+    /* Create a reflection-only compiler */
+    result = spvc_context_create_compiler(context, SPVC_BACKEND_NONE, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler);
+    if (result < 0) {
+        SPVC_ERROR(spvc_context_create_compiler);
+        spvc_context_destroy(context);
+        return false;
+    }
+
+    spvc_resources resources;
+    spvc_reflected_resource *reflected_resources;
+
+    result = spvc_compiler_create_shader_resources(compiler, &resources);
+    if (result < 0) {
+        SPVC_ERROR(spvc_compiler_create_shader_resources);
+        spvc_context_destroy(context);
+        return false;
+    }
+
+    // Combined texture-samplers
+    result = spvc_resources_get_resource_list_for_type(
+        resources,
+        SPVC_RESOURCE_TYPE_SAMPLED_IMAGE,
+        (const spvc_reflected_resource **)&reflected_resources,
+        &num_texture_samplers);
+    if (result < 0) {
+        SPVC_ERROR(spvc_resources_get_resource_list_for_type);
+        spvc_context_destroy(context);
+        return false;
+    }
+
+    // If source is HLSL, we might have separate images and samplers
+    if (num_texture_samplers == 0) {
+        result = spvc_resources_get_resource_list_for_type(
+            resources,
+            SPVC_RESOURCE_TYPE_SEPARATE_SAMPLERS,
+            (const spvc_reflected_resource **)&reflected_resources,
+            &num_texture_samplers);
+        if (result < 0) {
+            SPVC_ERROR(spvc_resources_get_resource_list_for_type);
+            spvc_context_destroy(context);
+            return false;
+        }
+    }
+
+    // Storage textures
+    result = spvc_resources_get_resource_list_for_type(
+        resources,
+        SPVC_RESOURCE_TYPE_STORAGE_IMAGE,
+        (const spvc_reflected_resource **)&reflected_resources,
+        &num_storage_textures);
+    if (result < 0) {
+        SPVC_ERROR(spvc_resources_get_resource_list_for_type);
+        spvc_context_destroy(context);
+        return false;
+    }
+
+    // Storage buffers
+    result = spvc_resources_get_resource_list_for_type(
+        resources,
+        SPVC_RESOURCE_TYPE_STORAGE_BUFFER,
+        (const spvc_reflected_resource **)&reflected_resources,
+        &num_storage_buffers);
+    if (result < 0) {
+        SPVC_ERROR(spvc_resources_get_resource_list_for_type);
+        spvc_context_destroy(context);
+        return false;
+    }
+
+    // Uniform buffers
+    result = spvc_resources_get_resource_list_for_type(
+        resources,
+        SPVC_RESOURCE_TYPE_UNIFORM_BUFFER,
+        (const spvc_reflected_resource **)&reflected_resources,
+        &num_uniform_buffers);
+    if (result < 0) {
+        SPVC_ERROR(spvc_resources_get_resource_list_for_type);
+        spvc_context_destroy(context);
+        return false;
+    }
+
+    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;
+    return true;
+}
+
+static bool SDL_ShaderCross_INTERNAL_ReflectComputeSPIRV(
+    const Uint8 *code,
+    size_t codeSize,
+    SDL_GPUComputePipelineCreateInfo *createInfo // filled in with reflected data
+) {
+    spvc_result result;
+    spvc_context context = NULL;
+    spvc_parsed_ir ir = NULL;
+    spvc_compiler compiler = NULL;
+    size_t num_texture_samplers = 0;
+    size_t num_readonly_storage_textures = 0;
+    size_t num_readonly_storage_buffers = 0;
+    size_t num_readwrite_storage_textures = 0;
+    size_t num_readwrite_storage_buffers = 0;
+    size_t num_uniform_buffers = 0;
+
+    size_t num_storage_textures = 0;
+    size_t num_storage_buffers = 0;
+
+    /* Create the SPIRV-Cross context */
+    result = spvc_context_create(&context);
+    if (result < 0) {
+        SDL_SetError("spvc_context_create failed: %X", result);
+        return false;
+    }
+
+    /* Parse the SPIR-V into IR */
+    result = spvc_context_parse_spirv(context, (const SpvId *)code, codeSize / sizeof(SpvId), &ir);
+    if (result < 0) {
+        SPVC_ERROR(spvc_context_parse_spirv);
+        spvc_context_destroy(context);
+        return false;
+    }
+
+    /* Create a reflection-only compiler */
+    result = spvc_context_create_compiler(context, SPVC_BACKEND_NONE, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler);
+    if (result < 0) {
+        SPVC_ERROR(spvc_context_create_compiler);
+        spvc_context_destroy(context);
+        return false;
+    }
+
+    spvc_resources resources;
+    spvc_reflected_resource *reflected_resources;
+
+    result = spvc_compiler_create_shader_resources(compiler, &resources);
+    if (result < 0) {
+        SPVC_ERROR(spvc_compiler_create_shader_resources);
+        spvc_context_destroy(context);
+        return false;
+    }
+
+    // Combined texture-samplers
+    result = spvc_resources_get_resource_list_for_type(
+        resources,
+        SPVC_RESOURCE_TYPE_SAMPLED_IMAGE,
+        (const spvc_reflected_resource **)&reflected_resources,
+        &num_texture_samplers);
+    if (result < 0) {
+        SPVC_ERROR(spvc_resources_get_resource_list_for_type);
+        spvc_context_destroy(context);
+        return false;
+    }
+
+    // If source is HLSL, we might have separate images and samplers
+    if (num_texture_samplers == 0) {
+        result = spvc_resources_get_resource_list_for_type(
+            resources,
+            SPVC_RESOURCE_TYPE_SEPARATE_SAMPLERS,
+            (const spvc_reflected_resource **)&reflected_resources,
+            &num_texture_samplers);
+        if (result < 0) {
+            SPVC_ERROR(spvc_resources_get_resource_list_for_type);
+            spvc_context_destroy(context);
+            return false;
+        }
+    }
+
+    // Storage textures
+    result = spvc_resources_get_resource_list_for_type(
+        resources,
+        SPVC_RESOURCE_TYPE_STORAGE_IMAGE,
+        (const spvc_reflected_resource **)&reflected_resources,
+        &num_storage_textures);
+    if (result < 0) {
+        SPVC_ERROR(spvc_resources_get_resource_list_for_type);
+        spvc_context_destroy(context);
+        return false;
+    }
+
+    for (size_t i = 0; i < num_storage_textures; i += 1) {
+        if (!spvc_compiler_has_decoration(compiler, reflected_resources[i].id, SpvDecorationDescriptorSet) || !spvc_compiler_has_decoration(compiler, reflected_resources[i].id, SpvDecorationBinding)) {
+            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", "Shader resources must have descriptor set and binding index!");
+            spvc_context_destroy(context);
+            return false;
+        }
+
+        unsigned int descriptor_set_index = spvc_compiler_get_decoration(compiler, reflected_resources[i].id, SpvDecorationDescriptorSet);
+
+        if (descriptor_set_index == 0) {
+            num_readonly_storage_textures += 1;
+        } else if (descriptor_set_index == 1) {
+            num_readwrite_storage_textures += 1;
+        } else {
+            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", "Descriptor set index for compute storage texture must be 0 or 1!");
+            spvc_context_destroy(context);
+            return false;
+        }
+    }
+
+    // Storage buffers
+    result = spvc_resources_get_resource_list_for_type(
+        resources,
+        SPVC_RESOURCE_TYPE_STORAGE_BUFFER,
+        (const spvc_reflected_resource **)&reflected_resources,
+        &num_storage_buffers);
+    if (result < 0) {
+        SPVC_ERROR(spvc_resources_get_resource_list_for_type);
+        spvc_context_destroy(context);
+        return false;
+    }
+
+    // Readonly storage buffers
+    for (size_t i = 0; i < num_storage_buffers; i += 1) {
+        if (!spvc_compiler_has_decoration(compiler, reflected_resources[i].id, SpvDecorationDescriptorSet) || !spvc_compiler_has_decoration(compiler, reflected_resources[i].id, SpvDecorationBinding)) {
+            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", "Shader resources must have descriptor set and binding index!");
+            spvc_context_destroy(context);
+            return false;
+        }
+
+        unsigned int descriptor_set_index = spvc_compiler_get_decoration(compiler, reflected_resources[i].id, SpvDecorationDescriptorSet);
+        if (!(descriptor_set_index == 0 || descriptor_set_index == 1)) {
+            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", "Descriptor set index for compute storage buffer must be 0 or 1!");
+            spvc_context_destroy(context);
+            return false;
+        }
+
+        if (descriptor_set_index == 0) {
+            num_readonly_storage_buffers += 1;
+        } else if (descriptor_set_index == 1) {
+            num_readwrite_storage_buffers += 1;
+        } else {
+            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", "Descriptor set index for compute storage buffer must be 0 or 1!");
+            spvc_context_destroy(context);
+            return false;
+        }
+    }
+
+    // Uniform buffers
+    result = spvc_resources_get_resource_list_for_type(
+        resources,
+        SPVC_RESOURCE_TYPE_UNIFORM_BUFFER,
+        (const spvc_reflected_resource **)&reflected_resources,
+        &num_uniform_buffers);
+    if (result < 0) {
+        SPVC_ERROR(spvc_resources_get_resource_list_for_type);
+        spvc_context_destroy(context);
+        return false;
+    }
+
+    // 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);
+
+    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;
+    return true;
+}
+
 static void *SDL_ShaderCross_INTERNAL_CompileFromSPIRV(
     SDL_GPUDevice *device,
     const Uint8 *bytecode,
     size_t bytecodeSize,
     const char *entrypoint,
     SDL_ShaderCross_ShaderStage shaderStage,
-    const void *resourceInfo,
     SDL_GPUShaderFormat targetFormat
 ) {
     spvc_backend backend;
@@ -1582,7 +1724,7 @@ static void *SDL_ShaderCross_INTERNAL_CompileFromSPIRV(
     } else if (targetFormat == SDL_GPU_SHADERFORMAT_MSL) {
         backend = SPVC_BACKEND_MSL;
     } else {
-        SDL_SetError("SDL_ShaderCross_INTERNAL_CreateShaderFromSPIRV: Unexpected SDL_GPUBackend");
+        SDL_SetError("SDL_ShaderCross_INTERNAL_CompileFromSPIRV: Unexpected SDL_GPUBackend");
         return NULL;
     }
 
@@ -1597,19 +1739,13 @@ static void *SDL_ShaderCross_INTERNAL_CompileFromSPIRV(
     void *shaderObject = NULL;
 
     if (shaderStage == SDL_SHADERCROSS_SHADERSTAGE_COMPUTE) {
-        SDL_ShaderCross_ComputeResourceInfo *info = (SDL_ShaderCross_ComputeResourceInfo *)resourceInfo;
         SDL_GPUComputePipelineCreateInfo createInfo;
+        SDL_ShaderCross_INTERNAL_ReflectComputeSPIRV(
+            bytecode,
+            bytecodeSize,
+            &createInfo);
         createInfo.entrypoint = transpileContext->cleansed_entrypoint;
         createInfo.format = targetFormat;
-        createInfo.num_samplers = info->num_samplers;
-        createInfo.num_readonly_storage_textures = info->num_readonly_storage_textures;
-        createInfo.num_readonly_storage_buffers = info->num_readonly_storage_buffers;
-        createInfo.num_readwrite_storage_textures =

(Patch may be truncated, please check the link at the top of this post.)