tessera_ui_basic_components/pipelines/
image.rs

1use 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        // This command does not require any specific barriers.
44        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}