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 mireforge_font::Font;
14use mireforge_font::FontRef;
15use mireforge_font::WeakFontRef;
16use mireforge_render::prelude::*;
17use mireforge_wgpu_sprites::{SpriteInfo, SpriteInstanceUniform};
18use monotonic_time_rs::Millis;
19use std::cmp::Ordering;
20use std::fmt::Debug;
21use std::mem::swap;
22use std::sync::Arc;
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 NineSliceAndMaterial {
81 pub slices: Slices,
82 pub material_ref: MaterialRef,
83}
84
85#[derive(Debug, PartialEq, Eq)]
86pub struct FontAndMaterial {
87 pub font_ref: FontRef,
88 pub material_ref: MaterialRef,
89}
90
91pub trait Gfx {
92 fn sprite_atlas_frame(&mut self, position: Vec3, frame: u16, atlas: &impl FrameLookup);
93 fn sprite_atlas(&mut self, position: Vec3, atlas_rect: URect, material_ref: &MaterialRef);
94 fn draw_sprite(&mut self, position: Vec3, material_ref: &MaterialRef);
95 fn draw_sprite_ex(&mut self, position: Vec3, material_ref: &MaterialRef, params: &SpriteParams);
96 fn nine_slice(
97 &mut self,
98 position: Vec3,
99 size: UVec2,
100 color: Color,
101 nine_slice: &NineSliceAndMaterial,
102 );
103 fn set_origin(&mut self, position: Vec2);
104 fn set_clear_color(&mut self, color: Color);
105
106 fn tilemap_params(
107 &mut self,
108 position: Vec3,
109 tiles: &[u16],
110 width: u16,
111 atlas_ref: &FixedAtlas,
112 scale: u8,
113 );
114
115 fn text_draw(&mut self, position: Vec3, text: &str, font_ref: &FontAndMaterial, color: &Color);
116
117 #[must_use]
118 fn now(&self) -> Millis;
119
120 #[must_use]
121 fn physical_aspect_ratio(&self) -> AspectRatio;
122
123 #[must_use]
124 fn physical_size(&self) -> UVec2;
125
126 fn set_viewport(&mut self, viewport_strategy: ViewportStrategy);
127
128 #[must_use]
129 fn viewport(&self) -> &ViewportStrategy;
130
131 fn set_scale(&mut self, scale_factor: VirtualScale);
132}
133
134fn to_wgpu_color(c: Color) -> wgpu::Color {
135 let f = c.to_f64();
136 wgpu::Color {
137 r: f.0,
138 g: f.1,
139 b: f.2,
140 a: f.3,
141 }
142}
143
144#[derive(Debug)]
145struct RenderItem {
146 position: Vec3,
147 material_ref: WeakMaterialRef,
148
149 renderable: Renderable,
150}
151
152#[derive(Debug)]
153pub struct Text {
154 text: String,
155 font_ref: WeakFontRef,
156 color: Color,
157}
158
159#[derive(Debug)]
160enum Renderable {
161 Sprite(Sprite),
162 QuadColor(QuadColor),
163 NineSlice(NineSlice),
164 TileMap(TileMap),
165 Text(Text),
166}
167
168#[derive(Resource)]
169pub struct Render {
170 index_buffer: Buffer, vertex_buffer: Buffer, sampler: wgpu::Sampler,
173 pipeline: RenderPipelineRef,
174 physical_surface_size: UVec2,
175 viewport_strategy: ViewportStrategy,
176 camera_bind_group: BindGroup,
178 #[allow(unused)]
179 camera_buffer: Buffer,
180
181 texture_sampler_bind_group_layout: BindGroupLayout,
183
184 quad_matrix_and_uv_instance_buffer: Buffer,
186
187 device: Arc<wgpu::Device>,
188 queue: Arc<wgpu::Queue>, items: Vec<RenderItem>,
192 origin: Vec2,
194
195 batch_offsets: Vec<(WeakMaterialRef, u32, u32)>,
197 viewport: URect,
198 clear_color: wgpu::Color,
199 last_render_at: Millis,
200 scale: f32,
201}
202
203impl Debug for Render {
204 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205 write!(f, "Render")
206 }
207}
208
209impl Gfx for Render {
210 fn sprite_atlas_frame(&mut self, position: Vec3, frame: u16, atlas: &impl FrameLookup) {
211 self.sprite_atlas_frame(position, frame, atlas);
212 }
213
214 fn sprite_atlas(&mut self, position: Vec3, atlas_rect: URect, material_ref: &MaterialRef) {
215 self.sprite_atlas(position, atlas_rect, material_ref);
216 }
217
218 fn draw_sprite(&mut self, position: Vec3, material_ref: &MaterialRef) {
219 self.draw_sprite(position, material_ref);
220 }
221
222 fn draw_sprite_ex(
223 &mut self,
224 position: Vec3,
225 material_ref: &MaterialRef,
226 params: &SpriteParams,
227 ) {
228 self.draw_sprite_ex(position, material_ref, *params);
229 }
230
231 fn nine_slice(
232 &mut self,
233 position: Vec3,
234 size: UVec2,
235 color: Color,
236 nine_slice: &NineSliceAndMaterial,
237 ) {
238 self.nine_slice(position, size, color, nine_slice);
239 }
240
241 fn set_origin(&mut self, position: Vec2) {
242 self.origin = position;
243 }
244
245 fn set_clear_color(&mut self, color: Color) {
246 self.clear_color = to_wgpu_color(color);
247 }
248
249 fn tilemap_params(
250 &mut self,
251 position: Vec3,
252 tiles: &[u16],
253 width: u16,
254 atlas_ref: &FixedAtlas,
255 scale: u8,
256 ) {
257 self.items.push(RenderItem {
258 position,
259 material_ref: (&atlas_ref.material).into(),
260 renderable: Renderable::TileMap(TileMap {
261 tiles_data_grid_size: UVec2::new(width, tiles.len() as u16 / width),
262 cell_count_size: atlas_ref.cell_count_size,
263 one_cell_size: atlas_ref.one_cell_size,
264 tiles: Vec::from(tiles),
265 scale,
266 }),
267 });
268 }
269
270 fn text_draw(
271 &mut self,
272 position: Vec3,
273 text: &str,
274 font_and_mat: &FontAndMaterial,
275 color: &Color,
276 ) {
277 self.items.push(RenderItem {
278 position,
279 material_ref: (&font_and_mat.material_ref).into(),
280 renderable: Renderable::Text(Text {
281 text: text.to_string(),
282 font_ref: (&font_and_mat.font_ref).into(),
283 color: *color,
284 }),
285 });
286 }
287
288 fn now(&self) -> Millis {
289 self.last_render_at
290 }
291
292 fn physical_aspect_ratio(&self) -> AspectRatio {
293 self.physical_surface_size.into()
294 }
295
296 fn physical_size(&self) -> UVec2 {
297 self.physical_surface_size
298 }
299
300 fn set_viewport(&mut self, viewport_strategy: ViewportStrategy) {
301 self.viewport_strategy = viewport_strategy;
302 }
303
304 fn viewport(&self) -> &ViewportStrategy {
305 &self.viewport_strategy
306 }
307
308 fn set_scale(&mut self, scale_factor: VirtualScale) {
309 match scale_factor {
310 VirtualScale::IntScale(scale) => self.scale = scale as f32,
311 VirtualScale::FloatScale(scale) => self.scale = scale,
312 }
313 }
314}
315
316impl Render {
317 #[must_use]
318 pub fn new(
319 device: Arc<wgpu::Device>,
320 queue: Arc<wgpu::Queue>, surface_texture_format: wgpu::TextureFormat,
322 physical_size: UVec2,
323 virtual_surface_size: UVec2,
324 now: Millis,
325 ) -> Self {
326 let (vertex_shader_source, fragment_shader_source) = sources();
327
328 let sprite_info = SpriteInfo::new(
329 &device,
330 surface_texture_format,
331 vertex_shader_source,
332 fragment_shader_source,
333 create_view_uniform_view_projection_matrix(physical_size),
334 );
335
336 Self {
337 device,
338 queue,
339 items: Vec::new(),
340 sampler: sprite_info.sampler,
342 pipeline: Arc::new(sprite_info.sprite_pipeline),
343 texture_sampler_bind_group_layout: sprite_info.sprite_texture_sampler_bind_group_layout,
344 index_buffer: sprite_info.index_buffer,
345 vertex_buffer: sprite_info.vertex_buffer,
346 quad_matrix_and_uv_instance_buffer: sprite_info.quad_matrix_and_uv_instance_buffer,
347 camera_bind_group: sprite_info.camera_bind_group,
348 batch_offsets: Vec::new(),
349 camera_buffer: sprite_info.camera_uniform_buffer,
350 viewport: Self::viewport_from_integer_scale(physical_size, virtual_surface_size),
351 clear_color: to_wgpu_color(Color::from_f32(0.008, 0.015, 0.008, 1.0)),
352 origin: Vec2::new(0, 0),
353 last_render_at: now,
354 physical_surface_size: physical_size,
355 viewport_strategy: ViewportStrategy::FitIntegerScaling(virtual_surface_size),
356 scale: 1.0,
357 }
358 }
359
360 pub fn set_now(&mut self, now: Millis) {
361 self.last_render_at = now;
362 }
363
364 pub const fn virtual_surface_size(&self) -> UVec2 {
365 match self.viewport_strategy {
366 ViewportStrategy::FitIntegerScaling(virtual_size)
367 | ViewportStrategy::FitFloatScaling(virtual_size) => virtual_size,
368 ViewportStrategy::MatchPhysicalSize => self.physical_surface_size,
369 }
370 }
371
372 pub const fn physical_surface_size(&self) -> UVec2 {
373 self.physical_surface_size
374 }
375
376 pub const fn viewport(&self) -> URect {
377 self.viewport
378 }
379
380 #[inline(always)]
381 fn push_sprite(&mut self, position: Vec3, material: &MaterialRef, sprite: Sprite) {
382 self.items.push(RenderItem {
383 position,
384 material_ref: material.into(),
385 renderable: Renderable::Sprite(sprite),
386 });
387 }
388
389 pub fn push_nine_slice(
390 &mut self,
391 position: Vec3,
392 size: UVec2,
393 color: Color,
394 nine_slice_and_material: &NineSliceAndMaterial,
395 ) {
396 let nine_slice_info = NineSlice {
397 size,
398 slices: nine_slice_and_material.slices.clone(),
399 color,
400 origin_in_atlas: UVec2::new(0, 0),
401 size_inside_atlas: None,
402 };
403
404 self.items.push(RenderItem {
405 position,
406 material_ref: (&nine_slice_and_material.material_ref).into(),
407 renderable: Renderable::NineSlice(nine_slice_info),
408 });
409 }
410
411 #[must_use]
412 pub fn viewport_from_integer_scale(physical_size: UVec2, virtual_size: UVec2) -> URect {
413 let window_aspect = physical_size.x as f32 / physical_size.y as f32;
414 let virtual_aspect = virtual_size.x as f32 / virtual_size.y as f32;
415
416 if physical_size.x < virtual_size.x || physical_size.y < virtual_size.y {
417 return URect::new(0, 0, physical_size.x, physical_size.y);
418 }
419
420 let mut integer_scale = if window_aspect > virtual_aspect {
421 physical_size.y / virtual_size.y
422 } else {
423 physical_size.x / virtual_size.x
424 };
425
426 if integer_scale < 1 {
427 integer_scale = 1;
428 }
429
430 let viewport_actual_size = UVec2::new(
431 virtual_size.x * integer_scale,
432 virtual_size.y * integer_scale,
433 );
434
435 let border_size = physical_size - viewport_actual_size;
436
437 let offset = border_size / 2;
438
439 URect::new(
440 offset.x,
441 offset.y,
442 viewport_actual_size.x,
443 viewport_actual_size.y,
444 )
445 }
446
447 #[must_use]
448 pub fn viewport_from_float_scale(physical_size: UVec2, virtual_size: UVec2) -> URect {
449 let window_aspect = physical_size.x as f32 / physical_size.y as f32;
450 let virtual_aspect = virtual_size.x as f32 / virtual_size.y as f32;
451
452 if physical_size.x < virtual_size.x || physical_size.y < virtual_size.y {
453 return URect::new(0, 0, physical_size.x, physical_size.y);
454 }
455
456 let mut float_scale = if window_aspect > virtual_aspect {
457 physical_size.y as f32 / virtual_size.y as f32
458 } else {
459 physical_size.x as f32 / virtual_size.x as f32
460 };
461
462 if float_scale < 0.01 {
463 float_scale = 0.01;
464 }
465
466 let viewport_actual_size = UVec2::new(
467 (virtual_size.x as f32 * float_scale) as u16,
468 (virtual_size.y as f32 * float_scale) as u16,
469 );
470
471 let border_size = physical_size - viewport_actual_size;
472
473 let offset = border_size / 2;
474
475 URect::new(
476 offset.x,
477 offset.y,
478 viewport_actual_size.x,
479 viewport_actual_size.y,
480 )
481 }
482
483 pub fn resize(&mut self, physical_size: UVec2) {
484 self.physical_surface_size = physical_size;
485 }
486
487 pub fn sprite_atlas(&mut self, position: Vec3, atlas_rect: URect, material_ref: &MaterialRef) {
495 self.push_sprite(
496 position,
497 material_ref,
498 Sprite {
499 params: SpriteParams {
500 texture_pos: atlas_rect.position,
501 texture_size: atlas_rect.size,
502 ..Default::default()
503 },
504 },
505 );
506 }
507
508 pub fn sprite_atlas_frame(&mut self, position: Vec3, frame: u16, atlas: &impl FrameLookup) {
509 let (material_ref, atlas_rect) = atlas.lookup(frame);
510 self.push_sprite(
511 position,
512 material_ref,
513 Sprite {
514 params: SpriteParams {
515 texture_pos: atlas_rect.position,
516 texture_size: atlas_rect.size,
517 ..Default::default()
518 },
519 },
520 );
521 }
522
523 pub fn sprite_atlas_frame_ex(
524 &mut self,
525 position: Vec3,
526 frame: u16,
527 atlas: &impl FrameLookup,
528 mut params: SpriteParams,
529 ) {
530 let (material_ref, atlas_rect) = atlas.lookup(frame);
531 params.texture_pos = atlas_rect.position;
532 params.texture_size = atlas_rect.size;
533 self.push_sprite(position, material_ref, Sprite { params });
534 }
535
536 pub fn draw_sprite(&mut self, position: Vec3, material: &MaterialRef) {
537 self.push_sprite(
538 position,
539 material,
540 Sprite {
541 params: SpriteParams::default(),
542 },
543 );
544 }
545
546 pub fn draw_sprite_ex(&mut self, position: Vec3, material: &MaterialRef, params: SpriteParams) {
547 self.push_sprite(position, material, Sprite { params });
548 }
549
550 pub fn nine_slice(
551 &mut self,
552 position: Vec3,
553 size: UVec2,
554 color: Color,
555 nine_slice_and_material: &NineSliceAndMaterial,
556 ) {
557 self.push_nine_slice(position, size, color, nine_slice_and_material);
558 }
559
560 pub fn draw_quad(&mut self, position: Vec3, size: UVec2, color: Color) {
561 self.items.push(RenderItem {
562 position,
563 material_ref: WeakId::<Material>::new(RawWeakId::with_asset_type::<Material>(
564 RawAssetId::new(0, 0),
565 "nothing".into(),
566 )),
567 renderable: Renderable::QuadColor(QuadColor { size, color }),
568 });
569 }
570
571 #[allow(clippy::too_many_arguments)]
572 pub fn draw_nine_slice(
573 &mut self,
574 position: Vec3,
575 size: UVec2,
576 slices: Slices,
577 material_ref: &MaterialRef,
578 color: Color,
579 ) {
580 self.items.push(RenderItem {
581 position,
582 material_ref: material_ref.into(),
583 renderable: Renderable::NineSlice(NineSlice {
584 size,
585 slices,
586 color,
587 origin_in_atlas: UVec2::new(0, 0),
588 size_inside_atlas: None,
589 }),
590 });
591 }
592
593 pub const fn clear_color(&self) -> wgpu::Color {
622 self.clear_color
623 }
624
625 fn calculate_texture_coords_mul_add(atlas_rect: URect, texture_size: UVec2) -> Vec4 {
627 let x = atlas_rect.position.x as f32 / texture_size.x as f32;
628 let y = atlas_rect.position.y as f32 / texture_size.y as f32;
629 let width = atlas_rect.size.x as f32 / texture_size.x as f32;
630 let height = atlas_rect.size.y as f32 / texture_size.y as f32;
631 Vec4([width, height, x, y])
632 }
633
634 fn order_render_items_in_batches(&self) -> Vec<Vec<&RenderItem>> {
635 let mut material_batches: Vec<Vec<&RenderItem>> = Vec::new();
636 let mut current_batch: Vec<&RenderItem> = Vec::new();
637 let mut current_material: Option<&WeakMaterialRef> = None;
638
639 for render_item in &self.items {
640 if Some(&render_item.material_ref) != current_material {
641 if !current_batch.is_empty() {
642 material_batches.push(current_batch.clone());
643 current_batch.clear();
644 }
645 current_material = Some(&render_item.material_ref);
646 }
647 current_batch.push(render_item);
648 }
649
650 if !current_batch.is_empty() {
651 material_batches.push(current_batch);
652 }
653
654 material_batches
655 }
656
657 #[must_use]
658 pub fn quad_helper_uniform(
659 position: Vec3,
660 quad_size: UVec2,
661 render_atlas: URect,
662 color: Color,
663
664 current_texture_size: UVec2,
665 ) -> SpriteInstanceUniform {
666 let model_matrix = Matrix4::from_translation(position.x as f32, position.y as f32, 0.0)
667 * Matrix4::from_scale(quad_size.x as f32, quad_size.y as f32, 1.0);
668
669 let tex_coords_mul_add =
670 Self::calculate_texture_coords_mul_add(render_atlas, current_texture_size);
671
672 let rotation_value = 0;
673
674 SpriteInstanceUniform::new(
675 model_matrix,
676 tex_coords_mul_add,
677 rotation_value,
678 Vec4(color.to_f32_slice()),
679 true,
680 )
681 }
682
683 #[allow(clippy::too_many_lines)]
686 pub fn prepare_render(&mut self, materials: &Assets<Material>, fonts: &Assets<Font>) {
687 const FLIP_X_MASK: u32 = 0b0000_0100;
688 const FLIP_Y_MASK: u32 = 0b0000_1000;
689
690 sort_render_items_by_z_and_material(&mut self.items);
691
692 let batches = self.order_render_items_in_batches();
693
694 let mut quad_matrix_and_uv: Vec<SpriteInstanceUniform> = Vec::new();
695 let mut batch_vertex_ranges: Vec<(WeakMaterialRef, u32, u32)> = Vec::new();
696
697 for render_items in &batches {
698 let quad_len_before = quad_matrix_and_uv.len() as u32;
699
700 let weak_material_ref = render_items
702 .first()
703 .map(|item| {
704 let material_ref: WeakId<Material> = item.material_ref;
706 material_ref
707 })
708 .expect("Render items batch was empty");
709
710 let result = materials.get_weak(weak_material_ref);
711 if result.is_none() {
712 continue;
714 }
715 let material = result.unwrap();
716 let current_texture_size = material.texture_size;
717
718 for render_item in render_items {
719 match &render_item.renderable {
720 Renderable::Sprite(sprite) => {
721 let params = &sprite.params;
722 let mut size = params.texture_size;
723 if size.x == 0 && size.y == 0 {
724 size = current_texture_size;
725 }
726
727 let render_atlas = URect {
728 position: params.texture_pos,
729 size,
730 };
731
732 match params.rotation {
733 Rotation::Degrees90 | Rotation::Degrees270 => {
734 swap(&mut size.x, &mut size.y);
735 }
736 _ => {}
737 }
738
739 let model_matrix = Matrix4::from_translation(
740 render_item.position.x as f32,
741 render_item.position.y as f32,
742 0.0,
743 ) * Matrix4::from_scale(
744 (size.x * params.scale as u16) as f32,
745 (size.y * params.scale as u16) as f32,
746 1.0,
747 );
748
749 let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
750 render_atlas,
751 current_texture_size,
752 );
753
754 let mut rotation_value = match params.rotation {
755 Rotation::Degrees0 => 0,
756 Rotation::Degrees90 => 1,
757 Rotation::Degrees180 => 2,
758 Rotation::Degrees270 => 3,
759 };
760
761 if params.flip_x {
762 rotation_value |= FLIP_X_MASK;
763 }
764 if params.flip_y {
765 rotation_value |= FLIP_Y_MASK;
766 }
767
768 let quad_instance = SpriteInstanceUniform::new(
769 model_matrix,
770 tex_coords_mul_add,
771 rotation_value,
772 Vec4(params.color.to_f32_slice()),
773 true,
774 );
775 quad_matrix_and_uv.push(quad_instance);
776 }
777
778 Renderable::NineSlice(nine_slice) => {
779 Self::prepare_nine_slice(
780 nine_slice,
781 render_item.position,
782 &mut quad_matrix_and_uv,
783 current_texture_size,
784 );
785 }
786
787 Renderable::QuadColor(quad) => {
788 let model_matrix =
810 Matrix4::from_translation(
811 render_item.position.x as f32,
812 render_item.position.y as f32,
813 0.0,
814 ) * Matrix4::from_scale(quad.size.x as f32, quad.size.y as f32, 1.0);
815
816 let tex_coords_mul_add = Vec4([
840 0.0, 0.0, 0.0, 0.0,
843 ]);
844 let rotation_value = 0;
845
846 let quad_instance = SpriteInstanceUniform::new(
847 model_matrix,
848 tex_coords_mul_add,
849 rotation_value,
850 Vec4(quad.color.to_f32_slice()),
851 false,
852 );
853 quad_matrix_and_uv.push(quad_instance);
854 }
855
856 Renderable::Text(text) => {
857 let result = fonts.get_weak(text.font_ref);
858 if result.is_none() {
859 continue;
860 }
861 let font = result.unwrap();
862
863 let glyphs = font.draw(&text.text);
864 for glyph in glyphs {
865 let pos = render_item.position + Vec3::from(glyph.relative_position);
866 let texture_size = glyph.texture_rectangle.size;
867 let model_matrix =
868 Matrix4::from_translation(pos.x as f32, pos.y as f32, 0.0)
869 * Matrix4::from_scale(
870 texture_size.x as f32,
871 texture_size.y as f32,
872 1.0,
873 );
874 let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
875 glyph.texture_rectangle,
876 current_texture_size,
877 );
878
879 let quad_instance = SpriteInstanceUniform::new(
880 model_matrix,
881 tex_coords_mul_add,
882 0,
883 Vec4(text.color.to_f32_slice()),
884 true,
885 );
886 quad_matrix_and_uv.push(quad_instance);
887 }
888 }
889
890 Renderable::TileMap(tile_map) => {
891 for (index, tile) in tile_map.tiles.iter().enumerate() {
892 let cell_pos_x = (index as u16 % tile_map.tiles_data_grid_size.x)
893 * tile_map.one_cell_size.x
894 * tile_map.scale as u16;
895 let cell_pos_y = (index as u16 / tile_map.tiles_data_grid_size.x)
896 * tile_map.one_cell_size.y
897 * tile_map.scale as u16;
898 let cell_x = *tile % tile_map.cell_count_size.x;
899 let cell_y = *tile / tile_map.cell_count_size.x;
900
901 let tex_x = cell_x * tile_map.one_cell_size.x;
902 let tex_y = cell_y * tile_map.one_cell_size.x;
903
904 let cell_texture_area = URect::new(
905 tex_x,
906 tex_y,
907 tile_map.one_cell_size.x,
908 tile_map.one_cell_size.y,
909 );
910
911 let cell_model_matrix = Matrix4::from_translation(
912 (render_item.position.x + cell_pos_x as i16) as f32,
913 (render_item.position.y + cell_pos_y as i16) as f32,
914 0.0,
915 ) * Matrix4::from_scale(
916 (tile_map.one_cell_size.x * tile_map.scale as u16) as f32,
917 (tile_map.one_cell_size.y * tile_map.scale as u16) as f32,
918 1.0,
919 );
920
921 let cell_tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
922 cell_texture_area,
923 current_texture_size,
924 );
925
926 let quad_instance = SpriteInstanceUniform::new(
927 cell_model_matrix,
928 cell_tex_coords_mul_add,
929 0,
930 Vec4([1.0, 1.0, 1.0, 1.0]),
931 true,
932 );
933 quad_matrix_and_uv.push(quad_instance);
934 }
935 }
936 }
937 }
938
939 let quad_count = quad_matrix_and_uv.len() as u32 - quad_len_before;
940 batch_vertex_ranges.push((weak_material_ref, quad_len_before, quad_count));
941 }
942
943 self.queue.write_buffer(
945 &self.quad_matrix_and_uv_instance_buffer,
946 0,
947 bytemuck::cast_slice(&quad_matrix_and_uv),
948 );
949
950 self.batch_offsets = batch_vertex_ranges;
951 }
952
953 #[allow(clippy::too_many_lines)]
954 #[inline]
955 pub fn prepare_nine_slice(
956 nine_slice: &NineSlice,
957 position_offset: Vec3,
958 quad_matrix_and_uv: &mut Vec<SpriteInstanceUniform>,
959 current_texture_size: UVec2,
960 ) {
961 let color = nine_slice.color;
962 let world_window_size = nine_slice.size;
963 let slices = &nine_slice.slices;
964 let atlas_origin = nine_slice.origin_in_atlas;
965 let texture_window_size = nine_slice.size_inside_atlas.unwrap_or(current_texture_size);
966
967 let world_edge_width = nine_slice.size.x - slices.left - slices.right;
968 let world_edge_height = nine_slice.size.y - slices.top - slices.bottom;
969 let texture_edge_width = texture_window_size.x - slices.left - slices.right;
970 let texture_edge_height = texture_window_size.y - slices.top - slices.bottom;
971
972 let lower_left_pos = Vec3::new(position_offset.x, position_offset.y, 0);
975 let corner_size = UVec2::new(slices.left, slices.bottom);
976 let lower_left_quad_size = UVec2::new(corner_size.x, corner_size.y);
978 let lower_left_atlas = URect::new(
979 atlas_origin.x,
980 atlas_origin.y + texture_window_size.y - slices.bottom, corner_size.x,
982 corner_size.y,
983 );
984 let lower_left_quad = Self::quad_helper_uniform(
985 lower_left_pos,
986 lower_left_quad_size,
987 lower_left_atlas,
988 color,
989 current_texture_size,
990 );
991 quad_matrix_and_uv.push(lower_left_quad);
992
993 let lower_side_position =
995 Vec3::new(position_offset.x + slices.left as i16, position_offset.y, 0);
996 let lower_side_world_quad_size = UVec2::new(world_edge_width, slices.bottom);
999 let lower_side_texture_size = UVec2::new(texture_edge_width, slices.bottom);
1000 let lower_side_atlas = URect::new(
1002 atlas_origin.x + slices.left,
1003 atlas_origin.y + texture_window_size.y - slices.bottom, lower_side_texture_size.x,
1005 lower_side_texture_size.y,
1006 );
1007 let lower_side_quad = Self::quad_helper_uniform(
1008 lower_side_position,
1009 lower_side_world_quad_size,
1010 lower_side_atlas,
1011 color,
1012 current_texture_size,
1013 );
1014 quad_matrix_and_uv.push(lower_side_quad);
1015
1016 let lower_right_pos = Vec3::new(
1018 position_offset.x + (world_window_size.x - slices.right) as i16,
1019 position_offset.y,
1020 0,
1021 );
1022 let lower_right_corner_size = UVec2::new(slices.right, slices.bottom);
1023 let lower_right_atlas = URect::new(
1024 atlas_origin.x + texture_window_size.x - slices.right,
1025 atlas_origin.y + texture_window_size.y - slices.bottom, lower_right_corner_size.x,
1027 lower_right_corner_size.y,
1028 );
1029 let lower_right_quad = Self::quad_helper_uniform(
1030 lower_right_pos,
1031 lower_right_corner_size,
1032 lower_right_atlas,
1033 color,
1034 current_texture_size,
1035 );
1036 quad_matrix_and_uv.push(lower_right_quad);
1037
1038 let left_edge_pos = Vec3::new(
1040 position_offset.x,
1041 position_offset.y + slices.bottom as i16,
1042 0,
1043 );
1044 let left_edge_world_quad_size = UVec2::new(slices.left, world_edge_height);
1045 let left_edge_texture_size = UVec2::new(slices.left, texture_edge_height);
1046 let left_edge_atlas = URect::new(
1047 atlas_origin.x,
1048 atlas_origin.y + slices.top, left_edge_texture_size.x,
1050 left_edge_texture_size.y,
1051 );
1052 let left_edge_quad = Self::quad_helper_uniform(
1053 left_edge_pos,
1054 left_edge_world_quad_size,
1055 left_edge_atlas,
1056 color,
1057 current_texture_size,
1058 );
1059 quad_matrix_and_uv.push(left_edge_quad);
1060
1061 let base_center_x = atlas_origin.x + slices.left;
1067 let base_center_y = atlas_origin.y + slices.top;
1068
1069 let repeat_x_count = (world_edge_width as f32 / texture_edge_width as f32).ceil() as usize;
1071 let repeat_y_count =
1072 (world_edge_height as f32 / texture_edge_height as f32).ceil() as usize;
1073
1074 for y in 0..repeat_y_count {
1075 for x in 0..repeat_x_count {
1076 let this_quad_width =
1077 if x == repeat_x_count - 1 && world_edge_width % texture_edge_width != 0 {
1078 world_edge_width % texture_edge_width
1079 } else {
1080 texture_edge_width
1081 };
1082
1083 let this_quad_height =
1084 if y == repeat_y_count - 1 && world_edge_height % texture_edge_height != 0 {
1085 world_edge_height % texture_edge_height
1086 } else {
1087 texture_edge_height
1088 };
1089
1090 let quad_pos = Vec3::new(
1091 position_offset.x + slices.left as i16 + (x as u16 * texture_edge_width) as i16,
1092 position_offset.y
1093 + slices.bottom as i16
1094 + (y as u16 * texture_edge_height) as i16,
1095 0,
1096 );
1097
1098 let texture_x = base_center_x;
1099
1100 let texture_y = if y == repeat_y_count - 1 && this_quad_height < texture_edge_height
1101 {
1102 base_center_y + (texture_edge_height - this_quad_height)
1103 } else {
1104 base_center_y
1105 };
1106
1107 let this_texture_region =
1108 URect::new(texture_x, texture_y, this_quad_width, this_quad_height);
1109
1110 let center_quad = Self::quad_helper_uniform(
1111 quad_pos,
1112 UVec2::new(this_quad_width, this_quad_height),
1113 this_texture_region,
1114 color,
1115 current_texture_size,
1116 );
1117
1118 quad_matrix_and_uv.push(center_quad);
1119 }
1120 }
1121 let right_edge_pos = Vec3::new(
1125 position_offset.x + (world_window_size.x - slices.right) as i16,
1126 position_offset.y + slices.bottom as i16,
1127 0,
1128 );
1129 let right_edge_world_quad_size = UVec2::new(slices.right, world_edge_height);
1130 let right_edge_texture_size = UVec2::new(slices.right, texture_edge_height);
1131 let right_edge_atlas = URect::new(
1132 atlas_origin.x + texture_window_size.x - slices.right,
1133 atlas_origin.y + slices.top, right_edge_texture_size.x,
1135 right_edge_texture_size.y,
1136 );
1137
1138 let right_edge_quad = Self::quad_helper_uniform(
1139 right_edge_pos,
1140 right_edge_world_quad_size,
1141 right_edge_atlas,
1142 color,
1143 current_texture_size,
1144 );
1145 quad_matrix_and_uv.push(right_edge_quad);
1146
1147 let top_left_pos = Vec3::new(
1149 position_offset.x,
1150 position_offset.y + (world_window_size.y - slices.top) as i16,
1151 0,
1152 );
1153 let top_left_corner_size = UVec2::new(slices.left, slices.top);
1154 let top_left_atlas = URect::new(
1155 atlas_origin.x,
1156 atlas_origin.y, top_left_corner_size.x,
1158 top_left_corner_size.y,
1159 );
1160 let top_left_quad = Self::quad_helper_uniform(
1161 top_left_pos,
1162 top_left_corner_size,
1163 top_left_atlas,
1164 color,
1165 current_texture_size,
1166 );
1167 quad_matrix_and_uv.push(top_left_quad);
1168
1169 let top_edge_pos = Vec3::new(
1171 position_offset.x + slices.left as i16,
1172 position_offset.y + (world_window_size.y - slices.top) as i16,
1173 0,
1174 );
1175 let top_edge_world_quad_size = UVec2::new(world_edge_width, slices.top);
1176 let top_edge_texture_size = UVec2::new(texture_edge_width, slices.top);
1177 let top_edge_atlas = URect::new(
1178 atlas_origin.x + slices.left,
1179 atlas_origin.y, top_edge_texture_size.x,
1181 top_edge_texture_size.y,
1182 );
1183 let top_edge_quad = Self::quad_helper_uniform(
1184 top_edge_pos,
1185 top_edge_world_quad_size,
1186 top_edge_atlas,
1187 color,
1188 current_texture_size,
1189 );
1190 quad_matrix_and_uv.push(top_edge_quad);
1191
1192 let top_right_pos = Vec3::new(
1194 position_offset.x + (world_window_size.x - slices.right) as i16,
1195 position_offset.y + (world_window_size.y - slices.top) as i16,
1196 0,
1197 );
1198 let top_right_corner_size = UVec2::new(slices.right, slices.top);
1199 let top_right_atlas = URect::new(
1200 atlas_origin.x + texture_window_size.x - slices.right,
1201 atlas_origin.y, top_right_corner_size.x,
1203 top_right_corner_size.y,
1204 );
1205 let top_right_quad = Self::quad_helper_uniform(
1206 top_right_pos,
1207 top_right_corner_size,
1208 top_right_atlas,
1209 color,
1210 current_texture_size,
1211 );
1212 quad_matrix_and_uv.push(top_right_quad);
1213 }
1214
1215 pub fn render(
1218 &mut self,
1219 render_pass: &mut RenderPass,
1220 materials: &Assets<Material>,
1221 fonts: &Assets<Font>,
1222 now: Millis,
1223 ) {
1224 trace!("start render()");
1225 self.last_render_at = now;
1226
1227 self.viewport = match self.viewport_strategy {
1228 ViewportStrategy::FitIntegerScaling(virtual_surface_size) => {
1229 Self::viewport_from_integer_scale(self.physical_surface_size, virtual_surface_size)
1230 }
1231 ViewportStrategy::FitFloatScaling(virtual_surface_size) => {
1232 Self::viewport_from_float_scale(self.physical_surface_size, virtual_surface_size)
1233 }
1234 ViewportStrategy::MatchPhysicalSize => URect::new(
1235 0,
1236 0,
1237 self.physical_surface_size.x,
1238 self.physical_surface_size.y,
1239 ),
1240 };
1241
1242 let view_proj_matrix = match self.viewport_strategy {
1243 ViewportStrategy::MatchPhysicalSize => {
1244 create_view_uniform_view_projection_matrix(self.physical_surface_size)
1245 }
1246 ViewportStrategy::FitFloatScaling(virtual_surface_size)
1247 | ViewportStrategy::FitIntegerScaling(virtual_surface_size) => {
1248 create_view_projection_matrix_from_virtual(
1249 virtual_surface_size.x,
1250 virtual_surface_size.y,
1251 )
1252 }
1253 };
1254
1255 let scale_matrix = Matrix4::from_scale(self.scale, self.scale, 0.0);
1256 let origin_translation_matrix =
1257 Matrix4::from_translation(-self.origin.x as f32, -self.origin.y as f32, 0.0);
1258
1259 let total_matrix = scale_matrix * view_proj_matrix * origin_translation_matrix;
1260
1261 self.queue.write_buffer(
1263 &self.camera_buffer,
1264 0,
1265 bytemuck::cast_slice(&[total_matrix]),
1266 );
1267
1268 self.prepare_render(materials, fonts);
1269
1270 render_pass.set_viewport(
1271 self.viewport.position.x as f32,
1272 self.viewport.position.y as f32,
1273 self.viewport.size.x as f32,
1274 self.viewport.size.y as f32,
1275 0.0,
1276 1.0,
1277 );
1278
1279 render_pass.set_pipeline(&self.pipeline);
1280
1281 render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
1283 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1284
1285 render_pass.set_vertex_buffer(1, self.quad_matrix_and_uv_instance_buffer.slice(..));
1287
1288 render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
1290
1291 let num_indices = mireforge_wgpu_sprites::INDICES.len() as u32;
1292
1293 for &(weak_material_ref, start, count) in &self.batch_offsets {
1294 let wgpu_material = materials
1295 .get_weak(weak_material_ref)
1296 .expect("no such material");
1297
1298 render_pass.set_bind_group(1, &wgpu_material.texture_and_sampler_bind_group, &[]);
1300
1301 trace!(material=%weak_material_ref, start=%start, count=%count, "draw instanced");
1303 render_pass.draw_indexed(0..num_indices, 0, start..(start + count));
1304 }
1305
1306 self.items.clear();
1307 }
1308
1309 pub fn material_from_texture(&self, texture: wgpu::Texture, label: &str) -> Material {
1310 trace!("load texture from memory with name: '{label}'");
1311 let size = &texture.size();
1312 let texture_and_sampler_bind_group =
1313 mireforge_wgpu_sprites::create_sprite_texture_and_sampler_bind_group(
1314 &self.device,
1315 &self.texture_sampler_bind_group_layout,
1316 &texture,
1317 &self.sampler,
1318 label,
1319 );
1320
1321 let texture_size = UVec2::new(size.width as u16, size.height as u16);
1322
1323 Material {
1324 texture_and_sampler_bind_group,
1325 texture_size,
1327 }
1328 }
1329}
1330
1331fn create_view_projection_matrix_from_virtual(virtual_width: u16, virtual_height: u16) -> Matrix4 {
1334 OrthoInfo {
1335 left: 0.0,
1336 right: virtual_width as f32,
1337 bottom: 0.0,
1338 top: virtual_height as f32,
1339 near: 1.0,
1340 far: -1.0,
1341 }
1342 .into()
1343}
1344
1345fn create_view_uniform_view_projection_matrix(viewport_size: UVec2) -> Matrix4 {
1346 let viewport_width = viewport_size.x as f32;
1347 let viewport_height = viewport_size.y as f32;
1348
1349 let viewport_aspect_ratio = viewport_width / viewport_height;
1350
1351 let scale_x = 1.0;
1352 let scale_y = viewport_aspect_ratio; let view_projection_matrix = [
1355 [scale_x, 0.0, 0.0, 0.0],
1356 [0.0, scale_y, 0.0, 0.0],
1357 [0.0, 0.0, -1.0, 0.0],
1358 [0.0, 0.0, 0.0, 1.0],
1359 ];
1360
1361 view_projection_matrix.into()
1362}
1363
1364fn sort_render_items_by_z_and_material(items: &mut [RenderItem]) {
1365 items.sort_by_key(|item| (item.position.z, item.material_ref));
1366}
1367
1368#[derive(Debug, Clone, Copy, Default)]
1369pub enum Rotation {
1370 #[default]
1371 Degrees0,
1372 Degrees90,
1373 Degrees180,
1374 Degrees270,
1375}
1376
1377#[derive(Debug, Copy, Clone)]
1378pub struct SpriteParams {
1379 pub texture_size: UVec2,
1380 pub texture_pos: UVec2,
1381 pub scale: u8,
1382 pub rotation: Rotation,
1383 pub flip_x: bool,
1384 pub flip_y: bool,
1385 pub pivot: Vec2,
1386 pub color: Color,
1387}
1388
1389impl Default for SpriteParams {
1390 fn default() -> Self {
1391 Self {
1392 texture_size: UVec2::new(0, 0),
1393 texture_pos: UVec2::new(0, 0),
1394 pivot: Vec2::new(0, 0),
1395 flip_x: false,
1396 flip_y: false,
1397 color: Color::from_octet(255, 255, 255, 255),
1398 scale: 1,
1399 rotation: Rotation::Degrees0,
1400 }
1401 }
1402}
1403
1404#[derive(Debug, PartialEq, Eq, Asset)]
1405pub struct Material {
1406 pub texture_and_sampler_bind_group: BindGroup,
1407 pub texture_size: UVec2,
1408}
1409
1410impl PartialOrd<Self> for Material {
1411 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1412 Some(
1413 self.texture_and_sampler_bind_group
1414 .cmp(&other.texture_and_sampler_bind_group),
1415 )
1416 }
1417}
1418
1419impl Ord for Material {
1420 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1421 self.texture_and_sampler_bind_group
1422 .cmp(&other.texture_and_sampler_bind_group)
1423 }
1424}
1425
1426#[derive(Debug)]
1427pub struct Sprite {
1428 pub params: SpriteParams,
1429}
1430
1431#[derive(Debug)]
1432pub struct QuadColor {
1433 pub size: UVec2,
1434 pub color: Color,
1435}
1436
1437#[derive(Debug, Copy, Clone)]
1438pub struct Slices {
1439 pub left: u16,
1440 pub top: u16,
1441 pub right: u16, pub bottom: u16, }
1444
1445#[derive(Debug)]
1446pub struct NineSlice {
1447 pub size: UVec2, pub slices: Slices,
1449 pub color: Color, pub origin_in_atlas: UVec2,
1451 pub size_inside_atlas: Option<UVec2>,
1452}
1453
1454#[derive(Debug)]
1455pub struct TileMap {
1456 pub tiles_data_grid_size: UVec2,
1457 pub cell_count_size: UVec2,
1458 pub one_cell_size: UVec2,
1459 pub tiles: Vec<u16>,
1460 pub scale: u8,
1461}
1462
1463pub type RenderPipelineRef = Arc<RenderPipeline>;
1464
1465const fn sources() -> (&'static str, &'static str) {
1466 let vertex_shader_source = "
1467// Bind Group 0: Uniforms (view-projection matrix)
1468struct Uniforms {
1469 view_proj: mat4x4<f32>,
1470};
1471
1472@group(0) @binding(0)
1473var<uniform> camera_uniforms: Uniforms;
1474
1475// Bind Group 1: Texture and Sampler (Unused in Vertex Shader but needed for consistency)
1476@group(1) @binding(0)
1477var diffuse_texture: texture_2d<f32>;
1478
1479@group(1) @binding(1)
1480var sampler_diffuse: sampler;
1481
1482// Vertex input structure
1483struct VertexInput {
1484 @location(0) position: vec3<f32>,
1485 @location(1) tex_coords: vec2<f32>,
1486 @builtin(instance_index) instance_idx: u32,
1487};
1488
1489// Vertex output structure to fragment shader
1490struct VertexOutput {
1491 @builtin(position) position: vec4<f32>,
1492 @location(0) tex_coords: vec2<f32>,
1493 @location(1) color: vec4<f32>,
1494 @location(2) use_texture: u32,
1495};
1496
1497// Vertex shader entry point
1498@vertex
1499fn vs_main(
1500 input: VertexInput,
1501 // Instance attributes
1502 @location(2) model_matrix0: vec4<f32>,
1503 @location(3) model_matrix1: vec4<f32>,
1504 @location(4) model_matrix2: vec4<f32>,
1505 @location(5) model_matrix3: vec4<f32>,
1506 @location(6) tex_multiplier: vec4<f32>,
1507 @location(7) rotation_step: u32,
1508 @location(8) color: vec4<f32>,
1509 @location(9) use_texture: u32,
1510) -> VertexOutput {
1511 var output: VertexOutput;
1512
1513 // Reconstruct the model matrix from the instance data
1514 let model_matrix = mat4x4<f32>(
1515 model_matrix0,
1516 model_matrix1,
1517 model_matrix2,
1518 model_matrix3,
1519 );
1520
1521 // Compute world position
1522 let world_position = model_matrix * vec4<f32>(input.position, 1.0);
1523
1524 // Apply view-projection matrix
1525 output.position = camera_uniforms.view_proj * world_position;
1526
1527 // Decode rotation_step
1528 let rotation_val = rotation_step & 3u; // Bits 0-1
1529 let flip_x = (rotation_step & 4u) != 0u; // Bit 2
1530 let flip_y = (rotation_step & 8u) != 0u; // Bit 3
1531
1532 // Rotate texture coordinates based on rotation_val
1533 var rotated_tex_coords = input.tex_coords;
1534 if (rotation_val == 1) {
1535 // 90 degrees rotation
1536 rotated_tex_coords = vec2<f32>(1.0 - input.tex_coords.y, input.tex_coords.x);
1537 } else if (rotation_val == 2) {
1538 // 180 degrees rotation
1539 rotated_tex_coords = vec2<f32>(1.0 - input.tex_coords.x, 1.0 - input.tex_coords.y);
1540 } else if (rotation_val == 3) {
1541 // 270 degrees rotation
1542 rotated_tex_coords = vec2<f32>(input.tex_coords.y, 1.0 - input.tex_coords.x);
1543 }
1544 // else rotation_val == Degrees0, no rotation
1545
1546 // Apply flipping
1547 if (flip_x) {
1548 rotated_tex_coords.x = 1.0 - rotated_tex_coords.x;
1549 }
1550 if (flip_y) {
1551 rotated_tex_coords.y = 1.0 - rotated_tex_coords.y;
1552 }
1553
1554 // Modify texture coordinates
1555 output.tex_coords = rotated_tex_coords * tex_multiplier.xy + tex_multiplier.zw;
1556 output.color = color;
1557 output.use_texture = use_texture;
1558
1559 return output;
1560}
1561 ";
1562 let fragment_shader_source = "
1565
1566// Bind Group 1: Texture and Sampler
1567@group(1) @binding(0)
1568var diffuse_texture: texture_2d<f32>;
1569
1570@group(1) @binding(1)
1571var sampler_diffuse: sampler;
1572
1573// Fragment input structure from vertex shader
1574struct VertexOutput {
1575 @builtin(position) position: vec4<f32>,
1576 @location(0) tex_coords: vec2<f32>,
1577 @location(1) color: vec4<f32>,
1578 @location(2) use_texture: u32,
1579};
1580
1581// Fragment shader entry point
1582@fragment
1583fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
1584 var final_color: vec4<f32>;
1585
1586 // Sample the texture using the texture coordinates
1587 let texture_color = textureSample(diffuse_texture, sampler_diffuse, input.tex_coords);
1588 if (input.use_texture == 1u) { // Check if use_texture is true (1)
1589 // Apply color modulation and opacity
1590 final_color = texture_color * input.color;
1591 } else {
1592 final_color = input.color;
1593 }
1594
1595 return final_color;
1596}
1597
1598";
1599 (vertex_shader_source, fragment_shader_source)
1600}