SDL_gpu_shadercross: Link SPIRV-Cross instead of using SDL_LoadObject (#46)

From 3385fb5c8b802abe8208ece01ffee6b5ef743867 Mon Sep 17 00:00:00 2001
From: Evan Hemsley <[EMAIL REDACTED]>
Date: Tue, 5 Nov 2024 10:53:19 -0800
Subject: [PATCH] Link SPIRV-Cross instead of using SDL_LoadObject (#46)

---------

Co-authored-by: Anonymous Maarten <anonymous.maarten@gmail.com>
---
 .github/workflows/main.yml                    |   15 +-
 .gitmodules                                   |    3 +
 CMakeLists.txt                                |  115 +-
 cmake/SDL3_gpu_shadercrossConfig.cmake.in     |   10 +-
 cmake/sdl3-gpu-shadercross.pc.in              |    4 +-
 cmake/test/CMakeLists.txt                     |   44 +
 cmake/test/main.c                             |   16 +
 external/Get-GitModules.ps1                   |   36 +
 external/SPIRV-Cross                          |    1 +
 external/download.sh                          |   18 +
 .../SDL_gpu_shadercross.h                     |    1 +
 src/SDL_gpu_shadercross.c                     |  374 +--
 src/cli.c                                     |   12 +-
 src/spirv.h                                   | 2894 -----------------
 src/spirv_cross_c.h                           | 1101 -------
 15 files changed, 355 insertions(+), 4289 deletions(-)
 create mode 100644 .gitmodules
 create mode 100644 cmake/test/CMakeLists.txt
 create mode 100644 cmake/test/main.c
 create mode 100644 external/Get-GitModules.ps1
 create mode 160000 external/SPIRV-Cross
 create mode 100755 external/download.sh
 delete mode 100644 src/spirv.h
 delete mode 100644 src/spirv_cross_c.h

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index b1c7820..dedba3d 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -73,13 +73,16 @@ jobs:
       - name: Configure (CMake)
         run: |
           cmake -S . -B build -GNinja \
+            -DCMAKE_BUILD_TYPE=Release \
+            -DSDLGPUSHADERCROSS_SHARED=ON \
             -DSDLGPUSHADERCROSS_STATIC=ON \
+            -DSDLGPUSHADERCROSS_VENDORED=ON \
             -DSDLGPUSHADERCROSS_CLI=ON \
             -DSDLGPUSHADERCROSS_WERROR=ON \
             -DSDLGPUSHADERCROSS_INSTALL=ON \
             -DSDLGPUSHADERCROSS_INSTALL_DEPS=ON \
             -DSDLGPUSHADERCROSS_INSTALL_CPACK=ON \
-            -DCMAKE_INSTALL_PREFIX=prefix
+            -DCMAKE_INSTALL_PREFIX="${PWD}/prefix"
 
       - name: Build (CMake)
         id: build
@@ -87,6 +90,7 @@ jobs:
           cmake --build build --config Release --parallel --verbose
 
       - name: Install (CMake)
+        id: install
         if: ${{ always() && steps.build.outcome == 'success' }}
         run: |
           cmake --install build/ --config Release
@@ -96,6 +100,15 @@ jobs:
         run: |
           cmake --build build/ --target package
 
+      - name: Verify CMake configuration files
+        if: ${{ always() && steps.install.outcome == 'success' }}
+        run: |
+          cmake -S cmake/test -B cmake_config_build \
+            -DCMAKE_BUILD_TYPE=Release \
+            -DCMAKE_PREFIX_PATH="${PWD}/prefix" \
+            -GNinja
+          cmake --build cmake_config_build --verbose
+
       - uses: actions/upload-artifact@v4
         if: ${{ always() && steps.package.outcome == 'success' }}
         with:
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..3dea3c4
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "spirv-cross"]
+	path = external/SPIRV-Cross
+	url = https://github.com/KhronosGroup/SPIRV-Cross.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9b878d0..4311604 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -8,6 +8,9 @@ set(MINOR_VERSION 0)
 set(MICRO_VERSION 0)
 set(SDL_REQUIRED_VERSION "3.1.3")
 
+# option() honors normal variables.
+set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
+
 project(SDL3_gpu_shadercross LANGUAGES C VERSION "${MAJOR_VERSION}.${MINOR_VERSION}.${MICRO_VERSION}")
 
 include(CMakeDependentOption)
@@ -30,14 +33,14 @@ endif()
 # Options
 option(SDLGPUSHADERCROSS_SHARED "Build shared SDL_gpu_shadercross library" ${SDLGPUSHADERCROSS_SHARED_DEFAULT})
 option(SDLGPUSHADERCROSS_STATIC "Build static SDL_gpu_shadercross library" ${SDLGPUSHADERCROSS_STATIC_DEFAULT})
-option(SDLGPUSHADERCROSS_LOAD_DYNAMIC "Load dependencies using SDL_LoadObject" ON)
-cmake_dependent_option(SDLGPUSHADERCROSS_LINK_STATIC "Link to static library variants of dependencies" OFF "NOT SDLGPUSHADERCROSS_LOAD_DYNAMIC" OFF)
+option(SDLGPUSHADERCROSS_LINK_STATIC "Link to static library variants of dependencies" OFF)
+option(SDLGPUSHADERCROSS_VENDORED "Use vendored dependencies" OFF)
 option(SDLGPUSHADERCROSS_CLI "Build command line executable" ON)
 cmake_dependent_option(SDLGPUSHADERCROSS_CLI_STATIC "Link CLI with static libraries" OFF "SDLGPUSHADERCROSS_CLI;SDLGPUSHADERCROSS_STATIC;TARGET SDL3::SDL3-static" OFF)
 option(SDLGPUSHADERCROSS_WERROR "Enable Werror" OFF)
 option(SDLGPUSHADERCROSS_INSTALL "Enable installation" OFF)
 cmake_dependent_option(SDLGPUSHADERCROSS_INSTALL_CPACK "Enable CPack installation" OFF "SDLGPUSHADERCROSS_INSTALL" OFF)
-cmake_dependent_option(SDLGPUSHADERCROSS_INSTALL_DEPS "Download, build and install dependencies" OFF "SDLGPUSHADERCROSS_INSTALL" OFF)
+cmake_dependent_option(SDLGPUSHADERCROSS_INSTALL_DEPS "Download, build and install optional dependencies" OFF "SDLGPUSHADERCROSS_INSTALL" OFF)
 
 sdl_calculate_derived_version_variables(${MAJOR_VERSION} ${MINOR_VERSION} ${MICRO_VERSION})
 SDL_DetectTargetCPUArchitectures(SDL_CPU_NAMES)
@@ -57,14 +60,63 @@ if(NOT MSVC)
 	add_compile_options(-pedantic) # -Wno-strict-aliasing
 endif()
 
+set(pc_requires )
+set(install_extra_targets )
+if(SDLGPUSHADERCROSS_VENDORED)
+	set(SPIRV_CROSS_SKIP_INSTALL ON)
+	set(SPIRV_CROSS_CLI OFF)
+	set(SPIRV_CROSS_ENABLE_TESTS OFF)
+	if(SDLGPUSHADERCROSS_LINK_STATIC)
+		set(SPIRV_CROSS_SHARED OFF)
+		set(SPIRV_CROSS_STATIC ON)
+	else()
+		set(SPIRV_CROSS_SHARED ON)
+		set(SPIRV_CROSS_STATIC OFF)
+	endif()
+
+	sdl_check_project_in_subfolder(external/SPIRV-Cross SPIRV-Cross SDLGPUSHADERCROSS_VENDORED)
+	set(SPIRV_CROSS_ENABLE_TESTS ON)
+	set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+	add_subdirectory(external/SPIRV-Cross EXCLUDE_FROM_ALL)
+
+	if(SDLGPUSHADERCROSS_LINK_STATIC)
+		enable_language(CXX)
+		if(SDLGPUSHADERCROSS_STATIC)
+			list(APPEND install_extra_targets spirv-cross-c)
+			list(APPEND pc_requires "spirv-cross-c")
+			set(spirv_extra_targets spirv-cross-glsl spirv-cross-hlsl spirv-cross-msl spirv-cross-cpp spirv-cross-reflect spirv-cross-core)
+			foreach(extra IN LISTS spirv_extra_targets)
+				if(TARGET ${extra})
+					list(APPEND install_extra_targets ${extra})
+					list(APPEND pc_requires "${extra}")
+				endif()
+			endforeach()
+		endif()
+	else()
+		list(APPEND install_extra_targets spirv-cross-c-shared)
+		list(APPEND pc_requires "spirv-cross-c-shared")
+	endif()
+else()
+	if(SDLGPUSHADERCROSS_LINK_STATIC)
+		enable_language(CXX)
+		find_package(spirv_cross_core QUIET)
+		find_package(spirv_cross_glsl QUIET)
+		find_package(spirv_cross_hlsl QUIET)
+		find_package(spirv_cross_msl QUIET)
+		find_package(spirv_cross_cpp QUIET)
+		find_package(spirv_cross_reflect QUIET)
+		find_package(spirv_cross_c REQUIRED)
+	else()
+		find_package(spirv_cross_c_shared REQUIRED)
+	endif()
+endif()
+
 # Source lists
 set(SOURCE_FILES
 	# Public Headers
 	include/SDL3_gpu_shadercross/SDL_gpu_shadercross.h
 	# Source Files
 	src/SDL_gpu_shadercross.c
-	src/spirv_cross_c.h
-	src/spirv.h
 )
 
 set(SDL3_gpu_shadercross_targets)
@@ -75,6 +127,7 @@ if(SDLGPUSHADERCROSS_SHARED)
 
 	set_property(TARGET SDL3_gpu_shadercross-shared PROPERTY DEFINE_SYMBOL DLL_EXPORT)
 	sdl_target_link_option_version_file(SDL3_gpu_shadercross-shared "${CMAKE_CURRENT_SOURCE_DIR}/src/SDL_gpu_shadercross.sym")
+	sdl_target_link_options_no_undefined(SDL3_gpu_shadercross-shared)
 
 	# Build flags
 	if(WIN32)
@@ -86,11 +139,6 @@ if(SDLGPUSHADERCROSS_SHARED)
 	target_include_directories(SDL3_gpu_shadercross-shared PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>")
 	target_include_directories(SDL3_gpu_shadercross-shared PUBLIC "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>")
 
-	# MinGW builds should statically link libgcc
-	if(MINGW)
-		target_link_options(SDL3_gpu_shadercross-shared PRIVATE "-static-libgcc")
-	endif()
-
 	# Soname
 	set_target_properties(SDL3_gpu_shadercross-shared PROPERTIES
 		OUTPUT_NAME "SDL3_gpu_shadercross"
@@ -129,6 +177,16 @@ endif()
 foreach(target IN LISTS SDL3_gpu_shadercross_targets)
 	sdl_add_warning_options(${target} WARNING_AS_ERROR ${SDLGPUSHADERCROSS_WERROR})
 	target_compile_features(${target} PRIVATE c_std_99)
+
+	if(SDLGPUSHADERCROSS_LINK_STATIC)
+		target_link_libraries(${target} PRIVATE spirv-cross-c)
+	else()
+		target_link_libraries(${target} PRIVATE spirv-cross-c-shared)
+	endif()
+	if(SDLGPUSHADERCROSS_LINK_STATIC)
+		# spirv-cross uses C++
+		set_property(TARGET ${target} PROPERTY LINKER_LANGUAGE CXX)
+	endif()
 endforeach()
 
 if(NOT TARGET SDL3_gpu_shadercross::SDL3_gpu_shadercross)
@@ -142,6 +200,7 @@ endif()
 if(SDLGPUSHADERCROSS_CLI)
 	add_executable(shadercross src/cli.c)
 	sdl_add_warning_options(shadercross WARNING_AS_ERROR ${SDLGPUSHADERCROSS_WERROR})
+	sdl_target_link_options_no_undefined(shadercross)
 
 	if(SDLGPUSHADERCROSS_CLI_STATIC)
 		target_link_libraries(shadercross PRIVATE SDL3_gpu_shadercross::SDL3_gpu_shadercross-static)
@@ -193,6 +252,19 @@ if(SDLGPUSHADERCROSS_INSTALL)
 			COMPONENT devel
 		)
 	endif()
+	if(install_extra_targets)
+		install(TARGETS ${install_extra_targets} EXPORT SDL3_gpu_shadercross-vendored
+			ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT devel
+			RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT library
+			LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT library
+		)
+		install(EXPORT SDL3_gpu_shadercross-vendored
+			FILE SDL3_gpu_shadercross-vendored-targets.cmake
+			NAMESPACE SDL3_gpu_shadercross::vendored::
+			DESTINATION "${SDLGPUSHADERCROSS_INSTALL_CMAKEDIR}"
+			COMPONENT devel
+		)
+	endif()
 	install(
 		FILES "${CMAKE_CURRENT_SOURCE_DIR}/include/SDL3_gpu_shadercross/SDL_gpu_shadercross.h"
 		DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/SDL3_gpu_shadercross" COMPONENT DEVEL
@@ -220,7 +292,13 @@ if(SDLGPUSHADERCROSS_INSTALL)
 	file(RELATIVE_PATH SDL_PATH_PREFIX_RELATIVE_TO_PKGCONFIG "${CMAKE_INSTALL_PREFIX}/${SDLGPUSHADERCROSS_PKGCONFIG_INSTALLDIR}" "${CMAKE_INSTALL_PREFIX}")
 	string(REGEX REPLACE "[/]+$" "" SDL_PATH_PREFIX_RELATIVE_TO_PKGCONFIG "${SDL_PATH_PREFIX_RELATIVE_TO_PKGCONFIG}")
 	set(SDL_PKGCONFIG_PREFIX "\${pcfiledir}/${SDL_PATH_PREFIX_RELATIVE_TO_PKGCONFIG}")
-	set(PC_REQUIRED "")
+	if(NOT SDLGPUSHADERCROSS_VENDORED)
+		if(SDLGPUSHADERCROSS_LINK_STATIC)
+			set(PC_REQUIRES "spirv-cross-c")
+		else()
+			set(PC_REQUIRES "spirv-cross-c-shared")
+		endif()
+	endif()
 	set(PC_LIBS "")
 	configure_file(cmake/sdl3-gpu-shadercross.pc.in sdl3-gpu-shadercross.pc @ONLY)
 
@@ -305,21 +383,6 @@ if(SDLGPUSHADERCROSS_INSTALL_DEPS)
 		install(FILES "${SOURCE_DIR}/LICENSE-LLVM.txt" "${SOURCE_DIR}/LICENSE-MS.txt" "${SOURCE_DIR}/LICENSE-MIT.txt" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/licenses/dxc")
 	endif()
 
-	# Download, configure, build and install spirv-cross
-	ExternalProject_Add(spirv_cross
-		GIT_REPOSITORY 	"https://github.com/KhronosGroup/SPIRV-Cross.git"
-		GIT_TAG 		"main"
-		CMAKE_ARGS 		"-DCMAKE_BUILD_TYPE=Release" "-DSPIRV_CROSS_SHARED=ON" "-DSPIRV_CROSS_STATIC=OFF" "-DSPIRV_CROSS_CLI=OFF" "-DSPIRV_CROSS_ENABLE_TESTS=OFF" "-DCMAKE_INSTALL_LIBDIR=lib" "-DCMAKE_INSTALL_BINDIR=bin" "-DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>" "-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}" "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}"
-		BUILD_COMMAND 	"${CMAKE_COMMAND}" "--build" "<BINARY_DIR>" --config "Release"
-		INSTALL_COMMAND "${CMAKE_COMMAND}" "--install" "<BINARY_DIR>" --config "Release"
-	)
-	ExternalProject_Get_property(spirv_cross INSTALL_DIR)
-	ExternalProject_Get_property(spirv_cross SOURCE_DIR)
-	install(FILES "${SOURCE_DIR}/LICENSE" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/licenses/spirv-cross")
-	install(DIRECTORY "${INSTALL_DIR}/lib/" DESTINATION "${CMAKE_INSTALL_LIBDIR}" FILES_MATCHING PATTERN "*.so*" PERMISSIONS ${chmod_0755})
-	install(DIRECTORY "${INSTALL_DIR}/lib/" DESTINATION "${CMAKE_INSTALL_LIBDIR}" FILES_MATCHING PATTERN "*.dylib*" PERMISSIONS ${chmod_0755})
-	install(DIRECTORY "${INSTALL_DIR}/bin/" DESTINATION "${CMAKE_INSTALL_BINDIR}" FILES_MATCHING PATTERN "*.dll" PERMISSIONS ${chmod_0755})
-
 	if(NOT WIN32)
 		ExternalProject_Add(spirv_headers
 			GIT_REPOSITORY 		"https://github.com/KhronosGroup/SPIRV-Headers.git"
diff --git a/cmake/SDL3_gpu_shadercrossConfig.cmake.in b/cmake/SDL3_gpu_shadercrossConfig.cmake.in
index de46cb4..9604323 100644
--- a/cmake/SDL3_gpu_shadercrossConfig.cmake.in
+++ b/cmake/SDL3_gpu_shadercrossConfig.cmake.in
@@ -3,22 +3,26 @@
 include(FeatureSummary)
 set_package_properties(SDL3_gpu_shadercross PROPERTIES
     URL "https://github.com/libsdl-org/SDL_gpu_shadercross/"
-    DESCRIPTION "Support support SPIR-V and HLSL on various backends"
+    DESCRIPTION "Support SPIR-V and HLSL on various backends"
 )
 
 set(SDL3_gpu_shadercross_FOUND ON)
 
 set(SDL3_gpu_SHADERCROSS_REQUIRED_VERSION  @SDL_REQUIRED_VERSION@)
 
+if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/SDL3_gpu_shadercross-vendored-targets.cmake")
+    include("${CMAKE_CURRENT_LIST_DIR}/SDL3_gpu_shadercross-vendored-targets.cmake")
+endif()
+
 set(SDL3_gpu_shadercross_SDL3_gpu_shadercross-shared_FOUND FALSE)
 if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/SDL3_gpu_shadercross-shared-targets.cmake")
-    include("${CMAKE_CURRENT_LIST_DIR}/SDL3_gpu_shadercross-shared.cmake")
+    include("${CMAKE_CURRENT_LIST_DIR}/SDL3_gpu_shadercross-shared-targets.cmake")
     set(SDL3_gpu_shadercross_SDL3_gpu_shadercross-shared_FOUND TRUE)
 endif()
 
 set(SDL3_gpu_shadercross_SDL3_gpu_shadercross-static FALSE)
 if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/SDL3_gpu_shadercross-static-targets.cmake")
-    include("${CMAKE_CURRENT_LIST_DIR}/SDL3_gpu_shadercross-static.cmake")
+    include("${CMAKE_CURRENT_LIST_DIR}/SDL3_gpu_shadercross-static-targets.cmake")
     set(SDL3_gpu_shadercross_SDL3_gpu_shadercross-static_FOUND TRUE)
 endif()
 
diff --git a/cmake/sdl3-gpu-shadercross.pc.in b/cmake/sdl3-gpu-shadercross.pc.in
index 422d974..a61fbf0 100644
--- a/cmake/sdl3-gpu-shadercross.pc.in
+++ b/cmake/sdl3-gpu-shadercross.pc.in
@@ -4,10 +4,10 @@ libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
 includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
 
 Name: @PROJECT_NAME@
-Description: image loading library for Simple DirectMedia Layer
+Description: Support SPIR-V and HLSL on various backends
 Version: @PROJECT_VERSION@
 Requires: sdl3 >= @SDL_REQUIRED_VERSION@
-Libs: -L${libdir} -lSDL_gpu_shadercross
+Libs: -L${libdir} -lSDL3_gpu_shadercross
 Requires.private: @PC_REQUIRES@
 Libs.private: @PC_LIBS@
 Cflags: -I${includedir}
diff --git a/cmake/test/CMakeLists.txt b/cmake/test/CMakeLists.txt
new file mode 100644
index 0000000..893138d
--- /dev/null
+++ b/cmake/test/CMakeLists.txt
@@ -0,0 +1,44 @@
+# This cmake build script is meant for verifying the various CMake configuration script.
+
+cmake_minimum_required(VERSION 3.12)
+project(sdl_test LANGUAGES C)
+
+cmake_policy(SET CMP0074 NEW)
+
+# Override CMAKE_FIND_ROOT_PATH_MODE to allow search for SDL3 outside of sysroot
+set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE NEVER)
+
+include(FeatureSummary)
+
+option(TEST_SHARED "Test linking to shared SDL3_gpu_shadercross library" ON)
+add_feature_info("TEST_SHARED" TEST_SHARED "Test linking with shared library")
+
+option(TEST_STATIC "Test linking to static SDL3_gpu_shadercross library" ON)
+add_feature_info("TEST_STATIC" TEST_STATIC "Test linking with static library")
+
+if(ANDROID)
+    macro(add_executable NAME)
+        set(args ${ARGN})
+        list(REMOVE_ITEM args WIN32)
+        add_library(${NAME} SHARED ${args})
+        unset(args)
+    endmacro()
+endif()
+
+if(TEST_SHARED)
+    find_package(SDL3 REQUIRED CONFIG COMPONENTS SDL3)
+    find_package(SDL3_gpu_shadercross REQUIRED CONFIG)
+    add_executable(main_shared main.c)
+    target_link_libraries(main_shared PRIVATE SDL3_gpu_shadercross::SDL3_gpu_shadercross-shared SDL3::SDL3)
+endif()
+
+if(TEST_STATIC)
+    find_package(SDL3 REQUIRED CONFIG COMPONENTS SDL3)
+    # some static vendored libraries use c++ (enable CXX after `find_package` might show a warning)
+    enable_language(CXX)
+    find_package(SDL3_gpu_shadercross REQUIRED CONFIG)
+    add_executable(main_static main.c)
+    target_link_libraries(main_static PRIVATE SDL3_gpu_shadercross::SDL3_gpu_shadercross-static SDL3::SDL3)
+endif()
+
+feature_summary(WHAT ALL)
diff --git a/cmake/test/main.c b/cmake/test/main.c
new file mode 100644
index 0000000..f8c34e3
--- /dev/null
+++ b/cmake/test/main.c
@@ -0,0 +1,16 @@
+#include <SDL3/SDL.h>
+#include <SDL3_gpu_shadercross/SDL_gpu_shadercross.h>
+
+int main(int argc, char *argv[]) {
+    if (!SDL_Init(0)) {
+        SDL_Log("SDL_Init failed (%s)", SDL_GetError());
+        return 1;
+    }
+    if (!SDL_ShaderCross_Init()) {
+        SDL_Log("SDL_ShaderCross_Init failed (%s)", SDL_GetError());
+        return 1;
+    }
+    SDL_ShaderCross_Quit();
+    SDL_Quit();
+    return 0;
+}
diff --git a/external/Get-GitModules.ps1 b/external/Get-GitModules.ps1
new file mode 100644
index 0000000..7238700
--- /dev/null
+++ b/external/Get-GitModules.ps1
@@ -0,0 +1,36 @@
+<#
+  .SYNOPSIS
+  Downloads the Git modules specified in ../.gitmodules
+
+  .DESCRIPTION
+  Parses and downloads the Github repositories specified in the .gitmodules file
+
+  .EXAMPLE
+  PS> .\Get-GitModules.ps1
+  < Downloads and parses the repositories in the .gitmodules file. >
+#>
+
+#------- Variables -------------------------------------------------------------
+[String] $PathRegex   = "path\s*=\s*(?<path>.*)"
+[String] $URLRegex    = "url\s*=\s*(?<url>.*)" 
+[String] $BranchRegex = "branch\s*=\s*(?<Branch>.*)"
+
+#------- Script ----------------------------------------------------------------
+foreach ($Line in Get-Content $PSScriptRoot\..\.gitmodules) {
+    if ($Line -match $PathRegex) {
+        $Match  = Select-String -InputObject $Line -Pattern $PathRegex
+        $Path   = $Match.Matches[0].Groups[1].Value
+    }
+    elseif ($Line -match $URLRegex) {
+        $Match  = Select-String -InputObject $Line -Pattern $URLRegex
+        $URL    = $Match.Matches[0].Groups[1].Value
+    }
+    elseif ($Line -match $BranchRegex) {
+        $Match  = Select-String -InputObject $Line -Pattern $BranchRegex
+        $Branch = $Match.Matches[0].Groups[1].Value
+        
+        Write-Host "git clone --filter=blob:none $URL $Path -b $Branch --recursive" `
+            -ForegroundColor Blue
+        git clone --filter=blob:none $URL $PSScriptRoot/../$Path -b $Branch --recursive
+    }
+}
diff --git a/external/SPIRV-Cross b/external/SPIRV-Cross
new file mode 160000
index 0000000..d1d4adb
--- /dev/null
+++ b/external/SPIRV-Cross
@@ -0,0 +1 @@
+Subproject commit d1d4adbefd411fc4721a2fece15a7f4aaa3dcdfa
diff --git a/external/download.sh b/external/download.sh
new file mode 100755
index 0000000..b78a4a5
--- /dev/null
+++ b/external/download.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+set -e
+
+ARGUMENTS="$*"
+
+cd $(dirname "$0")/..
+cat .gitmodules | \
+while true; do
+    read module || break
+    read line; set -- $line
+    path=$3
+    read line; set -- $line
+    url=$3
+    read line; set -- $line
+    branch=$3
+    git clone --filter=blob:none $url $path -b $branch --recursive $ARGUMENTS
+done
diff --git a/include/SDL3_gpu_shadercross/SDL_gpu_shadercross.h b/include/SDL3_gpu_shadercross/SDL_gpu_shadercross.h
index dbba139..e4b873a 100644
--- a/include/SDL3_gpu_shadercross/SDL_gpu_shadercross.h
+++ b/include/SDL3_gpu_shadercross/SDL_gpu_shadercross.h
@@ -55,6 +55,7 @@ typedef enum SDL_ShaderCross_ShaderStage
 
 typedef enum SDL_ShaderCross_ShaderModel
 {
+    SDL_SHADERCROSS_SHADERMODEL_INVALID,
     SDL_SHADERCROSS_SHADERMODEL_5_0,
     SDL_SHADERCROSS_SHADERMODEL_6_0
 } SDL_ShaderCross_ShaderModel;
diff --git a/src/SDL_gpu_shadercross.c b/src/SDL_gpu_shadercross.c
index bd41126..170356f 100644
--- a/src/SDL_gpu_shadercross.c
+++ b/src/SDL_gpu_shadercross.c
@@ -762,80 +762,10 @@ SDL_GPUComputePipeline *SDL_ShaderCross_CompileComputePipelineFromHLSL(
 #error SDL_GPU_SHADERCROSS_HLSL must be enabled for SDL_GPU_SHADERCROSS_SPIRVCROSS!
 #endif /* !SDL_GPU_SHADERCROSS_HLSL */
 
-#include "spirv_cross_c.h"
-
-#ifndef SDL_GPU_SHADERCROSS_STATIC
-
-#ifndef SDL_GPU_SPIRV_CROSS_DLL
-#if defined(_WIN32)
-#define SDL_GPU_SPIRV_CROSS_DLL "spirv-cross-c-shared.dll"
-#define SDL_GPU_SPIRV_CROSS_DLL_2 "libspirv-cross-c-shared.dll"
-#elif defined(__APPLE__)
-#define SDL_GPU_SPIRV_CROSS_DLL "libspirv-cross-c-shared.0.dylib"
-#else
-#define SDL_GPU_SPIRV_CROSS_DLL "libspirv-cross-c-shared.so.0"
-#endif
-#endif /* SDL_GPU_SPIRV_CROSS_DLL */
-
-static SDL_SharedObject *spirvcross_dll = NULL;
-
-typedef spvc_result (*pfn_spvc_context_create)(spvc_context *context);
-typedef void (*pfn_spvc_context_destroy)(spvc_context);
-typedef spvc_result (*pfn_spvc_context_parse_spirv)(spvc_context, const SpvId *, size_t, spvc_parsed_ir *);
-typedef spvc_result (*pfn_spvc_context_create_compiler)(spvc_context, spvc_backend, spvc_parsed_ir, spvc_capture_mode, spvc_compiler *);
-typedef spvc_result (*pfn_spvc_compiler_create_compiler_options)(spvc_compiler, spvc_compiler_options *);
-typedef spvc_result (*pfn_spvc_compiler_options_set_uint)(spvc_compiler_options, spvc_compiler_option, unsigned);
-typedef spvc_result (*pfn_spvc_compiler_create_shader_resources)(spvc_compiler, spvc_resources *);
-typedef spvc_result (*pfn_spvc_compiler_msl_add_resource_binding)(spvc_compiler, const spvc_msl_resource_binding *);
-typedef spvc_result (*pfn_spvc_compiler_has_decoration)(spvc_compiler, SpvId, SpvDecoration);
-typedef spvc_result (*pfn_spvc_compiler_get_decoration)(spvc_compiler, SpvId, SpvDecoration);
-typedef spvc_result (*pfn_spvc_compiler_install_compiler_options)(spvc_compiler, spvc_compiler_options);
-typedef spvc_result (*pfn_spvc_compiler_compile)(spvc_compiler, const char **);
-typedef spvc_result (*pfn_spvc_resources_get_resource_list_for_type)(spvc_resources, spvc_resource_type, const spvc_reflected_resource **, size_t *);
-typedef const char *(*pfn_spvc_context_get_last_error_string)(spvc_context);
-typedef SpvExecutionModel (*pfn_spvc_compiler_get_execution_model)(spvc_compiler compiler);
-typedef const char *(*pfn_spvc_compiler_get_cleansed_entry_point_name)(spvc_compiler compiler, const char *name, SpvExecutionModel model);
-
-static pfn_spvc_context_create SDL_spvc_context_create = NULL;
-static pfn_spvc_context_destroy SDL_spvc_context_destroy = NULL;
-static pfn_spvc_context_parse_spirv SDL_spvc_context_parse_spirv = NULL;
-static pfn_spvc_context_create_compiler SDL_spvc_context_create_compiler = NULL;
-static pfn_spvc_compiler_create_compiler_options SDL_spvc_compiler_create_compiler_options = NULL;
-static pfn_spvc_compiler_options_set_uint SDL_spvc_compiler_options_set_uint = NULL;
-static pfn_spvc_compiler_create_shader_resources SDL_spvc_compiler_create_shader_resources = NULL;
-static pfn_spvc_compiler_msl_add_resource_binding SDL_spvc_compiler_msl_add_resource_binding = NULL;
-static pfn_spvc_compiler_has_decoration SDL_spvc_compiler_has_decoration = NULL;
-static pfn_spvc_compiler_get_decoration SDL_spvc_compiler_get_decoration = NULL;
-static pfn_spvc_compiler_install_compiler_options SDL_spvc_compiler_install_compiler_options = NULL;
-static pfn_spvc_compiler_compile SDL_spvc_compiler_compile = NULL;
-static pfn_spvc_resources_get_resource_list_for_type SDL_spvc_resources_get_resource_list_for_type = NULL;
-static pfn_spvc_context_get_last_error_string SDL_spvc_context_get_last_error_string = NULL;
-static pfn_spvc_compiler_get_execution_model SDL_spvc_compiler_get_execution_model = NULL;
-static pfn_spvc_compiler_get_cleansed_entry_point_name SDL_spvc_compiler_get_cleansed_entry_point_name = NULL;
-
-#else /* SDL_GPU_SHADERCROSS_STATIC */
-
-#define SDL_spvc_context_create                         spvc_context_create
-#define SDL_spvc_context_destroy                        spvc_context_destroy
-#define SDL_spvc_context_parse_spirv                    spvc_context_parse_spirv
-#define SDL_spvc_context_create_compiler                spvc_context_create_compiler
-#define SDL_spvc_compiler_create_compiler_options       spvc_compiler_create_compiler_options
-#define SDL_spvc_compiler_options_set_uint              spvc_compiler_options_set_uint
-#define SDL_spvc_compiler_create_shader_resources       spvc_compiler_create_shader_resources
-#define SDL_spvc_compiler_msl_add_resource_binding      spvc_compiler_msl_add_resource_binding
-#define SDL_spvc_compiler_has_decoration                spvc_compiler_has_decoration
-#define SDL_spvc_compiler_get_decoration                spvc_compiler_get_decoration
-#define SDL_spvc_compiler_install_compiler_options      spvc_compiler_install_compiler_options
-#define SDL_spvc_compiler_compile                       spvc_compiler_compile
-#define SDL_spvc_resources_get_resource_list_for_type   spvc_resources_get_resource_list_for_type
-#define SDL_spvc_context_get_last_error_string          spvc_context_get_last_error_string
-#define SDL_spvc_compiler_get_execution_model           spvc_compiler_get_execution_model
-#define SDL_spvc_compiler_get_cleansed_entry_point_name spvc_compiler_get_cleansed_entry_point_name
-
-#endif /* SDL_GPU_SHADERCROSS_STATIC */
+#include <spirv_cross_c.h>
 
 #define SPVC_ERROR(func) \
-    SDL_SetError(#func " failed: %s", SDL_spvc_context_get_last_error_string(context))
+    SDL_SetError(#func " failed: %s", spvc_context_get_last_error_string(context))
 
 typedef struct SPIRVTranspileContext {
     spvc_context context;
@@ -846,7 +776,7 @@ typedef struct SPIRVTranspileContext {
 static void SDL_ShaderCross_INTERNAL_DestroyTranspileContext(
     SPIRVTranspileContext *context)
 {
-    SDL_spvc_context_destroy(context->context);
+    spvc_context_destroy(context->context);
     SDL_free(context);
 }
 
@@ -868,40 +798,40 @@ static SPIRVTranspileContext *SDL_ShaderCross_INTERNAL_TranspileFromSPIRV(
     const char *cleansed_entrypoint;
 
     /* Create the SPIRV-Cross context */
-    result = SDL_spvc_context_create(&context);
+    result = spvc_context_create(&context);
     if (result < 0) {
         SDL_SetError("spvc_context_create failed: %X", result);
         return NULL;
     }
 
     /* Parse the SPIR-V into IR */
-    result = SDL_spvc_context_parse_spirv(context, (const SpvId *)code, codeSize / sizeof(SpvId), &ir);
+    result = spvc_context_parse_spirv(context, (const SpvId *)code, codeSize / sizeof(SpvId), &ir);
     if (result < 0) {
         SPVC_ERROR(spvc_context_parse_spirv);
-        SDL_spvc_context_destroy(context);
+        spvc_context_destroy(context);
         return NULL;
     }
 
     /* Create the cross-compiler */
-    result = SDL_spvc_context_create_compiler(context, backend, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler);
+    result = spvc_context_create_compiler(context, backend, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler);
     if (result < 0) {
         SPVC_ERROR(spvc_context_create_compiler);
-        SDL_spvc_context_destroy(context);
+        spvc_context_destroy(context);
         return NULL;
     }
 
     /* Set up the cross-compiler options */
-    result = SDL_spvc_compiler_create_compiler_options(compiler, &options);
+    result = spvc_compiler_create_compiler_options(compiler, &options);
     if (result < 0) {
         SPVC_ERROR(spvc_compiler_create_compiler_options);
-        SDL_spvc_context_destroy(context);
+        spvc_context_destroy(context);
         return NULL;
     }
 
     if (backend == SPVC_BACKEND_HLSL) {
-        SDL_spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_HLSL_SHADER_MODEL, shadermodel);
-        SDL_spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_HLSL_NONWRITABLE_UAV_TEXTURE_AS_SRV, 1);
-        SDL_spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_HLSL_FLATTEN_MATRIX_VERTEX_INPUT_SEMANTICS, 1);
+        spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_HLSL_SHADER_MODEL, shadermodel);
+        spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_HLSL_NONWRITABLE_UAV_TEXTURE_AS_SRV, 1);
+        spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_HLSL_FLATTEN_MATRIX_VERTEX_INPUT_SEMANTICS, 1);
     }
 
     SpvExecutionModel executionModel;
@@ -922,171 +852,171 @@ static SPIRVTranspileContext *SDL_ShaderCross_INTERNAL_TranspileFromSPIRV(
         size_t num_storage_buffers;
         size_t num_uniform_buffers;
 
-        result = SDL_spvc_compiler_create_shader_resources(compiler, &resources);
+        result = spvc_compiler_create_shader_resources(compiler, &resources);
         if (result < 0) {
             SPVC_ERROR(spvc_compiler_create_shader_resources);
-            SDL_spvc_context_destroy(context);
+            spvc_context_destroy(context);
             return NULL;
         }
 
         // Combined texture-samplers
-        result = SDL_spvc_resources_get_resource_list_for_type(
+        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);
-            SDL_spvc_context_destroy(context);
+            spvc_context_destroy(context);
             return NULL;
         }
 
         spvc_msl_resource_binding binding;
         for (size_t i = 0; i < num_texture_samplers; i += 1) {
-            if (!SDL_spvc_compiler_has_decoration(compiler, reflected_resources[i].id, SpvDecorationDescriptorSet) || !SDL_spvc_compiler_has_decoration(compiler, reflected_resources[i].id, SpvDecorationBinding)) {
+            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!");
-                SDL_spvc_context_destroy(context);
+                spvc_context_destroy(context);
                 return NULL;
             }
 
-            unsigned int descriptor_set_index = SDL_spvc_compiler_get_decoration(compiler, reflected_resources[i].id, SpvDecorationDescriptorSet);
+            unsigned int descriptor_set_index = spvc_compiler_get_decoration(compiler, reflected_resources[i].id, SpvDecorationDescriptorSet);
             if (!(descriptor_set_index == 0 || descriptor_set_index == 2)) {
                 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", "Descriptor set index for graphics texture-sampler must be 0 or 2!");
-                SDL_spvc_context_destroy(context);
+                spvc_context_destroy(context);
                 return NULL;
             }
 
-            unsigned int binding_index = SDL_spvc_compiler_get_decoration(compiler, reflected_resources[i].id, SpvDecorationBinding);
+            unsigned int binding_index = spvc_compiler_get_decoration(compiler, reflected_resources[i].id, SpvDecorationBinding);
 
             binding.stage = executionModel;
             binding.desc_set = descriptor_set_index;
             binding.binding = binding_index;
             binding.msl_texture = binding_index;
             binding.msl_sampler = binding_index;
-            result = SDL_spvc_compiler_msl_add_resource_binding(compiler, &binding);
+            result = spvc_co

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