hui_wgpu/
lib.rs

1use glam::{vec2, Vec2};
2use hui::{draw::{TextureAtlasMeta, UiDrawCall, UiVertex}, UiInstance};
3
4const DEFAULT_BUFFER_SIZE: u64 = 1024;
5const DEFAULT_TEXTURE_SIZE: u32 = 512;
6const SHADER_MODULE: &str = include_str!("../shaders/ui.wgsl");
7
8#[derive(Clone, Copy)]
9#[repr(C, packed)]
10struct WgpuVertex {
11  position: [f32; 2],
12  uv: [f32; 2],
13  color: [f32; 4],
14}
15unsafe impl bytemuck::Pod for WgpuVertex {}
16unsafe impl bytemuck::Zeroable for WgpuVertex {}
17
18impl WgpuVertex {
19  pub const LAYOUT: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout {
20    array_stride: std::mem::size_of::<WgpuVertex>() as wgpu::BufferAddress,
21    step_mode: wgpu::VertexStepMode::Vertex,
22    attributes: &wgpu::vertex_attr_array![
23      0 => Float32x2,
24      1 => Float32x2,
25      2 => Float32x4,
26    ],
27  };
28}
29
30impl From<UiVertex> for WgpuVertex {
31  fn from(v: UiVertex) -> Self {
32    Self {
33      position: v.position.to_array(),
34      uv: v.uv.to_array(),
35      color: v.color.to_array(),
36    }
37  }
38}
39
40pub struct WgpuUiRenderer {
41  pub modified: bool,
42  pub vertex_buffer: wgpu::Buffer,
43  pub index_buffer: wgpu::Buffer,
44  pub vertex_count: usize,
45  pub index_count: usize,
46  pub bind_group_layout: wgpu::BindGroupLayout,
47  pub bind_group: wgpu::BindGroup,
48  pub pipeline: wgpu::RenderPipeline,
49  pub texture: wgpu::Texture,
50  pub texture_view: wgpu::TextureView,
51  pub texture_sampler: wgpu::Sampler,
52}
53
54impl WgpuUiRenderer {
55  pub fn new(
56    device: &wgpu::Device,
57    surface_format: wgpu::TextureFormat,
58  ) -> Self {
59    let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
60      label: Some("ui_vertex_buffer"),
61      size: std::mem::size_of::<WgpuVertex>() as u64 * DEFAULT_BUFFER_SIZE,
62      usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
63      mapped_at_creation: false,
64    });
65
66    let index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
67      label: Some("hui_index_buffer"),
68      size: std::mem::size_of::<u32>() as u64 * DEFAULT_BUFFER_SIZE,
69      usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
70      mapped_at_creation: false,
71    });
72
73    let texture = device.create_texture(&wgpu::TextureDescriptor {
74      label: Some("ui_texture"),
75      size: wgpu::Extent3d {
76        width: DEFAULT_TEXTURE_SIZE,
77        height: DEFAULT_TEXTURE_SIZE,
78        depth_or_array_layers: 1,
79      },
80      mip_level_count: 1,
81      sample_count: 1,
82      dimension: wgpu::TextureDimension::D2,
83      format: wgpu::TextureFormat::Rgba8UnormSrgb,
84      usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
85      view_formats: &[],
86    });
87
88    let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
89      label: Some("ui_bind_group_layout"),
90      entries: &[
91        wgpu::BindGroupLayoutEntry {
92          binding: 0,
93          visibility: wgpu::ShaderStages::FRAGMENT,
94          ty: wgpu::BindingType::Texture {
95            sample_type: wgpu::TextureSampleType::Float { filterable: false },
96            view_dimension: wgpu::TextureViewDimension::D2,
97            multisampled: false,
98          },
99          count: None,
100        },
101        wgpu::BindGroupLayoutEntry {
102          binding: 1,
103          visibility: wgpu::ShaderStages::FRAGMENT,
104          ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
105          count: None,
106        },
107      ],
108    });
109
110    let texture_view = texture.create_view(&wgpu::TextureViewDescriptor {
111      label: Some("ui_texture_view"),
112      ..Default::default()
113    });
114
115    let texture_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
116      label: Some("ui_texture_sampler"),
117      address_mode_u: wgpu::AddressMode::ClampToEdge,
118      address_mode_v: wgpu::AddressMode::ClampToEdge,
119      address_mode_w: wgpu::AddressMode::ClampToEdge,
120      mag_filter: wgpu::FilterMode::Nearest,
121      min_filter: wgpu::FilterMode::Nearest,
122      mipmap_filter: wgpu::FilterMode::Nearest,
123      ..Default::default()
124    });
125
126    let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
127      label: Some("ui_bind_group"),
128      layout: &bind_group_layout,
129      entries: &[
130        wgpu::BindGroupEntry {
131          binding: 0,
132          resource: wgpu::BindingResource::TextureView(&texture_view),
133        },
134        wgpu::BindGroupEntry {
135          binding: 1,
136          resource: wgpu::BindingResource::Sampler(&texture_sampler),
137        },
138      ],
139    });
140
141    let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
142      label: Some("ui_vertex_shader"),
143      source: wgpu::ShaderSource::Wgsl(SHADER_MODULE.into()),
144    });
145
146    let pipeline = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
147      label: Some("ui_pipeline_layout"),
148      bind_group_layouts: &[&bind_group_layout],
149      push_constant_ranges: &[],
150    });
151
152    let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
153      label: Some("ui_pipeline"),
154      layout: Some(&pipeline),
155      vertex: wgpu::VertexState {
156        module: &shader_module,
157        compilation_options: wgpu::PipelineCompilationOptions::default(),
158        entry_point: Some("vs_main"),
159        buffers: &[WgpuVertex::LAYOUT],
160      },
161      fragment: Some(wgpu::FragmentState {
162        module: &shader_module,
163        compilation_options: wgpu::PipelineCompilationOptions::default(),
164        entry_point: Some("fs_main"),
165        targets: &[Some(wgpu::ColorTargetState {
166          format: surface_format,
167          blend: Some(wgpu::BlendState::ALPHA_BLENDING),
168          write_mask: wgpu::ColorWrites::COLOR,
169        })],
170      }),
171      primitive: wgpu::PrimitiveState {
172        topology: wgpu::PrimitiveTopology::TriangleList,
173        strip_index_format: None,
174        front_face: wgpu::FrontFace::Ccw,
175        cull_mode: None,
176        polygon_mode: wgpu::PolygonMode::Fill,
177        conservative: false,
178        unclipped_depth: false,
179      },
180      depth_stencil: None,
181      multisample: wgpu::MultisampleState::default(),
182      multiview: None,
183      cache: None,
184    });
185
186    Self {
187      modified: true,
188      vertex_buffer,
189      index_buffer,
190      vertex_count: 0,
191      index_count: 0,
192      bind_group_layout,
193      bind_group,
194      texture,
195      texture_view,
196      texture_sampler,
197      pipeline,
198    }
199  }
200
201  fn update_buffers(&mut self, call: &UiDrawCall, queue: &wgpu::Queue, device: &wgpu::Device, resolution: Vec2) {
202    let data_vtx = call.vertices.iter()
203      .copied()
204      .map(|x| {
205        let mut v = x;
206        v.position = vec2(1., -1.) * ((v.position / resolution) * 2.0 - 1.0);
207        v
208      })
209      .map(WgpuVertex::from)
210      .collect::<Vec<_>>();
211    let data_idx = &call.indices[..];
212
213    let data_vtx_view = bytemuck::cast_slice(&data_vtx);
214    let data_idx_view = bytemuck::cast_slice(data_idx);
215
216    self.vertex_count = call.vertices.len();
217    self.index_count = call.indices.len();
218
219    if data_vtx.is_empty() || data_idx.is_empty() {
220      return
221    }
222
223    if data_vtx_view.len() as u64 > self.vertex_buffer.size() {
224      self.vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
225        label: Some("ui_vertex_buffer"),
226        size: (data_vtx_view.len() as u64).next_power_of_two(),
227        usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
228        mapped_at_creation: false,
229      });
230    }
231
232    if data_idx_view.len() as u64 > self.index_buffer.size() {
233      self.index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
234        label: Some("ui_index_buffer"),
235        size: (data_idx_view.len() as u64).next_power_of_two(),
236        usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
237        mapped_at_creation: false,
238      });
239    }
240
241    queue.write_buffer(&self.vertex_buffer, 0, data_vtx_view);
242    queue.write_buffer(&self.index_buffer, 0, data_idx_view);
243  }
244
245  fn update_texture(&mut self, meta: TextureAtlasMeta, queue: &wgpu::Queue, device: &wgpu::Device) {
246    //TODO URGENCY:HIGH resize texture if needed
247    if meta.data.len() as u32 > (self.texture.size().width * self.texture.size().height * 4) {
248      self.texture.destroy();
249      // unimplemented!("texture resize not implemented");
250      self.texture = device.create_texture(&wgpu::TextureDescriptor {
251        label: Some("ui_texture"),
252        size: wgpu::Extent3d {
253          width: DEFAULT_TEXTURE_SIZE,
254          height: DEFAULT_TEXTURE_SIZE,
255          depth_or_array_layers: 1,
256        },
257        mip_level_count: 1,
258        sample_count: 1,
259        dimension: wgpu::TextureDimension::D2,
260        format: wgpu::TextureFormat::Rgba8UnormSrgb,
261        usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
262        view_formats: &[],
263      });
264    }
265    queue.write_texture(
266      wgpu::ImageCopyTexture {
267        texture: &self.texture,
268        mip_level: 0,
269        origin: wgpu::Origin3d::ZERO,
270        aspect: wgpu::TextureAspect::All,
271      },
272      meta.data,
273      wgpu::ImageDataLayout {
274        offset: 0,
275        bytes_per_row: Some(meta.size.x * 4),
276        rows_per_image: Some(meta.size.y),
277      },
278      wgpu::Extent3d {
279        width: meta.size.x,
280        height: meta.size.y,
281        depth_or_array_layers: 1,
282      }
283    );
284  }
285
286  pub fn update(
287    &mut self,
288    instance: &UiInstance,
289    queue: &wgpu::Queue,
290    device: &wgpu::Device,
291    resolution: Vec2,
292  ) {
293    let (modified, call) = instance.draw_call();
294    if self.modified || modified {
295      self.update_buffers(call, queue, device, resolution);
296    }
297
298    let meta = instance.atlas();
299    if self.modified || meta.modified {
300      self.update_texture(meta, queue, device);
301    }
302
303    self.modified = false;
304  }
305
306  pub fn draw(
307    &self,
308    encoder: &mut wgpu::CommandEncoder,
309    surface_view: &wgpu::TextureView,
310  ) {
311    if self.vertex_count == 0 || self.index_count == 0 {
312      return
313    }
314
315    let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
316      label: Some("ui_render_pass"),
317      color_attachments: &[Some(wgpu::RenderPassColorAttachment {
318        view: surface_view,
319        resolve_target: None,
320        ops: wgpu::Operations {
321          load: wgpu::LoadOp::Load,
322          store: wgpu::StoreOp::Store,
323        },
324      })],
325      ..Default::default()
326    });
327
328    let vtx_size = self.vertex_count as u64 * std::mem::size_of::<WgpuVertex>() as u64;
329    let idx_size = self.index_count as u64 * std::mem::size_of::<u32>() as u64;
330
331    rpass.set_pipeline(&self.pipeline);
332    rpass.set_bind_group(0, &self.bind_group, &[]);
333    rpass.set_vertex_buffer(0, self.vertex_buffer.slice(0..vtx_size));
334    rpass.set_index_buffer(self.index_buffer.slice(..idx_size), wgpu::IndexFormat::Uint32);
335    rpass.draw_indexed(0..self.index_count as u32, 0, 0..1);
336  }
337}