1use 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], tex_coords: [f32; 2], }
33
34unsafe impl Zeroable for Vertex {}
36
37unsafe 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#[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 pub model: Matrix4, 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 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 VertexAttribute {
137 offset: 64,
138 shader_location: 6,
139 format: VertexFormat::Float32x4,
140 },
141 VertexAttribute {
143 offset: 80,
144 shader_location: 7,
145 format: VertexFormat::Uint32,
146 },
147 VertexAttribute {
149 offset: 84,
150 shader_location: 8,
151 format: VertexFormat::Float32x4,
152 },
153 ],
154 }
155 }
156}
157
158const 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 }, Vertex {
171 position: [1.0, 0.0],
172 tex_coords: [UV_RIGHT, UV_DOWN],
173 }, Vertex {
175 position: [1.0, 1.0],
176 tex_coords: [UV_RIGHT, UV_UP],
177 }, Vertex {
179 position: [0.0, 1.0],
180 tex_coords: [UV_LEFT, UV_UP],
181 }, ];
183
184pub 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 pub camera_bind_group_layout: BindGroupLayout,
201
202 pub camera_uniform_buffer: Buffer,
203 pub camera_bind_group: BindGroup,
204
205 pub sprite_texture_sampler_bind_group_layout: BindGroupLayout,
207
208 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 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 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 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
445fn 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
456fn 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 let luma_data = buffer
524 .pixels()
525 .flat_map(|p| [p[0]]) .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, 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#[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 immediate_size: 0, })
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 cache: None,
737 multiview_mask: 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 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 // Sample the texture using the texture coordinates
859 let texture_color = textureSample(diffuse_texture, sampler_diffuse, input.tex_coords);
860
861 return texture_color * input.color;
862}
863
864";
865 (vertex_shader_source, fragment_shader_source)
866}
867
868#[allow(unused)]
869#[must_use]
870pub const fn masked_texture_tinted_fragment_source() -> &'static str {
871 r"
872// Masked Texture and tinted shader
873
874// Bind Group 1: Texture and Sampler (Unused in Vertex Shader but needed for consistency?)
875@group(1) @binding(0)
876var diffuse_texture: texture_2d<f32>;
877@group(1) @binding(1)
878var sampler_diffuse: sampler;
879
880// Bind Group 2: Texture and Sampler (Unused in Vertex Shader but needed for consistency?)
881@group(2) @binding(0)
882var alpha_texture: texture_2d<f32>;
883@group(2) @binding(1)
884var sampler_alpha: sampler;
885
886// Must be the same as vertex shader
887struct VertexOutput {
888 @builtin(position) position: vec4<f32>,
889 @location(0) modified_tex_coords: vec2<f32>,
890 @location(1) color: vec4<f32>,
891 @location(2) original_tex_coords: vec2<f32>,
892};
893
894@fragment
895fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
896 let color_sample = textureSample(diffuse_texture, sampler_diffuse, input.modified_tex_coords);
897 let mask_alpha = textureSample(alpha_texture, sampler_alpha, input.original_tex_coords).r;
898
899 let final_rgb = color_sample.rgb * input.color.rgb;
900 let final_alpha = mask_alpha * input.color.a;
901
902 return vec4<f32>(final_rgb, final_alpha);
903}
904 "
905}
906
907#[must_use]
908pub const fn masked_texture_tinted_vertex_source() -> &'static str {
909 "
910// Bind Group 0: Uniforms (view-projection matrix)
911struct Uniforms {
912 view_proj: mat4x4<f32>,
913};
914
915@group(0) @binding(0)
916var<uniform> camera_uniforms: Uniforms;
917
918// Bind Group 1: Texture and Sampler (Unused in Vertex Shader but needed for consistency?)
919@group(1) @binding(0)
920var diffuse_texture: texture_2d<f32>;
921@group(1) @binding(1)
922var sampler_diffuse: sampler;
923
924// Bind Group 2: Texture and Sampler (Unused in Vertex Shader but needed for consistency?)
925@group(2) @binding(0)
926var alpha_texture: texture_2d<f32>;
927@group(2) @binding(1)
928var sampler_alpha: sampler;
929
930// Vertex input structure
931struct VertexInput {
932 @location(0) position: vec3<f32>,
933 @location(1) tex_coords: vec2<f32>,
934 @builtin(instance_index) instance_idx: u32,
935};
936
937// Vertex output structure to fragment shader
938// Must be exactly the same as the fragment shader
939struct VertexOutput {
940 @builtin(position) position: vec4<f32>,
941 @location(0) modified_tex_coords: vec2<f32>,
942 @location(1) color: vec4<f32>,
943 @location(2) original_tex_coords: vec2<f32>,
944};
945
946// Vertex shader entry point
947@vertex
948fn vs_main(
949 input: VertexInput,
950 // Instance attributes
951 @location(2) model_matrix0: vec4<f32>,
952 @location(3) model_matrix1: vec4<f32>,
953 @location(4) model_matrix2: vec4<f32>,
954 @location(5) model_matrix3: vec4<f32>,
955 @location(6) tex_multiplier: vec4<f32>,
956 @location(7) rotation_step: u32,
957 @location(8) color: vec4<f32>,
958) -> VertexOutput {
959
960 var output: VertexOutput;
961
962 // Reconstruct the model matrix from the instance data
963 let model_matrix = mat4x4<f32>(
964 model_matrix0,
965 model_matrix1,
966 model_matrix2,
967 model_matrix3,
968 );
969
970 // Compute world position
971 let world_position = model_matrix * vec4<f32>(input.position, 1.0);
972
973 // Apply view-projection matrix
974 output.position = camera_uniforms.view_proj * world_position;
975
976 // Decode rotation_step
977 let rotation_val = rotation_step & 3u; // Bits 0-1
978 let flip_x = (rotation_step & 4u) != 0u; // Bit 2
979 let flip_y = (rotation_step & 8u) != 0u; // Bit 3
980
981 // Rotate texture coordinates based on rotation_val
982 var rotated_tex_coords = input.tex_coords;
983 if (rotation_val == 1) {
984 // 90 degrees rotation
985 rotated_tex_coords = vec2<f32>(1.0 - input.tex_coords.y, input.tex_coords.x);
986 } else if (rotation_val == 2) {
987 // 180 degrees rotation
988 rotated_tex_coords = vec2<f32>(1.0 - input.tex_coords.x, 1.0 - input.tex_coords.y);
989 } else if (rotation_val == 3) {
990 // 270 degrees rotation
991 rotated_tex_coords = vec2<f32>(input.tex_coords.y, 1.0 - input.tex_coords.x);
992 }
993 // else rotation_val == Degrees0, no rotation
994
995 // Apply flipping
996 if (flip_x) {
997 rotated_tex_coords.x = 1.0 - rotated_tex_coords.x;
998 }
999 if (flip_y) {
1000 rotated_tex_coords.y = 1.0 - rotated_tex_coords.y;
1001 }
1002
1003 // Modify texture coordinates
1004 output.modified_tex_coords = rotated_tex_coords * tex_multiplier.xy + tex_multiplier.zw;
1005 output.original_tex_coords = input.tex_coords;
1006
1007 output.color = color;
1008
1009 return output;
1010}"
1011}
1012
1013const fn quad_shaders() -> (&'static str, &'static str) {
1014 let vertex_shader_source = "
1015// Bind Group 0: Uniforms (view-projection matrix)
1016struct Uniforms {
1017 view_proj: mat4x4<f32>,
1018};
1019// Camera (view projection matrix) is always first
1020@group(0) @binding(0)
1021var<uniform> camera_uniforms: Uniforms;
1022
1023
1024// Vertex input structure
1025struct VertexInput {
1026 @location(0) position: vec3<f32>,
1027};
1028
1029// Vertex output structure to fragment shader
1030// Must be exactly the same in both places
1031struct VertexOutput {
1032 @builtin(position) position: vec4<f32>, // MUST BE HERE, DO NOT REMOVE
1033 @location(0) color: vec4<f32>,
1034};
1035
1036// Vertex shader entry point
1037@vertex
1038fn vs_main(
1039 input: VertexInput,
1040 // Instance attributes
1041 @location(2) model_matrix0: vec4<f32>, // Always fixed
1042 @location(3) model_matrix1: vec4<f32>, // Always fixed
1043 @location(4) model_matrix2: vec4<f32>, // Always fixed
1044 @location(5) model_matrix3: vec4<f32>, // Always fixed
1045 @location(8) color: vec4<f32>, // Always fixed at position 8
1046) -> VertexOutput {
1047 var output: VertexOutput;
1048
1049 // Reconstruct the model matrix from the instance data
1050 let model_matrix = mat4x4<f32>(
1051 model_matrix0,
1052 model_matrix1,
1053 model_matrix2,
1054 model_matrix3,
1055 );
1056
1057 // Compute world position
1058 let world_position = model_matrix * vec4<f32>(input.position, 1.0);
1059
1060 // Apply view-projection matrix
1061 output.position = camera_uniforms.view_proj * world_position;
1062 output.color = color;
1063
1064 return output;
1065}
1066 ";
1067 let fragment_shader_source = "
1070
1071// Fragment input structure from vertex shader,
1072// Must be exactly the same in both places
1073struct VertexOutput {
1074 @builtin(position) position: vec4<f32>, // MUST BE HERE, DO NOT REMOVE
1075 @location(0) color: vec4<f32>,
1076};
1077
1078// Fragment shader entry point
1079@fragment
1080fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
1081 // It is a quad, so we only use the color
1082 return input.color;
1083}
1084
1085";
1086 (vertex_shader_source, fragment_shader_source)
1087}
1088
1089pub const SCREEN_QUAD_VERTEX_SHADER: &str = "
1090// Define the output structure
1091struct VertexOutput {
1092 @builtin(position) position: vec4<f32>,
1093 @location(0) texcoord: vec2<f32>,
1094};
1095
1096
1097@vertex
1098fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
1099 var positions = array<vec2<f32>, 6>(
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 vec2<f32>(1.0, -1.0),
1105 vec2<f32>(1.0, 1.0),
1106 );
1107
1108 var texcoords = array<vec2<f32>, 6>(
1109 vec2<f32>(0.0, 1.0),
1110 vec2<f32>(1.0, 1.0),
1111 vec2<f32>(0.0, 0.0),
1112 vec2<f32>(0.0, 0.0),
1113 vec2<f32>(1.0, 1.0),
1114 vec2<f32>(1.0, 0.0),
1115 );
1116
1117 var output: VertexOutput;
1118 output.position = vec4<f32>(positions[vertex_index], 0.0, 1.0);
1119 output.texcoord = texcoords[vertex_index];
1120 return output;
1121}
1122";
1123
1124pub const SCREEN_QUAD_FRAGMENT_SHADER: &str = "
1126@group(0) @binding(0) var game_texture: texture_2d<f32>;
1127@group(0) @binding(1) var game_sampler: sampler;
1128
1129@fragment
1130fn fs_main(@location(0) texcoord: vec2<f32>) -> @location(0) vec4<f32> {
1131 return textureSample(game_texture, game_sampler, texcoord);
1132}
1133";