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