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