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