mireforge_render_wgpu/
lib.rs

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