Skip to main content

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::Assets;
12use limnus_assets::prelude::{Asset, Id, WeakId};
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    ShaderInfo, SpriteInfo, SpriteInstanceUniform, create_texture_and_sampler_bind_group_ex,
22    create_texture_and_sampler_group_layout,
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 = if x == repeat_x_count - 1
1189                    && !world_edge_width.is_multiple_of(texture_edge_width)
1190                {
1191                    world_edge_width % texture_edge_width
1192                } else {
1193                    texture_edge_width
1194                };
1195
1196                let this_quad_height = if y == repeat_y_count - 1
1197                    && !world_edge_height.is_multiple_of(texture_edge_height)
1198                {
1199                    world_edge_height % texture_edge_height
1200                } else {
1201                    texture_edge_height
1202                };
1203
1204                let quad_pos = Vec3::new(
1205                    position_offset.x + slices.left as i16 + (x as u16 * texture_edge_width) as i16,
1206                    position_offset.y
1207                        + slices.bottom as i16
1208                        + (y as u16 * texture_edge_height) as i16,
1209                    0,
1210                );
1211
1212                let texture_x = base_center_x;
1213
1214                let texture_y = if y == repeat_y_count - 1 && this_quad_height < texture_edge_height
1215                {
1216                    base_center_y + (texture_edge_height - this_quad_height)
1217                } else {
1218                    base_center_y
1219                };
1220
1221                let this_texture_region =
1222                    URect::new(texture_x, texture_y, this_quad_width, this_quad_height);
1223
1224                let center_quad = Self::quad_helper_uniform(
1225                    quad_pos,
1226                    UVec2::new(this_quad_width, this_quad_height),
1227                    this_texture_region,
1228                    color,
1229                    current_texture_size,
1230                );
1231
1232                quad_matrix_and_uv.push(center_quad);
1233            }
1234        }
1235        // CENTER IS DONE ---------
1236
1237        // Right edge
1238        let right_edge_pos = Vec3::new(
1239            position_offset.x + (world_window_size.x - slices.right) as i16,
1240            position_offset.y + slices.bottom as i16,
1241            0,
1242        );
1243        let right_edge_world_quad_size = UVec2::new(slices.right, world_edge_height);
1244        let right_edge_texture_size = UVec2::new(slices.right, texture_edge_height);
1245        let right_edge_atlas = URect::new(
1246            atlas_origin.x + texture_window_size.x - slices.right,
1247            atlas_origin.y + slices.top, // Skip top slice
1248            right_edge_texture_size.x,
1249            right_edge_texture_size.y,
1250        );
1251
1252        let right_edge_quad = Self::quad_helper_uniform(
1253            right_edge_pos,
1254            right_edge_world_quad_size,
1255            right_edge_atlas,
1256            color,
1257            current_texture_size,
1258        );
1259        quad_matrix_and_uv.push(right_edge_quad);
1260
1261        // Top left corner
1262        let top_left_pos = Vec3::new(
1263            position_offset.x,
1264            position_offset.y + (world_window_size.y - slices.top) as i16,
1265            0,
1266        );
1267        let top_left_corner_size = UVec2::new(slices.left, slices.top);
1268        let top_left_atlas = URect::new(
1269            atlas_origin.x,
1270            atlas_origin.y, // Top of texture
1271            top_left_corner_size.x,
1272            top_left_corner_size.y,
1273        );
1274        let top_left_quad = Self::quad_helper_uniform(
1275            top_left_pos,
1276            top_left_corner_size,
1277            top_left_atlas,
1278            color,
1279            current_texture_size,
1280        );
1281        quad_matrix_and_uv.push(top_left_quad);
1282
1283        // Top edge
1284        let top_edge_pos = Vec3::new(
1285            position_offset.x + slices.left as i16,
1286            position_offset.y + (world_window_size.y - slices.top) as i16,
1287            0,
1288        );
1289        let top_edge_world_quad_size = UVec2::new(world_edge_width, slices.top);
1290        let top_edge_texture_size = UVec2::new(texture_edge_width, slices.top);
1291        let top_edge_atlas = URect::new(
1292            atlas_origin.x + slices.left,
1293            atlas_origin.y, // Top of texture
1294            top_edge_texture_size.x,
1295            top_edge_texture_size.y,
1296        );
1297        let top_edge_quad = Self::quad_helper_uniform(
1298            top_edge_pos,
1299            top_edge_world_quad_size,
1300            top_edge_atlas,
1301            color,
1302            current_texture_size,
1303        );
1304        quad_matrix_and_uv.push(top_edge_quad);
1305
1306        // Top right corner
1307        let top_right_pos = Vec3::new(
1308            position_offset.x + (world_window_size.x - slices.right) as i16,
1309            position_offset.y + (world_window_size.y - slices.top) as i16,
1310            0,
1311        );
1312        let top_right_corner_size = UVec2::new(slices.right, slices.top);
1313        let top_right_atlas = URect::new(
1314            atlas_origin.x + texture_window_size.x - slices.right,
1315            atlas_origin.y, // Top of texture
1316            top_right_corner_size.x,
1317            top_right_corner_size.y,
1318        );
1319        let top_right_quad = Self::quad_helper_uniform(
1320            top_right_pos,
1321            top_right_corner_size,
1322            top_right_atlas,
1323            color,
1324            current_texture_size,
1325        );
1326        quad_matrix_and_uv.push(top_right_quad);
1327    }
1328
1329    #[allow(clippy::too_many_lines)]
1330    #[inline]
1331    pub fn prepare_nine_slice_single_center_quad(
1332        nine_slice: &NineSlice,
1333        position_offset: Vec3,
1334        quad_matrix_and_uv: &mut Vec<SpriteInstanceUniform>,
1335        current_texture_size: UVec2,
1336    ) {
1337        let world_window_size = nine_slice.size;
1338        let slices = &nine_slice.slices;
1339
1340        // ------------------------------------------------------------
1341        // Validate that our total window size is large enough to hold
1342        // the left+right and top+bottom slices without underflowing.
1343        // ------------------------------------------------------------
1344        assert!(
1345            world_window_size.x >= slices.left + slices.right,
1346            "NineSlice.width ({}) < slices.left + slices.right ({})",
1347            world_window_size.x,
1348            slices.left + slices.right
1349        );
1350        assert!(
1351            world_window_size.y >= slices.top + slices.bottom,
1352            "NineSlice.height ({}) < slices.top + slices.bottom ({})",
1353            world_window_size.y,
1354            slices.top + slices.bottom
1355        );
1356
1357        let texture_window_size = nine_slice.size_inside_atlas.unwrap_or(current_texture_size);
1358
1359        // check the texture region as well
1360        assert!(
1361            texture_window_size.x >= slices.left + slices.right,
1362            "texture_window_size.width ({}) < slices.left + slices.right ({})",
1363            texture_window_size.x,
1364            slices.left + slices.right
1365        );
1366        assert!(
1367            texture_window_size.y >= slices.top + slices.bottom,
1368            "texture_window_size.height ({}) < slices.top + slices.bottom ({})",
1369            texture_window_size.y,
1370            slices.top + slices.bottom
1371        );
1372        // ------------------------------------------------------------
1373
1374        let color = nine_slice.color;
1375
1376        let atlas_origin = nine_slice.origin_in_atlas;
1377        let texture_window_size = nine_slice.size_inside_atlas.unwrap_or(current_texture_size);
1378
1379        let world_edge_width = nine_slice.size.x - slices.left - slices.right;
1380        let world_edge_height = nine_slice.size.y - slices.top - slices.bottom;
1381        let texture_edge_width = texture_window_size.x - slices.left - slices.right;
1382        let texture_edge_height = texture_window_size.y - slices.top - slices.bottom;
1383
1384        // Lower left Corner
1385        // Y goes up, X goes to the right, right-handed coordinate system
1386        let lower_left_pos = Vec3::new(position_offset.x, position_offset.y, 0);
1387        let corner_size = UVec2::new(slices.left, slices.bottom);
1388        // it should be pixel perfect so it is the same size as the texture cut out
1389        let lower_left_quad_size = UVec2::new(corner_size.x, corner_size.y);
1390        let lower_left_atlas = URect::new(
1391            atlas_origin.x,
1392            atlas_origin.y + texture_window_size.y - slices.bottom, // Bottom of texture minus bottom slice height
1393            corner_size.x,
1394            corner_size.y,
1395        );
1396        let lower_left_quad = Self::quad_helper_uniform(
1397            lower_left_pos,
1398            lower_left_quad_size,
1399            lower_left_atlas,
1400            color,
1401            current_texture_size,
1402        );
1403        quad_matrix_and_uv.push(lower_left_quad);
1404
1405        // Lower edge
1406        let lower_side_position =
1407            Vec3::new(position_offset.x + slices.left as i16, position_offset.y, 0);
1408        // World quad size is potentially wider than the texture,
1409        // that is fine, since the texture will be repeated.
1410        let lower_side_world_quad_size = UVec2::new(world_edge_width, slices.bottom);
1411        let lower_side_texture_size = UVec2::new(texture_edge_width, slices.bottom);
1412        // Lower edge
1413        let lower_side_atlas = URect::new(
1414            atlas_origin.x + slices.left,
1415            atlas_origin.y + texture_window_size.y - slices.bottom, // Bottom of texture minus bottom slice height
1416            lower_side_texture_size.x,
1417            lower_side_texture_size.y,
1418        );
1419        let lower_side_quad = Self::quad_helper_uniform(
1420            lower_side_position,
1421            lower_side_world_quad_size,
1422            lower_side_atlas,
1423            color,
1424            current_texture_size,
1425        );
1426        quad_matrix_and_uv.push(lower_side_quad);
1427
1428        // Lower right corner
1429        let lower_right_pos = Vec3::new(
1430            position_offset.x + (world_window_size.x - slices.right) as i16,
1431            position_offset.y,
1432            0,
1433        );
1434        let lower_right_corner_size = UVec2::new(slices.right, slices.bottom);
1435        let lower_right_atlas = URect::new(
1436            atlas_origin.x + texture_window_size.x - slices.right,
1437            atlas_origin.y + texture_window_size.y - slices.bottom, // Bottom of texture minus bottom slice height
1438            lower_right_corner_size.x,
1439            lower_right_corner_size.y,
1440        );
1441        let lower_right_quad = Self::quad_helper_uniform(
1442            lower_right_pos,
1443            lower_right_corner_size,
1444            lower_right_atlas,
1445            color,
1446            current_texture_size,
1447        );
1448        quad_matrix_and_uv.push(lower_right_quad);
1449
1450        // Left edge
1451        let left_edge_pos = Vec3::new(
1452            position_offset.x,
1453            position_offset.y + slices.bottom as i16,
1454            0,
1455        );
1456        let left_edge_world_quad_size = UVec2::new(slices.left, world_edge_height);
1457        let left_edge_texture_size = UVec2::new(slices.left, texture_edge_height);
1458        let left_edge_atlas = URect::new(
1459            atlas_origin.x,
1460            atlas_origin.y + slices.top, // Skip top slice
1461            left_edge_texture_size.x,
1462            left_edge_texture_size.y,
1463        );
1464        let left_edge_quad = Self::quad_helper_uniform(
1465            left_edge_pos,
1466            left_edge_world_quad_size,
1467            left_edge_atlas,
1468            color,
1469            current_texture_size,
1470        );
1471        quad_matrix_and_uv.push(left_edge_quad);
1472
1473        // Center as a single, stretched quad
1474        let center_pos = Vec3::new(
1475            position_offset.x + slices.left as i16,
1476            position_offset.y + slices.bottom as i16,
1477            0,
1478        );
1479        let center_world_size = UVec2::new(world_edge_width, world_edge_height);
1480        // atlas_origin.x + slices.left  => x of center in atlas
1481        // atlas_origin.y + slices.top   => y of center in atlas
1482        let center_atlas = URect::new(
1483            atlas_origin.x + slices.left,
1484            atlas_origin.y + slices.top,
1485            texture_edge_width,
1486            texture_edge_height,
1487        );
1488        let center_quad = Self::quad_helper_uniform(
1489            center_pos,
1490            center_world_size,
1491            center_atlas,
1492            color,
1493            current_texture_size,
1494        );
1495        quad_matrix_and_uv.push(center_quad);
1496
1497        // Right edge
1498        let right_edge_pos = Vec3::new(
1499            position_offset.x + (world_window_size.x - slices.right) as i16,
1500            position_offset.y + slices.bottom as i16,
1501            0,
1502        );
1503        let right_edge_world_quad_size = UVec2::new(slices.right, world_edge_height);
1504        let right_edge_texture_size = UVec2::new(slices.right, texture_edge_height);
1505        let right_edge_atlas = URect::new(
1506            atlas_origin.x + texture_window_size.x - slices.right,
1507            atlas_origin.y + slices.top, // Skip top slice
1508            right_edge_texture_size.x,
1509            right_edge_texture_size.y,
1510        );
1511
1512        let right_edge_quad = Self::quad_helper_uniform(
1513            right_edge_pos,
1514            right_edge_world_quad_size,
1515            right_edge_atlas,
1516            color,
1517            current_texture_size,
1518        );
1519        quad_matrix_and_uv.push(right_edge_quad);
1520
1521        // Top left corner
1522        let top_left_pos = Vec3::new(
1523            position_offset.x,
1524            position_offset.y + (world_window_size.y - slices.top) as i16,
1525            0,
1526        );
1527        let top_left_corner_size = UVec2::new(slices.left, slices.top);
1528        let top_left_atlas = URect::new(
1529            atlas_origin.x,
1530            atlas_origin.y, // Top of texture
1531            top_left_corner_size.x,
1532            top_left_corner_size.y,
1533        );
1534        let top_left_quad = Self::quad_helper_uniform(
1535            top_left_pos,
1536            top_left_corner_size,
1537            top_left_atlas,
1538            color,
1539            current_texture_size,
1540        );
1541        quad_matrix_and_uv.push(top_left_quad);
1542
1543        // Top edge
1544        let top_edge_pos = Vec3::new(
1545            position_offset.x + slices.left as i16,
1546            position_offset.y + (world_window_size.y - slices.top) as i16,
1547            0,
1548        );
1549        let top_edge_world_quad_size = UVec2::new(world_edge_width, slices.top);
1550        let top_edge_texture_size = UVec2::new(texture_edge_width, slices.top);
1551        let top_edge_atlas = URect::new(
1552            atlas_origin.x + slices.left,
1553            atlas_origin.y, // Top of texture
1554            top_edge_texture_size.x,
1555            top_edge_texture_size.y,
1556        );
1557        let top_edge_quad = Self::quad_helper_uniform(
1558            top_edge_pos,
1559            top_edge_world_quad_size,
1560            top_edge_atlas,
1561            color,
1562            current_texture_size,
1563        );
1564        quad_matrix_and_uv.push(top_edge_quad);
1565
1566        // Top right corner
1567        let top_right_pos = Vec3::new(
1568            position_offset.x + (world_window_size.x - slices.right) as i16,
1569            position_offset.y + (world_window_size.y - slices.top) as i16,
1570            0,
1571        );
1572        let top_right_corner_size = UVec2::new(slices.right, slices.top);
1573        let top_right_atlas = URect::new(
1574            atlas_origin.x + texture_window_size.x - slices.right,
1575            atlas_origin.y, // Top of texture
1576            top_right_corner_size.x,
1577            top_right_corner_size.y,
1578        );
1579        let top_right_quad = Self::quad_helper_uniform(
1580            top_right_pos,
1581            top_right_corner_size,
1582            top_right_atlas,
1583            color,
1584            current_texture_size,
1585        );
1586        quad_matrix_and_uv.push(top_right_quad);
1587    }
1588
1589    fn sort_and_put_in_batches(&mut self) -> Vec<Vec<&RenderItem>> {
1590        sort_render_items_by_z_and_material(&mut self.items);
1591
1592        self.order_render_items_in_batches()
1593    }
1594
1595    /// # Panics
1596    ///
1597    #[allow(clippy::too_many_lines)]
1598    pub fn render(
1599        &mut self,
1600        command_encoder: &mut CommandEncoder,
1601        display_surface_texture_view: &TextureView,
1602        //        materials: &Assets<Material>,
1603        textures: &Assets<Texture>,
1604        fonts: &Assets<Font>,
1605        now: Millis,
1606    ) {
1607        self.debug_tick += 1;
1608        trace!("start render()");
1609        self.last_render_at = now;
1610
1611        self.set_viewport_and_view_projection_matrix();
1612
1613        self.write_vertex_indices_and_uv_to_buffer(textures, fonts);
1614
1615        self.render_batches_to_virtual_texture(command_encoder, textures);
1616
1617        self.render_virtual_texture_to_display(command_encoder, display_surface_texture_view);
1618    }
1619
1620    pub fn set_viewport_and_view_projection_matrix(&mut self) {
1621        let view_proj_matrix = create_view_projection_matrix_from_virtual(
1622            self.virtual_surface_size.x,
1623            self.virtual_surface_size.y,
1624        );
1625
1626        let scale_matrix = Matrix4::from_scale(self.scale, self.scale, 0.0);
1627        let origin_translation_matrix =
1628            Matrix4::from_translation(f32::from(-self.origin.x), f32::from(-self.origin.y), 0.0);
1629
1630        let total_matrix = scale_matrix * view_proj_matrix * origin_translation_matrix;
1631
1632        // write all model_matrix and uv_coords to instance buffer once, before the render pass
1633        self.queue.write_buffer(
1634            &self.camera_buffer,
1635            0,
1636            bytemuck::cast_slice(&[total_matrix]),
1637        );
1638    }
1639
1640    pub fn render_batches_to_virtual_texture(
1641        &mut self,
1642        command_encoder: &mut CommandEncoder,
1643        textures: &Assets<Texture>,
1644    ) {
1645        let mut render_pass = command_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1646            label: Some("Game Render Pass"),
1647            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1648                view: &self.virtual_surface_texture_view,
1649                depth_slice: None,
1650                resolve_target: None,
1651                ops: wgpu::Operations {
1652                    load: wgpu::LoadOp::Clear(self.clear_color),
1653                    store: wgpu::StoreOp::Store,
1654                },
1655            })],
1656            depth_stencil_attachment: None,
1657            timestamp_writes: None,
1658            occlusion_query_set: None,
1659            multiview_mask: None,
1660        });
1661
1662        render_pass.set_viewport(
1663            0.0,
1664            0.0,
1665            f32::from(self.virtual_surface_size.x),
1666            f32::from(self.virtual_surface_size.y),
1667            0.0,
1668            1.0,
1669        );
1670
1671        // Index and vertex buffers never change
1672        render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
1673        render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1674
1675        // Vertex buffer is reused
1676        render_pass.set_vertex_buffer(1, self.quad_matrix_and_uv_instance_buffer.slice(..));
1677
1678        let num_indices = mireforge_wgpu_sprites::INDICES.len() as u32;
1679
1680        let mut current_pipeline: Option<&MaterialKind> = None;
1681
1682        for &(ref weak_material_ref, start, count) in &self.batch_offsets {
1683            let wgpu_material = weak_material_ref;
1684
1685            let pipeline_kind = &wgpu_material.kind;
1686
1687            if current_pipeline != Some(pipeline_kind) {
1688                let pipeline = match pipeline_kind {
1689                    MaterialKind::NormalSprite { .. } => &self.normal_sprite_pipeline.pipeline,
1690                    MaterialKind::Quad => &self.quad_shader_info.pipeline,
1691                    MaterialKind::AlphaMasker { .. } => &self.mask_shader_info.pipeline,
1692                    MaterialKind::LightAdd { .. } => &self.light_shader_info.pipeline,
1693                };
1694                //trace!(%pipeline_kind, ?pipeline, "setting pipeline");
1695                render_pass.set_pipeline(pipeline);
1696                // Apparently after setting pipeline,
1697                // you must set all bind groups again
1698                current_pipeline = Some(pipeline_kind);
1699                render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
1700            }
1701
1702            match &wgpu_material.kind {
1703                MaterialKind::NormalSprite { primary_texture }
1704                | MaterialKind::LightAdd { primary_texture } => {
1705                    let texture = textures.get(primary_texture).unwrap();
1706                    // Bind the texture and sampler bind group (Bind Group 1)
1707                    render_pass.set_bind_group(1, &texture.texture_and_sampler_bind_group, &[]);
1708                }
1709                MaterialKind::AlphaMasker {
1710                    primary_texture,
1711                    alpha_texture,
1712                } => {
1713                    let real_diffuse_texture = textures.get(primary_texture).unwrap();
1714                    let alpha_texture = textures.get(alpha_texture).unwrap();
1715                    render_pass.set_bind_group(
1716                        1,
1717                        &real_diffuse_texture.texture_and_sampler_bind_group,
1718                        &[],
1719                    );
1720                    render_pass.set_bind_group(
1721                        2,
1722                        &alpha_texture.texture_and_sampler_bind_group,
1723                        &[],
1724                    );
1725                }
1726                MaterialKind::Quad => {
1727                    // Intentionally do nothing
1728                }
1729            }
1730            assert!(
1731                count <= MAXIMUM_QUADS_IN_A_BATCH as u32,
1732                "too many instanced draw in a batch {count}"
1733            );
1734
1735            // Issue the instanced draw call for the batch
1736            trace!(material=%weak_material_ref, start=%start, count=%count, %num_indices, "draw instanced");
1737            render_pass.draw_indexed(0..num_indices, 0, start..(start + count));
1738        }
1739        self.items.clear();
1740    }
1741
1742    pub fn render_virtual_texture_to_display(
1743        &mut self,
1744        command_encoder: &mut CommandEncoder,
1745        display_surface_texture_view: &TextureView,
1746    ) {
1747        let mut render_pass = command_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1748            label: Some("Screen Render Pass"),
1749            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1750                view: display_surface_texture_view,
1751                depth_slice: None,
1752                resolve_target: None,
1753                ops: wgpu::Operations {
1754                    load: wgpu::LoadOp::Clear(self.screen_clear_color),
1755                    store: wgpu::StoreOp::Store,
1756                },
1757            })],
1758            depth_stencil_attachment: None,
1759            timestamp_writes: None,
1760            occlusion_query_set: None,
1761            multiview_mask: None,
1762        });
1763
1764        /*
1765        let scale_x = window_width as f32 / VIRTUAL_WIDTH as f32;
1766        let scale_y = window_height as f32 / VIRTUAL_HEIGHT as f32;
1767        let scale = scale_x.min(scale_y).floor(); // Use integer scaling
1768
1769        let viewport_width = VIRTUAL_WIDTH as f32 * scale;
1770        let viewport_height = VIRTUAL_HEIGHT as f32 * scale;
1771        let viewport_x = (window_width as f32 - viewport_width) / 2.0;
1772        let viewport_y = (window_height as f32 - viewport_height) / 2.0;
1773         */
1774
1775        self.viewport = match self.viewport_strategy {
1776            ViewportStrategy::FitIntegerScaling => Self::viewport_from_integer_scale(
1777                self.physical_surface_size,
1778                self.virtual_surface_size,
1779            ),
1780            ViewportStrategy::FitFloatScaling => Self::viewport_from_float_scale(
1781                self.physical_surface_size,
1782                self.virtual_surface_size,
1783            ),
1784            ViewportStrategy::MatchPhysicalSize => URect::new(
1785                0,
1786                0,
1787                self.physical_surface_size.x,
1788                self.physical_surface_size.y,
1789            ),
1790        };
1791
1792        render_pass.set_viewport(
1793            f32::from(self.viewport.position.x),
1794            f32::from(self.viewport.position.y),
1795            f32::from(self.viewport.size.x),
1796            f32::from(self.viewport.size.y),
1797            0.0,
1798            1.0,
1799        );
1800
1801        // Draw the render texture to the screen
1802        render_pass.set_pipeline(&self.virtual_to_screen_shader_info.pipeline);
1803        render_pass.set_bind_group(0, &self.virtual_to_surface_bind_group, &[]);
1804        render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1805
1806        render_pass.draw(0..6, 0..1);
1807    }
1808
1809    pub fn texture_resource_from_texture(&self, texture: &wgpu::Texture, label: &str) -> Texture {
1810        trace!("load texture from memory with name: '{label}'");
1811        let size = &texture.size();
1812        let texture_and_sampler_bind_group =
1813            mireforge_wgpu_sprites::create_sprite_texture_and_sampler_bind_group(
1814                &self.device,
1815                &self.texture_sampler_bind_group_layout,
1816                texture,
1817                &self.sampler,
1818                label,
1819            );
1820
1821        let texture_size = UVec2::new(size.width as u16, size.height as u16);
1822
1823        Texture {
1824            texture_and_sampler_bind_group,
1825            texture_size,
1826        }
1827    }
1828}
1829
1830fn create_view_projection_matrix_from_virtual(virtual_width: u16, virtual_height: u16) -> Matrix4 {
1831    let (bottom, top) = (0.0, f32::from(virtual_height));
1832
1833    // flip Z by swapping near/far if you want the opposite handedness
1834    // (e.g. for a left-handed vs right-handed depth axis)
1835    let (near, far) =
1836        // standard: near < far maps 0 → near, 1 → far
1837        (/* nearPlaneDepth */ -1.0, /* farPlaneDepth */ 1.0)
1838    ;
1839
1840    OrthoInfo {
1841        left: 0.0,
1842        right: f32::from(virtual_width),
1843        bottom,
1844        top,
1845        near, // Maybe flipped? -1.0
1846        far,  // maybe flipped? 1.0 or 0.0
1847    }
1848    .into()
1849}
1850
1851fn create_view_uniform_view_projection_matrix(viewport_size: UVec2) -> Matrix4 {
1852    let viewport_width = f32::from(viewport_size.x);
1853    let viewport_height = f32::from(viewport_size.y);
1854
1855    let viewport_aspect_ratio = viewport_width / viewport_height;
1856
1857    let scale_x = 1.0;
1858    let scale_y = viewport_aspect_ratio;
1859
1860    let view_projection_matrix = [
1861        [scale_x, 0.0, 0.0, 0.0],
1862        [0.0, scale_y, 0.0, 0.0],
1863        [0.0, 0.0, -1.0, 0.0],
1864        [0.0, 0.0, 0.0, 1.0],
1865    ];
1866
1867    view_projection_matrix.into()
1868}
1869
1870fn sort_render_items_by_z_and_material(items: &mut [RenderItem]) {
1871    items.sort_by_key(|item| (item.position.z, item.material_ref.clone()));
1872}
1873
1874#[derive(Debug, Clone, Copy, Default)]
1875pub enum Rotation {
1876    #[default]
1877    Degrees0,
1878    Degrees90,
1879    Degrees180,
1880    Degrees270,
1881}
1882
1883#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1884pub enum Anchor {
1885    LowerLeft,
1886    UpperLeft,
1887}
1888
1889#[derive(Debug, Copy, Clone)]
1890pub struct SpriteParams {
1891    pub texture_size: UVec2,
1892    pub texture_pos: UVec2,
1893    pub scale: u8,
1894    pub rotation: Rotation,
1895    pub flip_x: bool,
1896    pub flip_y: bool,
1897    pub pivot: Vec2,
1898    pub color: Color,
1899    pub anchor: Anchor,
1900}
1901
1902impl Default for SpriteParams {
1903    fn default() -> Self {
1904        Self {
1905            texture_size: UVec2::new(0, 0),
1906            texture_pos: UVec2::new(0, 0),
1907            pivot: Vec2::new(0, 0),
1908            flip_x: false,
1909            flip_y: false,
1910            color: Color::from_octet(255, 255, 255, 255),
1911            scale: 1,
1912            rotation: Rotation::Degrees0,
1913            anchor: Anchor::LowerLeft,
1914        }
1915    }
1916}
1917
1918#[derive(Debug, Copy, Clone)]
1919pub struct QuadParams {
1920    pub scale: u8,
1921    pub pivot: Vec2,
1922}
1923
1924impl Default for QuadParams {
1925    fn default() -> Self {
1926        Self {
1927            pivot: Vec2::new(0, 0),
1928            scale: 1,
1929        }
1930    }
1931}
1932
1933pub type BindGroupRef = Arc<BindGroup>;
1934
1935#[derive(Debug, PartialEq, Eq, Asset)]
1936pub struct Texture {
1937    pub texture_and_sampler_bind_group: BindGroup,
1938    //    pub pipeline: RenderPipelineRef,
1939    pub texture_size: UVec2,
1940}
1941
1942impl Display for Texture {
1943    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
1944        write!(f, "{:?}", self.texture_size)
1945    }
1946}
1947
1948impl PartialOrd<Self> for Texture {
1949    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1950        Some(
1951            self.texture_and_sampler_bind_group
1952                .cmp(&other.texture_and_sampler_bind_group),
1953        )
1954    }
1955}
1956
1957impl Ord for Texture {
1958    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1959        self.texture_and_sampler_bind_group
1960            .cmp(&other.texture_and_sampler_bind_group)
1961    }
1962}
1963
1964#[derive(Debug, Ord, PartialOrd, PartialEq, Eq)]
1965pub struct MaterialBase {
1966    //pub pipeline: PipelineRef,
1967}
1968
1969#[derive(Debug, Ord, PartialOrd, PartialEq, Eq)]
1970pub struct Material {
1971    pub base: MaterialBase,
1972    pub kind: MaterialKind,
1973}
1974
1975impl Material {
1976    #[inline]
1977    #[must_use]
1978    pub fn primary_texture(&self) -> Option<TextureRef> {
1979        self.kind.primary_texture()
1980    }
1981
1982    #[inline]
1983    #[must_use]
1984    pub fn is_complete(&self, textures: &Assets<Texture>) -> bool {
1985        self.kind.is_complete(textures)
1986    }
1987}
1988
1989impl Display for Material {
1990    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1991        write!(f, "{}", self.kind)
1992    }
1993}
1994
1995#[derive(Debug, Ord, PartialOrd, PartialEq, Eq)]
1996pub enum MaterialKind {
1997    NormalSprite {
1998        primary_texture: Id<Texture>,
1999    },
2000    AlphaMasker {
2001        primary_texture: Id<Texture>,
2002        alpha_texture: Id<Texture>,
2003    },
2004    Quad,
2005    LightAdd {
2006        primary_texture: Id<Texture>,
2007    },
2008}
2009
2010impl MaterialKind {}
2011
2012impl MaterialKind {
2013    #[must_use]
2014    pub fn primary_texture(&self) -> Option<Id<Texture>> {
2015        match &self {
2016            Self::NormalSprite {
2017                primary_texture, ..
2018            }
2019            | Self::LightAdd { primary_texture }
2020            | Self::AlphaMasker {
2021                primary_texture, ..
2022            } => Some(primary_texture.clone()),
2023            Self::Quad => None,
2024        }
2025    }
2026
2027    pub(crate) fn is_complete(&self, textures: &Assets<Texture>) -> bool {
2028        match &self {
2029            Self::NormalSprite { primary_texture } | Self::LightAdd { primary_texture } => {
2030                textures.contains(primary_texture)
2031            }
2032            Self::AlphaMasker {
2033                primary_texture,
2034                alpha_texture,
2035            } => textures.contains(primary_texture) && textures.contains(alpha_texture),
2036            Self::Quad => true,
2037        }
2038    }
2039}
2040
2041impl Display for MaterialKind {
2042    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2043        let texture_name = self
2044            .primary_texture()
2045            .map_or_else(String::new, |x| x.to_string());
2046
2047        let kind_name = match self {
2048            Self::NormalSprite { .. } => "NormalSprite",
2049            Self::LightAdd { .. } => "Light (Add)",
2050            Self::Quad => "Quad",
2051            Self::AlphaMasker { .. } => "AlphaMasker",
2052        };
2053
2054        write!(f, "{kind_name} texture {texture_name}")
2055    }
2056}
2057
2058#[derive(Debug)]
2059pub struct Sprite {
2060    pub params: SpriteParams,
2061}
2062
2063#[derive(Debug)]
2064pub struct QuadColor {
2065    pub size: UVec2,
2066    pub color: Color,
2067    pub params: QuadParams,
2068}
2069
2070#[derive(Debug, Copy, Clone)]
2071pub struct Slices {
2072    pub left: u16,
2073    pub top: u16,
2074    pub right: u16,  // how many pixels from the right side of the texture and going in
2075    pub bottom: u16, // how much to take from bottom of slice
2076}
2077
2078#[derive(Debug)]
2079pub struct NineSlice {
2080    pub size: UVec2, // size of whole "window"
2081    pub slices: Slices,
2082    pub color: Color, // color tint
2083    pub origin_in_atlas: UVec2,
2084    pub size_inside_atlas: Option<UVec2>,
2085}
2086
2087#[derive(Debug)]
2088pub struct TileMap {
2089    pub tiles_data_grid_size: UVec2,
2090    pub cell_count_size: UVec2,
2091    pub one_cell_size: UVec2,
2092    pub tiles: Vec<u16>,
2093    pub scale: u8,
2094}
2095
2096#[derive(PartialEq, Debug, Eq, Ord, PartialOrd)]
2097pub struct Pipeline {
2098    name: String,
2099    render_pipeline: RenderPipeline,
2100}
2101
2102impl Display for Pipeline {
2103    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2104        write!(f, "pipeline: {}", self.name)
2105    }
2106}
2107
2108pub type PipelineRef = Arc<Pipeline>;