1use std::{
2 collections::HashMap,
3 hash::{Hash, Hasher},
4 sync::Arc,
5};
6
7use encase::{ShaderType, UniformBuffer};
8use glam::Vec4;
9use tessera_ui::{
10 DrawCommand, PxPosition, PxSize, px::PxRect, renderer::drawer::DrawablePipeline, wgpu,
11};
12
13#[derive(Debug, Clone)]
14pub struct ImageData {
27 pub data: Arc<Vec<u8>>,
28 pub width: u32,
29 pub height: u32,
30}
31
32impl Hash for ImageData {
33 fn hash<H: Hasher>(&self, state: &mut H) {
34 self.data.as_ref().hash(state);
35 self.width.hash(state);
36 self.height.hash(state);
37 }
38}
39
40impl PartialEq for ImageData {
41 fn eq(&self, other: &Self) -> bool {
42 self.width == other.width
43 && self.height == other.height
44 && self.data.as_ref() == other.data.as_ref()
45 }
46}
47
48impl Eq for ImageData {}
49
50#[derive(Debug, Clone, Hash, PartialEq, Eq)]
51pub struct ImageCommand {
59 pub data: Arc<ImageData>,
60}
61
62impl DrawCommand for ImageCommand {
63 fn barrier(&self) -> Option<tessera_ui::BarrierRequirement> {
64 None
66 }
67}
68
69#[derive(ShaderType)]
70struct ImageUniforms {
71 rect: Vec4,
72 is_bgra: u32,
73}
74
75struct ImageResources {
76 bind_group: wgpu::BindGroup,
77 uniform_buffer: wgpu::Buffer,
78}
79
80pub struct ImagePipeline {
88 pipeline: wgpu::RenderPipeline,
89 bind_group_layout: wgpu::BindGroupLayout,
90 resources: HashMap<ImageData, ImageResources>,
91}
92
93impl ImagePipeline {
94 pub fn new(
96 device: &wgpu::Device,
97 config: &wgpu::SurfaceConfiguration,
98 sample_count: u32,
99 ) -> Self {
100 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
101 label: Some("Image Shader"),
102 source: wgpu::ShaderSource::Wgsl(include_str!("image/image.wgsl").into()),
103 });
104
105 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
106 entries: &[
107 wgpu::BindGroupLayoutEntry {
108 binding: 0,
109 visibility: wgpu::ShaderStages::FRAGMENT,
110 ty: wgpu::BindingType::Texture {
111 multisampled: false,
112 view_dimension: wgpu::TextureViewDimension::D2,
113 sample_type: wgpu::TextureSampleType::Float { filterable: true },
114 },
115 count: None,
116 },
117 wgpu::BindGroupLayoutEntry {
118 binding: 1,
119 visibility: wgpu::ShaderStages::FRAGMENT,
120 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
121 count: None,
122 },
123 wgpu::BindGroupLayoutEntry {
124 binding: 2,
125 visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
126 ty: wgpu::BindingType::Buffer {
127 ty: wgpu::BufferBindingType::Uniform,
128 has_dynamic_offset: false,
129 min_binding_size: None,
130 },
131 count: None,
132 },
133 ],
134 label: Some("texture_bind_group_layout"),
135 });
136
137 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
138 label: Some("Image Pipeline Layout"),
139 bind_group_layouts: &[&bind_group_layout],
140 push_constant_ranges: &[],
141 });
142
143 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
144 label: Some("Image Render Pipeline"),
145 layout: Some(&pipeline_layout),
146 vertex: wgpu::VertexState {
147 module: &shader,
148 entry_point: Some("vs_main"),
149 buffers: &[],
150 compilation_options: Default::default(),
151 },
152 fragment: Some(wgpu::FragmentState {
153 module: &shader,
154 entry_point: Some("fs_main"),
155 targets: &[Some(wgpu::ColorTargetState {
156 format: config.format,
157 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
158 write_mask: wgpu::ColorWrites::ALL,
159 })],
160 compilation_options: Default::default(),
161 }),
162 primitive: wgpu::PrimitiveState::default(),
163 depth_stencil: None,
164 multisample: wgpu::MultisampleState {
165 count: sample_count,
166 mask: !0,
167 alpha_to_coverage_enabled: false,
168 },
169 multiview: None,
170 cache: None,
171 });
172
173 Self {
174 pipeline,
175 bind_group_layout,
176 resources: HashMap::new(),
177 }
178 }
179
180 fn get_or_create_resources(
182 &mut self,
183 device: &wgpu::Device,
184 queue: &wgpu::Queue,
185 config: &wgpu::SurfaceConfiguration,
186 data: &ImageData,
187 ) -> &ImageResources {
188 self.resources.entry(data.clone()).or_insert_with(|| {
189 Self::create_image_resources(device, queue, config, &self.bind_group_layout, data)
190 })
191 }
192
193 fn compute_uniforms(
195 start_pos: PxPosition,
196 size: PxSize,
197 config: &wgpu::SurfaceConfiguration,
198 ) -> ImageUniforms {
199 let rect = [
201 (start_pos.x.0 as f32 / config.width as f32) * 2.0 - 1.0
202 + (size.width.0 as f32 / config.width as f32),
203 (start_pos.y.0 as f32 / config.height as f32) * -2.0 + 1.0
204 - (size.height.0 as f32 / config.height as f32),
205 size.width.0 as f32 / config.width as f32,
206 size.height.0 as f32 / config.height as f32,
207 ]
208 .into();
209
210 let is_bgra = matches!(
211 config.format,
212 wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb
213 );
214
215 ImageUniforms {
216 rect,
217 is_bgra: if is_bgra { 1 } else { 0 },
218 }
219 }
220
221 fn create_image_resources(
224 device: &wgpu::Device,
225 queue: &wgpu::Queue,
226 config: &wgpu::SurfaceConfiguration,
227 layout: &wgpu::BindGroupLayout,
228 data: &ImageData,
229 ) -> ImageResources {
230 let texture_size = wgpu::Extent3d {
231 width: data.width,
232 height: data.height,
233 depth_or_array_layers: 1,
234 };
235 let diffuse_texture = device.create_texture(&wgpu::TextureDescriptor {
236 size: texture_size,
237 mip_level_count: 1,
238 sample_count: 1,
239 dimension: wgpu::TextureDimension::D2,
240 format: config.format,
241 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
242 label: Some("diffuse_texture"),
243 view_formats: &[],
244 });
245
246 queue.write_texture(
247 wgpu::TexelCopyTextureInfo {
248 texture: &diffuse_texture,
249 mip_level: 0,
250 origin: wgpu::Origin3d::ZERO,
251 aspect: wgpu::TextureAspect::All,
252 },
253 &data.data,
254 wgpu::TexelCopyBufferLayout {
255 offset: 0,
256 bytes_per_row: Some(4 * data.width),
257 rows_per_image: Some(data.height),
258 },
259 texture_size,
260 );
261
262 let diffuse_texture_view =
263 diffuse_texture.create_view(&wgpu::TextureViewDescriptor::default());
264 let diffuse_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
265 address_mode_u: wgpu::AddressMode::ClampToEdge,
266 address_mode_v: wgpu::AddressMode::ClampToEdge,
267 address_mode_w: wgpu::AddressMode::ClampToEdge,
268 mag_filter: wgpu::FilterMode::Linear,
269 min_filter: wgpu::FilterMode::Nearest,
270 mipmap_filter: wgpu::FilterMode::Nearest,
271 ..Default::default()
272 });
273
274 let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
275 label: Some("Image Uniform Buffer"),
276 size: ImageUniforms::min_size().get(),
277 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
278 mapped_at_creation: false,
279 });
280
281 let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
282 layout,
283 entries: &[
284 wgpu::BindGroupEntry {
285 binding: 0,
286 resource: wgpu::BindingResource::TextureView(&diffuse_texture_view),
287 },
288 wgpu::BindGroupEntry {
289 binding: 1,
290 resource: wgpu::BindingResource::Sampler(&diffuse_sampler),
291 },
292 wgpu::BindGroupEntry {
293 binding: 2,
294 resource: uniform_buffer.as_entire_binding(),
295 },
296 ],
297 label: Some("diffuse_bind_group"),
298 });
299
300 ImageResources {
301 bind_group: diffuse_bind_group,
302 uniform_buffer,
303 }
304 }
305}
306
307impl DrawablePipeline<ImageCommand> for ImagePipeline {
308 fn draw(
309 &mut self,
310 gpu: &wgpu::Device,
311 gpu_queue: &wgpu::Queue,
312 config: &wgpu::SurfaceConfiguration,
313 render_pass: &mut wgpu::RenderPass<'_>,
314 commands: &[(&ImageCommand, PxSize, PxPosition)],
315 _scene_texture_view: &wgpu::TextureView,
316 _clip_rect: Option<PxRect>,
317 ) {
318 render_pass.set_pipeline(&self.pipeline);
319
320 for (command, size, start_pos) in commands {
321 let resources = self.get_or_create_resources(gpu, gpu_queue, config, &command.data);
323
324 let uniforms = Self::compute_uniforms(*start_pos, *size, config);
326
327 let mut buffer = UniformBuffer::new(Vec::new());
328 buffer.write(&uniforms).unwrap();
329 gpu_queue.write_buffer(&resources.uniform_buffer, 0, &buffer.into_inner());
330
331 render_pass.set_bind_group(0, &resources.bind_group, &[]);
332 render_pass.draw(0..6, 0..1);
333 }
334 }
335}