tessera_ui_basic_components/pipelines/simple_rect/
pipeline.rs

1use encase::{ShaderSize, ShaderType, StorageBuffer};
2use glam::{Vec2, Vec4};
3use tessera_ui::{
4    PxSize,
5    px::PxPosition,
6    renderer::drawer::pipeline::{DrawContext, DrawablePipeline},
7    wgpu::{self, include_wgsl, util::DeviceExt},
8};
9
10use super::command::SimpleRectCommand;
11
12#[repr(C)]
13#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
14struct Vertex {
15    position: [f32; 2],
16}
17
18#[derive(ShaderType, Clone, Copy, Debug, PartialEq)]
19struct RectUniform {
20    position: Vec4,
21    color: Vec4,
22    screen_size: Vec2,
23    #[shader(size(8))]
24    _padding: [f32; 2],
25}
26
27#[derive(ShaderType)]
28struct RectInstances {
29    #[shader(size(runtime))]
30    instances: Vec<RectUniform>,
31}
32
33pub struct SimpleRectPipeline {
34    pipeline: wgpu::RenderPipeline,
35    bind_group_layout: wgpu::BindGroupLayout,
36    quad_vertex_buffer: wgpu::Buffer,
37    quad_index_buffer: wgpu::Buffer,
38}
39
40impl SimpleRectPipeline {
41    pub fn new(
42        gpu: &wgpu::Device,
43        config: &wgpu::SurfaceConfiguration,
44        pipeline_cache: Option<&wgpu::PipelineCache>,
45        sample_count: u32,
46    ) -> Self {
47        let shader = gpu.create_shader_module(include_wgsl!("simple_rect.wgsl"));
48
49        let bind_group_layout = gpu.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
50            entries: &[wgpu::BindGroupLayoutEntry {
51                binding: 0,
52                visibility: wgpu::ShaderStages::VERTEX,
53                ty: wgpu::BindingType::Buffer {
54                    ty: wgpu::BufferBindingType::Storage { read_only: true },
55                    has_dynamic_offset: false,
56                    min_binding_size: None,
57                },
58                count: None,
59            }],
60            label: Some("simple_rect_bind_group_layout"),
61        });
62
63        let pipeline_layout = gpu.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
64            label: Some("Simple Rect Pipeline Layout"),
65            bind_group_layouts: &[&bind_group_layout],
66            push_constant_ranges: &[],
67        });
68
69        let pipeline = gpu.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
70            label: Some("Simple Rect Pipeline"),
71            layout: Some(&pipeline_layout),
72            vertex: wgpu::VertexState {
73                module: &shader,
74                entry_point: Some("vs_main"),
75                buffers: &[wgpu::VertexBufferLayout {
76                    array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
77                    step_mode: wgpu::VertexStepMode::Vertex,
78                    attributes: &wgpu::vertex_attr_array![0 => Float32x2],
79                }],
80                compilation_options: Default::default(),
81            },
82            primitive: wgpu::PrimitiveState {
83                topology: wgpu::PrimitiveTopology::TriangleList,
84                strip_index_format: None,
85                front_face: wgpu::FrontFace::Ccw,
86                cull_mode: Some(wgpu::Face::Back),
87                unclipped_depth: false,
88                polygon_mode: wgpu::PolygonMode::Fill,
89                conservative: false,
90            },
91            depth_stencil: None,
92            multisample: wgpu::MultisampleState {
93                count: sample_count,
94                mask: !0,
95                alpha_to_coverage_enabled: false,
96            },
97            fragment: Some(wgpu::FragmentState {
98                module: &shader,
99                entry_point: Some("fs_main"),
100                compilation_options: Default::default(),
101                targets: &[Some(wgpu::ColorTargetState {
102                    format: config.format,
103                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
104                    write_mask: wgpu::ColorWrites::ALL,
105                })],
106            }),
107            multiview: None,
108            cache: pipeline_cache,
109        });
110
111        let quad_vertices = [
112            Vertex {
113                position: [0.0, 0.0],
114            },
115            Vertex {
116                position: [1.0, 0.0],
117            },
118            Vertex {
119                position: [1.0, 1.0],
120            },
121            Vertex {
122                position: [0.0, 1.0],
123            },
124        ];
125        let quad_vertex_buffer = gpu.create_buffer_init(&wgpu::util::BufferInitDescriptor {
126            label: Some("Simple Rect Quad Vertex Buffer"),
127            contents: bytemuck::cast_slice(&quad_vertices),
128            usage: wgpu::BufferUsages::VERTEX,
129        });
130
131        let quad_indices: [u16; 6] = [0, 2, 1, 0, 3, 2];
132        let quad_index_buffer = gpu.create_buffer_init(&wgpu::util::BufferInitDescriptor {
133            label: Some("Simple Rect Quad Index Buffer"),
134            contents: bytemuck::cast_slice(&quad_indices),
135            usage: wgpu::BufferUsages::INDEX,
136        });
137
138        Self {
139            pipeline,
140            bind_group_layout,
141            quad_vertex_buffer,
142            quad_index_buffer,
143        }
144    }
145}
146
147fn build_instances(
148    commands: &[(&SimpleRectCommand, PxSize, PxPosition)],
149    config: &wgpu::SurfaceConfiguration,
150) -> Vec<RectUniform> {
151    commands
152        .iter()
153        .map(|(command, size, position)| RectUniform {
154            position: Vec4::new(
155                position.x.raw() as f32,
156                position.y.raw() as f32,
157                size.width.raw() as f32,
158                size.height.raw() as f32,
159            ),
160            color: Vec4::from_array(command.color.to_array()),
161            screen_size: Vec2::new(config.width as f32, config.height as f32),
162            _padding: [0.0; 2],
163        })
164        .collect()
165}
166
167impl DrawablePipeline<SimpleRectCommand> for SimpleRectPipeline {
168    fn draw(&mut self, context: &mut DrawContext<SimpleRectCommand>) {
169        if context.commands.is_empty() {
170            return;
171        }
172
173        let instances = build_instances(context.commands, context.config);
174        if instances.is_empty() {
175            return;
176        }
177
178        let uniform_buffer = context.device.create_buffer(&wgpu::BufferDescriptor {
179            label: Some("Simple Rect Storage Buffer"),
180            size: 16 + RectUniform::SHADER_SIZE.get() * instances.len() as u64,
181            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
182            mapped_at_creation: false,
183        });
184
185        let uniforms = RectInstances { instances };
186        let mut buffer_content = StorageBuffer::new(Vec::<u8>::new());
187        buffer_content.write(&uniforms).unwrap();
188        context
189            .queue
190            .write_buffer(&uniform_buffer, 0, buffer_content.as_ref());
191
192        let bind_group = context
193            .device
194            .create_bind_group(&wgpu::BindGroupDescriptor {
195                layout: &self.bind_group_layout,
196                entries: &[wgpu::BindGroupEntry {
197                    binding: 0,
198                    resource: uniform_buffer.as_entire_binding(),
199                }],
200                label: Some("simple_rect_bind_group"),
201            });
202
203        context.render_pass.set_pipeline(&self.pipeline);
204        context.render_pass.set_bind_group(0, &bind_group, &[]);
205        context
206            .render_pass
207            .set_vertex_buffer(0, self.quad_vertex_buffer.slice(..));
208        context
209            .render_pass
210            .set_index_buffer(self.quad_index_buffer.slice(..), wgpu::IndexFormat::Uint16);
211        context
212            .render_pass
213            .draw_indexed(0..6, 0, 0..context.commands.len() as u32);
214    }
215}