CrossTL: a shader translator that goes through a CrossGL intermediate representation
Universal Programming Language & Translator
At a glance
- What is it?
- A Python toolchain that parses native HLSL, Metal and GLSL into a CrossGL IR and then regenerates shader code for a dozen targets, from Vulkan SPIR-V to CUDA, HIP, Rust, Mojo and WGSL. The interesting part is the bidirectional support for the three cornerstone graphics APIs, and the honest gaps in WebGL and WGSL input.
- Who is it for?
- CrossTL suits graphics engineers who maintain shaders across several APIs and want one editable source of truth instead of several near-duplicate files, and who are prepared to treat the generated code as source that needs a diff review.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What CrossTL is, and what CrossGL is for
CrossTL is a shader and compute program translator built around CrossGL, an intermediate representation language that bridges graphics APIs, GPU compute platforms and systems-language targets. The repository description calls the project a universal programming language and translator, and the primary language is Python.
The intermediate representation is the design decision, and everything else follows from it. Instead of writing pairwise translators from HLSL to Metal, from Metal to GLSL and from GLSL to SPIR-V, you write one frontend per source language into CrossGL and one backend per target out of it. The README describes a five-stage pipeline:
1. Lexical Analysis, tokenization. 2. Syntax Analysis, AST generation. 3. Semantic Analysis, type checking and scope resolution. 4. IR Generation, conversion to the CrossGL intermediate representation. 5. Target Generation, backend-specific code generation.
The payoff is that adding a target becomes a backend rather than a matrix of converters, and the cost is a fidelity question that the IR has to answer for every feature of every source language. That question is where the interesting engineering in this project sits, and the README is refreshingly specific about it.
The project is Apache-2.0 licensed, is not archived, and the last push was on 2026-10-15 with v3.3.0 released on 2026-10-01, following v3.2.0 in August and v3.1.0 in July. The version in `setup.py` matches the release tag, and the console script is registered as `crosstl=crosstl._crosstl:main`.
Twelve targets, and two that only work in one direction
The list of translation targets is long: Metal, DirectX through HLSL, OpenGL through GLSL, WebGL through GLSL ES, WebGPU through WGSL, Vulkan through SPIRV, CUDA, HIP, Rust, Mojo, Slang, and CrossGL itself as the interchange format.
That list needs a caveat, and the README gives it. Native source import is available for the bidirectional backends listed in the support matrix, while WebGL and WGSL are currently target-only outputs. The inputs `.webgl.glsl`, `.wgsl` and `.wesl` are rejected until dedicated source frontends land.
So the matrix is not symmetric. You can generate WebGL or WGSL, but you cannot feed existing WebGL or WGSL back in. If your existing shader library is in GLSL ES or WGSL, the practical starting point is the OpenGL frontend, and the WGSL route is one-way only.
The same asymmetry appears in what happens after translation. For DirectX the target profile aliases `dx11`, `dx12`, `d3d11` and `d3d12` all resolve to the HLSL emitter for Direct3D deployment planning, and the README is explicit that final DXBC and DXIL packaging remains a toolchain step. CrossTL produces HLSL; it does not produce a compiled Direct3D binary.
The Rust, Mojo and Slang targets are similarly positioned as GPU-oriented output rather than as a general-purpose Rust compiler front end. Mojo appears in the repository topics alongside Metal, DirectX, OpenGL, Vulkan and graphics, which places the project in the graphics and compute world rather than in general language tooling.
What the DirectX, Metal and OpenGL backends actually cover
The README claims first-class, bidirectional support for the three cornerstone graphics APIs, and then lists what that means per backend. It is worth reading the lists, because they are the difference between a demo and a usable tool.
For DirectX and HLSL the pipeline coverage lists vertex, fragment and pixel, compute, geometry, hull and domain tessellation, mesh and task, and full ray-tracing stages. The resource model covers cbuffers, register and space bindings, UAV and RW textures and buffers, structured buffers, Interlocked atomics, wave operations, and texture and buffer dimension queries. Semantics map to the `SV_*` set and to user semantics, and are preserved through CrossGL attributes.
For Metal the stages are vertex, fragment, compute, mesh and object, and ray-tracing qualifiers, with argument buffers, the `[[buffer]]`, `[[texture]]` and `[[sampler]]` qualifiers, function constants, packed and simd types, indirect command buffers, and payload and hit attributes. Texture methods including sample, load, store, gather and compare, along with threadgroup builtins, are translated to CrossGL intrinsics.
For OpenGL and GLSL the stages are vertex, fragment and compute, with version inference that defaults to `#version 450 core` when the version is absent. It handles interface blocks, layouts and bindings, sampler and image operations, control flow, structs and arrays, discard, the `gl_*` builtins and preprocessor directives.
The round trips are registered in `translator.source_registry` and `translator.codegen.registry`, with the converters living in `crosstl.backend.DirectX`, `crosstl.backend.Metal` and `crosstl.backend.GLSL`.
How the project validates backend parity
The README explains the validation approach in two parts, and this is the section to read before trusting a translation.
First, targeted unit suites exercise the full feature surface for the three cornerstone backends, covering shader stages, bindings, intrinsics, control flow, resources and preprocessor handling. The quick check is a single command:
pytest tests/test_backend/test_directx tests/test_backend/test_metal tests/test_backend/test_GLSL -qThe README states the current count as 321 passing tests.
Second, and more importantly for a translator, the end-to-end check translates native HLSL, Metal or GLSL into CrossGL and then back out to HLSL, Metal, GLSL or Vulkan, to confirm that attributes, layouts and semantics survive the round trip. A translator that produces plausible output is not the same as one that preserves semantics, and the second check is the one that distinguishes the two.
The test dependencies are visible in `requirements.txt`: pytest, hypothesis at 6.82 or newer, numpy and jinja2, alongside gast. Hypothesis is a property-based testing library, and its presence in a shader translator's dependency list is a good sign, because round-trip properties are exactly the kind of thing that benefits from generated inputs rather than fixed examples. The `tests/` directory is a top level entry in the repository tree.
For validation of the output languages themselves, the requirements file lists optional formatting and validation tools in commented-out lines: `clang-format` for C-like languages such as HLSL and Metal, `spirv-tools` for SPIR-V validation and assembly, and `glslang` for GLSL validation. These are not installed by default, which means the pipeline does not verify its own SPIR-V or GLSL output unless you add them.
Installing the package and the platform-specific extras
The package is on PyPI as `crosstl` and is built with setuptools. The `pyproject.toml` pins the build requirements exactly, at setuptools 75.3.2 and wheel 0.45.1, which is a sign of a reproducible build.
The runtime requirements are small: `gast`, and `tomli` for Python versions below 3.11. The supported Python range is 3.8 to 3.14, with the `python_requires` setting of `>=3.8`, and the classifiers list every version in that range plus OS Independent and Python 3 only.
The one platform-specific dependency is declared as an extra rather than a hard requirement:
extras_require={
"directx-runtime": [
"compushady>=0.17.5,<0.18; platform_system=='Windows'",
],
}So a Windows user working with DirectX needs `compushady`, pinned to a narrow range, and only on Windows. That is a runtime library for Direct3D, and it is the point at which the tool stops being a pure translator and starts needing a graphics stack present on the machine.
The console script is `crosstl`, mapped to `crosstl._crosstl:main`, so the tool is invoked from a shell once installed.
The repository also carries `setup.cfg` alongside `setup.py`, a `MANIFEST.in` for packaging data, and a `deploy.sh` script, so the package data, such as the example and grammar files under `crosstl/`, has to be declared explicitly for an install to contain them.
The examples tree and the PBR demo
The `examples/` directory in the repository is organised by use case rather than by target language: `advanced/`, `compute/`, `cross_platform/`, `gpu_computing/`, `graphics/` and `output/`, plus a `test.py` at the top.
That layout is a reasonable answer to the question a translator always raises, which is what a correct translation actually looks like in each target. Comparing the `output/` directory against the `graphics/` source directory shows you the generated code side by side with the CrossGL that produced it, which is the fastest way to understand the fidelity of a backend.
The README's showcase is a PBR-style material path, with tangent-space normal mapping and a GGX normal distribution, and it points at a specific source file, `demos/readme/showcase_pbr.cgl`, used by both the README and a notebook, `demos/readme/demo.ipynb`. A `.cgl` file is the CrossGL interchange format, and the presence of a notebook alongside it means you can step through the translation rather than just reading the output.
Other top level entries support the same idea. There is a `support/` directory, a `tools/` directory, a `docs/` directory and a `demos/` directory, and a `further-work-backlog.json` file that records what is planned but not built, which is a more useful signal than an issue list because it is versioned with the code. A `CHANGELOG.md`, a `CITATION.cff` and a Zenodo DOI badge are also present, so the project is set up to be cited and archived.
Where CrossTL is the wrong tool, and the cost of the IR
The first wrong case is a single-target project. If your shaders only ever run in OpenGL, the intermediate representation buys you nothing and costs you a translation step, plus a fidelity question on every feature you use. The IR is a bet that pays off across APIs, and a single API is the case where the bet loses.
The second is a project that needs to import WebGL or WGSL. The README is explicit that those inputs are rejected until dedicated source frontends land. Migrating an existing WGSL codebase to CrossGL is not possible with the current tool.
The third is a team that needs compiled Direct3D output. CrossTL emits HLSL and the README says the final DXBC and DXIL packaging remains a toolchain step, so your build still needs the Microsoft shader compiler. The same shape applies to the other targets: SPIR-V needs the SPIR-V tools to be assembled, and GLSL needs a driver or glslang to compile.
The fourth is a risk appetite question. A translator that round trips through an IR is, by construction, lossy somewhere. The README's own parity testing is scoped to DirectX, Metal and OpenGL, and the validation tools for the other outputs are optional and commented out by default. That is a defensible engineering position for a young project, but it means the maturity of the guarantee varies sharply by target.
What the project does offer is a clear mechanism for reporting and tracking that gap, and an intermediate format that is designed to be a real interchange format rather than an internal detail, with CrossGL itself listed as a translation target so you can treat the IR as the canonical source.
Editorial conclusion
CrossTL suits graphics engineers who maintain shaders across several APIs and want one editable source of truth instead of several near-duplicate files, and who are prepared to treat the generated code as source that needs a diff review. It does not suit a project that ships a single target, since the intermediate representation costs you a translation step and a fidelity risk for no benefit, and it does not suit a team that needs to import WebGL or WGSL, because those inputs are rejected until dedicated source frontends land. Before adopting it, run the backend parity suite in the repository to see which features are covered on your API of interest, check the directx-runtime extra if you are on Windows, and remember that the final Direct3D packaging step, DXBC and DXIL, stays outside the tool.
Frequently asked questions
Which shader languages can CrossTL translate from and to?
Native import is available for the bidirectional backends, including HLSL, Metal and GLSL. Targets include Metal, HLSL, GLSL, GLSL ES for WebGL, WGSL, SPIR-V, CUDA, HIP, Rust, Mojo, Slang and CrossGL itself. WebGL and WGSL inputs are rejected until dedicated source frontends land.
How do I install the crosstl package?
It is published on PyPI as `crosstl` and installs with the standard installer, providing the `crosstl` console script. It requires Python 3.8 or newer, with `gast` always and `tomli` on Python versions below 3.11.
What is the directx-runtime extra for?
It pulls in `compushady` at 0.17.5 or newer but below 0.18, and only on Windows. It is a Direct3D runtime library needed when working with the DirectX backend on a Windows machine.
How does CrossTL validate that a translation is correct?
Targeted unit suites cover the feature surface of the DirectX, Metal and GLSL backends, and an end-to-end check translates native shaders to CrossGL and back out to HLSL, Metal, GLSL or Vulkan to check that attributes, layouts and semantics survive the round trip.
Does CrossTL produce compiled Direct3D binaries?
No. The target profile aliases such as `dx11` and `d3d12` resolve to the HLSL emitter, and the README states that final DXBC and DXIL packaging remains a toolchain step outside the translator.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/crossgl-crosstl)Was this useful?