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