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