1pub mod plugin;
6pub mod prelude;
7
8use int_math::{URect, UVec2, Vec2, Vec3};
9use limnus_assets::prelude::{Asset, Id, WeakId};
10use limnus_assets::Assets;
11use limnus_resource::prelude::Resource;
12use limnus_wgpu_math::{Matrix4, OrthoInfo, Vec4};
13use monotonic_time_rs::Millis;
14use std::cmp::Ordering;
15use std::fmt::Debug;
16use std::mem::swap;
17use std::sync::Arc;
18use swamp_font::Font;
19use swamp_font::FontRef;
20use swamp_font::WeakFontRef;
21use swamp_render::prelude::*;
22use swamp_wgpu_sprites::{SpriteInfo, SpriteInstanceUniform};
23use tracing::trace;
24use wgpu::{BindGroup, BindGroupLayout, Buffer, RenderPass, RenderPipeline};
25
26pub type MaterialRef = Id<Material>;
27pub type WeakMaterialRef = WeakId<Material>;
28
29pub trait FrameLookup {
30 fn lookup(&self, frame: u16) -> (&MaterialRef, URect);
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct FixedAtlas {
35 pub material: MaterialRef,
36 pub texture_size: UVec2,
37 pub one_cell_size: UVec2,
38 pub cell_count_size: UVec2,
39}
40
41impl FixedAtlas {
42 #[must_use]
45 pub fn new(one_cell_size: UVec2, texture_size: UVec2, material_ref: MaterialRef) -> Self {
46 let cell_count_size = UVec2::new(
47 texture_size.x / one_cell_size.x,
48 texture_size.y / one_cell_size.y,
49 );
50
51 assert_ne!(cell_count_size.x, 0, "illegal texture and one cell size");
52
53 Self {
54 material: material_ref,
55 texture_size,
56 one_cell_size,
57 cell_count_size,
58 }
59 }
60}
61
62impl FrameLookup for FixedAtlas {
63 fn lookup(&self, frame: u16) -> (&MaterialRef, URect) {
64 let x = frame % self.cell_count_size.x;
65 let y = frame / self.cell_count_size.x;
66
67 (
68 &self.material,
69 URect::new(
70 x * self.one_cell_size.x,
71 y * self.one_cell_size.y,
72 self.one_cell_size.x,
73 self.one_cell_size.y,
74 ),
75 )
76 }
77}
78
79#[derive(Debug)]
80pub struct FontAndMaterial {
81 pub font_ref: FontRef,
82 pub material_ref: MaterialRef,
83}
84
85pub trait Gfx {
86 fn sprite_atlas_frame(&mut self, position: Vec3, frame: u16, atlas: &impl FrameLookup);
87 fn sprite_atlas(&mut self, position: Vec3, atlas_rect: URect, material_ref: &MaterialRef);
88 fn draw_sprite(&mut self, position: Vec3, material_ref: &MaterialRef);
89 fn draw_sprite_ex(&mut self, position: Vec3, material_ref: &MaterialRef, params: &SpriteParams);
90 fn set_origin(&mut self, position: Vec2);
91
92 fn set_clear_color(&mut self, color: Color);
93
94 fn tilemap_params(
95 &mut self,
96 position: Vec3,
97 tiles: &[u16],
98 width: u16,
99 atlas_ref: &FixedAtlas,
100 scale: u8,
101 );
102
103 fn text_draw(&mut self, position: Vec3, text: &str, font_ref: &FontAndMaterial, color: &Color);
104
105 #[must_use]
106 fn now(&self) -> Millis;
107
108 #[must_use]
109 fn physical_aspect_ratio(&self) -> AspectRatio;
110
111 #[must_use]
112 fn physical_size(&self) -> UVec2;
113
114 fn set_viewport(&mut self, viewport_strategy: ViewportStrategy);
115
116 #[must_use]
117 fn viewport(&self) -> &ViewportStrategy;
118
119 fn set_scale(&mut self, scale_factor: VirtualScale);
120}
121
122fn to_wgpu_color(c: Color) -> wgpu::Color {
123 let f = c.to_f64();
124 wgpu::Color {
125 r: f.0,
126 g: f.1,
127 b: f.2,
128 a: f.3,
129 }
130}
131
132#[derive(Debug)]
133struct RenderItem {
134 position: Vec3,
135 material_ref: WeakMaterialRef,
136
137 renderable: Renderable,
138}
139
140#[derive(Debug)]
141pub struct Text {
142 text: String,
143 font_ref: WeakFontRef,
144 color: Color,
145}
146
147#[derive(Debug)]
148enum Renderable {
149 Sprite(Sprite),
150 TileMap(TileMap),
151 Text(Text),
152}
153
154#[derive(Resource)]
155pub struct Render {
156 index_buffer: Buffer, vertex_buffer: Buffer, sampler: wgpu::Sampler,
159 pipeline: RenderPipelineRef,
160 physical_surface_size: UVec2,
161 viewport_strategy: ViewportStrategy,
162 camera_bind_group: BindGroup,
164 #[allow(unused)]
165 camera_buffer: Buffer,
166
167 texture_sampler_bind_group_layout: BindGroupLayout,
169
170 quad_matrix_and_uv_instance_buffer: Buffer,
172
173 device: Arc<wgpu::Device>,
174 queue: Arc<wgpu::Queue>, items: Vec<RenderItem>,
178 origin: Vec2,
180
181 batch_offsets: Vec<(WeakMaterialRef, u32, u32)>,
183 viewport: URect,
184 clear_color: wgpu::Color,
185 last_render_at: Millis,
186 scale: f32,
187}
188
189impl Debug for Render {
190 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191 write!(f, "Render")
192 }
193}
194
195impl Gfx for Render {
196 fn sprite_atlas_frame(&mut self, position: Vec3, frame: u16, atlas: &impl FrameLookup) {
197 self.sprite_atlas_frame(position, frame, atlas);
198 }
199
200 fn sprite_atlas(&mut self, position: Vec3, atlas_rect: URect, material_ref: &MaterialRef) {
201 self.sprite_atlas(position, atlas_rect, material_ref);
202 }
203
204 fn draw_sprite(&mut self, position: Vec3, material_ref: &MaterialRef) {
205 self.draw_sprite(position, material_ref);
206 }
207
208 fn draw_sprite_ex(
209 &mut self,
210 position: Vec3,
211 material_ref: &MaterialRef,
212 params: &SpriteParams,
213 ) {
214 self.draw_sprite_ex(position, material_ref, *params);
215 }
216
217 fn set_origin(&mut self, position: Vec2) {
218 self.origin = position;
219 }
220
221 fn set_clear_color(&mut self, color: Color) {
222 self.clear_color = to_wgpu_color(color);
223 }
224
225 fn tilemap_params(
226 &mut self,
227 position: Vec3,
228 tiles: &[u16],
229 width: u16,
230 atlas_ref: &FixedAtlas,
231 scale: u8,
232 ) {
233 self.items.push(RenderItem {
234 position,
235 material_ref: (&atlas_ref.material).into(),
236 renderable: Renderable::TileMap(TileMap {
237 tiles_data_grid_size: UVec2::new(width, tiles.len() as u16 / width),
238 cell_count_size: atlas_ref.cell_count_size,
239 one_cell_size: atlas_ref.one_cell_size,
240 tiles: Vec::from(tiles),
241 scale,
242 }),
243 });
244 }
245
246 fn text_draw(
247 &mut self,
248 position: Vec3,
249 text: &str,
250 font_and_mat: &FontAndMaterial,
251 color: &Color,
252 ) {
253 self.items.push(RenderItem {
254 position,
255 material_ref: (&font_and_mat.material_ref).into(),
256 renderable: Renderable::Text(Text {
257 text: text.to_string(),
258 font_ref: (&font_and_mat.font_ref).into(),
259 color: *color,
260 }),
261 });
262 }
263
264 fn now(&self) -> Millis {
265 self.last_render_at
266 }
267
268 fn physical_aspect_ratio(&self) -> AspectRatio {
269 self.physical_surface_size.into()
270 }
271
272 fn physical_size(&self) -> UVec2 {
273 self.physical_surface_size
274 }
275
276 fn set_viewport(&mut self, viewport_strategy: ViewportStrategy) {
277 self.viewport_strategy = viewport_strategy;
278 }
279
280 fn viewport(&self) -> &ViewportStrategy {
281 &self.viewport_strategy
282 }
283
284 fn set_scale(&mut self, scale_factor: VirtualScale) {
285 match scale_factor {
286 VirtualScale::IntScale(scale) => self.scale = scale as f32,
287 VirtualScale::FloatScale(scale) => self.scale = scale,
288 }
289 }
290}
291
292impl Render {
293 #[must_use]
294 pub fn new(
295 device: Arc<wgpu::Device>,
296 queue: Arc<wgpu::Queue>, surface_texture_format: wgpu::TextureFormat,
298 physical_size: UVec2,
299 virtual_surface_size: UVec2,
300 now: Millis,
301 ) -> Self {
302 let (vertex_shader_source, fragment_shader_source) = sources();
303
304 let sprite_info = SpriteInfo::new(
305 &device,
306 surface_texture_format,
307 vertex_shader_source,
308 fragment_shader_source,
309 create_view_uniform_view_projection_matrix(physical_size),
310 );
311
312 Self {
313 device,
314 queue,
315 items: Vec::new(),
316 sampler: sprite_info.sampler,
318 pipeline: Arc::new(sprite_info.sprite_pipeline),
319 texture_sampler_bind_group_layout: sprite_info.sprite_texture_sampler_bind_group_layout,
320 index_buffer: sprite_info.index_buffer,
321 vertex_buffer: sprite_info.vertex_buffer,
322 quad_matrix_and_uv_instance_buffer: sprite_info.quad_matrix_and_uv_instance_buffer,
323 camera_bind_group: sprite_info.camera_bind_group,
324 batch_offsets: Vec::new(),
325 camera_buffer: sprite_info.camera_uniform_buffer,
326 viewport: Self::viewport_from_integer_scale(physical_size, virtual_surface_size),
327 clear_color: to_wgpu_color(Color::from_f32(0.008, 0.015, 0.008, 1.0)),
328 origin: Vec2::new(0, 0),
329 last_render_at: now,
330 physical_surface_size: physical_size,
331 viewport_strategy: ViewportStrategy::FitIntegerScaling(virtual_surface_size),
332 scale: 1.0,
333 }
334 }
335
336 pub fn set_now(&mut self, now: Millis) {
337 self.last_render_at = now;
338 }
339
340 pub const fn virtual_surface_size(&self) -> UVec2 {
341 match self.viewport_strategy {
342 ViewportStrategy::FitIntegerScaling(virtual_size)
343 | ViewportStrategy::FitFloatScaling(virtual_size) => virtual_size,
344 ViewportStrategy::MatchPhysicalSize => self.physical_surface_size,
345 }
346 }
347
348 pub const fn physical_surface_size(&self) -> UVec2 {
349 self.physical_surface_size
350 }
351
352 pub const fn viewport(&self) -> URect {
353 self.viewport
354 }
355
356 #[inline(always)]
357 fn push_sprite(&mut self, position: Vec3, material: &MaterialRef, sprite: Sprite) {
358 self.items.push(RenderItem {
359 position,
360 material_ref: material.into(),
361 renderable: Renderable::Sprite(sprite),
362 });
363 }
364
365 #[must_use]
366 pub fn viewport_from_integer_scale(physical_size: UVec2, virtual_size: UVec2) -> URect {
367 let window_aspect = physical_size.x as f32 / physical_size.y as f32;
368 let virtual_aspect = virtual_size.x as f32 / virtual_size.y as f32;
369
370 if physical_size.x < virtual_size.x || physical_size.y < virtual_size.y {
371 return URect::new(0, 0, physical_size.x, physical_size.y);
372 }
373
374 let mut integer_scale = if window_aspect > virtual_aspect {
375 physical_size.y / virtual_size.y
376 } else {
377 physical_size.x / virtual_size.x
378 };
379
380 if integer_scale < 1 {
381 integer_scale = 1;
382 }
383
384 let viewport_actual_size = UVec2::new(
385 virtual_size.x * integer_scale,
386 virtual_size.y * integer_scale,
387 );
388
389 let border_size = physical_size - viewport_actual_size;
390
391 let offset = border_size / 2;
392
393 URect::new(
394 offset.x,
395 offset.y,
396 viewport_actual_size.x,
397 viewport_actual_size.y,
398 )
399 }
400
401 #[must_use]
402 pub fn viewport_from_float_scale(physical_size: UVec2, virtual_size: UVec2) -> URect {
403 let window_aspect = physical_size.x as f32 / physical_size.y as f32;
404 let virtual_aspect = virtual_size.x as f32 / virtual_size.y as f32;
405
406 if physical_size.x < virtual_size.x || physical_size.y < virtual_size.y {
407 return URect::new(0, 0, physical_size.x, physical_size.y);
408 }
409
410 let mut float_scale = if window_aspect > virtual_aspect {
411 physical_size.y as f32 / virtual_size.y as f32
412 } else {
413 physical_size.x as f32 / virtual_size.x as f32
414 };
415
416 if float_scale < 0.01 {
417 float_scale = 0.01;
418 }
419
420 let viewport_actual_size = UVec2::new(
421 (virtual_size.x as f32 * float_scale) as u16,
422 (virtual_size.y as f32 * float_scale) as u16,
423 );
424
425 let border_size = physical_size - viewport_actual_size;
426
427 let offset = border_size / 2;
428
429 URect::new(
430 offset.x,
431 offset.y,
432 viewport_actual_size.x,
433 viewport_actual_size.y,
434 )
435 }
436
437 pub fn resize(&mut self, physical_size: UVec2) {
438 self.physical_surface_size = physical_size;
439 }
440
441 pub fn sprite_atlas(&mut self, position: Vec3, atlas_rect: URect, material_ref: &MaterialRef) {
449 self.push_sprite(
450 position,
451 material_ref,
452 Sprite {
453 params: SpriteParams {
454 texture_pos: atlas_rect.position,
455 texture_size: atlas_rect.size,
456 ..Default::default()
457 },
458 },
459 );
460 }
461
462 pub fn sprite_atlas_frame(&mut self, position: Vec3, frame: u16, atlas: &impl FrameLookup) {
463 let (material_ref, atlas_rect) = atlas.lookup(frame);
464 self.push_sprite(
465 position,
466 material_ref,
467 Sprite {
468 params: SpriteParams {
469 texture_pos: atlas_rect.position,
470 texture_size: atlas_rect.size,
471 ..Default::default()
472 },
473 },
474 );
475 }
476
477 pub fn sprite_atlas_frame_ex(
478 &mut self,
479 position: Vec3,
480 frame: u16,
481 atlas: &impl FrameLookup,
482 mut params: SpriteParams,
483 ) {
484 let (material_ref, atlas_rect) = atlas.lookup(frame);
485 params.texture_pos = atlas_rect.position;
486 params.texture_size = atlas_rect.size;
487 self.push_sprite(position, material_ref, Sprite { params });
488 }
489
490 pub fn draw_sprite(&mut self, position: Vec3, material: &MaterialRef) {
491 self.push_sprite(
492 position,
493 material,
494 Sprite {
495 params: SpriteParams::default(),
496 },
497 );
498 }
499
500 pub fn draw_sprite_ex(&mut self, position: Vec3, material: &MaterialRef, params: SpriteParams) {
501 self.push_sprite(position, material, Sprite { params });
502 }
503
504 pub const fn clear_color(&self) -> wgpu::Color {
505 self.clear_color
506 }
507
508 fn calculate_texture_coords_mul_add(atlas_rect: URect, texture_size: UVec2) -> Vec4 {
510 let x = atlas_rect.position.x as f32 / texture_size.x as f32;
511 let y = atlas_rect.position.y as f32 / texture_size.y as f32;
512 let width = atlas_rect.size.x as f32 / texture_size.x as f32;
513 let height = atlas_rect.size.y as f32 / texture_size.y as f32;
514 Vec4([width, height, x, y])
515 }
516
517 fn order_render_items_in_batches(&self) -> Vec<Vec<&RenderItem>> {
518 let mut material_batches: Vec<Vec<&RenderItem>> = Vec::new();
519 let mut current_batch: Vec<&RenderItem> = Vec::new();
520 let mut current_material: Option<&WeakMaterialRef> = None;
521
522 for render_item in &self.items {
523 if Some(&render_item.material_ref) != current_material {
524 if !current_batch.is_empty() {
525 material_batches.push(current_batch.clone());
526 current_batch.clear();
527 }
528 current_material = Some(&render_item.material_ref);
529 }
530 current_batch.push(render_item);
531 }
532
533 if !current_batch.is_empty() {
534 material_batches.push(current_batch);
535 }
536
537 material_batches
538 }
539
540 #[allow(clippy::too_many_lines)]
543 pub fn prepare_render(&mut self, materials: &Assets<Material>, fonts: &Assets<Font>) {
544 const FLIP_X_MASK: u32 = 0b0000_0100;
545 const FLIP_Y_MASK: u32 = 0b0000_1000;
546
547 sort_render_items_by_z_and_material(&mut self.items);
548
549 let batches = self.order_render_items_in_batches();
550
551 let mut quad_matrix_and_uv: Vec<SpriteInstanceUniform> = Vec::new();
552 let mut batch_vertex_ranges: Vec<(WeakMaterialRef, u32, u32)> = Vec::new();
553
554 for render_items in &batches {
555 let quad_len_before = quad_matrix_and_uv.len() as u32;
556
557 let weak_material_ref = render_items
559 .first()
560 .map(|item| {
561 let material_ref: WeakId<Material> = item.material_ref;
563 material_ref
564 })
565 .expect("Render items batch was empty");
566
567 let result = materials.get_weak(weak_material_ref);
568 if result.is_none() {
569 continue;
571 }
572 let material = result.unwrap();
573 let current_texture_size = material.texture_size;
574
575 for render_item in render_items {
576 match &render_item.renderable {
577 Renderable::Sprite(ref sprite) => {
578 let params = &sprite.params;
579 let mut size = params.texture_size;
580 if size.x == 0 && size.y == 0 {
581 size = current_texture_size;
582 }
583
584 let render_atlas = URect {
585 position: params.texture_pos,
586 size,
587 };
588
589 match params.rotation {
590 Rotation::Degrees90 | Rotation::Degrees270 => {
591 swap(&mut size.x, &mut size.y);
592 }
593 _ => {}
594 }
595
596 let model_matrix = Matrix4::from_translation(
597 render_item.position.x as f32,
598 render_item.position.y as f32,
599 0.0,
600 ) * Matrix4::from_scale(
601 (size.x * params.scale as u16) as f32,
602 (size.y * params.scale as u16) as f32,
603 1.0,
604 );
605
606 let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
607 render_atlas,
608 current_texture_size,
609 );
610
611 let mut rotation_value = match params.rotation {
612 Rotation::Degrees0 => 0,
613 Rotation::Degrees90 => 1,
614 Rotation::Degrees180 => 2,
615 Rotation::Degrees270 => 3,
616 };
617
618 if params.flip_x {
619 rotation_value |= FLIP_X_MASK;
620 }
621 if params.flip_y {
622 rotation_value |= FLIP_Y_MASK;
623 }
624
625 let quad_instance = SpriteInstanceUniform::new(
626 model_matrix,
627 tex_coords_mul_add,
628 rotation_value,
629 Vec4(params.color.to_f32_slice()),
630 );
631 quad_matrix_and_uv.push(quad_instance);
632 }
633
634 Renderable::Text(text) => {
635 let result = fonts.get_weak(text.font_ref);
636 if result.is_none() {
637 continue;
638 }
639 let font = result.unwrap();
640
641 let glyphs = font.draw(&text.text);
642 for glyph in glyphs {
643 let pos = render_item.position + Vec3::from(glyph.relative_position);
644 let texture_size = glyph.texture_rectangle.size;
645 let model_matrix =
646 Matrix4::from_translation(pos.x as f32, pos.y as f32, 0.0)
647 * Matrix4::from_scale(
648 texture_size.x as f32,
649 texture_size.y as f32,
650 1.0,
651 );
652 let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
653 glyph.texture_rectangle,
654 current_texture_size,
655 );
656
657 let quad_instance = SpriteInstanceUniform::new(
658 model_matrix,
659 tex_coords_mul_add,
660 0,
661 Vec4(text.color.to_f32_slice()),
662 );
663 quad_matrix_and_uv.push(quad_instance);
664 }
665 }
666
667 Renderable::TileMap(ref tile_map) => {
668 for (index, tile) in tile_map.tiles.iter().enumerate() {
669 let cell_pos_x = (index as u16 % tile_map.tiles_data_grid_size.x)
670 * tile_map.one_cell_size.x
671 * tile_map.scale as u16;
672 let cell_pos_y = (index as u16 / tile_map.tiles_data_grid_size.x)
673 * tile_map.one_cell_size.y
674 * tile_map.scale as u16;
675 let cell_x = *tile % tile_map.cell_count_size.x;
676 let cell_y = *tile / tile_map.cell_count_size.x;
677
678 let tex_x = cell_x * tile_map.one_cell_size.x;
679 let tex_y = cell_y * tile_map.one_cell_size.x;
680
681 let cell_texture_area = URect::new(
682 tex_x,
683 tex_y,
684 tile_map.one_cell_size.x,
685 tile_map.one_cell_size.y,
686 );
687
688 let cell_model_matrix = Matrix4::from_translation(
689 (render_item.position.x + cell_pos_x as i16) as f32,
690 (render_item.position.y + cell_pos_y as i16) as f32,
691 0.0,
692 ) * Matrix4::from_scale(
693 (tile_map.one_cell_size.x * tile_map.scale as u16) as f32,
694 (tile_map.one_cell_size.y * tile_map.scale as u16) as f32,
695 1.0,
696 );
697
698 let cell_tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
699 cell_texture_area,
700 current_texture_size,
701 );
702
703 let quad_instance = SpriteInstanceUniform::new(
704 cell_model_matrix,
705 cell_tex_coords_mul_add,
706 0,
707 Vec4([1.0, 1.0, 1.0, 1.0]),
708 );
709 quad_matrix_and_uv.push(quad_instance);
710 }
711 }
712 }
713 }
714
715 let quad_count = quad_matrix_and_uv.len() as u32 - quad_len_before;
716 batch_vertex_ranges.push((weak_material_ref, quad_len_before, quad_count));
717 }
718
719 self.queue.write_buffer(
721 &self.quad_matrix_and_uv_instance_buffer,
722 0,
723 bytemuck::cast_slice(&quad_matrix_and_uv),
724 );
725
726 self.batch_offsets = batch_vertex_ranges;
727 }
728
729 pub fn render(
732 &mut self,
733 render_pass: &mut RenderPass,
734 materials: &Assets<Material>,
735 fonts: &Assets<Font>,
736 now: Millis,
737 ) {
738 trace!("start render()");
739 self.last_render_at = now;
740
741 self.viewport = match self.viewport_strategy {
742 ViewportStrategy::FitIntegerScaling(virtual_surface_size) => {
743 Self::viewport_from_integer_scale(self.physical_surface_size, virtual_surface_size)
744 }
745 ViewportStrategy::FitFloatScaling(virtual_surface_size) => {
746 Self::viewport_from_float_scale(self.physical_surface_size, virtual_surface_size)
747 }
748 ViewportStrategy::MatchPhysicalSize => URect::new(
749 0,
750 0,
751 self.physical_surface_size.x,
752 self.physical_surface_size.y,
753 ),
754 };
755
756 let view_proj_matrix = match self.viewport_strategy {
757 ViewportStrategy::MatchPhysicalSize => {
758 create_view_uniform_view_projection_matrix(self.physical_surface_size)
759 }
760 ViewportStrategy::FitFloatScaling(virtual_surface_size)
761 | ViewportStrategy::FitIntegerScaling(virtual_surface_size) => {
762 create_view_projection_matrix_from_virtual(
763 virtual_surface_size.x,
764 virtual_surface_size.y,
765 )
766 }
767 };
768
769 let scale_matrix = Matrix4::from_scale(self.scale, self.scale, 0.0);
770 let origin_translation_matrix =
771 Matrix4::from_translation(-self.origin.x as f32, -self.origin.y as f32, 0.0);
772
773 let total_matrix = scale_matrix * view_proj_matrix * origin_translation_matrix;
774
775 self.queue.write_buffer(
777 &self.camera_buffer,
778 0,
779 bytemuck::cast_slice(&[total_matrix]),
780 );
781
782 self.prepare_render(materials, fonts);
783
784 render_pass.set_viewport(
785 self.viewport.position.x as f32,
786 self.viewport.position.y as f32,
787 self.viewport.size.x as f32,
788 self.viewport.size.y as f32,
789 0.0,
790 1.0,
791 );
792
793 render_pass.set_pipeline(&self.pipeline);
794
795 render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
797 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
798
799 render_pass.set_vertex_buffer(1, self.quad_matrix_and_uv_instance_buffer.slice(..));
801
802 render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
804
805 let num_indices = swamp_wgpu_sprites::INDICES.len() as u32;
806
807 for &(weak_material_ref, start, count) in &self.batch_offsets {
808 let wgpu_material = materials
809 .get_weak(weak_material_ref)
810 .expect("no such material");
811 render_pass.set_bind_group(1, &wgpu_material.texture_and_sampler_bind_group, &[]);
813
814 trace!(material=%weak_material_ref, start=%start, count=%count, "draw instanced");
816 render_pass.draw_indexed(0..num_indices, 0, start..(start + count));
817 }
818
819 self.items.clear();
820 }
821
822 pub fn material_from_texture(&self, texture: wgpu::Texture, label: &str) -> Material {
823 trace!("load texture from memory with name: '{label}'");
824 let size = &texture.size();
825 let texture_and_sampler_bind_group =
826 swamp_wgpu_sprites::create_sprite_texture_and_sampler_bind_group(
827 &self.device,
828 &self.texture_sampler_bind_group_layout,
829 &texture,
830 &self.sampler,
831 label,
832 );
833
834 let texture_size = UVec2::new(size.width as u16, size.height as u16);
835
836 Material {
837 texture_and_sampler_bind_group,
838 texture_size,
840 }
841 }
842}
843
844fn create_view_projection_matrix_from_virtual(virtual_width: u16, virtual_height: u16) -> Matrix4 {
847 OrthoInfo {
848 left: 0.0,
849 right: virtual_width as f32,
850 bottom: 0.0,
851 top: virtual_height as f32,
852 near: 1.0,
853 far: -1.0,
854 }
855 .into()
856}
857
858fn create_view_uniform_view_projection_matrix(viewport_size: UVec2) -> Matrix4 {
859 let viewport_width = viewport_size.x as f32;
860 let viewport_height = viewport_size.y as f32;
861
862 let viewport_aspect_ratio = viewport_width / viewport_height;
863
864 let scale_x = 1.0;
865 let scale_y = viewport_aspect_ratio; let view_projection_matrix = [
868 [scale_x, 0.0, 0.0, 0.0],
869 [0.0, scale_y, 0.0, 0.0],
870 [0.0, 0.0, -1.0, 0.0],
871 [0.0, 0.0, 0.0, 1.0],
872 ];
873
874 view_projection_matrix.into()
875}
876
877fn sort_render_items_by_z_and_material(items: &mut [RenderItem]) {
878 items.sort_by_key(|item| (item.position.z, item.material_ref));
879}
880
881#[derive(Debug, Clone, Copy, Default)]
882pub enum Rotation {
883 #[default]
884 Degrees0,
885 Degrees90,
886 Degrees180,
887 Degrees270,
888}
889
890#[derive(Debug, Copy, Clone)]
891pub struct SpriteParams {
892 pub texture_size: UVec2,
893 pub texture_pos: UVec2,
894 pub scale: u8,
895 pub rotation: Rotation,
896 pub flip_x: bool,
897 pub flip_y: bool,
898 pub pivot: Vec2,
899 pub color: Color,
900}
901
902impl Default for SpriteParams {
903 fn default() -> Self {
904 Self {
905 texture_size: UVec2::new(0, 0),
906 texture_pos: UVec2::new(0, 0),
907 pivot: Vec2::new(0, 0),
908 flip_x: false,
909 flip_y: false,
910 color: Color::from_octet(255, 255, 255, 255),
911 scale: 1,
912 rotation: Rotation::Degrees0,
913 }
914 }
915}
916
917#[derive(Debug, PartialEq, Eq, Asset)]
918pub struct Material {
919 pub texture_and_sampler_bind_group: BindGroup,
920 pub texture_size: UVec2,
921}
922
923impl PartialOrd<Self> for Material {
924 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
925 Some(
926 self.texture_and_sampler_bind_group
927 .cmp(&other.texture_and_sampler_bind_group),
928 )
929 }
930}
931
932impl Ord for Material {
933 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
934 self.texture_and_sampler_bind_group
935 .cmp(&other.texture_and_sampler_bind_group)
936 }
937}
938
939#[derive(Debug)]
940pub struct Sprite {
941 pub params: SpriteParams,
942}
943
944#[derive(Debug)]
945pub struct TileMap {
946 pub tiles_data_grid_size: UVec2,
947 pub cell_count_size: UVec2,
948 pub one_cell_size: UVec2,
949 pub tiles: Vec<u16>,
950 pub scale: u8,
951}
952
953pub type RenderPipelineRef = Arc<RenderPipeline>;
954
955const fn sources() -> (&'static str, &'static str) {
956 let vertex_shader_source = "
957// Bind Group 0: Uniforms (view-projection matrix)
958struct Uniforms {
959 view_proj: mat4x4<f32>,
960};
961
962@group(0) @binding(0)
963var<uniform> camera_uniforms: Uniforms;
964
965// Bind Group 1: Texture and Sampler (Unused in Vertex Shader but needed for consistency)
966@group(1) @binding(0)
967var diffuse_texture: texture_2d<f32>;
968
969@group(1) @binding(1)
970var sampler_diffuse: sampler;
971
972// Vertex input structure
973struct VertexInput {
974 @location(0) position: vec3<f32>,
975 @location(1) tex_coords: vec2<f32>,
976 @builtin(instance_index) instance_idx: u32,
977};
978
979// Vertex output structure to fragment shader
980struct VertexOutput {
981 @builtin(position) position: vec4<f32>,
982 @location(0) tex_coords: vec2<f32>,
983 @location(1) color: vec4<f32>,
984};
985
986// Vertex shader entry point
987@vertex
988fn vs_main(
989 input: VertexInput,
990 // Instance attributes
991 @location(2) model_matrix0: vec4<f32>,
992 @location(3) model_matrix1: vec4<f32>,
993 @location(4) model_matrix2: vec4<f32>,
994 @location(5) model_matrix3: vec4<f32>,
995 @location(6) tex_multiplier: vec4<f32>,
996 @location(7) rotation_step: u32,
997 @location(8) color: vec4<f32>,
998) -> VertexOutput {
999 var output: VertexOutput;
1000
1001 // Reconstruct the model matrix from the instance data
1002 let model_matrix = mat4x4<f32>(
1003 model_matrix0,
1004 model_matrix1,
1005 model_matrix2,
1006 model_matrix3,
1007 );
1008
1009 // Compute world position
1010 let world_position = model_matrix * vec4<f32>(input.position, 1.0);
1011
1012 // Apply view-projection matrix
1013 output.position = camera_uniforms.view_proj * world_position;
1014
1015 // Decode rotation_step
1016 let rotation_val = rotation_step & 3u; // Bits 0-1
1017 let flip_x = (rotation_step & 4u) != 0u; // Bit 2
1018 let flip_y = (rotation_step & 8u) != 0u; // Bit 3
1019
1020 // Rotate texture coordinates based on rotation_val
1021 var rotated_tex_coords = input.tex_coords;
1022 if (rotation_val == 1) {
1023 // 90 degrees rotation
1024 rotated_tex_coords = vec2<f32>(1.0 - input.tex_coords.y, input.tex_coords.x);
1025 } else if (rotation_val == 2) {
1026 // 180 degrees rotation
1027 rotated_tex_coords = vec2<f32>(1.0 - input.tex_coords.x, 1.0 - input.tex_coords.y);
1028 } else if (rotation_val == 3) {
1029 // 270 degrees rotation
1030 rotated_tex_coords = vec2<f32>(input.tex_coords.y, 1.0 - input.tex_coords.x);
1031 }
1032 // else rotation_val == Degrees0, no rotation
1033
1034 // Apply flipping
1035 if (flip_x) {
1036 rotated_tex_coords.x = 1.0 - rotated_tex_coords.x;
1037 }
1038 if (flip_y) {
1039 rotated_tex_coords.y = 1.0 - rotated_tex_coords.y;
1040 }
1041
1042 // Modify texture coordinates
1043 output.tex_coords = rotated_tex_coords * tex_multiplier.xy + tex_multiplier.zw;
1044 output.color = color;
1045
1046 return output;
1047}
1048 ";
1049 let fragment_shader_source = "
1052
1053// Bind Group 1: Texture and Sampler
1054@group(1) @binding(0)
1055var diffuse_texture: texture_2d<f32>;
1056
1057@group(1) @binding(1)
1058var sampler_diffuse: sampler;
1059
1060// Fragment input structure from vertex shader
1061struct VertexOutput {
1062 @builtin(position) position: vec4<f32>,
1063 @location(0) tex_coords: vec2<f32>,
1064 @location(1) color: vec4<f32>,
1065};
1066
1067// Fragment shader entry point
1068@fragment
1069fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
1070 // Sample the texture using the texture coordinates
1071 let texture_color = textureSample(diffuse_texture, sampler_diffuse, input.tex_coords);
1072
1073 // Apply color modulation and opacity
1074 let final_color = texture_color * input.color;
1075
1076 return final_color;
1077}
1078
1079";
1080 (vertex_shader_source, fragment_shader_source)
1081}