1use super::super::{Bound, Consts, GlobalsLayouts, Quad, Texture, Tri, Vertex as VertexTrait};
2use bytemuck::{Pod, Zeroable};
3use std::mem;
4use vek::*;
5
6const UI_IMAGE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb;
15
16#[repr(C)]
17#[derive(Copy, Clone, Debug, Zeroable, Pod)]
18pub struct Vertex {
19 pos: [f32; 2],
20 uv: [f32; 2],
21 color: [f32; 4],
22 center: [f32; 2],
23 scale: [f32; 2],
25 mode: u32,
26}
27
28impl Vertex {
29 fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
30 const ATTRIBUTES: [wgpu::VertexAttribute; 6] = wgpu::vertex_attr_array![
31 0 => Float32x2, 1 => Float32x2, 2 => Float32x4,
32 3 => Float32x2, 4 => Float32x2, 5 => Uint32,
33 ];
34 wgpu::VertexBufferLayout {
35 array_stride: Self::STRIDE,
36 step_mode: wgpu::VertexStepMode::Vertex,
37 attributes: &ATTRIBUTES,
38 }
39 }
40}
41
42impl VertexTrait for Vertex {
43 const QUADS_INDEX: Option<wgpu::IndexFormat> = None;
44 const STRIDE: wgpu::BufferAddress = mem::size_of::<Self>() as wgpu::BufferAddress;
45}
46
47#[repr(C)]
48#[derive(Copy, Clone, Debug, Zeroable, Pod)]
49pub struct Locals {
50 pos: [f32; 4],
51}
52
53impl From<Vec4<f32>> for Locals {
54 fn from(pos: Vec4<f32>) -> Self {
55 Self {
56 pos: pos.into_array(),
57 }
58 }
59}
60
61impl Default for Locals {
62 fn default() -> Self { Self { pos: [0.0; 4] } }
63}
64
65#[repr(C)]
66#[derive(Copy, Clone, Debug, Zeroable, Pod)]
67pub struct TexLocals {
68 texture_size: [u32; 2],
69}
70
71impl From<Vec2<u32>> for TexLocals {
72 fn from(texture_size: Vec2<u32>) -> Self {
73 Self {
74 texture_size: texture_size.into_array(),
75 }
76 }
77}
78
79pub const MODE_TEXT: u32 = 0;
81pub const MODE_IMAGE: u32 = 1;
83pub const MODE_GEOMETRY: u32 = 2;
85pub const MODE_IMAGE_SOURCE_NORTH: u32 = 3;
90pub const MODE_IMAGE_TARGET_NORTH: u32 = 5;
95pub const MODE_IMAGE_TARGET_PLAYER: u32 = 7;
100
101#[derive(Clone, Copy)]
102pub enum Mode {
103 Text,
104 Image {
105 scale: Vec2<f32>,
106 },
107 Geometry,
108 ImageSourceNorth {
112 scale: Vec2<f32>,
113 },
114 ImageTargetNorth {
118 scale: Vec2<f32>,
119 },
120 ImageTargetPlayer {
123 scale: Vec2<f32>,
124 },
125}
126
127impl Mode {
128 fn value(self) -> u32 {
129 match self {
130 Mode::Text => MODE_TEXT,
131 Mode::Image { .. } => MODE_IMAGE,
132 Mode::Geometry => MODE_GEOMETRY,
133 Mode::ImageSourceNorth { .. } => MODE_IMAGE_SOURCE_NORTH,
134 Mode::ImageTargetNorth { .. } => MODE_IMAGE_TARGET_NORTH,
135 Mode::ImageTargetPlayer { .. } => MODE_IMAGE_TARGET_PLAYER,
136 }
137 }
138
139 fn scale(self) -> Vec2<f32> {
141 match self {
142 Mode::ImageSourceNorth { scale }
143 | Mode::ImageTargetNorth { scale }
144 | Mode::ImageTargetPlayer { scale } => scale,
145 Mode::Image { scale } => scale,
146 Mode::Text | Mode::Geometry => Vec2::one(),
147 }
148 }
149}
150
151pub type BoundLocals = Bound<Consts<Locals>>;
152
153pub struct TextureBindGroup {
154 pub(in super::super) bind_group: wgpu::BindGroup,
155}
156
157pub struct UiLayout {
158 locals: wgpu::BindGroupLayout,
159 texture: wgpu::BindGroupLayout,
160}
161
162impl UiLayout {
163 pub fn new(device: &wgpu::Device) -> Self {
164 Self {
165 locals: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
166 label: None,
167 entries: &[
168 wgpu::BindGroupLayoutEntry {
170 binding: 0,
171 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
172 ty: wgpu::BindingType::Buffer {
173 ty: wgpu::BufferBindingType::Uniform,
174 has_dynamic_offset: false,
175 min_binding_size: None,
176 },
177 count: None,
178 },
179 ],
180 }),
181 texture: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
182 label: None,
183 entries: &[
184 wgpu::BindGroupLayoutEntry {
186 binding: 0,
187 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
188 ty: wgpu::BindingType::Texture {
189 sample_type: wgpu::TextureSampleType::Float { filterable: true },
190 view_dimension: wgpu::TextureViewDimension::D2,
191 multisampled: false,
192 },
193 count: None,
194 },
195 wgpu::BindGroupLayoutEntry {
196 binding: 1,
197 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
198 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
199 count: None,
200 },
201 wgpu::BindGroupLayoutEntry {
203 binding: 2,
204 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
205 ty: wgpu::BindingType::Buffer {
206 ty: wgpu::BufferBindingType::Uniform,
207 has_dynamic_offset: false,
208 min_binding_size: None,
209 },
210 count: None,
211 },
212 ],
213 }),
214 }
215 }
216
217 pub fn bind_locals(&self, device: &wgpu::Device, locals: Consts<Locals>) -> BoundLocals {
218 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
219 label: None,
220 layout: &self.locals,
221 entries: &[wgpu::BindGroupEntry {
222 binding: 0,
223 resource: locals.buf().as_entire_binding(),
224 }],
225 });
226
227 BoundLocals {
228 bind_group,
229 with: locals,
230 }
231 }
232
233 pub fn bind_texture(
234 &self,
235 device: &wgpu::Device,
236 texture: &Texture,
237 tex_locals: Consts<TexLocals>,
238 ) -> TextureBindGroup {
239 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
240 label: None,
241 layout: &self.texture,
242 entries: &[
243 wgpu::BindGroupEntry {
244 binding: 0,
245 resource: wgpu::BindingResource::TextureView(&texture.view),
246 },
247 wgpu::BindGroupEntry {
248 binding: 1,
249 resource: wgpu::BindingResource::Sampler(&texture.sampler),
250 },
251 wgpu::BindGroupEntry {
252 binding: 2,
253 resource: tex_locals.buf().as_entire_binding(),
254 },
255 ],
256 });
257
258 TextureBindGroup { bind_group }
259 }
260}
261
262pub struct UiPipeline {
263 pub pipeline: wgpu::RenderPipeline,
264}
265
266impl UiPipeline {
267 pub fn new(
268 device: &wgpu::Device,
269 vs_module: &wgpu::ShaderModule,
270 fs_module: &wgpu::ShaderModule,
271 surface_config: &wgpu::SurfaceConfiguration,
272 global_layout: &GlobalsLayouts,
273 layout: &UiLayout,
274 ) -> Self {
275 let render_pipeline_layout =
276 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
277 label: Some("Ui pipeline layout"),
278 push_constant_ranges: &[],
279 bind_group_layouts: &[&global_layout.globals, &layout.locals, &layout.texture],
280 });
281
282 let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
283 label: Some("UI pipeline"),
284 layout: Some(&render_pipeline_layout),
285 vertex: wgpu::VertexState {
286 module: vs_module,
287 entry_point: Some("main"),
288 buffers: &[Vertex::desc()],
289 compilation_options: Default::default(),
290 },
291 primitive: wgpu::PrimitiveState {
292 topology: wgpu::PrimitiveTopology::TriangleList,
293 strip_index_format: None,
294 front_face: wgpu::FrontFace::Ccw,
295 cull_mode: Some(wgpu::Face::Back),
296 unclipped_depth: false,
297 polygon_mode: wgpu::PolygonMode::Fill,
298 conservative: false,
299 },
300 depth_stencil: None,
301 multisample: wgpu::MultisampleState {
302 count: 1,
303 mask: !0,
304 alpha_to_coverage_enabled: false,
305 },
306 fragment: Some(wgpu::FragmentState {
307 module: fs_module,
308 entry_point: Some("main"),
309 targets: &[Some(wgpu::ColorTargetState {
310 format: surface_config.format,
311 blend: Some(wgpu::BlendState {
312 color: wgpu::BlendComponent {
313 src_factor: wgpu::BlendFactor::SrcAlpha,
314 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
315 operation: wgpu::BlendOperation::Add,
316 },
317 alpha: wgpu::BlendComponent {
318 src_factor: wgpu::BlendFactor::One,
319 dst_factor: wgpu::BlendFactor::One,
320 operation: wgpu::BlendOperation::Add,
321 },
322 }),
323 write_mask: wgpu::ColorWrites::ALL,
324 })],
325 compilation_options: Default::default(),
326 }),
327 multiview: None,
328 cache: None,
329 });
330
331 Self {
332 pipeline: render_pipeline,
333 }
334 }
335}
336
337pub fn create_quad(
338 rect: Aabr<f32>,
339 uv_rect: Aabr<f32>,
340 color: Rgba<f32>,
341 mode: Mode,
342) -> Quad<Vertex> {
343 create_quad_vert_gradient(rect, uv_rect, color, color, mode)
344}
345
346pub fn create_quad_vert_gradient(
347 rect: Aabr<f32>,
348 uv_rect: Aabr<f32>,
349 top_color: Rgba<f32>,
350 bottom_color: Rgba<f32>,
351 mode: Mode,
352) -> Quad<Vertex> {
353 let top_color = top_color.into_array();
354 let bottom_color = bottom_color.into_array();
355
356 let center = if let Mode::ImageSourceNorth { .. } = mode {
357 uv_rect.center().into_array()
358 } else {
359 rect.center().into_array()
360 };
361 let scale = mode.scale().into_array();
362 let mode_val = mode.value();
363 let v = |pos, uv, color| Vertex {
364 pos,
365 uv,
366 center,
367 color,
368 scale,
369 mode: mode_val,
370 };
371 let aabr_to_lbrt = |aabr: Aabr<f32>| (aabr.min.x, aabr.min.y, aabr.max.x, aabr.max.y);
372
373 let (l, b, r, t) = aabr_to_lbrt(rect);
374 let (uv_l, uv_b, uv_r, uv_t) = aabr_to_lbrt(uv_rect);
375
376 match (uv_b > uv_t, uv_l > uv_r) {
377 (true, true) => Quad::new(
378 v([r, t], [uv_l, uv_b], top_color),
379 v([l, t], [uv_l, uv_t], top_color),
380 v([l, b], [uv_r, uv_t], bottom_color),
381 v([r, b], [uv_r, uv_b], bottom_color),
382 ),
383 (false, false) => Quad::new(
384 v([r, t], [uv_l, uv_b], top_color),
385 v([l, t], [uv_l, uv_t], top_color),
386 v([l, b], [uv_r, uv_t], bottom_color),
387 v([r, b], [uv_r, uv_b], bottom_color),
388 ),
389 _ => Quad::new(
390 v([r, t], [uv_r, uv_t], top_color),
391 v([l, t], [uv_l, uv_t], top_color),
392 v([l, b], [uv_l, uv_b], bottom_color),
393 v([r, b], [uv_r, uv_b], bottom_color),
394 ),
395 }
396}
397
398pub fn create_tri(
399 tri: [[f32; 2]; 3],
400 uv_tri: [[f32; 2]; 3],
401 color: Rgba<f32>,
402 mode: Mode,
403) -> Tri<Vertex> {
404 let center = [0.0, 0.0];
405 let scale = mode.scale().into_array();
406 let mode_val = mode.value();
407 let v = |pos, uv| Vertex {
408 pos,
409 uv,
410 center,
411 color: color.into_array(),
412 scale,
413 mode: mode_val,
414 };
415 Tri::new(
416 v([tri[0][0], tri[0][1]], [uv_tri[0][0], uv_tri[0][1]]),
417 v([tri[1][0], tri[1][1]], [uv_tri[1][0], uv_tri[1][1]]),
418 v([tri[2][0], tri[2][1]], [uv_tri[2][0], uv_tri[2][1]]),
419 )
420}
421
422pub struct PremultiplyAlphaLayout {
467 source_texture: wgpu::BindGroupLayout,
468}
469
470impl PremultiplyAlphaLayout {
471 pub fn new(device: &wgpu::Device) -> Self {
472 Self {
473 source_texture: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
474 label: None,
475 entries: &[
476 wgpu::BindGroupLayoutEntry {
478 binding: 0,
479 visibility: wgpu::ShaderStages::FRAGMENT,
480 ty: wgpu::BindingType::Texture {
481 sample_type: wgpu::TextureSampleType::Float { filterable: false },
482 view_dimension: wgpu::TextureViewDimension::D2,
483 multisampled: false,
484 },
485 count: None,
486 },
487 ],
488 }),
489 }
490 }
491}
492
493pub struct PremultiplyAlphaPipeline {
494 pub pipeline: wgpu::RenderPipeline,
495}
496
497impl PremultiplyAlphaPipeline {
498 pub fn new(
499 device: &wgpu::Device,
500 vs_module: &wgpu::ShaderModule,
501 fs_module: &wgpu::ShaderModule,
502 layout: &PremultiplyAlphaLayout,
503 ) -> Self {
504 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
505 label: Some("Premultiply alpha pipeline layout"),
506 bind_group_layouts: &[&layout.source_texture],
507 push_constant_ranges: &[wgpu::PushConstantRange {
508 stages: wgpu::ShaderStages::VERTEX,
509 range: 0..core::mem::size_of::<PremultiplyAlphaParams>() as u32,
510 }],
511 });
512
513 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
514 label: Some("Premultiply alpha pipeline"),
515 layout: Some(&pipeline_layout),
516 vertex: wgpu::VertexState {
517 module: vs_module,
518 entry_point: Some("main"),
519 buffers: &[],
520 compilation_options: Default::default(),
521 },
522 primitive: wgpu::PrimitiveState {
523 topology: wgpu::PrimitiveTopology::TriangleList,
524 strip_index_format: None,
525 front_face: wgpu::FrontFace::Ccw,
526 cull_mode: Some(wgpu::Face::Back),
527 unclipped_depth: false,
528 polygon_mode: wgpu::PolygonMode::Fill,
529 conservative: false,
530 },
531 depth_stencil: None,
532 multisample: wgpu::MultisampleState::default(),
533 fragment: Some(wgpu::FragmentState {
534 module: fs_module,
535 entry_point: Some("main"),
536 targets: &[Some(wgpu::ColorTargetState {
537 format: UI_IMAGE_FORMAT,
538 blend: None,
539 write_mask: wgpu::ColorWrites::ALL,
540 })],
541 compilation_options: Default::default(),
542 }),
543 multiview: None,
544 cache: None,
545 });
546
547 Self { pipeline }
548 }
549}
550
551#[repr(C)]
553#[derive(Copy, Clone, Debug, Zeroable, Pod)]
554pub struct PremultiplyAlphaParams {
555 source_size_xy: u32,
557 target_offset_xy: u32,
561 target_size_xy: u32,
563}
564
565pub(in super::super) struct PremultiplyUpload {
571 source_bg: wgpu::BindGroup,
572 source_size_xy: u32,
573 offset: Vec2<u16>,
577}
578
579impl PremultiplyUpload {
580 pub(in super::super) fn prepare(
581 device: &wgpu::Device,
582 queue: &wgpu::Queue,
583 layout: &PremultiplyAlphaLayout,
584 image: &image::RgbaImage,
585 offset: Vec2<u16>,
586 ) -> Self {
587 let image_size = wgpu::Extent3d {
593 width: image.width(),
594 height: image.height(),
595 depth_or_array_layers: 1,
596 };
597 let source_tex = device.create_texture(&wgpu::TextureDescriptor {
598 label: None,
599 size: image_size,
600 mip_level_count: 1,
601 sample_count: 1,
602 dimension: wgpu::TextureDimension::D2,
603 format: wgpu::TextureFormat::Rgba8UnormSrgb,
604 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
605 view_formats: &[],
606 });
607 queue.write_texture(
608 wgpu::TexelCopyTextureInfo {
609 texture: &source_tex,
610 mip_level: 0,
611 origin: wgpu::Origin3d::ZERO,
612 aspect: wgpu::TextureAspect::All,
613 },
614 &(&**image)[..(image.width() as usize * image.height() as usize * 4)],
615 wgpu::TexelCopyBufferLayout {
616 offset: 0,
617 bytes_per_row: Some(image.width() * 4),
618 rows_per_image: Some(image.height()),
619 },
620 image_size,
621 );
622 let view = source_tex.create_view(&wgpu::TextureViewDescriptor {
624 label: None,
625 format: Some(wgpu::TextureFormat::Rgba8UnormSrgb),
626 dimension: Some(wgpu::TextureViewDimension::D2),
627 usage: None,
628 aspect: wgpu::TextureAspect::All,
629 base_mip_level: 0,
630 mip_level_count: None,
631 base_array_layer: 0,
632 array_layer_count: None,
633 });
634 let source_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
635 label: None,
636 layout: &layout.source_texture,
637 entries: &[wgpu::BindGroupEntry {
638 binding: 0,
639 resource: wgpu::BindingResource::TextureView(&view),
640 }],
641 });
642
643 let source_size_xy = image_size.width + (image_size.height << 16);
645
646 Self {
647 source_bg,
648 source_size_xy,
649 offset,
650 }
651 }
652
653 pub(in super::super) fn draw_data(
657 &self,
658 target: &Texture,
659 ) -> (&wgpu::BindGroup, PremultiplyAlphaParams) {
660 let target_offset_xy = u32::from(self.offset.x) + (u32::from(self.offset.y) << 16);
661 let target_dims = target.get_dimensions();
662 let target_size_xy = target_dims.x + (target_dims.y << 16);
664 (&self.source_bg, PremultiplyAlphaParams {
665 source_size_xy: self.source_size_xy,
666 target_offset_xy,
667 target_size_xy,
668 })
669 }
670}
671
672use std::sync::Arc;
673#[derive(Default)]
675pub(in super::super) struct BatchedUploads {
676 batches: Vec<(Arc<Texture>, Vec<PremultiplyUpload>)>,
677}
678#[derive(Default, Clone, Copy)]
679pub struct UploadBatchId(usize);
680
681impl BatchedUploads {
682 pub(in super::super) fn submit(
699 &mut self,
700 target_texture: &Arc<Texture>,
701 batch_id: UploadBatchId,
702 upload: PremultiplyUpload,
703 ) -> UploadBatchId {
704 if let Some(batch) = self
705 .batches
706 .get_mut(batch_id.0)
707 .filter(|b| Arc::ptr_eq(&b.0, target_texture))
708 {
709 batch.1.push(upload);
710 batch_id
711 } else {
712 let new_batch_id = UploadBatchId(self.batches.len());
713 self.batches
714 .push((Arc::clone(target_texture), vec![upload]));
715 new_batch_id
716 }
717 }
718
719 pub(in super::super) fn take(&mut self) -> Vec<(Arc<Texture>, Vec<PremultiplyUpload>)> {
720 core::mem::take(&mut self.batches)
721 }
722}