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 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 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 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
1125pub 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";