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
:
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::Map
andStyleRule::Map
, it provides access to a cells color and position allowing to map the output of the shader to more complex behaviour. - Shader
Canvas ShaderCanvas
implements theStatefulWidget
trait from Ratatui. It holds the logic for applying the result of GPU computation to theBuffer
struct which Ratatui uses to display to the terminal.- Shader
Canvas State ShaderCanvasState
holds 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
enum
to 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
.