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 — языка промежуточного представления (IR), связывающего графические API, платформы GPU-вычислений и целевые системные языки.
Это Python-библиотека или отдельный инструмент-компилятор?CrossTL — Python-библиотека, которую вы импортируете, чтобы парсить нативные шейдеры или исходники CrossGL (.cgl) и генерировать код для целевых платформ Metal, HLSL, GLSL, WGSL, SPIR-V, CUDA, HIP, Rust, Mojo и Slang.
Как транслировать шейдер с помощью CrossTL?Установите CrossTL, напишите или импортируйте файл шейдера CrossGL (.cgl), затем вызовите транслятор, чтобы сгенерировать код для выбранного бэкенда.
Нужно ли что-то помимо Python, чтобы пользоваться CrossTL?CrossTL — Python-пакет, не требующий API-ключей, хотя финальное развёртывание на GPU, например упаковка DXBC/DXIL, всё ещё требует нативного тулчейна вендора.
Поддерживает ли каждый бэкенд полный язык CrossGL?Нет — продвинутые возможности вроде обобщённых функций и стадий geometry, tessellation и mesh/task вне DirectX, Metal и OpenGL покрыты лишь частично, а неподдерживаемые комбинации вызывают понятную ошибку.
Можно импортировать существующие шейдеры WebGL или WebGPU в CrossGL?Пока нет — WebGL и WGSL сейчас только целевые форматы вывода, а исходные файлы .webgl.glsl, .wgsl и .wesl отклоняются, пока не добавят отдельных исходных фронтендов.
Какие бэкенды имеют полную двунаправленную (импорт и экспорт) поддержку?DirectX/HLSL, Metal и OpenGL/GLSL — первоклассные двунаправленные бэкенды: каждый умеет и парсить нативный исходник шейдера в CrossGL, и генерировать код обратно, что подтверждено round-trip-тестами (321 проход).
Что такое CrossGL?CrossGL — язык промежуточного представления (IR) в ядре CrossTL: шейдеры либо парсятся в него из нативного исходника, либо пишутся сразу как файлы CrossGL (.cgl), а затем компилируются в любой поддерживаемый бэкенд.
Что делает workflow аудита проекта?Для кодовых баз с множеством шейдерных или GPU-исходников CrossTL может выполнить отчёт в режиме только сканирования, охватывающий диагностику трансляции, происхождение артефактов и заметки по ручной миграции, прежде чем записывать какие-либо переведённые файлы.
Какие стадии проходит конвейер трансляции?Он выполняет лексический анализ (токенизацию), синтаксический анализ (построение AST), семантический анализ (проверку типов и областей видимости), генерацию IR в CrossGL и в конце генерацию кода под конкретный бэкенд.
Creator
CrossGL
Size
78.8 MB
Stars
1.1K
Age
2y
Commits · 90 Days