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