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    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: virtual_surface_size.x as u32,
261                height: virtual_surface_size.y as u32,
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    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    pub const fn physical_surface_size(&self) -> UVec2 {
308        self.physical_surface_size
309    }
310
311    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.clone(),
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 = physical_size.x as f32 / physical_size.y as f32;
415        let virtual_aspect = virtual_size.x as f32 / virtual_size.y as f32;
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            physical_size.y as f32 / virtual_size.y as f32
423        } else {
424            physical_size.x as f32 / virtual_size.x as f32
425        };
426
427        if float_scale < 0.01 {
428            float_scale = 0.01;
429        }
430
431        let viewport_actual_size = UVec2::new(
432            (virtual_size.x as f32 * float_scale) as u16,
433            (virtual_size.y as f32 * 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 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 { size, color }),
545        });
546    }
547
548    #[allow(clippy::too_many_arguments)]
549    pub fn draw_nine_slice(
550        &mut self,
551        position: Vec3,
552        size: UVec2,
553        slices: Slices,
554        material_ref: &MaterialRef,
555        color: Color,
556    ) {
557        self.items.push(RenderItem {
558            position,
559            material_ref: material_ref.clone(),
560            renderable: Renderable::NineSlice(NineSlice {
561                size,
562                slices,
563                color,
564                origin_in_atlas: UVec2::new(0, 0),
565                size_inside_atlas: None,
566            }),
567        });
568    }
569
570    pub const fn clear_color(&self) -> wgpu::Color {
571        self.clear_color
572    }
573
574    // first two is multiplier and second pair is offset
575    fn calculate_texture_coords_mul_add(atlas_rect: URect, texture_size: UVec2) -> Vec4 {
576        let x = atlas_rect.position.x as f32 / texture_size.x as f32;
577        let y = atlas_rect.position.y as f32 / texture_size.y as f32;
578        let width = atlas_rect.size.x as f32 / texture_size.x as f32;
579        let height = atlas_rect.size.y as f32 / texture_size.y as f32;
580        Vec4([width, height, x, y])
581    }
582
583    fn order_render_items_in_batches(&mut self) -> Vec<Vec<&RenderItem>> {
584        let mut material_batches: Vec<Vec<&RenderItem>> = Vec::new();
585        let mut current_batch: Vec<&RenderItem> = Vec::new();
586        let mut current_material: Option<MaterialRef> = None;
587
588        for render_item in &self.items {
589            if Some(&render_item.material_ref) != current_material.as_ref() {
590                if !current_batch.is_empty() {
591                    material_batches.push(current_batch.clone());
592                    current_batch.clear();
593                }
594                current_material = Some(render_item.material_ref.clone());
595            }
596            current_batch.push(render_item);
597        }
598
599        if !current_batch.is_empty() {
600            material_batches.push(current_batch);
601        }
602
603        material_batches
604    }
605
606    #[must_use]
607    pub fn quad_helper_uniform(
608        position: Vec3,
609        quad_size: UVec2,
610        render_atlas: URect,
611        color: Color,
612
613        current_texture_size: UVec2,
614    ) -> SpriteInstanceUniform {
615        let model_matrix = Matrix4::from_translation(position.x as f32, position.y as f32, 0.0)
616            * Matrix4::from_scale(quad_size.x as f32, quad_size.y as f32, 1.0);
617
618        let tex_coords_mul_add =
619            Self::calculate_texture_coords_mul_add(render_atlas, current_texture_size);
620
621        let rotation_value = 0;
622
623        SpriteInstanceUniform::new(
624            model_matrix,
625            tex_coords_mul_add,
626            rotation_value,
627            Vec4(color.to_f32_slice()),
628        )
629    }
630
631    /// # Panics
632    ///
633    #[allow(clippy::too_many_lines)]
634    pub fn write_vertex_indices_and_uv_to_buffer(
635        &mut self,
636        textures: &Assets<Texture>,
637        fonts: &Assets<Font>,
638    ) {
639        const FLIP_X_MASK: u32 = 0b0000_0100;
640        const FLIP_Y_MASK: u32 = 0b0000_1000;
641
642        let batches = self.sort_and_put_in_batches();
643
644        let mut quad_matrix_and_uv: Vec<SpriteInstanceUniform> = Vec::new();
645        let mut batch_vertex_ranges: Vec<(MaterialRef, u32, u32)> = Vec::new();
646
647        for render_items in batches {
648            let quad_len_before = quad_matrix_and_uv.len();
649
650            // Fix: Access material_ref through reference and copy it
651            let weak_material_ref = render_items
652                .first()
653                .map(|item| {
654                    // Force copy semantics by dereferencing the shared reference
655                    let material_ref: MaterialRef = item.material_ref.clone();
656                    material_ref
657                })
658                .expect("Render items batch was empty");
659
660            if !weak_material_ref.is_complete(textures) {
661                // Material is not loaded yet
662                trace!(?weak_material_ref, "material is not complete yet");
663                continue;
664            }
665            let material = weak_material_ref.clone();
666
667            let maybe_texture_ref = material.primary_texture();
668            let maybe_texture = maybe_texture_ref
669                .and_then(|found_primary_texture_ref| textures.get(&found_primary_texture_ref));
670
671            for render_item in render_items {
672                let quad_len_before_inner = quad_matrix_and_uv.len();
673
674                match &render_item.renderable {
675                    Renderable::Sprite(sprite) => {
676                        let current_texture_size = maybe_texture.unwrap().texture_size;
677
678                        let params = &sprite.params;
679                        let mut size = params.texture_size;
680                        if size.x == 0 && size.y == 0 {
681                            size = current_texture_size;
682                        }
683
684                        let render_atlas = URect {
685                            position: params.texture_pos,
686                            size,
687                        };
688
689                        match params.rotation {
690                            Rotation::Degrees90 | Rotation::Degrees270 => {
691                                swap(&mut size.x, &mut size.y);
692                            }
693                            _ => {}
694                        }
695
696                        let model_matrix = Matrix4::from_translation(
697                            render_item.position.x as f32,
698                            render_item.position.y as f32,
699                            0.0,
700                        ) * Matrix4::from_scale(
701                            (size.x * params.scale as u16) as f32,
702                            (size.y * params.scale as u16) as f32,
703                            1.0,
704                        );
705
706                        let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
707                            render_atlas,
708                            current_texture_size,
709                        );
710
711                        let mut rotation_value = match params.rotation {
712                            Rotation::Degrees0 => 0,
713                            Rotation::Degrees90 => 1,
714                            Rotation::Degrees180 => 2,
715                            Rotation::Degrees270 => 3,
716                        };
717
718                        if params.flip_x {
719                            rotation_value |= FLIP_X_MASK;
720                        }
721                        if params.flip_y {
722                            rotation_value |= FLIP_Y_MASK;
723                        }
724
725                        let quad_instance = SpriteInstanceUniform::new(
726                            model_matrix,
727                            tex_coords_mul_add,
728                            rotation_value,
729                            Vec4(params.color.to_f32_slice()),
730                        );
731                        quad_matrix_and_uv.push(quad_instance);
732                    }
733
734                    Renderable::Mask(texture_offset, color) => {
735                        let current_texture_size = maybe_texture.unwrap().texture_size;
736                        let params = SpriteParams {
737                            texture_size: current_texture_size,
738                            texture_pos: *texture_offset,
739                            scale: 1,
740                            rotation: Rotation::default(),
741                            flip_x: false,
742                            flip_y: false,
743                            pivot: Vec2 { x: 0, y: 0 },
744                            color: *color,
745                        };
746
747                        let mut size = params.texture_size;
748                        if size.x == 0 && size.y == 0 {
749                            size = current_texture_size;
750                        }
751
752                        let render_atlas = URect {
753                            position: params.texture_pos,
754                            size,
755                        };
756
757                        let model_matrix = Matrix4::from_translation(
758                            render_item.position.x as f32,
759                            render_item.position.y as f32,
760                            0.0,
761                        ) * Matrix4::from_scale(
762                            (size.x * params.scale as u16) as f32,
763                            (size.y * params.scale as u16) as f32,
764                            1.0,
765                        );
766
767                        let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
768                            render_atlas,
769                            current_texture_size,
770                        );
771
772                        let mut rotation_value = match params.rotation {
773                            Rotation::Degrees0 => 0,
774                            Rotation::Degrees90 => 1,
775                            Rotation::Degrees180 => 2,
776                            Rotation::Degrees270 => 3,
777                        };
778
779                        if params.flip_x {
780                            rotation_value |= FLIP_X_MASK;
781                        }
782                        if params.flip_y {
783                            rotation_value |= FLIP_Y_MASK;
784                        }
785
786                        let quad_instance = SpriteInstanceUniform::new(
787                            model_matrix,
788                            tex_coords_mul_add,
789                            rotation_value,
790                            Vec4(params.color.to_f32_slice()),
791                        );
792                        quad_matrix_and_uv.push(quad_instance);
793                    }
794
795                    Renderable::NineSlice(nine_slice) => {
796                        let current_texture_size = maybe_texture.unwrap().texture_size;
797                        Self::prepare_nine_slice(
798                            nine_slice,
799                            render_item.position,
800                            &mut quad_matrix_and_uv,
801                            current_texture_size,
802                        );
803                    }
804
805                    Renderable::QuadColor(quad) => {
806                        let model_matrix =
807                            Matrix4::from_translation(
808                                render_item.position.x as f32,
809                                render_item.position.y as f32,
810                                0.0,
811                            ) * Matrix4::from_scale(quad.size.x as f32, quad.size.y as f32, 1.0);
812
813                        let tex_coords_mul_add = Vec4([
814                            0.0, //x
815                            0.0, //y
816                            0.0, 0.0,
817                        ]);
818                        let rotation_value = 0;
819
820                        let quad_instance = SpriteInstanceUniform::new(
821                            model_matrix,
822                            tex_coords_mul_add,
823                            rotation_value,
824                            Vec4(quad.color.to_f32_slice()),
825                        );
826                        quad_matrix_and_uv.push(quad_instance);
827                    }
828
829                    Renderable::Text(text) => {
830                        let current_texture_size = maybe_texture.unwrap().texture_size;
831                        let result = fonts.get_weak(text.font_ref);
832                        if result.is_none() {
833                            continue;
834                        }
835                        let font = result.unwrap();
836
837                        let glyph_draw = font.draw(&text.text);
838                        for glyph in glyph_draw.glyphs {
839                            let pos = render_item.position + Vec3::from(glyph.relative_position);
840                            let texture_size = glyph.texture_rectangle.size;
841                            let model_matrix =
842                                Matrix4::from_translation(pos.x as f32, pos.y as f32, 0.0)
843                                    * Matrix4::from_scale(
844                                        texture_size.x as f32,
845                                        texture_size.y as f32,
846                                        1.0,
847                                    );
848                            let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
849                                glyph.texture_rectangle,
850                                current_texture_size,
851                            );
852
853                            let quad_instance = SpriteInstanceUniform::new(
854                                model_matrix,
855                                tex_coords_mul_add,
856                                0,
857                                Vec4(text.color.to_f32_slice()),
858                            );
859                            quad_matrix_and_uv.push(quad_instance);
860                        }
861                    }
862
863                    Renderable::TileMap(tile_map) => {
864                        for (index, tile) in tile_map.tiles.iter().enumerate() {
865                            let cell_pos_x = (index as u16 % tile_map.tiles_data_grid_size.x)
866                                * tile_map.one_cell_size.x
867                                * tile_map.scale as u16;
868                            let cell_pos_y = (index as u16 / tile_map.tiles_data_grid_size.x)
869                                * tile_map.one_cell_size.y
870                                * tile_map.scale as u16;
871                            let cell_x = *tile % tile_map.cell_count_size.x;
872                            let cell_y = *tile / tile_map.cell_count_size.x;
873
874                            let tex_x = cell_x * tile_map.one_cell_size.x;
875                            let tex_y = cell_y * tile_map.one_cell_size.x;
876
877                            let cell_texture_area = URect::new(
878                                tex_x,
879                                tex_y,
880                                tile_map.one_cell_size.x,
881                                tile_map.one_cell_size.y,
882                            );
883
884                            let cell_model_matrix = Matrix4::from_translation(
885                                (render_item.position.x + cell_pos_x as i16) as f32,
886                                (render_item.position.y + cell_pos_y as i16) as f32,
887                                0.0,
888                            ) * Matrix4::from_scale(
889                                (tile_map.one_cell_size.x * tile_map.scale as u16) as f32,
890                                (tile_map.one_cell_size.y * tile_map.scale as u16) as f32,
891                                1.0,
892                            );
893
894                            let current_texture_size = maybe_texture.unwrap().texture_size;
895                            let cell_tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
896                                cell_texture_area,
897                                current_texture_size,
898                            );
899
900                            let quad_instance = SpriteInstanceUniform::new(
901                                cell_model_matrix,
902                                cell_tex_coords_mul_add,
903                                0,
904                                Vec4([1.0, 1.0, 1.0, 1.0]),
905                            );
906                            quad_matrix_and_uv.push(quad_instance);
907                        }
908                    }
909                }
910
911                let quad_count_for_this_render_item =
912                    quad_matrix_and_uv.len() - quad_len_before_inner;
913                assert!(
914                    quad_count_for_this_render_item <= MAXIMUM_QUADS_FOR_RENDER_ITEM,
915                    "too many quads {quad_count_for_this_render_item} for render item {render_item:?}"
916                );
917            }
918
919            let quad_count_for_this_batch = quad_matrix_and_uv.len() - quad_len_before;
920            assert!(
921                quad_count_for_this_batch <= MAXIMUM_QUADS_IN_A_BATCH,
922                "too many quads {quad_count_for_this_batch} total to render in this batch"
923            );
924
925            assert!(
926                quad_matrix_and_uv.len() <= MAXIMUM_QUADS_IN_ONE_RENDER,
927                "too many quads for whole render {}",
928                quad_matrix_and_uv.len()
929            );
930
931            batch_vertex_ranges.push((
932                weak_material_ref,
933                quad_len_before as u32,
934                quad_count_for_this_batch as u32,
935            ));
936        }
937
938        // write all model_matrix and uv_coords to instance buffer once, before the render pass
939        self.queue.write_buffer(
940            &self.quad_matrix_and_uv_instance_buffer,
941            0,
942            bytemuck::cast_slice(&quad_matrix_and_uv),
943        );
944
945        self.batch_offsets = batch_vertex_ranges;
946    }
947
948    #[allow(clippy::too_many_lines)]
949    #[inline]
950    pub fn prepare_nine_slice(
951        nine_slice: &NineSlice,
952        position_offset: Vec3,
953        quad_matrix_and_uv: &mut Vec<SpriteInstanceUniform>,
954        current_texture_size: UVec2,
955    ) {
956        let world_window_size = nine_slice.size;
957        let slices = &nine_slice.slices;
958
959        // ------------------------------------------------------------
960        // Validate that our total window size is large enough to hold
961        // the left+right and top+bottom slices without underflowing.
962        // ------------------------------------------------------------
963        assert!(
964            world_window_size.x >= slices.left + slices.right,
965            "NineSlice.width ({}) < slices.left + slices.right ({})",
966            world_window_size.x,
967            slices.left + slices.right
968        );
969        assert!(
970            world_window_size.y >= slices.top + slices.bottom,
971            "NineSlice.height ({}) < slices.top + slices.bottom ({})",
972            world_window_size.y,
973            slices.top + slices.bottom
974        );
975
976        let texture_window_size = nine_slice.size_inside_atlas.unwrap_or(current_texture_size);
977
978        // check the texture region as well
979        assert!(
980            texture_window_size.x >= slices.left + slices.right,
981            "texture_window_size.width ({}) < slices.left + slices.right ({})",
982            texture_window_size.x,
983            slices.left + slices.right
984        );
985        assert!(
986            texture_window_size.y >= slices.top + slices.bottom,
987            "texture_window_size.height ({}) < slices.top + slices.bottom ({})",
988            texture_window_size.y,
989            slices.top + slices.bottom
990        );
991        // ------------------------------------------------------------
992
993        let color = nine_slice.color;
994
995        let atlas_origin = nine_slice.origin_in_atlas;
996        let texture_window_size = nine_slice.size_inside_atlas.unwrap_or(current_texture_size);
997
998        let world_edge_width = nine_slice.size.x - slices.left - slices.right;
999        let world_edge_height = nine_slice.size.y - slices.top - slices.bottom;
1000        let texture_edge_width = texture_window_size.x - slices.left - slices.right;
1001        let texture_edge_height = texture_window_size.y - slices.top - slices.bottom;
1002
1003        // Lower left Corner
1004        // Y goes up, X goes to the right, right-handed coordinate system
1005        let lower_left_pos = Vec3::new(position_offset.x, position_offset.y, 0);
1006        let corner_size = UVec2::new(slices.left, slices.bottom);
1007        // it should be pixel perfect so it is the same size as the texture cut out
1008        let lower_left_quad_size = UVec2::new(corner_size.x, corner_size.y);
1009        let lower_left_atlas = URect::new(
1010            atlas_origin.x,
1011            atlas_origin.y + texture_window_size.y - slices.bottom, // Bottom of texture minus bottom slice height
1012            corner_size.x,
1013            corner_size.y,
1014        );
1015        let lower_left_quad = Self::quad_helper_uniform(
1016            lower_left_pos,
1017            lower_left_quad_size,
1018            lower_left_atlas,
1019            color,
1020            current_texture_size,
1021        );
1022        quad_matrix_and_uv.push(lower_left_quad);
1023
1024        // Lower edge
1025        let lower_side_position =
1026            Vec3::new(position_offset.x + slices.left as i16, position_offset.y, 0);
1027        // World quad size is potentially wider than the texture,
1028        // that is fine, since the texture will be repeated.
1029        let lower_side_world_quad_size = UVec2::new(world_edge_width, slices.bottom);
1030        let lower_side_texture_size = UVec2::new(texture_edge_width, slices.bottom);
1031        // Lower edge
1032        let lower_side_atlas = URect::new(
1033            atlas_origin.x + slices.left,
1034            atlas_origin.y + texture_window_size.y - slices.bottom, // Bottom of texture minus bottom slice height
1035            lower_side_texture_size.x,
1036            lower_side_texture_size.y,
1037        );
1038        let lower_side_quad = Self::quad_helper_uniform(
1039            lower_side_position,
1040            lower_side_world_quad_size,
1041            lower_side_atlas,
1042            color,
1043            current_texture_size,
1044        );
1045        quad_matrix_and_uv.push(lower_side_quad);
1046
1047        // Lower right corner
1048        let lower_right_pos = Vec3::new(
1049            position_offset.x + (world_window_size.x - slices.right) as i16,
1050            position_offset.y,
1051            0,
1052        );
1053        let lower_right_corner_size = UVec2::new(slices.right, slices.bottom);
1054        let lower_right_atlas = URect::new(
1055            atlas_origin.x + texture_window_size.x - slices.right,
1056            atlas_origin.y + texture_window_size.y - slices.bottom, // Bottom of texture minus bottom slice height
1057            lower_right_corner_size.x,
1058            lower_right_corner_size.y,
1059        );
1060        let lower_right_quad = Self::quad_helper_uniform(
1061            lower_right_pos,
1062            lower_right_corner_size,
1063            lower_right_atlas,
1064            color,
1065            current_texture_size,
1066        );
1067        quad_matrix_and_uv.push(lower_right_quad);
1068
1069        // Left edge
1070        let left_edge_pos = Vec3::new(
1071            position_offset.x,
1072            position_offset.y + slices.bottom as i16,
1073            0,
1074        );
1075        let left_edge_world_quad_size = UVec2::new(slices.left, world_edge_height);
1076        let left_edge_texture_size = UVec2::new(slices.left, texture_edge_height);
1077        let left_edge_atlas = URect::new(
1078            atlas_origin.x,
1079            atlas_origin.y + slices.top, // Skip top slice
1080            left_edge_texture_size.x,
1081            left_edge_texture_size.y,
1082        );
1083        let left_edge_quad = Self::quad_helper_uniform(
1084            left_edge_pos,
1085            left_edge_world_quad_size,
1086            left_edge_atlas,
1087            color,
1088            current_texture_size,
1089        );
1090        quad_matrix_and_uv.push(left_edge_quad);
1091
1092        // CENTER IS COMPLICATED
1093        // This was pretty tricky to get going, but @catnipped wanted it :)
1094
1095        // For the center region, we have to create multiple quads, since we want
1096        // it pixel perfect and not stretch it
1097        let base_center_x = atlas_origin.x + slices.left;
1098        let base_center_y = atlas_origin.y + slices.top;
1099
1100        // Calculate how many repetitions (quads) we need in each direction
1101        let repeat_x_count = (world_edge_width as f32 / texture_edge_width as f32).ceil() as usize;
1102        let repeat_y_count =
1103            (world_edge_height as f32 / texture_edge_height as f32).ceil() as usize;
1104
1105        for y in 0..repeat_y_count {
1106            for x in 0..repeat_x_count {
1107                let this_quad_width =
1108                    if x == repeat_x_count - 1 && world_edge_width % texture_edge_width != 0 {
1109                        world_edge_width % texture_edge_width
1110                    } else {
1111                        texture_edge_width
1112                    };
1113
1114                let this_quad_height =
1115                    if y == repeat_y_count - 1 && world_edge_height % texture_edge_height != 0 {
1116                        world_edge_height % texture_edge_height
1117                    } else {
1118                        texture_edge_height
1119                    };
1120
1121                let quad_pos = Vec3::new(
1122                    position_offset.x + slices.left as i16 + (x as u16 * texture_edge_width) as i16,
1123                    position_offset.y
1124                        + slices.bottom as i16
1125                        + (y as u16 * texture_edge_height) as i16,
1126                    0,
1127                );
1128
1129                let texture_x = base_center_x;
1130
1131                let texture_y = if y == repeat_y_count - 1 && this_quad_height < texture_edge_height
1132                {
1133                    base_center_y + (texture_edge_height - this_quad_height)
1134                } else {
1135                    base_center_y
1136                };
1137
1138                let this_texture_region =
1139                    URect::new(texture_x, texture_y, this_quad_width, this_quad_height);
1140
1141                let center_quad = Self::quad_helper_uniform(
1142                    quad_pos,
1143                    UVec2::new(this_quad_width, this_quad_height),
1144                    this_texture_region,
1145                    color,
1146                    current_texture_size,
1147                );
1148
1149                quad_matrix_and_uv.push(center_quad);
1150            }
1151        }
1152        // CENTER IS DONE ---------
1153
1154        // Right edge
1155        let right_edge_pos = Vec3::new(
1156            position_offset.x + (world_window_size.x - slices.right) as i16,
1157            position_offset.y + slices.bottom as i16,
1158            0,
1159        );
1160        let right_edge_world_quad_size = UVec2::new(slices.right, world_edge_height);
1161        let right_edge_texture_size = UVec2::new(slices.right, texture_edge_height);
1162        let right_edge_atlas = URect::new(
1163            atlas_origin.x + texture_window_size.x - slices.right,
1164            atlas_origin.y + slices.top, // Skip top slice
1165            right_edge_texture_size.x,
1166            right_edge_texture_size.y,
1167        );
1168
1169        let right_edge_quad = Self::quad_helper_uniform(
1170            right_edge_pos,
1171            right_edge_world_quad_size,
1172            right_edge_atlas,
1173            color,
1174            current_texture_size,
1175        );
1176        quad_matrix_and_uv.push(right_edge_quad);
1177
1178        // Top left corner
1179        let top_left_pos = Vec3::new(
1180            position_offset.x,
1181            position_offset.y + (world_window_size.y - slices.top) as i16,
1182            0,
1183        );
1184        let top_left_corner_size = UVec2::new(slices.left, slices.top);
1185        let top_left_atlas = URect::new(
1186            atlas_origin.x,
1187            atlas_origin.y, // Top of texture
1188            top_left_corner_size.x,
1189            top_left_corner_size.y,
1190        );
1191        let top_left_quad = Self::quad_helper_uniform(
1192            top_left_pos,
1193            top_left_corner_size,
1194            top_left_atlas,
1195            color,
1196            current_texture_size,
1197        );
1198        quad_matrix_and_uv.push(top_left_quad);
1199
1200        // Top edge
1201        let top_edge_pos = Vec3::new(
1202            position_offset.x + slices.left as i16,
1203            position_offset.y + (world_window_size.y - slices.top) as i16,
1204            0,
1205        );
1206        let top_edge_world_quad_size = UVec2::new(world_edge_width, slices.top);
1207        let top_edge_texture_size = UVec2::new(texture_edge_width, slices.top);
1208        let top_edge_atlas = URect::new(
1209            atlas_origin.x + slices.left,
1210            atlas_origin.y, // Top of texture
1211            top_edge_texture_size.x,
1212            top_edge_texture_size.y,
1213        );
1214        let top_edge_quad = Self::quad_helper_uniform(
1215            top_edge_pos,
1216            top_edge_world_quad_size,
1217            top_edge_atlas,
1218            color,
1219            current_texture_size,
1220        );
1221        quad_matrix_and_uv.push(top_edge_quad);
1222
1223        // Top right corner
1224        let top_right_pos = Vec3::new(
1225            position_offset.x + (world_window_size.x - slices.right) as i16,
1226            position_offset.y + (world_window_size.y - slices.top) as i16,
1227            0,
1228        );
1229        let top_right_corner_size = UVec2::new(slices.right, slices.top);
1230        let top_right_atlas = URect::new(
1231            atlas_origin.x + texture_window_size.x - slices.right,
1232            atlas_origin.y, // Top of texture
1233            top_right_corner_size.x,
1234            top_right_corner_size.y,
1235        );
1236        let top_right_quad = Self::quad_helper_uniform(
1237            top_right_pos,
1238            top_right_corner_size,
1239            top_right_atlas,
1240            color,
1241            current_texture_size,
1242        );
1243        quad_matrix_and_uv.push(top_right_quad);
1244    }
1245
1246    fn sort_and_put_in_batches(&mut self) -> Vec<Vec<&RenderItem>> {
1247        sort_render_items_by_z_and_material(&mut self.items);
1248
1249        self.order_render_items_in_batches()
1250    }
1251
1252    /// # Panics
1253    ///
1254    #[allow(clippy::too_many_lines)]
1255    pub fn render(
1256        &mut self,
1257        command_encoder: &mut CommandEncoder,
1258        display_surface_texture_view: &TextureView,
1259        //        materials: &Assets<Material>,
1260        textures: &Assets<Texture>,
1261        fonts: &Assets<Font>,
1262        now: Millis,
1263    ) {
1264        self.debug_tick += 1;
1265        trace!("start render()");
1266        self.last_render_at = now;
1267
1268        self.set_viewport_and_view_projection_matrix();
1269
1270        self.write_vertex_indices_and_uv_to_buffer(textures, fonts);
1271
1272        self.render_batches_to_virtual_texture(command_encoder, textures);
1273
1274        self.render_virtual_texture_to_display(command_encoder, display_surface_texture_view);
1275    }
1276
1277    pub fn set_viewport_and_view_projection_matrix(&mut self) {
1278        let view_proj_matrix = create_view_projection_matrix_from_virtual(
1279            self.virtual_surface_size.x,
1280            self.virtual_surface_size.y,
1281        );
1282
1283        let scale_matrix = Matrix4::from_scale(self.scale, self.scale, 0.0);
1284        let origin_translation_matrix =
1285            Matrix4::from_translation(-self.origin.x as f32, -self.origin.y as f32, 0.0);
1286
1287        let total_matrix = scale_matrix * view_proj_matrix * origin_translation_matrix;
1288
1289        // write all model_matrix and uv_coords to instance buffer once, before the render pass
1290        self.queue.write_buffer(
1291            &self.camera_buffer,
1292            0,
1293            bytemuck::cast_slice(&[total_matrix]),
1294        );
1295    }
1296
1297    pub fn render_batches_to_virtual_texture(
1298        &mut self,
1299        command_encoder: &mut CommandEncoder,
1300        textures: &Assets<Texture>,
1301    ) {
1302        let mut render_pass = command_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1303            label: Some("Game Render Pass"),
1304            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1305                view: &self.virtual_surface_texture_view,
1306                depth_slice: None,
1307                resolve_target: None,
1308                ops: wgpu::Operations {
1309                    load: wgpu::LoadOp::Clear(self.clear_color),
1310                    store: wgpu::StoreOp::Store,
1311                },
1312            })],
1313            depth_stencil_attachment: None,
1314            timestamp_writes: None,
1315            occlusion_query_set: None,
1316        });
1317
1318        render_pass.set_viewport(
1319            0.0,
1320            0.0,
1321            self.virtual_surface_size.x as f32,
1322            self.virtual_surface_size.y as f32,
1323            0.0,
1324            1.0,
1325        );
1326
1327        // Index and vertex buffers never change
1328        render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
1329        render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1330
1331        // Vertex buffer is reused
1332        render_pass.set_vertex_buffer(1, self.quad_matrix_and_uv_instance_buffer.slice(..));
1333
1334        let num_indices = mireforge_wgpu_sprites::INDICES.len() as u32;
1335
1336        let mut current_pipeline: Option<&MaterialKind> = None;
1337
1338        for &(ref weak_material_ref, start, count) in &self.batch_offsets {
1339            let wgpu_material = weak_material_ref;
1340
1341            let pipeline_kind = &wgpu_material.kind;
1342
1343            if current_pipeline != Some(pipeline_kind) {
1344                let pipeline = match pipeline_kind {
1345                    MaterialKind::NormalSprite { .. } => &self.normal_sprite_pipeline.pipeline,
1346                    MaterialKind::Quad => &self.quad_shader_info.pipeline,
1347                    MaterialKind::AlphaMasker { .. } => &self.mask_shader_info.pipeline,
1348                    MaterialKind::LightAdd { .. } => &self.light_shader_info.pipeline,
1349                };
1350                //trace!(%pipeline_kind, ?pipeline, "setting pipeline");
1351                render_pass.set_pipeline(pipeline);
1352                // Apparently after setting pipeline,
1353                // you must set all bind groups again
1354                current_pipeline = Some(pipeline_kind);
1355                render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
1356            }
1357
1358            match &wgpu_material.kind {
1359                MaterialKind::NormalSprite { primary_texture }
1360                | MaterialKind::LightAdd { primary_texture } => {
1361                    let texture = textures.get(primary_texture).unwrap();
1362                    // Bind the texture and sampler bind group (Bind Group 1)
1363                    render_pass.set_bind_group(1, &texture.texture_and_sampler_bind_group, &[]);
1364                }
1365                MaterialKind::AlphaMasker {
1366                    primary_texture,
1367                    alpha_texture,
1368                } => {
1369                    let real_diffuse_texture = textures.get(primary_texture).unwrap();
1370                    let alpha_texture = textures.get(alpha_texture).unwrap();
1371                    render_pass.set_bind_group(
1372                        1,
1373                        &real_diffuse_texture.texture_and_sampler_bind_group,
1374                        &[],
1375                    );
1376                    render_pass.set_bind_group(
1377                        2,
1378                        &alpha_texture.texture_and_sampler_bind_group,
1379                        &[],
1380                    );
1381                }
1382                MaterialKind::Quad => {
1383                    // Intentionally do nothing
1384                }
1385            }
1386            assert!(
1387                count <= MAXIMUM_QUADS_IN_A_BATCH as u32,
1388                "too many instanced draw in a batch {count}"
1389            );
1390
1391            // Issue the instanced draw call for the batch
1392            trace!(material=%weak_material_ref, start=%start, count=%count, %num_indices, "draw instanced");
1393            render_pass.draw_indexed(0..num_indices, 0, start..(start + count));
1394        }
1395        self.items.clear();
1396    }
1397
1398    pub fn render_virtual_texture_to_display(
1399        &mut self,
1400        command_encoder: &mut CommandEncoder,
1401        display_surface_texture_view: &TextureView,
1402    ) {
1403        let mut render_pass = command_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1404            label: Some("Screen Render Pass"),
1405            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1406                view: display_surface_texture_view,
1407                depth_slice: None,
1408                resolve_target: None,
1409                ops: wgpu::Operations {
1410                    load: wgpu::LoadOp::Clear(self.screen_clear_color),
1411                    store: wgpu::StoreOp::Store,
1412                },
1413            })],
1414            depth_stencil_attachment: None,
1415            timestamp_writes: None,
1416            occlusion_query_set: None,
1417        });
1418
1419        /*
1420        let scale_x = window_width as f32 / VIRTUAL_WIDTH as f32;
1421        let scale_y = window_height as f32 / VIRTUAL_HEIGHT as f32;
1422        let scale = scale_x.min(scale_y).floor(); // Use integer scaling
1423
1424        let viewport_width = VIRTUAL_WIDTH as f32 * scale;
1425        let viewport_height = VIRTUAL_HEIGHT as f32 * scale;
1426        let viewport_x = (window_width as f32 - viewport_width) / 2.0;
1427        let viewport_y = (window_height as f32 - viewport_height) / 2.0;
1428         */
1429
1430        self.viewport = match self.viewport_strategy {
1431            ViewportStrategy::FitIntegerScaling => Self::viewport_from_integer_scale(
1432                self.physical_surface_size,
1433                self.virtual_surface_size,
1434            ),
1435            ViewportStrategy::FitFloatScaling => Self::viewport_from_float_scale(
1436                self.physical_surface_size,
1437                self.virtual_surface_size,
1438            ),
1439            ViewportStrategy::MatchPhysicalSize => URect::new(
1440                0,
1441                0,
1442                self.physical_surface_size.x,
1443                self.physical_surface_size.y,
1444            ),
1445        };
1446
1447        render_pass.set_viewport(
1448            self.viewport.position.x as f32,
1449            self.viewport.position.y as f32,
1450            self.viewport.size.x as f32,
1451            self.viewport.size.y as f32,
1452            0.0,
1453            1.0,
1454        );
1455
1456        // Draw the render texture to the screen
1457        render_pass.set_pipeline(&self.virtual_to_screen_shader_info.pipeline);
1458        render_pass.set_bind_group(0, &self.virtual_to_surface_bind_group, &[]);
1459        render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1460
1461        render_pass.draw(0..6, 0..1);
1462    }
1463
1464    pub fn texture_resource_from_texture(&self, texture: &wgpu::Texture, label: &str) -> Texture {
1465        trace!("load texture from memory with name: '{label}'");
1466        let size = &texture.size();
1467        let texture_and_sampler_bind_group =
1468            mireforge_wgpu_sprites::create_sprite_texture_and_sampler_bind_group(
1469                &self.device,
1470                &self.texture_sampler_bind_group_layout,
1471                texture,
1472                &self.sampler,
1473                label,
1474            );
1475
1476        let texture_size = UVec2::new(size.width as u16, size.height as u16);
1477
1478        Texture {
1479            texture_and_sampler_bind_group,
1480            texture_size,
1481        }
1482    }
1483}
1484
1485fn create_view_projection_matrix_from_virtual(virtual_width: u16, virtual_height: u16) -> Matrix4 {
1486    let (bottom, top) = (0.0, virtual_height as f32);
1487
1488    // flip Z by swapping near/far if you want the opposite handedness
1489    // (e.g. for a left-handed vs right-handed depth axis)
1490    let (near, far) =
1491        // standard: near < far maps 0 → near, 1 → far
1492        (/* nearPlaneDepth */ -1.0, /* farPlaneDepth */ 1.0)
1493    ;
1494
1495    OrthoInfo {
1496        left: 0.0,
1497        right: virtual_width as f32,
1498        bottom,
1499        top,
1500        near, // Maybe flipped? -1.0
1501        far,  // maybe flipped? 1.0 or 0.0
1502    }
1503    .into()
1504}
1505
1506fn create_view_uniform_view_projection_matrix(viewport_size: UVec2) -> Matrix4 {
1507    let viewport_width = viewport_size.x as f32;
1508    let viewport_height = viewport_size.y as f32;
1509
1510    let viewport_aspect_ratio = viewport_width / viewport_height;
1511
1512    let scale_x = 1.0;
1513    let scale_y = viewport_aspect_ratio;
1514
1515    let view_projection_matrix = [
1516        [scale_x, 0.0, 0.0, 0.0],
1517        [0.0, scale_y, 0.0, 0.0],
1518        [0.0, 0.0, -1.0, 0.0],
1519        [0.0, 0.0, 0.0, 1.0],
1520    ];
1521
1522    view_projection_matrix.into()
1523}
1524
1525fn sort_render_items_by_z_and_material(items: &mut [RenderItem]) {
1526    items.sort_by_key(|item| (item.position.z, item.material_ref.clone()));
1527}
1528
1529#[derive(Debug, Clone, Copy, Default)]
1530pub enum Rotation {
1531    #[default]
1532    Degrees0,
1533    Degrees90,
1534    Degrees180,
1535    Degrees270,
1536}
1537
1538#[derive(Debug, Copy, Clone)]
1539pub struct SpriteParams {
1540    pub texture_size: UVec2,
1541    pub texture_pos: UVec2,
1542    pub scale: u8,
1543    pub rotation: Rotation,
1544    pub flip_x: bool,
1545    pub flip_y: bool,
1546    pub pivot: Vec2,
1547    pub color: Color,
1548}
1549
1550impl Default for SpriteParams {
1551    fn default() -> Self {
1552        Self {
1553            texture_size: UVec2::new(0, 0),
1554            texture_pos: UVec2::new(0, 0),
1555            pivot: Vec2::new(0, 0),
1556            flip_x: false,
1557            flip_y: false,
1558            color: Color::from_octet(255, 255, 255, 255),
1559            scale: 1,
1560            rotation: Rotation::Degrees0,
1561        }
1562    }
1563}
1564
1565pub type BindGroupRef = Arc<BindGroup>;
1566
1567#[derive(Debug, PartialEq, Eq, Asset)]
1568pub struct Texture {
1569    pub texture_and_sampler_bind_group: BindGroup,
1570    //    pub pipeline: RenderPipelineRef,
1571    pub texture_size: UVec2,
1572}
1573
1574impl Display for Texture {
1575    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
1576        write!(f, "{:?}", self.texture_size)
1577    }
1578}
1579
1580impl PartialOrd<Self> for Texture {
1581    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1582        Some(
1583            self.texture_and_sampler_bind_group
1584                .cmp(&other.texture_and_sampler_bind_group),
1585        )
1586    }
1587}
1588
1589impl Ord for Texture {
1590    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1591        self.texture_and_sampler_bind_group
1592            .cmp(&other.texture_and_sampler_bind_group)
1593    }
1594}
1595
1596#[derive(Debug, Ord, PartialOrd, PartialEq, Eq)]
1597pub struct MaterialBase {
1598    //pub pipeline: PipelineRef,
1599}
1600
1601#[derive(Debug, Ord, PartialOrd, PartialEq, Eq)]
1602pub struct Material {
1603    pub base: MaterialBase,
1604    pub kind: MaterialKind,
1605}
1606
1607impl Material {
1608    #[inline]
1609    #[must_use]
1610    pub fn primary_texture(&self) -> Option<TextureRef> {
1611        self.kind.primary_texture()
1612    }
1613
1614    #[inline]
1615    #[must_use]
1616    pub fn is_complete(&self, textures: &Assets<Texture>) -> bool {
1617        self.kind.is_complete(textures)
1618    }
1619}
1620
1621impl Display for Material {
1622    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1623        write!(f, "{}", self.kind)
1624    }
1625}
1626
1627#[derive(Debug, Ord, PartialOrd, PartialEq, Eq)]
1628pub enum MaterialKind {
1629    NormalSprite {
1630        primary_texture: Id<Texture>,
1631    },
1632    AlphaMasker {
1633        primary_texture: Id<Texture>,
1634        alpha_texture: Id<Texture>,
1635    },
1636    Quad,
1637    LightAdd {
1638        primary_texture: Id<Texture>,
1639    },
1640}
1641
1642impl MaterialKind {}
1643
1644impl MaterialKind {
1645    pub fn primary_texture(&self) -> Option<Id<Texture>> {
1646        match &self {
1647            Self::NormalSprite {
1648                primary_texture, ..
1649            }
1650            | Self::LightAdd { primary_texture }
1651            | Self::AlphaMasker {
1652                primary_texture, ..
1653            } => Some(primary_texture.clone()),
1654            Self::Quad => None,
1655        }
1656    }
1657
1658    pub(crate) fn is_complete(&self, textures: &Assets<Texture>) -> bool {
1659        match &self {
1660            Self::NormalSprite { primary_texture } | Self::LightAdd { primary_texture } => {
1661                textures.contains(primary_texture)
1662            }
1663            Self::AlphaMasker {
1664                primary_texture,
1665                alpha_texture,
1666            } => textures.contains(primary_texture) && textures.contains(alpha_texture),
1667            Self::Quad => true,
1668        }
1669    }
1670}
1671
1672impl Display for MaterialKind {
1673    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1674        let texture_name = self
1675            .primary_texture()
1676            .map_or_else(String::new, |x| x.to_string());
1677
1678        let kind_name = match self {
1679            Self::NormalSprite { .. } => "NormalSprite",
1680            Self::LightAdd { .. } => "Light (Add)",
1681            Self::Quad => "Quad",
1682            Self::AlphaMasker { .. } => "AlphaMasker",
1683        };
1684
1685        write!(f, "{kind_name} texture {texture_name}")
1686    }
1687}
1688
1689#[derive(Debug)]
1690pub struct Sprite {
1691    pub params: SpriteParams,
1692}
1693
1694#[derive(Debug)]
1695pub struct QuadColor {
1696    pub size: UVec2,
1697    pub color: Color,
1698}
1699
1700#[derive(Debug, Copy, Clone)]
1701pub struct Slices {
1702    pub left: u16,
1703    pub top: u16,
1704    pub right: u16,  // how many pixels from the right side of the texture and going in
1705    pub bottom: u16, // how much to take from bottom of slice
1706}
1707
1708#[derive(Debug)]
1709pub struct NineSlice {
1710    pub size: UVec2, // size of whole "window"
1711    pub slices: Slices,
1712    pub color: Color, // color tint
1713    pub origin_in_atlas: UVec2,
1714    pub size_inside_atlas: Option<UVec2>,
1715}
1716
1717#[derive(Debug)]
1718pub struct TileMap {
1719    pub tiles_data_grid_size: UVec2,
1720    pub cell_count_size: UVec2,
1721    pub one_cell_size: UVec2,
1722    pub tiles: Vec<u16>,
1723    pub scale: u8,
1724}
1725
1726#[derive(PartialEq, Debug, Eq, Ord, PartialOrd)]
1727pub struct Pipeline {
1728    name: String,
1729    render_pipeline: RenderPipeline,
1730}
1731
1732impl Display for Pipeline {
1733    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1734        write!(f, "pipeline: {}", self.name)
1735    }
1736}
1737
1738pub type PipelineRef = Arc<Pipeline>;