Java bindings for the Slang shading-language compiler.
Compile Slang source to SPIR-V, DXIL, HLSL, GLSL, WGSL, or Metal from the JVM, inspect the
result with Slang's full reflection API, and resolve imports through your own code — pack
files, classpath resources, in-memory maps. Java on the FFM API (java.lang.foreign):
no JNI, no native glue of its own — it binds the official Khronos-signed Slang binaries
directly. Windows, macOS, and Linux, on x86_64 and aarch64. Requires JDK 25+.
| Artifact | Purpose |
|---|---|
io.github.refux:slang-java:0.0.11 |
The library: compiler API, reflection, Java file systems |
io.github.refux:slang-java-natives:0.0.11:<os>-<arch> |
The official Slang binaries, as one classifier per platform: windows, linux, or macos × x86_64 or aarch64 |
The library version tracks slang-java's own API, not Slang's. To see which Slang release a jar binds, read its manifest — no resolving, no running:
unzip -p slang-java-0.0.11.jar META-INF/MANIFEST.MFSlang-Version there is a floor, not a lockstep: it names the release the bindings were
generated against, and any newer Slang works, because Slang's public ABI is append-only. The same
value is available at runtime as SlangLibrary.PINNED_SLANG_VERSION, alongside
GlobalSession.buildTagString() for the library actually loaded.
Serialized modules (Module.serialize()) are versioned separately and more narrowly: each Slang
release reads only a small range of module versions, so treat them as a cache tied to the release
that wrote them. Session.loadModuleFromIr throws a SlangCompileException for anything the
loaded Slang cannot read, and Session.moduleInfo tells you up front which version a blob is.
Add the library and the natives classifier for your machine — compute it from the host so you never hardcode a platform:
// build.gradle.kts
val slangNatives = "${
when {
org.gradle.internal.os.OperatingSystem.current().isWindows -> "windows"
org.gradle.internal.os.OperatingSystem.current().isMacOsX -> "macos"
else -> "linux"
}
}-${if (System.getProperty("os.arch") in listOf("aarch64", "arm64")) "aarch64" else "x86_64"}"
dependencies {
implementation("io.github.refux:slang-java:0.0.11")
runtimeOnly("io.github.refux:slang-java-natives:0.0.11:$slangNatives")
}
tasks.withType<JavaExec> {
jvmArgs("--enable-native-access=ALL-UNNAMED") // FFM restricted methods, JEP 472
}Shipping one build that runs on every OS? List the classifiers instead — they sit on the classpath together and the loader picks the host's at runtime (LWJGL-style):
dependencies {
implementation("io.github.refux:slang-java:0.0.11")
runtimeOnly("io.github.refux:slang-java-natives:0.0.11:macos-aarch64")
runtimeOnly("io.github.refux:slang-java-natives:0.0.11:windows-x86_64")
runtimeOnly("io.github.refux:slang-java-natives:0.0.11:linux-x86_64")
}Then compile a shader and reflect on it:
import io.github.refux.slang.*;
import io.github.refux.slang.Module; // disambiguates from java.lang.Module
try (GlobalSession global = Slang.createGlobalSession();
Session session = global.newSession()
.target(CompileTarget.SPIRV, t -> t.profile("spirv_1_5"))
.create()) {
Module module = session.loadModuleFromSource("hello", """
RWStructuredBuffer<float> result;
[shader("compute")] [numthreads(8,8,1)]
void main(uint3 tid : SV_DispatchThreadID)
{
result[tid.x] = float(tid.x) * 2.0;
}
""");
try (ComponentType linked = session.composite(module, module.entryPoint("main")).link()) {
byte[] spirv = linked.entryPointCode(0, 0); // hand to Vulkan, cache to disk, ...
ShaderReflection reflection = linked.layout(0);
for (var parameter : reflection.parameters()) {
System.out.println(parameter.name() + " : " + parameter.category());
}
}
}Compile errors throw SlangCompileException carrying the compiler's diagnostics text; warnings
go to an optional onDiagnostics consumer on the session builder. Imports can be served from
Java instead of the OS file system:
Session session = global.newSession()
.target(CompileTarget.SPIRV)
.fileSystem(SlangFileSystem.ofMap(Map.of(
"common.slang", "public static const float kScale = 2.0;")))
.create();Matrices follow SessionDesc's row-major default, which is not what the slangc command
line uses — compile the same shader offline with slangc and at run time through this library
and every matrix comes out transposed relative to the other build, with nothing to diagnose it.
matrixLayout(MatrixLayout.COLUMN_MAJOR) on the session builder matches slangc.
Everything is AutoCloseable with try-with-resources as the idiom; anything left unclosed is
released by a Cleaner when unreachable, and -Dio.github.refux.slang.debug=true traces leaks
to their allocation site and asserts session thread confinement.
To use a different Slang build than the bundled one (say, a local checkout), set
-Dio.github.refux.slang.libraryPath=<dir> or SLANG_JAVA_LIBRARY_PATH to any directory
holding the Slang libraries — it takes precedence over the natives artifacts.
- The low-level layer is generated from slang.h itself: COM vtable dispatch, structs with clang-verified offsets, all enums, and the complete reflection C ABI — validated byte-identical across all six OS/architecture ABIs, with an append-only lockfile guarding against upstream ABI drift.
- Java-implemented Slang interfaces (file systems, blobs) are real refcounted COM objects backed by FFM upcall stubs.
- The native binaries are the unmodified official Slang releases — nothing is rebuilt or patched.
Architecture, decisions, and status: DESIGN.md. Contributor commands: CLAUDE.md.
Licensed under the Apache License 2.0.