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: "main",
275                buffers: &[Vertex::desc()],
276            },
277            primitive: wgpu::PrimitiveState {
278                topology: wgpu::PrimitiveTopology::TriangleList,
279                strip_index_format: None,
280                front_face: wgpu::FrontFace::Ccw,
281                cull_mode: Some(wgpu::Face::Back),
282                unclipped_depth: false,
283                polygon_mode: wgpu::PolygonMode::Fill,
284                conservative: false,
285            },
286            depth_stencil: None,
287            multisample: wgpu::MultisampleState {
288                count: 1,
289                mask: !0,
290                alpha_to_coverage_enabled: false,
291            },
292            fragment: Some(wgpu::FragmentState {
293                module: fs_module,
294                entry_point: "main",
295                targets: &[Some(wgpu::ColorTargetState {
296                    format: surface_config.format,
297                    blend: Some(wgpu::BlendState {
298                        color: wgpu::BlendComponent {
299                            src_factor: wgpu::BlendFactor::SrcAlpha,
300                            dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
301                            operation: wgpu::BlendOperation::Add,
302                        },
303                        alpha: wgpu::BlendComponent {
304                            src_factor: wgpu::BlendFactor::One,
305                            dst_factor: wgpu::BlendFactor::One,
306                            operation: wgpu::BlendOperation::Add,
307                        },
308                    }),
309                    write_mask: wgpu::ColorWrites::ALL,
310                })],
311            }),
312            multiview: None,
313        });
314
315        Self {
316            pipeline: render_pipeline,
317        }
318    }
319}
320
321pub fn create_quad(
322    rect: Aabr<f32>,
323    uv_rect: Aabr<f32>,
324    color: Rgba<f32>,
325    mode: Mode,
326) -> Quad<Vertex> {
327    create_quad_vert_gradient(rect, uv_rect, color, color, mode)
328}
329
330pub fn create_quad_vert_gradient(
331    rect: Aabr<f32>,
332    uv_rect: Aabr<f32>,
333    top_color: Rgba<f32>,
334    bottom_color: Rgba<f32>,
335    mode: Mode,
336) -> Quad<Vertex> {
337    let top_color = top_color.into_array();
338    let bottom_color = bottom_color.into_array();
339
340    let center = if let Mode::ImageSourceNorth { .. } = mode {
341        uv_rect.center().into_array()
342    } else {
343        rect.center().into_array()
344    };
345    let scale = mode.scale().into_array();
346    let mode_val = mode.value();
347    let v = |pos, uv, color| Vertex {
348        pos,
349        uv,
350        center,
351        color,
352        scale,
353        mode: mode_val,
354    };
355    let aabr_to_lbrt = |aabr: Aabr<f32>| (aabr.min.x, aabr.min.y, aabr.max.x, aabr.max.y);
356
357    let (l, b, r, t) = aabr_to_lbrt(rect);
358    let (uv_l, uv_b, uv_r, uv_t) = aabr_to_lbrt(uv_rect);
359
360    match (uv_b > uv_t, uv_l > uv_r) {
361        (true, true) => Quad::new(
362            v([r, t], [uv_l, uv_b], top_color),
363            v([l, t], [uv_l, uv_t], top_color),
364            v([l, b], [uv_r, uv_t], bottom_color),
365            v([r, b], [uv_r, uv_b], bottom_color),
366        ),
367        (false, false) => Quad::new(
368            v([r, t], [uv_l, uv_b], top_color),
369            v([l, t], [uv_l, uv_t], top_color),
370            v([l, b], [uv_r, uv_t], bottom_color),
371            v([r, b], [uv_r, uv_b], bottom_color),
372        ),
373        _ => Quad::new(
374            v([r, t], [uv_r, uv_t], top_color),
375            v([l, t], [uv_l, uv_t], top_color),
376            v([l, b], [uv_l, uv_b], bottom_color),
377            v([r, b], [uv_r, uv_b], bottom_color),
378        ),
379    }
380}
381
382pub fn create_tri(
383    tri: [[f32; 2]; 3],
384    uv_tri: [[f32; 2]; 3],
385    color: Rgba<f32>,
386    mode: Mode,
387) -> Tri<Vertex> {
388    let center = [0.0, 0.0];
389    let scale = mode.scale().into_array();
390    let mode_val = mode.value();
391    let v = |pos, uv| Vertex {
392        pos,
393        uv,
394        center,
395        color: color.into_array(),
396        scale,
397        mode: mode_val,
398    };
399    Tri::new(
400        v([tri[0][0], tri[0][1]], [uv_tri[0][0], uv_tri[0][1]]),
401        v([tri[1][0], tri[1][1]], [uv_tri[1][0], uv_tri[1][1]]),
402        v([tri[2][0], tri[2][1]], [uv_tri[2][0], uv_tri[2][1]]),
403    )
404}
405
406// Premultiplying alpha on the GPU before placing images into the textures that
407// will be sampled from in the UI pipeline.
408//
409// Steps:
410//
411// 1. Upload new image via `Device::create_texture_with_data`.
412//
413//    (NOTE: Initially considered: Creating a storage buffer to read from in the
414//    shader via `Device::create_buffer_init`, with `MAP_WRITE` flag to avoid
415//    staging buffer. However, with GPUs combining usages other than `COPY_SRC`
416//    with `MAP_WRITE` may be less ideal. Plus, by copying into a texture first
417//    we can get free srgb conversion when fetching colors from the texture. In
418//    the future, we may want to branch based on the whether the GPU is
419//    integrated and avoid this extra copy.)
420//
421// 2. Run render pipeline to multiply by alpha reading from this texture and
422//    writing to the final texture (this can either be in an atlas or in an
423//    independent texture if the image is over a certain size threshold).
424//
425//    (NOTE: Initially considered: using a compute pipeline and writing to the
426//     final texture as a storage texture. However, the srgb format can't be
427//     used with storage texture and there is not yet the capability to create
428//     non-srgb views of srgb textures.)
429//
430// Info needed:
431//
432// * source texture (texture binding)
433// * target texture (render attachment)
434// * source image dimensions (push constant)
435// * target texture dimensions (push constant)
436// * position in the target texture (push constant)
437//
438// TODO: potential optimizations
439// * what is the overhead of this draw call call? at some point we may be better
440//   off converting very small images on the cpu and/or batching these into a
441//   single draw call
442// * what is the overhead of creating new small textures? for processing many
443//   small images would it be useful to create a single texture the same size as
444//   our cache texture and use Queue::write_texture?
445// * is using create_buffer_init and reading directly from that (with manual
446//   srgb conversion) worth avoiding staging buffer/copy-to-texture for
447//   integrated GPUs?
448// * premultipying alpha in a release asset preparation step
449
450pub struct PremultiplyAlphaLayout {
451    source_texture: wgpu::BindGroupLayout,
452}
453
454impl PremultiplyAlphaLayout {
455    pub fn new(device: &wgpu::Device) -> Self {
456        Self {
457            source_texture: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
458                label: None,
459                entries: &[
460                    // source_texture
461                    wgpu::BindGroupLayoutEntry {
462                        binding: 0,
463                        visibility: wgpu::ShaderStages::FRAGMENT,
464                        ty: wgpu::BindingType::Texture {
465                            sample_type: wgpu::TextureSampleType::Float { filterable: false },
466                            view_dimension: wgpu::TextureViewDimension::D2,
467                            multisampled: false,
468                        },
469                        count: None,
470                    },
471                ],
472            }),
473        }
474    }
475}
476
477pub struct PremultiplyAlphaPipeline {
478    pub pipeline: wgpu::RenderPipeline,
479}
480
481impl PremultiplyAlphaPipeline {
482    pub fn new(
483        device: &wgpu::Device,
484        vs_module: &wgpu::ShaderModule,
485        fs_module: &wgpu::ShaderModule,
486        layout: &PremultiplyAlphaLayout,
487    ) -> Self {
488        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
489            label: Some("Premultiply alpha pipeline layout"),
490            bind_group_layouts: &[&layout.source_texture],
491            push_constant_ranges: &[wgpu::PushConstantRange {
492                stages: wgpu::ShaderStages::VERTEX,
493                range: 0..core::mem::size_of::<PremultiplyAlphaParams>() as u32,
494            }],
495        });
496
497        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
498            label: Some("Premultiply alpha pipeline"),
499            layout: Some(&pipeline_layout),
500            vertex: wgpu::VertexState {
501                module: vs_module,
502                entry_point: "main",
503                buffers: &[],
504            },
505            primitive: wgpu::PrimitiveState {
506                topology: wgpu::PrimitiveTopology::TriangleList,
507                strip_index_format: None,
508                front_face: wgpu::FrontFace::Ccw,
509                cull_mode: Some(wgpu::Face::Back),
510                unclipped_depth: false,
511                polygon_mode: wgpu::PolygonMode::Fill,
512                conservative: false,
513            },
514            depth_stencil: None,
515            multisample: wgpu::MultisampleState::default(),
516            fragment: Some(wgpu::FragmentState {
517                module: fs_module,
518                entry_point: "main",
519                targets: &[Some(wgpu::ColorTargetState {
520                    format: UI_IMAGE_FORMAT,
521                    blend: None,
522                    write_mask: wgpu::ColorWrites::ALL,
523                })],
524            }),
525            multiview: None,
526        });
527
528        Self { pipeline }
529    }
530}
531
532/// Uploaded as push constant.
533#[repr(C)]
534#[derive(Copy, Clone, Debug, Zeroable, Pod)]
535pub struct PremultiplyAlphaParams {
536    /// Size of the source image.
537    source_size_xy: u32,
538    /// Offset to place the image at in the target texture.
539    ///
540    /// Origin is the top-left.
541    target_offset_xy: u32,
542    /// Size of the target texture.
543    target_size_xy: u32,
544}
545
546/// An image upload that needs alpha premultiplication and which is in a pending
547/// state.
548///
549/// From here we will use the `PremultiplyAlpha` pipeline to premultiply the
550/// alpha while transfering the image to its destination texture.
551pub(in super::super) struct PremultiplyUpload {
552    source_bg: wgpu::BindGroup,
553    source_size_xy: u32,
554    /// The location in the final texture this will be placed at. Technically,
555    /// we don't need this information at this point but it is convenient to
556    /// store it here.
557    offset: Vec2<u16>,
558}
559
560impl PremultiplyUpload {
561    pub(in super::super) fn prepare(
562        device: &wgpu::Device,
563        queue: &wgpu::Queue,
564        layout: &PremultiplyAlphaLayout,
565        image: &image::RgbaImage,
566        offset: Vec2<u16>,
567    ) -> Self {
568        // TODO: duplicating some code from `Texture` since:
569        // 1. We don't need to create a sampler.
570        // 2. Texture::new accepts &DynamicImage which isn't possible to create from
571        //    &RgbaImage without cloning. (this might be addressed on zoomy worldgen
572        //    branch)
573        let image_size = wgpu::Extent3d {
574            width: image.width(),
575            height: image.height(),
576            depth_or_array_layers: 1,
577        };
578        let source_tex = device.create_texture(&wgpu::TextureDescriptor {
579            label: None,
580            size: image_size,
581            mip_level_count: 1,
582            sample_count: 1,
583            dimension: wgpu::TextureDimension::D2,
584            format: wgpu::TextureFormat::Rgba8UnormSrgb,
585            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
586            view_formats: &[],
587        });
588        queue.write_texture(
589            wgpu::ImageCopyTexture {
590                texture: &source_tex,
591                mip_level: 0,
592                origin: wgpu::Origin3d::ZERO,
593                aspect: wgpu::TextureAspect::All,
594            },
595            &(&**image)[..(image.width() as usize * image.height() as usize * 4)],
596            wgpu::ImageDataLayout {
597                offset: 0,
598                bytes_per_row: Some(image.width() * 4),
599                rows_per_image: Some(image.height()),
600            },
601            image_size,
602        );
603        // Create view to use to create bind group
604        let view = source_tex.create_view(&wgpu::TextureViewDescriptor {
605            label: None,
606            format: Some(wgpu::TextureFormat::Rgba8UnormSrgb),
607            dimension: Some(wgpu::TextureViewDimension::D2),
608            aspect: wgpu::TextureAspect::All,
609            base_mip_level: 0,
610            mip_level_count: None,
611            base_array_layer: 0,
612            array_layer_count: None,
613        });
614        let source_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
615            label: None,
616            layout: &layout.source_texture,
617            entries: &[wgpu::BindGroupEntry {
618                binding: 0,
619                resource: wgpu::BindingResource::TextureView(&view),
620            }],
621        });
622
623        // NOTE: We assume the max texture size is less than u16::MAX.
624        let source_size_xy = image_size.width + (image_size.height << 16);
625
626        Self {
627            source_bg,
628            source_size_xy,
629            offset,
630        }
631    }
632
633    /// Semantically, this consumes the `PremultiplyUpload` but we need to keep
634    /// the bind group alive to the end of the render pass and don't want to
635    /// bother storing it somewhere else.
636    pub(in super::super) fn draw_data(
637        &self,
638        target: &Texture,
639    ) -> (&wgpu::BindGroup, PremultiplyAlphaParams) {
640        let target_offset_xy = u32::from(self.offset.x) + (u32::from(self.offset.y) << 16);
641        let target_dims = target.get_dimensions();
642        // NOTE: We assume the max texture size is less than u16::MAX.
643        let target_size_xy = target_dims.x + (target_dims.y << 16);
644        (&self.source_bg, PremultiplyAlphaParams {
645            source_size_xy: self.source_size_xy,
646            target_offset_xy,
647            target_size_xy,
648        })
649    }
650}
651
652use std::sync::Arc;
653/// Per-target texture batched uploads
654#[derive(Default)]
655pub(in super::super) struct BatchedUploads {
656    batches: Vec<(Arc<Texture>, Vec<PremultiplyUpload>)>,
657}
658#[derive(Default, Clone, Copy)]
659pub struct UploadBatchId(usize);
660
661impl BatchedUploads {
662    /// Adds the provided upload to the batch indicated by the provided target
663    /// texture and optional batch id. A new batch will be created if the batch
664    /// id is invalid (doesn't refer to an existing batch) or the provided
665    /// target texture isn't the same as the one associated with the
666    /// provided batch id. Creating a new batch involves cloning the
667    /// provided texture `Arc`.
668    ///
669    /// The id of the batch where the upload is ultimately submitted will be
670    /// returned. This id can be used in subsequent calls to add items to
671    /// the same batch (i.e. uploads for the same texture).
672    ///
673    /// Batch ids will reset every frame, however since we check that the
674    /// texture matches, it is perfectly fine to use a stale id (just keep
675    /// in mind that this will create a new batch). This also means that it is
676    /// sufficient to use `UploadBatchId::default()` when calling this with
677    /// new textures.
678    pub(in super::super) fn submit(
679        &mut self,
680        target_texture: &Arc<Texture>,
681        batch_id: UploadBatchId,
682        upload: PremultiplyUpload,
683    ) -> UploadBatchId {
684        if let Some(batch) = self
685            .batches
686            .get_mut(batch_id.0)
687            .filter(|b| Arc::ptr_eq(&b.0, target_texture))
688        {
689            batch.1.push(upload);
690            batch_id
691        } else {
692            let new_batch_id = UploadBatchId(self.batches.len());
693            self.batches
694                .push((Arc::clone(target_texture), vec![upload]));
695            new_batch_id
696        }
697    }
698
699    pub(in super::super) fn take(&mut self) -> Vec<(Arc<Texture>, Vec<PremultiplyUpload>)> {
700        core::mem::take(&mut self.batches)
701    }
702}