tessera_ui_basic_components/pipelines/
shape.rs1mod command;
20
21use encase::{ShaderSize, ShaderType, StorageBuffer};
22use glam::{Vec2, Vec4};
23use tessera_ui::{
24 PxPosition, PxSize,
25 px::PxRect,
26 renderer::DrawablePipeline,
27 wgpu::{self, include_wgsl},
28};
29
30use self::command::rect_to_uniforms;
31
32pub use command::{RippleProps, ShadowProps, ShapeCommand};
33
34#[derive(ShaderType, Clone, Copy, Debug, PartialEq)]
47pub struct ShapeUniforms {
48 pub corner_radii: Vec4, pub primary_color: Vec4,
50 pub border_color: Vec4,
51 pub shadow_color: Vec4,
52 pub render_params: Vec4,
53 pub ripple_params: Vec4,
54 pub ripple_color: Vec4,
55 pub g2_k_value: f32,
56 pub border_width: f32, pub position: Vec4, pub screen_size: Vec2,
59}
60
61#[derive(ShaderType)]
62struct ShapeInstances {
63 #[shader(size(runtime))]
64 instances: Vec<ShapeUniforms>,
65}
66
67pub const MAX_CONCURRENT_SHAPES: wgpu::BufferAddress = 1024;
69
70pub struct ShapePipeline {
80 pipeline: wgpu::RenderPipeline,
81 bind_group_layout: wgpu::BindGroupLayout,
82}
83
84impl ShapePipeline {
85 pub fn new(gpu: &wgpu::Device, config: &wgpu::SurfaceConfiguration, sample_count: u32) -> Self {
86 let shader = gpu.create_shader_module(include_wgsl!("shape/shape.wgsl"));
87
88 let bind_group_layout = gpu.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
89 entries: &[wgpu::BindGroupLayoutEntry {
90 binding: 0,
91 visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
92 ty: wgpu::BindingType::Buffer {
93 ty: wgpu::BufferBindingType::Storage { read_only: true },
94 has_dynamic_offset: false,
95 min_binding_size: None,
96 },
97 count: None,
98 }],
99 label: Some("shape_bind_group_layout"),
100 });
101
102 let pipeline_layout = gpu.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
103 label: Some("Shape Pipeline Layout"),
104 bind_group_layouts: &[&bind_group_layout],
105 push_constant_ranges: &[],
106 });
107
108 let pipeline = gpu.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
109 label: Some("Shape Pipeline"),
110 layout: Some(&pipeline_layout),
111 vertex: wgpu::VertexState {
112 module: &shader,
113 entry_point: Some("vs_main"),
114 buffers: &[],
115 compilation_options: Default::default(),
116 },
117 primitive: wgpu::PrimitiveState {
118 topology: wgpu::PrimitiveTopology::TriangleList,
119 strip_index_format: None,
120 front_face: wgpu::FrontFace::Ccw,
121 cull_mode: Some(wgpu::Face::Back),
122 unclipped_depth: false,
123 polygon_mode: wgpu::PolygonMode::Fill,
124 conservative: false,
125 },
126 depth_stencil: None,
127 multisample: wgpu::MultisampleState {
128 count: sample_count,
129 mask: !0,
130 alpha_to_coverage_enabled: false,
131 },
132 fragment: Some(wgpu::FragmentState {
133 module: &shader,
134 entry_point: Some("fs_main"),
135 compilation_options: Default::default(),
136 targets: &[Some(wgpu::ColorTargetState {
137 format: config.format,
138 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
139 write_mask: wgpu::ColorWrites::ALL,
140 })],
141 }),
142 multiview: None,
143 cache: None,
144 });
145
146 Self {
147 pipeline,
148 bind_group_layout,
149 }
150 }
151}
152
153fn build_instances(
154 commands: &[(&ShapeCommand, PxSize, PxPosition)],
155 config: &wgpu::SurfaceConfiguration,
156) -> Vec<ShapeUniforms> {
157 commands
159 .iter()
160 .flat_map(|(command, size, start_pos)| {
161 let mut uniforms = rect_to_uniforms(command, *size, *start_pos);
162 uniforms.screen_size = [config.width as f32, config.height as f32].into();
163
164 let has_shadow = uniforms.shadow_color[3] > 0.0 && uniforms.render_params[2] > 0.0;
165
166 if has_shadow {
167 let mut uniforms_for_shadow = uniforms;
168 uniforms_for_shadow.render_params[3] = 2.0;
169 vec![uniforms_for_shadow, uniforms]
170 } else {
171 vec![uniforms]
172 }
173 })
174 .collect()
175}
176
177impl DrawablePipeline<ShapeCommand> for ShapePipeline {
178 fn draw(
179 &mut self,
180 gpu: &wgpu::Device,
181 gpu_queue: &wgpu::Queue,
182 config: &wgpu::SurfaceConfiguration,
183 render_pass: &mut wgpu::RenderPass<'_>,
184 commands: &[(&ShapeCommand, PxSize, PxPosition)],
185 _scene_texture_view: &wgpu::TextureView,
186 _clip_rect: Option<PxRect>,
187 ) {
188 if commands.is_empty() {
189 return;
190 }
191
192 let mut instances = build_instances(commands, config);
193
194 if instances.len() > MAX_CONCURRENT_SHAPES as usize {
195 instances.truncate(MAX_CONCURRENT_SHAPES as usize);
197 }
198
199 if instances.is_empty() {
200 return;
201 }
202
203 let uniform_buffer = gpu.create_buffer(&wgpu::BufferDescriptor {
204 label: Some("Shape Storage Buffer"),
205 size: 16 + ShapeUniforms::SHADER_SIZE.get() * instances.len() as u64,
206 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
207 mapped_at_creation: false,
208 });
209
210 let uniforms = ShapeInstances { instances };
211 let instance_count = uniforms.instances.len();
212
213 let mut buffer_content = StorageBuffer::new(Vec::<u8>::new());
214 buffer_content.write(&uniforms).unwrap();
215 gpu_queue.write_buffer(&uniform_buffer, 0, buffer_content.as_ref());
216
217 let bind_group = gpu.create_bind_group(&wgpu::BindGroupDescriptor {
218 layout: &self.bind_group_layout,
219 entries: &[wgpu::BindGroupEntry {
220 binding: 0,
221 resource: uniform_buffer.as_entire_binding(),
222 }],
223 label: Some("shape_bind_group"),
224 });
225
226 render_pass.set_pipeline(&self.pipeline);
227 render_pass.set_bind_group(0, &bind_group, &[]);
228 render_pass.draw(0..6, 0..instance_count as u32);
229 }
230}