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