mireforge_render_wgpu/
lib.rs

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