pixel_widgets/backend/
wgpu.rs

1use std::collections::HashMap;
2use std::ops::{Deref, DerefMut};
3
4use zerocopy::AsBytes;
5
6use wgpu::*;
7
8use crate::draw::{Command as DrawCommand, DrawList, Update, Vertex};
9use crate::layout::Rectangle;
10use crate::style::Style;
11use crate::Component;
12use std::num::NonZeroU32;
13use wgpu::util::DeviceExt;
14
15/// Wrapper for [`Ui`](../../struct.Ui.html) that adds wgpu rendering.
16/// Requires the "wgpu" feature.
17pub struct Ui<C: 'static + Component> {
18    inner: crate::Ui<C>,
19    pipeline: RenderPipeline,
20    bind_group_layout: BindGroupLayout,
21    sampler: Sampler,
22    textures: HashMap<usize, TextureEntry>,
23    vertex_buffer: Option<Buffer>,
24    draw_commands: Vec<DrawCommand>,
25}
26
27struct TextureEntry {
28    texture: Texture,
29    bind_group: BindGroup,
30}
31
32impl<C: Component> Ui<C> {
33    /// Constructs a new `Ui`. Returns an error if the style fails to load.
34    pub fn new<S, E>(
35        root_component: C,
36        viewport: Rectangle,
37        hidpi_scale: f32,
38        style: S,
39        format: wgpu::TextureFormat,
40        device: &Device,
41    ) -> anyhow::Result<Self>
42    where
43        S: TryInto<Style, Error = E>,
44        anyhow::Error: From<E>,
45    {
46        Ok(Self::new_inner(
47            crate::Ui::new(root_component, viewport, hidpi_scale, style)?,
48            format,
49            device,
50        ))
51    }
52
53    fn new_inner(inner: crate::Ui<C>, format: wgpu::TextureFormat, device: &Device) -> Self {
54        let shader_module = device.create_shader_module(&ShaderModuleDescriptor {
55            label: Some("wgpu.wgsl"),
56            source: wgpu::ShaderSource::Wgsl(include_str!("wgpu.wgsl").into()),
57        });
58        let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
59            label: None,
60            entries: &[
61                wgpu::BindGroupLayoutEntry {
62                    binding: 0,
63                    visibility: wgpu::ShaderStages::FRAGMENT,
64                    ty: wgpu::BindingType::Texture {
65                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
66                        multisampled: false,
67                        view_dimension: wgpu::TextureViewDimension::D2,
68                    },
69                    count: None,
70                },
71                wgpu::BindGroupLayoutEntry {
72                    binding: 1,
73                    visibility: wgpu::ShaderStages::FRAGMENT,
74                    ty: wgpu::BindingType::Sampler {
75                        filtering: true,
76                        comparison: false,
77                    },
78                    count: None,
79                },
80            ],
81        });
82        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
83            label: None,
84            bind_group_layouts: &[&bind_group_layout],
85            push_constant_ranges: &[],
86        });
87        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
88            label: None,
89            layout: Some(&pipeline_layout),
90            vertex: wgpu::VertexState {
91                module: &shader_module,
92                entry_point: "vs_main",
93                buffers: &[wgpu::VertexBufferLayout {
94                    array_stride: std::mem::size_of::<Vertex>() as u64,
95                    step_mode: wgpu::VertexStepMode::Vertex,
96                    attributes: &[
97                        wgpu::VertexAttribute {
98                            format: VertexFormat::Float32x2,
99                            offset: 0,
100                            shader_location: 0,
101                        },
102                        wgpu::VertexAttribute {
103                            format: VertexFormat::Float32x2,
104                            offset: 8,
105                            shader_location: 1,
106                        },
107                        wgpu::VertexAttribute {
108                            format: VertexFormat::Float32x4,
109                            offset: 16,
110                            shader_location: 2,
111                        },
112                        wgpu::VertexAttribute {
113                            format: VertexFormat::Float32,
114                            offset: 32,
115                            shader_location: 3,
116                        },
117                    ],
118                }],
119            },
120            primitive: wgpu::PrimitiveState {
121                topology: PrimitiveTopology::TriangleList,
122                ..wgpu::PrimitiveState::default()
123            },
124            depth_stencil: None,
125            multisample: Default::default(),
126            fragment: Some(wgpu::FragmentState {
127                module: &shader_module,
128                entry_point: "fs_main",
129                targets: &[wgpu::ColorTargetState {
130                    format,
131                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
132                    write_mask: wgpu::ColorWrites::ALL,
133                }],
134            }),
135        });
136
137        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
138            label: None,
139            address_mode_u: wgpu::AddressMode::ClampToEdge,
140            address_mode_v: wgpu::AddressMode::ClampToEdge,
141            address_mode_w: wgpu::AddressMode::ClampToEdge,
142            mag_filter: wgpu::FilterMode::Nearest,
143            min_filter: wgpu::FilterMode::Linear,
144            mipmap_filter: wgpu::FilterMode::Linear,
145            lod_min_clamp: 0.0,
146            lod_max_clamp: 0.0,
147            compare: None,
148            anisotropy_clamp: None,
149            border_color: None,
150        });
151
152        Self {
153            inner,
154            pipeline,
155            bind_group_layout,
156            sampler,
157            textures: HashMap::new(),
158            vertex_buffer: None,
159            draw_commands: Vec::new(),
160        }
161    }
162
163    /// Draw the ui to a `RenderPass`.
164    /// The `device` must be the same as the one passed to [`new()`](#method.new).
165    /// The `render_pass` render target must be compatible with the `texture_format` passed to [`new`](#method.new).
166    pub fn draw<'a>(&'a mut self, device: &Device, queue: &Queue, render_pass: &mut RenderPass<'a>) {
167        if self.inner.needs_redraw() {
168            let DrawList {
169                updates,
170                vertices,
171                commands,
172            } = self.inner.draw();
173
174            self.vertex_buffer.take();
175            self.draw_commands = commands;
176
177            if !updates.is_empty() {
178                let cmd = device.create_command_encoder(&CommandEncoderDescriptor { label: None });
179                queue.submit(Some(
180                    updates
181                        .into_iter()
182                        .fold(cmd, |mut cmd, update| {
183                            match update {
184                                Update::Texture {
185                                    id,
186                                    size,
187                                    data,
188                                    atlas: _,
189                                } => {
190                                    let texture_desc = wgpu::TextureDescriptor {
191                                        label: None,
192                                        size: wgpu::Extent3d {
193                                            width: size[0],
194                                            height: size[1],
195                                            depth_or_array_layers: 1,
196                                        },
197                                        mip_level_count: 1,
198                                        sample_count: 1,
199                                        dimension: wgpu::TextureDimension::D2,
200                                        format: wgpu::TextureFormat::Rgba8Unorm,
201                                        usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
202                                    };
203                                    let texture = if data.is_empty() {
204                                        device.create_texture(&texture_desc)
205                                    } else {
206                                        device.create_texture_with_data(queue, &texture_desc, data.as_slice())
207                                    };
208
209                                    let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
210
211                                    let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
212                                        layout: &self.bind_group_layout,
213                                        entries: &[
214                                            wgpu::BindGroupEntry {
215                                                binding: 0,
216                                                resource: wgpu::BindingResource::TextureView(&view),
217                                            },
218                                            wgpu::BindGroupEntry {
219                                                binding: 1,
220                                                resource: wgpu::BindingResource::Sampler(&self.sampler),
221                                            },
222                                        ],
223                                        label: None,
224                                    });
225
226                                    self.textures.insert(id, TextureEntry { bind_group, texture });
227                                }
228                                Update::TextureSubresource { id, offset, size, data } => {
229                                    let texture = self
230                                        .textures
231                                        .get(&id)
232                                        .map(|val| &val.texture)
233                                        .expect("non existing texture is updated");
234
235                                    let padding = 256 - (size[0] * 4) % 256;
236                                    let data = if padding > 0 {
237                                        data.chunks(size[0] as usize * 4).fold(Vec::new(), |mut data, row| {
238                                            data.extend_from_slice(row);
239                                            data.extend(std::iter::repeat(0).take(padding as _));
240                                            data
241                                        })
242                                    } else {
243                                        data
244                                    };
245                                    let staging = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
246                                        label: None,
247                                        contents: data.as_slice(),
248                                        usage: wgpu::BufferUsages::COPY_SRC,
249                                    });
250                                    cmd.copy_buffer_to_texture(
251                                        wgpu::ImageCopyBuffer {
252                                            buffer: &staging,
253                                            layout: wgpu::ImageDataLayout {
254                                                offset: 0,
255                                                bytes_per_row: NonZeroU32::new(size[0] * 4 + padding),
256                                                rows_per_image: None,
257                                            },
258                                        },
259                                        wgpu::ImageCopyTexture {
260                                            texture,
261                                            mip_level: 0,
262                                            origin: wgpu::Origin3d {
263                                                x: offset[0],
264                                                y: offset[1],
265                                                z: 0,
266                                            },
267                                            aspect: wgpu::TextureAspect::All,
268                                        },
269                                        wgpu::Extent3d {
270                                            width: size[0],
271                                            height: size[1],
272                                            depth_or_array_layers: 1,
273                                        },
274                                    );
275                                }
276                            }
277                            cmd
278                        })
279                        .finish(),
280                ));
281            }
282
283            if !vertices.is_empty() {
284                self.vertex_buffer
285                    .replace(device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
286                        label: None,
287                        contents: vertices.as_bytes(),
288                        usage: wgpu::BufferUsages::VERTEX,
289                    }));
290            }
291        }
292
293        if let Some(vertex_buffer) = self.vertex_buffer.as_ref() {
294            render_pass.set_pipeline(&self.pipeline);
295            render_pass.set_bind_group(0, &self.textures.values().next().unwrap().bind_group, &[]);
296            render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
297        }
298
299        for command in self.draw_commands.iter() {
300            match command {
301                DrawCommand::Clip { scissor } => {
302                    render_pass.set_scissor_rect(
303                        scissor.left as u32,
304                        scissor.top as u32,
305                        scissor.width() as u32,
306                        scissor.height() as u32,
307                    );
308                }
309                &DrawCommand::Colored { offset, count } => {
310                    render_pass.draw(offset as u32..(offset + count) as u32, 0..1);
311                }
312                &DrawCommand::Textured { texture, offset, count } => {
313                    render_pass.set_bind_group(0, &self.textures.get(&texture).unwrap().bind_group, &[]);
314                    render_pass.draw(offset as u32..(offset + count) as u32, 0..1);
315                }
316                DrawCommand::Nop => (),
317            }
318        }
319    }
320}
321
322impl<C: Component> Deref for Ui<C> {
323    type Target = crate::Ui<C>;
324
325    fn deref(&self) -> &Self::Target {
326        &self.inner
327    }
328}
329
330impl<C: Component> DerefMut for Ui<C> {
331    fn deref_mut(&mut self) -> &mut Self::Target {
332        &mut self.inner
333    }
334}