crosstl README

The project's README from github.com/CrossGL/crosstl. As one markdown file: README.md.




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:

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.

How we validate backend parity

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

The README demo uses the same current CrossGL source file as the showcase notebook: demos/readme/showcase_pbr.cgl and demos/readme/demo.ipynb. The shader demonstrates a real PBR-style material path: tangent-space normal mapping, GGX normal distribution, Smith geometry, Schlick Fresnel, multiple point lights, texture sampling, tone mapping, and gamma correction.

It uses the current struct-return stage style so backends such as Metal can emit proper stage input/output structs and vertex/fragment entry points.

shader ShowcasePBRShader {
    const float PI = 3.14159265359;
    const float EPSILON = 0.0001;
    const int MAX_LIGHTS = 4;

    struct VertexInput {
        vec3 position;
        vec3 normal;
        vec3 tangent;
        vec2 texCoord;
    }

    struct VertexOutput {
        vec3 worldPosition;
        vec3 worldNormal;
        vec3 worldTangent;
        vec3 worldBitangent;
        vec2 uv;
        vec4 position;
    }

    struct FragmentInput {
        vec3 worldPosition;
        vec3 worldNormal;
        vec3 worldTangent;
        vec3 worldBitangent;
        vec2 uv;
    }

    struct FragmentOutput {
        vec4 color;
    }

    float distributionGGX(vec3 normal, vec3 halfVector, float roughness) {
        float alpha = roughness * roughness;
        float alpha2 = alpha * alpha;
        float ndoth = max(dot(normal, halfVector), 0.0);
        float denom = ndoth * ndoth * (alpha2 - 1.0) + 1.0;
        return alpha2 / max(PI * denom * denom, EPSILON);
    }

    float geometrySchlickGGX(float ndotv, float roughness) {
        float k = (roughness + 1.0) * (roughness + 1.0) / 8.0;
        return ndotv / max(ndotv * (1.0 - k) + k, EPSILON);
    }

    float geometrySmith(vec3 normal, vec3 viewDir, vec3 lightDir, float roughness) {
        float ndotv = max(dot(normal, viewDir), 0.0);
        float ndotl = max(dot(normal, lightDir), 0.0);
        return geometrySchlickGGX(ndotv, roughness) * geometrySchlickGGX(ndotl, roughness);
    }

    vec3 fresnelSchlick(float cosTheta, vec3 baseReflectance) {
        return baseReflectance + (vec3(1.0) - baseReflectance) * pow(max(1.0 - cosTheta, 0.0), 5.0);
    }

    vertex {
        uniform mat4 modelMatrix;
        uniform mat4 viewProjectionMatrix;
        uniform mat3 normalMatrix;

        VertexOutput main(VertexInput input) {
            VertexOutput output;
            vec4 worldPosition = modelMatrix * vec4(input.position, 1.0);
            output.worldPosition = worldPosition.xyz;
            output.worldNormal = normalize(normalMatrix * input.normal);
            output.worldTangent = normalize(normalMatrix * input.tangent);
            output.worldBitangent = normalize(cross(output.worldNormal, output.worldTangent));
            output.uv = input.texCoord;
            output.position = viewProjectionMatrix * worldPosition;
            return output;
        }
    }

    fragment {
        uniform sampler2D albedoMap;
        uniform sampler2D normalMap;
        uniform sampler2D metallicRoughnessMap;
        uniform vec3 cameraPosition;
        uniform vec3 lightPositions[MAX_LIGHTS];
        uniform vec3 lightColors[MAX_LIGHTS];
        uniform int activeLightCount;
        uniform vec3 baseColor;
        uniform float metallicFactor;
        uniform float roughnessFactor;

        vec3 decodeNormal(vec3 encodedNormal, mat3 tangentFrame) {
            vec3 tangentNormal = encodedNormal * 2.0 - 1.0;
            return normalize(tangentFrame * tangentNormal);
        }

        FragmentOutput main(FragmentInput input) {
            FragmentOutput output;
            mat3 tangentFrame = mat3(
                normalize(input.worldTangent),
                normalize(input.worldBitangent),
                normalize(input.worldNormal)
            );
            vec3 normal = decodeNormal(texture(normalMap, input.uv).rgb, tangentFrame);
            vec3 viewDir = normalize(cameraPosition - input.worldPosition);
            vec3 albedo = texture(albedoMap, input.uv).rgb * baseColor;
            vec3 materialSample = texture(metallicRoughnessMap, input.uv).rgb;
            float metallic = clamp(materialSample.b * metallicFactor, 0.0, 1.0);
            float roughness = clamp(materialSample.g * roughnessFactor, 0.04, 1.0);
            vec3 baseReflectance = mix(vec3(0.04), albedo, metallic);
            vec3 radianceSum = vec3(0.0);

            for (int i = 0; i < MAX_LIGHTS; i++) {
                if (i >= activeLightCount) {
                    break;
                }
                vec3 lightVector = lightPositions[i] - input.worldPosition;
                float distanceSq = max(dot(lightVector, lightVector), EPSILON);
                vec3 lightDir = normalize(lightVector);
                vec3 halfVector = normalize(viewDir + lightDir);
                float attenuation = 1.0 / distanceSq;
                vec3 radiance = lightColors[i] * attenuation;

                float normalDistribution = distributionGGX(normal, halfVector, roughness);
                float geometry = geometrySmith(normal, viewDir, lightDir, roughness);
                vec3 fresnel = fresnelSchlick(max(dot(halfVector, viewDir), 0.0), baseReflectance);
                vec3 diffuseShare = (vec3(1.0) - fresnel) * (1.0 - metallic);
                vec3 numerator = normalDistribution * geometry * fresnel;
                float denominator = max(4.0 * max(dot(normal, viewDir), 0.0) * max(dot(normal, lightDir), 0.0), EPSILON);
                vec3 specular = numerator / denominator;
                float ndotl = max(dot(normal, lightDir), 0.0);
                radianceSum += (diffuseShare * albedo / PI + specular) * radiance * ndotl;
            }

            vec3 ambient = albedo * 0.03;
            vec3 color = ambient + radianceSum;
            color = color / (color + vec3(1.0));
            color = pow(color, vec3(1.0 / 2.2));
            output.color = vec4(color, 1.0);
            return output;
        }
    }
}

Run the showcase notebook or translate the source directly:

python -m crosstl translate demos/readme/showcase_pbr.cgl --backend metal --output -
python -m crosstl translate demos/readme/showcase_pbr.cgl --backend directx --output -
python -m crosstl translate demos/readme/showcase_pbr.cgl --backend opengl --output -

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 {
    struct VertexInput {
        vec3 position;
        vec2 texCoord;
    }

    struct VertexOutput {
        vec2 uv;
        vec4 position;
    }

    struct FragmentInput {
        vec2 uv;
    }

    struct FragmentOutput {
        vec4 color;
    }

    vertex {
        VertexOutput main(VertexInput input) {
            VertexOutput output;
            output.uv = input.texCoord;
            output.position = vec4(input.position, 1.0);
            return output;
        }
    }

    fragment {
        FragmentOutput main(FragmentInput input) {
            FragmentOutput output;
            output.color = vec4(input.uv, 0.5, 1.0);
            return output;
        }
    }
}

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')
dx12_hlsl = crosstl.translate('shader.cgl', backend='dx12', save_shader='shader.hlsl')
glsl_code = crosstl.translate('shader.cgl', backend='opengl', save_shader='shader.glsl')

Project audit workflow

For repositories with many shader or GPU source files, start with a scan-only report before writing translated artifacts:

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/portability-report.json

python -m crosstl validate-project \
  crosstl-out/portability-report.json \
  --format text

python -m crosstl inspect-report \
  crosstl-out/portability-report.json \
  --format text

Project reports cover shader and kernel source translation, diagnostics, artifact provenance, optional toolchain checks, and manual migration actions for host/runtime integration. See the project porting guide. The repository also includes pinned open-source porting demos with checked artifacts and platform validator coverage in demos/open-source-porting.

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',
    'dx12': '.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

crosstl (CrossGL/crosstl)

Searching Database…

1.1K ·GitHub
这是什么?CrossTL 是一个着色器和计算程序转换器,围绕 CrossGL 构建——CrossGL 是一种中间表示(IR)语言,用于衔接各种图形 API、GPU 计算平台和系统级语言目标。
这是 Python 库,还是独立的编译器工具?CrossTL 是一个 Python 库,导入后可以解析原生着色器或 CrossGL(.cgl)源码,并为 Metal、HLSL、GLSL、WGSL、SPIR-V、CUDA、HIP、Rust、Mojo 和 Slang 等目标生成代码。
怎么用 CrossTL 转换着色器?安装 CrossTL,编写或导入一个 CrossGL(.cgl)着色器文件,然后调用转换器为选定的后端目标生成代码。
用 CrossTL 除 Python 外还需要什么?CrossTL 是一个 Python 包,不需要任何 API 密钥,不过最终的 GPU 部署(例如 DXBC/DXIL 打包)仍需要厂商的原生工具链。
每个后端都支持完整的 CrossGL 语言吗?不支持——在 DirectX、Metal 和 OpenGL 之外,泛型函数以及 geometry、tessellation 和 mesh/task 阶段等高级特性只有部分覆盖,不受支持的组合会给出明确的报错。
我能把现有的 WebGL 或 WebGPU 着色器导入 CrossGL 吗?暂时不能——WebGL 和 WGSL 目前只能作为目标输出,.webgl.glsl、.wgsl 和 .wesl 源文件会被拒绝,直到加入专门的源码前端为止。
哪些后端有完整的双向(导入和导出)支持?DirectX/HLSL、Metal 和 OpenGL/GLSL 是第一梯队的双向后端——每一个都能把原生着色器源码解析成 CrossGL,也能反向生成代码,并通过往返测试验证(321 项通过)。
CrossGL 是什么?CrossGL 是 CrossTL 核心的中间表示(IR)语言:着色器要么从原生源码解析成它,要么直接写成 CrossGL(.cgl)文件,然后再编译输出到任何受支持的后端。
项目审计工作流是做什么的?对于有大量着色器或 GPU 源文件的代码库,CrossTL 可以在写出任何转换文件之前,先跑一份仅扫描的报告,涵盖转换诊断、产物来源和人工迁移备注。
转换流水线要经过哪些阶段?它依次执行词法分析(token 化)、语法分析(生成 AST)、语义分析(类型/作用域检查)、生成 CrossGL 中间表示,最后进行特定后端的代码生成。
Creator
CrossGL
Size
70.8 MB
Stars
1.1K
Age
2y
Commits · 90 Days