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
96#[derive(Clone, Copy)]
97pub enum Mode {
98    Text,
99    Image {
100        scale: Vec2<f32>,
101    },
102    Geometry,
103    /// Draw an image from the texture at `tex` in the fragment shader, with the
104    /// source rectangle rotated to face north (TODO: detail on what "north"
105    /// means here).
106    ImageSourceNorth {
107        scale: Vec2<f32>,
108    },
109    /// Draw an image from the texture at `tex` in the fragment shader, with the
110    /// target rectangle rotated to face north. (TODO: detail on what "target"
111    /// means)
112    ImageTargetNorth {
113        scale: Vec2<f32>,
114    },
115}
116
117impl Mode {
118    fn value(self) -> u32 {
119        match self {
120            Mode::Text => MODE_TEXT,
121            Mode::Image { .. } => MODE_IMAGE,
122            Mode::Geometry => MODE_GEOMETRY,
123            Mode::ImageSourceNorth { .. } => MODE_IMAGE_SOURCE_NORTH,
124            Mode::ImageTargetNorth { .. } => MODE_IMAGE_TARGET_NORTH,
125        }
126    }
127
128    /// Gets the scaling of the displayed image compared to the source.
129    fn scale(self) -> Vec2<f32> {
130        match self {
131            Mode::ImageSourceNorth { scale } | Mode::ImageTargetNorth { scale } => scale,
132            Mode::Image { scale } => scale,
133            Mode::Text | Mode::Geometry => Vec2::one(),
134        }
135    }
136}
137
138pub type BoundLocals = Bound<Consts<Locals>>;
139
140pub struct TextureBindGroup {
141    pub(in super::super) bind_group: wgpu::BindGroup,
142}
143
144pub struct UiLayout {
145    locals: wgpu::BindGroupLayout,
146    texture: wgpu::BindGroupLayout,
147}
148
149impl UiLayout {
150    pub fn new(device: &wgpu::Device) -> Self {
151        Self {
152            locals: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
153                label: None,
154                entries: &[
155                    // locals
156                    wgpu::BindGroupLayoutEntry {
157                        binding: 0,
158                        visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
159                        ty: wgpu::BindingType::Buffer {
160                            ty: wgpu::BufferBindingType::Uniform,
161                            has_dynamic_offset: false,
162                            min_binding_size: None,
163                        },
164                        count: None,
165                    },
166                ],
167            }),
168            texture: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
169                label: None,
170                entries: &[
171                    // texture
172                    wgpu::BindGroupLayoutEntry {
173                        binding: 0,
174                        visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
175                        ty: wgpu::BindingType::Texture {
176                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
177                            view_dimension: wgpu::TextureViewDimension::D2,
178                            multisampled: false,
179                        },
180                        count: None,
181                    },
182                    wgpu::BindGroupLayoutEntry {
183                        binding: 1,
184                        visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
185                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
186                        count: None,
187                    },
188                    // tex_locals
189                    wgpu::BindGroupLayoutEntry {
190                        binding: 2,
191                        visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
192                        ty: wgpu::BindingType::Buffer {
193                            ty: wgpu::BufferBindingType::Uniform,
194                            has_dynamic_offset: false,
195                            min_binding_size: None,
196                        },
197                        count: None,
198                    },
199                ],
200            }),
201        }
202    }
203
204    pub fn bind_locals(&self, device: &wgpu::Device, locals: Consts<Locals>) -> BoundLocals {
205        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
206            label: None,
207            layout: &self.locals,
208            entries: &[wgpu::BindGroupEntry {
209                binding: 0,
210                resource: locals.buf().as_entire_binding(),
211            }],
212        });
213
214        BoundLocals {
215            bind_group,
216            with: locals,
217        }
218    }
219
220    pub fn bind_texture(
221        &self,
222        device: &wgpu::Device,
223        texture: &Texture,
224        tex_locals: Consts<TexLocals>,
225    ) -> TextureBindGroup {
226        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
227            label: None,
228            layout: &self.texture,
229            entries: &[
230                wgpu::BindGroupEntry {
231                    binding: 0,
232                    resource: wgpu::BindingResource::TextureView(&texture.view),
233                },
234                wgpu::BindGroupEntry {
235                    binding: 1,
236                    resource: wgpu::BindingResource::Sampler(&texture.sampler),
237                },
238                wgpu::BindGroupEntry {
239                    binding: 2,
240                    resource: tex_locals.buf().as_entire_binding(),
241                },
242            ],
243        });
244
245        TextureBindGroup { bind_group }
246    }
247}
248
249pub struct UiPipeline {
250    pub pipeline: wgpu::RenderPipeline,
251}
252
253impl UiPipeline {
254    pub fn new(
255        device: &wgpu::Device,
256        vs_module: &wgpu::ShaderModule,
257        fs_module: &wgpu::ShaderModule,
258        surface_config: &wgpu::SurfaceConfiguration,
259        global_layout: &GlobalsLayouts,
260        layout: &UiLayout,
261    ) -> Self {
262        let render_pipeline_layout =
263            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
264                label: Some("Ui pipeline layout"),
265                push_constant_ranges: &[],
266                bind_group_layouts: &[&global_layout.globals, &layout.locals, &layout.texture],
267            });
268
269        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
270            label: Some("UI pipeline"),
271            layout: Some(&render_pipeline_layout),
272            vertex: wgpu::VertexState {
273                module: vs_module,
274                entry_point: Some("main"),
275                buffers: &[Vertex::desc()],
276                compilation_options: Default::default(),
277            },
278            primitive: wgpu::PrimitiveState {
279                topology: wgpu::PrimitiveTopology::TriangleList,
280                strip_index_format: None,
281                front_face: wgpu::FrontFace::Ccw,
282                cull_mode: Some(wgpu::Face::Back),
283                unclipped_depth: false,
284                polygon_mode: wgpu::PolygonMode::Fill,
285                conservative: false,
286            },
287            depth_stencil: None,
288            multisample: wgpu::MultisampleState {
289                count: 1,
290                mask: !0,
291                alpha_to_coverage_enabled: false,
292            },
293            fragment: Some(wgpu::FragmentState {
294                module: fs_module,
295                entry_point: Some("main"),
296                targets: &[Some(wgpu::ColorTargetState {
297                    format: surface_config.format,
298                    blend: Some(wgpu::BlendState {
299                        color: wgpu::BlendComponent {
300                            src_factor: wgpu::BlendFactor::SrcAlpha,
301                            dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
302                            operation: wgpu::BlendOperation::Add,
303                        },
304                        alpha: wgpu::BlendComponent {
305                            src_factor: wgpu::BlendFactor::One,
306                            dst_factor: wgpu::BlendFactor::One,
307                            operation: wgpu::BlendOperation::Add,
308                        },
309                    }),
310                    write_mask: wgpu::ColorWrites::ALL,
311                })],
312                compilation_options: Default::default(),
313            }),
314            multiview: None,
315            cache: None,
316        });
317
318        Self {
319            pipeline: render_pipeline,
320        }
321    }
322}
323
324pub fn create_quad(
325    rect: Aabr<f32>,
326    uv_rect: Aabr<f32>,
327    color: Rgba<f32>,
328    mode: Mode,
329) -> Quad<Vertex> {
330    create_quad_vert_gradient(rect, uv_rect, color, color, mode)
331}
332
333pub fn create_quad_vert_gradient(
334    rect: Aabr<f32>,
335    uv_rect: Aabr<f32>,
336    top_color: Rgba<f32>,
337    bottom_color: Rgba<f32>,
338    mode: Mode,
339) -> Quad<Vertex> {
340    let top_color = top_color.into_array();
341    let bottom_color = bottom_color.into_array();
342
343    let center = if let Mode::ImageSourceNorth { .. } = mode {
344        uv_rect.center().into_array()
345    } else {
346        rect.center().into_array()
347    };
348    let scale = mode.scale().into_array();
349    let mode_val = mode.value();
350    let v = |pos, uv, color| Vertex {
351        pos,
352        uv,
353        center,
354        color,
355        scale,
356        mode: mode_val,
357    };
358    let aabr_to_lbrt = |aabr: Aabr<f32>| (aabr.min.x, aabr.min.y, aabr.max.x, aabr.max.y);
359
360    let (l, b, r, t) = aabr_to_lbrt(rect);
361    let (uv_l, uv_b, uv_r, uv_t) = aabr_to_lbrt(uv_rect);
362
363    match (uv_b > uv_t, uv_l > uv_r) {
364        (true, true) => Quad::new(
365            v([r, t], [uv_l, uv_b], top_color),
366            v([l, t], [uv_l, uv_t], top_color),
367            v([l, b], [uv_r, uv_t], bottom_color),
368            v([r, b], [uv_r, uv_b], bottom_color),
369        ),
370        (false, false) => Quad::new(
371            v([r, t], [uv_l, uv_b], top_color),
372            v([l, t], [uv_l, uv_t], top_color),
373            v([l, b], [uv_r, uv_t], bottom_color),
374            v([r, b], [uv_r, uv_b], bottom_color),
375        ),
376        _ => Quad::new(
377            v([r, t], [uv_r, uv_t], top_color),
378            v([l, t], [uv_l, uv_t], top_color),
379            v([l, b], [uv_l, uv_b], bottom_color),
380            v([r, b], [uv_r, uv_b], bottom_color),
381        ),
382    }
383}
384
385pub fn create_tri(
386    tri: [[f32; 2]; 3],
387    uv_tri: [[f32; 2]; 3],
388    color: Rgba<f32>,
389    mode: Mode,
390) -> Tri<Vertex> {
391    let center = [0.0, 0.0];
392    let scale = mode.scale().into_array();
393    let mode_val = mode.value();
394    let v = |pos, uv| Vertex {
395        pos,
396        uv,
397        center,
398        color: color.into_array(),
399        scale,
400        mode: mode_val,
401    };
402    Tri::new(
403        v([tri[0][0], tri[0][1]], [uv_tri[0][0], uv_tri[0][1]]),
404        v([tri[1][0], tri[1][1]], [uv_tri[1][0], uv_tri[1][1]]),
405        v([tri[2][0], tri[2][1]], [uv_tri[2][0], uv_tri[2][1]]),
406    )
407}
408
409// Premultiplying alpha on the GPU before placing images into the textures that
410// will be sampled from in the UI pipeline.
411//
412// Steps:
413//
414// 1. Upload new image via `Device::create_texture_with_data`.
415//
416//    (NOTE: Initially considered: Creating a storage buffer to read from in the
417//    shader via `Device::create_buffer_init`, with `MAP_WRITE` flag to avoid
418//    staging buffer. However, with GPUs combining usages other than `COPY_SRC`
419//    with `MAP_WRITE` may be less ideal. Plus, by copying into a texture first
420//    we can get free srgb conversion when fetching colors from the texture. In
421//    the future, we may want to branch based on the whether the GPU is
422//    integrated and avoid this extra copy.)
423//
424// 2. Run render pipeline to multiply by alpha reading from this texture and
425//    writing to the final texture (this can either be in an atlas or in an
426//    independent texture if the image is over a certain size threshold).
427//
428//    (NOTE: Initially considered: using a compute pipeline and writing to the
429//     final texture as a storage texture. However, the srgb format can't be
430//     used with storage texture and there is not yet the capability to create
431//     non-srgb views of srgb textures.)
432//
433// Info needed:
434//
435// * source texture (texture binding)
436// * target texture (render attachment)
437// * source image dimensions (push constant)
438// * target texture dimensions (push constant)
439// * position in the target texture (push constant)
440//
441// TODO: potential optimizations
442// * what is the overhead of this draw call call? at some point we may be better
443//   off converting very small images on the cpu and/or batching these into a
444//   single draw call
445// * what is the overhead of creating new small textures? for processing many
446//   small images would it be useful to create a single texture the same size as
447//   our cache texture and use Queue::write_texture?
448// * is using create_buffer_init and reading directly from that (with manual
449//   srgb conversion) worth avoiding staging buffer/copy-to-texture for
450//   integrated GPUs?
451// * premultipying alpha in a release asset preparation step
452
453pub struct PremultiplyAlphaLayout {
454    source_texture: wgpu::BindGroupLayout,
455}
456
457impl PremultiplyAlphaLayout {
458    pub fn new(device: &wgpu::Device) -> Self {
459        Self {
460            source_texture: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
461                label: None,
462                entries: &[
463                    // source_texture
464                    wgpu::BindGroupLayoutEntry {
465                        binding: 0,
466                        visibility: wgpu::ShaderStages::FRAGMENT,
467                        ty: wgpu::BindingType::Texture {
468                            sample_type: wgpu::TextureSampleType::Float { filterable: false },
469                            view_dimension: wgpu::TextureViewDimension::D2,
470                            multisampled: false,
471                        },
472                        count: None,
473                    },
474                ],
475            }),
476        }
477    }
478}
479
480pub struct PremultiplyAlphaPipeline {
481    pub pipeline: wgpu::RenderPipeline,
482}
483
484impl PremultiplyAlphaPipeline {
485    pub fn new(
486        device: &wgpu::Device,
487        vs_module: &wgpu::ShaderModule,
488        fs_module: &wgpu::ShaderModule,
489        layout: &PremultiplyAlphaLayout,
490    ) -> Self {
491        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
492            label: Some("Premultiply alpha pipeline layout"),
493            bind_group_layouts: &[&layout.source_texture],
494            push_constant_ranges: &[wgpu::PushConstantRange {
495                stages: wgpu::ShaderStages::VERTEX,
496                range: 0..core::mem::size_of::<PremultiplyAlphaParams>() as u32,
497            }],
498        });
499
500        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
501            label: Some("Premultiply alpha pipeline"),
502            layout: Some(&pipeline_layout),
503            vertex: wgpu::VertexState {
504                module: vs_module,
505                entry_point: Some("main"),
506                buffers: &[],
507                compilation_options: Default::default(),
508            },
509            primitive: wgpu::PrimitiveState {
510                topology: wgpu::PrimitiveTopology::TriangleList,
511                strip_index_format: None,
512                front_face: wgpu::FrontFace::Ccw,
513                cull_mode: Some(wgpu::Face::Back),
514                unclipped_depth: false,
515                polygon_mode: wgpu::PolygonMode::Fill,
516                conservative: false,
517            },
518            depth_stencil: None,
519            multisample: wgpu::MultisampleState::default(),
520            fragment: Some(wgpu::FragmentState {
521                module: fs_module,
522                entry_point: Some("main"),
523                targets: &[Some(wgpu::ColorTargetState {
524                    format: UI_IMAGE_FORMAT,
525                    blend: None,
526                    write_mask: wgpu::ColorWrites::ALL,
527                })],
528                compilation_options: Default::default(),
529            }),
530            multiview: None,
531            cache: None,
532        });
533
534        Self { pipeline }
535    }
536}
537
538/// Uploaded as push constant.
539#[repr(C)]
540#[derive(Copy, Clone, Debug, Zeroable, Pod)]
541pub struct PremultiplyAlphaParams {
542    /// Size of the source image.
543    source_size_xy: u32,
544    /// Offset to place the image at in the target texture.
545    ///
546    /// Origin is the top-left.
547    target_offset_xy: u32,
548    /// Size of the target texture.
549    target_size_xy: u32,
550}
551
552/// An image upload that needs alpha premultiplication and which is in a pending
553/// state.
554///
555/// From here we will use the `PremultiplyAlpha` pipeline to premultiply the
556/// alpha while transfering the image to its destination texture.
557pub(in super::super) struct PremultiplyUpload {
558    source_bg: wgpu::BindGroup,
559    source_size_xy: u32,
560    /// The location in the final texture this will be placed at. Technically,
561    /// we don't need this information at this point but it is convenient to
562    /// store it here.
563    offset: Vec2<u16>,
564}
565
566impl PremultiplyUpload {
567    pub(in super::super) fn prepare(
568        device: &wgpu::Device,
569        queue: &wgpu::Queue,
570        layout: &PremultiplyAlphaLayout,
571        image: &image::RgbaImage,
572        offset: Vec2<u16>,
573    ) -> Self {
574        // TODO: duplicating some code from `Texture` since:
575        // 1. We don't need to create a sampler.
576        // 2. Texture::new accepts &DynamicImage which isn't possible to create from
577        //    &RgbaImage without cloning. (this might be addressed on zoomy worldgen
578        //    branch)
579        let image_size = wgpu::Extent3d {
580            width: image.width(),
581            height: image.height(),
582            depth_or_array_layers: 1,
583        };
584        let source_tex = device.create_texture(&wgpu::TextureDescriptor {
585            label: None,
586            size: image_size,
587            mip_level_count: 1,
588            sample_count: 1,
589            dimension: wgpu::TextureDimension::D2,
590            format: wgpu::TextureFormat::Rgba8UnormSrgb,
591            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
592            view_formats: &[],
593        });
594        queue.write_texture(
595            wgpu::TexelCopyTextureInfo {
596                texture: &source_tex,
597                mip_level: 0,
598                origin: wgpu::Origin3d::ZERO,
599                aspect: wgpu::TextureAspect::All,
600            },
601            &(&**image)[..(image.width() as usize * image.height() as usize * 4)],
602            wgpu::TexelCopyBufferLayout {
603                offset: 0,
604                bytes_per_row: Some(image.width() * 4),
605                rows_per_image: Some(image.height()),
606            },
607            image_size,
608        );
609        // Create view to use to create bind group
610        let view = source_tex.create_view(&wgpu::TextureViewDescriptor {
611            label: None,
612            format: Some(wgpu::TextureFormat::Rgba8UnormSrgb),
613            dimension: Some(wgpu::TextureViewDimension::D2),
614            usage: None,
615            aspect: wgpu::TextureAspect::All,
616            base_mip_level: 0,
617            mip_level_count: None,
618            base_array_layer: 0,
619            array_layer_count: None,
620        });
621        let source_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
622            label: None,
623            layout: &layout.source_texture,
624            entries: &[wgpu::BindGroupEntry {
625                binding: 0,
626                resource: wgpu::BindingResource::TextureView(&view),
627            }],
628        });
629
630        // NOTE: We assume the max texture size is less than u16::MAX.
631        let source_size_xy = image_size.width + (image_size.height << 16);
632
633        Self {
634            source_bg,
635            source_size_xy,
636            offset,
637        }
638    }
639
640    /// Semantically, this consumes the `PremultiplyUpload` but we need to keep
641    /// the bind group alive to the end of the render pass and don't want to
642    /// bother storing it somewhere else.
643    pub(in super::super) fn draw_data(
644        &self,
645        target: &Texture,
646    ) -> (&wgpu::BindGroup, PremultiplyAlphaParams) {
647        let target_offset_xy = u32::from(self.offset.x) + (u32::from(self.offset.y) << 16);
648        let target_dims = target.get_dimensions();
649        // NOTE: We assume the max texture size is less than u16::MAX.
650        let target_size_xy = target_dims.x + (target_dims.y << 16);
651        (&self.source_bg, PremultiplyAlphaParams {
652            source_size_xy: self.source_size_xy,
653            target_offset_xy,
654            target_size_xy,
655        })
656    }
657}
658
659use std::sync::Arc;
660/// Per-target texture batched uploads
661#[derive(Default)]
662pub(in super::super) struct BatchedUploads {
663    batches: Vec<(Arc<Texture>, Vec<PremultiplyUpload>)>,
664}
665#[derive(Default, Clone, Copy)]
666pub struct UploadBatchId(usize);
667
668impl BatchedUploads {
669    /// Adds the provided upload to the batch indicated by the provided target
670    /// texture and optional batch id. A new batch will be created if the batch
671    /// id is invalid (doesn't refer to an existing batch) or the provided
672    /// target texture isn't the same as the one associated with the
673    /// provided batch id. Creating a new batch involves cloning the
674    /// provided texture `Arc`.
675    ///
676    /// The id of the batch where the upload is ultimately submitted will be
677    /// returned. This id can be used in subsequent calls to add items to
678    /// the same batch (i.e. uploads for the same texture).
679    ///
680    /// Batch ids will reset every frame, however since we check that the
681    /// texture matches, it is perfectly fine to use a stale id (just keep
682    /// in mind that this will create a new batch). This also means that it is
683    /// sufficient to use `UploadBatchId::default()` when calling this with
684    /// new textures.
685    pub(in super::super) fn submit(
686        &mut self,
687        target_texture: &Arc<Texture>,
688        batch_id: UploadBatchId,
689        upload: PremultiplyUpload,
690    ) -> UploadBatchId {
691        if let Some(batch) = self
692            .batches
693            .get_mut(batch_id.0)
694            .filter(|b| Arc::ptr_eq(&b.0, target_texture))
695        {
696            batch.1.push(upload);
697            batch_id
698        } else {
699            let new_batch_id = UploadBatchId(self.batches.len());
700            self.batches
701                .push((Arc::clone(target_texture), vec![upload]));
702            new_batch_id
703        }
704    }
705
706    pub(in super::super) fn take(&mut self) -> Vec<(Arc<Texture>, Vec<PremultiplyUpload>)> {
707        core::mem::take(&mut self.batches)
708    }
709}