1pub mod plugin;
6pub mod prelude;
7
8use int_math::{URect, UVec2, Vec2, Vec3};
9use limnus_assets::Assets;
10use limnus_assets::prelude::{Asset, Id, RawAssetId, RawWeakId, WeakId};
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, PartialEq)]
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 QuadColor(QuadColor),
151 NineSlice(NineSlice),
152 TileMap(TileMap),
153 Text(Text),
154}
155
156#[derive(Resource)]
157pub struct Render {
158 index_buffer: Buffer, vertex_buffer: Buffer, sampler: wgpu::Sampler,
161 pipeline: RenderPipelineRef,
162 physical_surface_size: UVec2,
163 viewport_strategy: ViewportStrategy,
164 camera_bind_group: BindGroup,
166 #[allow(unused)]
167 camera_buffer: Buffer,
168
169 texture_sampler_bind_group_layout: BindGroupLayout,
171
172 quad_matrix_and_uv_instance_buffer: Buffer,
174
175 device: Arc<wgpu::Device>,
176 queue: Arc<wgpu::Queue>, items: Vec<RenderItem>,
180 origin: Vec2,
182
183 batch_offsets: Vec<(WeakMaterialRef, u32, u32)>,
185 viewport: URect,
186 clear_color: wgpu::Color,
187 last_render_at: Millis,
188 scale: f32,
189}
190
191impl Debug for Render {
192 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193 write!(f, "Render")
194 }
195}
196
197impl Gfx for Render {
198 fn sprite_atlas_frame(&mut self, position: Vec3, frame: u16, atlas: &impl FrameLookup) {
199 self.sprite_atlas_frame(position, frame, atlas);
200 }
201
202 fn sprite_atlas(&mut self, position: Vec3, atlas_rect: URect, material_ref: &MaterialRef) {
203 self.sprite_atlas(position, atlas_rect, material_ref);
204 }
205
206 fn draw_sprite(&mut self, position: Vec3, material_ref: &MaterialRef) {
207 self.draw_sprite(position, material_ref);
208 }
209
210 fn draw_sprite_ex(
211 &mut self,
212 position: Vec3,
213 material_ref: &MaterialRef,
214 params: &SpriteParams,
215 ) {
216 self.draw_sprite_ex(position, material_ref, *params);
217 }
218
219 fn set_origin(&mut self, position: Vec2) {
220 self.origin = position;
221 }
222
223 fn set_clear_color(&mut self, color: Color) {
224 self.clear_color = to_wgpu_color(color);
225 }
226
227 fn tilemap_params(
228 &mut self,
229 position: Vec3,
230 tiles: &[u16],
231 width: u16,
232 atlas_ref: &FixedAtlas,
233 scale: u8,
234 ) {
235 self.items.push(RenderItem {
236 position,
237 material_ref: (&atlas_ref.material).into(),
238 renderable: Renderable::TileMap(TileMap {
239 tiles_data_grid_size: UVec2::new(width, tiles.len() as u16 / width),
240 cell_count_size: atlas_ref.cell_count_size,
241 one_cell_size: atlas_ref.one_cell_size,
242 tiles: Vec::from(tiles),
243 scale,
244 }),
245 });
246 }
247
248 fn text_draw(
249 &mut self,
250 position: Vec3,
251 text: &str,
252 font_and_mat: &FontAndMaterial,
253 color: &Color,
254 ) {
255 self.items.push(RenderItem {
256 position,
257 material_ref: (&font_and_mat.material_ref).into(),
258 renderable: Renderable::Text(Text {
259 text: text.to_string(),
260 font_ref: (&font_and_mat.font_ref).into(),
261 color: *color,
262 }),
263 });
264 }
265
266 fn now(&self) -> Millis {
267 self.last_render_at
268 }
269
270 fn physical_aspect_ratio(&self) -> AspectRatio {
271 self.physical_surface_size.into()
272 }
273
274 fn physical_size(&self) -> UVec2 {
275 self.physical_surface_size
276 }
277
278 fn set_viewport(&mut self, viewport_strategy: ViewportStrategy) {
279 self.viewport_strategy = viewport_strategy;
280 }
281
282 fn viewport(&self) -> &ViewportStrategy {
283 &self.viewport_strategy
284 }
285
286 fn set_scale(&mut self, scale_factor: VirtualScale) {
287 match scale_factor {
288 VirtualScale::IntScale(scale) => self.scale = scale as f32,
289 VirtualScale::FloatScale(scale) => self.scale = scale,
290 }
291 }
292}
293
294impl Render {
295 #[must_use]
296 pub fn new(
297 device: Arc<wgpu::Device>,
298 queue: Arc<wgpu::Queue>, surface_texture_format: wgpu::TextureFormat,
300 physical_size: UVec2,
301 virtual_surface_size: UVec2,
302 now: Millis,
303 ) -> Self {
304 let (vertex_shader_source, fragment_shader_source) = sources();
305
306 let sprite_info = SpriteInfo::new(
307 &device,
308 surface_texture_format,
309 vertex_shader_source,
310 fragment_shader_source,
311 create_view_uniform_view_projection_matrix(physical_size),
312 );
313
314 Self {
315 device,
316 queue,
317 items: Vec::new(),
318 sampler: sprite_info.sampler,
320 pipeline: Arc::new(sprite_info.sprite_pipeline),
321 texture_sampler_bind_group_layout: sprite_info.sprite_texture_sampler_bind_group_layout,
322 index_buffer: sprite_info.index_buffer,
323 vertex_buffer: sprite_info.vertex_buffer,
324 quad_matrix_and_uv_instance_buffer: sprite_info.quad_matrix_and_uv_instance_buffer,
325 camera_bind_group: sprite_info.camera_bind_group,
326 batch_offsets: Vec::new(),
327 camera_buffer: sprite_info.camera_uniform_buffer,
328 viewport: Self::viewport_from_integer_scale(physical_size, virtual_surface_size),
329 clear_color: to_wgpu_color(Color::from_f32(0.008, 0.015, 0.008, 1.0)),
330 origin: Vec2::new(0, 0),
331 last_render_at: now,
332 physical_surface_size: physical_size,
333 viewport_strategy: ViewportStrategy::FitIntegerScaling(virtual_surface_size),
334 scale: 1.0,
335 }
336 }
337
338 pub fn set_now(&mut self, now: Millis) {
339 self.last_render_at = now;
340 }
341
342 pub const fn virtual_surface_size(&self) -> UVec2 {
343 match self.viewport_strategy {
344 ViewportStrategy::FitIntegerScaling(virtual_size)
345 | ViewportStrategy::FitFloatScaling(virtual_size) => virtual_size,
346 ViewportStrategy::MatchPhysicalSize => self.physical_surface_size,
347 }
348 }
349
350 pub const fn physical_surface_size(&self) -> UVec2 {
351 self.physical_surface_size
352 }
353
354 pub const fn viewport(&self) -> URect {
355 self.viewport
356 }
357
358 #[inline(always)]
359 fn push_sprite(&mut self, position: Vec3, material: &MaterialRef, sprite: Sprite) {
360 self.items.push(RenderItem {
361 position,
362 material_ref: material.into(),
363 renderable: Renderable::Sprite(sprite),
364 });
365 }
366
367 #[must_use]
368 pub fn viewport_from_integer_scale(physical_size: UVec2, virtual_size: UVec2) -> URect {
369 let window_aspect = physical_size.x as f32 / physical_size.y as f32;
370 let virtual_aspect = virtual_size.x as f32 / virtual_size.y as f32;
371
372 if physical_size.x < virtual_size.x || physical_size.y < virtual_size.y {
373 return URect::new(0, 0, physical_size.x, physical_size.y);
374 }
375
376 let mut integer_scale = if window_aspect > virtual_aspect {
377 physical_size.y / virtual_size.y
378 } else {
379 physical_size.x / virtual_size.x
380 };
381
382 if integer_scale < 1 {
383 integer_scale = 1;
384 }
385
386 let viewport_actual_size = UVec2::new(
387 virtual_size.x * integer_scale,
388 virtual_size.y * integer_scale,
389 );
390
391 let border_size = physical_size - viewport_actual_size;
392
393 let offset = border_size / 2;
394
395 URect::new(
396 offset.x,
397 offset.y,
398 viewport_actual_size.x,
399 viewport_actual_size.y,
400 )
401 }
402
403 #[must_use]
404 pub fn viewport_from_float_scale(physical_size: UVec2, virtual_size: UVec2) -> URect {
405 let window_aspect = physical_size.x as f32 / physical_size.y as f32;
406 let virtual_aspect = virtual_size.x as f32 / virtual_size.y as f32;
407
408 if physical_size.x < virtual_size.x || physical_size.y < virtual_size.y {
409 return URect::new(0, 0, physical_size.x, physical_size.y);
410 }
411
412 let mut float_scale = if window_aspect > virtual_aspect {
413 physical_size.y as f32 / virtual_size.y as f32
414 } else {
415 physical_size.x as f32 / virtual_size.x as f32
416 };
417
418 if float_scale < 0.01 {
419 float_scale = 0.01;
420 }
421
422 let viewport_actual_size = UVec2::new(
423 (virtual_size.x as f32 * float_scale) as u16,
424 (virtual_size.y as f32 * float_scale) as u16,
425 );
426
427 let border_size = physical_size - viewport_actual_size;
428
429 let offset = border_size / 2;
430
431 URect::new(
432 offset.x,
433 offset.y,
434 viewport_actual_size.x,
435 viewport_actual_size.y,
436 )
437 }
438
439 pub fn resize(&mut self, physical_size: UVec2) {
440 self.physical_surface_size = physical_size;
441 }
442
443 pub fn sprite_atlas(&mut self, position: Vec3, atlas_rect: URect, material_ref: &MaterialRef) {
451 self.push_sprite(
452 position,
453 material_ref,
454 Sprite {
455 params: SpriteParams {
456 texture_pos: atlas_rect.position,
457 texture_size: atlas_rect.size,
458 ..Default::default()
459 },
460 },
461 );
462 }
463
464 pub fn sprite_atlas_frame(&mut self, position: Vec3, frame: u16, atlas: &impl FrameLookup) {
465 let (material_ref, atlas_rect) = atlas.lookup(frame);
466 self.push_sprite(
467 position,
468 material_ref,
469 Sprite {
470 params: SpriteParams {
471 texture_pos: atlas_rect.position,
472 texture_size: atlas_rect.size,
473 ..Default::default()
474 },
475 },
476 );
477 }
478
479 pub fn sprite_atlas_frame_ex(
480 &mut self,
481 position: Vec3,
482 frame: u16,
483 atlas: &impl FrameLookup,
484 mut params: SpriteParams,
485 ) {
486 let (material_ref, atlas_rect) = atlas.lookup(frame);
487 params.texture_pos = atlas_rect.position;
488 params.texture_size = atlas_rect.size;
489 self.push_sprite(position, material_ref, Sprite { params });
490 }
491
492 pub fn draw_sprite(&mut self, position: Vec3, material: &MaterialRef) {
493 self.push_sprite(
494 position,
495 material,
496 Sprite {
497 params: SpriteParams::default(),
498 },
499 );
500 }
501
502 pub fn draw_sprite_ex(&mut self, position: Vec3, material: &MaterialRef, params: SpriteParams) {
503 self.push_sprite(position, material, Sprite { params });
504 }
505
506 pub fn draw_quad(&mut self, position: Vec3, size: UVec2, color: Color) {
507 self.items.push(RenderItem {
508 position,
509 material_ref: WeakId::<Material>::new(RawWeakId::with_asset_type::<Material>(
510 RawAssetId::new(0, 0),
511 "nothing".into(),
512 )),
513 renderable: Renderable::QuadColor(QuadColor { size, color }),
514 });
515 }
516
517 #[allow(clippy::too_many_arguments)]
518 pub fn draw_nine_slice(
519 &mut self,
520 position: Vec3,
521 size: UVec2,
522 corner_size: UVec2,
523 texture_window_size: UVec2,
524 material_ref: &MaterialRef,
525 atlas_offset: UVec2,
526 color: Color,
527 ) {
528 self.items.push(RenderItem {
529 position,
530 material_ref: material_ref.into(),
531 renderable: Renderable::NineSlice(NineSlice {
532 corner_size,
533 texture_window_size,
534 size,
535 atlas_offset,
536 color,
537 }),
538 });
539 }
540
541 pub const fn clear_color(&self) -> wgpu::Color {
542 self.clear_color
543 }
544
545 fn calculate_texture_coords_mul_add(atlas_rect: URect, texture_size: UVec2) -> Vec4 {
547 let x = atlas_rect.position.x as f32 / texture_size.x as f32;
548 let y = atlas_rect.position.y as f32 / texture_size.y as f32;
549 let width = atlas_rect.size.x as f32 / texture_size.x as f32;
550 let height = atlas_rect.size.y as f32 / texture_size.y as f32;
551 Vec4([width, height, x, y])
552 }
553
554 fn order_render_items_in_batches(&self) -> Vec<Vec<&RenderItem>> {
555 let mut material_batches: Vec<Vec<&RenderItem>> = Vec::new();
556 let mut current_batch: Vec<&RenderItem> = Vec::new();
557 let mut current_material: Option<&WeakMaterialRef> = None;
558
559 for render_item in &self.items {
560 if Some(&render_item.material_ref) != current_material {
561 if !current_batch.is_empty() {
562 material_batches.push(current_batch.clone());
563 current_batch.clear();
564 }
565 current_material = Some(&render_item.material_ref);
566 }
567 current_batch.push(render_item);
568 }
569
570 if !current_batch.is_empty() {
571 material_batches.push(current_batch);
572 }
573
574 material_batches
575 }
576
577 pub fn quad_helper_uniform(
578 position: Vec3,
579 quad_size: UVec2,
580 render_atlas: URect,
581 color: Color,
582
583 current_texture_size: UVec2,
584 ) -> SpriteInstanceUniform {
586 let model_matrix = Matrix4::from_translation(position.x as f32, position.y as f32, 0.0)
587 * Matrix4::from_scale(quad_size.x as f32, quad_size.y as f32, 1.0);
588
589 let tex_coords_mul_add =
590 Self::calculate_texture_coords_mul_add(render_atlas, current_texture_size);
591
592 let rotation_value = 0;
593
594 SpriteInstanceUniform::new(
595 model_matrix,
596 tex_coords_mul_add,
597 rotation_value,
598 Vec4(color.to_f32_slice()),
599 true,
600 )
601 }
602
603 #[allow(clippy::too_many_lines)]
606 pub fn prepare_render(&mut self, materials: &Assets<Material>, fonts: &Assets<Font>) {
607 const FLIP_X_MASK: u32 = 0b0000_0100;
608 const FLIP_Y_MASK: u32 = 0b0000_1000;
609
610 sort_render_items_by_z_and_material(&mut self.items);
611
612 let batches = self.order_render_items_in_batches();
613
614 let mut quad_matrix_and_uv: Vec<SpriteInstanceUniform> = Vec::new();
615 let mut batch_vertex_ranges: Vec<(WeakMaterialRef, u32, u32)> = Vec::new();
616
617 for render_items in &batches {
618 let quad_len_before = quad_matrix_and_uv.len() as u32;
619
620 let weak_material_ref = render_items
622 .first()
623 .map(|item| {
624 let material_ref: WeakId<Material> = item.material_ref;
626 material_ref
627 })
628 .expect("Render items batch was empty");
629
630 let result = materials.get_weak(weak_material_ref);
631 if result.is_none() {
632 continue;
634 }
635 let material = result.unwrap();
636 let current_texture_size = material.texture_size;
637
638 for render_item in render_items {
639 match &render_item.renderable {
640 Renderable::Sprite(sprite) => {
641 let params = &sprite.params;
642 let mut size = params.texture_size;
643 if size.x == 0 && size.y == 0 {
644 size = current_texture_size;
645 }
646
647 let render_atlas = URect {
648 position: params.texture_pos,
649 size,
650 };
651
652 match params.rotation {
653 Rotation::Degrees90 | Rotation::Degrees270 => {
654 swap(&mut size.x, &mut size.y);
655 }
656 _ => {}
657 }
658
659 let model_matrix = Matrix4::from_translation(
660 render_item.position.x as f32,
661 render_item.position.y as f32,
662 0.0,
663 ) * Matrix4::from_scale(
664 (size.x * params.scale as u16) as f32,
665 (size.y * params.scale as u16) as f32,
666 1.0,
667 );
668
669 let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
670 render_atlas,
671 current_texture_size,
672 );
673
674 let mut rotation_value = match params.rotation {
675 Rotation::Degrees0 => 0,
676 Rotation::Degrees90 => 1,
677 Rotation::Degrees180 => 2,
678 Rotation::Degrees270 => 3,
679 };
680
681 if params.flip_x {
682 rotation_value |= FLIP_X_MASK;
683 }
684 if params.flip_y {
685 rotation_value |= FLIP_Y_MASK;
686 }
687
688 let quad_instance = SpriteInstanceUniform::new(
689 model_matrix,
690 tex_coords_mul_add,
691 rotation_value,
692 Vec4(params.color.to_f32_slice()),
693 true,
694 );
695 quad_matrix_and_uv.push(quad_instance);
696 }
697
698 Renderable::NineSlice(nine_slice) => {
699 Self::prepare_nine_slice(
700 nine_slice,
701 render_item.position,
702 &mut quad_matrix_and_uv,
703 current_texture_size,
704 );
705 }
706
707 Renderable::QuadColor(quad) => {
708 let model_matrix =
730 Matrix4::from_translation(
731 render_item.position.x as f32,
732 render_item.position.y as f32,
733 0.0,
734 ) * Matrix4::from_scale(quad.size.x as f32, quad.size.y as f32, 1.0);
735
736 let tex_coords_mul_add = Vec4([
760 0.0, 0.0, 0.0, 0.0,
763 ]);
764 let rotation_value = 0;
765
766 let quad_instance = SpriteInstanceUniform::new(
767 model_matrix,
768 tex_coords_mul_add,
769 rotation_value,
770 Vec4(quad.color.to_f32_slice()),
771 false,
772 );
773 quad_matrix_and_uv.push(quad_instance);
774 }
775
776 Renderable::Text(text) => {
777 let result = fonts.get_weak(text.font_ref);
778 if result.is_none() {
779 continue;
780 }
781 let font = result.unwrap();
782
783 let glyphs = font.draw(&text.text);
784 for glyph in glyphs {
785 let pos = render_item.position + Vec3::from(glyph.relative_position);
786 let texture_size = glyph.texture_rectangle.size;
787 let model_matrix =
788 Matrix4::from_translation(pos.x as f32, pos.y as f32, 0.0)
789 * Matrix4::from_scale(
790 texture_size.x as f32,
791 texture_size.y as f32,
792 1.0,
793 );
794 let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
795 glyph.texture_rectangle,
796 current_texture_size,
797 );
798
799 let quad_instance = SpriteInstanceUniform::new(
800 model_matrix,
801 tex_coords_mul_add,
802 0,
803 Vec4(text.color.to_f32_slice()),
804 true,
805 );
806 quad_matrix_and_uv.push(quad_instance);
807 }
808 }
809
810 Renderable::TileMap(tile_map) => {
811 for (index, tile) in tile_map.tiles.iter().enumerate() {
812 let cell_pos_x = (index as u16 % tile_map.tiles_data_grid_size.x)
813 * tile_map.one_cell_size.x
814 * tile_map.scale as u16;
815 let cell_pos_y = (index as u16 / tile_map.tiles_data_grid_size.x)
816 * tile_map.one_cell_size.y
817 * tile_map.scale as u16;
818 let cell_x = *tile % tile_map.cell_count_size.x;
819 let cell_y = *tile / tile_map.cell_count_size.x;
820
821 let tex_x = cell_x * tile_map.one_cell_size.x;
822 let tex_y = cell_y * tile_map.one_cell_size.x;
823
824 let cell_texture_area = URect::new(
825 tex_x,
826 tex_y,
827 tile_map.one_cell_size.x,
828 tile_map.one_cell_size.y,
829 );
830
831 let cell_model_matrix = Matrix4::from_translation(
832 (render_item.position.x + cell_pos_x as i16) as f32,
833 (render_item.position.y + cell_pos_y as i16) as f32,
834 0.0,
835 ) * Matrix4::from_scale(
836 (tile_map.one_cell_size.x * tile_map.scale as u16) as f32,
837 (tile_map.one_cell_size.y * tile_map.scale as u16) as f32,
838 1.0,
839 );
840
841 let cell_tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
842 cell_texture_area,
843 current_texture_size,
844 );
845
846 let quad_instance = SpriteInstanceUniform::new(
847 cell_model_matrix,
848 cell_tex_coords_mul_add,
849 0,
850 Vec4([1.0, 1.0, 1.0, 1.0]),
851 true,
852 );
853 quad_matrix_and_uv.push(quad_instance);
854 }
855 }
856 }
857 }
858
859 let quad_count = quad_matrix_and_uv.len() as u32 - quad_len_before;
860 batch_vertex_ranges.push((weak_material_ref, quad_len_before, quad_count));
861 }
862
863 self.queue.write_buffer(
865 &self.quad_matrix_and_uv_instance_buffer,
866 0,
867 bytemuck::cast_slice(&quad_matrix_and_uv),
868 );
869
870 self.batch_offsets = batch_vertex_ranges;
871 }
872
873 #[allow(clippy::too_many_lines)]
874 #[inline]
875 pub fn prepare_nine_slice(
876 nine_slice: &NineSlice,
877 position_offset: Vec3,
878 quad_matrix_and_uv: &mut Vec<SpriteInstanceUniform>,
879 current_texture_size: UVec2,
880 ) {
881 let corner_size = nine_slice.corner_size;
882 let corner_height = corner_size.y;
883 let corner_width = corner_size.x;
884 let color = nine_slice.color;
885 let outer_size = nine_slice.size - corner_size * 2;
886 let side_width = outer_size.x;
887 let side_height = outer_size.y;
888 let window_size = nine_slice.size;
889 let texture_window_size = nine_slice.texture_window_size;
890
891 let atlas_origin = nine_slice.atlas_offset;
892
893 let lower_left_pos = Vec3::new(position_offset.x, position_offset.y, 0);
895 let lower_left_quad_size = UVec2::new(corner_width, corner_height);
896 let lower_left_atlas =
897 URect::new(atlas_origin.x, atlas_origin.y, corner_size.x, corner_size.y);
898 let lower_left_quad = Self::quad_helper_uniform(
899 lower_left_pos,
900 lower_left_quad_size,
901 lower_left_atlas,
902 color,
903 current_texture_size,
904 );
905 quad_matrix_and_uv.push(lower_left_quad);
906
907 let lower_side_position = Vec3::new(
909 position_offset.x + corner_width as i16,
910 position_offset.y,
911 0,
912 );
913 let lower_side_quad_size = UVec2::new(side_width, corner_height);
914 let lower_side_atlas = URect::new(
915 atlas_origin.x + corner_width,
916 atlas_origin.y,
917 texture_window_size.x - corner_width * 2,
918 corner_size.y,
919 );
920 let lower_side_quad = Self::quad_helper_uniform(
921 lower_side_position,
922 lower_side_quad_size,
923 lower_side_atlas,
924 color,
925 current_texture_size,
926 );
927 quad_matrix_and_uv.push(lower_side_quad);
928
929 let lower_side_position = Vec3::new(
931 position_offset.x + window_size.x as i16 - corner_width as i16,
932 position_offset.y,
933 0,
934 );
935 let lower_right_quad_size = UVec2::new(corner_width, corner_height);
936 let lower_right_atlas = URect::new(
937 atlas_origin.x + corner_width * 2,
938 atlas_origin.y,
939 corner_size.x,
940 corner_size.y,
941 );
942 let lower_right_quad = Self::quad_helper_uniform(
943 lower_side_position,
944 lower_right_quad_size,
945 lower_right_atlas,
946 color,
947 current_texture_size,
948 );
949 quad_matrix_and_uv.push(lower_right_quad);
950
951 let left_side_pos = Vec3::new(
953 position_offset.x,
954 position_offset.y + corner_height as i16,
955 0,
956 );
957 let left_side_quad_size = UVec2::new(corner_width, side_height);
958 let left_side_atlas = URect::new(
959 atlas_origin.x,
960 atlas_origin.y - corner_height,
961 corner_size.x,
962 corner_size.y,
963 );
964 let left_side_quad = Self::quad_helper_uniform(
965 left_side_pos,
966 left_side_quad_size,
967 left_side_atlas,
968 color,
969 current_texture_size,
970 );
971 quad_matrix_and_uv.push(left_side_quad);
972
973 let middle_pos = Vec3::new(
975 position_offset.x + corner_width as i16,
976 position_offset.y + corner_height as i16,
977 0,
978 );
979 let middle_quad_size = UVec2::new(
980 nine_slice.size.x - corner_width * 2,
981 nine_slice.size.y - corner_height * 2,
982 );
983 let middle_atlas = URect::new(
984 atlas_origin.x + corner_width,
985 atlas_origin.y - corner_height,
986 texture_window_size.x - corner_width * 2,
987 texture_window_size.y - corner_height * 2,
988 );
989 let middle_quad = Self::quad_helper_uniform(
990 middle_pos,
991 middle_quad_size,
992 middle_atlas,
993 color,
994 current_texture_size,
995 );
996 quad_matrix_and_uv.push(middle_quad);
997
998 let right_side_pos = Vec3::new(
1000 position_offset.x + window_size.x as i16 - corner_width as i16,
1001 position_offset.y + corner_height as i16,
1002 0,
1003 );
1004 let right_side_quad_size = UVec2::new(corner_width, window_size.y - corner_height * 2);
1005 let right_side_atlas = URect::new(
1006 atlas_origin.x + corner_width * 2,
1007 atlas_origin.y - corner_height,
1008 corner_size.x,
1009 corner_size.y,
1010 );
1011 let right_side_quad = Self::quad_helper_uniform(
1012 right_side_pos,
1013 right_side_quad_size,
1014 right_side_atlas,
1015 color,
1016 current_texture_size,
1017 );
1018 quad_matrix_and_uv.push(right_side_quad);
1019
1020 let upper_y = position_offset.y + window_size.y as i16 - corner_height as i16;
1023 let atlas_upper_y = atlas_origin.y - corner_height * 2;
1024 let upper_left_pos = Vec3::new(position_offset.x, upper_y, 0);
1026 let upper_left_quad_size = UVec2::new(corner_width, corner_height);
1027 let upper_left_atlas =
1028 URect::new(atlas_origin.x, atlas_upper_y, corner_size.x, corner_size.y);
1029 let upper_left_quad = Self::quad_helper_uniform(
1030 upper_left_pos,
1031 upper_left_quad_size,
1032 upper_left_atlas,
1033 color,
1034 current_texture_size,
1035 );
1036 quad_matrix_and_uv.push(upper_left_quad);
1037
1038 let upper_side_position = Vec3::new(position_offset.x + corner_width as i16, upper_y, 0);
1040 let upper_side_quad_size = UVec2::new(side_width, corner_height);
1041 let upper_side_atlas = URect::new(
1042 atlas_origin.x + corner_width,
1043 atlas_upper_y,
1044 texture_window_size.x - corner_width * 2,
1045 corner_size.y,
1046 );
1047 let upper_side_quad = Self::quad_helper_uniform(
1048 upper_side_position,
1049 upper_side_quad_size,
1050 upper_side_atlas,
1051 color,
1052 current_texture_size,
1053 );
1054 quad_matrix_and_uv.push(upper_side_quad);
1055
1056 let upper_side_position = Vec3::new(
1058 position_offset.x + window_size.x as i16 - corner_width as i16,
1059 upper_y,
1060 0,
1061 );
1062 let upper_right_quad_size = UVec2::new(corner_width, corner_height);
1063 let upper_right_atlas = URect::new(
1064 atlas_origin.x + corner_width * 2,
1065 atlas_upper_y,
1066 corner_size.x,
1067 corner_size.y,
1068 );
1069 let upper_right_quad = Self::quad_helper_uniform(
1070 upper_side_position,
1071 upper_right_quad_size,
1072 upper_right_atlas,
1073 color,
1074 current_texture_size,
1075 );
1076 quad_matrix_and_uv.push(upper_right_quad);
1077 }
1078
1079 pub fn render(
1082 &mut self,
1083 render_pass: &mut RenderPass,
1084 materials: &Assets<Material>,
1085 fonts: &Assets<Font>,
1086 now: Millis,
1087 ) {
1088 trace!("start render()");
1089 self.last_render_at = now;
1090
1091 self.viewport = match self.viewport_strategy {
1092 ViewportStrategy::FitIntegerScaling(virtual_surface_size) => {
1093 Self::viewport_from_integer_scale(self.physical_surface_size, virtual_surface_size)
1094 }
1095 ViewportStrategy::FitFloatScaling(virtual_surface_size) => {
1096 Self::viewport_from_float_scale(self.physical_surface_size, virtual_surface_size)
1097 }
1098 ViewportStrategy::MatchPhysicalSize => URect::new(
1099 0,
1100 0,
1101 self.physical_surface_size.x,
1102 self.physical_surface_size.y,
1103 ),
1104 };
1105
1106 let view_proj_matrix = match self.viewport_strategy {
1107 ViewportStrategy::MatchPhysicalSize => {
1108 create_view_uniform_view_projection_matrix(self.physical_surface_size)
1109 }
1110 ViewportStrategy::FitFloatScaling(virtual_surface_size)
1111 | ViewportStrategy::FitIntegerScaling(virtual_surface_size) => {
1112 create_view_projection_matrix_from_virtual(
1113 virtual_surface_size.x,
1114 virtual_surface_size.y,
1115 )
1116 }
1117 };
1118
1119 let scale_matrix = Matrix4::from_scale(self.scale, self.scale, 0.0);
1120 let origin_translation_matrix =
1121 Matrix4::from_translation(-self.origin.x as f32, -self.origin.y as f32, 0.0);
1122
1123 let total_matrix = scale_matrix * view_proj_matrix * origin_translation_matrix;
1124
1125 self.queue.write_buffer(
1127 &self.camera_buffer,
1128 0,
1129 bytemuck::cast_slice(&[total_matrix]),
1130 );
1131
1132 self.prepare_render(materials, fonts);
1133
1134 render_pass.set_viewport(
1135 self.viewport.position.x as f32,
1136 self.viewport.position.y as f32,
1137 self.viewport.size.x as f32,
1138 self.viewport.size.y as f32,
1139 0.0,
1140 1.0,
1141 );
1142
1143 render_pass.set_pipeline(&self.pipeline);
1144
1145 render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
1147 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1148
1149 render_pass.set_vertex_buffer(1, self.quad_matrix_and_uv_instance_buffer.slice(..));
1151
1152 render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
1154
1155 let num_indices = swamp_wgpu_sprites::INDICES.len() as u32;
1156
1157 for &(weak_material_ref, start, count) in &self.batch_offsets {
1158 let wgpu_material = materials
1159 .get_weak(weak_material_ref)
1160 .expect("no such material");
1161
1162 render_pass.set_bind_group(1, &wgpu_material.texture_and_sampler_bind_group, &[]);
1164
1165 trace!(material=%weak_material_ref, start=%start, count=%count, "draw instanced");
1167 render_pass.draw_indexed(0..num_indices, 0, start..(start + count));
1168 }
1169
1170 self.items.clear();
1171 }
1172
1173 pub fn material_from_texture(&self, texture: wgpu::Texture, label: &str) -> Material {
1174 trace!("load texture from memory with name: '{label}'");
1175 let size = &texture.size();
1176 let texture_and_sampler_bind_group =
1177 swamp_wgpu_sprites::create_sprite_texture_and_sampler_bind_group(
1178 &self.device,
1179 &self.texture_sampler_bind_group_layout,
1180 &texture,
1181 &self.sampler,
1182 label,
1183 );
1184
1185 let texture_size = UVec2::new(size.width as u16, size.height as u16);
1186
1187 Material {
1188 texture_and_sampler_bind_group,
1189 texture_size,
1191 }
1192 }
1193}
1194
1195fn create_view_projection_matrix_from_virtual(virtual_width: u16, virtual_height: u16) -> Matrix4 {
1198 OrthoInfo {
1199 left: 0.0,
1200 right: virtual_width as f32,
1201 bottom: 0.0,
1202 top: virtual_height as f32,
1203 near: 1.0,
1204 far: -1.0,
1205 }
1206 .into()
1207}
1208
1209fn create_view_uniform_view_projection_matrix(viewport_size: UVec2) -> Matrix4 {
1210 let viewport_width = viewport_size.x as f32;
1211 let viewport_height = viewport_size.y as f32;
1212
1213 let viewport_aspect_ratio = viewport_width / viewport_height;
1214
1215 let scale_x = 1.0;
1216 let scale_y = viewport_aspect_ratio; let view_projection_matrix = [
1219 [scale_x, 0.0, 0.0, 0.0],
1220 [0.0, scale_y, 0.0, 0.0],
1221 [0.0, 0.0, -1.0, 0.0],
1222 [0.0, 0.0, 0.0, 1.0],
1223 ];
1224
1225 view_projection_matrix.into()
1226}
1227
1228fn sort_render_items_by_z_and_material(items: &mut [RenderItem]) {
1229 items.sort_by_key(|item| (item.position.z, item.material_ref));
1230}
1231
1232#[derive(Debug, Clone, Copy, Default)]
1233pub enum Rotation {
1234 #[default]
1235 Degrees0,
1236 Degrees90,
1237 Degrees180,
1238 Degrees270,
1239}
1240
1241#[derive(Debug, Copy, Clone)]
1242pub struct SpriteParams {
1243 pub texture_size: UVec2,
1244 pub texture_pos: UVec2,
1245 pub scale: u8,
1246 pub rotation: Rotation,
1247 pub flip_x: bool,
1248 pub flip_y: bool,
1249 pub pivot: Vec2,
1250 pub color: Color,
1251}
1252
1253impl Default for SpriteParams {
1254 fn default() -> Self {
1255 Self {
1256 texture_size: UVec2::new(0, 0),
1257 texture_pos: UVec2::new(0, 0),
1258 pivot: Vec2::new(0, 0),
1259 flip_x: false,
1260 flip_y: false,
1261 color: Color::from_octet(255, 255, 255, 255),
1262 scale: 1,
1263 rotation: Rotation::Degrees0,
1264 }
1265 }
1266}
1267
1268#[derive(Debug, PartialEq, Eq, Asset)]
1269pub struct Material {
1270 pub texture_and_sampler_bind_group: BindGroup,
1271 pub texture_size: UVec2,
1272}
1273
1274impl PartialOrd<Self> for Material {
1275 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1276 Some(
1277 self.texture_and_sampler_bind_group
1278 .cmp(&other.texture_and_sampler_bind_group),
1279 )
1280 }
1281}
1282
1283impl Ord for Material {
1284 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1285 self.texture_and_sampler_bind_group
1286 .cmp(&other.texture_and_sampler_bind_group)
1287 }
1288}
1289
1290#[derive(Debug)]
1291pub struct Sprite {
1292 pub params: SpriteParams,
1293}
1294
1295#[derive(Debug)]
1296pub struct QuadColor {
1297 pub size: UVec2,
1298 pub color: Color,
1299}
1300
1301#[derive(Debug)]
1302pub struct NineSlice {
1303 pub corner_size: UVec2,
1304 pub texture_window_size: UVec2,
1305 pub size: UVec2,
1306 pub atlas_offset: UVec2,
1307 pub color: Color,
1308}
1309
1310#[derive(Debug)]
1311pub struct TileMap {
1312 pub tiles_data_grid_size: UVec2,
1313 pub cell_count_size: UVec2,
1314 pub one_cell_size: UVec2,
1315 pub tiles: Vec<u16>,
1316 pub scale: u8,
1317}
1318
1319pub type RenderPipelineRef = Arc<RenderPipeline>;
1320
1321const fn sources() -> (&'static str, &'static str) {
1322 let vertex_shader_source = "
1323// Bind Group 0: Uniforms (view-projection matrix)
1324struct Uniforms {
1325 view_proj: mat4x4<f32>,
1326};
1327
1328@group(0) @binding(0)
1329var<uniform> camera_uniforms: Uniforms;
1330
1331// Bind Group 1: Texture and Sampler (Unused in Vertex Shader but needed for consistency)
1332@group(1) @binding(0)
1333var diffuse_texture: texture_2d<f32>;
1334
1335@group(1) @binding(1)
1336var sampler_diffuse: sampler;
1337
1338// Vertex input structure
1339struct VertexInput {
1340 @location(0) position: vec3<f32>,
1341 @location(1) tex_coords: vec2<f32>,
1342 @builtin(instance_index) instance_idx: u32,
1343};
1344
1345// Vertex output structure to fragment shader
1346struct VertexOutput {
1347 @builtin(position) position: vec4<f32>,
1348 @location(0) tex_coords: vec2<f32>,
1349 @location(1) color: vec4<f32>,
1350 @location(2) use_texture: u32,
1351};
1352
1353// Vertex shader entry point
1354@vertex
1355fn vs_main(
1356 input: VertexInput,
1357 // Instance attributes
1358 @location(2) model_matrix0: vec4<f32>,
1359 @location(3) model_matrix1: vec4<f32>,
1360 @location(4) model_matrix2: vec4<f32>,
1361 @location(5) model_matrix3: vec4<f32>,
1362 @location(6) tex_multiplier: vec4<f32>,
1363 @location(7) rotation_step: u32,
1364 @location(8) color: vec4<f32>,
1365 @location(9) use_texture: u32,
1366) -> VertexOutput {
1367 var output: VertexOutput;
1368
1369 // Reconstruct the model matrix from the instance data
1370 let model_matrix = mat4x4<f32>(
1371 model_matrix0,
1372 model_matrix1,
1373 model_matrix2,
1374 model_matrix3,
1375 );
1376
1377 // Compute world position
1378 let world_position = model_matrix * vec4<f32>(input.position, 1.0);
1379
1380 // Apply view-projection matrix
1381 output.position = camera_uniforms.view_proj * world_position;
1382
1383 // Decode rotation_step
1384 let rotation_val = rotation_step & 3u; // Bits 0-1
1385 let flip_x = (rotation_step & 4u) != 0u; // Bit 2
1386 let flip_y = (rotation_step & 8u) != 0u; // Bit 3
1387
1388 // Rotate texture coordinates based on rotation_val
1389 var rotated_tex_coords = input.tex_coords;
1390 if (rotation_val == 1) {
1391 // 90 degrees rotation
1392 rotated_tex_coords = vec2<f32>(1.0 - input.tex_coords.y, input.tex_coords.x);
1393 } else if (rotation_val == 2) {
1394 // 180 degrees rotation
1395 rotated_tex_coords = vec2<f32>(1.0 - input.tex_coords.x, 1.0 - input.tex_coords.y);
1396 } else if (rotation_val == 3) {
1397 // 270 degrees rotation
1398 rotated_tex_coords = vec2<f32>(input.tex_coords.y, 1.0 - input.tex_coords.x);
1399 }
1400 // else rotation_val == Degrees0, no rotation
1401
1402 // Apply flipping
1403 if (flip_x) {
1404 rotated_tex_coords.x = 1.0 - rotated_tex_coords.x;
1405 }
1406 if (flip_y) {
1407 rotated_tex_coords.y = 1.0 - rotated_tex_coords.y;
1408 }
1409
1410 // Modify texture coordinates
1411 output.tex_coords = rotated_tex_coords * tex_multiplier.xy + tex_multiplier.zw;
1412 output.color = color;
1413 output.use_texture = use_texture;
1414
1415 return output;
1416}
1417 ";
1418 let fragment_shader_source = "
1421
1422// Bind Group 1: Texture and Sampler
1423@group(1) @binding(0)
1424var diffuse_texture: texture_2d<f32>;
1425
1426@group(1) @binding(1)
1427var sampler_diffuse: sampler;
1428
1429// Fragment input structure from vertex shader
1430struct VertexOutput {
1431 @builtin(position) position: vec4<f32>,
1432 @location(0) tex_coords: vec2<f32>,
1433 @location(1) color: vec4<f32>,
1434 @location(2) use_texture: u32,
1435};
1436
1437// Fragment shader entry point
1438@fragment
1439fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
1440 var final_color: vec4<f32>;
1441
1442 // Sample the texture using the texture coordinates
1443 let texture_color = textureSample(diffuse_texture, sampler_diffuse, input.tex_coords);
1444 if (input.use_texture == 1u) { // Check if use_texture is true (1)
1445 // Apply color modulation and opacity
1446 final_color = texture_color * input.color;
1447 } else {
1448 final_color = input.color;
1449 }
1450
1451 return final_color;
1452}
1453
1454";
1455 (vertex_shader_source, fragment_shader_source)
1456}