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 pub fn draw_nine_slice(
518 &mut self,
519 position: Vec3,
520 size: UVec2,
521 corner_size: UVec2,
522 texture_window_size: UVec2,
523 material_ref: &MaterialRef,
524 atlas_offset: UVec2,
525 color: Color,
526 ) {
527 self.items.push(RenderItem {
528 position,
529 material_ref: material_ref.into(),
530 renderable: Renderable::NineSlice(NineSlice {
531 corner_size,
532 texture_window_size,
533 size,
534 atlas_offset,
535 color,
536 }),
537 });
538 }
539
540 pub const fn clear_color(&self) -> wgpu::Color {
541 self.clear_color
542 }
543
544 fn calculate_texture_coords_mul_add(atlas_rect: URect, texture_size: UVec2) -> Vec4 {
546 let x = atlas_rect.position.x as f32 / texture_size.x as f32;
547 let y = atlas_rect.position.y as f32 / texture_size.y as f32;
548 let width = atlas_rect.size.x as f32 / texture_size.x as f32;
549 let height = atlas_rect.size.y as f32 / texture_size.y as f32;
550 Vec4([width, height, x, y])
551 }
552
553 fn order_render_items_in_batches(&self) -> Vec<Vec<&RenderItem>> {
554 let mut material_batches: Vec<Vec<&RenderItem>> = Vec::new();
555 let mut current_batch: Vec<&RenderItem> = Vec::new();
556 let mut current_material: Option<&WeakMaterialRef> = None;
557
558 for render_item in &self.items {
559 if Some(&render_item.material_ref) != current_material {
560 if !current_batch.is_empty() {
561 material_batches.push(current_batch.clone());
562 current_batch.clear();
563 }
564 current_material = Some(&render_item.material_ref);
565 }
566 current_batch.push(render_item);
567 }
568
569 if !current_batch.is_empty() {
570 material_batches.push(current_batch);
571 }
572
573 material_batches
574 }
575
576 pub fn quad_helper_uniform(
577 position: Vec3,
578 quad_size: UVec2,
579 render_atlas: URect,
580 color: Color,
581
582 current_texture_size: UVec2,
583 ) -> SpriteInstanceUniform {
585 let model_matrix = Matrix4::from_translation(position.x as f32, position.y as f32, 0.0)
586 * Matrix4::from_scale(quad_size.x as f32, quad_size.y as f32, 1.0);
587
588 let tex_coords_mul_add =
589 Self::calculate_texture_coords_mul_add(render_atlas, current_texture_size);
590
591 let rotation_value = 0;
592
593 SpriteInstanceUniform::new(
594 model_matrix,
595 tex_coords_mul_add,
596 rotation_value,
597 Vec4(color.to_f32_slice()),
598 true,
599 )
600 }
601
602 #[allow(clippy::too_many_lines)]
605 pub fn prepare_render(&mut self, materials: &Assets<Material>, fonts: &Assets<Font>) {
606 const FLIP_X_MASK: u32 = 0b0000_0100;
607 const FLIP_Y_MASK: u32 = 0b0000_1000;
608
609 sort_render_items_by_z_and_material(&mut self.items);
610
611 let batches = self.order_render_items_in_batches();
612
613 let mut quad_matrix_and_uv: Vec<SpriteInstanceUniform> = Vec::new();
614 let mut batch_vertex_ranges: Vec<(WeakMaterialRef, u32, u32)> = Vec::new();
615
616 for render_items in &batches {
617 let quad_len_before = quad_matrix_and_uv.len() as u32;
618
619 let weak_material_ref = render_items
621 .first()
622 .map(|item| {
623 let material_ref: WeakId<Material> = item.material_ref;
625 material_ref
626 })
627 .expect("Render items batch was empty");
628
629 let result = materials.get_weak(weak_material_ref);
630 if result.is_none() {
631 continue;
633 }
634 let material = result.unwrap();
635 let current_texture_size = material.texture_size;
636
637 for render_item in render_items {
638 match &render_item.renderable {
639 Renderable::Sprite(sprite) => {
640 let params = &sprite.params;
641 let mut size = params.texture_size;
642 if size.x == 0 && size.y == 0 {
643 size = current_texture_size;
644 }
645
646 let render_atlas = URect {
647 position: params.texture_pos,
648 size,
649 };
650
651 match params.rotation {
652 Rotation::Degrees90 | Rotation::Degrees270 => {
653 swap(&mut size.x, &mut size.y);
654 }
655 _ => {}
656 }
657
658 let model_matrix = Matrix4::from_translation(
659 render_item.position.x as f32,
660 render_item.position.y as f32,
661 0.0,
662 ) * Matrix4::from_scale(
663 (size.x * params.scale as u16) as f32,
664 (size.y * params.scale as u16) as f32,
665 1.0,
666 );
667
668 let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
669 render_atlas,
670 current_texture_size,
671 );
672
673 let mut rotation_value = match params.rotation {
674 Rotation::Degrees0 => 0,
675 Rotation::Degrees90 => 1,
676 Rotation::Degrees180 => 2,
677 Rotation::Degrees270 => 3,
678 };
679
680 if params.flip_x {
681 rotation_value |= FLIP_X_MASK;
682 }
683 if params.flip_y {
684 rotation_value |= FLIP_Y_MASK;
685 }
686
687 let quad_instance = SpriteInstanceUniform::new(
688 model_matrix,
689 tex_coords_mul_add,
690 rotation_value,
691 Vec4(params.color.to_f32_slice()),
692 true,
693 );
694 quad_matrix_and_uv.push(quad_instance);
695 }
696
697 Renderable::NineSlice(nine_slice) => {
698 Self::prepare_nine_slice(
699 nine_slice,
700 render_item.position,
701 &mut quad_matrix_and_uv,
702 current_texture_size,
703 );
704 }
705
706 Renderable::QuadColor(quad) => {
707 let model_matrix =
729 Matrix4::from_translation(
730 render_item.position.x as f32,
731 render_item.position.y as f32,
732 0.0,
733 ) * Matrix4::from_scale(quad.size.x as f32, quad.size.y as f32, 1.0);
734
735 let tex_coords_mul_add = Vec4([
759 0.0, 0.0, 0.0, 0.0,
762 ]);
763 let rotation_value = 0;
764
765 let quad_instance = SpriteInstanceUniform::new(
766 model_matrix,
767 tex_coords_mul_add,
768 rotation_value,
769 Vec4(quad.color.to_f32_slice()),
770 false,
771 );
772 quad_matrix_and_uv.push(quad_instance);
773 }
774
775 Renderable::Text(text) => {
776 let result = fonts.get_weak(text.font_ref);
777 if result.is_none() {
778 continue;
779 }
780 let font = result.unwrap();
781
782 let glyphs = font.draw(&text.text);
783 for glyph in glyphs {
784 let pos = render_item.position + Vec3::from(glyph.relative_position);
785 let texture_size = glyph.texture_rectangle.size;
786 let model_matrix =
787 Matrix4::from_translation(pos.x as f32, pos.y as f32, 0.0)
788 * Matrix4::from_scale(
789 texture_size.x as f32,
790 texture_size.y as f32,
791 1.0,
792 );
793 let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
794 glyph.texture_rectangle,
795 current_texture_size,
796 );
797
798 let quad_instance = SpriteInstanceUniform::new(
799 model_matrix,
800 tex_coords_mul_add,
801 0,
802 Vec4(text.color.to_f32_slice()),
803 true,
804 );
805 quad_matrix_and_uv.push(quad_instance);
806 }
807 }
808
809 Renderable::TileMap(tile_map) => {
810 for (index, tile) in tile_map.tiles.iter().enumerate() {
811 let cell_pos_x = (index as u16 % tile_map.tiles_data_grid_size.x)
812 * tile_map.one_cell_size.x
813 * tile_map.scale as u16;
814 let cell_pos_y = (index as u16 / tile_map.tiles_data_grid_size.x)
815 * tile_map.one_cell_size.y
816 * tile_map.scale as u16;
817 let cell_x = *tile % tile_map.cell_count_size.x;
818 let cell_y = *tile / tile_map.cell_count_size.x;
819
820 let tex_x = cell_x * tile_map.one_cell_size.x;
821 let tex_y = cell_y * tile_map.one_cell_size.x;
822
823 let cell_texture_area = URect::new(
824 tex_x,
825 tex_y,
826 tile_map.one_cell_size.x,
827 tile_map.one_cell_size.y,
828 );
829
830 let cell_model_matrix = Matrix4::from_translation(
831 (render_item.position.x + cell_pos_x as i16) as f32,
832 (render_item.position.y + cell_pos_y as i16) as f32,
833 0.0,
834 ) * Matrix4::from_scale(
835 (tile_map.one_cell_size.x * tile_map.scale as u16) as f32,
836 (tile_map.one_cell_size.y * tile_map.scale as u16) as f32,
837 1.0,
838 );
839
840 let cell_tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
841 cell_texture_area,
842 current_texture_size,
843 );
844
845 let quad_instance = SpriteInstanceUniform::new(
846 cell_model_matrix,
847 cell_tex_coords_mul_add,
848 0,
849 Vec4([1.0, 1.0, 1.0, 1.0]),
850 true,
851 );
852 quad_matrix_and_uv.push(quad_instance);
853 }
854 }
855 }
856 }
857
858 let quad_count = quad_matrix_and_uv.len() as u32 - quad_len_before;
859 batch_vertex_ranges.push((weak_material_ref, quad_len_before, quad_count));
860 }
861
862 self.queue.write_buffer(
864 &self.quad_matrix_and_uv_instance_buffer,
865 0,
866 bytemuck::cast_slice(&quad_matrix_and_uv),
867 );
868
869 self.batch_offsets = batch_vertex_ranges;
870 }
871
872 #[inline]
873 pub fn prepare_nine_slice(
874 nine_slice: &NineSlice,
875 position_offset: Vec3,
876 quad_matrix_and_uv: &mut Vec<SpriteInstanceUniform>,
877 current_texture_size: UVec2,
878 ) {
879 let corner_size = nine_slice.corner_size;
880 let corner_height = corner_size.y;
881 let corner_width = corner_size.x;
882 let color = nine_slice.color;
883 let outer_size = nine_slice.size - corner_size * 2;
884 let side_width = outer_size.x;
885 let side_height = outer_size.y;
886 let window_size = nine_slice.size;
887 let texture_window_size = nine_slice.texture_window_size;
888
889 let atlas_origin = nine_slice.atlas_offset;
890
891 let lower_left_pos = Vec3::new(position_offset.x, position_offset.y, 0);
893 let lower_left_quad_size = UVec2::new(corner_width, corner_height);
894 let lower_left_atlas =
895 URect::new(atlas_origin.x, atlas_origin.y, corner_size.x, corner_size.y);
896 let lower_left_quad = Self::quad_helper_uniform(
897 lower_left_pos,
898 lower_left_quad_size,
899 lower_left_atlas,
900 color,
901 current_texture_size,
902 );
903 quad_matrix_and_uv.push(lower_left_quad);
904
905 let lower_side_position = Vec3::new(
907 position_offset.x + corner_width as i16,
908 position_offset.y,
909 0,
910 );
911 let lower_side_quad_size = UVec2::new(side_width, corner_height);
912 let lower_side_atlas = URect::new(
913 atlas_origin.x + corner_width,
914 atlas_origin.y,
915 texture_window_size.x - corner_width * 2,
916 corner_size.y,
917 );
918 let lower_side_quad = Self::quad_helper_uniform(
919 lower_side_position,
920 lower_side_quad_size,
921 lower_side_atlas,
922 color,
923 current_texture_size,
924 );
925 quad_matrix_and_uv.push(lower_side_quad);
926
927 let lower_side_position = Vec3::new(
929 position_offset.x + window_size.x as i16 - corner_width as i16,
930 position_offset.y,
931 0,
932 );
933 let lower_right_quad_size = UVec2::new(corner_width, corner_height);
934 let lower_right_atlas = URect::new(
935 atlas_origin.x + corner_width * 2,
936 atlas_origin.y,
937 corner_size.x,
938 corner_size.y,
939 );
940 let lower_right_quad = Self::quad_helper_uniform(
941 lower_side_position,
942 lower_right_quad_size,
943 lower_right_atlas,
944 color,
945 current_texture_size,
946 );
947 quad_matrix_and_uv.push(lower_right_quad);
948
949 let left_side_pos = Vec3::new(
951 position_offset.x,
952 position_offset.y + corner_height as i16,
953 0,
954 );
955 let left_side_quad_size = UVec2::new(corner_width, side_height);
956 let left_side_atlas = URect::new(
957 atlas_origin.x,
958 atlas_origin.y - corner_height,
959 corner_size.x,
960 corner_size.y,
961 );
962 let left_side_quad = Self::quad_helper_uniform(
963 left_side_pos,
964 left_side_quad_size,
965 left_side_atlas,
966 color,
967 current_texture_size,
968 );
969 quad_matrix_and_uv.push(left_side_quad);
970
971 let middle_pos = Vec3::new(
973 position_offset.x + corner_width as i16,
974 position_offset.y + corner_height as i16,
975 0,
976 );
977 let middle_quad_size = UVec2::new(
978 nine_slice.size.x - corner_width * 2,
979 nine_slice.size.y - corner_height * 2,
980 );
981 let middle_atlas = URect::new(
982 atlas_origin.x + corner_width,
983 atlas_origin.y - corner_height,
984 texture_window_size.x - corner_width * 2,
985 texture_window_size.y - corner_height * 2,
986 );
987 let middle_quad = Self::quad_helper_uniform(
988 middle_pos,
989 middle_quad_size,
990 middle_atlas,
991 color,
992 current_texture_size,
993 );
994 quad_matrix_and_uv.push(middle_quad);
995
996 let right_side_pos = Vec3::new(
998 position_offset.x + window_size.x as i16 - corner_width as i16,
999 position_offset.y + corner_height as i16,
1000 0,
1001 );
1002 let right_side_quad_size = UVec2::new(corner_width, window_size.y - corner_height * 2);
1003 let right_side_atlas = URect::new(
1004 atlas_origin.x + corner_width * 2,
1005 atlas_origin.y - corner_height,
1006 corner_size.x,
1007 corner_size.y,
1008 );
1009 let right_side_quad = Self::quad_helper_uniform(
1010 right_side_pos,
1011 right_side_quad_size,
1012 right_side_atlas,
1013 color,
1014 current_texture_size,
1015 );
1016 quad_matrix_and_uv.push(right_side_quad);
1017
1018 let upper_y = position_offset.y + window_size.y as i16 - corner_height as i16;
1021 let atlas_upper_y = atlas_origin.y - corner_height * 2;
1022 let upper_left_pos = Vec3::new(position_offset.x, upper_y, 0);
1024 let upper_left_quad_size = UVec2::new(corner_width, corner_height);
1025 let upper_left_atlas =
1026 URect::new(atlas_origin.x, atlas_upper_y, corner_size.x, corner_size.y);
1027 let upper_left_quad = Self::quad_helper_uniform(
1028 upper_left_pos,
1029 upper_left_quad_size,
1030 upper_left_atlas,
1031 color,
1032 current_texture_size,
1033 );
1034 quad_matrix_and_uv.push(upper_left_quad);
1035
1036 let upper_side_position = Vec3::new(position_offset.x + corner_width as i16, upper_y, 0);
1038 let upper_side_quad_size = UVec2::new(side_width, corner_height);
1039 let upper_side_atlas = URect::new(
1040 atlas_origin.x + corner_width,
1041 atlas_upper_y,
1042 texture_window_size.x - corner_width * 2,
1043 corner_size.y,
1044 );
1045 let upper_side_quad = Self::quad_helper_uniform(
1046 upper_side_position,
1047 upper_side_quad_size,
1048 upper_side_atlas,
1049 color,
1050 current_texture_size,
1051 );
1052 quad_matrix_and_uv.push(upper_side_quad);
1053
1054 let upper_side_position = Vec3::new(
1056 position_offset.x + window_size.x as i16 - corner_width as i16,
1057 upper_y,
1058 0,
1059 );
1060 let upper_right_quad_size = UVec2::new(corner_width, corner_height);
1061 let upper_right_atlas = URect::new(
1062 atlas_origin.x + corner_width * 2,
1063 atlas_upper_y,
1064 corner_size.x,
1065 corner_size.y,
1066 );
1067 let upper_right_quad = Self::quad_helper_uniform(
1068 upper_side_position,
1069 upper_right_quad_size,
1070 upper_right_atlas,
1071 color,
1072 current_texture_size,
1073 );
1074 quad_matrix_and_uv.push(upper_right_quad);
1075 }
1076
1077 pub fn render(
1080 &mut self,
1081 render_pass: &mut RenderPass,
1082 materials: &Assets<Material>,
1083 fonts: &Assets<Font>,
1084 now: Millis,
1085 ) {
1086 trace!("start render()");
1087 self.last_render_at = now;
1088
1089 self.viewport = match self.viewport_strategy {
1090 ViewportStrategy::FitIntegerScaling(virtual_surface_size) => {
1091 Self::viewport_from_integer_scale(self.physical_surface_size, virtual_surface_size)
1092 }
1093 ViewportStrategy::FitFloatScaling(virtual_surface_size) => {
1094 Self::viewport_from_float_scale(self.physical_surface_size, virtual_surface_size)
1095 }
1096 ViewportStrategy::MatchPhysicalSize => URect::new(
1097 0,
1098 0,
1099 self.physical_surface_size.x,
1100 self.physical_surface_size.y,
1101 ),
1102 };
1103
1104 let view_proj_matrix = match self.viewport_strategy {
1105 ViewportStrategy::MatchPhysicalSize => {
1106 create_view_uniform_view_projection_matrix(self.physical_surface_size)
1107 }
1108 ViewportStrategy::FitFloatScaling(virtual_surface_size)
1109 | ViewportStrategy::FitIntegerScaling(virtual_surface_size) => {
1110 create_view_projection_matrix_from_virtual(
1111 virtual_surface_size.x,
1112 virtual_surface_size.y,
1113 )
1114 }
1115 };
1116
1117 let scale_matrix = Matrix4::from_scale(self.scale, self.scale, 0.0);
1118 let origin_translation_matrix =
1119 Matrix4::from_translation(-self.origin.x as f32, -self.origin.y as f32, 0.0);
1120
1121 let total_matrix = scale_matrix * view_proj_matrix * origin_translation_matrix;
1122
1123 self.queue.write_buffer(
1125 &self.camera_buffer,
1126 0,
1127 bytemuck::cast_slice(&[total_matrix]),
1128 );
1129
1130 self.prepare_render(materials, fonts);
1131
1132 render_pass.set_viewport(
1133 self.viewport.position.x as f32,
1134 self.viewport.position.y as f32,
1135 self.viewport.size.x as f32,
1136 self.viewport.size.y as f32,
1137 0.0,
1138 1.0,
1139 );
1140
1141 render_pass.set_pipeline(&self.pipeline);
1142
1143 render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
1145 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1146
1147 render_pass.set_vertex_buffer(1, self.quad_matrix_and_uv_instance_buffer.slice(..));
1149
1150 render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
1152
1153 let num_indices = swamp_wgpu_sprites::INDICES.len() as u32;
1154
1155 for &(weak_material_ref, start, count) in &self.batch_offsets {
1156 let wgpu_material = materials
1157 .get_weak(weak_material_ref)
1158 .expect("no such material");
1159
1160 render_pass.set_bind_group(1, &wgpu_material.texture_and_sampler_bind_group, &[]);
1162
1163 trace!(material=%weak_material_ref, start=%start, count=%count, "draw instanced");
1165 render_pass.draw_indexed(0..num_indices, 0, start..(start + count));
1166 }
1167
1168 self.items.clear();
1169 }
1170
1171 pub fn material_from_texture(&self, texture: wgpu::Texture, label: &str) -> Material {
1172 trace!("load texture from memory with name: '{label}'");
1173 let size = &texture.size();
1174 let texture_and_sampler_bind_group =
1175 swamp_wgpu_sprites::create_sprite_texture_and_sampler_bind_group(
1176 &self.device,
1177 &self.texture_sampler_bind_group_layout,
1178 &texture,
1179 &self.sampler,
1180 label,
1181 );
1182
1183 let texture_size = UVec2::new(size.width as u16, size.height as u16);
1184
1185 Material {
1186 texture_and_sampler_bind_group,
1187 texture_size,
1189 }
1190 }
1191}
1192
1193fn create_view_projection_matrix_from_virtual(virtual_width: u16, virtual_height: u16) -> Matrix4 {
1196 OrthoInfo {
1197 left: 0.0,
1198 right: virtual_width as f32,
1199 bottom: 0.0,
1200 top: virtual_height as f32,
1201 near: 1.0,
1202 far: -1.0,
1203 }
1204 .into()
1205}
1206
1207fn create_view_uniform_view_projection_matrix(viewport_size: UVec2) -> Matrix4 {
1208 let viewport_width = viewport_size.x as f32;
1209 let viewport_height = viewport_size.y as f32;
1210
1211 let viewport_aspect_ratio = viewport_width / viewport_height;
1212
1213 let scale_x = 1.0;
1214 let scale_y = viewport_aspect_ratio; let view_projection_matrix = [
1217 [scale_x, 0.0, 0.0, 0.0],
1218 [0.0, scale_y, 0.0, 0.0],
1219 [0.0, 0.0, -1.0, 0.0],
1220 [0.0, 0.0, 0.0, 1.0],
1221 ];
1222
1223 view_projection_matrix.into()
1224}
1225
1226fn sort_render_items_by_z_and_material(items: &mut [RenderItem]) {
1227 items.sort_by_key(|item| (item.position.z, item.material_ref));
1228}
1229
1230#[derive(Debug, Clone, Copy, Default)]
1231pub enum Rotation {
1232 #[default]
1233 Degrees0,
1234 Degrees90,
1235 Degrees180,
1236 Degrees270,
1237}
1238
1239#[derive(Debug, Copy, Clone)]
1240pub struct SpriteParams {
1241 pub texture_size: UVec2,
1242 pub texture_pos: UVec2,
1243 pub scale: u8,
1244 pub rotation: Rotation,
1245 pub flip_x: bool,
1246 pub flip_y: bool,
1247 pub pivot: Vec2,
1248 pub color: Color,
1249}
1250
1251impl Default for SpriteParams {
1252 fn default() -> Self {
1253 Self {
1254 texture_size: UVec2::new(0, 0),
1255 texture_pos: UVec2::new(0, 0),
1256 pivot: Vec2::new(0, 0),
1257 flip_x: false,
1258 flip_y: false,
1259 color: Color::from_octet(255, 255, 255, 255),
1260 scale: 1,
1261 rotation: Rotation::Degrees0,
1262 }
1263 }
1264}
1265
1266#[derive(Debug, PartialEq, Eq, Asset)]
1267pub struct Material {
1268 pub texture_and_sampler_bind_group: BindGroup,
1269 pub texture_size: UVec2,
1270}
1271
1272impl PartialOrd<Self> for Material {
1273 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1274 Some(
1275 self.texture_and_sampler_bind_group
1276 .cmp(&other.texture_and_sampler_bind_group),
1277 )
1278 }
1279}
1280
1281impl Ord for Material {
1282 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1283 self.texture_and_sampler_bind_group
1284 .cmp(&other.texture_and_sampler_bind_group)
1285 }
1286}
1287
1288#[derive(Debug)]
1289pub struct Sprite {
1290 pub params: SpriteParams,
1291}
1292
1293#[derive(Debug)]
1294pub struct QuadColor {
1295 pub size: UVec2,
1296 pub color: Color,
1297}
1298
1299#[derive(Debug)]
1300pub struct NineSlice {
1301 pub corner_size: UVec2,
1302 pub texture_window_size: UVec2,
1303 pub size: UVec2,
1304 pub atlas_offset: UVec2,
1305 pub color: Color,
1306}
1307
1308#[derive(Debug)]
1309pub struct TileMap {
1310 pub tiles_data_grid_size: UVec2,
1311 pub cell_count_size: UVec2,
1312 pub one_cell_size: UVec2,
1313 pub tiles: Vec<u16>,
1314 pub scale: u8,
1315}
1316
1317pub type RenderPipelineRef = Arc<RenderPipeline>;
1318
1319const fn sources() -> (&'static str, &'static str) {
1320 let vertex_shader_source = "
1321// Bind Group 0: Uniforms (view-projection matrix)
1322struct Uniforms {
1323 view_proj: mat4x4<f32>,
1324};
1325
1326@group(0) @binding(0)
1327var<uniform> camera_uniforms: Uniforms;
1328
1329// Bind Group 1: Texture and Sampler (Unused in Vertex Shader but needed for consistency)
1330@group(1) @binding(0)
1331var diffuse_texture: texture_2d<f32>;
1332
1333@group(1) @binding(1)
1334var sampler_diffuse: sampler;
1335
1336// Vertex input structure
1337struct VertexInput {
1338 @location(0) position: vec3<f32>,
1339 @location(1) tex_coords: vec2<f32>,
1340 @builtin(instance_index) instance_idx: u32,
1341};
1342
1343// Vertex output structure to fragment shader
1344struct VertexOutput {
1345 @builtin(position) position: vec4<f32>,
1346 @location(0) tex_coords: vec2<f32>,
1347 @location(1) color: vec4<f32>,
1348 @location(2) use_texture: u32,
1349};
1350
1351// Vertex shader entry point
1352@vertex
1353fn vs_main(
1354 input: VertexInput,
1355 // Instance attributes
1356 @location(2) model_matrix0: vec4<f32>,
1357 @location(3) model_matrix1: vec4<f32>,
1358 @location(4) model_matrix2: vec4<f32>,
1359 @location(5) model_matrix3: vec4<f32>,
1360 @location(6) tex_multiplier: vec4<f32>,
1361 @location(7) rotation_step: u32,
1362 @location(8) color: vec4<f32>,
1363 @location(9) use_texture: u32,
1364) -> VertexOutput {
1365 var output: VertexOutput;
1366
1367 // Reconstruct the model matrix from the instance data
1368 let model_matrix = mat4x4<f32>(
1369 model_matrix0,
1370 model_matrix1,
1371 model_matrix2,
1372 model_matrix3,
1373 );
1374
1375 // Compute world position
1376 let world_position = model_matrix * vec4<f32>(input.position, 1.0);
1377
1378 // Apply view-projection matrix
1379 output.position = camera_uniforms.view_proj * world_position;
1380
1381 // Decode rotation_step
1382 let rotation_val = rotation_step & 3u; // Bits 0-1
1383 let flip_x = (rotation_step & 4u) != 0u; // Bit 2
1384 let flip_y = (rotation_step & 8u) != 0u; // Bit 3
1385
1386 // Rotate texture coordinates based on rotation_val
1387 var rotated_tex_coords = input.tex_coords;
1388 if (rotation_val == 1) {
1389 // 90 degrees rotation
1390 rotated_tex_coords = vec2<f32>(1.0 - input.tex_coords.y, input.tex_coords.x);
1391 } else if (rotation_val == 2) {
1392 // 180 degrees rotation
1393 rotated_tex_coords = vec2<f32>(1.0 - input.tex_coords.x, 1.0 - input.tex_coords.y);
1394 } else if (rotation_val == 3) {
1395 // 270 degrees rotation
1396 rotated_tex_coords = vec2<f32>(input.tex_coords.y, 1.0 - input.tex_coords.x);
1397 }
1398 // else rotation_val == Degrees0, no rotation
1399
1400 // Apply flipping
1401 if (flip_x) {
1402 rotated_tex_coords.x = 1.0 - rotated_tex_coords.x;
1403 }
1404 if (flip_y) {
1405 rotated_tex_coords.y = 1.0 - rotated_tex_coords.y;
1406 }
1407
1408 // Modify texture coordinates
1409 output.tex_coords = rotated_tex_coords * tex_multiplier.xy + tex_multiplier.zw;
1410 output.color = color;
1411 output.use_texture = use_texture;
1412
1413 return output;
1414}
1415 ";
1416 let fragment_shader_source = "
1419
1420// Bind Group 1: Texture and Sampler
1421@group(1) @binding(0)
1422var diffuse_texture: texture_2d<f32>;
1423
1424@group(1) @binding(1)
1425var sampler_diffuse: sampler;
1426
1427// Fragment input structure from vertex shader
1428struct VertexOutput {
1429 @builtin(position) position: vec4<f32>,
1430 @location(0) tex_coords: vec2<f32>,
1431 @location(1) color: vec4<f32>,
1432 @location(2) use_texture: u32,
1433};
1434
1435// Fragment shader entry point
1436@fragment
1437fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
1438 var final_color: vec4<f32>;
1439
1440 // Sample the texture using the texture coordinates
1441 let texture_color = textureSample(diffuse_texture, sampler_diffuse, input.tex_coords);
1442 if (input.use_texture == 1u) { // Check if use_texture is true (1)
1443 // Apply color modulation and opacity
1444 final_color = texture_color * input.color;
1445 } else {
1446 final_color = input.color;
1447 }
1448
1449 return final_color;
1450}
1451
1452";
1453 (vertex_shader_source, fragment_shader_source)
1454}