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::prelude::{Asset, Id, WeakId};
10use limnus_assets::Assets;
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)]
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    TileMap(TileMap),
151    Text(Text),
152}
153
154#[derive(Resource)]
155pub struct Render {
156    index_buffer: Buffer,  // Only indicies for a single identity quad
157    vertex_buffer: Buffer, // Only one identity quad (0,0,1,1)
158    sampler: wgpu::Sampler,
159    pipeline: RenderPipelineRef,
160    physical_surface_size: UVec2,
161    viewport_strategy: ViewportStrategy,
162    // Group 0
163    camera_bind_group: BindGroup,
164    #[allow(unused)]
165    camera_buffer: Buffer,
166
167    // Group 1
168    texture_sampler_bind_group_layout: BindGroupLayout,
169
170    // Group 1
171    quad_matrix_and_uv_instance_buffer: Buffer,
172
173    device: Arc<wgpu::Device>,
174    queue: Arc<wgpu::Queue>, // Queue to talk to device
175
176    // Internals
177    items: Vec<RenderItem>,
178    //fonts: Vec<FontAndMaterialRef>,
179    origin: Vec2,
180
181    // Cache
182    batch_offsets: Vec<(WeakMaterialRef, u32, u32)>,
183    viewport: URect,
184    clear_color: wgpu::Color,
185    last_render_at: Millis,
186    scale: f32,
187}
188
189impl Debug for Render {
190    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191        write!(f, "Render")
192    }
193}
194
195impl Gfx for Render {
196    fn sprite_atlas_frame(&mut self, position: Vec3, frame: u16, atlas: &impl FrameLookup) {
197        self.sprite_atlas_frame(position, frame, atlas);
198    }
199
200    fn sprite_atlas(&mut self, position: Vec3, atlas_rect: URect, material_ref: &MaterialRef) {
201        self.sprite_atlas(position, atlas_rect, material_ref);
202    }
203
204    fn draw_sprite(&mut self, position: Vec3, material_ref: &MaterialRef) {
205        self.draw_sprite(position, material_ref);
206    }
207
208    fn draw_sprite_ex(
209        &mut self,
210        position: Vec3,
211        material_ref: &MaterialRef,
212        params: &SpriteParams,
213    ) {
214        self.draw_sprite_ex(position, material_ref, *params);
215    }
216
217    fn set_origin(&mut self, position: Vec2) {
218        self.origin = position;
219    }
220
221    fn set_clear_color(&mut self, color: Color) {
222        self.clear_color = to_wgpu_color(color);
223    }
224
225    fn tilemap_params(
226        &mut self,
227        position: Vec3,
228        tiles: &[u16],
229        width: u16,
230        atlas_ref: &FixedAtlas,
231        scale: u8,
232    ) {
233        self.items.push(RenderItem {
234            position,
235            material_ref: (&atlas_ref.material).into(),
236            renderable: Renderable::TileMap(TileMap {
237                tiles_data_grid_size: UVec2::new(width, tiles.len() as u16 / width),
238                cell_count_size: atlas_ref.cell_count_size,
239                one_cell_size: atlas_ref.one_cell_size,
240                tiles: Vec::from(tiles),
241                scale,
242            }),
243        });
244    }
245
246    fn text_draw(
247        &mut self,
248        position: Vec3,
249        text: &str,
250        font_and_mat: &FontAndMaterial,
251        color: &Color,
252    ) {
253        self.items.push(RenderItem {
254            position,
255            material_ref: (&font_and_mat.material_ref).into(),
256            renderable: Renderable::Text(Text {
257                text: text.to_string(),
258                font_ref: (&font_and_mat.font_ref).into(),
259                color: *color,
260            }),
261        });
262    }
263
264    fn now(&self) -> Millis {
265        self.last_render_at
266    }
267
268    fn physical_aspect_ratio(&self) -> AspectRatio {
269        self.physical_surface_size.into()
270    }
271
272    fn physical_size(&self) -> UVec2 {
273        self.physical_surface_size
274    }
275
276    fn set_viewport(&mut self, viewport_strategy: ViewportStrategy) {
277        self.viewport_strategy = viewport_strategy;
278    }
279
280    fn viewport(&self) -> &ViewportStrategy {
281        &self.viewport_strategy
282    }
283
284    fn set_scale(&mut self, scale_factor: VirtualScale) {
285        match scale_factor {
286            VirtualScale::IntScale(scale) => self.scale = scale as f32,
287            VirtualScale::FloatScale(scale) => self.scale = scale,
288        }
289    }
290}
291
292impl Render {
293    #[must_use]
294    pub fn new(
295        device: Arc<wgpu::Device>,
296        queue: Arc<wgpu::Queue>, // Queue to talk to device
297        surface_texture_format: wgpu::TextureFormat,
298        physical_size: UVec2,
299        virtual_surface_size: UVec2,
300        now: Millis,
301    ) -> Self {
302        let (vertex_shader_source, fragment_shader_source) = sources();
303
304        let sprite_info = SpriteInfo::new(
305            &device,
306            surface_texture_format,
307            vertex_shader_source,
308            fragment_shader_source,
309            create_view_uniform_view_projection_matrix(physical_size),
310        );
311
312        Self {
313            device,
314            queue,
315            items: Vec::new(),
316            //   fonts: Vec::new(),
317            sampler: sprite_info.sampler,
318            pipeline: Arc::new(sprite_info.sprite_pipeline),
319            texture_sampler_bind_group_layout: sprite_info.sprite_texture_sampler_bind_group_layout,
320            index_buffer: sprite_info.index_buffer,
321            vertex_buffer: sprite_info.vertex_buffer,
322            quad_matrix_and_uv_instance_buffer: sprite_info.quad_matrix_and_uv_instance_buffer,
323            camera_bind_group: sprite_info.camera_bind_group,
324            batch_offsets: Vec::new(),
325            camera_buffer: sprite_info.camera_uniform_buffer,
326            viewport: Self::viewport_from_integer_scale(physical_size, virtual_surface_size),
327            clear_color: to_wgpu_color(Color::from_f32(0.008, 0.015, 0.008, 1.0)),
328            origin: Vec2::new(0, 0),
329            last_render_at: now,
330            physical_surface_size: physical_size,
331            viewport_strategy: ViewportStrategy::FitIntegerScaling(virtual_surface_size),
332            scale: 1.0,
333        }
334    }
335
336    pub fn set_now(&mut self, now: Millis) {
337        self.last_render_at = now;
338    }
339
340    pub const fn virtual_surface_size(&self) -> UVec2 {
341        match self.viewport_strategy {
342            ViewportStrategy::FitIntegerScaling(virtual_size)
343            | ViewportStrategy::FitFloatScaling(virtual_size) => virtual_size,
344            ViewportStrategy::MatchPhysicalSize => self.physical_surface_size,
345        }
346    }
347
348    pub const fn physical_surface_size(&self) -> UVec2 {
349        self.physical_surface_size
350    }
351
352    pub const fn viewport(&self) -> URect {
353        self.viewport
354    }
355
356    #[inline(always)]
357    fn push_sprite(&mut self, position: Vec3, material: &MaterialRef, sprite: Sprite) {
358        self.items.push(RenderItem {
359            position,
360            material_ref: material.into(),
361            renderable: Renderable::Sprite(sprite),
362        });
363    }
364
365    #[must_use]
366    pub fn viewport_from_integer_scale(physical_size: UVec2, virtual_size: UVec2) -> URect {
367        let window_aspect = physical_size.x as f32 / physical_size.y as f32;
368        let virtual_aspect = virtual_size.x as f32 / virtual_size.y as f32;
369
370        if physical_size.x < virtual_size.x || physical_size.y < virtual_size.y {
371            return URect::new(0, 0, physical_size.x, physical_size.y);
372        }
373
374        let mut integer_scale = if window_aspect > virtual_aspect {
375            physical_size.y / virtual_size.y
376        } else {
377            physical_size.x / virtual_size.x
378        };
379
380        if integer_scale < 1 {
381            integer_scale = 1;
382        }
383
384        let viewport_actual_size = UVec2::new(
385            virtual_size.x * integer_scale,
386            virtual_size.y * integer_scale,
387        );
388
389        let border_size = physical_size - viewport_actual_size;
390
391        let offset = border_size / 2;
392
393        URect::new(
394            offset.x,
395            offset.y,
396            viewport_actual_size.x,
397            viewport_actual_size.y,
398        )
399    }
400
401    #[must_use]
402    pub fn viewport_from_float_scale(physical_size: UVec2, virtual_size: UVec2) -> URect {
403        let window_aspect = physical_size.x as f32 / physical_size.y as f32;
404        let virtual_aspect = virtual_size.x as f32 / virtual_size.y as f32;
405
406        if physical_size.x < virtual_size.x || physical_size.y < virtual_size.y {
407            return URect::new(0, 0, physical_size.x, physical_size.y);
408        }
409
410        let mut float_scale = if window_aspect > virtual_aspect {
411            physical_size.y as f32 / virtual_size.y as f32
412        } else {
413            physical_size.x as f32 / virtual_size.x as f32
414        };
415
416        if float_scale < 0.01 {
417            float_scale = 0.01;
418        }
419
420        let viewport_actual_size = UVec2::new(
421            (virtual_size.x as f32 * float_scale) as u16,
422            (virtual_size.y as f32 * float_scale) as u16,
423        );
424
425        let border_size = physical_size - viewport_actual_size;
426
427        let offset = border_size / 2;
428
429        URect::new(
430            offset.x,
431            offset.y,
432            viewport_actual_size.x,
433            viewport_actual_size.y,
434        )
435    }
436
437    pub fn resize(&mut self, physical_size: UVec2) {
438        self.physical_surface_size = physical_size;
439    }
440
441    /*
442    pub fn render_sprite(&mut self, position: Vec3, material: &MaterialRef, params: SpriteParams) {
443        let atlas_rect = URect::new(0, 0, material.texture_size().x, material.texture_size().y);
444
445        self.push_sprite(position, material, Sprite { atlas_rect, params });
446    }*/
447
448    pub fn sprite_atlas(&mut self, position: Vec3, atlas_rect: URect, material_ref: &MaterialRef) {
449        self.push_sprite(
450            position,
451            material_ref,
452            Sprite {
453                params: SpriteParams {
454                    texture_pos: atlas_rect.position,
455                    texture_size: atlas_rect.size,
456                    ..Default::default()
457                },
458            },
459        );
460    }
461
462    pub fn sprite_atlas_frame(&mut self, position: Vec3, frame: u16, atlas: &impl FrameLookup) {
463        let (material_ref, atlas_rect) = atlas.lookup(frame);
464        self.push_sprite(
465            position,
466            material_ref,
467            Sprite {
468                params: SpriteParams {
469                    texture_pos: atlas_rect.position,
470                    texture_size: atlas_rect.size,
471                    ..Default::default()
472                },
473            },
474        );
475    }
476
477    pub fn sprite_atlas_frame_ex(
478        &mut self,
479        position: Vec3,
480        frame: u16,
481        atlas: &impl FrameLookup,
482        mut params: SpriteParams,
483    ) {
484        let (material_ref, atlas_rect) = atlas.lookup(frame);
485        params.texture_pos = atlas_rect.position;
486        params.texture_size = atlas_rect.size;
487        self.push_sprite(position, material_ref, Sprite { params });
488    }
489
490    pub fn draw_sprite(&mut self, position: Vec3, material: &MaterialRef) {
491        self.push_sprite(
492            position,
493            material,
494            Sprite {
495                params: SpriteParams::default(),
496            },
497        );
498    }
499
500    pub fn draw_sprite_ex(&mut self, position: Vec3, material: &MaterialRef, params: SpriteParams) {
501        self.push_sprite(position, material, Sprite { params });
502    }
503
504    pub const fn clear_color(&self) -> wgpu::Color {
505        self.clear_color
506    }
507
508    // first two is multiplier and second pair is offset
509    fn calculate_texture_coords_mul_add(atlas_rect: URect, texture_size: UVec2) -> Vec4 {
510        let x = atlas_rect.position.x as f32 / texture_size.x as f32;
511        let y = atlas_rect.position.y as f32 / texture_size.y as f32;
512        let width = atlas_rect.size.x as f32 / texture_size.x as f32;
513        let height = atlas_rect.size.y as f32 / texture_size.y as f32;
514        Vec4([width, height, x, y])
515    }
516
517    fn order_render_items_in_batches(&self) -> Vec<Vec<&RenderItem>> {
518        let mut material_batches: Vec<Vec<&RenderItem>> = Vec::new();
519        let mut current_batch: Vec<&RenderItem> = Vec::new();
520        let mut current_material: Option<&WeakMaterialRef> = None;
521
522        for render_item in &self.items {
523            if Some(&render_item.material_ref) != current_material {
524                if !current_batch.is_empty() {
525                    material_batches.push(current_batch.clone());
526                    current_batch.clear();
527                }
528                current_material = Some(&render_item.material_ref);
529            }
530            current_batch.push(render_item);
531        }
532
533        if !current_batch.is_empty() {
534            material_batches.push(current_batch);
535        }
536
537        material_batches
538    }
539
540    /// # Panics
541    ///
542    #[allow(clippy::too_many_lines)]
543    pub fn prepare_render(&mut self, materials: &Assets<Material>, fonts: &Assets<Font>) {
544        const FLIP_X_MASK: u32 = 0b0000_0100;
545        const FLIP_Y_MASK: u32 = 0b0000_1000;
546
547        sort_render_items_by_z_and_material(&mut self.items);
548
549        let batches = self.order_render_items_in_batches();
550
551        let mut quad_matrix_and_uv: Vec<SpriteInstanceUniform> = Vec::new();
552        let mut batch_vertex_ranges: Vec<(WeakMaterialRef, u32, u32)> = Vec::new();
553
554        for render_items in &batches {
555            let quad_len_before = quad_matrix_and_uv.len() as u32;
556
557            // Fix: Access material_ref through reference and copy it
558            let weak_material_ref = render_items
559                .first()
560                .map(|item| {
561                    // Force copy semantics by dereferencing the shared reference
562                    let material_ref: WeakId<Material> = item.material_ref;
563                    material_ref
564                })
565                .expect("Render items batch was empty");
566
567            let result = materials.get_weak(weak_material_ref);
568            if result.is_none() {
569                // Material is not loaded yet
570                continue;
571            }
572            let material = result.unwrap();
573            let current_texture_size = material.texture_size;
574
575            for render_item in render_items {
576                match &render_item.renderable {
577                    Renderable::Sprite(ref sprite) => {
578                        let params = &sprite.params;
579                        let mut size = params.texture_size;
580                        if size.x == 0 && size.y == 0 {
581                            size = current_texture_size;
582                        }
583
584                        let render_atlas = URect {
585                            position: params.texture_pos,
586                            size,
587                        };
588
589                        match params.rotation {
590                            Rotation::Degrees90 | Rotation::Degrees270 => {
591                                swap(&mut size.x, &mut size.y);
592                            }
593                            _ => {}
594                        }
595
596                        let model_matrix = Matrix4::from_translation(
597                            render_item.position.x as f32,
598                            render_item.position.y as f32,
599                            0.0,
600                        ) * Matrix4::from_scale(
601                            (size.x * params.scale as u16) as f32,
602                            (size.y * params.scale as u16) as f32,
603                            1.0,
604                        );
605
606                        let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
607                            render_atlas,
608                            current_texture_size,
609                        );
610
611                        let mut rotation_value = match params.rotation {
612                            Rotation::Degrees0 => 0,
613                            Rotation::Degrees90 => 1,
614                            Rotation::Degrees180 => 2,
615                            Rotation::Degrees270 => 3,
616                        };
617
618                        if params.flip_x {
619                            rotation_value |= FLIP_X_MASK;
620                        }
621                        if params.flip_y {
622                            rotation_value |= FLIP_Y_MASK;
623                        }
624
625                        let quad_instance = SpriteInstanceUniform::new(
626                            model_matrix,
627                            tex_coords_mul_add,
628                            rotation_value,
629                            Vec4(params.color.to_f32_slice()),
630                        );
631                        quad_matrix_and_uv.push(quad_instance);
632                    }
633
634                    Renderable::Text(text) => {
635                        let result = fonts.get_weak(text.font_ref);
636                        if result.is_none() {
637                            continue;
638                        }
639                        let font = result.unwrap();
640
641                        let glyphs = font.draw(&text.text);
642                        for glyph in glyphs {
643                            let pos = render_item.position + Vec3::from(glyph.relative_position);
644                            let texture_size = glyph.texture_rectangle.size;
645                            let model_matrix =
646                                Matrix4::from_translation(pos.x as f32, pos.y as f32, 0.0)
647                                    * Matrix4::from_scale(
648                                        texture_size.x as f32,
649                                        texture_size.y as f32,
650                                        1.0,
651                                    );
652                            let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
653                                glyph.texture_rectangle,
654                                current_texture_size,
655                            );
656
657                            let quad_instance = SpriteInstanceUniform::new(
658                                model_matrix,
659                                tex_coords_mul_add,
660                                0,
661                                Vec4(text.color.to_f32_slice()),
662                            );
663                            quad_matrix_and_uv.push(quad_instance);
664                        }
665                    }
666
667                    Renderable::TileMap(ref tile_map) => {
668                        for (index, tile) in tile_map.tiles.iter().enumerate() {
669                            let cell_pos_x = (index as u16 % tile_map.tiles_data_grid_size.x)
670                                * tile_map.one_cell_size.x
671                                * tile_map.scale as u16;
672                            let cell_pos_y = (index as u16 / tile_map.tiles_data_grid_size.x)
673                                * tile_map.one_cell_size.y
674                                * tile_map.scale as u16;
675                            let cell_x = *tile % tile_map.cell_count_size.x;
676                            let cell_y = *tile / tile_map.cell_count_size.x;
677
678                            let tex_x = cell_x * tile_map.one_cell_size.x;
679                            let tex_y = cell_y * tile_map.one_cell_size.x;
680
681                            let cell_texture_area = URect::new(
682                                tex_x,
683                                tex_y,
684                                tile_map.one_cell_size.x,
685                                tile_map.one_cell_size.y,
686                            );
687
688                            let cell_model_matrix = Matrix4::from_translation(
689                                (render_item.position.x + cell_pos_x as i16) as f32,
690                                (render_item.position.y + cell_pos_y as i16) as f32,
691                                0.0,
692                            ) * Matrix4::from_scale(
693                                (tile_map.one_cell_size.x * tile_map.scale as u16) as f32,
694                                (tile_map.one_cell_size.y * tile_map.scale as u16) as f32,
695                                1.0,
696                            );
697
698                            let cell_tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
699                                cell_texture_area,
700                                current_texture_size,
701                            );
702
703                            let quad_instance = SpriteInstanceUniform::new(
704                                cell_model_matrix,
705                                cell_tex_coords_mul_add,
706                                0,
707                                Vec4([1.0, 1.0, 1.0, 1.0]),
708                            );
709                            quad_matrix_and_uv.push(quad_instance);
710                        }
711                    }
712                }
713            }
714
715            let quad_count = quad_matrix_and_uv.len() as u32 - quad_len_before;
716            batch_vertex_ranges.push((weak_material_ref, quad_len_before, quad_count));
717        }
718
719        // write all model_matrix and uv_coords to instance buffer once, before the render pass
720        self.queue.write_buffer(
721            &self.quad_matrix_and_uv_instance_buffer,
722            0,
723            bytemuck::cast_slice(&quad_matrix_and_uv),
724        );
725
726        self.batch_offsets = batch_vertex_ranges;
727    }
728
729    /// # Panics
730    ///
731    pub fn render(
732        &mut self,
733        render_pass: &mut RenderPass,
734        materials: &Assets<Material>,
735        fonts: &Assets<Font>,
736        now: Millis,
737    ) {
738        trace!("start render()");
739        self.last_render_at = now;
740
741        self.viewport = match self.viewport_strategy {
742            ViewportStrategy::FitIntegerScaling(virtual_surface_size) => {
743                Self::viewport_from_integer_scale(self.physical_surface_size, virtual_surface_size)
744            }
745            ViewportStrategy::FitFloatScaling(virtual_surface_size) => {
746                Self::viewport_from_float_scale(self.physical_surface_size, virtual_surface_size)
747            }
748            ViewportStrategy::MatchPhysicalSize => URect::new(
749                0,
750                0,
751                self.physical_surface_size.x,
752                self.physical_surface_size.y,
753            ),
754        };
755
756        let view_proj_matrix = match self.viewport_strategy {
757            ViewportStrategy::MatchPhysicalSize => {
758                create_view_uniform_view_projection_matrix(self.physical_surface_size)
759            }
760            ViewportStrategy::FitFloatScaling(virtual_surface_size)
761            | ViewportStrategy::FitIntegerScaling(virtual_surface_size) => {
762                create_view_projection_matrix_from_virtual(
763                    virtual_surface_size.x,
764                    virtual_surface_size.y,
765                )
766            }
767        };
768
769        let scale_matrix = Matrix4::from_scale(self.scale, self.scale, 0.0);
770        let origin_translation_matrix =
771            Matrix4::from_translation(-self.origin.x as f32, -self.origin.y as f32, 0.0);
772
773        let total_matrix = scale_matrix * view_proj_matrix * origin_translation_matrix;
774
775        // write all model_matrix and uv_coords to instance buffer once, before the render pass
776        self.queue.write_buffer(
777            &self.camera_buffer,
778            0,
779            bytemuck::cast_slice(&[total_matrix]),
780        );
781
782        self.prepare_render(materials, fonts);
783
784        render_pass.set_viewport(
785            self.viewport.position.x as f32,
786            self.viewport.position.y as f32,
787            self.viewport.size.x as f32,
788            self.viewport.size.y as f32,
789            0.0,
790            1.0,
791        );
792
793        render_pass.set_pipeline(&self.pipeline);
794
795        // Index and vertex buffers never change
796        render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
797        render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
798
799        // Vertex buffer is reused
800        render_pass.set_vertex_buffer(1, self.quad_matrix_and_uv_instance_buffer.slice(..));
801
802        // Camera is the same for everything
803        render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
804
805        let num_indices = swamp_wgpu_sprites::INDICES.len() as u32;
806
807        for &(weak_material_ref, start, count) in &self.batch_offsets {
808            let wgpu_material = materials
809                .get_weak(weak_material_ref)
810                .expect("no such material");
811            // Bind the texture and sampler bind group (Bind Group 1)
812            render_pass.set_bind_group(1, &wgpu_material.texture_and_sampler_bind_group, &[]);
813
814            // Issue the instanced draw call for the batch
815            trace!(material=%weak_material_ref, start=%start, count=%count, "draw instanced");
816            render_pass.draw_indexed(0..num_indices, 0, start..(start + count));
817        }
818
819        self.items.clear();
820    }
821
822    pub fn material_from_texture(&self, texture: wgpu::Texture, label: &str) -> Material {
823        trace!("load texture from memory with name: '{label}'");
824        let size = &texture.size();
825        let texture_and_sampler_bind_group =
826            swamp_wgpu_sprites::create_sprite_texture_and_sampler_bind_group(
827                &self.device,
828                &self.texture_sampler_bind_group_layout,
829                &texture,
830                &self.sampler,
831                label,
832            );
833
834        let texture_size = UVec2::new(size.width as u16, size.height as u16);
835
836        Material {
837            texture_and_sampler_bind_group,
838            //render_pipeline: Arc::clone(&self.pipeline),
839            texture_size,
840        }
841    }
842}
843
844//fn set_view_projection(&mut self) {}
845
846fn create_view_projection_matrix_from_virtual(virtual_width: u16, virtual_height: u16) -> Matrix4 {
847    OrthoInfo {
848        left: 0.0,
849        right: virtual_width as f32,
850        bottom: 0.0,
851        top: virtual_height as f32,
852        near: 1.0,
853        far: -1.0,
854    }
855    .into()
856}
857
858fn create_view_uniform_view_projection_matrix(viewport_size: UVec2) -> Matrix4 {
859    let viewport_width = viewport_size.x as f32;
860    let viewport_height = viewport_size.y as f32;
861
862    let viewport_aspect_ratio = viewport_width / viewport_height;
863
864    let scale_x = 1.0;
865    let scale_y = viewport_aspect_ratio; // scaling Y probably gives the best precision?
866
867    let view_projection_matrix = [
868        [scale_x, 0.0, 0.0, 0.0],
869        [0.0, scale_y, 0.0, 0.0],
870        [0.0, 0.0, -1.0, 0.0],
871        [0.0, 0.0, 0.0, 1.0],
872    ];
873
874    view_projection_matrix.into()
875}
876
877fn sort_render_items_by_z_and_material(items: &mut [RenderItem]) {
878    items.sort_by_key(|item| (item.position.z, item.material_ref));
879}
880
881#[derive(Debug, Clone, Copy, Default)]
882pub enum Rotation {
883    #[default]
884    Degrees0,
885    Degrees90,
886    Degrees180,
887    Degrees270,
888}
889
890#[derive(Debug, Copy, Clone)]
891pub struct SpriteParams {
892    pub texture_size: UVec2,
893    pub texture_pos: UVec2,
894    pub scale: u8,
895    pub rotation: Rotation,
896    pub flip_x: bool,
897    pub flip_y: bool,
898    pub pivot: Vec2,
899    pub color: Color,
900}
901
902impl Default for SpriteParams {
903    fn default() -> Self {
904        Self {
905            texture_size: UVec2::new(0, 0),
906            texture_pos: UVec2::new(0, 0),
907            pivot: Vec2::new(0, 0),
908            flip_x: false,
909            flip_y: false,
910            color: Color::from_octet(255, 255, 255, 255),
911            scale: 1,
912            rotation: Rotation::Degrees0,
913        }
914    }
915}
916
917#[derive(Debug, PartialEq, Eq, Asset)]
918pub struct Material {
919    pub texture_and_sampler_bind_group: BindGroup,
920    pub texture_size: UVec2,
921}
922
923impl PartialOrd<Self> for Material {
924    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
925        Some(
926            self.texture_and_sampler_bind_group
927                .cmp(&other.texture_and_sampler_bind_group),
928        )
929    }
930}
931
932impl Ord for Material {
933    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
934        self.texture_and_sampler_bind_group
935            .cmp(&other.texture_and_sampler_bind_group)
936    }
937}
938
939#[derive(Debug)]
940pub struct Sprite {
941    pub params: SpriteParams,
942}
943
944#[derive(Debug)]
945pub struct TileMap {
946    pub tiles_data_grid_size: UVec2,
947    pub cell_count_size: UVec2,
948    pub one_cell_size: UVec2,
949    pub tiles: Vec<u16>,
950    pub scale: u8,
951}
952
953pub type RenderPipelineRef = Arc<RenderPipeline>;
954
955const fn sources() -> (&'static str, &'static str) {
956    let vertex_shader_source = "
957// Bind Group 0: Uniforms (view-projection matrix)
958struct Uniforms {
959    view_proj: mat4x4<f32>,
960};
961
962@group(0) @binding(0)
963var<uniform> camera_uniforms: Uniforms;
964
965// Bind Group 1: Texture and Sampler (Unused in Vertex Shader but needed for consistency)
966@group(1) @binding(0)
967var diffuse_texture: texture_2d<f32>;
968
969@group(1) @binding(1)
970var sampler_diffuse: sampler;
971
972// Vertex input structure
973struct VertexInput {
974    @location(0) position: vec3<f32>,
975    @location(1) tex_coords: vec2<f32>,
976    @builtin(instance_index) instance_idx: u32,
977};
978
979// Vertex output structure to fragment shader
980struct VertexOutput {
981    @builtin(position) position: vec4<f32>,
982    @location(0) tex_coords: vec2<f32>,
983    @location(1) color: vec4<f32>,
984};
985
986// Vertex shader entry point
987@vertex
988fn vs_main(
989    input: VertexInput,
990    // Instance attributes
991    @location(2) model_matrix0: vec4<f32>,
992    @location(3) model_matrix1: vec4<f32>,
993    @location(4) model_matrix2: vec4<f32>,
994    @location(5) model_matrix3: vec4<f32>,
995    @location(6) tex_multiplier: vec4<f32>,
996    @location(7) rotation_step: u32,
997    @location(8) color: vec4<f32>,
998) -> VertexOutput {
999    var output: VertexOutput;
1000
1001    // Reconstruct the model matrix from the instance data
1002    let model_matrix = mat4x4<f32>(
1003        model_matrix0,
1004        model_matrix1,
1005        model_matrix2,
1006        model_matrix3,
1007    );
1008
1009    // Compute world position
1010    let world_position = model_matrix * vec4<f32>(input.position, 1.0);
1011
1012    // Apply view-projection matrix
1013    output.position = camera_uniforms.view_proj * world_position;
1014
1015    // Decode rotation_step
1016    let rotation_val = rotation_step & 3u; // Bits 0-1
1017    let flip_x = (rotation_step & 4u) != 0u; // Bit 2
1018    let flip_y = (rotation_step & 8u) != 0u; // Bit 3
1019
1020    // Rotate texture coordinates based on rotation_val
1021    var rotated_tex_coords = input.tex_coords;
1022    if (rotation_val == 1) {
1023        // 90 degrees rotation
1024        rotated_tex_coords = vec2<f32>(1.0 - input.tex_coords.y, input.tex_coords.x);
1025    } else if (rotation_val == 2) {
1026        // 180 degrees rotation
1027        rotated_tex_coords = vec2<f32>(1.0 - input.tex_coords.x, 1.0 - input.tex_coords.y);
1028    } else if (rotation_val == 3) {
1029        // 270 degrees rotation
1030        rotated_tex_coords = vec2<f32>(input.tex_coords.y, 1.0 - input.tex_coords.x);
1031    }
1032    // else rotation_val == Degrees0, no rotation
1033
1034    // Apply flipping
1035    if (flip_x) {
1036        rotated_tex_coords.x = 1.0 - rotated_tex_coords.x;
1037    }
1038    if (flip_y) {
1039        rotated_tex_coords.y = 1.0 - rotated_tex_coords.y;
1040    }
1041
1042    // Modify texture coordinates
1043    output.tex_coords = rotated_tex_coords * tex_multiplier.xy + tex_multiplier.zw;
1044    output.color = color;
1045
1046    return output;
1047}
1048        ";
1049    //
1050
1051    let fragment_shader_source = "
1052
1053// Bind Group 1: Texture and Sampler
1054@group(1) @binding(0)
1055var diffuse_texture: texture_2d<f32>;
1056
1057@group(1) @binding(1)
1058var sampler_diffuse: sampler;
1059
1060// Fragment input structure from vertex shader
1061struct VertexOutput {
1062    @builtin(position) position: vec4<f32>,
1063    @location(0) tex_coords: vec2<f32>,
1064    @location(1) color: vec4<f32>,
1065};
1066
1067// Fragment shader entry point
1068@fragment
1069fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
1070    // Sample the texture using the texture coordinates
1071    let texture_color = textureSample(diffuse_texture, sampler_diffuse, input.tex_coords);
1072
1073    // Apply color modulation and opacity
1074    let final_color = texture_color * input.color;
1075
1076    return final_color;
1077}
1078
1079";
1080    (vertex_shader_source, fragment_shader_source)
1081}