mireforge_wgpu_sprites/
lib.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/mireforge/mireforge
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5use bytemuck::{Pod, Zeroable};
6use image::DynamicImage;
7use image::GenericImageView;
8use limnus_wgpu_math::{Matrix4, Vec4};
9use tracing::{debug, warn};
10use wgpu::util::DeviceExt;
11use wgpu::{
12    util, BlendState, ColorTargetState, ColorWrites, Face, FrontFace, MultisampleState,
13    PolygonMode, PrimitiveState, PrimitiveTopology,
14};
15use wgpu::{
16    BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
17    BindGroupLayoutEntry, BindingType, Buffer, BufferAddress, BufferDescriptor, BufferUsages,
18    Device, Extent3d, PipelineLayout, PipelineLayoutDescriptor, Queue, RenderPipeline,
19    RenderPipelineDescriptor, Sampler, SamplerBindingType, ShaderModule, ShaderStages, Texture,
20    TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureUsages,
21    TextureViewDescriptor, TextureViewDimension, VertexAttribute, VertexBufferLayout, VertexFormat,
22    VertexStepMode,
23};
24use wgpu::{BindingResource, PipelineCompilationOptions};
25use wgpu::{BufferBindingType, TextureView};
26
27#[repr(C)]
28#[derive(Copy, Clone, Debug)]
29struct Vertex {
30    position: [f32; 2],   // 2D position of the vertex
31    tex_coords: [f32; 2], // Texture coordinates
32}
33
34// Implement Zeroable manually
35unsafe impl Zeroable for Vertex {}
36
37// Implement Pod manually
38unsafe impl Pod for Vertex {}
39
40impl Vertex {
41    const ATTRIBUTES: [VertexAttribute; 2] =
42        wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2];
43
44    pub const fn desc() -> VertexBufferLayout<'static> {
45        VertexBufferLayout {
46            array_stride: size_of::<Self>() as BufferAddress,
47            step_mode: VertexStepMode::Vertex,
48            attributes: &Self::ATTRIBUTES,
49        }
50    }
51}
52
53/// Buffer that stores a model and texture coordinates for one sprite
54// model: Mx4
55// tex_coords: V4
56#[must_use]
57pub fn create_sprite_uniform_buffer(device: &Device, label: &str) -> Buffer {
58    device.create_buffer_init(&util::BufferInitDescriptor {
59        label: Some(label),
60        contents: bytemuck::cast_slice(&[SpriteInstanceUniform {
61            model: Matrix4::identity(),
62            tex_coords_mul_add: Vec4([0.0, 0.0, 1.0, 1.0]),
63            rotation: 0,
64            color: Vec4([1.0, 0.0, 1.0, 1.0]),
65        }]),
66        usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
67    })
68}
69
70#[repr(C)]
71#[derive(Copy, Clone)]
72pub struct CameraUniform {
73    pub view_proj: Matrix4,
74}
75
76unsafe impl Pod for CameraUniform {}
77unsafe impl Zeroable for CameraUniform {}
78
79#[repr(C)]
80#[derive(Copy, Clone)]
81pub struct SpriteInstanceUniform {
82    //     transformed_pos = transformed_pos * vec2<f32>(instance.scale) + vec2<f32>(instance.position);
83    //     let world_pos = vec4<f32>(transformed_pos, 0.0, 1.0);
84    //     output.position = camera.view_proj * world_pos;
85    //pub position: [i32; 2],      // Integer pixel positions
86    //pub scale: [u32; 2],         // Integer pixel dimensions
87    pub model: Matrix4, // Model Transformation matrix. use
88    pub tex_coords_mul_add: Vec4,
89    pub rotation: u32,
90    pub color: Vec4,
91}
92
93unsafe impl Pod for SpriteInstanceUniform {}
94unsafe impl Zeroable for SpriteInstanceUniform {}
95
96impl SpriteInstanceUniform {
97    #[must_use]
98    pub const fn new(model: Matrix4, tex_coords_mul_add: Vec4, rotation: u32, color: Vec4) -> Self {
99        Self {
100            model,
101            tex_coords_mul_add,
102            rotation,
103            color,
104        }
105    }
106}
107
108impl SpriteInstanceUniform {
109    const fn desc<'a>() -> VertexBufferLayout<'a> {
110        VertexBufferLayout {
111            array_stride: size_of::<Self>() as BufferAddress,
112            step_mode: VertexStepMode::Instance,
113            attributes: &[
114                // Model Matrix. There is unfortunately no Matrix type, so you have to define it as 4 attributes of Float32x4.
115                VertexAttribute {
116                    offset: 0,
117                    shader_location: 2,
118                    format: VertexFormat::Float32x4,
119                },
120                VertexAttribute {
121                    offset: 16,
122                    shader_location: 3,
123                    format: VertexFormat::Float32x4,
124                },
125                VertexAttribute {
126                    offset: 32,
127                    shader_location: 4,
128                    format: VertexFormat::Float32x4,
129                },
130                VertexAttribute {
131                    offset: 48,
132                    shader_location: 5,
133                    format: VertexFormat::Float32x4,
134                },
135                // Texture multiplier and add
136                VertexAttribute {
137                    offset: 64,
138                    shader_location: 6,
139                    format: VertexFormat::Float32x4,
140                },
141                // Rotation
142                VertexAttribute {
143                    offset: 80,
144                    shader_location: 7,
145                    format: VertexFormat::Uint32,
146                },
147                // color (RGBA)
148                VertexAttribute {
149                    offset: 84,
150                    shader_location: 8,
151                    format: VertexFormat::Float32x4,
152                },
153            ],
154        }
155    }
156}
157
158// wgpu has, for very unknown reasons, put coordinate texture origo at top-left(!)
159const RIGHT: f32 = 1.0;
160const DOWN: f32 = 1.0;
161
162const IDENTITY_QUAD_VERTICES: &[Vertex] = &[
163    Vertex {
164        position: [0.0, 0.0],
165        tex_coords: [0.0, DOWN],
166    }, // Bottom left
167    Vertex {
168        position: [1.0, 0.0],
169        tex_coords: [RIGHT, DOWN],
170    }, // Bottom right
171    Vertex {
172        position: [1.0, 1.0],
173        tex_coords: [RIGHT, 0.0],
174    }, // Top right
175    Vertex {
176        position: [0.0, 1.0],
177        tex_coords: [0.0, 0.0],
178    }, // Top left
179];
180
181// u16 is the smallest index buffer supported by wgpu // IndexFormat
182pub const INDICES: &[u16] = &[0, 1, 2, 0, 2, 3];
183
184#[derive(Debug)]
185pub struct SpriteInfo {
186    pub sprite_shader_info: ShaderInfo,
187    pub quad_shader_info: ShaderInfo,
188    pub mask_shader_info: ShaderInfo,
189    pub light_shader_info: ShaderInfo,
190    pub virtual_to_screen_shader_info: ShaderInfo,
191
192    pub sampler: Sampler,
193    pub vertex_buffer: Buffer,
194    pub index_buffer: Buffer,
195
196    // Camera - Group 0
197    pub camera_bind_group_layout: BindGroupLayout,
198
199    pub camera_uniform_buffer: Buffer,
200    pub camera_bind_group: BindGroup,
201
202    // Texture and Sampler - Group 1
203    pub sprite_texture_sampler_bind_group_layout: BindGroupLayout,
204
205    // Vertex Instances - Group 1
206    pub quad_matrix_and_uv_instance_buffer: Buffer,
207}
208
209const MAX_RENDER_SPRITE_COUNT: usize = 10_000;
210
211#[derive(Debug)]
212pub struct ShaderInfo {
213    pub vertex_shader: ShaderModule,
214    pub fragment_shader: ShaderModule,
215    pub pipeline: RenderPipeline,
216}
217
218#[must_use]
219pub fn create_shader_info(
220    device: &Device,
221    surface_texture_format: TextureFormat,
222    camera_bind_group_layout: &BindGroupLayout,
223    specific_layouts: &[&BindGroupLayout],
224    vertex_source: &str,
225    fragment_source: &str,
226    blend_state: BlendState,
227    name: &str,
228) -> ShaderInfo {
229    let mut layouts = Vec::new();
230    layouts.push(camera_bind_group_layout);
231    layouts.extend_from_slice(specific_layouts);
232
233    create_shader_info_ex(
234        device,
235        surface_texture_format,
236        &layouts,
237        vertex_source,
238        fragment_source,
239        &[Vertex::desc(), SpriteInstanceUniform::desc()],
240        blend_state,
241        name,
242    )
243}
244
245#[must_use]
246pub fn create_shader_info_ex(
247    device: &Device,
248    surface_texture_format: TextureFormat,
249    specific_layouts: &[&BindGroupLayout],
250    vertex_source: &str,
251    fragment_source: &str,
252    buffers: &[VertexBufferLayout],
253    blend_state: BlendState,
254    name: &str,
255) -> ShaderInfo {
256    let vertex_shader =
257        mireforge_wgpu::create_shader_module(device, &format!("{name} vertex"), vertex_source);
258    let fragment_shader =
259        mireforge_wgpu::create_shader_module(device, &format!("{name} fragment"), fragment_source);
260
261    let custom_layout =
262        create_pipeline_layout(device, specific_layouts, &format!("{name} pipeline layout"));
263
264    let pipeline = create_pipeline_with_buffers(
265        device,
266        surface_texture_format,
267        &custom_layout,
268        &vertex_shader,
269        &fragment_shader,
270        buffers,
271        blend_state,
272        name,
273    );
274
275    ShaderInfo {
276        vertex_shader,
277        fragment_shader,
278        pipeline,
279    }
280}
281
282impl SpriteInfo {
283    #[allow(clippy::too_many_lines)]
284    #[must_use]
285    pub fn new(
286        device: &Device,
287        surface_texture_format: TextureFormat,
288        view_proj_matrix: Matrix4,
289    ) -> Self {
290        let index_buffer = create_sprite_index_buffer(device, "identity quad index buffer");
291        let vertex_buffer = create_sprite_vertex_buffer(device, "identity quad vertex buffer");
292
293        // ------------------------------- Camera View Projection Matrix in Group 0 --------------------------
294        let camera_uniform_buffer = create_camera_uniform_buffer(
295            device,
296            view_proj_matrix,
297            "view and projection matrix (camera)",
298        );
299
300        let camera_bind_group_layout =
301            create_camera_uniform_bind_group_layout(device, "camera bind group layout");
302
303        let camera_bind_group = create_camera_uniform_bind_group(
304            device,
305            &camera_bind_group_layout,
306            &camera_uniform_buffer,
307            "camera matrix",
308        );
309
310        // Create normal sprite shader
311        let (sprite_vertex_shader_source, sprite_fragment_shader_source) = normal_sprite_sources();
312
313        let sprite_texture_sampler_bind_group_layout =
314            create_texture_and_sampler_group_layout(device, "sprite texture and sampler layout");
315
316        let alpha_blending = BlendState::ALPHA_BLENDING;
317
318        let sprite_shader_info = create_shader_info(
319            device,
320            surface_texture_format,
321            &camera_bind_group_layout,
322            &[&sprite_texture_sampler_bind_group_layout],
323            sprite_vertex_shader_source,
324            sprite_fragment_shader_source,
325            alpha_blending,
326            "Sprite",
327        );
328
329        // Create quad shader
330        let quad_shader_info = {
331            let (vertex_shader_source, fragment_shader_source) = quad_shaders();
332
333            create_shader_info(
334                device,
335                surface_texture_format,
336                &camera_bind_group_layout,
337                &[],
338                vertex_shader_source,
339                fragment_shader_source,
340                alpha_blending,
341                "Quad",
342            )
343        };
344
345        let mask_shader_info = {
346            let vertex_shader_source = masked_texture_tinted_vertex_source();
347            let fragment_shader_source = masked_texture_tinted_fragment_source();
348
349            let diffuse_texture_group =
350                create_texture_and_sampler_group_layout(device, "normal diffuse texture group");
351
352            let alpha_texture_group =
353                create_texture_and_sampler_group_layout(device, "alpha texture group");
354
355            create_shader_info(
356                device,
357                surface_texture_format,
358                &camera_bind_group_layout,
359                &[&diffuse_texture_group, &alpha_texture_group],
360                vertex_shader_source,
361                fragment_shader_source,
362                alpha_blending,
363                "AlphaMask",
364            )
365        };
366
367        let virtual_to_screen_shader_info = {
368            let virtual_texture_group_layout =
369                create_texture_and_sampler_group_layout(device, "virtual texture group");
370            create_shader_info_ex(
371                device,
372                surface_texture_format,
373                &[&virtual_texture_group_layout],
374                SCREEN_QUAD_VERTEX_SHADER,
375                SCREEN_QUAD_FRAGMENT_SHADER,
376                &[],
377                alpha_blending,
378                "VirtualToScreen",
379            )
380        };
381
382        let light_shader_info = {
383            let vertex_shader_source = sprite_vertex_shader_source;
384            let fragment_shader_source = sprite_fragment_shader_source;
385
386            let light_texture_group =
387                create_texture_and_sampler_group_layout(device, "light texture group");
388
389            let additive_blend = wgpu::BlendState {
390                color: wgpu::BlendComponent {
391                    src_factor: wgpu::BlendFactor::SrcAlpha,
392                    dst_factor: wgpu::BlendFactor::One,
393                    operation: wgpu::BlendOperation::Add,
394                },
395                alpha: wgpu::BlendComponent {
396                    src_factor: wgpu::BlendFactor::Zero,
397                    dst_factor: wgpu::BlendFactor::One,
398                    operation: wgpu::BlendOperation::Add,
399                },
400            };
401
402            create_shader_info(
403                device,
404                surface_texture_format,
405                &camera_bind_group_layout,
406                &[&light_texture_group],
407                vertex_shader_source,
408                fragment_shader_source,
409                additive_blend,
410                "Light (Additive)",
411            )
412        };
413
414        let quad_matrix_and_uv_instance_buffer = create_quad_matrix_and_uv_instance_buffer(
415            device,
416            MAX_RENDER_SPRITE_COUNT,
417            "sprite_instance buffer",
418        );
419
420        let sampler = mireforge_wgpu::create_nearest_sampler(device, "sprite nearest sampler");
421
422        Self {
423            sprite_shader_info,
424            quad_shader_info,
425            mask_shader_info,
426            light_shader_info,
427            virtual_to_screen_shader_info,
428            sampler,
429            vertex_buffer,
430            index_buffer,
431            camera_bind_group_layout,
432            camera_uniform_buffer,
433            camera_bind_group,
434            sprite_texture_sampler_bind_group_layout,
435            quad_matrix_and_uv_instance_buffer,
436        }
437    }
438}
439
440/// Creates the view - projection matrix (Camera)
441fn create_camera_uniform_buffer(device: &Device, view_proj: Matrix4, label: &str) -> Buffer {
442    let camera_uniform = CameraUniform { view_proj };
443
444    device.create_buffer_init(&util::BufferInitDescriptor {
445        label: Some(label),
446        contents: bytemuck::cast_slice(&[camera_uniform]),
447        usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
448    })
449}
450
451/// Camera is just one binding, the view projection camera matrix
452fn create_camera_uniform_bind_group_layout(device: &Device, label: &str) -> BindGroupLayout {
453    device.create_bind_group_layout(&BindGroupLayoutDescriptor {
454        label: Some(label),
455        entries: &[BindGroupLayoutEntry {
456            binding: 0,
457            visibility: ShaderStages::VERTEX,
458            ty: BindingType::Buffer {
459                ty: BufferBindingType::Uniform,
460                has_dynamic_offset: false,
461                min_binding_size: None,
462            },
463            count: None,
464        }],
465    })
466}
467
468fn create_camera_uniform_bind_group(
469    device: &Device,
470    bind_group_layout: &BindGroupLayout,
471    uniform_buffer: &Buffer,
472    label: &str,
473) -> BindGroup {
474    device.create_bind_group(&BindGroupDescriptor {
475        label: Some(label),
476        layout: bind_group_layout,
477        entries: &[BindGroupEntry {
478            binding: 0,
479            resource: uniform_buffer.as_entire_binding(),
480        }],
481    })
482}
483
484#[must_use]
485pub fn load_texture_from_memory(
486    device: &Device,
487    queue: &Queue,
488    img: DynamicImage,
489    label: &str,
490) -> Texture {
491    let (width, height) = img.dimensions();
492    let texture_size = Extent3d {
493        width,
494        height,
495        depth_or_array_layers: 1,
496    };
497
498    let (texture_format, texture_data): (TextureFormat, Vec<u8>) = match img {
499        DynamicImage::ImageLuma8(buffer) => {
500            debug!(
501                ?label,
502                "Detected Luma8 image. Using texture format R8Unorm."
503            );
504            if !label.contains(".alpha") {
505                warn!("it is recommended that filename includes '.alpha' for luma8 textures");
506            }
507            (TextureFormat::R8Unorm, buffer.into_raw())
508        }
509        DynamicImage::ImageLumaA8(buffer) => {
510            warn!(
511                ?label,
512                "Detected LumaA8 image. Discarding alpha channel and using Luma for alpha R8Unorm. Please do not use this format, since half of it is discarded."
513            );
514            if !label.contains(".alpha") {
515                warn!("it is recommended that filename includes '.alpha' for luma8 textures");
516            }
517            // Extract only the Luma channel
518            let luma_data = buffer
519                .pixels()
520                .flat_map(|p| [p[0]]) // p[0] is Luma, p[1] is Alpha
521                .collect();
522            (TextureFormat::R8Unorm, luma_data)
523        }
524        DynamicImage::ImageRgba8(buffer) => {
525            debug!(
526                ?label,
527                "Detected Rgba8 image. Using texture format Rgba8UnormSrgb."
528            );
529            (TextureFormat::Rgba8UnormSrgb, buffer.into_raw())
530        }
531        DynamicImage::ImageRgb8(buffer) => {
532            warn!(
533                ?label,
534                "Detected Rgb8 image. Converting to Rgba8. Using texture format Rgba8UnormSrgb."
535            );
536            let rgba_buffer = buffer.pixels().fold(
537                Vec::with_capacity((width * height * 4) as usize),
538                |mut acc, rgb| {
539                    acc.extend_from_slice(&[rgb[0], rgb[1], rgb[2], 255u8]);
540                    acc
541                },
542            );
543            (TextureFormat::Rgba8UnormSrgb, rgba_buffer)
544        }
545        _ => {
546            warn!(
547                ?label,
548                "Detected unknown format. Converting to Rgba8. Using texture format Rgba8UnormSrgb."
549            );
550            let rgba_buffer = img.clone().into_rgba8();
551            (TextureFormat::Rgba8UnormSrgb, rgba_buffer.into_raw())
552        }
553    };
554
555    let texture_descriptor = TextureDescriptor {
556        label: Some(label),
557        size: texture_size,
558        mip_level_count: 1,
559        sample_count: 1,
560        dimension: TextureDimension::D2,
561        format: texture_format, // Use the detected format
562        usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
563        view_formats: &[texture_format],
564    };
565
566    device.create_texture_with_data(
567        queue,
568        &texture_descriptor,
569        util::TextureDataOrder::LayerMajor,
570        &texture_data,
571    )
572}
573
574#[must_use]
575pub fn create_sprite_vertex_buffer(device: &Device, label: &str) -> Buffer {
576    device.create_buffer_init(&util::BufferInitDescriptor {
577        label: Some(label),
578        contents: bytemuck::cast_slice(IDENTITY_QUAD_VERTICES),
579        usage: BufferUsages::VERTEX,
580    })
581}
582
583#[must_use]
584pub fn create_sprite_index_buffer(device: &Device, label: &str) -> Buffer {
585    device.create_buffer_init(&util::BufferInitDescriptor {
586        label: Some(label),
587        contents: bytemuck::cast_slice(INDICES),
588        usage: BufferUsages::INDEX,
589    })
590}
591
592/// Binding0: Texture
593/// Binding1: Sampler
594#[must_use]
595pub fn create_texture_and_sampler_group_layout(device: &Device, label: &str) -> BindGroupLayout {
596    device.create_bind_group_layout(&BindGroupLayoutDescriptor {
597        label: Some(label),
598        entries: &[
599            BindGroupLayoutEntry {
600                binding: 0,
601                visibility: ShaderStages::FRAGMENT,
602                ty: BindingType::Texture {
603                    multisampled: false,
604                    view_dimension: TextureViewDimension::D2,
605                    sample_type: TextureSampleType::Float { filterable: true },
606                },
607                count: None,
608            },
609            BindGroupLayoutEntry {
610                binding: 1,
611                visibility: ShaderStages::FRAGMENT,
612                ty: BindingType::Sampler(SamplerBindingType::Filtering),
613                count: None,
614            },
615        ],
616    })
617}
618
619#[must_use]
620pub fn create_sprite_texture_and_sampler_bind_group(
621    device: &Device,
622    bind_group_layout: &BindGroupLayout,
623    texture: &Texture,
624    sampler: &Sampler,
625    label: &str,
626) -> BindGroup {
627    let texture_view = texture.create_view(&TextureViewDescriptor::default());
628    create_texture_and_sampler_bind_group_ex(
629        device,
630        bind_group_layout,
631        &texture_view,
632        sampler,
633        label,
634    )
635}
636
637#[must_use]
638pub fn create_texture_and_sampler_bind_group_ex(
639    device: &Device,
640    bind_group_layout: &BindGroupLayout,
641    texture_view: &TextureView,
642    sampler: &Sampler,
643    label: &str,
644) -> BindGroup {
645    device.create_bind_group(&BindGroupDescriptor {
646        layout: bind_group_layout,
647        entries: &[
648            BindGroupEntry {
649                binding: 0,
650                resource: BindingResource::TextureView(texture_view),
651            },
652            BindGroupEntry {
653                binding: 1,
654                resource: BindingResource::Sampler(sampler),
655            },
656        ],
657        label: Some(label),
658    })
659}
660
661#[must_use]
662pub fn create_quad_matrix_and_uv_instance_buffer(
663    device: &Device,
664    max_instances: usize,
665    label: &str,
666) -> Buffer {
667    let buffer_size = (size_of::<SpriteInstanceUniform>() * max_instances) as BufferAddress;
668
669    device.create_buffer(&BufferDescriptor {
670        label: Some(label),
671        size: buffer_size,
672        usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
673        mapped_at_creation: false,
674    })
675}
676
677fn create_pipeline_layout(
678    device: &Device,
679    layouts: &[&BindGroupLayout],
680    label: &str,
681) -> PipelineLayout {
682    device.create_pipeline_layout(&PipelineLayoutDescriptor {
683        label: Some(label),
684        bind_group_layouts: layouts,
685        push_constant_ranges: &[],
686    })
687}
688
689fn create_pipeline_with_buffers(
690    device: &Device,
691    format: TextureFormat,
692    pipeline_layout: &PipelineLayout,
693    vertex_shader: &ShaderModule,
694    fragment_shader: &ShaderModule,
695    buffers: &[VertexBufferLayout],
696    blend_state: BlendState,
697    label: &str,
698) -> RenderPipeline {
699    device.create_render_pipeline(&RenderPipelineDescriptor {
700        label: Some(label),
701        layout: Some(pipeline_layout),
702        vertex: wgpu::VertexState {
703            module: vertex_shader,
704            entry_point: Some("vs_main"),
705            compilation_options: PipelineCompilationOptions::default(),
706            buffers,
707        },
708        fragment: Some(wgpu::FragmentState {
709            module: fragment_shader,
710            entry_point: Some("fs_main"),
711            compilation_options: PipelineCompilationOptions::default(),
712            targets: &[Some(ColorTargetState {
713                format,
714                blend: Some(blend_state),
715                write_mask: ColorWrites::ALL,
716            })],
717        }),
718        primitive: PrimitiveState {
719            topology: PrimitiveTopology::TriangleList,
720            strip_index_format: None,
721            front_face: FrontFace::Ccw,
722            cull_mode: Some(Face::Back),
723            unclipped_depth: false,
724            polygon_mode: PolygonMode::Fill,
725            conservative: false,
726        },
727
728        depth_stencil: None,
729        multisample: MultisampleState::default(),
730        multiview: None,
731        cache: None,
732    })
733}
734
735#[must_use]
736pub const fn normal_sprite_sources() -> (&'static str, &'static str) {
737    let vertex_shader_source = "
738// Bind Group 0: Uniforms (view-projection matrix)
739struct Uniforms {
740    view_proj: mat4x4<f32>,
741};
742
743@group(0) @binding(0)
744var<uniform> camera_uniforms: Uniforms;
745
746// Bind Group 1: Texture and Sampler (Unused in Vertex Shader but needed for consistency)
747@group(1) @binding(0)
748var diffuse_texture: texture_2d<f32>;
749
750@group(1) @binding(1)
751var sampler_diffuse: sampler;
752
753// Vertex input structure
754struct VertexInput {
755    @location(0) position: vec3<f32>,
756    @location(1) tex_coords: vec2<f32>,
757    @builtin(instance_index) instance_idx: u32,
758};
759
760// Vertex output structure to fragment shader
761// Must be exactly the same as the fragment shader
762struct VertexOutput {
763    @builtin(position) position: vec4<f32>,
764    @location(0) tex_coords: vec2<f32>,
765    @location(1) color: vec4<f32>,
766};
767
768// Vertex shader entry point
769@vertex
770fn vs_main(
771    input: VertexInput,
772    // Instance attributes
773    @location(2) model_matrix0: vec4<f32>,
774    @location(3) model_matrix1: vec4<f32>,
775    @location(4) model_matrix2: vec4<f32>,
776    @location(5) model_matrix3: vec4<f32>,
777    @location(6) tex_multiplier: vec4<f32>,
778    @location(7) rotation_step: u32,
779    @location(8) color: vec4<f32>,
780) -> VertexOutput {
781    var output: VertexOutput;
782
783    // Reconstruct the model matrix from the instance data
784    let model_matrix = mat4x4<f32>(
785        model_matrix0,
786        model_matrix1,
787        model_matrix2,
788        model_matrix3,
789    );
790
791    // Compute world position
792    let world_position = model_matrix * vec4<f32>(input.position, 1.0);
793
794    // Apply view-projection matrix
795    output.position = camera_uniforms.view_proj * world_position;
796
797    // Decode rotation_step
798    let rotation_val = rotation_step & 3u; // Bits 0-1
799    let flip_x = (rotation_step & 4u) != 0u; // Bit 2
800    let flip_y = (rotation_step & 8u) != 0u; // Bit 3
801
802    // Rotate texture coordinates based on rotation_val
803    var rotated_tex_coords = input.tex_coords;
804    if (rotation_val == 1) {
805        // 90 degrees rotation
806        rotated_tex_coords = vec2<f32>(1.0 - input.tex_coords.y, input.tex_coords.x);
807    } else if (rotation_val == 2) {
808        // 180 degrees rotation
809        rotated_tex_coords = vec2<f32>(1.0 - input.tex_coords.x, 1.0 - input.tex_coords.y);
810    } else if (rotation_val == 3) {
811        // 270 degrees rotation
812        rotated_tex_coords = vec2<f32>(input.tex_coords.y, 1.0 - input.tex_coords.x);
813    }
814    // else rotation_val == Degrees0, no rotation
815
816    // Apply flipping
817    if (flip_x) {
818        rotated_tex_coords.x = 1.0 - rotated_tex_coords.x;
819    }
820    if (flip_y) {
821        rotated_tex_coords.y = 1.0 - rotated_tex_coords.y;
822    }
823
824    // Modify texture coordinates
825    output.tex_coords = rotated_tex_coords * tex_multiplier.xy + tex_multiplier.zw;
826    output.color = color;
827
828    return output;
829}
830        ";
831    //
832
833    let fragment_shader_source = "
834
835// Bind Group 1: Texture and Sampler
836@group(1) @binding(0)
837var diffuse_texture: texture_2d<f32>;
838
839@group(1) @binding(1)
840var sampler_diffuse: sampler;
841
842// Fragment input structure from vertex shader
843struct VertexOutput {
844    @builtin(position) position: vec4<f32>,
845    @location(0) tex_coords: vec2<f32>,
846    @location(1) color: vec4<f32>,
847};
848
849// Fragment shader entry point
850@fragment
851fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
852    var final_color: vec4<f32>;
853
854    // Sample the texture using the texture coordinates
855    let texture_color = textureSample(diffuse_texture, sampler_diffuse, input.tex_coords);
856
857    return  texture_color * input.color;;
858}
859
860";
861    (vertex_shader_source, fragment_shader_source)
862}
863
864#[allow(unused)]
865pub const fn masked_texture_tinted_fragment_source() -> &'static str {
866    r"
867// Masked Texture and tinted shader
868
869// Bind Group 1: Texture and Sampler (Unused in Vertex Shader but needed for consistency?)
870@group(1) @binding(0)
871var diffuse_texture: texture_2d<f32>;
872@group(1) @binding(1)
873var sampler_diffuse: sampler;
874
875// Bind Group 2: Texture and Sampler (Unused in Vertex Shader but needed for consistency?)
876@group(2) @binding(0)
877var alpha_texture: texture_2d<f32>;
878@group(2) @binding(1)
879var sampler_alpha: sampler;
880
881// Must be the same as vertex shader
882struct VertexOutput {
883    @builtin(position) position: vec4<f32>,
884    @location(0) modified_tex_coords: vec2<f32>,
885    @location(1) color: vec4<f32>,
886    @location(2) original_tex_coords: vec2<f32>,
887};
888
889@fragment
890fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
891    let color_sample = textureSample(diffuse_texture, sampler_diffuse, input.modified_tex_coords);
892    let mask_alpha = textureSample(alpha_texture, sampler_alpha, input.original_tex_coords).r;
893
894    let final_rgb = color_sample.rgb * input.color.rgb;
895    let final_alpha = mask_alpha * input.color.a;
896
897    return vec4<f32>(final_rgb, final_alpha);
898}
899    "
900}
901
902#[must_use]
903pub const fn masked_texture_tinted_vertex_source() -> &'static str {
904    "
905// Bind Group 0: Uniforms (view-projection matrix)
906struct Uniforms {
907    view_proj: mat4x4<f32>,
908};
909
910@group(0) @binding(0)
911var<uniform> camera_uniforms: Uniforms;
912
913// Bind Group 1: Texture and Sampler (Unused in Vertex Shader but needed for consistency?)
914@group(1) @binding(0)
915var diffuse_texture: texture_2d<f32>;
916@group(1) @binding(1)
917var sampler_diffuse: sampler;
918
919// Bind Group 2: Texture and Sampler (Unused in Vertex Shader but needed for consistency?)
920@group(2) @binding(0)
921var alpha_texture: texture_2d<f32>;
922@group(2) @binding(1)
923var sampler_alpha: sampler;
924
925// Vertex input structure
926struct VertexInput {
927    @location(0) position: vec3<f32>,
928    @location(1) tex_coords: vec2<f32>,
929    @builtin(instance_index) instance_idx: u32,
930};
931
932// Vertex output structure to fragment shader
933// Must be exactly the same as the fragment shader
934struct VertexOutput {
935    @builtin(position) position: vec4<f32>,
936    @location(0) modified_tex_coords: vec2<f32>,
937    @location(1) color: vec4<f32>,
938    @location(2) original_tex_coords: vec2<f32>,
939};
940
941// Vertex shader entry point
942@vertex
943fn vs_main(
944    input: VertexInput,
945    // Instance attributes
946    @location(2) model_matrix0: vec4<f32>,
947    @location(3) model_matrix1: vec4<f32>,
948    @location(4) model_matrix2: vec4<f32>,
949    @location(5) model_matrix3: vec4<f32>,
950    @location(6) tex_multiplier: vec4<f32>,
951    @location(7) rotation_step: u32,
952    @location(8) color: vec4<f32>,
953) -> VertexOutput {
954
955    var output: VertexOutput;
956
957    // Reconstruct the model matrix from the instance data
958    let model_matrix = mat4x4<f32>(
959        model_matrix0,
960        model_matrix1,
961        model_matrix2,
962        model_matrix3,
963    );
964
965    // Compute world position
966    let world_position = model_matrix * vec4<f32>(input.position, 1.0);
967
968    // Apply view-projection matrix
969    output.position = camera_uniforms.view_proj * world_position;
970
971    // Decode rotation_step
972    let rotation_val = rotation_step & 3u; // Bits 0-1
973    let flip_x = (rotation_step & 4u) != 0u; // Bit 2
974    let flip_y = (rotation_step & 8u) != 0u; // Bit 3
975
976    // Rotate texture coordinates based on rotation_val
977    var rotated_tex_coords = input.tex_coords;
978    if (rotation_val == 1) {
979        // 90 degrees rotation
980        rotated_tex_coords = vec2<f32>(1.0 - input.tex_coords.y, input.tex_coords.x);
981    } else if (rotation_val == 2) {
982        // 180 degrees rotation
983        rotated_tex_coords = vec2<f32>(1.0 - input.tex_coords.x, 1.0 - input.tex_coords.y);
984    } else if (rotation_val == 3) {
985        // 270 degrees rotation
986        rotated_tex_coords = vec2<f32>(input.tex_coords.y, 1.0 - input.tex_coords.x);
987    }
988    // else rotation_val == Degrees0, no rotation
989
990    // Apply flipping
991    if (flip_x) {
992        rotated_tex_coords.x = 1.0 - rotated_tex_coords.x;
993    }
994    if (flip_y) {
995        rotated_tex_coords.y = 1.0 - rotated_tex_coords.y;
996    }
997
998    // Modify texture coordinates
999    output.modified_tex_coords = rotated_tex_coords * tex_multiplier.xy + tex_multiplier.zw;
1000    output.original_tex_coords = input.tex_coords;
1001
1002    output.color = color;
1003
1004    return output;
1005}"
1006}
1007
1008const fn quad_shaders() -> (&'static str, &'static str) {
1009    let vertex_shader_source = "
1010// Bind Group 0: Uniforms (view-projection matrix)
1011struct Uniforms {
1012    view_proj: mat4x4<f32>,
1013};
1014// Camera (view projection matrix) is always first
1015@group(0) @binding(0)
1016var<uniform> camera_uniforms: Uniforms;
1017
1018
1019// Vertex input structure
1020struct VertexInput {
1021    @location(0) position: vec3<f32>,
1022};
1023
1024// Vertex output structure to fragment shader
1025// Must be exactly the same in both places
1026struct VertexOutput {
1027    @builtin(position) position: vec4<f32>, // MUST BE HERE, DO NOT REMOVE
1028    @location(0) color: vec4<f32>,
1029};
1030
1031// Vertex shader entry point
1032@vertex
1033fn vs_main(
1034    input: VertexInput,
1035    // Instance attributes
1036    @location(2) model_matrix0: vec4<f32>, // Always fixed
1037    @location(3) model_matrix1: vec4<f32>, // Always fixed
1038    @location(4) model_matrix2: vec4<f32>, // Always fixed
1039    @location(5) model_matrix3: vec4<f32>, // Always fixed
1040    @location(8) color: vec4<f32>, //  Always fixed at position 8
1041) -> VertexOutput {
1042    var output: VertexOutput;
1043
1044    // Reconstruct the model matrix from the instance data
1045    let model_matrix = mat4x4<f32>(
1046        model_matrix0,
1047        model_matrix1,
1048        model_matrix2,
1049        model_matrix3,
1050    );
1051
1052    // Compute world position
1053    let world_position = model_matrix * vec4<f32>(input.position, 1.0);
1054
1055    // Apply view-projection matrix
1056    output.position = camera_uniforms.view_proj * world_position;
1057    output.color = color;
1058
1059    return output;
1060}
1061        ";
1062    //
1063
1064    let fragment_shader_source = "
1065
1066// Fragment input structure from vertex shader,
1067// Must be exactly the same in both places
1068struct VertexOutput {
1069    @builtin(position) position: vec4<f32>, // MUST BE HERE, DO NOT REMOVE
1070    @location(0) color: vec4<f32>,
1071};
1072
1073// Fragment shader entry point
1074@fragment
1075fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
1076    // It is a quad, so we only use the color
1077    return input.color;
1078}
1079
1080";
1081    (vertex_shader_source, fragment_shader_source)
1082}
1083
1084pub const SCREEN_QUAD_VERTEX_SHADER: &str = "
1085// Define the output structure
1086struct VertexOutput {
1087    @builtin(position) position: vec4<f32>,
1088    @location(0) texcoord: vec2<f32>,
1089};
1090
1091
1092@vertex
1093fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
1094    var positions = array<vec2<f32>, 6>(
1095        vec2<f32>(-1.0, -1.0),
1096        vec2<f32>(1.0, -1.0),
1097        vec2<f32>(-1.0, 1.0),
1098        vec2<f32>(-1.0, 1.0),
1099        vec2<f32>(1.0, -1.0),
1100        vec2<f32>(1.0, 1.0),
1101    );
1102
1103    var texcoords = array<vec2<f32>, 6>(
1104        vec2<f32>(0.0, 1.0),
1105        vec2<f32>(1.0, 1.0),
1106        vec2<f32>(0.0, 0.0),
1107        vec2<f32>(0.0, 0.0),
1108        vec2<f32>(1.0, 1.0),
1109        vec2<f32>(1.0, 0.0),
1110    );
1111
1112    var output: VertexOutput;
1113    output.position = vec4<f32>(positions[vertex_index], 0.0, 1.0);
1114    output.texcoord = texcoords[vertex_index];
1115    return output;
1116}
1117";
1118
1119// Fragment shader for the screen quad
1120pub const SCREEN_QUAD_FRAGMENT_SHADER: &str = "
1121@group(0) @binding(0) var game_texture: texture_2d<f32>;
1122@group(0) @binding(1) var game_sampler: sampler;
1123
1124@fragment
1125fn fs_main(@location(0) texcoord: vec2<f32>) -> @location(0) vec4<f32> {
1126    return textureSample(game_texture, game_sampler, texcoord);
1127}
1128";