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 if meta.data.len() as u32 > (self.texture.size().width * self.texture.size().height * 4) {
248 self.texture.destroy();
249 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}