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