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