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-shaderThen 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 runWell 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:
| Input | Type | Binding | Explanation |
|---|---|---|---|
| Time | vec4<f32> | @group(0) @binding(0) | x: time in seconds as f32, y: time.x * 10, z: sin(time.x), w: cos(time.x) |
| Rect | vec4<u32> | @group(0) @binding(1) | x: x position of rect, y: y position of rect, z: width, w: height |
| UV | vec2<f32> | @location(0) | x: normalized x coordinate y: norimalized y coordinate |
| Position | vec4<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::MapandStyleRule::Map, it provides access to a cells color and position allowing to map the output of the shader to more complex behaviour. - Shader
Canvas ShaderCanvasimplements theStatefulWidgettrait from Ratatui. It holds the logic for applying the result of GPU computation to theBufferstruct which Ratatui uses to display to the terminal.- Shader
Canvas State ShaderCanvasStateholds the state to execute a render pass. It handles window/widget resizing automatically and creates new textures and buffers when necessary.
Enums§
- Character
Rule - Determines which character to use for Cell.
- Style
Rule - Determines how to use the output of the fragment shader to style a Cell.
- Wgsl
Shader - Utility
enumto pass in a shader intoShaderCanvasState. Another option is to use the re-exportedinclude_wgsl!macro, which checks at runtime if the path to the file is valid and returns aShaderModuleDescriptor.