CrossTL
CrossTL translates GPU shaders and compute kernels between languages. Every translation goes through CrossGL, a small intermediate language: a frontend imports GLSL, HLSL, Metal or another source language into CrossGL, and a code generator writes CrossGL out as GLSL, HLSL, Metal, SPIR-V, WGSL or another target. You can also write CrossGL directly and generate every target from one source file.
!CrossTL coverage per language and direction
CI measures this chart on every push to main and nightly. Every feature in a pinned upstream feature list gets a
generated probe, CrossTL translates it, and the language's reference compiler or validator checks the result. The
score is supported / (supported + refused + wrong). COVERAGE.md
lists every gap behind each bar.
Install
pip install crosstl
CrossTL needs Python 3.11 or later. Validating the generated code needs the target's own compiler or validator (glslang, DXC, spirv-tools, naga, the Metal compiler, nvcc, hipcc, slangc, rust-gpu or the Mojo compiler), which CrossTL does not install.
Quickstart
Save this compute kernel as saxpy.cgl:
shader Saxpy {
compute {
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
buffer float* x;
buffer float* y;
uniform float a;
void main() {
uint i = gl_GlobalInvocationID.x;
y[i] = a * x[i] + y[i];
}
}
}
Then translate it from Python, where the source language comes from the file extension:
import crosstl
metal = crosstl.translate("saxpy.cgl", backend="metal")
crosstl.translate("saxpy.cgl", backend="wgsl", save_shader="saxpy.wgsl")
A native shader is imported into CrossGL first, then generated for the target.
glsl = crosstl.translate("blur.hlsl", backend="opengl")
crossgl = crosstl.translate("blur.hlsl", backend="cgl")
or from the command line:
crosstl translate saxpy.cgl --backend directx --output saxpy.hlsl
crosstl translate saxpy.cgl --backend vulkan --output -
python -m crosstl is the same command. The examples/ directory has larger CrossGL programs.
Languages
| Language | backend | Output | Input |
|---|---|---|---|
| GLSL | opengl | measured | measured |
| HLSL | directx | measured | measured |
| Metal | metal | measured | measured |
| SPIR-V assembly | vulkan | measured | measured |
| WGSL | wgsl | measured | not offered |
| GLSL ES 3.00 (WebGL 2) | webgl | measured | not offered |
| CUDA | cuda | measured | measured |
| HIP | hip | measured | measured |
| Slang | slang | measured | measured |
| Rust (rust-gpu) | rust | measured | measured |
| Mojo | mojo | measured | measured |
| OpenCL C | | not offered | available |
A measured direction has a row in COVERAGE.md. An
available direction is implemented and unit-tested but not yet measured. The language names glsl, hlsl, msl
and spirv work as backend names too, and cgl writes the CrossGL a native source imports to. CrossGL has its own
coverage row, CrossGL IR, which measures how many of the features in all the measured languages CrossGL can express.
CrossTL is meant to fail closed: a construct it cannot translate raises a diagnostic that names the construct and the target, instead of emitting a placeholder. Output that the reference tool rejects counts as wrong in the coverage report, and the Coverage workflow files it as a bug.
How it works
.hlsl .metal .glsl .cu ... .glsl .hlsl .metal .spvasm .wgsl ...
native source --> frontend --> CrossGL source --> CrossGL parser --> code generator --> target source
(parse and (AST and types) (one per target)
import)
Each source language has a frontend in crosstl/backend/ that parses it and writes CrossGL. The CrossGL
parser in crosstl/translator/ builds the typed AST that every code generator in crosstl/translator/codegen/
reads, so a feature added to CrossGL is available to every target that can express it.
Porting a repository
For a project with many shader or kernel sources, scan it before writing anything, then translate it and check the report:
python -m crosstl scan path/to/repo --target metal --output scan-report.json
python -m crosstl translate-project path/to/repo --target metal --output-dir crosstl-out \
--report crosstl-out/report.json
python -m crosstl validate-project crosstl-out/report.json --format text
python -m crosstl inspect-report crosstl-out/report.json --format text
A relative --output-dir is resolved against the project, so the translated files land in
path/to/repo/crosstl-out/. The report records each translated file, its diagnostics and provenance, the
reference toolchain checks that ran, and the host-side changes that remain manual. The project porting guide covers
the options, and demos/ has pinned open-source ports with checked artifacts.
Documentation
The user documentation is at docs.crossgl.net.
The Sphinx sources, including the architecture notes and decision records, are in docs/source.
Contributing
Read CONTRIBUTING.md for setup, the test tiers and the pull request flow. Issues labelled good first issue are a good place to start, and questions belong in Discussions or on Discord. Report security problems as described in SECURITY.md.
Citation
If you use CrossTL in your work, cite it with the metadata in CITATION.cff; GitHub's "Cite this repository" button produces BibTeX and APA from it. Each release is archived on Zenodo under 10.5281/zenodo.15826974.
License
CrossTL is licensed under the Apache License 2.0.