tessera_ui_basic_components/pipelines/image/
pipeline.rs1use std::collections::HashMap;
2
3use encase::{ShaderType, UniformBuffer};
4use glam::Vec4;
5use tessera_ui::{
6 PxPosition, PxSize,
7 renderer::drawer::pipeline::{DrawContext, DrawablePipeline},
8 wgpu,
9};
10
11use super::command::{ImageCommand, ImageData};
12
13#[derive(ShaderType)]
14struct ImageUniforms {
15 rect: Vec4,
16 is_bgra: u32,
17}
18
19struct ImageResources {
20 bind_group: wgpu::BindGroup,
21 uniform_buffer: wgpu::Buffer,
22}
23
24pub struct ImagePipeline {
32 pipeline: wgpu::RenderPipeline,
33 bind_group_layout: wgpu::BindGroupLayout,
34 resources: HashMap<ImageData, ImageResources>,
35}
36
37impl ImagePipeline {
38 pub fn new(
40 device: &wgpu::Device,
41 config: &wgpu::SurfaceConfiguration,
42 pipeline_cache: Option<&wgpu::PipelineCache>,
43 sample_count: u32,
44 ) -> Self {
45 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
46 label: Some("Image Shader"),
47 source: wgpu::ShaderSource::Wgsl(include_str!("image.wgsl").into()),
48 });
49
50 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
51 entries: &[
52 wgpu::BindGroupLayoutEntry {
53 binding: 0,
54 visibility: wgpu::ShaderStages::FRAGMENT,
55 ty: wgpu::BindingType::Texture {
56 multisampled: false,
57 view_dimension: wgpu::TextureViewDimension::D2,
58 sample_type: wgpu::TextureSampleType::Float { filterable: true },
59 },
60 count: None,
61 },
62 wgpu::BindGroupLayoutEntry {
63 binding: 1,
64 visibility: wgpu::ShaderStages::FRAGMENT,
65 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
66 count: None,
67 },
68 wgpu::BindGroupLayoutEntry {
69 binding: 2,
70 visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
71 ty: wgpu::BindingType::Buffer {
72 ty: wgpu::BufferBindingType::Uniform,
73 has_dynamic_offset: false,
74 min_binding_size: None,
75 },
76 count: None,
77 },
78 ],
79 label: Some("texture_bind_group_layout"),
80 });
81
82 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
83 label: Some("Image Pipeline Layout"),
84 bind_group_layouts: &[&bind_group_layout],
85 push_constant_ranges: &[],
86 });
87
88 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
89 label: Some("Image Render Pipeline"),
90 layout: Some(&pipeline_layout),
91 vertex: wgpu::VertexState {
92 module: &shader,
93 entry_point: Some("vs_main"),
94 buffers: &[],
95 compilation_options: Default::default(),
96 },
97 fragment: Some(wgpu::FragmentState {
98 module: &shader,
99 entry_point: Some("fs_main"),
100 targets: &[Some(wgpu::ColorTargetState {
101 format: config.format,
102 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
103 write_mask: wgpu::ColorWrites::ALL,
104 })],
105 compilation_options: Default::default(),
106 }),
107 primitive: wgpu::PrimitiveState::default(),
108 depth_stencil: None,
109 multisample: wgpu::MultisampleState {
110 count: sample_count,
111 mask: !0,
112 alpha_to_coverage_enabled: false,
113 },
114 multiview: None,
115 cache: pipeline_cache,
116 });
117
118 Self {
119 pipeline,
120 bind_group_layout,
121 resources: HashMap::new(),
122 }
123 }
124
125 fn get_or_create_resources(
127 &mut self,
128 device: &wgpu::Device,
129 queue: &wgpu::Queue,
130 config: &wgpu::SurfaceConfiguration,
131 data: &ImageData,
132 ) -> &ImageResources {
133 self.resources.entry(data.clone()).or_insert_with(|| {
134 Self::create_image_resources(device, queue, config, &self.bind_group_layout, data)
135 })
136 }
137
138 fn compute_uniforms(
140 start_pos: PxPosition,
141 size: PxSize,
142 config: &wgpu::SurfaceConfiguration,
143 ) -> ImageUniforms {
144 let rect = [
146 (start_pos.x.0 as f32 / config.width as f32) * 2.0 - 1.0
147 + (size.width.0 as f32 / config.width as f32),
148 (start_pos.y.0 as f32 / config.height as f32) * -2.0 + 1.0
149 - (size.height.0 as f32 / config.height as f32),
150 size.width.0 as f32 / config.width as f32,
151 size.height.0 as f32 / config.height as f32,
152 ]
153 .into();
154
155 let is_bgra = matches!(
156 config.format,
157 wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb
158 );
159
160 ImageUniforms {
161 rect,
162 is_bgra: if is_bgra { 1 } else { 0 },
163 }
164 }
165
166 fn create_image_resources(
169 device: &wgpu::Device,
170 queue: &wgpu::Queue,
171 config: &wgpu::SurfaceConfiguration,
172 layout: &wgpu::BindGroupLayout,
173 data: &ImageData,
174 ) -> ImageResources {
175 let texture_size = wgpu::Extent3d {
176 width: data.width,
177 height: data.height,
178 depth_or_array_layers: 1,
179 };
180 let diffuse_texture = device.create_texture(&wgpu::TextureDescriptor {
181 size: texture_size,
182 mip_level_count: 1,
183 sample_count: 1,
184 dimension: wgpu::TextureDimension::D2,
185 format: config.format,
186 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
187 label: Some("diffuse_texture"),
188 view_formats: &[],
189 });
190
191 queue.write_texture(
192 wgpu::TexelCopyTextureInfo {
193 texture: &diffuse_texture,
194 mip_level: 0,
195 origin: wgpu::Origin3d::ZERO,
196 aspect: wgpu::TextureAspect::All,
197 },
198 &data.data,
199 wgpu::TexelCopyBufferLayout {
200 offset: 0,
201 bytes_per_row: Some(4 * data.width),
202 rows_per_image: Some(data.height),
203 },
204 texture_size,
205 );
206
207 let diffuse_texture_view =
208 diffuse_texture.create_view(&wgpu::TextureViewDescriptor::default());
209 let diffuse_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
210 address_mode_u: wgpu::AddressMode::ClampToEdge,
211 address_mode_v: wgpu::AddressMode::ClampToEdge,
212 address_mode_w: wgpu::AddressMode::ClampToEdge,
213 mag_filter: wgpu::FilterMode::Linear,
214 min_filter: wgpu::FilterMode::Nearest,
215 mipmap_filter: wgpu::FilterMode::Nearest,
216 ..Default::default()
217 });
218
219 let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
220 label: Some("Image Uniform Buffer"),
221 size: ImageUniforms::min_size().get(),
222 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
223 mapped_at_creation: false,
224 });
225
226 let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
227 layout,
228 entries: &[
229 wgpu::BindGroupEntry {
230 binding: 0,
231 resource: wgpu::BindingResource::TextureView(&diffuse_texture_view),
232 },
233 wgpu::BindGroupEntry {
234 binding: 1,
235 resource: wgpu::BindingResource::Sampler(&diffuse_sampler),
236 },
237 wgpu::BindGroupEntry {
238 binding: 2,
239 resource: uniform_buffer.as_entire_binding(),
240 },
241 ],
242 label: Some("diffuse_bind_group"),
243 });
244
245 ImageResources {
246 bind_group: diffuse_bind_group,
247 uniform_buffer,
248 }
249 }
250}
251
252impl DrawablePipeline<ImageCommand> for ImagePipeline {
253 fn draw(&mut self, context: &mut DrawContext<ImageCommand>) {
254 context.render_pass.set_pipeline(&self.pipeline);
255
256 for (command, size, start_pos) in context.commands.iter() {
257 let resources = self.get_or_create_resources(
259 context.device,
260 context.queue,
261 context.config,
262 &command.data,
263 );
264
265 let uniforms = Self::compute_uniforms(*start_pos, *size, context.config);
267
268 let mut buffer = UniformBuffer::new(Vec::new());
269 buffer.write(&uniforms).unwrap();
270 context
271 .queue
272 .write_buffer(&resources.uniform_buffer, 0, &buffer.into_inner());
273
274 context
275 .render_pass
276 .set_bind_group(0, &resources.bind_group, &[]);
277 context.render_pass.draw(0..6, 0..1);
278 }
279 }
280}