Crate tui_shader

Source
Expand description

The tui-shader crate enables GPU accelerated styling for Ratatui based applications.

Computing styles at runtime can be expensive when run on the CPU, despite the small “resolution” of cells in a terminal window. Utilizing the power of the GPU helps us update the styling in the terminal at considerably higher framerates.

§Quickstart

Add ratatui and tui-shader as dependencies to your Corgo.toml:

cargo add ratatui tui-shader

Then create a new application:

let mut terminal = ratatui::init();
let mut state = ShaderCanvasState::default();
while state.get_instant().elapsed().as_secs() < 7 {
    terminal.draw(|frame| {
        frame.render_stateful_widget(ShaderCanvas::new(),
            frame.area(),
            &mut state);
    }).unwrap();
}
ratatui::restore();

And run it

cargo run

Well that was lame. Where are all the cool shader-y effects? We haven’t actually provided a shader that the application should use so our ShaderCanvasState uses a default shader, which always returns the magenta color. This happends because we created it using ShaderCanvasState::default(). Now, let’s write a wgsl shader and render some fancy stuff in the terminal.

@group(0) @binding(0) var<uniform> time: f32;

@fragment
fn main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
    let r = sin(1.0 - uv.x + time);
    let g = cos(1.0 - uv.y + time);
    let b = 0.5;
    let d = 1.0 - distance(vec2<f32>(0.5), uv);
    let color = vec4<f32>(r, g, b, 1.0) * d;
    return color;
}

Ok, there’s a lot to unwrap here. At first we define a uniform time of type f32. The time field is filled with data out-of-the-box by our ShaderCanvasState and can be used in any shader. The important thing to note is that it isn’t the name of the field that’s important, it’s the @group and @binding attributes that determine which data we are reading from it.

Finally - and this is were the magic happens - we can define our function for manipulating colors. We must denote our function with @fragment because we are writing a fragment shader. As long as we only define a single @fragment function in our file, we can name it whatever we want. Otherwise, we must create our ShaderCanvasState using ShaderCanvasState::new_with_entry_point and pass in the name of the desired @fragment function. A vertex shader cannot be provided as it always uses a single triangle, full-screen vertex shader.

We can use the UV coordinates provided by the vertex shader with @location(0) uv: vec2<f32>. Now we have time and UV coordinates to work with to create amazing shaders. This shader just does some math with these values and returns a new color. Time to get creative!

Now, all we need to do is create our ShaderCanvasState using ShaderCanvasState::new and pass in our shader.

let mut terminal = ratatui::init();
let shader = WgslShader::Path("shader.wgsl");
let mut state = ShaderCanvasState::new(shader).unwrap();
while state.get_instant().elapsed().as_secs() < 5 {
    terminal.draw(|frame| {
        frame.render_stateful_widget(ShaderCanvas::new(),
            frame.area(),
            &mut state);
    }).unwrap();
}
ratatui::restore();

Now that’s more like it!

§Shader Input Parameters

There a few input parameters that are setup out-of-the-box for use in tui-shader:

InputTypeBindingExplanation
Timevec4<f32>@group(0) @binding(0)x: time in seconds as f32, y: time.x * 10, z: sin(time.x), w: cos(time.x)
Rectvec4<u32>@group(0) @binding(1)x: x position of rect, y: y position of rect, z: width, w: height
UVvec2<f32>@location(0)x: normalized x coordinate y: norimalized y coordinate
Positionvec4<f32>@builtin(position)x: absolute x position y: absolute y position z/w: useless in tui-shader

Macros§

include_wgsl
Load WGSL source code from a file at compile time.

Structs§

Sample
Primarily used in CharacterRule::Map and StyleRule::Map, it provides access to a cells color and position allowing to map the output of the shader to more complex behaviour.
ShaderCanvas
ShaderCanvas implements the StatefulWidget trait from Ratatui. It holds the logic for applying the result of GPU computation to the Buffer struct which Ratatui uses to display to the terminal.
ShaderCanvasState
ShaderCanvasState holds the state to execute a render pass. It handles window/widget resizing automatically and creates new textures and buffers when necessary.

Enums§

CharacterRule
Determines which character to use for Cell.
StyleRule
Determines how to use the output of the fragment shader to style a Cell.
WgslShader
Utility enum to pass in a shader into ShaderCanvasState. Another option is to use the re-exported include_wgsl! macro, which checks at runtime if the path to the file is valid and returns a ShaderModuleDescriptor.