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 */
5pub mod plugin;
6pub mod prelude;
7
8use int_math::{URect, UVec2, Vec2, Vec3};
9use limnus_assets::Assets;
10use limnus_assets::prelude::{Asset, Id, RawAssetId, RawWeakId, WeakId};
11use limnus_resource::prelude::Resource;
12use limnus_wgpu_math::{Matrix4, OrthoInfo, Vec4};
13use mireforge_font::Font;
14use mireforge_font::FontRef;
15use mireforge_font::WeakFontRef;
16use mireforge_render::prelude::*;
17use mireforge_wgpu_sprites::{SpriteInfo, SpriteInstanceUniform};
18use monotonic_time_rs::Millis;
19use std::cmp::Ordering;
20use std::fmt::Debug;
21use std::mem::swap;
22use std::sync::Arc;
23use tracing::trace;
24use wgpu::{BindGroup, BindGroupLayout, Buffer, RenderPass, RenderPipeline};
25
26pub type MaterialRef = Id<Material>;
27pub type WeakMaterialRef = WeakId<Material>;
28
29pub trait FrameLookup {
30    fn lookup(&self, frame: u16) -> (&MaterialRef, URect);
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct FixedAtlas {
35    pub material: MaterialRef,
36    pub texture_size: UVec2,
37    pub one_cell_size: UVec2,
38    pub cell_count_size: UVec2,
39}
40
41impl FixedAtlas {
42    /// # Panics
43    ///
44    #[must_use]
45    pub fn new(one_cell_size: UVec2, texture_size: UVec2, material_ref: MaterialRef) -> Self {
46        let cell_count_size = UVec2::new(
47            texture_size.x / one_cell_size.x,
48            texture_size.y / one_cell_size.y,
49        );
50
51        assert_ne!(cell_count_size.x, 0, "illegal texture and one cell size");
52
53        Self {
54            material: material_ref,
55            texture_size,
56            one_cell_size,
57            cell_count_size,
58        }
59    }
60}
61
62impl FrameLookup for FixedAtlas {
63    fn lookup(&self, frame: u16) -> (&MaterialRef, URect) {
64        let x = frame % self.cell_count_size.x;
65        let y = frame / self.cell_count_size.x;
66
67        (
68            &self.material,
69            URect::new(
70                x * self.one_cell_size.x,
71                y * self.one_cell_size.y,
72                self.one_cell_size.x,
73                self.one_cell_size.y,
74            ),
75        )
76    }
77}
78
79#[derive(Debug)]
80pub struct NineSliceAndMaterial {
81    pub slices: Slices,
82    pub material_ref: MaterialRef,
83}
84
85#[derive(Debug, PartialEq, Eq)]
86pub struct FontAndMaterial {
87    pub font_ref: FontRef,
88    pub material_ref: MaterialRef,
89}
90
91pub trait Gfx {
92    fn sprite_atlas_frame(&mut self, position: Vec3, frame: u16, atlas: &impl FrameLookup);
93    fn sprite_atlas(&mut self, position: Vec3, atlas_rect: URect, material_ref: &MaterialRef);
94    fn draw_sprite(&mut self, position: Vec3, material_ref: &MaterialRef);
95    fn draw_sprite_ex(&mut self, position: Vec3, material_ref: &MaterialRef, params: &SpriteParams);
96    fn nine_slice(
97        &mut self,
98        position: Vec3,
99        size: UVec2,
100        color: Color,
101        nine_slice: &NineSliceAndMaterial,
102    );
103    fn set_origin(&mut self, position: Vec2);
104    fn set_clear_color(&mut self, color: Color);
105
106    fn tilemap_params(
107        &mut self,
108        position: Vec3,
109        tiles: &[u16],
110        width: u16,
111        atlas_ref: &FixedAtlas,
112        scale: u8,
113    );
114
115    fn text_draw(&mut self, position: Vec3, text: &str, font_ref: &FontAndMaterial, color: &Color);
116
117    #[must_use]
118    fn now(&self) -> Millis;
119
120    #[must_use]
121    fn physical_aspect_ratio(&self) -> AspectRatio;
122
123    #[must_use]
124    fn physical_size(&self) -> UVec2;
125
126    fn set_viewport(&mut self, viewport_strategy: ViewportStrategy);
127
128    #[must_use]
129    fn viewport(&self) -> &ViewportStrategy;
130
131    fn set_scale(&mut self, scale_factor: VirtualScale);
132}
133
134fn to_wgpu_color(c: Color) -> wgpu::Color {
135    let f = c.to_f64();
136    wgpu::Color {
137        r: f.0,
138        g: f.1,
139        b: f.2,
140        a: f.3,
141    }
142}
143
144#[derive(Debug)]
145struct RenderItem {
146    position: Vec3,
147    material_ref: WeakMaterialRef,
148
149    renderable: Renderable,
150}
151
152#[derive(Debug)]
153pub struct Text {
154    text: String,
155    font_ref: WeakFontRef,
156    color: Color,
157}
158
159#[derive(Debug)]
160enum Renderable {
161    Sprite(Sprite),
162    QuadColor(QuadColor),
163    NineSlice(NineSlice),
164    TileMap(TileMap),
165    Text(Text),
166}
167
168#[derive(Resource)]
169pub struct Render {
170    index_buffer: Buffer,  // Only indicies for a single identity quad
171    vertex_buffer: Buffer, // Only one identity quad (0,0,1,1)
172    sampler: wgpu::Sampler,
173    pipeline: RenderPipelineRef,
174    physical_surface_size: UVec2,
175    viewport_strategy: ViewportStrategy,
176    // Group 0
177    camera_bind_group: BindGroup,
178    #[allow(unused)]
179    camera_buffer: Buffer,
180
181    // Group 1
182    texture_sampler_bind_group_layout: BindGroupLayout,
183
184    // Group 1
185    quad_matrix_and_uv_instance_buffer: Buffer,
186
187    device: Arc<wgpu::Device>,
188    queue: Arc<wgpu::Queue>, // Queue to talk to device
189
190    // Internals
191    items: Vec<RenderItem>,
192    //fonts: Vec<FontAndMaterialRef>,
193    origin: Vec2,
194
195    // Cache
196    batch_offsets: Vec<(WeakMaterialRef, u32, u32)>,
197    viewport: URect,
198    clear_color: wgpu::Color,
199    last_render_at: Millis,
200    scale: f32,
201}
202
203impl Debug for Render {
204    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205        write!(f, "Render")
206    }
207}
208
209impl Gfx for Render {
210    fn sprite_atlas_frame(&mut self, position: Vec3, frame: u16, atlas: &impl FrameLookup) {
211        self.sprite_atlas_frame(position, frame, atlas);
212    }
213
214    fn sprite_atlas(&mut self, position: Vec3, atlas_rect: URect, material_ref: &MaterialRef) {
215        self.sprite_atlas(position, atlas_rect, material_ref);
216    }
217
218    fn draw_sprite(&mut self, position: Vec3, material_ref: &MaterialRef) {
219        self.draw_sprite(position, material_ref);
220    }
221
222    fn draw_sprite_ex(
223        &mut self,
224        position: Vec3,
225        material_ref: &MaterialRef,
226        params: &SpriteParams,
227    ) {
228        self.draw_sprite_ex(position, material_ref, *params);
229    }
230
231    fn nine_slice(
232        &mut self,
233        position: Vec3,
234        size: UVec2,
235        color: Color,
236        nine_slice: &NineSliceAndMaterial,
237    ) {
238        self.nine_slice(position, size, color, nine_slice);
239    }
240
241    fn set_origin(&mut self, position: Vec2) {
242        self.origin = position;
243    }
244
245    fn set_clear_color(&mut self, color: Color) {
246        self.clear_color = to_wgpu_color(color);
247    }
248
249    fn tilemap_params(
250        &mut self,
251        position: Vec3,
252        tiles: &[u16],
253        width: u16,
254        atlas_ref: &FixedAtlas,
255        scale: u8,
256    ) {
257        self.items.push(RenderItem {
258            position,
259            material_ref: (&atlas_ref.material).into(),
260            renderable: Renderable::TileMap(TileMap {
261                tiles_data_grid_size: UVec2::new(width, tiles.len() as u16 / width),
262                cell_count_size: atlas_ref.cell_count_size,
263                one_cell_size: atlas_ref.one_cell_size,
264                tiles: Vec::from(tiles),
265                scale,
266            }),
267        });
268    }
269
270    fn text_draw(
271        &mut self,
272        position: Vec3,
273        text: &str,
274        font_and_mat: &FontAndMaterial,
275        color: &Color,
276    ) {
277        self.items.push(RenderItem {
278            position,
279            material_ref: (&font_and_mat.material_ref).into(),
280            renderable: Renderable::Text(Text {
281                text: text.to_string(),
282                font_ref: (&font_and_mat.font_ref).into(),
283                color: *color,
284            }),
285        });
286    }
287
288    fn now(&self) -> Millis {
289        self.last_render_at
290    }
291
292    fn physical_aspect_ratio(&self) -> AspectRatio {
293        self.physical_surface_size.into()
294    }
295
296    fn physical_size(&self) -> UVec2 {
297        self.physical_surface_size
298    }
299
300    fn set_viewport(&mut self, viewport_strategy: ViewportStrategy) {
301        self.viewport_strategy = viewport_strategy;
302    }
303
304    fn viewport(&self) -> &ViewportStrategy {
305        &self.viewport_strategy
306    }
307
308    fn set_scale(&mut self, scale_factor: VirtualScale) {
309        match scale_factor {
310            VirtualScale::IntScale(scale) => self.scale = scale as f32,
311            VirtualScale::FloatScale(scale) => self.scale = scale,
312        }
313    }
314}
315
316impl Render {
317    #[must_use]
318    pub fn new(
319        device: Arc<wgpu::Device>,
320        queue: Arc<wgpu::Queue>, // Queue to talk to device
321        surface_texture_format: wgpu::TextureFormat,
322        physical_size: UVec2,
323        virtual_surface_size: UVec2,
324        now: Millis,
325    ) -> Self {
326        let (vertex_shader_source, fragment_shader_source) = sources();
327
328        let sprite_info = SpriteInfo::new(
329            &device,
330            surface_texture_format,
331            vertex_shader_source,
332            fragment_shader_source,
333            create_view_uniform_view_projection_matrix(physical_size),
334        );
335
336        Self {
337            device,
338            queue,
339            items: Vec::new(),
340            //   fonts: Vec::new(),
341            sampler: sprite_info.sampler,
342            pipeline: Arc::new(sprite_info.sprite_pipeline),
343            texture_sampler_bind_group_layout: sprite_info.sprite_texture_sampler_bind_group_layout,
344            index_buffer: sprite_info.index_buffer,
345            vertex_buffer: sprite_info.vertex_buffer,
346            quad_matrix_and_uv_instance_buffer: sprite_info.quad_matrix_and_uv_instance_buffer,
347            camera_bind_group: sprite_info.camera_bind_group,
348            batch_offsets: Vec::new(),
349            camera_buffer: sprite_info.camera_uniform_buffer,
350            viewport: Self::viewport_from_integer_scale(physical_size, virtual_surface_size),
351            clear_color: to_wgpu_color(Color::from_f32(0.008, 0.015, 0.008, 1.0)),
352            origin: Vec2::new(0, 0),
353            last_render_at: now,
354            physical_surface_size: physical_size,
355            viewport_strategy: ViewportStrategy::FitIntegerScaling(virtual_surface_size),
356            scale: 1.0,
357        }
358    }
359
360    pub fn set_now(&mut self, now: Millis) {
361        self.last_render_at = now;
362    }
363
364    pub const fn virtual_surface_size(&self) -> UVec2 {
365        match self.viewport_strategy {
366            ViewportStrategy::FitIntegerScaling(virtual_size)
367            | ViewportStrategy::FitFloatScaling(virtual_size) => virtual_size,
368            ViewportStrategy::MatchPhysicalSize => self.physical_surface_size,
369        }
370    }
371
372    pub const fn physical_surface_size(&self) -> UVec2 {
373        self.physical_surface_size
374    }
375
376    pub const fn viewport(&self) -> URect {
377        self.viewport
378    }
379
380    #[inline(always)]
381    fn push_sprite(&mut self, position: Vec3, material: &MaterialRef, sprite: Sprite) {
382        self.items.push(RenderItem {
383            position,
384            material_ref: material.into(),
385            renderable: Renderable::Sprite(sprite),
386        });
387    }
388
389    pub fn push_nine_slice(
390        &mut self,
391        position: Vec3,
392        size: UVec2,
393        color: Color,
394        nine_slice_and_material: &NineSliceAndMaterial,
395    ) {
396        let nine_slice_info = NineSlice {
397            size,
398            slices: nine_slice_and_material.slices.clone(),
399            color,
400            origin_in_atlas: UVec2::new(0, 0),
401            size_inside_atlas: None,
402        };
403
404        self.items.push(RenderItem {
405            position,
406            material_ref: (&nine_slice_and_material.material_ref).into(),
407            renderable: Renderable::NineSlice(nine_slice_info),
408        });
409    }
410
411    #[must_use]
412    pub fn viewport_from_integer_scale(physical_size: UVec2, virtual_size: UVec2) -> URect {
413        let window_aspect = physical_size.x as f32 / physical_size.y as f32;
414        let virtual_aspect = virtual_size.x as f32 / virtual_size.y as f32;
415
416        if physical_size.x < virtual_size.x || physical_size.y < virtual_size.y {
417            return URect::new(0, 0, physical_size.x, physical_size.y);
418        }
419
420        let mut integer_scale = if window_aspect > virtual_aspect {
421            physical_size.y / virtual_size.y
422        } else {
423            physical_size.x / virtual_size.x
424        };
425
426        if integer_scale < 1 {
427            integer_scale = 1;
428        }
429
430        let viewport_actual_size = UVec2::new(
431            virtual_size.x * integer_scale,
432            virtual_size.y * integer_scale,
433        );
434
435        let border_size = physical_size - viewport_actual_size;
436
437        let offset = border_size / 2;
438
439        URect::new(
440            offset.x,
441            offset.y,
442            viewport_actual_size.x,
443            viewport_actual_size.y,
444        )
445    }
446
447    #[must_use]
448    pub fn viewport_from_float_scale(physical_size: UVec2, virtual_size: UVec2) -> URect {
449        let window_aspect = physical_size.x as f32 / physical_size.y as f32;
450        let virtual_aspect = virtual_size.x as f32 / virtual_size.y as f32;
451
452        if physical_size.x < virtual_size.x || physical_size.y < virtual_size.y {
453            return URect::new(0, 0, physical_size.x, physical_size.y);
454        }
455
456        let mut float_scale = if window_aspect > virtual_aspect {
457            physical_size.y as f32 / virtual_size.y as f32
458        } else {
459            physical_size.x as f32 / virtual_size.x as f32
460        };
461
462        if float_scale < 0.01 {
463            float_scale = 0.01;
464        }
465
466        let viewport_actual_size = UVec2::new(
467            (virtual_size.x as f32 * float_scale) as u16,
468            (virtual_size.y as f32 * float_scale) as u16,
469        );
470
471        let border_size = physical_size - viewport_actual_size;
472
473        let offset = border_size / 2;
474
475        URect::new(
476            offset.x,
477            offset.y,
478            viewport_actual_size.x,
479            viewport_actual_size.y,
480        )
481    }
482
483    pub fn resize(&mut self, physical_size: UVec2) {
484        self.physical_surface_size = physical_size;
485    }
486
487    /*
488    pub fn render_sprite(&mut self, position: Vec3, material: &MaterialRef, params: SpriteParams) {
489        let atlas_rect = URect::new(0, 0, material.texture_size().x, material.texture_size().y);
490
491        self.push_sprite(position, material, Sprite { atlas_rect, params });
492    }*/
493
494    pub fn sprite_atlas(&mut self, position: Vec3, atlas_rect: URect, material_ref: &MaterialRef) {
495        self.push_sprite(
496            position,
497            material_ref,
498            Sprite {
499                params: SpriteParams {
500                    texture_pos: atlas_rect.position,
501                    texture_size: atlas_rect.size,
502                    ..Default::default()
503                },
504            },
505        );
506    }
507
508    pub fn sprite_atlas_frame(&mut self, position: Vec3, frame: u16, atlas: &impl FrameLookup) {
509        let (material_ref, atlas_rect) = atlas.lookup(frame);
510        self.push_sprite(
511            position,
512            material_ref,
513            Sprite {
514                params: SpriteParams {
515                    texture_pos: atlas_rect.position,
516                    texture_size: atlas_rect.size,
517                    ..Default::default()
518                },
519            },
520        );
521    }
522
523    pub fn sprite_atlas_frame_ex(
524        &mut self,
525        position: Vec3,
526        frame: u16,
527        atlas: &impl FrameLookup,
528        mut params: SpriteParams,
529    ) {
530        let (material_ref, atlas_rect) = atlas.lookup(frame);
531        params.texture_pos = atlas_rect.position;
532        params.texture_size = atlas_rect.size;
533        self.push_sprite(position, material_ref, Sprite { params });
534    }
535
536    pub fn draw_sprite(&mut self, position: Vec3, material: &MaterialRef) {
537        self.push_sprite(
538            position,
539            material,
540            Sprite {
541                params: SpriteParams::default(),
542            },
543        );
544    }
545
546    pub fn draw_sprite_ex(&mut self, position: Vec3, material: &MaterialRef, params: SpriteParams) {
547        self.push_sprite(position, material, Sprite { params });
548    }
549
550    pub fn nine_slice(
551        &mut self,
552        position: Vec3,
553        size: UVec2,
554        color: Color,
555        nine_slice_and_material: &NineSliceAndMaterial,
556    ) {
557        self.push_nine_slice(position, size, color, nine_slice_and_material);
558    }
559
560    pub fn draw_quad(&mut self, position: Vec3, size: UVec2, color: Color) {
561        self.items.push(RenderItem {
562            position,
563            material_ref: WeakId::<Material>::new(RawWeakId::with_asset_type::<Material>(
564                RawAssetId::new(0, 0),
565                "nothing".into(),
566            )),
567            renderable: Renderable::QuadColor(QuadColor { size, color }),
568        });
569    }
570
571    #[allow(clippy::too_many_arguments)]
572    pub fn draw_nine_slice(
573        &mut self,
574        position: Vec3,
575        size: UVec2,
576        slices: Slices,
577        material_ref: &MaterialRef,
578        color: Color,
579    ) {
580        self.items.push(RenderItem {
581            position,
582            material_ref: material_ref.into(),
583            renderable: Renderable::NineSlice(NineSlice {
584                size,
585                slices,
586                color,
587                origin_in_atlas: UVec2::new(0, 0),
588                size_inside_atlas: None,
589            }),
590        });
591    }
592
593    // TODO: Not done yet
594    /*
595    #[allow(clippy::too_many_arguments)]
596    pub fn draw_nine_slice_atlas(
597        &mut self,
598        position: Vec3,
599        size: UVec2,
600        corner_size: UVec2,
601        texture_window_size: UVec2,
602        material_ref: &MaterialRef,
603        atlas_offset: UVec2,
604        color: Color,
605    ) {
606        self.items.push(RenderItem {
607            position,
608            material_ref: material_ref.into(),
609            renderable: Renderable::NineSlice(NineSlice {
610                corner_size,
611                texture_window_size,
612                size,
613                atlas_offset,
614                color,
615            }),
616        });
617    }
618
619     */
620
621    pub const fn clear_color(&self) -> wgpu::Color {
622        self.clear_color
623    }
624
625    // first two is multiplier and second pair is offset
626    fn calculate_texture_coords_mul_add(atlas_rect: URect, texture_size: UVec2) -> Vec4 {
627        let x = atlas_rect.position.x as f32 / texture_size.x as f32;
628        let y = atlas_rect.position.y as f32 / texture_size.y as f32;
629        let width = atlas_rect.size.x as f32 / texture_size.x as f32;
630        let height = atlas_rect.size.y as f32 / texture_size.y as f32;
631        Vec4([width, height, x, y])
632    }
633
634    fn order_render_items_in_batches(&self) -> Vec<Vec<&RenderItem>> {
635        let mut material_batches: Vec<Vec<&RenderItem>> = Vec::new();
636        let mut current_batch: Vec<&RenderItem> = Vec::new();
637        let mut current_material: Option<&WeakMaterialRef> = None;
638
639        for render_item in &self.items {
640            if Some(&render_item.material_ref) != current_material {
641                if !current_batch.is_empty() {
642                    material_batches.push(current_batch.clone());
643                    current_batch.clear();
644                }
645                current_material = Some(&render_item.material_ref);
646            }
647            current_batch.push(render_item);
648        }
649
650        if !current_batch.is_empty() {
651            material_batches.push(current_batch);
652        }
653
654        material_batches
655    }
656
657    #[must_use]
658    pub fn quad_helper_uniform(
659        position: Vec3,
660        quad_size: UVec2,
661        render_atlas: URect,
662        color: Color,
663
664        current_texture_size: UVec2,
665    ) -> SpriteInstanceUniform {
666        let model_matrix = Matrix4::from_translation(position.x as f32, position.y as f32, 0.0)
667            * Matrix4::from_scale(quad_size.x as f32, quad_size.y as f32, 1.0);
668
669        let tex_coords_mul_add =
670            Self::calculate_texture_coords_mul_add(render_atlas, current_texture_size);
671
672        let rotation_value = 0;
673
674        SpriteInstanceUniform::new(
675            model_matrix,
676            tex_coords_mul_add,
677            rotation_value,
678            Vec4(color.to_f32_slice()),
679            true,
680        )
681    }
682
683    /// # Panics
684    ///
685    #[allow(clippy::too_many_lines)]
686    pub fn prepare_render(&mut self, materials: &Assets<Material>, fonts: &Assets<Font>) {
687        const FLIP_X_MASK: u32 = 0b0000_0100;
688        const FLIP_Y_MASK: u32 = 0b0000_1000;
689
690        sort_render_items_by_z_and_material(&mut self.items);
691
692        let batches = self.order_render_items_in_batches();
693
694        let mut quad_matrix_and_uv: Vec<SpriteInstanceUniform> = Vec::new();
695        let mut batch_vertex_ranges: Vec<(WeakMaterialRef, u32, u32)> = Vec::new();
696
697        for render_items in &batches {
698            let quad_len_before = quad_matrix_and_uv.len() as u32;
699
700            // Fix: Access material_ref through reference and copy it
701            let weak_material_ref = render_items
702                .first()
703                .map(|item| {
704                    // Force copy semantics by dereferencing the shared reference
705                    let material_ref: WeakId<Material> = item.material_ref;
706                    material_ref
707                })
708                .expect("Render items batch was empty");
709
710            let result = materials.get_weak(weak_material_ref);
711            if result.is_none() {
712                // Material is not loaded yet
713                continue;
714            }
715            let material = result.unwrap();
716            let current_texture_size = material.texture_size;
717
718            for render_item in render_items {
719                match &render_item.renderable {
720                    Renderable::Sprite(sprite) => {
721                        let params = &sprite.params;
722                        let mut size = params.texture_size;
723                        if size.x == 0 && size.y == 0 {
724                            size = current_texture_size;
725                        }
726
727                        let render_atlas = URect {
728                            position: params.texture_pos,
729                            size,
730                        };
731
732                        match params.rotation {
733                            Rotation::Degrees90 | Rotation::Degrees270 => {
734                                swap(&mut size.x, &mut size.y);
735                            }
736                            _ => {}
737                        }
738
739                        let model_matrix = Matrix4::from_translation(
740                            render_item.position.x as f32,
741                            render_item.position.y as f32,
742                            0.0,
743                        ) * Matrix4::from_scale(
744                            (size.x * params.scale as u16) as f32,
745                            (size.y * params.scale as u16) as f32,
746                            1.0,
747                        );
748
749                        let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
750                            render_atlas,
751                            current_texture_size,
752                        );
753
754                        let mut rotation_value = match params.rotation {
755                            Rotation::Degrees0 => 0,
756                            Rotation::Degrees90 => 1,
757                            Rotation::Degrees180 => 2,
758                            Rotation::Degrees270 => 3,
759                        };
760
761                        if params.flip_x {
762                            rotation_value |= FLIP_X_MASK;
763                        }
764                        if params.flip_y {
765                            rotation_value |= FLIP_Y_MASK;
766                        }
767
768                        let quad_instance = SpriteInstanceUniform::new(
769                            model_matrix,
770                            tex_coords_mul_add,
771                            rotation_value,
772                            Vec4(params.color.to_f32_slice()),
773                            true,
774                        );
775                        quad_matrix_and_uv.push(quad_instance);
776                    }
777
778                    Renderable::NineSlice(nine_slice) => {
779                        Self::prepare_nine_slice(
780                            nine_slice,
781                            render_item.position,
782                            &mut quad_matrix_and_uv,
783                            current_texture_size,
784                        );
785                    }
786
787                    Renderable::QuadColor(quad) => {
788                        /*
789                        let params = &sprite.params;
790                        let mut size = params.texture_size;
791                        if size.x == 0 && size.y == 0 {
792                            size = current_texture_size;
793                        }
794
795                        let render_atlas = URect {
796                            position: params.texture_pos,
797                            size,
798                        };
799
800
801                        match params.rotation {
802                            Rotation::Degrees90 | Rotation::Degrees270 => {
803                                swap(&mut size.x, &mut size.y);
804                            }
805                            _ => {}
806                        }
807                         */
808
809                        let model_matrix =
810                            Matrix4::from_translation(
811                                render_item.position.x as f32,
812                                render_item.position.y as f32,
813                                0.0,
814                            ) * Matrix4::from_scale(quad.size.x as f32, quad.size.y as f32, 1.0);
815
816                        /*
817                        let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
818                            render_atlas,
819                            current_texture_size,
820                        );
821
822
823                        let mut rotation_value = match params.rotation {
824                            Rotation::Degrees0 => 0,
825                            Rotation::Degrees90 => 1,
826                            Rotation::Degrees180 => 2,
827                            Rotation::Degrees270 => 3,
828                        };
829
830                        if params.flip_x {
831                            rotation_value |= FLIP_X_MASK;
832                        }
833                        if params.flip_y {
834                            rotation_value |= FLIP_Y_MASK;
835                        }
836
837                         */
838
839                        let tex_coords_mul_add = Vec4([
840                            0.0, //x
841                            0.0, //y
842                            0.0, 0.0,
843                        ]);
844                        let rotation_value = 0;
845
846                        let quad_instance = SpriteInstanceUniform::new(
847                            model_matrix,
848                            tex_coords_mul_add,
849                            rotation_value,
850                            Vec4(quad.color.to_f32_slice()),
851                            false,
852                        );
853                        quad_matrix_and_uv.push(quad_instance);
854                    }
855
856                    Renderable::Text(text) => {
857                        let result = fonts.get_weak(text.font_ref);
858                        if result.is_none() {
859                            continue;
860                        }
861                        let font = result.unwrap();
862
863                        let glyphs = font.draw(&text.text);
864                        for glyph in glyphs {
865                            let pos = render_item.position + Vec3::from(glyph.relative_position);
866                            let texture_size = glyph.texture_rectangle.size;
867                            let model_matrix =
868                                Matrix4::from_translation(pos.x as f32, pos.y as f32, 0.0)
869                                    * Matrix4::from_scale(
870                                        texture_size.x as f32,
871                                        texture_size.y as f32,
872                                        1.0,
873                                    );
874                            let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
875                                glyph.texture_rectangle,
876                                current_texture_size,
877                            );
878
879                            let quad_instance = SpriteInstanceUniform::new(
880                                model_matrix,
881                                tex_coords_mul_add,
882                                0,
883                                Vec4(text.color.to_f32_slice()),
884                                true,
885                            );
886                            quad_matrix_and_uv.push(quad_instance);
887                        }
888                    }
889
890                    Renderable::TileMap(tile_map) => {
891                        for (index, tile) in tile_map.tiles.iter().enumerate() {
892                            let cell_pos_x = (index as u16 % tile_map.tiles_data_grid_size.x)
893                                * tile_map.one_cell_size.x
894                                * tile_map.scale as u16;
895                            let cell_pos_y = (index as u16 / tile_map.tiles_data_grid_size.x)
896                                * tile_map.one_cell_size.y
897                                * tile_map.scale as u16;
898                            let cell_x = *tile % tile_map.cell_count_size.x;
899                            let cell_y = *tile / tile_map.cell_count_size.x;
900
901                            let tex_x = cell_x * tile_map.one_cell_size.x;
902                            let tex_y = cell_y * tile_map.one_cell_size.x;
903
904                            let cell_texture_area = URect::new(
905                                tex_x,
906                                tex_y,
907                                tile_map.one_cell_size.x,
908                                tile_map.one_cell_size.y,
909                            );
910
911                            let cell_model_matrix = Matrix4::from_translation(
912                                (render_item.position.x + cell_pos_x as i16) as f32,
913                                (render_item.position.y + cell_pos_y as i16) as f32,
914                                0.0,
915                            ) * Matrix4::from_scale(
916                                (tile_map.one_cell_size.x * tile_map.scale as u16) as f32,
917                                (tile_map.one_cell_size.y * tile_map.scale as u16) as f32,
918                                1.0,
919                            );
920
921                            let cell_tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
922                                cell_texture_area,
923                                current_texture_size,
924                            );
925
926                            let quad_instance = SpriteInstanceUniform::new(
927                                cell_model_matrix,
928                                cell_tex_coords_mul_add,
929                                0,
930                                Vec4([1.0, 1.0, 1.0, 1.0]),
931                                true,
932                            );
933                            quad_matrix_and_uv.push(quad_instance);
934                        }
935                    }
936                }
937            }
938
939            let quad_count = quad_matrix_and_uv.len() as u32 - quad_len_before;
940            batch_vertex_ranges.push((weak_material_ref, quad_len_before, quad_count));
941        }
942
943        // write all model_matrix and uv_coords to instance buffer once, before the render pass
944        self.queue.write_buffer(
945            &self.quad_matrix_and_uv_instance_buffer,
946            0,
947            bytemuck::cast_slice(&quad_matrix_and_uv),
948        );
949
950        self.batch_offsets = batch_vertex_ranges;
951    }
952
953    #[allow(clippy::too_many_lines)]
954    #[inline]
955    pub fn prepare_nine_slice(
956        nine_slice: &NineSlice,
957        position_offset: Vec3,
958        quad_matrix_and_uv: &mut Vec<SpriteInstanceUniform>,
959        current_texture_size: UVec2,
960    ) {
961        let color = nine_slice.color;
962        let world_window_size = nine_slice.size;
963        let slices = &nine_slice.slices;
964        let atlas_origin = nine_slice.origin_in_atlas;
965        let texture_window_size = nine_slice.size_inside_atlas.unwrap_or(current_texture_size);
966
967        let world_edge_width = nine_slice.size.x - slices.left - slices.right;
968        let world_edge_height = nine_slice.size.y - slices.top - slices.bottom;
969        let texture_edge_width = texture_window_size.x - slices.left - slices.right;
970        let texture_edge_height = texture_window_size.y - slices.top - slices.bottom;
971
972        // Lower left Corner
973        // Y goes up, X goes to the right, right-handed coordinate system
974        let lower_left_pos = Vec3::new(position_offset.x, position_offset.y, 0);
975        let corner_size = UVec2::new(slices.left, slices.bottom);
976        // it should be pixel perfect so it is the same size as the texture cut out
977        let lower_left_quad_size = UVec2::new(corner_size.x, corner_size.y);
978        let lower_left_atlas = URect::new(
979            atlas_origin.x,
980            atlas_origin.y + texture_window_size.y - slices.bottom, // Bottom of texture minus bottom slice height
981            corner_size.x,
982            corner_size.y,
983        );
984        let lower_left_quad = Self::quad_helper_uniform(
985            lower_left_pos,
986            lower_left_quad_size,
987            lower_left_atlas,
988            color,
989            current_texture_size,
990        );
991        quad_matrix_and_uv.push(lower_left_quad);
992
993        // Lower edge
994        let lower_side_position =
995            Vec3::new(position_offset.x + slices.left as i16, position_offset.y, 0);
996        // World quad size is potentially wider than the texture,
997        // that is fine, since the texture will be repeated.
998        let lower_side_world_quad_size = UVec2::new(world_edge_width, slices.bottom);
999        let lower_side_texture_size = UVec2::new(texture_edge_width, slices.bottom);
1000        // Lower edge
1001        let lower_side_atlas = URect::new(
1002            atlas_origin.x + slices.left,
1003            atlas_origin.y + texture_window_size.y - slices.bottom, // Bottom of texture minus bottom slice height
1004            lower_side_texture_size.x,
1005            lower_side_texture_size.y,
1006        );
1007        let lower_side_quad = Self::quad_helper_uniform(
1008            lower_side_position,
1009            lower_side_world_quad_size,
1010            lower_side_atlas,
1011            color,
1012            current_texture_size,
1013        );
1014        quad_matrix_and_uv.push(lower_side_quad);
1015
1016        // Lower right corner
1017        let lower_right_pos = Vec3::new(
1018            position_offset.x + (world_window_size.x - slices.right) as i16,
1019            position_offset.y,
1020            0,
1021        );
1022        let lower_right_corner_size = UVec2::new(slices.right, slices.bottom);
1023        let lower_right_atlas = URect::new(
1024            atlas_origin.x + texture_window_size.x - slices.right,
1025            atlas_origin.y + texture_window_size.y - slices.bottom, // Bottom of texture minus bottom slice height
1026            lower_right_corner_size.x,
1027            lower_right_corner_size.y,
1028        );
1029        let lower_right_quad = Self::quad_helper_uniform(
1030            lower_right_pos,
1031            lower_right_corner_size,
1032            lower_right_atlas,
1033            color,
1034            current_texture_size,
1035        );
1036        quad_matrix_and_uv.push(lower_right_quad);
1037
1038        // Left edge
1039        let left_edge_pos = Vec3::new(
1040            position_offset.x,
1041            position_offset.y + slices.bottom as i16,
1042            0,
1043        );
1044        let left_edge_world_quad_size = UVec2::new(slices.left, world_edge_height);
1045        let left_edge_texture_size = UVec2::new(slices.left, texture_edge_height);
1046        let left_edge_atlas = URect::new(
1047            atlas_origin.x,
1048            atlas_origin.y + slices.top, // Skip top slice
1049            left_edge_texture_size.x,
1050            left_edge_texture_size.y,
1051        );
1052        let left_edge_quad = Self::quad_helper_uniform(
1053            left_edge_pos,
1054            left_edge_world_quad_size,
1055            left_edge_atlas,
1056            color,
1057            current_texture_size,
1058        );
1059        quad_matrix_and_uv.push(left_edge_quad);
1060
1061        // CENTER IS COMPLICATED
1062        // This was pretty tricky to get going, but @catnipped wanted it :)
1063
1064        // For the center region, we have to create multiple quads, since we want
1065        // it pixel perfect and not stretch it
1066        let base_center_x = atlas_origin.x + slices.left;
1067        let base_center_y = atlas_origin.y + slices.top;
1068
1069        // Calculate how many repetitions (quads) we need in each direction
1070        let repeat_x_count = (world_edge_width as f32 / texture_edge_width as f32).ceil() as usize;
1071        let repeat_y_count =
1072            (world_edge_height as f32 / texture_edge_height as f32).ceil() as usize;
1073
1074        for y in 0..repeat_y_count {
1075            for x in 0..repeat_x_count {
1076                let this_quad_width =
1077                    if x == repeat_x_count - 1 && world_edge_width % texture_edge_width != 0 {
1078                        world_edge_width % texture_edge_width
1079                    } else {
1080                        texture_edge_width
1081                    };
1082
1083                let this_quad_height =
1084                    if y == repeat_y_count - 1 && world_edge_height % texture_edge_height != 0 {
1085                        world_edge_height % texture_edge_height
1086                    } else {
1087                        texture_edge_height
1088                    };
1089
1090                let quad_pos = Vec3::new(
1091                    position_offset.x + slices.left as i16 + (x as u16 * texture_edge_width) as i16,
1092                    position_offset.y
1093                        + slices.bottom as i16
1094                        + (y as u16 * texture_edge_height) as i16,
1095                    0,
1096                );
1097
1098                let texture_x = base_center_x;
1099
1100                let texture_y = if y == repeat_y_count - 1 && this_quad_height < texture_edge_height
1101                {
1102                    base_center_y + (texture_edge_height - this_quad_height)
1103                } else {
1104                    base_center_y
1105                };
1106
1107                let this_texture_region =
1108                    URect::new(texture_x, texture_y, this_quad_width, this_quad_height);
1109
1110                let center_quad = Self::quad_helper_uniform(
1111                    quad_pos,
1112                    UVec2::new(this_quad_width, this_quad_height),
1113                    this_texture_region,
1114                    color,
1115                    current_texture_size,
1116                );
1117
1118                quad_matrix_and_uv.push(center_quad);
1119            }
1120        }
1121        // CENTER IS DONE ---------
1122
1123        // Right edge
1124        let right_edge_pos = Vec3::new(
1125            position_offset.x + (world_window_size.x - slices.right) as i16,
1126            position_offset.y + slices.bottom as i16,
1127            0,
1128        );
1129        let right_edge_world_quad_size = UVec2::new(slices.right, world_edge_height);
1130        let right_edge_texture_size = UVec2::new(slices.right, texture_edge_height);
1131        let right_edge_atlas = URect::new(
1132            atlas_origin.x + texture_window_size.x - slices.right,
1133            atlas_origin.y + slices.top, // Skip top slice
1134            right_edge_texture_size.x,
1135            right_edge_texture_size.y,
1136        );
1137
1138        let right_edge_quad = Self::quad_helper_uniform(
1139            right_edge_pos,
1140            right_edge_world_quad_size,
1141            right_edge_atlas,
1142            color,
1143            current_texture_size,
1144        );
1145        quad_matrix_and_uv.push(right_edge_quad);
1146
1147        // Top left corner
1148        let top_left_pos = Vec3::new(
1149            position_offset.x,
1150            position_offset.y + (world_window_size.y - slices.top) as i16,
1151            0,
1152        );
1153        let top_left_corner_size = UVec2::new(slices.left, slices.top);
1154        let top_left_atlas = URect::new(
1155            atlas_origin.x,
1156            atlas_origin.y, // Top of texture
1157            top_left_corner_size.x,
1158            top_left_corner_size.y,
1159        );
1160        let top_left_quad = Self::quad_helper_uniform(
1161            top_left_pos,
1162            top_left_corner_size,
1163            top_left_atlas,
1164            color,
1165            current_texture_size,
1166        );
1167        quad_matrix_and_uv.push(top_left_quad);
1168
1169        // Top edge
1170        let top_edge_pos = Vec3::new(
1171            position_offset.x + slices.left as i16,
1172            position_offset.y + (world_window_size.y - slices.top) as i16,
1173            0,
1174        );
1175        let top_edge_world_quad_size = UVec2::new(world_edge_width, slices.top);
1176        let top_edge_texture_size = UVec2::new(texture_edge_width, slices.top);
1177        let top_edge_atlas = URect::new(
1178            atlas_origin.x + slices.left,
1179            atlas_origin.y, // Top of texture
1180            top_edge_texture_size.x,
1181            top_edge_texture_size.y,
1182        );
1183        let top_edge_quad = Self::quad_helper_uniform(
1184            top_edge_pos,
1185            top_edge_world_quad_size,
1186            top_edge_atlas,
1187            color,
1188            current_texture_size,
1189        );
1190        quad_matrix_and_uv.push(top_edge_quad);
1191
1192        // Top right corner
1193        let top_right_pos = Vec3::new(
1194            position_offset.x + (world_window_size.x - slices.right) as i16,
1195            position_offset.y + (world_window_size.y - slices.top) as i16,
1196            0,
1197        );
1198        let top_right_corner_size = UVec2::new(slices.right, slices.top);
1199        let top_right_atlas = URect::new(
1200            atlas_origin.x + texture_window_size.x - slices.right,
1201            atlas_origin.y, // Top of texture
1202            top_right_corner_size.x,
1203            top_right_corner_size.y,
1204        );
1205        let top_right_quad = Self::quad_helper_uniform(
1206            top_right_pos,
1207            top_right_corner_size,
1208            top_right_atlas,
1209            color,
1210            current_texture_size,
1211        );
1212        quad_matrix_and_uv.push(top_right_quad);
1213    }
1214
1215    /// # Panics
1216    ///
1217    pub fn render(
1218        &mut self,
1219        render_pass: &mut RenderPass,
1220        materials: &Assets<Material>,
1221        fonts: &Assets<Font>,
1222        now: Millis,
1223    ) {
1224        trace!("start render()");
1225        self.last_render_at = now;
1226
1227        self.viewport = match self.viewport_strategy {
1228            ViewportStrategy::FitIntegerScaling(virtual_surface_size) => {
1229                Self::viewport_from_integer_scale(self.physical_surface_size, virtual_surface_size)
1230            }
1231            ViewportStrategy::FitFloatScaling(virtual_surface_size) => {
1232                Self::viewport_from_float_scale(self.physical_surface_size, virtual_surface_size)
1233            }
1234            ViewportStrategy::MatchPhysicalSize => URect::new(
1235                0,
1236                0,
1237                self.physical_surface_size.x,
1238                self.physical_surface_size.y,
1239            ),
1240        };
1241
1242        let view_proj_matrix = match self.viewport_strategy {
1243            ViewportStrategy::MatchPhysicalSize => {
1244                create_view_uniform_view_projection_matrix(self.physical_surface_size)
1245            }
1246            ViewportStrategy::FitFloatScaling(virtual_surface_size)
1247            | ViewportStrategy::FitIntegerScaling(virtual_surface_size) => {
1248                create_view_projection_matrix_from_virtual(
1249                    virtual_surface_size.x,
1250                    virtual_surface_size.y,
1251                )
1252            }
1253        };
1254
1255        let scale_matrix = Matrix4::from_scale(self.scale, self.scale, 0.0);
1256        let origin_translation_matrix =
1257            Matrix4::from_translation(-self.origin.x as f32, -self.origin.y as f32, 0.0);
1258
1259        let total_matrix = scale_matrix * view_proj_matrix * origin_translation_matrix;
1260
1261        // write all model_matrix and uv_coords to instance buffer once, before the render pass
1262        self.queue.write_buffer(
1263            &self.camera_buffer,
1264            0,
1265            bytemuck::cast_slice(&[total_matrix]),
1266        );
1267
1268        self.prepare_render(materials, fonts);
1269
1270        render_pass.set_viewport(
1271            self.viewport.position.x as f32,
1272            self.viewport.position.y as f32,
1273            self.viewport.size.x as f32,
1274            self.viewport.size.y as f32,
1275            0.0,
1276            1.0,
1277        );
1278
1279        render_pass.set_pipeline(&self.pipeline);
1280
1281        // Index and vertex buffers never change
1282        render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
1283        render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1284
1285        // Vertex buffer is reused
1286        render_pass.set_vertex_buffer(1, self.quad_matrix_and_uv_instance_buffer.slice(..));
1287
1288        // Camera is the same for everything
1289        render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
1290
1291        let num_indices = mireforge_wgpu_sprites::INDICES.len() as u32;
1292
1293        for &(weak_material_ref, start, count) in &self.batch_offsets {
1294            let wgpu_material = materials
1295                .get_weak(weak_material_ref)
1296                .expect("no such material");
1297
1298            // Bind the texture and sampler bind group (Bind Group 1)
1299            render_pass.set_bind_group(1, &wgpu_material.texture_and_sampler_bind_group, &[]);
1300
1301            // Issue the instanced draw call for the batch
1302            trace!(material=%weak_material_ref, start=%start, count=%count, "draw instanced");
1303            render_pass.draw_indexed(0..num_indices, 0, start..(start + count));
1304        }
1305
1306        self.items.clear();
1307    }
1308
1309    pub fn material_from_texture(&self, texture: wgpu::Texture, label: &str) -> Material {
1310        trace!("load texture from memory with name: '{label}'");
1311        let size = &texture.size();
1312        let texture_and_sampler_bind_group =
1313            mireforge_wgpu_sprites::create_sprite_texture_and_sampler_bind_group(
1314                &self.device,
1315                &self.texture_sampler_bind_group_layout,
1316                &texture,
1317                &self.sampler,
1318                label,
1319            );
1320
1321        let texture_size = UVec2::new(size.width as u16, size.height as u16);
1322
1323        Material {
1324            texture_and_sampler_bind_group,
1325            //render_pipeline: Arc::clone(&self.pipeline),
1326            texture_size,
1327        }
1328    }
1329}
1330
1331//fn set_view_projection(&mut self) {}
1332
1333fn create_view_projection_matrix_from_virtual(virtual_width: u16, virtual_height: u16) -> Matrix4 {
1334    OrthoInfo {
1335        left: 0.0,
1336        right: virtual_width as f32,
1337        bottom: 0.0,
1338        top: virtual_height as f32,
1339        near: 1.0,
1340        far: -1.0,
1341    }
1342    .into()
1343}
1344
1345fn create_view_uniform_view_projection_matrix(viewport_size: UVec2) -> Matrix4 {
1346    let viewport_width = viewport_size.x as f32;
1347    let viewport_height = viewport_size.y as f32;
1348
1349    let viewport_aspect_ratio = viewport_width / viewport_height;
1350
1351    let scale_x = 1.0;
1352    let scale_y = viewport_aspect_ratio; // scaling Y probably gives the best precision?
1353
1354    let view_projection_matrix = [
1355        [scale_x, 0.0, 0.0, 0.0],
1356        [0.0, scale_y, 0.0, 0.0],
1357        [0.0, 0.0, -1.0, 0.0],
1358        [0.0, 0.0, 0.0, 1.0],
1359    ];
1360
1361    view_projection_matrix.into()
1362}
1363
1364fn sort_render_items_by_z_and_material(items: &mut [RenderItem]) {
1365    items.sort_by_key(|item| (item.position.z, item.material_ref));
1366}
1367
1368#[derive(Debug, Clone, Copy, Default)]
1369pub enum Rotation {
1370    #[default]
1371    Degrees0,
1372    Degrees90,
1373    Degrees180,
1374    Degrees270,
1375}
1376
1377#[derive(Debug, Copy, Clone)]
1378pub struct SpriteParams {
1379    pub texture_size: UVec2,
1380    pub texture_pos: UVec2,
1381    pub scale: u8,
1382    pub rotation: Rotation,
1383    pub flip_x: bool,
1384    pub flip_y: bool,
1385    pub pivot: Vec2,
1386    pub color: Color,
1387}
1388
1389impl Default for SpriteParams {
1390    fn default() -> Self {
1391        Self {
1392            texture_size: UVec2::new(0, 0),
1393            texture_pos: UVec2::new(0, 0),
1394            pivot: Vec2::new(0, 0),
1395            flip_x: false,
1396            flip_y: false,
1397            color: Color::from_octet(255, 255, 255, 255),
1398            scale: 1,
1399            rotation: Rotation::Degrees0,
1400        }
1401    }
1402}
1403
1404#[derive(Debug, PartialEq, Eq, Asset)]
1405pub struct Material {
1406    pub texture_and_sampler_bind_group: BindGroup,
1407    pub texture_size: UVec2,
1408}
1409
1410impl PartialOrd<Self> for Material {
1411    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1412        Some(
1413            self.texture_and_sampler_bind_group
1414                .cmp(&other.texture_and_sampler_bind_group),
1415        )
1416    }
1417}
1418
1419impl Ord for Material {
1420    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1421        self.texture_and_sampler_bind_group
1422            .cmp(&other.texture_and_sampler_bind_group)
1423    }
1424}
1425
1426#[derive(Debug)]
1427pub struct Sprite {
1428    pub params: SpriteParams,
1429}
1430
1431#[derive(Debug)]
1432pub struct QuadColor {
1433    pub size: UVec2,
1434    pub color: Color,
1435}
1436
1437#[derive(Debug, Copy, Clone)]
1438pub struct Slices {
1439    pub left: u16,
1440    pub top: u16,
1441    pub right: u16,  // how many pixels from the right side of the texture and going in
1442    pub bottom: u16, // how much to take from bottom of slice
1443}
1444
1445#[derive(Debug)]
1446pub struct NineSlice {
1447    pub size: UVec2, // size of whole "window"
1448    pub slices: Slices,
1449    pub color: Color, // color tint
1450    pub origin_in_atlas: UVec2,
1451    pub size_inside_atlas: Option<UVec2>,
1452}
1453
1454#[derive(Debug)]
1455pub struct TileMap {
1456    pub tiles_data_grid_size: UVec2,
1457    pub cell_count_size: UVec2,
1458    pub one_cell_size: UVec2,
1459    pub tiles: Vec<u16>,
1460    pub scale: u8,
1461}
1462
1463pub type RenderPipelineRef = Arc<RenderPipeline>;
1464
1465const fn sources() -> (&'static str, &'static str) {
1466    let vertex_shader_source = "
1467// Bind Group 0: Uniforms (view-projection matrix)
1468struct Uniforms {
1469    view_proj: mat4x4<f32>,
1470};
1471
1472@group(0) @binding(0)
1473var<uniform> camera_uniforms: Uniforms;
1474
1475// Bind Group 1: Texture and Sampler (Unused in Vertex Shader but needed for consistency)
1476@group(1) @binding(0)
1477var diffuse_texture: texture_2d<f32>;
1478
1479@group(1) @binding(1)
1480var sampler_diffuse: sampler;
1481
1482// Vertex input structure
1483struct VertexInput {
1484    @location(0) position: vec3<f32>,
1485    @location(1) tex_coords: vec2<f32>,
1486    @builtin(instance_index) instance_idx: u32,
1487};
1488
1489// Vertex output structure to fragment shader
1490struct VertexOutput {
1491    @builtin(position) position: vec4<f32>,
1492    @location(0) tex_coords: vec2<f32>,
1493    @location(1) color: vec4<f32>,
1494    @location(2) use_texture: u32,
1495};
1496
1497// Vertex shader entry point
1498@vertex
1499fn vs_main(
1500    input: VertexInput,
1501    // Instance attributes
1502    @location(2) model_matrix0: vec4<f32>,
1503    @location(3) model_matrix1: vec4<f32>,
1504    @location(4) model_matrix2: vec4<f32>,
1505    @location(5) model_matrix3: vec4<f32>,
1506    @location(6) tex_multiplier: vec4<f32>,
1507    @location(7) rotation_step: u32,
1508    @location(8) color: vec4<f32>,
1509    @location(9) use_texture: u32,
1510) -> VertexOutput {
1511    var output: VertexOutput;
1512
1513    // Reconstruct the model matrix from the instance data
1514    let model_matrix = mat4x4<f32>(
1515        model_matrix0,
1516        model_matrix1,
1517        model_matrix2,
1518        model_matrix3,
1519    );
1520
1521    // Compute world position
1522    let world_position = model_matrix * vec4<f32>(input.position, 1.0);
1523
1524    // Apply view-projection matrix
1525    output.position = camera_uniforms.view_proj * world_position;
1526
1527    // Decode rotation_step
1528    let rotation_val = rotation_step & 3u; // Bits 0-1
1529    let flip_x = (rotation_step & 4u) != 0u; // Bit 2
1530    let flip_y = (rotation_step & 8u) != 0u; // Bit 3
1531
1532    // Rotate texture coordinates based on rotation_val
1533    var rotated_tex_coords = input.tex_coords;
1534    if (rotation_val == 1) {
1535        // 90 degrees rotation
1536        rotated_tex_coords = vec2<f32>(1.0 - input.tex_coords.y, input.tex_coords.x);
1537    } else if (rotation_val == 2) {
1538        // 180 degrees rotation
1539        rotated_tex_coords = vec2<f32>(1.0 - input.tex_coords.x, 1.0 - input.tex_coords.y);
1540    } else if (rotation_val == 3) {
1541        // 270 degrees rotation
1542        rotated_tex_coords = vec2<f32>(input.tex_coords.y, 1.0 - input.tex_coords.x);
1543    }
1544    // else rotation_val == Degrees0, no rotation
1545
1546    // Apply flipping
1547    if (flip_x) {
1548        rotated_tex_coords.x = 1.0 - rotated_tex_coords.x;
1549    }
1550    if (flip_y) {
1551        rotated_tex_coords.y = 1.0 - rotated_tex_coords.y;
1552    }
1553
1554    // Modify texture coordinates
1555    output.tex_coords = rotated_tex_coords * tex_multiplier.xy + tex_multiplier.zw;
1556    output.color = color;
1557    output.use_texture = use_texture;
1558
1559    return output;
1560}
1561        ";
1562    //
1563
1564    let fragment_shader_source = "
1565
1566// Bind Group 1: Texture and Sampler
1567@group(1) @binding(0)
1568var diffuse_texture: texture_2d<f32>;
1569
1570@group(1) @binding(1)
1571var sampler_diffuse: sampler;
1572
1573// Fragment input structure from vertex shader
1574struct VertexOutput {
1575    @builtin(position) position: vec4<f32>,
1576    @location(0) tex_coords: vec2<f32>,
1577    @location(1) color: vec4<f32>,
1578    @location(2) use_texture: u32,
1579};
1580
1581// Fragment shader entry point
1582@fragment
1583fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
1584    var final_color: vec4<f32>;
1585
1586    // Sample the texture using the texture coordinates
1587    let texture_color = textureSample(diffuse_texture, sampler_diffuse, input.tex_coords);
1588    if (input.use_texture == 1u) { // Check if use_texture is true (1)
1589        // Apply color modulation and opacity
1590        final_color = texture_color * input.color;
1591    } else {
1592        final_color = input.color;
1593    }
1594
1595    return final_color;
1596}
1597
1598";
1599    (vertex_shader_source, fragment_shader_source)
1600}