veloren_voxygen/render/pipelines/
ui.rs

1use super::super::{Bound, Consts, GlobalsLayouts, Quad, Texture, Tri, Vertex as VertexTrait};
2use bytemuck::{Pod, Zeroable};
3use std::mem;
4use vek::*;
5
6/// The format of textures that the UI sources image data from.
7///
8/// Note, the is not directly used in all relevant locations, but still helps to
9/// more clearly document the that this is the format being used. Notably,
10/// textures are created via `renderer.create_dynamic_texture(...)` and
11/// `renderer.create_texture(&DynamicImage::ImageRgba(image), ...)` (TODO:
12/// update if we have to refactor when implementing the RENDER_ATTACHMENT
13/// usage).
14const 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    // Used calculating where to sample scaled images.
24    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
79/// Draw text from the text cache texture `tex` in the fragment shader.
80pub const MODE_TEXT: u32 = 0;
81/// Draw an image from the texture at `tex` in the fragment shader.
82pub const MODE_IMAGE: u32 = 1;
83/// Ignore `tex` and draw simple, colored 2D geometry.
84pub const MODE_GEOMETRY: u32 = 2;
85/// Draw an image from the texture at `tex` in the fragment shader, with the
86/// source rectangle rotated to face north.
87///
88/// FIXME: Make more principled.
89pub const MODE_IMAGE_SOURCE_NORTH: u32 = 3;
90/// Draw an image from the texture at `tex` in the fragment shader, with the
91/// target rectangle rotated to face north.
92///
93/// FIXME: Make more principled.
94pub const MODE_IMAGE_TARGET_NORTH: u32 = 5;
95/// Draw an image from the texture at 'tex' in the fragment shader, with the
96/// target rectangle rotated to face the characters orientation.
97///
98/// FIXME: Make more pricipled.
99pub 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    /// Draw an image from the texture at `tex` in the fragment shader, with the
109    /// source rectangle rotated to face north (TODO: detail on what "north"
110    /// means here).
111    ImageSourceNorth {
112        scale: Vec2<f32>,
113    },
114    /// Draw an image from the texture at `tex` in the fragment shader, with the
115    /// target rectangle rotated to face north. (TODO: detail on what "target"
116    /// means)
117    ImageTargetNorth {
118        scale: Vec2<f32>,
119    },
120    /// Draw an image from the texture at 'tex' in the fragment shader, with the
121    /// target rectangle rotated to face the players orientation.
122    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    /// Gets the scaling of the displayed image compared to the source.
140    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                    // locals
169                    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                    // texture
185                    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                    // tex_locals
202                    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
422// Premultiplying alpha on the GPU before placing images into the textures that
423// will be sampled from in the UI pipeline.
424//
425// Steps:
426//
427// 1. Upload new image via `Device::create_texture_with_data`.
428//
429//    (NOTE: Initially considered: Creating a storage buffer to read from in the
430//    shader via `Device::create_buffer_init`, with `MAP_WRITE` flag to avoid
431//    staging buffer. However, with GPUs combining usages other than `COPY_SRC`
432//    with `MAP_WRITE` may be less ideal. Plus, by copying into a texture first
433//    we can get free srgb conversion when fetching colors from the texture. In
434//    the future, we may want to branch based on the whether the GPU is
435//    integrated and avoid this extra copy.)
436//
437// 2. Run render pipeline to multiply by alpha reading from this texture and
438//    writing to the final texture (this can either be in an atlas or in an
439//    independent texture if the image is over a certain size threshold).
440//
441//    (NOTE: Initially considered: using a compute pipeline and writing to the
442//     final texture as a storage texture. However, the srgb format can't be
443//     used with storage texture and there is not yet the capability to create
444//     non-srgb views of srgb textures.)
445//
446// Info needed:
447//
448// * source texture (texture binding)
449// * target texture (render attachment)
450// * source image dimensions (push constant)
451// * target texture dimensions (push constant)
452// * position in the target texture (push constant)
453//
454// TODO: potential optimizations
455// * what is the overhead of this draw call call? at some point we may be better
456//   off converting very small images on the cpu and/or batching these into a
457//   single draw call
458// * what is the overhead of creating new small textures? for processing many
459//   small images would it be useful to create a single texture the same size as
460//   our cache texture and use Queue::write_texture?
461// * is using create_buffer_init and reading directly from that (with manual
462//   srgb conversion) worth avoiding staging buffer/copy-to-texture for
463//   integrated GPUs?
464// * premultipying alpha in a release asset preparation step
465
466pub 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                    // source_texture
477                    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/// Uploaded as push constant.
552#[repr(C)]
553#[derive(Copy, Clone, Debug, Zeroable, Pod)]
554pub struct PremultiplyAlphaParams {
555    /// Size of the source image.
556    source_size_xy: u32,
557    /// Offset to place the image at in the target texture.
558    ///
559    /// Origin is the top-left.
560    target_offset_xy: u32,
561    /// Size of the target texture.
562    target_size_xy: u32,
563}
564
565/// An image upload that needs alpha premultiplication and which is in a pending
566/// state.
567///
568/// From here we will use the `PremultiplyAlpha` pipeline to premultiply the
569/// alpha while transfering the image to its destination texture.
570pub(in super::super) struct PremultiplyUpload {
571    source_bg: wgpu::BindGroup,
572    source_size_xy: u32,
573    /// The location in the final texture this will be placed at. Technically,
574    /// we don't need this information at this point but it is convenient to
575    /// store it here.
576    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        // TODO: duplicating some code from `Texture` since:
588        // 1. We don't need to create a sampler.
589        // 2. Texture::new accepts &DynamicImage which isn't possible to create from
590        //    &RgbaImage without cloning. (this might be addressed on zoomy worldgen
591        //    branch)
592        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        // Create view to use to create bind group
623        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        // NOTE: We assume the max texture size is less than u16::MAX.
644        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    /// Semantically, this consumes the `PremultiplyUpload` but we need to keep
654    /// the bind group alive to the end of the render pass and don't want to
655    /// bother storing it somewhere else.
656    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        // NOTE: We assume the max texture size is less than u16::MAX.
663        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/// Per-target texture batched uploads
674#[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    /// Adds the provided upload to the batch indicated by the provided target
683    /// texture and optional batch id. A new batch will be created if the batch
684    /// id is invalid (doesn't refer to an existing batch) or the provided
685    /// target texture isn't the same as the one associated with the
686    /// provided batch id. Creating a new batch involves cloning the
687    /// provided texture `Arc`.
688    ///
689    /// The id of the batch where the upload is ultimately submitted will be
690    /// returned. This id can be used in subsequent calls to add items to
691    /// the same batch (i.e. uploads for the same texture).
692    ///
693    /// Batch ids will reset every frame, however since we check that the
694    /// texture matches, it is perfectly fine to use a stale id (just keep
695    /// in mind that this will create a new batch). This also means that it is
696    /// sufficient to use `UploadBatchId::default()` when calling this with
697    /// new textures.
698    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}