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    pub fn draw_nine_slice(
518        &mut self,
519        position: Vec3,
520        size: UVec2,
521        corner_size: UVec2,
522        texture_window_size: UVec2,
523        material_ref: &MaterialRef,
524        atlas_offset: UVec2,
525        color: Color,
526    ) {
527        self.items.push(RenderItem {
528            position,
529            material_ref: material_ref.into(),
530            renderable: Renderable::NineSlice(NineSlice {
531                corner_size,
532                texture_window_size,
533                size,
534                atlas_offset,
535                color,
536            }),
537        });
538    }
539
540    pub const fn clear_color(&self) -> wgpu::Color {
541        self.clear_color
542    }
543
544    // first two is multiplier and second pair is offset
545    fn calculate_texture_coords_mul_add(atlas_rect: URect, texture_size: UVec2) -> Vec4 {
546        let x = atlas_rect.position.x as f32 / texture_size.x as f32;
547        let y = atlas_rect.position.y as f32 / texture_size.y as f32;
548        let width = atlas_rect.size.x as f32 / texture_size.x as f32;
549        let height = atlas_rect.size.y as f32 / texture_size.y as f32;
550        Vec4([width, height, x, y])
551    }
552
553    fn order_render_items_in_batches(&self) -> Vec<Vec<&RenderItem>> {
554        let mut material_batches: Vec<Vec<&RenderItem>> = Vec::new();
555        let mut current_batch: Vec<&RenderItem> = Vec::new();
556        let mut current_material: Option<&WeakMaterialRef> = None;
557
558        for render_item in &self.items {
559            if Some(&render_item.material_ref) != current_material {
560                if !current_batch.is_empty() {
561                    material_batches.push(current_batch.clone());
562                    current_batch.clear();
563                }
564                current_material = Some(&render_item.material_ref);
565            }
566            current_batch.push(render_item);
567        }
568
569        if !current_batch.is_empty() {
570            material_batches.push(current_batch);
571        }
572
573        material_batches
574    }
575
576    pub fn quad_helper_uniform(
577        position: Vec3,
578        quad_size: UVec2,
579        render_atlas: URect,
580        color: Color,
581
582        current_texture_size: UVec2,
583        //material: &MaterialRef,
584    ) -> SpriteInstanceUniform {
585        let model_matrix = Matrix4::from_translation(position.x as f32, position.y as f32, 0.0)
586            * Matrix4::from_scale(quad_size.x as f32, quad_size.y as f32, 1.0);
587
588        let tex_coords_mul_add =
589            Self::calculate_texture_coords_mul_add(render_atlas, current_texture_size);
590
591        let rotation_value = 0;
592
593        SpriteInstanceUniform::new(
594            model_matrix,
595            tex_coords_mul_add,
596            rotation_value,
597            Vec4(color.to_f32_slice()),
598            true,
599        )
600    }
601
602    /// # Panics
603    ///
604    #[allow(clippy::too_many_lines)]
605    pub fn prepare_render(&mut self, materials: &Assets<Material>, fonts: &Assets<Font>) {
606        const FLIP_X_MASK: u32 = 0b0000_0100;
607        const FLIP_Y_MASK: u32 = 0b0000_1000;
608
609        sort_render_items_by_z_and_material(&mut self.items);
610
611        let batches = self.order_render_items_in_batches();
612
613        let mut quad_matrix_and_uv: Vec<SpriteInstanceUniform> = Vec::new();
614        let mut batch_vertex_ranges: Vec<(WeakMaterialRef, u32, u32)> = Vec::new();
615
616        for render_items in &batches {
617            let quad_len_before = quad_matrix_and_uv.len() as u32;
618
619            // Fix: Access material_ref through reference and copy it
620            let weak_material_ref = render_items
621                .first()
622                .map(|item| {
623                    // Force copy semantics by dereferencing the shared reference
624                    let material_ref: WeakId<Material> = item.material_ref;
625                    material_ref
626                })
627                .expect("Render items batch was empty");
628
629            let result = materials.get_weak(weak_material_ref);
630            if result.is_none() {
631                // Material is not loaded yet
632                continue;
633            }
634            let material = result.unwrap();
635            let current_texture_size = material.texture_size;
636
637            for render_item in render_items {
638                match &render_item.renderable {
639                    Renderable::Sprite(sprite) => {
640                        let params = &sprite.params;
641                        let mut size = params.texture_size;
642                        if size.x == 0 && size.y == 0 {
643                            size = current_texture_size;
644                        }
645
646                        let render_atlas = URect {
647                            position: params.texture_pos,
648                            size,
649                        };
650
651                        match params.rotation {
652                            Rotation::Degrees90 | Rotation::Degrees270 => {
653                                swap(&mut size.x, &mut size.y);
654                            }
655                            _ => {}
656                        }
657
658                        let model_matrix = Matrix4::from_translation(
659                            render_item.position.x as f32,
660                            render_item.position.y as f32,
661                            0.0,
662                        ) * Matrix4::from_scale(
663                            (size.x * params.scale as u16) as f32,
664                            (size.y * params.scale as u16) as f32,
665                            1.0,
666                        );
667
668                        let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
669                            render_atlas,
670                            current_texture_size,
671                        );
672
673                        let mut rotation_value = match params.rotation {
674                            Rotation::Degrees0 => 0,
675                            Rotation::Degrees90 => 1,
676                            Rotation::Degrees180 => 2,
677                            Rotation::Degrees270 => 3,
678                        };
679
680                        if params.flip_x {
681                            rotation_value |= FLIP_X_MASK;
682                        }
683                        if params.flip_y {
684                            rotation_value |= FLIP_Y_MASK;
685                        }
686
687                        let quad_instance = SpriteInstanceUniform::new(
688                            model_matrix,
689                            tex_coords_mul_add,
690                            rotation_value,
691                            Vec4(params.color.to_f32_slice()),
692                            true,
693                        );
694                        quad_matrix_and_uv.push(quad_instance);
695                    }
696
697                    Renderable::NineSlice(nine_slice) => {
698                        Self::prepare_nine_slice(
699                            nine_slice,
700                            render_item.position,
701                            &mut quad_matrix_and_uv,
702                            current_texture_size,
703                        );
704                    }
705
706                    Renderable::QuadColor(quad) => {
707                        /*
708                        let params = &sprite.params;
709                        let mut size = params.texture_size;
710                        if size.x == 0 && size.y == 0 {
711                            size = current_texture_size;
712                        }
713
714                        let render_atlas = URect {
715                            position: params.texture_pos,
716                            size,
717                        };
718
719
720                        match params.rotation {
721                            Rotation::Degrees90 | Rotation::Degrees270 => {
722                                swap(&mut size.x, &mut size.y);
723                            }
724                            _ => {}
725                        }
726                         */
727
728                        let model_matrix =
729                            Matrix4::from_translation(
730                                render_item.position.x as f32,
731                                render_item.position.y as f32,
732                                0.0,
733                            ) * Matrix4::from_scale(quad.size.x as f32, quad.size.y as f32, 1.0);
734
735                        /*
736                        let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
737                            render_atlas,
738                            current_texture_size,
739                        );
740
741
742                        let mut rotation_value = match params.rotation {
743                            Rotation::Degrees0 => 0,
744                            Rotation::Degrees90 => 1,
745                            Rotation::Degrees180 => 2,
746                            Rotation::Degrees270 => 3,
747                        };
748
749                        if params.flip_x {
750                            rotation_value |= FLIP_X_MASK;
751                        }
752                        if params.flip_y {
753                            rotation_value |= FLIP_Y_MASK;
754                        }
755
756                         */
757
758                        let tex_coords_mul_add = Vec4([
759                            0.0, //x
760                            0.0, //y
761                            0.0, 0.0,
762                        ]);
763                        let rotation_value = 0;
764
765                        let quad_instance = SpriteInstanceUniform::new(
766                            model_matrix,
767                            tex_coords_mul_add,
768                            rotation_value,
769                            Vec4(quad.color.to_f32_slice()),
770                            false,
771                        );
772                        quad_matrix_and_uv.push(quad_instance);
773                    }
774
775                    Renderable::Text(text) => {
776                        let result = fonts.get_weak(text.font_ref);
777                        if result.is_none() {
778                            continue;
779                        }
780                        let font = result.unwrap();
781
782                        let glyphs = font.draw(&text.text);
783                        for glyph in glyphs {
784                            let pos = render_item.position + Vec3::from(glyph.relative_position);
785                            let texture_size = glyph.texture_rectangle.size;
786                            let model_matrix =
787                                Matrix4::from_translation(pos.x as f32, pos.y as f32, 0.0)
788                                    * Matrix4::from_scale(
789                                        texture_size.x as f32,
790                                        texture_size.y as f32,
791                                        1.0,
792                                    );
793                            let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
794                                glyph.texture_rectangle,
795                                current_texture_size,
796                            );
797
798                            let quad_instance = SpriteInstanceUniform::new(
799                                model_matrix,
800                                tex_coords_mul_add,
801                                0,
802                                Vec4(text.color.to_f32_slice()),
803                                true,
804                            );
805                            quad_matrix_and_uv.push(quad_instance);
806                        }
807                    }
808
809                    Renderable::TileMap(tile_map) => {
810                        for (index, tile) in tile_map.tiles.iter().enumerate() {
811                            let cell_pos_x = (index as u16 % tile_map.tiles_data_grid_size.x)
812                                * tile_map.one_cell_size.x
813                                * tile_map.scale as u16;
814                            let cell_pos_y = (index as u16 / tile_map.tiles_data_grid_size.x)
815                                * tile_map.one_cell_size.y
816                                * tile_map.scale as u16;
817                            let cell_x = *tile % tile_map.cell_count_size.x;
818                            let cell_y = *tile / tile_map.cell_count_size.x;
819
820                            let tex_x = cell_x * tile_map.one_cell_size.x;
821                            let tex_y = cell_y * tile_map.one_cell_size.x;
822
823                            let cell_texture_area = URect::new(
824                                tex_x,
825                                tex_y,
826                                tile_map.one_cell_size.x,
827                                tile_map.one_cell_size.y,
828                            );
829
830                            let cell_model_matrix = Matrix4::from_translation(
831                                (render_item.position.x + cell_pos_x as i16) as f32,
832                                (render_item.position.y + cell_pos_y as i16) as f32,
833                                0.0,
834                            ) * Matrix4::from_scale(
835                                (tile_map.one_cell_size.x * tile_map.scale as u16) as f32,
836                                (tile_map.one_cell_size.y * tile_map.scale as u16) as f32,
837                                1.0,
838                            );
839
840                            let cell_tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
841                                cell_texture_area,
842                                current_texture_size,
843                            );
844
845                            let quad_instance = SpriteInstanceUniform::new(
846                                cell_model_matrix,
847                                cell_tex_coords_mul_add,
848                                0,
849                                Vec4([1.0, 1.0, 1.0, 1.0]),
850                                true,
851                            );
852                            quad_matrix_and_uv.push(quad_instance);
853                        }
854                    }
855                }
856            }
857
858            let quad_count = quad_matrix_and_uv.len() as u32 - quad_len_before;
859            batch_vertex_ranges.push((weak_material_ref, quad_len_before, quad_count));
860        }
861
862        // write all model_matrix and uv_coords to instance buffer once, before the render pass
863        self.queue.write_buffer(
864            &self.quad_matrix_and_uv_instance_buffer,
865            0,
866            bytemuck::cast_slice(&quad_matrix_and_uv),
867        );
868
869        self.batch_offsets = batch_vertex_ranges;
870    }
871
872    #[inline]
873    pub fn prepare_nine_slice(
874        nine_slice: &NineSlice,
875        position_offset: Vec3,
876        quad_matrix_and_uv: &mut Vec<SpriteInstanceUniform>,
877        current_texture_size: UVec2,
878    ) {
879        let corner_size = nine_slice.corner_size;
880        let corner_height = corner_size.y;
881        let corner_width = corner_size.x;
882        let color = nine_slice.color;
883        let outer_size = nine_slice.size - corner_size * 2;
884        let side_width = outer_size.x;
885        let side_height = outer_size.y;
886        let window_size = nine_slice.size;
887        let texture_window_size = nine_slice.texture_window_size;
888
889        let atlas_origin = nine_slice.atlas_offset;
890
891        // Lower left
892        let lower_left_pos = Vec3::new(position_offset.x, position_offset.y, 0);
893        let lower_left_quad_size = UVec2::new(corner_width, corner_height);
894        let lower_left_atlas =
895            URect::new(atlas_origin.x, atlas_origin.y, corner_size.x, corner_size.y);
896        let lower_left_quad = Self::quad_helper_uniform(
897            lower_left_pos,
898            lower_left_quad_size,
899            lower_left_atlas,
900            color,
901            current_texture_size,
902        );
903        quad_matrix_and_uv.push(lower_left_quad);
904
905        // Lower side
906        let lower_side_position = Vec3::new(
907            position_offset.x + corner_width as i16,
908            position_offset.y,
909            0,
910        );
911        let lower_side_quad_size = UVec2::new(side_width, corner_height);
912        let lower_side_atlas = URect::new(
913            atlas_origin.x + corner_width,
914            atlas_origin.y,
915            texture_window_size.x - corner_width * 2,
916            corner_size.y,
917        );
918        let lower_side_quad = Self::quad_helper_uniform(
919            lower_side_position,
920            lower_side_quad_size,
921            lower_side_atlas,
922            color,
923            current_texture_size,
924        );
925        quad_matrix_and_uv.push(lower_side_quad);
926
927        // Lower right corner
928        let lower_side_position = Vec3::new(
929            position_offset.x + window_size.x as i16 - corner_width as i16,
930            position_offset.y,
931            0,
932        );
933        let lower_right_quad_size = UVec2::new(corner_width, corner_height);
934        let lower_right_atlas = URect::new(
935            atlas_origin.x + corner_width * 2,
936            atlas_origin.y,
937            corner_size.x,
938            corner_size.y,
939        );
940        let lower_right_quad = Self::quad_helper_uniform(
941            lower_side_position,
942            lower_right_quad_size,
943            lower_right_atlas,
944            color,
945            current_texture_size,
946        );
947        quad_matrix_and_uv.push(lower_right_quad);
948
949        // Left side
950        let left_side_pos = Vec3::new(
951            position_offset.x,
952            position_offset.y + corner_height as i16,
953            0,
954        );
955        let left_side_quad_size = UVec2::new(corner_width, side_height);
956        let left_side_atlas = URect::new(
957            atlas_origin.x,
958            atlas_origin.y - corner_height,
959            corner_size.x,
960            corner_size.y,
961        );
962        let left_side_quad = Self::quad_helper_uniform(
963            left_side_pos,
964            left_side_quad_size,
965            left_side_atlas,
966            color,
967            current_texture_size,
968        );
969        quad_matrix_and_uv.push(left_side_quad);
970
971        // Middle
972        let middle_pos = Vec3::new(
973            position_offset.x + corner_width as i16,
974            position_offset.y + corner_height as i16,
975            0,
976        );
977        let middle_quad_size = UVec2::new(
978            nine_slice.size.x - corner_width * 2,
979            nine_slice.size.y - corner_height * 2,
980        );
981        let middle_atlas = URect::new(
982            atlas_origin.x + corner_width,
983            atlas_origin.y - corner_height,
984            texture_window_size.x - corner_width * 2,
985            texture_window_size.y - corner_height * 2,
986        );
987        let middle_quad = Self::quad_helper_uniform(
988            middle_pos,
989            middle_quad_size,
990            middle_atlas,
991            color,
992            current_texture_size,
993        );
994        quad_matrix_and_uv.push(middle_quad);
995
996        // RightSide
997        let right_side_pos = Vec3::new(
998            position_offset.x + window_size.x as i16 - corner_width as i16,
999            position_offset.y + corner_height as i16,
1000            0,
1001        );
1002        let right_side_quad_size = UVec2::new(corner_width, window_size.y - corner_height * 2);
1003        let right_side_atlas = URect::new(
1004            atlas_origin.x + corner_width * 2,
1005            atlas_origin.y - corner_height,
1006            corner_size.x,
1007            corner_size.y,
1008        );
1009        let right_side_quad = Self::quad_helper_uniform(
1010            right_side_pos,
1011            right_side_quad_size,
1012            right_side_atlas,
1013            color,
1014            current_texture_size,
1015        );
1016        quad_matrix_and_uv.push(right_side_quad);
1017
1018        // -----------------------
1019
1020        let upper_y = position_offset.y + window_size.y as i16 - corner_height as i16;
1021        let atlas_upper_y = atlas_origin.y - corner_height * 2;
1022        // Upper left
1023        let upper_left_pos = Vec3::new(position_offset.x, upper_y, 0);
1024        let upper_left_quad_size = UVec2::new(corner_width, corner_height);
1025        let upper_left_atlas =
1026            URect::new(atlas_origin.x, atlas_upper_y, corner_size.x, corner_size.y);
1027        let upper_left_quad = Self::quad_helper_uniform(
1028            upper_left_pos,
1029            upper_left_quad_size,
1030            upper_left_atlas,
1031            color,
1032            current_texture_size,
1033        );
1034        quad_matrix_and_uv.push(upper_left_quad);
1035
1036        // Upper side
1037        let upper_side_position = Vec3::new(position_offset.x + corner_width as i16, upper_y, 0);
1038        let upper_side_quad_size = UVec2::new(side_width, corner_height);
1039        let upper_side_atlas = URect::new(
1040            atlas_origin.x + corner_width,
1041            atlas_upper_y,
1042            texture_window_size.x - corner_width * 2,
1043            corner_size.y,
1044        );
1045        let upper_side_quad = Self::quad_helper_uniform(
1046            upper_side_position,
1047            upper_side_quad_size,
1048            upper_side_atlas,
1049            color,
1050            current_texture_size,
1051        );
1052        quad_matrix_and_uv.push(upper_side_quad);
1053
1054        // Upper right corner
1055        let upper_side_position = Vec3::new(
1056            position_offset.x + window_size.x as i16 - corner_width as i16,
1057            upper_y,
1058            0,
1059        );
1060        let upper_right_quad_size = UVec2::new(corner_width, corner_height);
1061        let upper_right_atlas = URect::new(
1062            atlas_origin.x + corner_width * 2,
1063            atlas_upper_y,
1064            corner_size.x,
1065            corner_size.y,
1066        );
1067        let upper_right_quad = Self::quad_helper_uniform(
1068            upper_side_position,
1069            upper_right_quad_size,
1070            upper_right_atlas,
1071            color,
1072            current_texture_size,
1073        );
1074        quad_matrix_and_uv.push(upper_right_quad);
1075    }
1076
1077    /// # Panics
1078    ///
1079    pub fn render(
1080        &mut self,
1081        render_pass: &mut RenderPass,
1082        materials: &Assets<Material>,
1083        fonts: &Assets<Font>,
1084        now: Millis,
1085    ) {
1086        trace!("start render()");
1087        self.last_render_at = now;
1088
1089        self.viewport = match self.viewport_strategy {
1090            ViewportStrategy::FitIntegerScaling(virtual_surface_size) => {
1091                Self::viewport_from_integer_scale(self.physical_surface_size, virtual_surface_size)
1092            }
1093            ViewportStrategy::FitFloatScaling(virtual_surface_size) => {
1094                Self::viewport_from_float_scale(self.physical_surface_size, virtual_surface_size)
1095            }
1096            ViewportStrategy::MatchPhysicalSize => URect::new(
1097                0,
1098                0,
1099                self.physical_surface_size.x,
1100                self.physical_surface_size.y,
1101            ),
1102        };
1103
1104        let view_proj_matrix = match self.viewport_strategy {
1105            ViewportStrategy::MatchPhysicalSize => {
1106                create_view_uniform_view_projection_matrix(self.physical_surface_size)
1107            }
1108            ViewportStrategy::FitFloatScaling(virtual_surface_size)
1109            | ViewportStrategy::FitIntegerScaling(virtual_surface_size) => {
1110                create_view_projection_matrix_from_virtual(
1111                    virtual_surface_size.x,
1112                    virtual_surface_size.y,
1113                )
1114            }
1115        };
1116
1117        let scale_matrix = Matrix4::from_scale(self.scale, self.scale, 0.0);
1118        let origin_translation_matrix =
1119            Matrix4::from_translation(-self.origin.x as f32, -self.origin.y as f32, 0.0);
1120
1121        let total_matrix = scale_matrix * view_proj_matrix * origin_translation_matrix;
1122
1123        // write all model_matrix and uv_coords to instance buffer once, before the render pass
1124        self.queue.write_buffer(
1125            &self.camera_buffer,
1126            0,
1127            bytemuck::cast_slice(&[total_matrix]),
1128        );
1129
1130        self.prepare_render(materials, fonts);
1131
1132        render_pass.set_viewport(
1133            self.viewport.position.x as f32,
1134            self.viewport.position.y as f32,
1135            self.viewport.size.x as f32,
1136            self.viewport.size.y as f32,
1137            0.0,
1138            1.0,
1139        );
1140
1141        render_pass.set_pipeline(&self.pipeline);
1142
1143        // Index and vertex buffers never change
1144        render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
1145        render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1146
1147        // Vertex buffer is reused
1148        render_pass.set_vertex_buffer(1, self.quad_matrix_and_uv_instance_buffer.slice(..));
1149
1150        // Camera is the same for everything
1151        render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
1152
1153        let num_indices = swamp_wgpu_sprites::INDICES.len() as u32;
1154
1155        for &(weak_material_ref, start, count) in &self.batch_offsets {
1156            let wgpu_material = materials
1157                .get_weak(weak_material_ref)
1158                .expect("no such material");
1159
1160            // Bind the texture and sampler bind group (Bind Group 1)
1161            render_pass.set_bind_group(1, &wgpu_material.texture_and_sampler_bind_group, &[]);
1162
1163            // Issue the instanced draw call for the batch
1164            trace!(material=%weak_material_ref, start=%start, count=%count, "draw instanced");
1165            render_pass.draw_indexed(0..num_indices, 0, start..(start + count));
1166        }
1167
1168        self.items.clear();
1169    }
1170
1171    pub fn material_from_texture(&self, texture: wgpu::Texture, label: &str) -> Material {
1172        trace!("load texture from memory with name: '{label}'");
1173        let size = &texture.size();
1174        let texture_and_sampler_bind_group =
1175            swamp_wgpu_sprites::create_sprite_texture_and_sampler_bind_group(
1176                &self.device,
1177                &self.texture_sampler_bind_group_layout,
1178                &texture,
1179                &self.sampler,
1180                label,
1181            );
1182
1183        let texture_size = UVec2::new(size.width as u16, size.height as u16);
1184
1185        Material {
1186            texture_and_sampler_bind_group,
1187            //render_pipeline: Arc::clone(&self.pipeline),
1188            texture_size,
1189        }
1190    }
1191}
1192
1193//fn set_view_projection(&mut self) {}
1194
1195fn create_view_projection_matrix_from_virtual(virtual_width: u16, virtual_height: u16) -> Matrix4 {
1196    OrthoInfo {
1197        left: 0.0,
1198        right: virtual_width as f32,
1199        bottom: 0.0,
1200        top: virtual_height as f32,
1201        near: 1.0,
1202        far: -1.0,
1203    }
1204    .into()
1205}
1206
1207fn create_view_uniform_view_projection_matrix(viewport_size: UVec2) -> Matrix4 {
1208    let viewport_width = viewport_size.x as f32;
1209    let viewport_height = viewport_size.y as f32;
1210
1211    let viewport_aspect_ratio = viewport_width / viewport_height;
1212
1213    let scale_x = 1.0;
1214    let scale_y = viewport_aspect_ratio; // scaling Y probably gives the best precision?
1215
1216    let view_projection_matrix = [
1217        [scale_x, 0.0, 0.0, 0.0],
1218        [0.0, scale_y, 0.0, 0.0],
1219        [0.0, 0.0, -1.0, 0.0],
1220        [0.0, 0.0, 0.0, 1.0],
1221    ];
1222
1223    view_projection_matrix.into()
1224}
1225
1226fn sort_render_items_by_z_and_material(items: &mut [RenderItem]) {
1227    items.sort_by_key(|item| (item.position.z, item.material_ref));
1228}
1229
1230#[derive(Debug, Clone, Copy, Default)]
1231pub enum Rotation {
1232    #[default]
1233    Degrees0,
1234    Degrees90,
1235    Degrees180,
1236    Degrees270,
1237}
1238
1239#[derive(Debug, Copy, Clone)]
1240pub struct SpriteParams {
1241    pub texture_size: UVec2,
1242    pub texture_pos: UVec2,
1243    pub scale: u8,
1244    pub rotation: Rotation,
1245    pub flip_x: bool,
1246    pub flip_y: bool,
1247    pub pivot: Vec2,
1248    pub color: Color,
1249}
1250
1251impl Default for SpriteParams {
1252    fn default() -> Self {
1253        Self {
1254            texture_size: UVec2::new(0, 0),
1255            texture_pos: UVec2::new(0, 0),
1256            pivot: Vec2::new(0, 0),
1257            flip_x: false,
1258            flip_y: false,
1259            color: Color::from_octet(255, 255, 255, 255),
1260            scale: 1,
1261            rotation: Rotation::Degrees0,
1262        }
1263    }
1264}
1265
1266#[derive(Debug, PartialEq, Eq, Asset)]
1267pub struct Material {
1268    pub texture_and_sampler_bind_group: BindGroup,
1269    pub texture_size: UVec2,
1270}
1271
1272impl PartialOrd<Self> for Material {
1273    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1274        Some(
1275            self.texture_and_sampler_bind_group
1276                .cmp(&other.texture_and_sampler_bind_group),
1277        )
1278    }
1279}
1280
1281impl Ord for Material {
1282    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1283        self.texture_and_sampler_bind_group
1284            .cmp(&other.texture_and_sampler_bind_group)
1285    }
1286}
1287
1288#[derive(Debug)]
1289pub struct Sprite {
1290    pub params: SpriteParams,
1291}
1292
1293#[derive(Debug)]
1294pub struct QuadColor {
1295    pub size: UVec2,
1296    pub color: Color,
1297}
1298
1299#[derive(Debug)]
1300pub struct NineSlice {
1301    pub corner_size: UVec2,
1302    pub texture_window_size: UVec2,
1303    pub size: UVec2,
1304    pub atlas_offset: UVec2,
1305    pub color: Color,
1306}
1307
1308#[derive(Debug)]
1309pub struct TileMap {
1310    pub tiles_data_grid_size: UVec2,
1311    pub cell_count_size: UVec2,
1312    pub one_cell_size: UVec2,
1313    pub tiles: Vec<u16>,
1314    pub scale: u8,
1315}
1316
1317pub type RenderPipelineRef = Arc<RenderPipeline>;
1318
1319const fn sources() -> (&'static str, &'static str) {
1320    let vertex_shader_source = "
1321// Bind Group 0: Uniforms (view-projection matrix)
1322struct Uniforms {
1323    view_proj: mat4x4<f32>,
1324};
1325
1326@group(0) @binding(0)
1327var<uniform> camera_uniforms: Uniforms;
1328
1329// Bind Group 1: Texture and Sampler (Unused in Vertex Shader but needed for consistency)
1330@group(1) @binding(0)
1331var diffuse_texture: texture_2d<f32>;
1332
1333@group(1) @binding(1)
1334var sampler_diffuse: sampler;
1335
1336// Vertex input structure
1337struct VertexInput {
1338    @location(0) position: vec3<f32>,
1339    @location(1) tex_coords: vec2<f32>,
1340    @builtin(instance_index) instance_idx: u32,
1341};
1342
1343// Vertex output structure to fragment shader
1344struct VertexOutput {
1345    @builtin(position) position: vec4<f32>,
1346    @location(0) tex_coords: vec2<f32>,
1347    @location(1) color: vec4<f32>,
1348    @location(2) use_texture: u32,
1349};
1350
1351// Vertex shader entry point
1352@vertex
1353fn vs_main(
1354    input: VertexInput,
1355    // Instance attributes
1356    @location(2) model_matrix0: vec4<f32>,
1357    @location(3) model_matrix1: vec4<f32>,
1358    @location(4) model_matrix2: vec4<f32>,
1359    @location(5) model_matrix3: vec4<f32>,
1360    @location(6) tex_multiplier: vec4<f32>,
1361    @location(7) rotation_step: u32,
1362    @location(8) color: vec4<f32>,
1363    @location(9) use_texture: u32,
1364) -> VertexOutput {
1365    var output: VertexOutput;
1366
1367    // Reconstruct the model matrix from the instance data
1368    let model_matrix = mat4x4<f32>(
1369        model_matrix0,
1370        model_matrix1,
1371        model_matrix2,
1372        model_matrix3,
1373    );
1374
1375    // Compute world position
1376    let world_position = model_matrix * vec4<f32>(input.position, 1.0);
1377
1378    // Apply view-projection matrix
1379    output.position = camera_uniforms.view_proj * world_position;
1380
1381    // Decode rotation_step
1382    let rotation_val = rotation_step & 3u; // Bits 0-1
1383    let flip_x = (rotation_step & 4u) != 0u; // Bit 2
1384    let flip_y = (rotation_step & 8u) != 0u; // Bit 3
1385
1386    // Rotate texture coordinates based on rotation_val
1387    var rotated_tex_coords = input.tex_coords;
1388    if (rotation_val == 1) {
1389        // 90 degrees rotation
1390        rotated_tex_coords = vec2<f32>(1.0 - input.tex_coords.y, input.tex_coords.x);
1391    } else if (rotation_val == 2) {
1392        // 180 degrees rotation
1393        rotated_tex_coords = vec2<f32>(1.0 - input.tex_coords.x, 1.0 - input.tex_coords.y);
1394    } else if (rotation_val == 3) {
1395        // 270 degrees rotation
1396        rotated_tex_coords = vec2<f32>(input.tex_coords.y, 1.0 - input.tex_coords.x);
1397    }
1398    // else rotation_val == Degrees0, no rotation
1399
1400    // Apply flipping
1401    if (flip_x) {
1402        rotated_tex_coords.x = 1.0 - rotated_tex_coords.x;
1403    }
1404    if (flip_y) {
1405        rotated_tex_coords.y = 1.0 - rotated_tex_coords.y;
1406    }
1407
1408    // Modify texture coordinates
1409    output.tex_coords = rotated_tex_coords * tex_multiplier.xy + tex_multiplier.zw;
1410    output.color = color;
1411    output.use_texture = use_texture;
1412
1413    return output;
1414}
1415        ";
1416    //
1417
1418    let fragment_shader_source = "
1419
1420// Bind Group 1: Texture and Sampler
1421@group(1) @binding(0)
1422var diffuse_texture: texture_2d<f32>;
1423
1424@group(1) @binding(1)
1425var sampler_diffuse: sampler;
1426
1427// Fragment input structure from vertex shader
1428struct VertexOutput {
1429    @builtin(position) position: vec4<f32>,
1430    @location(0) tex_coords: vec2<f32>,
1431    @location(1) color: vec4<f32>,
1432    @location(2) use_texture: u32,
1433};
1434
1435// Fragment shader entry point
1436@fragment
1437fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
1438    var final_color: vec4<f32>;
1439
1440    // Sample the texture using the texture coordinates
1441    let texture_color = textureSample(diffuse_texture, sampler_diffuse, input.tex_coords);
1442    if (input.use_texture == 1u) { // Check if use_texture is true (1)
1443        // Apply color modulation and opacity
1444        final_color = texture_color * input.color;
1445    } else {
1446        final_color = input.color;
1447    }
1448
1449    return final_color;
1450}
1451
1452";
1453    (vertex_shader_source, fragment_shader_source)
1454}