swamp_render_wgpu/
lib.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/swamp/swamp
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5pub mod plugin;
6pub mod prelude;
7
8use int_math::{URect, UVec2, Vec2, Vec3};
9use limnus_assets::Assets;
10use limnus_assets::prelude::{Asset, Id, RawAssetId, RawWeakId, WeakId};
11use limnus_resource::prelude::Resource;
12use limnus_wgpu_math::{Matrix4, OrthoInfo, Vec4};
13use monotonic_time_rs::Millis;
14use std::cmp::Ordering;
15use std::fmt::Debug;
16use std::mem::swap;
17use std::sync::Arc;
18use swamp_font::Font;
19use swamp_font::FontRef;
20use swamp_font::WeakFontRef;
21use swamp_render::prelude::*;
22use swamp_wgpu_sprites::{SpriteInfo, SpriteInstanceUniform};
23use tracing::trace;
24use wgpu::{BindGroup, BindGroupLayout, Buffer, RenderPass, RenderPipeline};
25
26pub type MaterialRef = Id<Material>;
27pub type WeakMaterialRef = WeakId<Material>;
28
29pub trait FrameLookup {
30    fn lookup(&self, frame: u16) -> (&MaterialRef, URect);
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct FixedAtlas {
35    pub material: MaterialRef,
36    pub texture_size: UVec2,
37    pub one_cell_size: UVec2,
38    pub cell_count_size: UVec2,
39}
40
41impl FixedAtlas {
42    /// # Panics
43    ///
44    #[must_use]
45    pub fn new(one_cell_size: UVec2, texture_size: UVec2, material_ref: MaterialRef) -> Self {
46        let cell_count_size = UVec2::new(
47            texture_size.x / one_cell_size.x,
48            texture_size.y / one_cell_size.y,
49        );
50
51        assert_ne!(cell_count_size.x, 0, "illegal texture and one cell size");
52
53        Self {
54            material: material_ref,
55            texture_size,
56            one_cell_size,
57            cell_count_size,
58        }
59    }
60}
61
62impl FrameLookup for FixedAtlas {
63    fn lookup(&self, frame: u16) -> (&MaterialRef, URect) {
64        let x = frame % self.cell_count_size.x;
65        let y = frame / self.cell_count_size.x;
66
67        (
68            &self.material,
69            URect::new(
70                x * self.one_cell_size.x,
71                y * self.one_cell_size.y,
72                self.one_cell_size.x,
73                self.one_cell_size.y,
74            ),
75        )
76    }
77}
78
79#[derive(Debug, PartialEq)]
80pub struct FontAndMaterial {
81    pub font_ref: FontRef,
82    pub material_ref: MaterialRef,
83}
84
85pub trait Gfx {
86    fn sprite_atlas_frame(&mut self, position: Vec3, frame: u16, atlas: &impl FrameLookup);
87    fn sprite_atlas(&mut self, position: Vec3, atlas_rect: URect, material_ref: &MaterialRef);
88    fn draw_sprite(&mut self, position: Vec3, material_ref: &MaterialRef);
89    fn draw_sprite_ex(&mut self, position: Vec3, material_ref: &MaterialRef, params: &SpriteParams);
90    fn set_origin(&mut self, position: Vec2);
91
92    fn set_clear_color(&mut self, color: Color);
93
94    fn tilemap_params(
95        &mut self,
96        position: Vec3,
97        tiles: &[u16],
98        width: u16,
99        atlas_ref: &FixedAtlas,
100        scale: u8,
101    );
102
103    fn text_draw(&mut self, position: Vec3, text: &str, font_ref: &FontAndMaterial, color: &Color);
104
105    #[must_use]
106    fn now(&self) -> Millis;
107
108    #[must_use]
109    fn physical_aspect_ratio(&self) -> AspectRatio;
110
111    #[must_use]
112    fn physical_size(&self) -> UVec2;
113
114    fn set_viewport(&mut self, viewport_strategy: ViewportStrategy);
115
116    #[must_use]
117    fn viewport(&self) -> &ViewportStrategy;
118
119    fn set_scale(&mut self, scale_factor: VirtualScale);
120}
121
122fn to_wgpu_color(c: Color) -> wgpu::Color {
123    let f = c.to_f64();
124    wgpu::Color {
125        r: f.0,
126        g: f.1,
127        b: f.2,
128        a: f.3,
129    }
130}
131
132#[derive(Debug)]
133struct RenderItem {
134    position: Vec3,
135    material_ref: WeakMaterialRef,
136
137    renderable: Renderable,
138}
139
140#[derive(Debug)]
141pub struct Text {
142    text: String,
143    font_ref: WeakFontRef,
144    color: Color,
145}
146
147#[derive(Debug)]
148enum Renderable {
149    Sprite(Sprite),
150    QuadColor(QuadColor),
151    NineSlice(NineSlice),
152    TileMap(TileMap),
153    Text(Text),
154}
155
156#[derive(Resource)]
157pub struct Render {
158    index_buffer: Buffer,  // Only indicies for a single identity quad
159    vertex_buffer: Buffer, // Only one identity quad (0,0,1,1)
160    sampler: wgpu::Sampler,
161    pipeline: RenderPipelineRef,
162    physical_surface_size: UVec2,
163    viewport_strategy: ViewportStrategy,
164    // Group 0
165    camera_bind_group: BindGroup,
166    #[allow(unused)]
167    camera_buffer: Buffer,
168
169    // Group 1
170    texture_sampler_bind_group_layout: BindGroupLayout,
171
172    // Group 1
173    quad_matrix_and_uv_instance_buffer: Buffer,
174
175    device: Arc<wgpu::Device>,
176    queue: Arc<wgpu::Queue>, // Queue to talk to device
177
178    // Internals
179    items: Vec<RenderItem>,
180    //fonts: Vec<FontAndMaterialRef>,
181    origin: Vec2,
182
183    // Cache
184    batch_offsets: Vec<(WeakMaterialRef, u32, u32)>,
185    viewport: URect,
186    clear_color: wgpu::Color,
187    last_render_at: Millis,
188    scale: f32,
189}
190
191impl Debug for Render {
192    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193        write!(f, "Render")
194    }
195}
196
197impl Gfx for Render {
198    fn sprite_atlas_frame(&mut self, position: Vec3, frame: u16, atlas: &impl FrameLookup) {
199        self.sprite_atlas_frame(position, frame, atlas);
200    }
201
202    fn sprite_atlas(&mut self, position: Vec3, atlas_rect: URect, material_ref: &MaterialRef) {
203        self.sprite_atlas(position, atlas_rect, material_ref);
204    }
205
206    fn draw_sprite(&mut self, position: Vec3, material_ref: &MaterialRef) {
207        self.draw_sprite(position, material_ref);
208    }
209
210    fn draw_sprite_ex(
211        &mut self,
212        position: Vec3,
213        material_ref: &MaterialRef,
214        params: &SpriteParams,
215    ) {
216        self.draw_sprite_ex(position, material_ref, *params);
217    }
218
219    fn set_origin(&mut self, position: Vec2) {
220        self.origin = position;
221    }
222
223    fn set_clear_color(&mut self, color: Color) {
224        self.clear_color = to_wgpu_color(color);
225    }
226
227    fn tilemap_params(
228        &mut self,
229        position: Vec3,
230        tiles: &[u16],
231        width: u16,
232        atlas_ref: &FixedAtlas,
233        scale: u8,
234    ) {
235        self.items.push(RenderItem {
236            position,
237            material_ref: (&atlas_ref.material).into(),
238            renderable: Renderable::TileMap(TileMap {
239                tiles_data_grid_size: UVec2::new(width, tiles.len() as u16 / width),
240                cell_count_size: atlas_ref.cell_count_size,
241                one_cell_size: atlas_ref.one_cell_size,
242                tiles: Vec::from(tiles),
243                scale,
244            }),
245        });
246    }
247
248    fn text_draw(
249        &mut self,
250        position: Vec3,
251        text: &str,
252        font_and_mat: &FontAndMaterial,
253        color: &Color,
254    ) {
255        self.items.push(RenderItem {
256            position,
257            material_ref: (&font_and_mat.material_ref).into(),
258            renderable: Renderable::Text(Text {
259                text: text.to_string(),
260                font_ref: (&font_and_mat.font_ref).into(),
261                color: *color,
262            }),
263        });
264    }
265
266    fn now(&self) -> Millis {
267        self.last_render_at
268    }
269
270    fn physical_aspect_ratio(&self) -> AspectRatio {
271        self.physical_surface_size.into()
272    }
273
274    fn physical_size(&self) -> UVec2 {
275        self.physical_surface_size
276    }
277
278    fn set_viewport(&mut self, viewport_strategy: ViewportStrategy) {
279        self.viewport_strategy = viewport_strategy;
280    }
281
282    fn viewport(&self) -> &ViewportStrategy {
283        &self.viewport_strategy
284    }
285
286    fn set_scale(&mut self, scale_factor: VirtualScale) {
287        match scale_factor {
288            VirtualScale::IntScale(scale) => self.scale = scale as f32,
289            VirtualScale::FloatScale(scale) => self.scale = scale,
290        }
291    }
292}
293
294impl Render {
295    #[must_use]
296    pub fn new(
297        device: Arc<wgpu::Device>,
298        queue: Arc<wgpu::Queue>, // Queue to talk to device
299        surface_texture_format: wgpu::TextureFormat,
300        physical_size: UVec2,
301        virtual_surface_size: UVec2,
302        now: Millis,
303    ) -> Self {
304        let (vertex_shader_source, fragment_shader_source) = sources();
305
306        let sprite_info = SpriteInfo::new(
307            &device,
308            surface_texture_format,
309            vertex_shader_source,
310            fragment_shader_source,
311            create_view_uniform_view_projection_matrix(physical_size),
312        );
313
314        Self {
315            device,
316            queue,
317            items: Vec::new(),
318            //   fonts: Vec::new(),
319            sampler: sprite_info.sampler,
320            pipeline: Arc::new(sprite_info.sprite_pipeline),
321            texture_sampler_bind_group_layout: sprite_info.sprite_texture_sampler_bind_group_layout,
322            index_buffer: sprite_info.index_buffer,
323            vertex_buffer: sprite_info.vertex_buffer,
324            quad_matrix_and_uv_instance_buffer: sprite_info.quad_matrix_and_uv_instance_buffer,
325            camera_bind_group: sprite_info.camera_bind_group,
326            batch_offsets: Vec::new(),
327            camera_buffer: sprite_info.camera_uniform_buffer,
328            viewport: Self::viewport_from_integer_scale(physical_size, virtual_surface_size),
329            clear_color: to_wgpu_color(Color::from_f32(0.008, 0.015, 0.008, 1.0)),
330            origin: Vec2::new(0, 0),
331            last_render_at: now,
332            physical_surface_size: physical_size,
333            viewport_strategy: ViewportStrategy::FitIntegerScaling(virtual_surface_size),
334            scale: 1.0,
335        }
336    }
337
338    pub fn set_now(&mut self, now: Millis) {
339        self.last_render_at = now;
340    }
341
342    pub const fn virtual_surface_size(&self) -> UVec2 {
343        match self.viewport_strategy {
344            ViewportStrategy::FitIntegerScaling(virtual_size)
345            | ViewportStrategy::FitFloatScaling(virtual_size) => virtual_size,
346            ViewportStrategy::MatchPhysicalSize => self.physical_surface_size,
347        }
348    }
349
350    pub const fn physical_surface_size(&self) -> UVec2 {
351        self.physical_surface_size
352    }
353
354    pub const fn viewport(&self) -> URect {
355        self.viewport
356    }
357
358    #[inline(always)]
359    fn push_sprite(&mut self, position: Vec3, material: &MaterialRef, sprite: Sprite) {
360        self.items.push(RenderItem {
361            position,
362            material_ref: material.into(),
363            renderable: Renderable::Sprite(sprite),
364        });
365    }
366
367    #[must_use]
368    pub fn viewport_from_integer_scale(physical_size: UVec2, virtual_size: UVec2) -> URect {
369        let window_aspect = physical_size.x as f32 / physical_size.y as f32;
370        let virtual_aspect = virtual_size.x as f32 / virtual_size.y as f32;
371
372        if physical_size.x < virtual_size.x || physical_size.y < virtual_size.y {
373            return URect::new(0, 0, physical_size.x, physical_size.y);
374        }
375
376        let mut integer_scale = if window_aspect > virtual_aspect {
377            physical_size.y / virtual_size.y
378        } else {
379            physical_size.x / virtual_size.x
380        };
381
382        if integer_scale < 1 {
383            integer_scale = 1;
384        }
385
386        let viewport_actual_size = UVec2::new(
387            virtual_size.x * integer_scale,
388            virtual_size.y * integer_scale,
389        );
390
391        let border_size = physical_size - viewport_actual_size;
392
393        let offset = border_size / 2;
394
395        URect::new(
396            offset.x,
397            offset.y,
398            viewport_actual_size.x,
399            viewport_actual_size.y,
400        )
401    }
402
403    #[must_use]
404    pub fn viewport_from_float_scale(physical_size: UVec2, virtual_size: UVec2) -> URect {
405        let window_aspect = physical_size.x as f32 / physical_size.y as f32;
406        let virtual_aspect = virtual_size.x as f32 / virtual_size.y as f32;
407
408        if physical_size.x < virtual_size.x || physical_size.y < virtual_size.y {
409            return URect::new(0, 0, physical_size.x, physical_size.y);
410        }
411
412        let mut float_scale = if window_aspect > virtual_aspect {
413            physical_size.y as f32 / virtual_size.y as f32
414        } else {
415            physical_size.x as f32 / virtual_size.x as f32
416        };
417
418        if float_scale < 0.01 {
419            float_scale = 0.01;
420        }
421
422        let viewport_actual_size = UVec2::new(
423            (virtual_size.x as f32 * float_scale) as u16,
424            (virtual_size.y as f32 * float_scale) as u16,
425        );
426
427        let border_size = physical_size - viewport_actual_size;
428
429        let offset = border_size / 2;
430
431        URect::new(
432            offset.x,
433            offset.y,
434            viewport_actual_size.x,
435            viewport_actual_size.y,
436        )
437    }
438
439    pub fn resize(&mut self, physical_size: UVec2) {
440        self.physical_surface_size = physical_size;
441    }
442
443    /*
444    pub fn render_sprite(&mut self, position: Vec3, material: &MaterialRef, params: SpriteParams) {
445        let atlas_rect = URect::new(0, 0, material.texture_size().x, material.texture_size().y);
446
447        self.push_sprite(position, material, Sprite { atlas_rect, params });
448    }*/
449
450    pub fn sprite_atlas(&mut self, position: Vec3, atlas_rect: URect, material_ref: &MaterialRef) {
451        self.push_sprite(
452            position,
453            material_ref,
454            Sprite {
455                params: SpriteParams {
456                    texture_pos: atlas_rect.position,
457                    texture_size: atlas_rect.size,
458                    ..Default::default()
459                },
460            },
461        );
462    }
463
464    pub fn sprite_atlas_frame(&mut self, position: Vec3, frame: u16, atlas: &impl FrameLookup) {
465        let (material_ref, atlas_rect) = atlas.lookup(frame);
466        self.push_sprite(
467            position,
468            material_ref,
469            Sprite {
470                params: SpriteParams {
471                    texture_pos: atlas_rect.position,
472                    texture_size: atlas_rect.size,
473                    ..Default::default()
474                },
475            },
476        );
477    }
478
479    pub fn sprite_atlas_frame_ex(
480        &mut self,
481        position: Vec3,
482        frame: u16,
483        atlas: &impl FrameLookup,
484        mut params: SpriteParams,
485    ) {
486        let (material_ref, atlas_rect) = atlas.lookup(frame);
487        params.texture_pos = atlas_rect.position;
488        params.texture_size = atlas_rect.size;
489        self.push_sprite(position, material_ref, Sprite { params });
490    }
491
492    pub fn draw_sprite(&mut self, position: Vec3, material: &MaterialRef) {
493        self.push_sprite(
494            position,
495            material,
496            Sprite {
497                params: SpriteParams::default(),
498            },
499        );
500    }
501
502    pub fn draw_sprite_ex(&mut self, position: Vec3, material: &MaterialRef, params: SpriteParams) {
503        self.push_sprite(position, material, Sprite { params });
504    }
505
506    pub fn draw_quad(&mut self, position: Vec3, size: UVec2, color: Color) {
507        self.items.push(RenderItem {
508            position,
509            material_ref: WeakId::<Material>::new(RawWeakId::with_asset_type::<Material>(
510                RawAssetId::new(0, 0),
511                "nothing".into(),
512            )),
513            renderable: Renderable::QuadColor(QuadColor { size, color }),
514        });
515    }
516
517    #[allow(clippy::too_many_arguments)]
518    pub fn draw_nine_slice(
519        &mut self,
520        position: Vec3,
521        size: UVec2,
522        corner_size: UVec2,
523        texture_window_size: UVec2,
524        material_ref: &MaterialRef,
525        atlas_offset: UVec2,
526        color: Color,
527    ) {
528        self.items.push(RenderItem {
529            position,
530            material_ref: material_ref.into(),
531            renderable: Renderable::NineSlice(NineSlice {
532                corner_size,
533                texture_window_size,
534                size,
535                atlas_offset,
536                color,
537            }),
538        });
539    }
540
541    pub const fn clear_color(&self) -> wgpu::Color {
542        self.clear_color
543    }
544
545    // first two is multiplier and second pair is offset
546    fn calculate_texture_coords_mul_add(atlas_rect: URect, texture_size: UVec2) -> Vec4 {
547        let x = atlas_rect.position.x as f32 / texture_size.x as f32;
548        let y = atlas_rect.position.y as f32 / texture_size.y as f32;
549        let width = atlas_rect.size.x as f32 / texture_size.x as f32;
550        let height = atlas_rect.size.y as f32 / texture_size.y as f32;
551        Vec4([width, height, x, y])
552    }
553
554    fn order_render_items_in_batches(&self) -> Vec<Vec<&RenderItem>> {
555        let mut material_batches: Vec<Vec<&RenderItem>> = Vec::new();
556        let mut current_batch: Vec<&RenderItem> = Vec::new();
557        let mut current_material: Option<&WeakMaterialRef> = None;
558
559        for render_item in &self.items {
560            if Some(&render_item.material_ref) != current_material {
561                if !current_batch.is_empty() {
562                    material_batches.push(current_batch.clone());
563                    current_batch.clear();
564                }
565                current_material = Some(&render_item.material_ref);
566            }
567            current_batch.push(render_item);
568        }
569
570        if !current_batch.is_empty() {
571            material_batches.push(current_batch);
572        }
573
574        material_batches
575    }
576
577    pub fn quad_helper_uniform(
578        position: Vec3,
579        quad_size: UVec2,
580        render_atlas: URect,
581        color: Color,
582
583        current_texture_size: UVec2,
584        //material: &MaterialRef,
585    ) -> SpriteInstanceUniform {
586        let model_matrix = Matrix4::from_translation(position.x as f32, position.y as f32, 0.0)
587            * Matrix4::from_scale(quad_size.x as f32, quad_size.y as f32, 1.0);
588
589        let tex_coords_mul_add =
590            Self::calculate_texture_coords_mul_add(render_atlas, current_texture_size);
591
592        let rotation_value = 0;
593
594        SpriteInstanceUniform::new(
595            model_matrix,
596            tex_coords_mul_add,
597            rotation_value,
598            Vec4(color.to_f32_slice()),
599            true,
600        )
601    }
602
603    /// # Panics
604    ///
605    #[allow(clippy::too_many_lines)]
606    pub fn prepare_render(&mut self, materials: &Assets<Material>, fonts: &Assets<Font>) {
607        const FLIP_X_MASK: u32 = 0b0000_0100;
608        const FLIP_Y_MASK: u32 = 0b0000_1000;
609
610        sort_render_items_by_z_and_material(&mut self.items);
611
612        let batches = self.order_render_items_in_batches();
613
614        let mut quad_matrix_and_uv: Vec<SpriteInstanceUniform> = Vec::new();
615        let mut batch_vertex_ranges: Vec<(WeakMaterialRef, u32, u32)> = Vec::new();
616
617        for render_items in &batches {
618            let quad_len_before = quad_matrix_and_uv.len() as u32;
619
620            // Fix: Access material_ref through reference and copy it
621            let weak_material_ref = render_items
622                .first()
623                .map(|item| {
624                    // Force copy semantics by dereferencing the shared reference
625                    let material_ref: WeakId<Material> = item.material_ref;
626                    material_ref
627                })
628                .expect("Render items batch was empty");
629
630            let result = materials.get_weak(weak_material_ref);
631            if result.is_none() {
632                // Material is not loaded yet
633                continue;
634            }
635            let material = result.unwrap();
636            let current_texture_size = material.texture_size;
637
638            for render_item in render_items {
639                match &render_item.renderable {
640                    Renderable::Sprite(sprite) => {
641                        let params = &sprite.params;
642                        let mut size = params.texture_size;
643                        if size.x == 0 && size.y == 0 {
644                            size = current_texture_size;
645                        }
646
647                        let render_atlas = URect {
648                            position: params.texture_pos,
649                            size,
650                        };
651
652                        match params.rotation {
653                            Rotation::Degrees90 | Rotation::Degrees270 => {
654                                swap(&mut size.x, &mut size.y);
655                            }
656                            _ => {}
657                        }
658
659                        let model_matrix = Matrix4::from_translation(
660                            render_item.position.x as f32,
661                            render_item.position.y as f32,
662                            0.0,
663                        ) * Matrix4::from_scale(
664                            (size.x * params.scale as u16) as f32,
665                            (size.y * params.scale as u16) as f32,
666                            1.0,
667                        );
668
669                        let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
670                            render_atlas,
671                            current_texture_size,
672                        );
673
674                        let mut rotation_value = match params.rotation {
675                            Rotation::Degrees0 => 0,
676                            Rotation::Degrees90 => 1,
677                            Rotation::Degrees180 => 2,
678                            Rotation::Degrees270 => 3,
679                        };
680
681                        if params.flip_x {
682                            rotation_value |= FLIP_X_MASK;
683                        }
684                        if params.flip_y {
685                            rotation_value |= FLIP_Y_MASK;
686                        }
687
688                        let quad_instance = SpriteInstanceUniform::new(
689                            model_matrix,
690                            tex_coords_mul_add,
691                            rotation_value,
692                            Vec4(params.color.to_f32_slice()),
693                            true,
694                        );
695                        quad_matrix_and_uv.push(quad_instance);
696                    }
697
698                    Renderable::NineSlice(nine_slice) => {
699                        Self::prepare_nine_slice(
700                            nine_slice,
701                            render_item.position,
702                            &mut quad_matrix_and_uv,
703                            current_texture_size,
704                        );
705                    }
706
707                    Renderable::QuadColor(quad) => {
708                        /*
709                        let params = &sprite.params;
710                        let mut size = params.texture_size;
711                        if size.x == 0 && size.y == 0 {
712                            size = current_texture_size;
713                        }
714
715                        let render_atlas = URect {
716                            position: params.texture_pos,
717                            size,
718                        };
719
720
721                        match params.rotation {
722                            Rotation::Degrees90 | Rotation::Degrees270 => {
723                                swap(&mut size.x, &mut size.y);
724                            }
725                            _ => {}
726                        }
727                         */
728
729                        let model_matrix =
730                            Matrix4::from_translation(
731                                render_item.position.x as f32,
732                                render_item.position.y as f32,
733                                0.0,
734                            ) * Matrix4::from_scale(quad.size.x as f32, quad.size.y as f32, 1.0);
735
736                        /*
737                        let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
738                            render_atlas,
739                            current_texture_size,
740                        );
741
742
743                        let mut rotation_value = match params.rotation {
744                            Rotation::Degrees0 => 0,
745                            Rotation::Degrees90 => 1,
746                            Rotation::Degrees180 => 2,
747                            Rotation::Degrees270 => 3,
748                        };
749
750                        if params.flip_x {
751                            rotation_value |= FLIP_X_MASK;
752                        }
753                        if params.flip_y {
754                            rotation_value |= FLIP_Y_MASK;
755                        }
756
757                         */
758
759                        let tex_coords_mul_add = Vec4([
760                            0.0, //x
761                            0.0, //y
762                            0.0, 0.0,
763                        ]);
764                        let rotation_value = 0;
765
766                        let quad_instance = SpriteInstanceUniform::new(
767                            model_matrix,
768                            tex_coords_mul_add,
769                            rotation_value,
770                            Vec4(quad.color.to_f32_slice()),
771                            false,
772                        );
773                        quad_matrix_and_uv.push(quad_instance);
774                    }
775
776                    Renderable::Text(text) => {
777                        let result = fonts.get_weak(text.font_ref);
778                        if result.is_none() {
779                            continue;
780                        }
781                        let font = result.unwrap();
782
783                        let glyphs = font.draw(&text.text);
784                        for glyph in glyphs {
785                            let pos = render_item.position + Vec3::from(glyph.relative_position);
786                            let texture_size = glyph.texture_rectangle.size;
787                            let model_matrix =
788                                Matrix4::from_translation(pos.x as f32, pos.y as f32, 0.0)
789                                    * Matrix4::from_scale(
790                                        texture_size.x as f32,
791                                        texture_size.y as f32,
792                                        1.0,
793                                    );
794                            let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
795                                glyph.texture_rectangle,
796                                current_texture_size,
797                            );
798
799                            let quad_instance = SpriteInstanceUniform::new(
800                                model_matrix,
801                                tex_coords_mul_add,
802                                0,
803                                Vec4(text.color.to_f32_slice()),
804                                true,
805                            );
806                            quad_matrix_and_uv.push(quad_instance);
807                        }
808                    }
809
810                    Renderable::TileMap(tile_map) => {
811                        for (index, tile) in tile_map.tiles.iter().enumerate() {
812                            let cell_pos_x = (index as u16 % tile_map.tiles_data_grid_size.x)
813                                * tile_map.one_cell_size.x
814                                * tile_map.scale as u16;
815                            let cell_pos_y = (index as u16 / tile_map.tiles_data_grid_size.x)
816                                * tile_map.one_cell_size.y
817                                * tile_map.scale as u16;
818                            let cell_x = *tile % tile_map.cell_count_size.x;
819                            let cell_y = *tile / tile_map.cell_count_size.x;
820
821                            let tex_x = cell_x * tile_map.one_cell_size.x;
822                            let tex_y = cell_y * tile_map.one_cell_size.x;
823
824                            let cell_texture_area = URect::new(
825                                tex_x,
826                                tex_y,
827                                tile_map.one_cell_size.x,
828                                tile_map.one_cell_size.y,
829                            );
830
831                            let cell_model_matrix = Matrix4::from_translation(
832                                (render_item.position.x + cell_pos_x as i16) as f32,
833                                (render_item.position.y + cell_pos_y as i16) as f32,
834                                0.0,
835                            ) * Matrix4::from_scale(
836                                (tile_map.one_cell_size.x * tile_map.scale as u16) as f32,
837                                (tile_map.one_cell_size.y * tile_map.scale as u16) as f32,
838                                1.0,
839                            );
840
841                            let cell_tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
842                                cell_texture_area,
843                                current_texture_size,
844                            );
845
846                            let quad_instance = SpriteInstanceUniform::new(
847                                cell_model_matrix,
848                                cell_tex_coords_mul_add,
849                                0,
850                                Vec4([1.0, 1.0, 1.0, 1.0]),
851                                true,
852                            );
853                            quad_matrix_and_uv.push(quad_instance);
854                        }
855                    }
856                }
857            }
858
859            let quad_count = quad_matrix_and_uv.len() as u32 - quad_len_before;
860            batch_vertex_ranges.push((weak_material_ref, quad_len_before, quad_count));
861        }
862
863        // write all model_matrix and uv_coords to instance buffer once, before the render pass
864        self.queue.write_buffer(
865            &self.quad_matrix_and_uv_instance_buffer,
866            0,
867            bytemuck::cast_slice(&quad_matrix_and_uv),
868        );
869
870        self.batch_offsets = batch_vertex_ranges;
871    }
872
873    #[allow(clippy::too_many_lines)]
874    #[inline]
875    pub fn prepare_nine_slice(
876        nine_slice: &NineSlice,
877        position_offset: Vec3,
878        quad_matrix_and_uv: &mut Vec<SpriteInstanceUniform>,
879        current_texture_size: UVec2,
880    ) {
881        let corner_size = nine_slice.corner_size;
882        let corner_height = corner_size.y;
883        let corner_width = corner_size.x;
884        let color = nine_slice.color;
885        let outer_size = nine_slice.size - corner_size * 2;
886        let side_width = outer_size.x;
887        let side_height = outer_size.y;
888        let window_size = nine_slice.size;
889        let texture_window_size = nine_slice.texture_window_size;
890
891        let atlas_origin = nine_slice.atlas_offset;
892
893        // Lower left
894        let lower_left_pos = Vec3::new(position_offset.x, position_offset.y, 0);
895        let lower_left_quad_size = UVec2::new(corner_width, corner_height);
896        let lower_left_atlas =
897            URect::new(atlas_origin.x, atlas_origin.y, corner_size.x, corner_size.y);
898        let lower_left_quad = Self::quad_helper_uniform(
899            lower_left_pos,
900            lower_left_quad_size,
901            lower_left_atlas,
902            color,
903            current_texture_size,
904        );
905        quad_matrix_and_uv.push(lower_left_quad);
906
907        // Lower side
908        let lower_side_position = Vec3::new(
909            position_offset.x + corner_width as i16,
910            position_offset.y,
911            0,
912        );
913        let lower_side_quad_size = UVec2::new(side_width, corner_height);
914        let lower_side_atlas = URect::new(
915            atlas_origin.x + corner_width,
916            atlas_origin.y,
917            texture_window_size.x - corner_width * 2,
918            corner_size.y,
919        );
920        let lower_side_quad = Self::quad_helper_uniform(
921            lower_side_position,
922            lower_side_quad_size,
923            lower_side_atlas,
924            color,
925            current_texture_size,
926        );
927        quad_matrix_and_uv.push(lower_side_quad);
928
929        // Lower right corner
930        let lower_side_position = Vec3::new(
931            position_offset.x + window_size.x as i16 - corner_width as i16,
932            position_offset.y,
933            0,
934        );
935        let lower_right_quad_size = UVec2::new(corner_width, corner_height);
936        let lower_right_atlas = URect::new(
937            atlas_origin.x + corner_width * 2,
938            atlas_origin.y,
939            corner_size.x,
940            corner_size.y,
941        );
942        let lower_right_quad = Self::quad_helper_uniform(
943            lower_side_position,
944            lower_right_quad_size,
945            lower_right_atlas,
946            color,
947            current_texture_size,
948        );
949        quad_matrix_and_uv.push(lower_right_quad);
950
951        // Left side
952        let left_side_pos = Vec3::new(
953            position_offset.x,
954            position_offset.y + corner_height as i16,
955            0,
956        );
957        let left_side_quad_size = UVec2::new(corner_width, side_height);
958        let left_side_atlas = URect::new(
959            atlas_origin.x,
960            atlas_origin.y - corner_height,
961            corner_size.x,
962            corner_size.y,
963        );
964        let left_side_quad = Self::quad_helper_uniform(
965            left_side_pos,
966            left_side_quad_size,
967            left_side_atlas,
968            color,
969            current_texture_size,
970        );
971        quad_matrix_and_uv.push(left_side_quad);
972
973        // Middle
974        let middle_pos = Vec3::new(
975            position_offset.x + corner_width as i16,
976            position_offset.y + corner_height as i16,
977            0,
978        );
979        let middle_quad_size = UVec2::new(
980            nine_slice.size.x - corner_width * 2,
981            nine_slice.size.y - corner_height * 2,
982        );
983        let middle_atlas = URect::new(
984            atlas_origin.x + corner_width,
985            atlas_origin.y - corner_height,
986            texture_window_size.x - corner_width * 2,
987            texture_window_size.y - corner_height * 2,
988        );
989        let middle_quad = Self::quad_helper_uniform(
990            middle_pos,
991            middle_quad_size,
992            middle_atlas,
993            color,
994            current_texture_size,
995        );
996        quad_matrix_and_uv.push(middle_quad);
997
998        // RightSide
999        let right_side_pos = Vec3::new(
1000            position_offset.x + window_size.x as i16 - corner_width as i16,
1001            position_offset.y + corner_height as i16,
1002            0,
1003        );
1004        let right_side_quad_size = UVec2::new(corner_width, window_size.y - corner_height * 2);
1005        let right_side_atlas = URect::new(
1006            atlas_origin.x + corner_width * 2,
1007            atlas_origin.y - corner_height,
1008            corner_size.x,
1009            corner_size.y,
1010        );
1011        let right_side_quad = Self::quad_helper_uniform(
1012            right_side_pos,
1013            right_side_quad_size,
1014            right_side_atlas,
1015            color,
1016            current_texture_size,
1017        );
1018        quad_matrix_and_uv.push(right_side_quad);
1019
1020        // -----------------------
1021
1022        let upper_y = position_offset.y + window_size.y as i16 - corner_height as i16;
1023        let atlas_upper_y = atlas_origin.y - corner_height * 2;
1024        // Upper left
1025        let upper_left_pos = Vec3::new(position_offset.x, upper_y, 0);
1026        let upper_left_quad_size = UVec2::new(corner_width, corner_height);
1027        let upper_left_atlas =
1028            URect::new(atlas_origin.x, atlas_upper_y, corner_size.x, corner_size.y);
1029        let upper_left_quad = Self::quad_helper_uniform(
1030            upper_left_pos,
1031            upper_left_quad_size,
1032            upper_left_atlas,
1033            color,
1034            current_texture_size,
1035        );
1036        quad_matrix_and_uv.push(upper_left_quad);
1037
1038        // Upper side
1039        let upper_side_position = Vec3::new(position_offset.x + corner_width as i16, upper_y, 0);
1040        let upper_side_quad_size = UVec2::new(side_width, corner_height);
1041        let upper_side_atlas = URect::new(
1042            atlas_origin.x + corner_width,
1043            atlas_upper_y,
1044            texture_window_size.x - corner_width * 2,
1045            corner_size.y,
1046        );
1047        let upper_side_quad = Self::quad_helper_uniform(
1048            upper_side_position,
1049            upper_side_quad_size,
1050            upper_side_atlas,
1051            color,
1052            current_texture_size,
1053        );
1054        quad_matrix_and_uv.push(upper_side_quad);
1055
1056        // Upper right corner
1057        let upper_side_position = Vec3::new(
1058            position_offset.x + window_size.x as i16 - corner_width as i16,
1059            upper_y,
1060            0,
1061        );
1062        let upper_right_quad_size = UVec2::new(corner_width, corner_height);
1063        let upper_right_atlas = URect::new(
1064            atlas_origin.x + corner_width * 2,
1065            atlas_upper_y,
1066            corner_size.x,
1067            corner_size.y,
1068        );
1069        let upper_right_quad = Self::quad_helper_uniform(
1070            upper_side_position,
1071            upper_right_quad_size,
1072            upper_right_atlas,
1073            color,
1074            current_texture_size,
1075        );
1076        quad_matrix_and_uv.push(upper_right_quad);
1077    }
1078
1079    /// # Panics
1080    ///
1081    pub fn render(
1082        &mut self,
1083        render_pass: &mut RenderPass,
1084        materials: &Assets<Material>,
1085        fonts: &Assets<Font>,
1086        now: Millis,
1087    ) {
1088        trace!("start render()");
1089        self.last_render_at = now;
1090
1091        self.viewport = match self.viewport_strategy {
1092            ViewportStrategy::FitIntegerScaling(virtual_surface_size) => {
1093                Self::viewport_from_integer_scale(self.physical_surface_size, virtual_surface_size)
1094            }
1095            ViewportStrategy::FitFloatScaling(virtual_surface_size) => {
1096                Self::viewport_from_float_scale(self.physical_surface_size, virtual_surface_size)
1097            }
1098            ViewportStrategy::MatchPhysicalSize => URect::new(
1099                0,
1100                0,
1101                self.physical_surface_size.x,
1102                self.physical_surface_size.y,
1103            ),
1104        };
1105
1106        let view_proj_matrix = match self.viewport_strategy {
1107            ViewportStrategy::MatchPhysicalSize => {
1108                create_view_uniform_view_projection_matrix(self.physical_surface_size)
1109            }
1110            ViewportStrategy::FitFloatScaling(virtual_surface_size)
1111            | ViewportStrategy::FitIntegerScaling(virtual_surface_size) => {
1112                create_view_projection_matrix_from_virtual(
1113                    virtual_surface_size.x,
1114                    virtual_surface_size.y,
1115                )
1116            }
1117        };
1118
1119        let scale_matrix = Matrix4::from_scale(self.scale, self.scale, 0.0);
1120        let origin_translation_matrix =
1121            Matrix4::from_translation(-self.origin.x as f32, -self.origin.y as f32, 0.0);
1122
1123        let total_matrix = scale_matrix * view_proj_matrix * origin_translation_matrix;
1124
1125        // write all model_matrix and uv_coords to instance buffer once, before the render pass
1126        self.queue.write_buffer(
1127            &self.camera_buffer,
1128            0,
1129            bytemuck::cast_slice(&[total_matrix]),
1130        );
1131
1132        self.prepare_render(materials, fonts);
1133
1134        render_pass.set_viewport(
1135            self.viewport.position.x as f32,
1136            self.viewport.position.y as f32,
1137            self.viewport.size.x as f32,
1138            self.viewport.size.y as f32,
1139            0.0,
1140            1.0,
1141        );
1142
1143        render_pass.set_pipeline(&self.pipeline);
1144
1145        // Index and vertex buffers never change
1146        render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
1147        render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1148
1149        // Vertex buffer is reused
1150        render_pass.set_vertex_buffer(1, self.quad_matrix_and_uv_instance_buffer.slice(..));
1151
1152        // Camera is the same for everything
1153        render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
1154
1155        let num_indices = swamp_wgpu_sprites::INDICES.len() as u32;
1156
1157        for &(weak_material_ref, start, count) in &self.batch_offsets {
1158            let wgpu_material = materials
1159                .get_weak(weak_material_ref)
1160                .expect("no such material");
1161
1162            // Bind the texture and sampler bind group (Bind Group 1)
1163            render_pass.set_bind_group(1, &wgpu_material.texture_and_sampler_bind_group, &[]);
1164
1165            // Issue the instanced draw call for the batch
1166            trace!(material=%weak_material_ref, start=%start, count=%count, "draw instanced");
1167            render_pass.draw_indexed(0..num_indices, 0, start..(start + count));
1168        }
1169
1170        self.items.clear();
1171    }
1172
1173    pub fn material_from_texture(&self, texture: wgpu::Texture, label: &str) -> Material {
1174        trace!("load texture from memory with name: '{label}'");
1175        let size = &texture.size();
1176        let texture_and_sampler_bind_group =
1177            swamp_wgpu_sprites::create_sprite_texture_and_sampler_bind_group(
1178                &self.device,
1179                &self.texture_sampler_bind_group_layout,
1180                &texture,
1181                &self.sampler,
1182                label,
1183            );
1184
1185        let texture_size = UVec2::new(size.width as u16, size.height as u16);
1186
1187        Material {
1188            texture_and_sampler_bind_group,
1189            //render_pipeline: Arc::clone(&self.pipeline),
1190            texture_size,
1191        }
1192    }
1193}
1194
1195//fn set_view_projection(&mut self) {}
1196
1197fn create_view_projection_matrix_from_virtual(virtual_width: u16, virtual_height: u16) -> Matrix4 {
1198    OrthoInfo {
1199        left: 0.0,
1200        right: virtual_width as f32,
1201        bottom: 0.0,
1202        top: virtual_height as f32,
1203        near: 1.0,
1204        far: -1.0,
1205    }
1206    .into()
1207}
1208
1209fn create_view_uniform_view_projection_matrix(viewport_size: UVec2) -> Matrix4 {
1210    let viewport_width = viewport_size.x as f32;
1211    let viewport_height = viewport_size.y as f32;
1212
1213    let viewport_aspect_ratio = viewport_width / viewport_height;
1214
1215    let scale_x = 1.0;
1216    let scale_y = viewport_aspect_ratio; // scaling Y probably gives the best precision?
1217
1218    let view_projection_matrix = [
1219        [scale_x, 0.0, 0.0, 0.0],
1220        [0.0, scale_y, 0.0, 0.0],
1221        [0.0, 0.0, -1.0, 0.0],
1222        [0.0, 0.0, 0.0, 1.0],
1223    ];
1224
1225    view_projection_matrix.into()
1226}
1227
1228fn sort_render_items_by_z_and_material(items: &mut [RenderItem]) {
1229    items.sort_by_key(|item| (item.position.z, item.material_ref));
1230}
1231
1232#[derive(Debug, Clone, Copy, Default)]
1233pub enum Rotation {
1234    #[default]
1235    Degrees0,
1236    Degrees90,
1237    Degrees180,
1238    Degrees270,
1239}
1240
1241#[derive(Debug, Copy, Clone)]
1242pub struct SpriteParams {
1243    pub texture_size: UVec2,
1244    pub texture_pos: UVec2,
1245    pub scale: u8,
1246    pub rotation: Rotation,
1247    pub flip_x: bool,
1248    pub flip_y: bool,
1249    pub pivot: Vec2,
1250    pub color: Color,
1251}
1252
1253impl Default for SpriteParams {
1254    fn default() -> Self {
1255        Self {
1256            texture_size: UVec2::new(0, 0),
1257            texture_pos: UVec2::new(0, 0),
1258            pivot: Vec2::new(0, 0),
1259            flip_x: false,
1260            flip_y: false,
1261            color: Color::from_octet(255, 255, 255, 255),
1262            scale: 1,
1263            rotation: Rotation::Degrees0,
1264        }
1265    }
1266}
1267
1268#[derive(Debug, PartialEq, Eq, Asset)]
1269pub struct Material {
1270    pub texture_and_sampler_bind_group: BindGroup,
1271    pub texture_size: UVec2,
1272}
1273
1274impl PartialOrd<Self> for Material {
1275    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1276        Some(
1277            self.texture_and_sampler_bind_group
1278                .cmp(&other.texture_and_sampler_bind_group),
1279        )
1280    }
1281}
1282
1283impl Ord for Material {
1284    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1285        self.texture_and_sampler_bind_group
1286            .cmp(&other.texture_and_sampler_bind_group)
1287    }
1288}
1289
1290#[derive(Debug)]
1291pub struct Sprite {
1292    pub params: SpriteParams,
1293}
1294
1295#[derive(Debug)]
1296pub struct QuadColor {
1297    pub size: UVec2,
1298    pub color: Color,
1299}
1300
1301#[derive(Debug)]
1302pub struct NineSlice {
1303    pub corner_size: UVec2,
1304    pub texture_window_size: UVec2,
1305    pub size: UVec2,
1306    pub atlas_offset: UVec2,
1307    pub color: Color,
1308}
1309
1310#[derive(Debug)]
1311pub struct TileMap {
1312    pub tiles_data_grid_size: UVec2,
1313    pub cell_count_size: UVec2,
1314    pub one_cell_size: UVec2,
1315    pub tiles: Vec<u16>,
1316    pub scale: u8,
1317}
1318
1319pub type RenderPipelineRef = Arc<RenderPipeline>;
1320
1321const fn sources() -> (&'static str, &'static str) {
1322    let vertex_shader_source = "
1323// Bind Group 0: Uniforms (view-projection matrix)
1324struct Uniforms {
1325    view_proj: mat4x4<f32>,
1326};
1327
1328@group(0) @binding(0)
1329var<uniform> camera_uniforms: Uniforms;
1330
1331// Bind Group 1: Texture and Sampler (Unused in Vertex Shader but needed for consistency)
1332@group(1) @binding(0)
1333var diffuse_texture: texture_2d<f32>;
1334
1335@group(1) @binding(1)
1336var sampler_diffuse: sampler;
1337
1338// Vertex input structure
1339struct VertexInput {
1340    @location(0) position: vec3<f32>,
1341    @location(1) tex_coords: vec2<f32>,
1342    @builtin(instance_index) instance_idx: u32,
1343};
1344
1345// Vertex output structure to fragment shader
1346struct VertexOutput {
1347    @builtin(position) position: vec4<f32>,
1348    @location(0) tex_coords: vec2<f32>,
1349    @location(1) color: vec4<f32>,
1350    @location(2) use_texture: u32,
1351};
1352
1353// Vertex shader entry point
1354@vertex
1355fn vs_main(
1356    input: VertexInput,
1357    // Instance attributes
1358    @location(2) model_matrix0: vec4<f32>,
1359    @location(3) model_matrix1: vec4<f32>,
1360    @location(4) model_matrix2: vec4<f32>,
1361    @location(5) model_matrix3: vec4<f32>,
1362    @location(6) tex_multiplier: vec4<f32>,
1363    @location(7) rotation_step: u32,
1364    @location(8) color: vec4<f32>,
1365    @location(9) use_texture: u32,
1366) -> VertexOutput {
1367    var output: VertexOutput;
1368
1369    // Reconstruct the model matrix from the instance data
1370    let model_matrix = mat4x4<f32>(
1371        model_matrix0,
1372        model_matrix1,
1373        model_matrix2,
1374        model_matrix3,
1375    );
1376
1377    // Compute world position
1378    let world_position = model_matrix * vec4<f32>(input.position, 1.0);
1379
1380    // Apply view-projection matrix
1381    output.position = camera_uniforms.view_proj * world_position;
1382
1383    // Decode rotation_step
1384    let rotation_val = rotation_step & 3u; // Bits 0-1
1385    let flip_x = (rotation_step & 4u) != 0u; // Bit 2
1386    let flip_y = (rotation_step & 8u) != 0u; // Bit 3
1387
1388    // Rotate texture coordinates based on rotation_val
1389    var rotated_tex_coords = input.tex_coords;
1390    if (rotation_val == 1) {
1391        // 90 degrees rotation
1392        rotated_tex_coords = vec2<f32>(1.0 - input.tex_coords.y, input.tex_coords.x);
1393    } else if (rotation_val == 2) {
1394        // 180 degrees rotation
1395        rotated_tex_coords = vec2<f32>(1.0 - input.tex_coords.x, 1.0 - input.tex_coords.y);
1396    } else if (rotation_val == 3) {
1397        // 270 degrees rotation
1398        rotated_tex_coords = vec2<f32>(input.tex_coords.y, 1.0 - input.tex_coords.x);
1399    }
1400    // else rotation_val == Degrees0, no rotation
1401
1402    // Apply flipping
1403    if (flip_x) {
1404        rotated_tex_coords.x = 1.0 - rotated_tex_coords.x;
1405    }
1406    if (flip_y) {
1407        rotated_tex_coords.y = 1.0 - rotated_tex_coords.y;
1408    }
1409
1410    // Modify texture coordinates
1411    output.tex_coords = rotated_tex_coords * tex_multiplier.xy + tex_multiplier.zw;
1412    output.color = color;
1413    output.use_texture = use_texture;
1414
1415    return output;
1416}
1417        ";
1418    //
1419
1420    let fragment_shader_source = "
1421
1422// Bind Group 1: Texture and Sampler
1423@group(1) @binding(0)
1424var diffuse_texture: texture_2d<f32>;
1425
1426@group(1) @binding(1)
1427var sampler_diffuse: sampler;
1428
1429// Fragment input structure from vertex shader
1430struct VertexOutput {
1431    @builtin(position) position: vec4<f32>,
1432    @location(0) tex_coords: vec2<f32>,
1433    @location(1) color: vec4<f32>,
1434    @location(2) use_texture: u32,
1435};
1436
1437// Fragment shader entry point
1438@fragment
1439fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
1440    var final_color: vec4<f32>;
1441
1442    // Sample the texture using the texture coordinates
1443    let texture_color = textureSample(diffuse_texture, sampler_diffuse, input.tex_coords);
1444    if (input.use_texture == 1u) { // Check if use_texture is true (1)
1445        // Apply color modulation and opacity
1446        final_color = texture_color * input.color;
1447    } else {
1448        final_color = input.color;
1449    }
1450
1451    return final_color;
1452}
1453
1454";
1455    (vertex_shader_source, fragment_shader_source)
1456}