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, 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], 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 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 }, Vertex {
168 position: [1.0, 0.0],
169 tex_coords: [RIGHT, DOWN],
170 }, Vertex {
172 position: [1.0, 1.0],
173 tex_coords: [RIGHT, 0.0],
174 }, Vertex {
176 position: [0.0, 1.0],
177 tex_coords: [0.0, 0.0],
178 }, ];
180
181pub 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 pub camera_bind_group_layout: BindGroupLayout,
198
199 pub camera_uniform_buffer: Buffer,
200 pub camera_bind_group: BindGroup,
201
202 pub sprite_texture_sampler_bind_group_layout: BindGroupLayout,
204
205 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 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 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 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
442fn 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
453fn 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 let luma_data = buffer
521 .pixels()
522 .flat_map(|p| [p[0]]) .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, 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#[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 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 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
1122pub 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";