Skip to main content

CrossGL shader and compute translator

Project description




CrossTL - Universal Shader Translator

CrossTL is a shader and compute program translator built around CrossGL — an intermediate representation (IR) language that bridges graphics APIs, GPU-compute platforms, and systems-language targets.

Supported Translation Targets

CrossTL provides translation from CrossGL to:

  • Metal - Apple's graphics and compute API
  • DirectX (HLSL) - Microsoft's graphics API
  • OpenGL (GLSL) - Cross-platform graphics
  • WebGL (GLSL ES) - Browser graphics through WebGL 2.0 compatible GLSL ES
  • WebGPU (WGSL) - Browser and native WebGPU shader output
  • Vulkan (SPIRV) - High-performance graphics and compute
  • CUDA - NVIDIA parallel computing
  • HIP - AMD GPU computing
  • Rust - GPU-oriented Rust shader code
  • Mojo - Mojo shader/compute modules
  • Slang - Real-time shading language
  • CrossGL (.cgl) - The IR/interchange format itself

Native source import is available for the bidirectional backends listed in the support matrix. WebGL and WGSL are currently target-only outputs; .webgl.glsl, .wgsl, and .wesl inputs are rejected until dedicated source frontends land.

Backend Readiness: DirectX / Metal / OpenGL

We maintain first-class, bidirectional support for the three cornerstone graphics APIs. Each backend is implemented as both a source (parse/import) and codegen (export) target, so you can round‑trip between native shaders and CrossGL without lossy hops.

  • DirectX / HLSL
    • Pipeline coverage: vertex, fragment/pixel, compute, geometry, hull/domain (tessellation), mesh/task, full ray‑tracing stages.
    • Resource model: cbuffers, register/space bindings, UAV/RW textures & buffers, structured buffers, Interlocked atomics, wave ops, texture/buffer dimension queries.
    • Semantics map to SV_* and user semantics, preserved through CrossGL attributes.
  • Metal
    • Stages: vertex, fragment, compute, mesh/object, and ray‑tracing qualifiers.
    • Resource/binding support: argument buffers, [[buffer]], [[texture]], [[sampler]], function constants, packed/simd types, indirect command buffers, payload/hit attributes.
    • Texture methods (sample/load/store/gather/compare) and threadgroup builtins are translated to CrossGL intrinsics.
  • OpenGL / GLSL
    • Stages: vertex, fragment, compute with version inference (defaults to #version 450 core when absent).
    • Handles interface blocks, layouts/bindings, sampler & image operations, control flow, structs/arrays, discard, builtins (gl_*) and preprocessor directives.
  • Round‑trips verified via converters in crosstl.backend.{DirectX,Metal,GLSL} and registered in translator.source_registry and translator.codegen.registry.

How we validate backend parity

  • Targeted unit suites exercise the full feature surface for these three backends (shader stages, bindings, intrinsics, control‑flow, resources, and preprocessor handling).
  • Quick check: run pytest tests/test_backend/test_directx tests/test_backend/test_metal tests/test_backend/test_GLSL -q (currently 321 passing tests).
  • End‑to‑end translation: translate native HLSL/Metal/GLSL → CrossGL → HLSL/Metal/GLSL/Vulkan to ensure attributes, layouts, and semantics survive round‑trips.

Translation Architecture

CrossTL uses a multi-stage translation pipeline:

  1. Lexical Analysis: Tokenization
  2. Syntax Analysis: AST generation
  3. Semantic Analysis: Type checking and scope resolution
  4. IR Generation: Conversion to CrossGL intermediate representation
  5. Target Generation: Backend-specific code generation

CrossGL Programming Language Examples

PBR Shader Example

// Physically-based rendering shader
struct Material {
    albedo: vec3,
    metallic: float,
    roughness: float,
    normal_map: texture2d,
    displacement: texture2d
}

struct Lighting {
    position: vec3,
    color: vec3,
    intensity: float,
    attenuation: vec3
}

shader PBRShader {
    vertex {
        input vec3 position;
        input vec3 normal;
        input vec2 texCoord;
        input vec4 tangent;

        uniform mat4 modelMatrix;
        uniform mat4 viewMatrix;
        uniform mat4 projectionMatrix;

        output vec3 worldPos;
        output vec3 worldNormal;
        output vec2 uv;
        output mat3 TBN;

        void main() {
            vec4 worldPosition = modelMatrix * vec4(position, 1.0);
            worldPos = worldPosition.xyz;

            vec3 T = normalize(vec3(modelMatrix * vec4(tangent.xyz, 0.0)));
            vec3 N = normalize(vec3(modelMatrix * vec4(normal, 0.0)));
            vec3 B = cross(N, T) * tangent.w;
            TBN = mat3(T, B, N);

            worldNormal = N;
            uv = texCoord;

            gl_Position = projectionMatrix * viewMatrix * worldPosition;
        }
    }

    fragment {
        input vec3 worldPos;
        input vec3 worldNormal;
        input vec2 uv;
        input mat3 TBN;

        uniform Material material;
        uniform Lighting lights[8];
        uniform int lightCount;
        uniform vec3 cameraPos;

        output vec4 fragColor;

        vec3 calculatePBR(vec3 albedo, float metallic, float roughness,
                         vec3 normal, vec3 viewDir, vec3 lightDir, vec3 lightColor) {
            vec3 halfVector = normalize(viewDir + lightDir);
            float NdotV = max(dot(normal, viewDir), 0.0);
            float NdotL = max(dot(normal, lightDir), 0.0);
            float NdotH = max(dot(normal, halfVector), 0.0);
            float VdotH = max(dot(viewDir, halfVector), 0.0);

            vec3 F0 = mix(vec3(0.04), albedo, metallic);
            vec3 F = F0 + (1.0 - F0) * pow(1.0 - VdotH, 5.0);

            float alpha = roughness * roughness;
            float alpha2 = alpha * alpha;
            float denom = NdotH * NdotH * (alpha2 - 1.0) + 1.0;
            float D = alpha2 / (3.14159 * denom * denom);

            float G = geometrySmith(NdotV, NdotL, roughness);

            vec3 numerator = D * G * F;
            float denominator = 4.0 * NdotV * NdotL + 0.001;
            vec3 specular = numerator / denominator;

            vec3 kS = F;
            vec3 kD = vec3(1.0) - kS;
            kD *= 1.0 - metallic;

            return (kD * albedo / 3.14159 + specular) * lightColor * NdotL;
        }

        void main() {
            vec3 normal = normalize(TBN * (texture(material.normal_map, uv).rgb * 2.0 - 1.0));
            vec3 viewDir = normalize(cameraPos - worldPos);

            vec3 color = vec3(0.0);

            for (int i = 0; i < lightCount; ++i) {
                vec3 lightDir = normalize(lights[i].position - worldPos);
                float distance = length(lights[i].position - worldPos);
                float attenuation = 1.0 / (lights[i].attenuation.x +
                                          lights[i].attenuation.y * distance +
                                          lights[i].attenuation.z * distance * distance);

                vec3 lightColor = lights[i].color * lights[i].intensity * attenuation;
                color += calculatePBR(material.albedo, material.metallic,
                                     material.roughness, normal, viewDir, lightDir, lightColor);
            }

            color += material.albedo * 0.03;

            color = color / (color + vec3(1.0));
            color = pow(color, vec3(1.0/2.2));

            fragColor = vec4(color, 1.0);
        }
    }
}

Backend Compatibility

All backends support the core CrossGL language (structs, arrays, functions, control flow, texture sampling, shader stages). Some advanced language features have partial backend coverage:

Feature Not yet supported in
Generic functions SPIR-V, CUDA, HIP, Mojo, Slang (pending monomorphization)
Geometry stage Metal, CUDA, HIP, Mojo, Rust (diagnostic fallback)
Tessellation stage OpenGL, Metal, CUDA, HIP, Mojo, Rust (diagnostic fallback)
Mesh/Task stage CUDA, HIP, Mojo, Rust, Slang (diagnostic fallback)

Translation calls targeting unsupported feature/backend combinations raise a clear error with a reason string.

Getting Started

Install CrossTL:

pip install crosstl

Basic Usage

1. Create a CrossGL shader

shader SimpleShader {
    vertex {
        input vec3 position;
        uniform mat4 modelViewProjection;
        output vec4 fragPosition;

        void main() {
            fragPosition = modelViewProjection * vec4(position, 1.0);
        }
    }

    fragment {
        input vec4 fragPosition;
        output vec4 fragColor;

        void main() {
            fragColor = vec4(1.0, 0.0, 0.0, 1.0);
        }
    }
}

2. Translate to a target

import crosstl

# Translate to any supported backend
metal_code = crosstl.translate('shader.cgl', backend='metal', save_shader='shader.metal')
hlsl_code = crosstl.translate('shader.cgl', backend='directx', save_shader='shader.hlsl')
glsl_code = crosstl.translate('shader.cgl', backend='opengl', save_shader='shader.glsl')

Reverse Translation - Import Existing Code

# Import existing shaders into CrossGL
conversions = [
    ('existing_shader.hlsl', 'unified.cgl'),     # DirectX to CrossGL
    ('gpu_kernel.cu', 'unified.cgl'),            # CUDA to CrossGL
    ('graphics.metal', 'unified.cgl'),           # Metal to CrossGL
]

for source, target in conversions:
    unified_code = crosstl.translate(source, backend='cgl', save_shader=target)
    print(f"Unified {source} into CrossGL: {target}")

Deploying to All Backends

import crosstl
from pathlib import Path

program = 'universal_shader.cgl'

deployment_targets = {
    'metal': '.metal',
    'directx': '.hlsl',
    'opengl': '.glsl',
    'vulkan': '.spvasm',
    'rust': '.rs',
    'mojo': '.mojo',
    'cuda': '.cu',
    'hip': '.hip',
    'slang': '.slang',
}

stem = Path(program).stem
for backend, extension in deployment_targets.items():
    output_file = f'deployments/{stem}_{backend}{extension}'
    try:
        crosstl.translate(program, backend=backend, save_shader=output_file)
        print(f"{backend}: {output_file}")
    except Exception as e:
        print(f"{backend}: {str(e)}")

For comprehensive language documentation, visit our Language Reference.

Contributing

CrossGL is a community-driven project. Find out more in our Contributing Guide.

Community

License

CrossTL is open-source and licensed under the Apache License 2.0.


The CrossGL Team

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

crosstl-2.0.1.tar.gz (1.7 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

crosstl-2.0.1-py3-none-any.whl (1.6 MB view details)

Uploaded Python 3

File details

Details for the file crosstl-2.0.1.tar.gz.

File metadata

  • Download URL: crosstl-2.0.1.tar.gz
  • Upload date:
  • Size: 1.7 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for crosstl-2.0.1.tar.gz
Algorithm Hash digest
SHA256 a5f674cc1285542bc937dd2bc8e7c2205207306acae34e1bc1f2c9073ff192a7
MD5 7b8d290410aafb9dc49baef0299510da
BLAKE2b-256 6fbc1a37c9a27f13cf545ac4d70d55a28ec46636e27e4e931d2c5bb86d25c823

See more details on using hashes here.

File details

Details for the file crosstl-2.0.1-py3-none-any.whl.

File metadata

  • Download URL: crosstl-2.0.1-py3-none-any.whl
  • Upload date:
  • Size: 1.6 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for crosstl-2.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 fe5ac6f93e151de4f1c96a1003ed2f6e626f7bc905457badf52eb50c242886b9
MD5 bc238c4560e9311dfc0fc8e39854552b
BLAKE2b-256 50114a9c630c7c6a340702c952c15bcecbeabe3b8e611f7ca7e5e4e7c701a633

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page