mireforge_render_wgpu/
lib.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/mireforge/mireforge
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5mod gfx;
6mod gfx_impl;
7pub mod plugin;
8pub mod prelude;
9
10use int_math::{URect, UVec2, Vec2, Vec3};
11use limnus_assets::prelude::{Asset, Id, WeakId};
12use limnus_assets::Assets;
13use limnus_resource::prelude::Resource;
14use limnus_wgpu_math::{Matrix4, OrthoInfo, Vec4};
15use mireforge_font::Font;
16use mireforge_font::FontRef;
17use mireforge_font::WeakFontRef;
18use mireforge_render::prelude::*;
19use mireforge_wgpu::create_nearest_sampler;
20use mireforge_wgpu_sprites::{
21    create_texture_and_sampler_bind_group_ex, create_texture_and_sampler_group_layout, ShaderInfo, SpriteInfo,
22    SpriteInstanceUniform,
23};
24use monotonic_time_rs::Millis;
25use std::cmp::Ordering;
26use std::fmt::{Debug, Display, Formatter};
27use std::mem::swap;
28use std::sync::Arc;
29use tracing::{debug, trace};
30use wgpu::{
31    BindGroup, BindGroupLayout, Buffer, CommandEncoder, Device, RenderPipeline, TextureFormat,
32    TextureView,
33};
34
35pub type MaterialRef = Arc<Material>;
36
37pub type WeakMaterialRef = Arc<Material>;
38
39pub type TextureRef = Id<Texture>;
40pub type WeakTextureRef = WeakId<Texture>;
41
42pub trait FrameLookup {
43    fn lookup(&self, frame: u16) -> (&MaterialRef, URect);
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct FixedAtlas {
48    pub material: MaterialRef,
49    pub texture_size: UVec2,
50    pub one_cell_size: UVec2,
51    pub cell_count_size: UVec2,
52}
53
54impl FixedAtlas {
55    /// # Panics
56    ///
57    #[must_use]
58    pub fn new(one_cell_size: UVec2, texture_size: UVec2, material_ref: MaterialRef) -> Self {
59        let cell_count_size = UVec2::new(
60            texture_size.x / one_cell_size.x,
61            texture_size.y / one_cell_size.y,
62        );
63
64        assert_ne!(cell_count_size.x, 0, "illegal texture and one cell size");
65
66        Self {
67            material: material_ref,
68            texture_size,
69            one_cell_size,
70            cell_count_size,
71        }
72    }
73}
74
75impl FrameLookup for FixedAtlas {
76    fn lookup(&self, frame: u16) -> (&MaterialRef, URect) {
77        let x = frame % self.cell_count_size.x;
78        let y = frame / self.cell_count_size.x;
79
80        (
81            &self.material,
82            URect::new(
83                x * self.one_cell_size.x,
84                y * self.one_cell_size.y,
85                self.one_cell_size.x,
86                self.one_cell_size.y,
87            ),
88        )
89    }
90}
91
92#[derive(Debug)]
93pub struct NineSliceAndMaterial {
94    pub slices: Slices,
95    pub material_ref: MaterialRef,
96}
97
98#[derive(Debug, PartialEq, Eq, Clone)]
99pub struct FontAndMaterial {
100    pub font_ref: FontRef,
101    pub material_ref: MaterialRef,
102}
103
104fn to_wgpu_color(c: Color) -> wgpu::Color {
105    let f = c.to_f64();
106    wgpu::Color {
107        r: f.0,
108        g: f.1,
109        b: f.2,
110        a: f.3,
111    }
112}
113
114#[derive(Debug)]
115struct RenderItem {
116    position: Vec3,
117    material_ref: MaterialRef,
118
119    renderable: Renderable,
120}
121
122#[derive(Debug)]
123pub struct Text {
124    text: String,
125    font_ref: WeakFontRef,
126    color: Color,
127}
128
129#[derive(Debug)]
130enum Renderable {
131    Sprite(Sprite),
132    QuadColor(QuadColor),
133    NineSlice(NineSlice),
134    NineSliceStretch(NineSlice),
135    TileMap(TileMap),
136    Text(Text),
137    Mask(UVec2, Color),
138}
139
140const MAXIMUM_QUADS_FOR_RENDER_ITEM: usize = 1024;
141const MAXIMUM_QUADS_IN_A_BATCH: usize = 4096;
142const MAXIMUM_QUADS_IN_ONE_RENDER: usize = MAXIMUM_QUADS_IN_A_BATCH * 8;
143
144#[derive(Resource)]
145pub struct Render {
146    virtual_surface_texture_view: TextureView,
147    virtual_surface_texture: wgpu::Texture,
148    virtual_to_surface_bind_group: BindGroup,
149    index_buffer: Buffer,  // Only indices for a single identity quad
150    vertex_buffer: Buffer, // Only one identity quad (0,0,1,1)
151    sampler: wgpu::Sampler,
152    virtual_to_screen_shader_info: ShaderInfo,
153    pub normal_sprite_pipeline: ShaderInfo,
154    pub quad_shader_info: ShaderInfo,
155    pub mask_shader_info: ShaderInfo,
156    pub light_shader_info: ShaderInfo,
157    physical_surface_size: UVec2,
158    viewport_strategy: ViewportStrategy,
159    virtual_surface_size: UVec2,
160    // Group 0
161    camera_bind_group: BindGroup,
162    #[allow(unused)]
163    camera_buffer: Buffer,
164
165    // Group 1
166    texture_sampler_bind_group_layout: BindGroupLayout,
167
168    // Group 1
169    quad_matrix_and_uv_instance_buffer: Buffer,
170
171    device: Arc<wgpu::Device>,
172    queue: Arc<wgpu::Queue>, // Queue to talk to device
173
174    // Internals
175    items: Vec<RenderItem>,
176    //fonts: Vec<FontAndMaterialRef>,
177    origin: Vec2,
178
179    // Cache
180    batch_offsets: Vec<(WeakMaterialRef, u32, u32)>,
181    viewport: URect,
182    clear_color: wgpu::Color,
183    screen_clear_color: wgpu::Color,
184    last_render_at: Millis,
185    scale: f32,
186    surface_texture_format: TextureFormat,
187    debug_tick: u64,
188}
189
190impl Render {}
191
192impl Debug for Render {
193    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194        write!(f, "Render")
195    }
196}
197
198impl Render {
199    #[must_use]
200    pub fn new(
201        device: Arc<wgpu::Device>,
202        queue: Arc<wgpu::Queue>, // Queue to talk to device
203        surface_texture_format: wgpu::TextureFormat,
204        physical_size: UVec2,
205        virtual_surface_size: UVec2,
206        now: Millis,
207    ) -> Self {
208        let sprite_info = SpriteInfo::new(
209            &device,
210            surface_texture_format,
211            create_view_uniform_view_projection_matrix(physical_size),
212        );
213
214        let (virtual_surface_texture, virtual_surface_texture_view, virtual_to_surface_bind_group) =
215            Self::create_virtual_texture(&device, surface_texture_format, virtual_surface_size);
216
217        Self {
218            device,
219            queue,
220            surface_texture_format,
221            items: Vec::new(),
222            //   fonts: Vec::new(),
223            virtual_to_screen_shader_info: sprite_info.virtual_to_screen_shader_info,
224            virtual_surface_texture,
225            virtual_surface_texture_view,
226            virtual_to_surface_bind_group,
227            sampler: sprite_info.sampler,
228            normal_sprite_pipeline: sprite_info.sprite_shader_info,
229            quad_shader_info: sprite_info.quad_shader_info,
230            mask_shader_info: sprite_info.mask_shader_info,
231            light_shader_info: sprite_info.light_shader_info,
232            texture_sampler_bind_group_layout: sprite_info.sprite_texture_sampler_bind_group_layout,
233            index_buffer: sprite_info.index_buffer,
234            vertex_buffer: sprite_info.vertex_buffer,
235            quad_matrix_and_uv_instance_buffer: sprite_info.quad_matrix_and_uv_instance_buffer,
236            camera_bind_group: sprite_info.camera_bind_group,
237            batch_offsets: Vec::new(),
238            camera_buffer: sprite_info.camera_uniform_buffer,
239            viewport: Self::viewport_from_integer_scale(physical_size, virtual_surface_size),
240            clear_color: to_wgpu_color(Color::from_f32(0.008, 0.015, 0.008, 1.0)),
241            screen_clear_color: to_wgpu_color(Color::from_f32(0.018, 0.025, 0.018, 1.0)),
242            origin: Vec2::new(0, 0),
243            last_render_at: now,
244            physical_surface_size: physical_size,
245            viewport_strategy: ViewportStrategy::FitIntegerScaling,
246            virtual_surface_size,
247            scale: 1.0,
248            debug_tick: 0,
249        }
250    }
251
252    #[must_use]
253    pub fn create_virtual_texture(
254        device: &Device,
255        surface_texture_format: TextureFormat,
256        virtual_surface_size: UVec2,
257    ) -> (wgpu::Texture, TextureView, BindGroup) {
258        // Create a texture at your virtual resolution (e.g., 320x240)
259        let virtual_surface_texture = device.create_texture(&wgpu::TextureDescriptor {
260            label: Some("Render Texture"),
261            size: wgpu::Extent3d {
262                width: u32::from(virtual_surface_size.x),
263                height: u32::from(virtual_surface_size.y),
264                depth_or_array_layers: 1,
265            },
266            mip_level_count: 1,
267            sample_count: 1,
268            dimension: wgpu::TextureDimension::D2,
269            format: surface_texture_format, // TODO: Check: Should probably always be same as swap chain format?
270            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
271            view_formats: &[],
272        });
273
274        let virtual_surface_texture_view =
275            virtual_surface_texture.create_view(&wgpu::TextureViewDescriptor::default());
276
277        let virtual_to_screen_sampler =
278            create_nearest_sampler(device, "nearest sampler for virtual to screen");
279        let virtual_to_screen_layout =
280            create_texture_and_sampler_group_layout(device, "virtual to screen layout");
281        let virtual_to_surface_bind_group = create_texture_and_sampler_bind_group_ex(
282            device,
283            &virtual_to_screen_layout,
284            &virtual_surface_texture_view,
285            &virtual_to_screen_sampler,
286            "virtual to screen bind group",
287        );
288
289        (
290            virtual_surface_texture,
291            virtual_surface_texture_view,
292            virtual_to_surface_bind_group,
293        )
294    }
295
296    pub const fn set_now(&mut self, now: Millis) {
297        self.last_render_at = now;
298    }
299
300    #[must_use]
301    pub const fn virtual_surface_size_with_scaling(&self) -> UVec2 {
302        match self.viewport_strategy {
303            ViewportStrategy::FitIntegerScaling | ViewportStrategy::FitFloatScaling => {
304                self.virtual_surface_size
305            }
306            ViewportStrategy::MatchPhysicalSize => self.physical_surface_size,
307        }
308    }
309
310    #[must_use]
311    pub const fn physical_surface_size(&self) -> UVec2 {
312        self.physical_surface_size
313    }
314
315    #[must_use]
316    pub const fn viewport(&self) -> URect {
317        self.viewport
318    }
319
320    #[inline]
321    fn push_sprite(&mut self, position: Vec3, material: &MaterialRef, sprite: Sprite) {
322        self.items.push(RenderItem {
323            position,
324            material_ref: material.clone(),
325            renderable: Renderable::Sprite(sprite),
326        });
327    }
328
329    pub fn push_mask(
330        &mut self,
331        position: Vec3,
332        size: UVec2,
333        color: Color,
334        alpha_masked: &MaterialRef,
335    ) {
336        self.items.push(RenderItem {
337            position,
338            material_ref: alpha_masked.clone(),
339            renderable: Renderable::Mask(size, color),
340        });
341    }
342
343    pub fn push_mask_create_material(
344        &mut self,
345        position: Vec3,
346        primary_texture: TextureRef,
347        alpha_texture: TextureRef,
348        texture_offset: UVec2,
349        color: Color,
350    ) {
351        let masked_material = Material {
352            base: MaterialBase {},
353            kind: MaterialKind::AlphaMasker {
354                primary_texture,
355                alpha_texture,
356            },
357        };
358
359        let masked_material_ref = Arc::new(masked_material);
360
361        self.items.push(RenderItem {
362            position,
363            material_ref: masked_material_ref,
364            renderable: Renderable::Mask(texture_offset, color),
365        });
366    }
367
368    pub fn push_nine_slice(
369        &mut self,
370        position: Vec3,
371        size: UVec2,
372        color: Color,
373        nine_slice_and_material: &NineSliceAndMaterial,
374    ) {
375        let nine_slice_info = NineSlice {
376            size,
377            slices: nine_slice_and_material.slices,
378            color,
379            origin_in_atlas: UVec2::new(0, 0),
380            size_inside_atlas: None,
381        };
382
383        self.items.push(RenderItem {
384            position,
385            material_ref: nine_slice_and_material.material_ref.clone(),
386            renderable: Renderable::NineSlice(nine_slice_info),
387        });
388    }
389
390    pub fn push_nine_slice_stretch(
391        &mut self,
392        position: Vec3,
393        size: UVec2,
394        color: Color,
395        nine_slice_and_material: &NineSliceAndMaterial,
396    ) {
397        let nine_slice_info = NineSlice {
398            size,
399            slices: nine_slice_and_material.slices,
400            color,
401            origin_in_atlas: UVec2::new(0, 0),
402            size_inside_atlas: None,
403        };
404
405        self.items.push(RenderItem {
406            position,
407            material_ref: nine_slice_and_material.material_ref.clone(),
408            renderable: Renderable::NineSliceStretch(nine_slice_info),
409        });
410    }
411
412    #[must_use]
413    pub fn viewport_from_integer_scale(physical_size: UVec2, virtual_size: UVec2) -> URect {
414        let scale_factor = (physical_size.x / virtual_size.x)
415            .min(physical_size.y / virtual_size.y)
416            .max(1);
417
418        let ideal_viewport_size = virtual_size * scale_factor;
419
420        let final_viewport_size =
421            if physical_size.x < ideal_viewport_size.x || physical_size.y < ideal_viewport_size.y {
422                physical_size
423            } else {
424                ideal_viewport_size
425            };
426
427        let border_size = physical_size - final_viewport_size;
428
429        let offset = border_size / 2;
430
431        URect::new(
432            offset.x,
433            offset.y,
434            final_viewport_size.x,
435            final_viewport_size.y,
436        )
437    }
438
439    #[must_use]
440    pub fn viewport_from_float_scale(physical_size: UVec2, virtual_size: UVec2) -> URect {
441        let window_aspect = f32::from(physical_size.x) / f32::from(physical_size.y);
442        let virtual_aspect = f32::from(virtual_size.x) / f32::from(virtual_size.y);
443
444        if physical_size.x < virtual_size.x || physical_size.y < virtual_size.y {
445            return URect::new(0, 0, physical_size.x, physical_size.y);
446        }
447
448        let mut float_scale = if window_aspect > virtual_aspect {
449            f32::from(physical_size.y) / f32::from(virtual_size.y)
450        } else {
451            f32::from(physical_size.x) / f32::from(virtual_size.x)
452        };
453
454        if float_scale < 0.01 {
455            float_scale = 0.01;
456        }
457
458        let viewport_actual_size = UVec2::new(
459            (f32::from(virtual_size.x) * float_scale) as u16,
460            (f32::from(virtual_size.y) * float_scale) as u16,
461        );
462
463        let border_size = physical_size - viewport_actual_size;
464
465        let offset = border_size / 2;
466
467        URect::new(
468            offset.x,
469            offset.y,
470            viewport_actual_size.x,
471            viewport_actual_size.y,
472        )
473    }
474
475    pub const fn resize(&mut self, physical_size: UVec2) {
476        self.physical_surface_size = physical_size;
477    }
478
479    pub fn resize_virtual(&mut self, virtual_surface_size: UVec2) {
480        if virtual_surface_size == self.virtual_surface_size {
481            return;
482        }
483        self.virtual_surface_size = virtual_surface_size;
484        debug!(?virtual_surface_size, "virtual surface changed");
485        let (virtual_surface_texture, virtual_surface_texture_view, virtual_to_surface_bind_group) =
486            Self::create_virtual_texture(
487                &self.device,
488                self.surface_texture_format,
489                virtual_surface_size,
490            );
491        self.virtual_surface_texture = virtual_surface_texture;
492        self.virtual_surface_texture_view = virtual_surface_texture_view;
493        self.virtual_to_surface_bind_group = virtual_to_surface_bind_group;
494    }
495
496    pub fn sprite_atlas(&mut self, position: Vec3, atlas_rect: URect, material_ref: &MaterialRef) {
497        self.push_sprite(
498            position,
499            material_ref,
500            Sprite {
501                params: SpriteParams {
502                    texture_pos: atlas_rect.position,
503                    texture_size: atlas_rect.size,
504                    ..Default::default()
505                },
506            },
507        );
508    }
509
510    pub fn sprite_atlas_frame(&mut self, position: Vec3, frame: u16, atlas: &impl FrameLookup) {
511        let (material_ref, atlas_rect) = atlas.lookup(frame);
512        self.push_sprite(
513            position,
514            material_ref,
515            Sprite {
516                params: SpriteParams {
517                    texture_pos: atlas_rect.position,
518                    texture_size: atlas_rect.size,
519                    ..Default::default()
520                },
521            },
522        );
523    }
524
525    pub fn sprite_atlas_frame_ex(
526        &mut self,
527        position: Vec3,
528        frame: u16,
529        atlas: &impl FrameLookup,
530        mut params: SpriteParams,
531    ) {
532        let (material_ref, atlas_rect) = atlas.lookup(frame);
533        params.texture_pos = atlas_rect.position;
534        params.texture_size = atlas_rect.size;
535        self.push_sprite(position, material_ref, Sprite { params });
536    }
537
538    pub fn draw_sprite(&mut self, position: Vec3, material: &MaterialRef) {
539        self.push_sprite(
540            position,
541            material,
542            Sprite {
543                params: SpriteParams::default(),
544            },
545        );
546    }
547
548    pub fn draw_sprite_ex(&mut self, position: Vec3, material: &MaterialRef, params: SpriteParams) {
549        self.push_sprite(position, material, Sprite { params });
550    }
551
552    pub fn nine_slice(
553        &mut self,
554        position: Vec3,
555        size: UVec2,
556        color: Color,
557        nine_slice_and_material: &NineSliceAndMaterial,
558    ) {
559        self.push_nine_slice(position, size, color, nine_slice_and_material);
560    }
561
562    pub fn nine_slice_stretch(
563        &mut self,
564        position: Vec3,
565        size: UVec2,
566        color: Color,
567        nine_slice_and_material: &NineSliceAndMaterial,
568    ) {
569        self.push_nine_slice_stretch(position, size, color, nine_slice_and_material);
570    }
571
572    pub fn draw_quad(&mut self, position: Vec3, size: UVec2, color: Color) {
573        let material = Material {
574            base: MaterialBase {},
575            kind: MaterialKind::Quad,
576        };
577
578        self.items.push(RenderItem {
579            position,
580            material_ref: MaterialRef::from(material),
581            renderable: Renderable::QuadColor(QuadColor {
582                size,
583                color,
584                params: QuadParams::default(),
585            }),
586        });
587    }
588
589    pub fn draw_quad_ex(&mut self, position: Vec3, size: UVec2, color: Color, params: QuadParams) {
590        let material = Material {
591            base: MaterialBase {},
592            kind: MaterialKind::Quad,
593        };
594
595        self.items.push(RenderItem {
596            position,
597            material_ref: MaterialRef::from(material),
598            renderable: Renderable::QuadColor(QuadColor {
599                size,
600                color,
601                params,
602            }),
603        });
604    }
605
606    #[allow(clippy::too_many_arguments)]
607    pub fn draw_nine_slice(
608        &mut self,
609        position: Vec3,
610        size: UVec2,
611        slices: Slices,
612        material_ref: &MaterialRef,
613        color: Color,
614    ) {
615        self.items.push(RenderItem {
616            position,
617            material_ref: material_ref.clone(),
618            renderable: Renderable::NineSlice(NineSlice {
619                size,
620                slices,
621                color,
622                origin_in_atlas: UVec2::new(0, 0),
623                size_inside_atlas: None,
624            }),
625        });
626    }
627
628    #[must_use]
629    pub const fn clear_color(&self) -> wgpu::Color {
630        self.clear_color
631    }
632
633    // first two is multiplier and second pair is offset
634    fn calculate_texture_coords_mul_add(atlas_rect: URect, texture_size: UVec2) -> Vec4 {
635        let x = f32::from(atlas_rect.position.x) / f32::from(texture_size.x);
636        let y = f32::from(atlas_rect.position.y) / f32::from(texture_size.y);
637        let width = f32::from(atlas_rect.size.x) / f32::from(texture_size.x);
638        let height = f32::from(atlas_rect.size.y) / f32::from(texture_size.y);
639        Vec4([width, height, x, y])
640    }
641
642    fn order_render_items_in_batches(&mut self) -> Vec<Vec<&RenderItem>> {
643        let mut material_batches: Vec<Vec<&RenderItem>> = Vec::new();
644        let mut current_batch: Vec<&RenderItem> = Vec::new();
645        let mut current_material: Option<MaterialRef> = None;
646
647        for render_item in &self.items {
648            if Some(&render_item.material_ref) != current_material.as_ref() {
649                if !current_batch.is_empty() {
650                    material_batches.push(current_batch.clone());
651                    current_batch.clear();
652                }
653                current_material = Some(render_item.material_ref.clone());
654            }
655            current_batch.push(render_item);
656        }
657
658        if !current_batch.is_empty() {
659            material_batches.push(current_batch);
660        }
661
662        material_batches
663    }
664
665    #[must_use]
666    pub fn quad_helper_uniform(
667        position: Vec3,
668        quad_size: UVec2,
669        render_atlas: URect,
670        color: Color,
671
672        current_texture_size: UVec2,
673    ) -> SpriteInstanceUniform {
674        let model_matrix =
675            Matrix4::from_translation(f32::from(position.x), f32::from(position.y), 0.0)
676                * Matrix4::from_scale(f32::from(quad_size.x), f32::from(quad_size.y), 1.0);
677
678        let tex_coords_mul_add =
679            Self::calculate_texture_coords_mul_add(render_atlas, current_texture_size);
680
681        let rotation_value = 0;
682
683        SpriteInstanceUniform::new(
684            model_matrix,
685            tex_coords_mul_add,
686            rotation_value,
687            Vec4(color.to_f32_slice()),
688        )
689    }
690
691    /// # Panics
692    ///
693    #[allow(clippy::too_many_lines)]
694    pub fn write_vertex_indices_and_uv_to_buffer(
695        &mut self,
696        textures: &Assets<Texture>,
697        fonts: &Assets<Font>,
698    ) {
699        const FLIP_X_MASK: u32 = 0b0000_0100;
700        const FLIP_Y_MASK: u32 = 0b0000_1000;
701
702        let batches = self.sort_and_put_in_batches();
703
704        let mut quad_matrix_and_uv: Vec<SpriteInstanceUniform> = Vec::new();
705        let mut batch_vertex_ranges: Vec<(MaterialRef, u32, u32)> = Vec::new();
706
707        for render_items in batches {
708            let quad_len_before = quad_matrix_and_uv.len();
709
710            // Fix: Access material_ref through reference and copy it
711            let weak_material_ref = render_items
712                .first()
713                .map(|item| {
714                    // Force copy semantics by dereferencing the shared reference
715                    let material_ref: MaterialRef = item.material_ref.clone();
716                    material_ref
717                })
718                .expect("Render items batch was empty");
719
720            if !weak_material_ref.is_complete(textures) {
721                // Material is not loaded yet
722                trace!(?weak_material_ref, "material is not complete yet");
723                continue;
724            }
725            let material = weak_material_ref.clone();
726
727            let maybe_texture_ref = material.primary_texture();
728            let maybe_texture = maybe_texture_ref
729                .and_then(|found_primary_texture_ref| textures.get(&found_primary_texture_ref));
730
731            for render_item in render_items {
732                let quad_len_before_inner = quad_matrix_and_uv.len();
733
734                match &render_item.renderable {
735                    Renderable::Sprite(sprite) => {
736                        let current_texture_size = maybe_texture.unwrap().texture_size;
737
738                        let params = &sprite.params;
739                        let mut size = params.texture_size;
740                        if size.x == 0 && size.y == 0 {
741                            size = current_texture_size;
742                        }
743
744                        let render_atlas = URect {
745                            position: params.texture_pos,
746                            size,
747                        };
748
749                        match params.rotation {
750                            Rotation::Degrees90 | Rotation::Degrees270 => {
751                                swap(&mut size.x, &mut size.y);
752                            }
753                            _ => {}
754                        }
755
756                        let y_offset = if params.anchor == Anchor::UpperLeft {
757                            current_texture_size.y as i16
758                        } else {
759                            0
760                        };
761
762                        let model_matrix = Matrix4::from_translation(
763                            f32::from(render_item.position.x),
764                            f32::from(render_item.position.y - y_offset),
765                            0.0,
766                        ) * Matrix4::from_scale(
767                            f32::from(size.x * u16::from(params.scale)),
768                            f32::from(size.y * u16::from(params.scale)),
769                            1.0,
770                        );
771
772                        let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
773                            render_atlas,
774                            current_texture_size,
775                        );
776
777                        let mut rotation_value = match params.rotation {
778                            Rotation::Degrees0 => 0,
779                            Rotation::Degrees90 => 1,
780                            Rotation::Degrees180 => 2,
781                            Rotation::Degrees270 => 3,
782                        };
783
784                        if params.flip_x {
785                            rotation_value |= FLIP_X_MASK;
786                        }
787                        if params.flip_y {
788                            rotation_value |= FLIP_Y_MASK;
789                        }
790
791                        let quad_instance = SpriteInstanceUniform::new(
792                            model_matrix,
793                            tex_coords_mul_add,
794                            rotation_value,
795                            Vec4(params.color.to_f32_slice()),
796                        );
797                        quad_matrix_and_uv.push(quad_instance);
798                    }
799
800                    Renderable::Mask(texture_offset, color) => {
801                        let current_texture_size = maybe_texture.unwrap().texture_size;
802                        let params = SpriteParams {
803                            texture_size: current_texture_size,
804                            texture_pos: *texture_offset,
805                            scale: 1,
806                            rotation: Rotation::default(),
807                            flip_x: false,
808                            flip_y: false,
809                            pivot: Vec2 { x: 0, y: 0 },
810                            color: *color,
811                            anchor: Anchor::LowerLeft,
812                        };
813
814                        let mut size = params.texture_size;
815                        if size.x == 0 && size.y == 0 {
816                            size = current_texture_size;
817                        }
818
819                        let render_atlas = URect {
820                            position: params.texture_pos,
821                            size,
822                        };
823
824                        let model_matrix = Matrix4::from_translation(
825                            f32::from(render_item.position.x),
826                            f32::from(render_item.position.y),
827                            0.0,
828                        ) * Matrix4::from_scale(
829                            f32::from(size.x * u16::from(params.scale)),
830                            f32::from(size.y * u16::from(params.scale)),
831                            1.0,
832                        );
833
834                        let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
835                            render_atlas,
836                            current_texture_size,
837                        );
838
839                        let mut rotation_value = match params.rotation {
840                            Rotation::Degrees0 => 0,
841                            Rotation::Degrees90 => 1,
842                            Rotation::Degrees180 => 2,
843                            Rotation::Degrees270 => 3,
844                        };
845
846                        if params.flip_x {
847                            rotation_value |= FLIP_X_MASK;
848                        }
849                        if params.flip_y {
850                            rotation_value |= FLIP_Y_MASK;
851                        }
852
853                        let quad_instance = SpriteInstanceUniform::new(
854                            model_matrix,
855                            tex_coords_mul_add,
856                            rotation_value,
857                            Vec4(params.color.to_f32_slice()),
858                        );
859                        quad_matrix_and_uv.push(quad_instance);
860                    }
861
862                    Renderable::NineSlice(nine_slice) => {
863                        let current_texture_size = maybe_texture.unwrap().texture_size;
864                        Self::prepare_nine_slice(
865                            nine_slice,
866                            render_item.position,
867                            &mut quad_matrix_and_uv,
868                            current_texture_size,
869                        );
870                    }
871
872                    Renderable::NineSliceStretch(nine_slice) => {
873                        let current_texture_size = maybe_texture.unwrap().texture_size;
874                        Self::prepare_nine_slice_single_center_quad(
875                            nine_slice,
876                            render_item.position,
877                            &mut quad_matrix_and_uv,
878                            current_texture_size,
879                        );
880                    }
881
882                    Renderable::QuadColor(quad) => {
883                        let model_matrix = Matrix4::from_translation(
884                            f32::from(render_item.position.x) - f32::from(quad.params.pivot.x),
885                            f32::from(render_item.position.y) - f32::from(quad.params.pivot.y),
886                            0.0,
887                        ) * Matrix4::from_scale(
888                            f32::from(quad.size.x),
889                            f32::from(quad.size.y),
890                            1.0,
891                        );
892
893                        let tex_coords_mul_add = Vec4([
894                            0.0, //x
895                            0.0, //y
896                            0.0, 0.0,
897                        ]);
898                        let rotation_value = 0;
899
900                        let quad_instance = SpriteInstanceUniform::new(
901                            model_matrix,
902                            tex_coords_mul_add,
903                            rotation_value,
904                            Vec4(quad.color.to_f32_slice()),
905                        );
906                        quad_matrix_and_uv.push(quad_instance);
907                    }
908
909                    Renderable::Text(text) => {
910                        let current_texture_size = maybe_texture.unwrap().texture_size;
911                        let result = fonts.get_weak(text.font_ref);
912                        if result.is_none() {
913                            continue;
914                        }
915                        let font = result.unwrap();
916
917                        let glyph_draw = font.draw(&text.text);
918                        for glyph in glyph_draw.glyphs {
919                            let pos = render_item.position + Vec3::from(glyph.relative_position);
920                            let texture_size = glyph.texture_rectangle.size;
921                            let model_matrix =
922                                Matrix4::from_translation(f32::from(pos.x), f32::from(pos.y), 0.0)
923                                    * Matrix4::from_scale(
924                                        f32::from(texture_size.x),
925                                        f32::from(texture_size.y),
926                                        1.0,
927                                    );
928                            let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
929                                glyph.texture_rectangle,
930                                current_texture_size,
931                            );
932
933                            let quad_instance = SpriteInstanceUniform::new(
934                                model_matrix,
935                                tex_coords_mul_add,
936                                0,
937                                Vec4(text.color.to_f32_slice()),
938                            );
939                            quad_matrix_and_uv.push(quad_instance);
940                        }
941                    }
942
943                    Renderable::TileMap(tile_map) => {
944                        for (index, tile) in tile_map.tiles.iter().enumerate() {
945                            let cell_pos_x = (index as u16 % tile_map.tiles_data_grid_size.x)
946                                * tile_map.one_cell_size.x
947                                * u16::from(tile_map.scale);
948                            let cell_pos_y = (index as u16 / tile_map.tiles_data_grid_size.x)
949                                * tile_map.one_cell_size.y
950                                * u16::from(tile_map.scale);
951                            let cell_x = *tile % tile_map.cell_count_size.x;
952                            let cell_y = *tile / tile_map.cell_count_size.x;
953
954                            let tex_x = cell_x * tile_map.one_cell_size.x;
955                            let tex_y = cell_y * tile_map.one_cell_size.x;
956
957                            let cell_texture_area = URect::new(
958                                tex_x,
959                                tex_y,
960                                tile_map.one_cell_size.x,
961                                tile_map.one_cell_size.y,
962                            );
963
964                            let cell_model_matrix = Matrix4::from_translation(
965                                f32::from(render_item.position.x + cell_pos_x as i16),
966                                f32::from(render_item.position.y + cell_pos_y as i16),
967                                0.0,
968                            ) * Matrix4::from_scale(
969                                f32::from(tile_map.one_cell_size.x * u16::from(tile_map.scale)),
970                                f32::from(tile_map.one_cell_size.y * u16::from(tile_map.scale)),
971                                1.0,
972                            );
973
974                            let current_texture_size = maybe_texture.unwrap().texture_size;
975                            let cell_tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
976                                cell_texture_area,
977                                current_texture_size,
978                            );
979
980                            let quad_instance = SpriteInstanceUniform::new(
981                                cell_model_matrix,
982                                cell_tex_coords_mul_add,
983                                0,
984                                Vec4([1.0, 1.0, 1.0, 1.0]),
985                            );
986                            quad_matrix_and_uv.push(quad_instance);
987                        }
988                    }
989                }
990
991                let quad_count_for_this_render_item =
992                    quad_matrix_and_uv.len() - quad_len_before_inner;
993                assert!(
994                    quad_count_for_this_render_item <= MAXIMUM_QUADS_FOR_RENDER_ITEM,
995                    "too many quads {quad_count_for_this_render_item} for render item {render_item:?}"
996                );
997            }
998
999            let quad_count_for_this_batch = quad_matrix_and_uv.len() - quad_len_before;
1000            assert!(
1001                quad_count_for_this_batch <= MAXIMUM_QUADS_IN_A_BATCH,
1002                "too many quads {quad_count_for_this_batch} total to render in this batch"
1003            );
1004
1005            assert!(
1006                quad_matrix_and_uv.len() <= MAXIMUM_QUADS_IN_ONE_RENDER,
1007                "too many quads for whole render {}",
1008                quad_matrix_and_uv.len()
1009            );
1010
1011            batch_vertex_ranges.push((
1012                weak_material_ref,
1013                quad_len_before as u32,
1014                quad_count_for_this_batch as u32,
1015            ));
1016        }
1017
1018        // write all model_matrix and uv_coords to instance buffer once, before the render pass
1019        self.queue.write_buffer(
1020            &self.quad_matrix_and_uv_instance_buffer,
1021            0,
1022            bytemuck::cast_slice(&quad_matrix_and_uv),
1023        );
1024
1025        self.batch_offsets = batch_vertex_ranges;
1026    }
1027
1028    #[allow(clippy::too_many_lines)]
1029    #[inline]
1030    pub fn prepare_nine_slice(
1031        nine_slice: &NineSlice,
1032        position_offset: Vec3,
1033        quad_matrix_and_uv: &mut Vec<SpriteInstanceUniform>,
1034        current_texture_size: UVec2,
1035    ) {
1036        let world_window_size = nine_slice.size;
1037        let slices = &nine_slice.slices;
1038
1039        // ------------------------------------------------------------
1040        // Validate that our total window size is large enough to hold
1041        // the left+right and top+bottom slices without underflowing.
1042        // ------------------------------------------------------------
1043        assert!(
1044            world_window_size.x >= slices.left + slices.right,
1045            "NineSlice.width ({}) < slices.left + slices.right ({})",
1046            world_window_size.x,
1047            slices.left + slices.right
1048        );
1049        assert!(
1050            world_window_size.y >= slices.top + slices.bottom,
1051            "NineSlice.height ({}) < slices.top + slices.bottom ({})",
1052            world_window_size.y,
1053            slices.top + slices.bottom
1054        );
1055
1056        let texture_window_size = nine_slice.size_inside_atlas.unwrap_or(current_texture_size);
1057
1058        // check the texture region as well
1059        assert!(
1060            texture_window_size.x >= slices.left + slices.right,
1061            "texture_window_size.width ({}) < slices.left + slices.right ({})",
1062            texture_window_size.x,
1063            slices.left + slices.right
1064        );
1065        assert!(
1066            texture_window_size.y >= slices.top + slices.bottom,
1067            "texture_window_size.height ({}) < slices.top + slices.bottom ({})",
1068            texture_window_size.y,
1069            slices.top + slices.bottom
1070        );
1071        // ------------------------------------------------------------
1072
1073        let color = nine_slice.color;
1074
1075        let atlas_origin = nine_slice.origin_in_atlas;
1076        let texture_window_size = nine_slice.size_inside_atlas.unwrap_or(current_texture_size);
1077
1078        let world_edge_width = nine_slice.size.x - slices.left - slices.right;
1079        let world_edge_height = nine_slice.size.y - slices.top - slices.bottom;
1080        let texture_edge_width = texture_window_size.x - slices.left - slices.right;
1081        let texture_edge_height = texture_window_size.y - slices.top - slices.bottom;
1082
1083        // Lower left Corner
1084        // Y goes up, X goes to the right, right-handed coordinate system
1085        let lower_left_pos = Vec3::new(position_offset.x, position_offset.y, 0);
1086        let corner_size = UVec2::new(slices.left, slices.bottom);
1087        // it should be pixel perfect so it is the same size as the texture cut out
1088        let lower_left_quad_size = UVec2::new(corner_size.x, corner_size.y);
1089        let lower_left_atlas = URect::new(
1090            atlas_origin.x,
1091            atlas_origin.y + texture_window_size.y - slices.bottom, // Bottom of texture minus bottom slice height
1092            corner_size.x,
1093            corner_size.y,
1094        );
1095        let lower_left_quad = Self::quad_helper_uniform(
1096            lower_left_pos,
1097            lower_left_quad_size,
1098            lower_left_atlas,
1099            color,
1100            current_texture_size,
1101        );
1102        quad_matrix_and_uv.push(lower_left_quad);
1103
1104        // Lower edge
1105        let lower_side_position =
1106            Vec3::new(position_offset.x + slices.left as i16, position_offset.y, 0);
1107        // World quad size is potentially wider than the texture,
1108        // that is fine, since the texture will be repeated.
1109        let lower_side_world_quad_size = UVec2::new(world_edge_width, slices.bottom);
1110        let lower_side_texture_size = UVec2::new(texture_edge_width, slices.bottom);
1111        // Lower edge
1112        let lower_side_atlas = URect::new(
1113            atlas_origin.x + slices.left,
1114            atlas_origin.y + texture_window_size.y - slices.bottom, // Bottom of texture minus bottom slice height
1115            lower_side_texture_size.x,
1116            lower_side_texture_size.y,
1117        );
1118        let lower_side_quad = Self::quad_helper_uniform(
1119            lower_side_position,
1120            lower_side_world_quad_size,
1121            lower_side_atlas,
1122            color,
1123            current_texture_size,
1124        );
1125        quad_matrix_and_uv.push(lower_side_quad);
1126
1127        // Lower right corner
1128        let lower_right_pos = Vec3::new(
1129            position_offset.x + (world_window_size.x - slices.right) as i16,
1130            position_offset.y,
1131            0,
1132        );
1133        let lower_right_corner_size = UVec2::new(slices.right, slices.bottom);
1134        let lower_right_atlas = URect::new(
1135            atlas_origin.x + texture_window_size.x - slices.right,
1136            atlas_origin.y + texture_window_size.y - slices.bottom, // Bottom of texture minus bottom slice height
1137            lower_right_corner_size.x,
1138            lower_right_corner_size.y,
1139        );
1140        let lower_right_quad = Self::quad_helper_uniform(
1141            lower_right_pos,
1142            lower_right_corner_size,
1143            lower_right_atlas,
1144            color,
1145            current_texture_size,
1146        );
1147        quad_matrix_and_uv.push(lower_right_quad);
1148
1149        // Left edge
1150        let left_edge_pos = Vec3::new(
1151            position_offset.x,
1152            position_offset.y + slices.bottom as i16,
1153            0,
1154        );
1155        let left_edge_world_quad_size = UVec2::new(slices.left, world_edge_height);
1156        let left_edge_texture_size = UVec2::new(slices.left, texture_edge_height);
1157        let left_edge_atlas = URect::new(
1158            atlas_origin.x,
1159            atlas_origin.y + slices.top, // Skip top slice
1160            left_edge_texture_size.x,
1161            left_edge_texture_size.y,
1162        );
1163        let left_edge_quad = Self::quad_helper_uniform(
1164            left_edge_pos,
1165            left_edge_world_quad_size,
1166            left_edge_atlas,
1167            color,
1168            current_texture_size,
1169        );
1170        quad_matrix_and_uv.push(left_edge_quad);
1171
1172        // CENTER IS COMPLICATED
1173        // This was pretty tricky to get going, but @catnipped wanted it :)
1174
1175        // For the center region, we have to create multiple quads, since we want
1176        // it pixel perfect and not stretch it
1177        let base_center_x = atlas_origin.x + slices.left;
1178        let base_center_y = atlas_origin.y + slices.top;
1179
1180        // Calculate how many repetitions (quads) we need in each direction
1181        let repeat_x_count =
1182            (f32::from(world_edge_width) / f32::from(texture_edge_width)).ceil() as usize;
1183        let repeat_y_count =
1184            (f32::from(world_edge_height) / f32::from(texture_edge_height)).ceil() as usize;
1185
1186        for y in 0..repeat_y_count {
1187            for x in 0..repeat_x_count {
1188                let this_quad_width =
1189                    if x == repeat_x_count - 1 && world_edge_width % texture_edge_width != 0 {
1190                        world_edge_width % texture_edge_width
1191                    } else {
1192                        texture_edge_width
1193                    };
1194
1195                let this_quad_height =
1196                    if y == repeat_y_count - 1 && world_edge_height % texture_edge_height != 0 {
1197                        world_edge_height % texture_edge_height
1198                    } else {
1199                        texture_edge_height
1200                    };
1201
1202                let quad_pos = Vec3::new(
1203                    position_offset.x + slices.left as i16 + (x as u16 * texture_edge_width) as i16,
1204                    position_offset.y
1205                        + slices.bottom as i16
1206                        + (y as u16 * texture_edge_height) as i16,
1207                    0,
1208                );
1209
1210                let texture_x = base_center_x;
1211
1212                let texture_y = if y == repeat_y_count - 1 && this_quad_height < texture_edge_height
1213                {
1214                    base_center_y + (texture_edge_height - this_quad_height)
1215                } else {
1216                    base_center_y
1217                };
1218
1219                let this_texture_region =
1220                    URect::new(texture_x, texture_y, this_quad_width, this_quad_height);
1221
1222                let center_quad = Self::quad_helper_uniform(
1223                    quad_pos,
1224                    UVec2::new(this_quad_width, this_quad_height),
1225                    this_texture_region,
1226                    color,
1227                    current_texture_size,
1228                );
1229
1230                quad_matrix_and_uv.push(center_quad);
1231            }
1232        }
1233        // CENTER IS DONE ---------
1234
1235        // Right edge
1236        let right_edge_pos = Vec3::new(
1237            position_offset.x + (world_window_size.x - slices.right) as i16,
1238            position_offset.y + slices.bottom as i16,
1239            0,
1240        );
1241        let right_edge_world_quad_size = UVec2::new(slices.right, world_edge_height);
1242        let right_edge_texture_size = UVec2::new(slices.right, texture_edge_height);
1243        let right_edge_atlas = URect::new(
1244            atlas_origin.x + texture_window_size.x - slices.right,
1245            atlas_origin.y + slices.top, // Skip top slice
1246            right_edge_texture_size.x,
1247            right_edge_texture_size.y,
1248        );
1249
1250        let right_edge_quad = Self::quad_helper_uniform(
1251            right_edge_pos,
1252            right_edge_world_quad_size,
1253            right_edge_atlas,
1254            color,
1255            current_texture_size,
1256        );
1257        quad_matrix_and_uv.push(right_edge_quad);
1258
1259        // Top left corner
1260        let top_left_pos = Vec3::new(
1261            position_offset.x,
1262            position_offset.y + (world_window_size.y - slices.top) as i16,
1263            0,
1264        );
1265        let top_left_corner_size = UVec2::new(slices.left, slices.top);
1266        let top_left_atlas = URect::new(
1267            atlas_origin.x,
1268            atlas_origin.y, // Top of texture
1269            top_left_corner_size.x,
1270            top_left_corner_size.y,
1271        );
1272        let top_left_quad = Self::quad_helper_uniform(
1273            top_left_pos,
1274            top_left_corner_size,
1275            top_left_atlas,
1276            color,
1277            current_texture_size,
1278        );
1279        quad_matrix_and_uv.push(top_left_quad);
1280
1281        // Top edge
1282        let top_edge_pos = Vec3::new(
1283            position_offset.x + slices.left as i16,
1284            position_offset.y + (world_window_size.y - slices.top) as i16,
1285            0,
1286        );
1287        let top_edge_world_quad_size = UVec2::new(world_edge_width, slices.top);
1288        let top_edge_texture_size = UVec2::new(texture_edge_width, slices.top);
1289        let top_edge_atlas = URect::new(
1290            atlas_origin.x + slices.left,
1291            atlas_origin.y, // Top of texture
1292            top_edge_texture_size.x,
1293            top_edge_texture_size.y,
1294        );
1295        let top_edge_quad = Self::quad_helper_uniform(
1296            top_edge_pos,
1297            top_edge_world_quad_size,
1298            top_edge_atlas,
1299            color,
1300            current_texture_size,
1301        );
1302        quad_matrix_and_uv.push(top_edge_quad);
1303
1304        // Top right corner
1305        let top_right_pos = Vec3::new(
1306            position_offset.x + (world_window_size.x - slices.right) as i16,
1307            position_offset.y + (world_window_size.y - slices.top) as i16,
1308            0,
1309        );
1310        let top_right_corner_size = UVec2::new(slices.right, slices.top);
1311        let top_right_atlas = URect::new(
1312            atlas_origin.x + texture_window_size.x - slices.right,
1313            atlas_origin.y, // Top of texture
1314            top_right_corner_size.x,
1315            top_right_corner_size.y,
1316        );
1317        let top_right_quad = Self::quad_helper_uniform(
1318            top_right_pos,
1319            top_right_corner_size,
1320            top_right_atlas,
1321            color,
1322            current_texture_size,
1323        );
1324        quad_matrix_and_uv.push(top_right_quad);
1325    }
1326
1327    #[allow(clippy::too_many_lines)]
1328    #[inline]
1329    pub fn prepare_nine_slice_single_center_quad(
1330        nine_slice: &NineSlice,
1331        position_offset: Vec3,
1332        quad_matrix_and_uv: &mut Vec<SpriteInstanceUniform>,
1333        current_texture_size: UVec2,
1334    ) {
1335        let world_window_size = nine_slice.size;
1336        let slices = &nine_slice.slices;
1337
1338        // ------------------------------------------------------------
1339        // Validate that our total window size is large enough to hold
1340        // the left+right and top+bottom slices without underflowing.
1341        // ------------------------------------------------------------
1342        assert!(
1343            world_window_size.x >= slices.left + slices.right,
1344            "NineSlice.width ({}) < slices.left + slices.right ({})",
1345            world_window_size.x,
1346            slices.left + slices.right
1347        );
1348        assert!(
1349            world_window_size.y >= slices.top + slices.bottom,
1350            "NineSlice.height ({}) < slices.top + slices.bottom ({})",
1351            world_window_size.y,
1352            slices.top + slices.bottom
1353        );
1354
1355        let texture_window_size = nine_slice.size_inside_atlas.unwrap_or(current_texture_size);
1356
1357        // check the texture region as well
1358        assert!(
1359            texture_window_size.x >= slices.left + slices.right,
1360            "texture_window_size.width ({}) < slices.left + slices.right ({})",
1361            texture_window_size.x,
1362            slices.left + slices.right
1363        );
1364        assert!(
1365            texture_window_size.y >= slices.top + slices.bottom,
1366            "texture_window_size.height ({}) < slices.top + slices.bottom ({})",
1367            texture_window_size.y,
1368            slices.top + slices.bottom
1369        );
1370        // ------------------------------------------------------------
1371
1372        let color = nine_slice.color;
1373
1374        let atlas_origin = nine_slice.origin_in_atlas;
1375        let texture_window_size = nine_slice.size_inside_atlas.unwrap_or(current_texture_size);
1376
1377        let world_edge_width = nine_slice.size.x - slices.left - slices.right;
1378        let world_edge_height = nine_slice.size.y - slices.top - slices.bottom;
1379        let texture_edge_width = texture_window_size.x - slices.left - slices.right;
1380        let texture_edge_height = texture_window_size.y - slices.top - slices.bottom;
1381
1382        // Lower left Corner
1383        // Y goes up, X goes to the right, right-handed coordinate system
1384        let lower_left_pos = Vec3::new(position_offset.x, position_offset.y, 0);
1385        let corner_size = UVec2::new(slices.left, slices.bottom);
1386        // it should be pixel perfect so it is the same size as the texture cut out
1387        let lower_left_quad_size = UVec2::new(corner_size.x, corner_size.y);
1388        let lower_left_atlas = URect::new(
1389            atlas_origin.x,
1390            atlas_origin.y + texture_window_size.y - slices.bottom, // Bottom of texture minus bottom slice height
1391            corner_size.x,
1392            corner_size.y,
1393        );
1394        let lower_left_quad = Self::quad_helper_uniform(
1395            lower_left_pos,
1396            lower_left_quad_size,
1397            lower_left_atlas,
1398            color,
1399            current_texture_size,
1400        );
1401        quad_matrix_and_uv.push(lower_left_quad);
1402
1403        // Lower edge
1404        let lower_side_position =
1405            Vec3::new(position_offset.x + slices.left as i16, position_offset.y, 0);
1406        // World quad size is potentially wider than the texture,
1407        // that is fine, since the texture will be repeated.
1408        let lower_side_world_quad_size = UVec2::new(world_edge_width, slices.bottom);
1409        let lower_side_texture_size = UVec2::new(texture_edge_width, slices.bottom);
1410        // Lower edge
1411        let lower_side_atlas = URect::new(
1412            atlas_origin.x + slices.left,
1413            atlas_origin.y + texture_window_size.y - slices.bottom, // Bottom of texture minus bottom slice height
1414            lower_side_texture_size.x,
1415            lower_side_texture_size.y,
1416        );
1417        let lower_side_quad = Self::quad_helper_uniform(
1418            lower_side_position,
1419            lower_side_world_quad_size,
1420            lower_side_atlas,
1421            color,
1422            current_texture_size,
1423        );
1424        quad_matrix_and_uv.push(lower_side_quad);
1425
1426        // Lower right corner
1427        let lower_right_pos = Vec3::new(
1428            position_offset.x + (world_window_size.x - slices.right) as i16,
1429            position_offset.y,
1430            0,
1431        );
1432        let lower_right_corner_size = UVec2::new(slices.right, slices.bottom);
1433        let lower_right_atlas = URect::new(
1434            atlas_origin.x + texture_window_size.x - slices.right,
1435            atlas_origin.y + texture_window_size.y - slices.bottom, // Bottom of texture minus bottom slice height
1436            lower_right_corner_size.x,
1437            lower_right_corner_size.y,
1438        );
1439        let lower_right_quad = Self::quad_helper_uniform(
1440            lower_right_pos,
1441            lower_right_corner_size,
1442            lower_right_atlas,
1443            color,
1444            current_texture_size,
1445        );
1446        quad_matrix_and_uv.push(lower_right_quad);
1447
1448        // Left edge
1449        let left_edge_pos = Vec3::new(
1450            position_offset.x,
1451            position_offset.y + slices.bottom as i16,
1452            0,
1453        );
1454        let left_edge_world_quad_size = UVec2::new(slices.left, world_edge_height);
1455        let left_edge_texture_size = UVec2::new(slices.left, texture_edge_height);
1456        let left_edge_atlas = URect::new(
1457            atlas_origin.x,
1458            atlas_origin.y + slices.top, // Skip top slice
1459            left_edge_texture_size.x,
1460            left_edge_texture_size.y,
1461        );
1462        let left_edge_quad = Self::quad_helper_uniform(
1463            left_edge_pos,
1464            left_edge_world_quad_size,
1465            left_edge_atlas,
1466            color,
1467            current_texture_size,
1468        );
1469        quad_matrix_and_uv.push(left_edge_quad);
1470
1471        // Center as a single, stretched quad
1472        let center_pos = Vec3::new(
1473            position_offset.x + slices.left as i16,
1474            position_offset.y + slices.bottom as i16,
1475            0,
1476        );
1477        let center_world_size = UVec2::new(world_edge_width, world_edge_height);
1478        // atlas_origin.x + slices.left  => x of center in atlas
1479        // atlas_origin.y + slices.top   => y of center in atlas
1480        let center_atlas = URect::new(
1481            atlas_origin.x + slices.left,
1482            atlas_origin.y + slices.top,
1483            texture_edge_width,
1484            texture_edge_height,
1485        );
1486        let center_quad = Self::quad_helper_uniform(
1487            center_pos,
1488            center_world_size,
1489            center_atlas,
1490            color,
1491            current_texture_size,
1492        );
1493        quad_matrix_and_uv.push(center_quad);
1494
1495        // Right edge
1496        let right_edge_pos = Vec3::new(
1497            position_offset.x + (world_window_size.x - slices.right) as i16,
1498            position_offset.y + slices.bottom as i16,
1499            0,
1500        );
1501        let right_edge_world_quad_size = UVec2::new(slices.right, world_edge_height);
1502        let right_edge_texture_size = UVec2::new(slices.right, texture_edge_height);
1503        let right_edge_atlas = URect::new(
1504            atlas_origin.x + texture_window_size.x - slices.right,
1505            atlas_origin.y + slices.top, // Skip top slice
1506            right_edge_texture_size.x,
1507            right_edge_texture_size.y,
1508        );
1509
1510        let right_edge_quad = Self::quad_helper_uniform(
1511            right_edge_pos,
1512            right_edge_world_quad_size,
1513            right_edge_atlas,
1514            color,
1515            current_texture_size,
1516        );
1517        quad_matrix_and_uv.push(right_edge_quad);
1518
1519        // Top left corner
1520        let top_left_pos = Vec3::new(
1521            position_offset.x,
1522            position_offset.y + (world_window_size.y - slices.top) as i16,
1523            0,
1524        );
1525        let top_left_corner_size = UVec2::new(slices.left, slices.top);
1526        let top_left_atlas = URect::new(
1527            atlas_origin.x,
1528            atlas_origin.y, // Top of texture
1529            top_left_corner_size.x,
1530            top_left_corner_size.y,
1531        );
1532        let top_left_quad = Self::quad_helper_uniform(
1533            top_left_pos,
1534            top_left_corner_size,
1535            top_left_atlas,
1536            color,
1537            current_texture_size,
1538        );
1539        quad_matrix_and_uv.push(top_left_quad);
1540
1541        // Top edge
1542        let top_edge_pos = Vec3::new(
1543            position_offset.x + slices.left as i16,
1544            position_offset.y + (world_window_size.y - slices.top) as i16,
1545            0,
1546        );
1547        let top_edge_world_quad_size = UVec2::new(world_edge_width, slices.top);
1548        let top_edge_texture_size = UVec2::new(texture_edge_width, slices.top);
1549        let top_edge_atlas = URect::new(
1550            atlas_origin.x + slices.left,
1551            atlas_origin.y, // Top of texture
1552            top_edge_texture_size.x,
1553            top_edge_texture_size.y,
1554        );
1555        let top_edge_quad = Self::quad_helper_uniform(
1556            top_edge_pos,
1557            top_edge_world_quad_size,
1558            top_edge_atlas,
1559            color,
1560            current_texture_size,
1561        );
1562        quad_matrix_and_uv.push(top_edge_quad);
1563
1564        // Top right corner
1565        let top_right_pos = Vec3::new(
1566            position_offset.x + (world_window_size.x - slices.right) as i16,
1567            position_offset.y + (world_window_size.y - slices.top) as i16,
1568            0,
1569        );
1570        let top_right_corner_size = UVec2::new(slices.right, slices.top);
1571        let top_right_atlas = URect::new(
1572            atlas_origin.x + texture_window_size.x - slices.right,
1573            atlas_origin.y, // Top of texture
1574            top_right_corner_size.x,
1575            top_right_corner_size.y,
1576        );
1577        let top_right_quad = Self::quad_helper_uniform(
1578            top_right_pos,
1579            top_right_corner_size,
1580            top_right_atlas,
1581            color,
1582            current_texture_size,
1583        );
1584        quad_matrix_and_uv.push(top_right_quad);
1585    }
1586
1587    fn sort_and_put_in_batches(&mut self) -> Vec<Vec<&RenderItem>> {
1588        sort_render_items_by_z_and_material(&mut self.items);
1589
1590        self.order_render_items_in_batches()
1591    }
1592
1593    /// # Panics
1594    ///
1595    #[allow(clippy::too_many_lines)]
1596    pub fn render(
1597        &mut self,
1598        command_encoder: &mut CommandEncoder,
1599        display_surface_texture_view: &TextureView,
1600        //        materials: &Assets<Material>,
1601        textures: &Assets<Texture>,
1602        fonts: &Assets<Font>,
1603        now: Millis,
1604    ) {
1605        self.debug_tick += 1;
1606        trace!("start render()");
1607        self.last_render_at = now;
1608
1609        self.set_viewport_and_view_projection_matrix();
1610
1611        self.write_vertex_indices_and_uv_to_buffer(textures, fonts);
1612
1613        self.render_batches_to_virtual_texture(command_encoder, textures);
1614
1615        self.render_virtual_texture_to_display(command_encoder, display_surface_texture_view);
1616    }
1617
1618    pub fn set_viewport_and_view_projection_matrix(&mut self) {
1619        let view_proj_matrix = create_view_projection_matrix_from_virtual(
1620            self.virtual_surface_size.x,
1621            self.virtual_surface_size.y,
1622        );
1623
1624        let scale_matrix = Matrix4::from_scale(self.scale, self.scale, 0.0);
1625        let origin_translation_matrix =
1626            Matrix4::from_translation(f32::from(-self.origin.x), f32::from(-self.origin.y), 0.0);
1627
1628        let total_matrix = scale_matrix * view_proj_matrix * origin_translation_matrix;
1629
1630        // write all model_matrix and uv_coords to instance buffer once, before the render pass
1631        self.queue.write_buffer(
1632            &self.camera_buffer,
1633            0,
1634            bytemuck::cast_slice(&[total_matrix]),
1635        );
1636    }
1637
1638    pub fn render_batches_to_virtual_texture(
1639        &mut self,
1640        command_encoder: &mut CommandEncoder,
1641        textures: &Assets<Texture>,
1642    ) {
1643        let mut render_pass = command_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1644            label: Some("Game Render Pass"),
1645            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1646                view: &self.virtual_surface_texture_view,
1647                depth_slice: None,
1648                resolve_target: None,
1649                ops: wgpu::Operations {
1650                    load: wgpu::LoadOp::Clear(self.clear_color),
1651                    store: wgpu::StoreOp::Store,
1652                },
1653            })],
1654            depth_stencil_attachment: None,
1655            timestamp_writes: None,
1656            occlusion_query_set: None,
1657        });
1658
1659        render_pass.set_viewport(
1660            0.0,
1661            0.0,
1662            f32::from(self.virtual_surface_size.x),
1663            f32::from(self.virtual_surface_size.y),
1664            0.0,
1665            1.0,
1666        );
1667
1668        // Index and vertex buffers never change
1669        render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
1670        render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1671
1672        // Vertex buffer is reused
1673        render_pass.set_vertex_buffer(1, self.quad_matrix_and_uv_instance_buffer.slice(..));
1674
1675        let num_indices = mireforge_wgpu_sprites::INDICES.len() as u32;
1676
1677        let mut current_pipeline: Option<&MaterialKind> = None;
1678
1679        for &(ref weak_material_ref, start, count) in &self.batch_offsets {
1680            let wgpu_material = weak_material_ref;
1681
1682            let pipeline_kind = &wgpu_material.kind;
1683
1684            if current_pipeline != Some(pipeline_kind) {
1685                let pipeline = match pipeline_kind {
1686                    MaterialKind::NormalSprite { .. } => &self.normal_sprite_pipeline.pipeline,
1687                    MaterialKind::Quad => &self.quad_shader_info.pipeline,
1688                    MaterialKind::AlphaMasker { .. } => &self.mask_shader_info.pipeline,
1689                    MaterialKind::LightAdd { .. } => &self.light_shader_info.pipeline,
1690                };
1691                //trace!(%pipeline_kind, ?pipeline, "setting pipeline");
1692                render_pass.set_pipeline(pipeline);
1693                // Apparently after setting pipeline,
1694                // you must set all bind groups again
1695                current_pipeline = Some(pipeline_kind);
1696                render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
1697            }
1698
1699            match &wgpu_material.kind {
1700                MaterialKind::NormalSprite { primary_texture }
1701                | MaterialKind::LightAdd { primary_texture } => {
1702                    let texture = textures.get(primary_texture).unwrap();
1703                    // Bind the texture and sampler bind group (Bind Group 1)
1704                    render_pass.set_bind_group(1, &texture.texture_and_sampler_bind_group, &[]);
1705                }
1706                MaterialKind::AlphaMasker {
1707                    primary_texture,
1708                    alpha_texture,
1709                } => {
1710                    let real_diffuse_texture = textures.get(primary_texture).unwrap();
1711                    let alpha_texture = textures.get(alpha_texture).unwrap();
1712                    render_pass.set_bind_group(
1713                        1,
1714                        &real_diffuse_texture.texture_and_sampler_bind_group,
1715                        &[],
1716                    );
1717                    render_pass.set_bind_group(
1718                        2,
1719                        &alpha_texture.texture_and_sampler_bind_group,
1720                        &[],
1721                    );
1722                }
1723                MaterialKind::Quad => {
1724                    // Intentionally do nothing
1725                }
1726            }
1727            assert!(
1728                count <= MAXIMUM_QUADS_IN_A_BATCH as u32,
1729                "too many instanced draw in a batch {count}"
1730            );
1731
1732            // Issue the instanced draw call for the batch
1733            trace!(material=%weak_material_ref, start=%start, count=%count, %num_indices, "draw instanced");
1734            render_pass.draw_indexed(0..num_indices, 0, start..(start + count));
1735        }
1736        self.items.clear();
1737    }
1738
1739    pub fn render_virtual_texture_to_display(
1740        &mut self,
1741        command_encoder: &mut CommandEncoder,
1742        display_surface_texture_view: &TextureView,
1743    ) {
1744        let mut render_pass = command_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1745            label: Some("Screen Render Pass"),
1746            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1747                view: display_surface_texture_view,
1748                depth_slice: None,
1749                resolve_target: None,
1750                ops: wgpu::Operations {
1751                    load: wgpu::LoadOp::Clear(self.screen_clear_color),
1752                    store: wgpu::StoreOp::Store,
1753                },
1754            })],
1755            depth_stencil_attachment: None,
1756            timestamp_writes: None,
1757            occlusion_query_set: None,
1758        });
1759
1760        /*
1761        let scale_x = window_width as f32 / VIRTUAL_WIDTH as f32;
1762        let scale_y = window_height as f32 / VIRTUAL_HEIGHT as f32;
1763        let scale = scale_x.min(scale_y).floor(); // Use integer scaling
1764
1765        let viewport_width = VIRTUAL_WIDTH as f32 * scale;
1766        let viewport_height = VIRTUAL_HEIGHT as f32 * scale;
1767        let viewport_x = (window_width as f32 - viewport_width) / 2.0;
1768        let viewport_y = (window_height as f32 - viewport_height) / 2.0;
1769         */
1770
1771        self.viewport = match self.viewport_strategy {
1772            ViewportStrategy::FitIntegerScaling => Self::viewport_from_integer_scale(
1773                self.physical_surface_size,
1774                self.virtual_surface_size,
1775            ),
1776            ViewportStrategy::FitFloatScaling => Self::viewport_from_float_scale(
1777                self.physical_surface_size,
1778                self.virtual_surface_size,
1779            ),
1780            ViewportStrategy::MatchPhysicalSize => URect::new(
1781                0,
1782                0,
1783                self.physical_surface_size.x,
1784                self.physical_surface_size.y,
1785            ),
1786        };
1787
1788        render_pass.set_viewport(
1789            f32::from(self.viewport.position.x),
1790            f32::from(self.viewport.position.y),
1791            f32::from(self.viewport.size.x),
1792            f32::from(self.viewport.size.y),
1793            0.0,
1794            1.0,
1795        );
1796
1797        // Draw the render texture to the screen
1798        render_pass.set_pipeline(&self.virtual_to_screen_shader_info.pipeline);
1799        render_pass.set_bind_group(0, &self.virtual_to_surface_bind_group, &[]);
1800        render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1801
1802        render_pass.draw(0..6, 0..1);
1803    }
1804
1805    pub fn texture_resource_from_texture(&self, texture: &wgpu::Texture, label: &str) -> Texture {
1806        trace!("load texture from memory with name: '{label}'");
1807        let size = &texture.size();
1808        let texture_and_sampler_bind_group =
1809            mireforge_wgpu_sprites::create_sprite_texture_and_sampler_bind_group(
1810                &self.device,
1811                &self.texture_sampler_bind_group_layout,
1812                texture,
1813                &self.sampler,
1814                label,
1815            );
1816
1817        let texture_size = UVec2::new(size.width as u16, size.height as u16);
1818
1819        Texture {
1820            texture_and_sampler_bind_group,
1821            texture_size,
1822        }
1823    }
1824}
1825
1826fn create_view_projection_matrix_from_virtual(virtual_width: u16, virtual_height: u16) -> Matrix4 {
1827    let (bottom, top) = (0.0, f32::from(virtual_height));
1828
1829    // flip Z by swapping near/far if you want the opposite handedness
1830    // (e.g. for a left-handed vs right-handed depth axis)
1831    let (near, far) =
1832        // standard: near < far maps 0 → near, 1 → far
1833        (/* nearPlaneDepth */ -1.0, /* farPlaneDepth */ 1.0)
1834    ;
1835
1836    OrthoInfo {
1837        left: 0.0,
1838        right: f32::from(virtual_width),
1839        bottom,
1840        top,
1841        near, // Maybe flipped? -1.0
1842        far,  // maybe flipped? 1.0 or 0.0
1843    }
1844    .into()
1845}
1846
1847fn create_view_uniform_view_projection_matrix(viewport_size: UVec2) -> Matrix4 {
1848    let viewport_width = f32::from(viewport_size.x);
1849    let viewport_height = f32::from(viewport_size.y);
1850
1851    let viewport_aspect_ratio = viewport_width / viewport_height;
1852
1853    let scale_x = 1.0;
1854    let scale_y = viewport_aspect_ratio;
1855
1856    let view_projection_matrix = [
1857        [scale_x, 0.0, 0.0, 0.0],
1858        [0.0, scale_y, 0.0, 0.0],
1859        [0.0, 0.0, -1.0, 0.0],
1860        [0.0, 0.0, 0.0, 1.0],
1861    ];
1862
1863    view_projection_matrix.into()
1864}
1865
1866fn sort_render_items_by_z_and_material(items: &mut [RenderItem]) {
1867    items.sort_by_key(|item| (item.position.z, item.material_ref.clone()));
1868}
1869
1870#[derive(Debug, Clone, Copy, Default)]
1871pub enum Rotation {
1872    #[default]
1873    Degrees0,
1874    Degrees90,
1875    Degrees180,
1876    Degrees270,
1877}
1878
1879#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1880pub enum Anchor {
1881    LowerLeft,
1882    UpperLeft,
1883}
1884
1885#[derive(Debug, Copy, Clone)]
1886pub struct SpriteParams {
1887    pub texture_size: UVec2,
1888    pub texture_pos: UVec2,
1889    pub scale: u8,
1890    pub rotation: Rotation,
1891    pub flip_x: bool,
1892    pub flip_y: bool,
1893    pub pivot: Vec2,
1894    pub color: Color,
1895    pub anchor: Anchor,
1896}
1897
1898impl Default for SpriteParams {
1899    fn default() -> Self {
1900        Self {
1901            texture_size: UVec2::new(0, 0),
1902            texture_pos: UVec2::new(0, 0),
1903            pivot: Vec2::new(0, 0),
1904            flip_x: false,
1905            flip_y: false,
1906            color: Color::from_octet(255, 255, 255, 255),
1907            scale: 1,
1908            rotation: Rotation::Degrees0,
1909            anchor: Anchor::LowerLeft,
1910        }
1911    }
1912}
1913
1914#[derive(Debug, Copy, Clone)]
1915pub struct QuadParams {
1916    pub scale: u8,
1917    pub pivot: Vec2,
1918}
1919
1920impl Default for QuadParams {
1921    fn default() -> Self {
1922        Self {
1923            pivot: Vec2::new(0, 0),
1924            scale: 1,
1925        }
1926    }
1927}
1928
1929pub type BindGroupRef = Arc<BindGroup>;
1930
1931#[derive(Debug, PartialEq, Eq, Asset)]
1932pub struct Texture {
1933    pub texture_and_sampler_bind_group: BindGroup,
1934    //    pub pipeline: RenderPipelineRef,
1935    pub texture_size: UVec2,
1936}
1937
1938impl Display for Texture {
1939    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
1940        write!(f, "{:?}", self.texture_size)
1941    }
1942}
1943
1944impl PartialOrd<Self> for Texture {
1945    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1946        Some(
1947            self.texture_and_sampler_bind_group
1948                .cmp(&other.texture_and_sampler_bind_group),
1949        )
1950    }
1951}
1952
1953impl Ord for Texture {
1954    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1955        self.texture_and_sampler_bind_group
1956            .cmp(&other.texture_and_sampler_bind_group)
1957    }
1958}
1959
1960#[derive(Debug, Ord, PartialOrd, PartialEq, Eq)]
1961pub struct MaterialBase {
1962    //pub pipeline: PipelineRef,
1963}
1964
1965#[derive(Debug, Ord, PartialOrd, PartialEq, Eq)]
1966pub struct Material {
1967    pub base: MaterialBase,
1968    pub kind: MaterialKind,
1969}
1970
1971impl Material {
1972    #[inline]
1973    #[must_use]
1974    pub fn primary_texture(&self) -> Option<TextureRef> {
1975        self.kind.primary_texture()
1976    }
1977
1978    #[inline]
1979    #[must_use]
1980    pub fn is_complete(&self, textures: &Assets<Texture>) -> bool {
1981        self.kind.is_complete(textures)
1982    }
1983}
1984
1985impl Display for Material {
1986    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1987        write!(f, "{}", self.kind)
1988    }
1989}
1990
1991#[derive(Debug, Ord, PartialOrd, PartialEq, Eq)]
1992pub enum MaterialKind {
1993    NormalSprite {
1994        primary_texture: Id<Texture>,
1995    },
1996    AlphaMasker {
1997        primary_texture: Id<Texture>,
1998        alpha_texture: Id<Texture>,
1999    },
2000    Quad,
2001    LightAdd {
2002        primary_texture: Id<Texture>,
2003    },
2004}
2005
2006impl MaterialKind {}
2007
2008impl MaterialKind {
2009    #[must_use]
2010    pub fn primary_texture(&self) -> Option<Id<Texture>> {
2011        match &self {
2012            Self::NormalSprite {
2013                primary_texture, ..
2014            }
2015            | Self::LightAdd { primary_texture }
2016            | Self::AlphaMasker {
2017                primary_texture, ..
2018            } => Some(primary_texture.clone()),
2019            Self::Quad => None,
2020        }
2021    }
2022
2023    pub(crate) fn is_complete(&self, textures: &Assets<Texture>) -> bool {
2024        match &self {
2025            Self::NormalSprite { primary_texture } | Self::LightAdd { primary_texture } => {
2026                textures.contains(primary_texture)
2027            }
2028            Self::AlphaMasker {
2029                primary_texture,
2030                alpha_texture,
2031            } => textures.contains(primary_texture) && textures.contains(alpha_texture),
2032            Self::Quad => true,
2033        }
2034    }
2035}
2036
2037impl Display for MaterialKind {
2038    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2039        let texture_name = self
2040            .primary_texture()
2041            .map_or_else(String::new, |x| x.to_string());
2042
2043        let kind_name = match self {
2044            Self::NormalSprite { .. } => "NormalSprite",
2045            Self::LightAdd { .. } => "Light (Add)",
2046            Self::Quad => "Quad",
2047            Self::AlphaMasker { .. } => "AlphaMasker",
2048        };
2049
2050        write!(f, "{kind_name} texture {texture_name}")
2051    }
2052}
2053
2054#[derive(Debug)]
2055pub struct Sprite {
2056    pub params: SpriteParams,
2057}
2058
2059#[derive(Debug)]
2060pub struct QuadColor {
2061    pub size: UVec2,
2062    pub color: Color,
2063    pub params: QuadParams,
2064}
2065
2066#[derive(Debug, Copy, Clone)]
2067pub struct Slices {
2068    pub left: u16,
2069    pub top: u16,
2070    pub right: u16,  // how many pixels from the right side of the texture and going in
2071    pub bottom: u16, // how much to take from bottom of slice
2072}
2073
2074#[derive(Debug)]
2075pub struct NineSlice {
2076    pub size: UVec2, // size of whole "window"
2077    pub slices: Slices,
2078    pub color: Color, // color tint
2079    pub origin_in_atlas: UVec2,
2080    pub size_inside_atlas: Option<UVec2>,
2081}
2082
2083#[derive(Debug)]
2084pub struct TileMap {
2085    pub tiles_data_grid_size: UVec2,
2086    pub cell_count_size: UVec2,
2087    pub one_cell_size: UVec2,
2088    pub tiles: Vec<u16>,
2089    pub scale: u8,
2090}
2091
2092#[derive(PartialEq, Debug, Eq, Ord, PartialOrd)]
2093pub struct Pipeline {
2094    name: String,
2095    render_pipeline: RenderPipeline,
2096}
2097
2098impl Display for Pipeline {
2099    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2100        write!(f, "pipeline: {}", self.name)
2101    }
2102}
2103
2104pub type PipelineRef = Arc<Pipeline>;