rafx

Rafx Shader Processor

The shader processor reads GLSL and produces several outputs, including MSL source code, rust source code, compiled vulkan SPV, and a custom “package” format that can be used to create a shader at runtime in a cross-platform way.

rafx-shader-processor uses spirv_cross to read and translate between shader languages.

Diagram showing input and outputs of the shader processor

Installation

The easiest way to get the tool is to use cargo install.

cargo install rafx-shader-processor --version x.y.z

Be sure to install the same version as you include in your project. Don’t forget to update it if you update your project’s dependencies!

You can also build from source if you are modifying rafx or working from latest.

Usage

USAGE:
    rafx-shader-processor [FLAGS] [OPTIONS]

FLAGS:
    -h, --help                Prints help information
        --optimize-shaders    
        --trace               
    -V, --version             Prints version information

OPTIONS:
        --cooked-shader-file <cooked-shader-file>                
        --cooked-shaders-path <cooked-shaders-path>              
        --glsl-file <glsl-file>                                  
        --glsl-path <glsl-path>...                               
        --metal-generated-src-file <metal-generated-src-file>    
        --metal-generated-src-path <metal-generated-src-path>    
        --rs-file <rs-file>                                      
        --rs-lib-path <rs-path>                                      
        --rs-mod-path <rs-path>                                   
        --shader-kind <shader-kind>                              
        --spv-file <spv-file>                                    
        --spv-path <spv-path>      

Inputs

Outputs

When the “file” variants are used, rafx-shader-processor reads a single file and writes single files. With the “path” variant is used, rafx-shader-processor reads all shaders matching a glob and writes a file for each input at the provided paths.

Example

cargo run --package rafx-shader-processor -- --glsl-path glsl --rs-lib-path src --cooked-shaders-path ../../assets/shaders

Supported Input Formats

rafx-shader-processor currently supports just GLSL. Internally, the shader processor uses spirv_cross, so support for other languages like HLSL might not be too difficult to add in the future.

There are also some projects like rust-gpu to write shaders in rust. While this is an exciting area of development, rafx will prioritize production-ready workflows.

Supported Output Formats

spirv_cross can can read source code written in one language (like HLSL and GLSL) and output source code for a different language (like MSL).

This translation process is mostly automatic and 1:1, but there are a few key places where additional information is need to do the translation. Some of this is automatically generated by the shader processor, and some of it must be provided via custom annotation in the shader.

Shader Annotation and Code Generation

GLSL does not support a native form of annotation in the language. However, rafx-shader-processor looks for markup in comments.

See also: Shader Annotation and Generated Rust Code

Example 1: Automatically Creating and Binding an Immutable Sampler

This will automatically set up a sampler bound to this descriptor set. No changes in your code required! (Requires using DescriptorSetAllocatorManager in rafx-framework!):

// @[immutable_samplers([
//     (
//         mag_filter: Nearest,
//         min_filter: Nearest,
//         mip_map_mode: Linear,
//         address_mode_u: ClampToEdge,
//         address_mode_v: ClampToEdge,
//         address_mode_w: ClampToEdge,
//     )
// ])]
layout (set = 0, binding = 1) uniform sampler smp;

Example 2: Generating Rust Code that Matches the Shader

The @[export] annotation will cause the shader processor to generate rust code for this descriptor set with accessors to set the texture like this.

// @[export]
layout (set = 0, binding = 0) uniform PerViewData {
    vec4 ambient_light;
    uint point_light_count;
    uint directional_light_count;
    uint spot_light_count;
    PointLight point_lights[16];
    DirectionalLight directional_lights[16];
    SpotLight spot_lights[16];
    ShadowMap2DData shadow_map_2d_data[32];
    ShadowMapCubeData shadow_map_cube_data[16];
} per_view_data;
#[derive(Copy, Clone, Debug)]
#[repr(C)]
pub struct PerViewDataStd140 {
    pub ambient_light: [f32; 4],                             // +0 (size: 16)
    pub point_light_count: u32,                              // +16 (size: 4)
    pub directional_light_count: u32,                        // +20 (size: 4)
    pub spot_light_count: u32,                               // +24 (size: 4)
    pub _padding0: [u8; 4],                                  // +28 (size: 4)
    pub point_lights: [PointLightStd140; 16],                // +32 (size: 1024)
    pub directional_lights: [DirectionalLightStd140; 16],    // +1056 (size: 1024)
    pub spot_lights: [SpotLightStd140; 16],                  // +2080 (size: 1536)
    pub shadow_map_2d_data: [ShadowMap2DDataStd140; 32],     // +3616 (size: 2560)
    pub shadow_map_cube_data: [ShadowMapCubeDataStd140; 16], // +6176 (size: 256)
} // 6432 bytes

In addition to struct, rafx-shader-processor will also generate code for creating/setting descriptor sets that contain this field. (See Generated Rust Code for more details)