Skip to content

Commit e475f56

Browse files
committed
fail
1 parent 2d500b9 commit e475f56

File tree

10 files changed

+321
-155
lines changed

10 files changed

+321
-155
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ name = "tui-shader"
33
authors = ["Paul Mattern <[email protected]>"]
44
documentation = "https://docs.rs/tui-shader/latest/tui_shader/"
55
version = "0.0.7"
6-
edition = "2024"
6+
edition = "2021"
77
categories = ["graphics", "rendering", "gui"]
88
description = "A ratatui widget that renders a fragment shader in the terminal"
99
homepage = "https://github.com/pemattern/tui-shader"
1010
repository = "https://github.com/pemattern/tui-shader"
1111
keywords = ["tui", "shader", "wgpu", "terminal", "graphics"]
1212
license = "MIT OR Apache-2.0"
1313
readme = "README.md"
14-
rust-version = "1.85.0"
14+
rust-version = "1.83.0"
1515
exclude = ["assets/", ".github/"]
1616

1717
[dependencies]

examples/demo/main.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ const LIGHT_COLOR: ratatui::style::Color = ratatui::style::Color::Rgb(215, 222,
33

44
fn main() {
55
let mut terminal = ratatui::init();
6-
let mut state =
7-
tui_shader::ShaderCanvasState::new(wgpu::include_wgsl!("../../shaders/dither.wgsl"), None);
6+
let mut state = tui_shader::ShaderCanvasState::wgpu("shaders/dither.wgsl", "main");
87
const STYLE_RULE: tui_shader::StyleRule = tui_shader::StyleRule::Map(|sample| {
98
if sample.r() > 127 {
109
ratatui::style::Style::new().fg(DARK_COLOR).bg(LIGHT_COLOR)

examples/hello-shader/main.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
pub fn main() -> std::io::Result<()> {
22
let mut terminal = ratatui::init();
3-
let mut state =
4-
tui_shader::ShaderCanvasState::new(wgpu::include_wgsl!("../../shaders/voronoi.wgsl"), None);
3+
let mut state = tui_shader::ShaderCanvasState::wgpu("shaders/voronoi.wgsl", "main");
54

65
let start_time = std::time::Instant::now();
76
loop {

examples/pipe-into/main.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ pub fn main() -> std::io::Result<()> {
55
std::io::Read::read_to_string(&mut stdin_lock, &mut s).unwrap();
66
}
77
let mut terminal = ratatui::init();
8-
let mut state = tui_shader::ShaderCanvasState::new(
9-
wgpu::include_wgsl!("../../shaders/gradient.wgsl"),
10-
None,
11-
);
8+
let mut state = tui_shader::ShaderCanvasState::wgpu("shaders/gradient.wgsl", "main");
129
let start_time = std::time::Instant::now();
1310
while start_time.elapsed().as_secs() < 7 {
1411
terminal.draw(|frame| {

examples/style-rule/main.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
pub fn main() -> std::io::Result<()> {
22
let mut terminal = ratatui::init();
3-
let mut state = tui_shader::ShaderCanvasState::new(
4-
wgpu::include_wgsl!("../../shaders/gradient.wgsl"),
5-
None,
6-
);
3+
let mut state = tui_shader::ShaderCanvasState::wgpu("shaders/gradient.wgsl", "main");
74
const STYLE_RULE: tui_shader::StyleRule = tui_shader::StyleRule::Map(|sample| {
85
let color = ratatui::style::Color::Rgb(sample.r(), sample.g(), sample.b());
96
let sum = sample.r() as u16 + sample.g() as u16 + sample.b() as u16;

examples/stylize-other-widget/main.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,8 @@ use ratatui::style::Stylize;
22

33
pub fn main() -> std::io::Result<()> {
44
let mut terminal = ratatui::init();
5-
let mut fg_shader_state =
6-
tui_shader::ShaderCanvasState::new(wgpu::include_wgsl!("../../shaders/voronoi.wgsl"), None);
7-
let mut bg_shader_state = tui_shader::ShaderCanvasState::new(
8-
wgpu::include_wgsl!("../../shaders/starlight.wgsl"),
9-
None,
10-
);
5+
let mut fg_shader_state = tui_shader::ShaderCanvasState::wgpu("shaders/voronoi.wgsl", "main");
6+
let mut bg_shader_state = tui_shader::ShaderCanvasState::wgpu("shaders/starlight.wgsl", "main");
117
let mut list_state = ratatui::widgets::ListState::default();
128
*list_state.selected_mut() = Some(5);
139
let start_time = std::time::Instant::now();

src/backend/cpu.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use crate::{Pixel, ShaderContext};
2+
3+
use super::{NoUserData, TuiShaderBackend};
4+
5+
pub struct CpuBackend<T = NoUserData> {
6+
callback: Box<dyn CpuShaderCallback<T>>,
7+
}
8+
9+
impl CpuBackend {
10+
pub fn new<F>(callback: F) -> Self
11+
where
12+
F: Fn(u32, u32, ShaderContext) -> Pixel + 'static,
13+
{
14+
Self {
15+
callback: Box::new(CpuShaderCallbackWithoutUserData(callback)),
16+
}
17+
}
18+
}
19+
20+
impl<T> CpuBackend<T> {
21+
pub fn new_with_user_data<F>(callback: F) -> Self
22+
where
23+
F: Fn(u32, u32, ShaderContext, &T) -> Pixel + 'static,
24+
{
25+
Self {
26+
callback: Box::new(callback),
27+
}
28+
}
29+
}
30+
31+
impl<T> TuiShaderBackend<T> for CpuBackend<T> {
32+
fn execute(&mut self, ctx: ShaderContext, user_data: &T) -> Vec<Pixel> {
33+
let width = ctx.resolution[0];
34+
let height = ctx.resolution[1];
35+
let mut pixels = Vec::new();
36+
for y in 0..height {
37+
for x in 0..width {
38+
let value = self.callback.call(x, y, ctx, user_data);
39+
pixels.push(value);
40+
}
41+
}
42+
pixels
43+
}
44+
}
45+
46+
pub trait CpuShaderCallback<T = NoUserData> {
47+
fn call(&self, x: u32, y: u32, ctx: ShaderContext, user_data: &T) -> Pixel;
48+
}
49+
50+
impl<T, F> CpuShaderCallback<T> for F
51+
where
52+
F: Fn(u32, u32, ShaderContext, &T) -> Pixel,
53+
{
54+
fn call(&self, x: u32, y: u32, ctx: ShaderContext, user_data: &T) -> Pixel {
55+
self(x, y, ctx, user_data)
56+
}
57+
}
58+
59+
// NewType required to avoid conflicting implementations
60+
struct CpuShaderCallbackWithoutUserData<F>(F);
61+
impl<F> CpuShaderCallback for CpuShaderCallbackWithoutUserData<F>
62+
where
63+
F: Fn(u32, u32, ShaderContext) -> Pixel,
64+
{
65+
fn call(&self, x: u32, y: u32, ctx: ShaderContext, _user_data: &NoUserData) -> Pixel {
66+
self.0(x, y, ctx)
67+
}
68+
}

src/backend/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use crate::{Pixel, ShaderContext};
2+
3+
pub mod cpu;
4+
pub mod wgpu;
5+
6+
#[repr(C)]
7+
#[derive(Copy, Clone, Default, bytemuck::Pod, bytemuck::Zeroable)]
8+
pub struct NoUserData(f32);
9+
10+
pub trait TuiShaderBackend<T> {
11+
fn execute(&mut self, ctx: ShaderContext, user_data: &T) -> Vec<Pixel>;
12+
}

src/state.rs renamed to src/backend/wgpu.rs

Lines changed: 67 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
1-
use pollster::FutureExt;
1+
use std::{fs, marker::PhantomData};
2+
3+
use pollster::FutureExt as _;
24
use wgpu::util::DeviceExt;
35

46
use crate::{Pixel, ShaderContext};
57

8+
use super::{NoUserData, TuiShaderBackend};
9+
610
const DEFAULT_SIZE: u32 = 64;
711

812
#[derive(Debug, Clone)]
9-
pub struct ShaderCanvasState {
13+
pub struct WgpuBackend<T>
14+
where
15+
T: Copy + Clone + Default + bytemuck::Pod + bytemuck::Zeroable,
16+
{
1017
device: wgpu::Device,
1118
queue: wgpu::Queue,
1219
pipeline: wgpu::RenderPipeline,
@@ -16,23 +23,18 @@ pub struct ShaderCanvasState {
1623
bind_group: wgpu::BindGroup,
1724
width: u32,
1825
height: u32,
26+
_user_data: PhantomData<T>,
1927
}
2028

21-
impl ShaderCanvasState {
22-
pub fn new<'a, S: Into<wgpu::ShaderModuleDescriptor<'a>>>(
23-
shader: S,
24-
entry_point: Option<&str>,
25-
) -> Self {
26-
Self::new_inner(shader.into(), entry_point).block_on()
27-
}
28-
}
29-
30-
impl ShaderCanvasState {
31-
pub fn execute(&mut self, ctx: ShaderContext) -> Vec<Pixel> {
32-
self.execute_inner(ctx).block_on()
29+
impl<T> WgpuBackend<T>
30+
where
31+
T: Copy + Clone + Default + bytemuck::Pod + bytemuck::Zeroable,
32+
{
33+
pub fn new(path_to_fragment_shader: &str, entry_point: &str) -> Self {
34+
Self::new_inner(path_to_fragment_shader, entry_point).block_on()
3335
}
3436

35-
async fn create_device_and_queue() -> (wgpu::Device, wgpu::Queue) {
37+
async fn get_device_and_queue() -> (wgpu::Device, wgpu::Queue) {
3638
let instance = wgpu::Instance::default();
3739

3840
let adapter = instance
@@ -85,51 +87,19 @@ impl ShaderCanvasState {
8587
})
8688
}
8789

88-
fn create_pipeline(
89-
device: &wgpu::Device,
90-
pipeline_layout: &wgpu::PipelineLayout,
91-
vertex_shader: &wgpu::ShaderModule,
92-
fragment_shader: &wgpu::ShaderModule,
93-
entry_point: Option<&str>,
94-
) -> wgpu::RenderPipeline {
95-
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
96-
label: None,
97-
layout: Some(&pipeline_layout),
98-
vertex: wgpu::VertexState {
99-
module: vertex_shader,
100-
entry_point: None,
101-
buffers: &[],
102-
compilation_options: wgpu::PipelineCompilationOptions::default(),
103-
},
104-
fragment: Some(wgpu::FragmentState {
105-
module: fragment_shader,
106-
entry_point,
107-
compilation_options: wgpu::PipelineCompilationOptions::default(),
108-
targets: &[Some(wgpu::ColorTargetState {
109-
format: wgpu::TextureFormat::Rgba8Unorm,
110-
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
111-
write_mask: wgpu::ColorWrites::ALL,
112-
})],
113-
}),
114-
primitive: wgpu::PrimitiveState::default(),
115-
depth_stencil: None,
116-
multisample: wgpu::MultisampleState::default(),
117-
multiview: None,
118-
cache: None,
119-
});
120-
pipeline
121-
}
122-
123-
async fn new_inner<'a>(
124-
desc: wgpu::ShaderModuleDescriptor<'a>,
125-
entry_point: Option<&str>,
126-
) -> Self {
127-
let (device, queue) = Self::create_device_and_queue().await;
90+
async fn new_inner(path_to_fragment_shader: &str, entry_point: &str) -> Self {
91+
let (device, queue) = Self::get_device_and_queue().await;
12892

12993
let vertex_shader =
130-
device.create_shader_module(wgpu::include_wgsl!("shaders/fullscreen_vertex.wgsl"));
94+
device.create_shader_module(wgpu::include_wgsl!("../shaders/fullscreen_vertex.wgsl"));
95+
96+
let fragment_shader_source =
97+
fs::read_to_string(path_to_fragment_shader).expect("Unable to read fragment shader");
13198

132-
let fragment_shader = device.create_shader_module(desc);
99+
let fragment_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
100+
label: None,
101+
source: wgpu::ShaderSource::Wgsl(fragment_shader_source.into()),
102+
});
133103

134104
let texture = Self::create_texture(&device, DEFAULT_SIZE, DEFAULT_SIZE);
135105
let output_buffer = Self::create_buffer(&device, DEFAULT_SIZE, DEFAULT_SIZE);
@@ -142,7 +112,7 @@ impl ShaderCanvasState {
142112

143113
let user_data_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
144114
label: None,
145-
contents: bytemuck::cast_slice(&[0.0]),
115+
contents: bytemuck::cast_slice(&[T::default()]),
146116
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
147117
});
148118

@@ -201,15 +171,33 @@ impl ShaderCanvasState {
201171
push_constant_ranges: &[],
202172
});
203173

204-
let pipeline = Self::create_pipeline(
205-
&device,
206-
&pipeline_layout,
207-
&vertex_shader,
208-
&fragment_shader,
209-
entry_point,
210-
);
174+
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
175+
label: None,
176+
layout: Some(&pipeline_layout),
177+
vertex: wgpu::VertexState {
178+
module: &vertex_shader,
179+
entry_point: Some("main"),
180+
buffers: &[],
181+
compilation_options: wgpu::PipelineCompilationOptions::default(),
182+
},
183+
fragment: Some(wgpu::FragmentState {
184+
module: &fragment_shader,
185+
entry_point: Some(entry_point),
186+
compilation_options: wgpu::PipelineCompilationOptions::default(),
187+
targets: &[Some(wgpu::ColorTargetState {
188+
format: wgpu::TextureFormat::Rgba8Unorm,
189+
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
190+
write_mask: wgpu::ColorWrites::ALL,
191+
})],
192+
}),
193+
primitive: wgpu::PrimitiveState::default(),
194+
depth_stencil: None,
195+
multisample: wgpu::MultisampleState::default(),
196+
multiview: None,
197+
cache: None,
198+
});
211199

212-
Self {
200+
WgpuBackend {
213201
device,
214202
queue,
215203
pipeline,
@@ -219,11 +207,11 @@ impl ShaderCanvasState {
219207
bind_group,
220208
width: DEFAULT_SIZE.into(),
221209
height: DEFAULT_SIZE.into(),
210+
_user_data: PhantomData,
222211
}
223212
}
224213

225-
async fn execute_inner(&mut self, ctx: ShaderContext) -> Vec<Pixel> {
226-
// TODO: handle user_data;
214+
async fn execute_inner(&mut self, ctx: ShaderContext, _user_data: &T) -> Vec<Pixel> {
227215
let width = ctx.resolution[0];
228216
let height = ctx.resolution[1];
229217
if bytes_per_row(width) != bytes_per_row(self.width) || height != self.height {
@@ -313,12 +301,18 @@ impl ShaderCanvasState {
313301
}
314302
}
315303

316-
impl Default for ShaderCanvasState {
304+
impl<T> TuiShaderBackend<T> for WgpuBackend<T>
305+
where
306+
T: Copy + Clone + Default + bytemuck::Pod + bytemuck::Zeroable,
307+
{
308+
fn execute(&mut self, ctx: ShaderContext, user_data: &T) -> Vec<Pixel> {
309+
self.execute_inner(ctx, user_data).block_on()
310+
}
311+
}
312+
313+
impl Default for WgpuBackend<NoUserData> {
317314
fn default() -> Self {
318-
Self::new(
319-
wgpu::include_wgsl!("shaders/default_fragment.wgsl"),
320-
Some("magenta"),
321-
)
315+
Self::new("src/shaders/default_fragment.wgsl", "magenta")
322316
}
323317
}
324318

@@ -332,26 +326,3 @@ fn row_padding(width: u32) -> u32 {
332326
let bytes_per_row = bytes_per_row(width);
333327
(bytes_per_row - row_size) / 4
334328
}
335-
336-
pub enum WgslShader<'a> {
337-
Source(&'a str),
338-
Path(&'a str),
339-
}
340-
341-
impl<'a> From<WgslShader<'a>> for wgpu::ShaderModuleDescriptor<'a> {
342-
fn from(value: WgslShader<'a>) -> Self {
343-
match value {
344-
WgslShader::Source(source) => wgpu::ShaderModuleDescriptor {
345-
label: None,
346-
source: wgpu::ShaderSource::Wgsl(source.into()),
347-
},
348-
WgslShader::Path(path) => {
349-
let source = std::fs::read_to_string(path).expect("unable to read file");
350-
wgpu::ShaderModuleDescriptor {
351-
label: None,
352-
source: wgpu::ShaderSource::Wgsl(source.into()),
353-
}
354-
}
355-
}
356-
}
357-
}

0 commit comments

Comments
 (0)