1mod gfx;
6mod gfx_impl;
7pub mod plugin;
8pub mod prelude;
9
10use int_math::{URect, UVec2, Vec2, Vec3};
11use limnus_assets::prelude::{Asset, Id, WeakId};
12use limnus_assets::Assets;
13use limnus_resource::prelude::Resource;
14use limnus_wgpu_math::{Matrix4, OrthoInfo, Vec4};
15use mireforge_font::Font;
16use mireforge_font::FontRef;
17use mireforge_font::WeakFontRef;
18use mireforge_render::prelude::*;
19use mireforge_wgpu::create_nearest_sampler;
20use mireforge_wgpu_sprites::{
21 create_texture_and_sampler_bind_group_ex, create_texture_and_sampler_group_layout, ShaderInfo, SpriteInfo,
22 SpriteInstanceUniform,
23};
24use monotonic_time_rs::Millis;
25use std::cmp::Ordering;
26use std::fmt::{Debug, Display, Formatter};
27use std::mem::swap;
28use std::sync::Arc;
29use tracing::{debug, trace};
30use wgpu::{
31 BindGroup, BindGroupLayout, Buffer, CommandEncoder, Device, RenderPipeline, TextureFormat,
32 TextureView,
33};
34
35pub type MaterialRef = Arc<Material>;
36
37pub type WeakMaterialRef = Arc<Material>;
38
39pub type TextureRef = Id<Texture>;
40pub type WeakTextureRef = WeakId<Texture>;
41
42pub trait FrameLookup {
43 fn lookup(&self, frame: u16) -> (&MaterialRef, URect);
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct FixedAtlas {
48 pub material: MaterialRef,
49 pub texture_size: UVec2,
50 pub one_cell_size: UVec2,
51 pub cell_count_size: UVec2,
52}
53
54impl FixedAtlas {
55 #[must_use]
58 pub fn new(one_cell_size: UVec2, texture_size: UVec2, material_ref: MaterialRef) -> Self {
59 let cell_count_size = UVec2::new(
60 texture_size.x / one_cell_size.x,
61 texture_size.y / one_cell_size.y,
62 );
63
64 assert_ne!(cell_count_size.x, 0, "illegal texture and one cell size");
65
66 Self {
67 material: material_ref,
68 texture_size,
69 one_cell_size,
70 cell_count_size,
71 }
72 }
73}
74
75impl FrameLookup for FixedAtlas {
76 fn lookup(&self, frame: u16) -> (&MaterialRef, URect) {
77 let x = frame % self.cell_count_size.x;
78 let y = frame / self.cell_count_size.x;
79
80 (
81 &self.material,
82 URect::new(
83 x * self.one_cell_size.x,
84 y * self.one_cell_size.y,
85 self.one_cell_size.x,
86 self.one_cell_size.y,
87 ),
88 )
89 }
90}
91
92#[derive(Debug)]
93pub struct NineSliceAndMaterial {
94 pub slices: Slices,
95 pub material_ref: MaterialRef,
96}
97
98#[derive(Debug, PartialEq, Eq)]
99pub struct FontAndMaterial {
100 pub font_ref: FontRef,
101 pub material_ref: MaterialRef,
102}
103
104fn to_wgpu_color(c: Color) -> wgpu::Color {
105 let f = c.to_f64();
106 wgpu::Color {
107 r: f.0,
108 g: f.1,
109 b: f.2,
110 a: f.3,
111 }
112}
113
114#[derive(Debug)]
115struct RenderItem {
116 position: Vec3,
117 material_ref: MaterialRef,
118
119 renderable: Renderable,
120}
121
122#[derive(Debug)]
123pub struct Text {
124 text: String,
125 font_ref: WeakFontRef,
126 color: Color,
127}
128
129#[derive(Debug)]
130enum Renderable {
131 Sprite(Sprite),
132 QuadColor(QuadColor),
133 NineSlice(NineSlice),
134 TileMap(TileMap),
135 Text(Text),
136 Mask(UVec2, Color),
137}
138
139const MAXIMUM_QUADS_FOR_RENDER_ITEM: usize = 1024;
140const MAXIMUM_QUADS_IN_A_BATCH: usize = 4096;
141const MAXIMUM_QUADS_IN_ONE_RENDER: usize = MAXIMUM_QUADS_IN_A_BATCH * 8;
142
143#[derive(Resource)]
144pub struct Render {
145 virtual_surface_texture_view: TextureView,
146 virtual_surface_texture: wgpu::Texture,
147 virtual_to_surface_bind_group: BindGroup,
148 index_buffer: Buffer, vertex_buffer: Buffer, sampler: wgpu::Sampler,
151 virtual_to_screen_shader_info: ShaderInfo,
152 pub normal_sprite_pipeline: ShaderInfo,
153 pub quad_shader_info: ShaderInfo,
154 pub mask_shader_info: ShaderInfo,
155 pub light_shader_info: ShaderInfo,
156 physical_surface_size: UVec2,
157 viewport_strategy: ViewportStrategy,
158 virtual_surface_size: UVec2,
159 camera_bind_group: BindGroup,
161 #[allow(unused)]
162 camera_buffer: Buffer,
163
164 texture_sampler_bind_group_layout: BindGroupLayout,
166
167 quad_matrix_and_uv_instance_buffer: Buffer,
169
170 device: Arc<wgpu::Device>,
171 queue: Arc<wgpu::Queue>, items: Vec<RenderItem>,
175 origin: Vec2,
177
178 batch_offsets: Vec<(WeakMaterialRef, u32, u32)>,
180 viewport: URect,
181 clear_color: wgpu::Color,
182 screen_clear_color: wgpu::Color,
183 last_render_at: Millis,
184 scale: f32,
185 surface_texture_format: TextureFormat,
186
187 debug_tick: u64,
188}
189
190impl Render {}
191
192impl Debug for Render {
193 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194 write!(f, "Render")
195 }
196}
197
198impl Render {
199 #[must_use]
200 pub fn new(
201 device: Arc<wgpu::Device>,
202 queue: Arc<wgpu::Queue>, surface_texture_format: wgpu::TextureFormat,
204 physical_size: UVec2,
205 virtual_surface_size: UVec2,
206 now: Millis,
207 ) -> Self {
208 let sprite_info = SpriteInfo::new(
209 &device,
210 surface_texture_format,
211 create_view_uniform_view_projection_matrix(physical_size),
212 );
213
214 let (virtual_surface_texture, virtual_surface_texture_view, virtual_to_surface_bind_group) =
215 Self::create_virtual_texture(&device, surface_texture_format, virtual_surface_size);
216
217 Self {
218 device,
219 queue,
220 surface_texture_format,
221 items: Vec::new(),
222 virtual_to_screen_shader_info: sprite_info.virtual_to_screen_shader_info,
224 virtual_surface_texture,
225 virtual_surface_texture_view,
226 virtual_to_surface_bind_group,
227 sampler: sprite_info.sampler,
228 normal_sprite_pipeline: sprite_info.sprite_shader_info,
229 quad_shader_info: sprite_info.quad_shader_info,
230 mask_shader_info: sprite_info.mask_shader_info,
231 light_shader_info: sprite_info.light_shader_info,
232 texture_sampler_bind_group_layout: sprite_info.sprite_texture_sampler_bind_group_layout,
233 index_buffer: sprite_info.index_buffer,
234 vertex_buffer: sprite_info.vertex_buffer,
235 quad_matrix_and_uv_instance_buffer: sprite_info.quad_matrix_and_uv_instance_buffer,
236 camera_bind_group: sprite_info.camera_bind_group,
237 batch_offsets: Vec::new(),
238 camera_buffer: sprite_info.camera_uniform_buffer,
239 viewport: Self::viewport_from_integer_scale(physical_size, virtual_surface_size),
240 clear_color: to_wgpu_color(Color::from_f32(0.008, 0.015, 0.008, 1.0)),
241 screen_clear_color: to_wgpu_color(Color::from_f32(0.018, 0.025, 0.018, 1.0)),
242 origin: Vec2::new(0, 0),
243 last_render_at: now,
244 physical_surface_size: physical_size,
245 viewport_strategy: ViewportStrategy::FitIntegerScaling,
246 virtual_surface_size,
247 scale: 1.0,
248 debug_tick: 0,
249 }
250 }
251
252 pub fn create_virtual_texture(
253 device: &Device,
254 surface_texture_format: TextureFormat,
255 virtual_surface_size: UVec2,
256 ) -> (wgpu::Texture, TextureView, BindGroup) {
257 let virtual_surface_texture = device.create_texture(&wgpu::TextureDescriptor {
259 label: Some("Render Texture"),
260 size: wgpu::Extent3d {
261 width: virtual_surface_size.x as u32,
262 height: virtual_surface_size.y as u32,
263 depth_or_array_layers: 1,
264 },
265 mip_level_count: 1,
266 sample_count: 1,
267 dimension: wgpu::TextureDimension::D2,
268 format: surface_texture_format, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
270 view_formats: &[],
271 });
272
273 let virtual_surface_texture_view =
274 virtual_surface_texture.create_view(&wgpu::TextureViewDescriptor::default());
275
276 let virtual_to_screen_sampler =
277 create_nearest_sampler(device, "nearest sampler for virtual to screen");
278 let virtual_to_screen_layout =
279 create_texture_and_sampler_group_layout(device, "virtual to screen layout");
280 let virtual_to_surface_bind_group = create_texture_and_sampler_bind_group_ex(
281 device,
282 &virtual_to_screen_layout,
283 &virtual_surface_texture_view,
284 &virtual_to_screen_sampler,
285 "virtual to screen bind group",
286 );
287
288 (
289 virtual_surface_texture,
290 virtual_surface_texture_view,
291 virtual_to_surface_bind_group,
292 )
293 }
294
295 pub const fn set_now(&mut self, now: Millis) {
296 self.last_render_at = now;
297 }
298
299 pub const fn virtual_surface_size_with_scaling(&self) -> UVec2 {
300 match self.viewport_strategy {
301 ViewportStrategy::FitIntegerScaling | ViewportStrategy::FitFloatScaling => {
302 self.virtual_surface_size
303 }
304 ViewportStrategy::MatchPhysicalSize => self.physical_surface_size,
305 }
306 }
307
308 pub const fn physical_surface_size(&self) -> UVec2 {
309 self.physical_surface_size
310 }
311
312 pub const fn viewport(&self) -> URect {
313 self.viewport
314 }
315
316 #[inline]
317 fn push_sprite(&mut self, position: Vec3, material: &MaterialRef, sprite: Sprite) {
318 self.items.push(RenderItem {
319 position,
320 material_ref: material.clone(),
321 renderable: Renderable::Sprite(sprite),
322 });
323 }
324
325 pub fn push_mask(
326 &mut self,
327 position: Vec3,
328 size: UVec2,
329 color: Color,
330 alpha_masked: &MaterialRef,
331 ) {
332 self.items.push(RenderItem {
333 position,
334 material_ref: alpha_masked.clone(),
335 renderable: Renderable::Mask(size, color),
336 });
337 }
338
339 pub fn push_mask_create_material(
340 &mut self,
341 position: Vec3,
342 primary_texture: TextureRef,
343 alpha_texture: TextureRef,
344 texture_offset: UVec2,
345 color: Color,
346 ) {
347 let masked_material = Material {
348 base: MaterialBase {},
349 kind: MaterialKind::AlphaMasker {
350 primary_texture,
351 alpha_texture,
352 },
353 };
354
355 let masked_material_ref = Arc::new(masked_material);
356
357 self.items.push(RenderItem {
358 position,
359 material_ref: masked_material_ref.clone(),
360 renderable: Renderable::Mask(texture_offset, color),
361 });
362 }
363
364 pub fn push_nine_slice(
365 &mut self,
366 position: Vec3,
367 size: UVec2,
368 color: Color,
369 nine_slice_and_material: &NineSliceAndMaterial,
370 ) {
371 let nine_slice_info = NineSlice {
372 size,
373 slices: nine_slice_and_material.slices,
374 color,
375 origin_in_atlas: UVec2::new(0, 0),
376 size_inside_atlas: None,
377 };
378
379 self.items.push(RenderItem {
380 position,
381 material_ref: nine_slice_and_material.material_ref.clone(),
382 renderable: Renderable::NineSlice(nine_slice_info),
383 });
384 }
385
386 #[must_use]
387 pub fn viewport_from_integer_scale(physical_size: UVec2, virtual_size: UVec2) -> URect {
388 let scale_factor = (physical_size.x / virtual_size.x)
389 .min(physical_size.y / virtual_size.y)
390 .max(1);
391
392 let ideal_viewport_size = virtual_size * scale_factor;
393
394 let final_viewport_size =
395 if physical_size.x < ideal_viewport_size.x || physical_size.y < ideal_viewport_size.y {
396 physical_size
397 } else {
398 ideal_viewport_size
399 };
400
401 let border_size = physical_size - final_viewport_size;
402
403 let offset = border_size / 2;
404
405 URect::new(
406 offset.x,
407 offset.y,
408 final_viewport_size.x,
409 final_viewport_size.y,
410 )
411 }
412
413 #[must_use]
414 pub fn viewport_from_float_scale(physical_size: UVec2, virtual_size: UVec2) -> URect {
415 let window_aspect = physical_size.x as f32 / physical_size.y as f32;
416 let virtual_aspect = virtual_size.x as f32 / virtual_size.y as f32;
417
418 if physical_size.x < virtual_size.x || physical_size.y < virtual_size.y {
419 return URect::new(0, 0, physical_size.x, physical_size.y);
420 }
421
422 let mut float_scale = if window_aspect > virtual_aspect {
423 physical_size.y as f32 / virtual_size.y as f32
424 } else {
425 physical_size.x as f32 / virtual_size.x as f32
426 };
427
428 if float_scale < 0.01 {
429 float_scale = 0.01;
430 }
431
432 let viewport_actual_size = UVec2::new(
433 (virtual_size.x as f32 * float_scale) as u16,
434 (virtual_size.y as f32 * float_scale) as u16,
435 );
436
437 let border_size = physical_size - viewport_actual_size;
438
439 let offset = border_size / 2;
440
441 URect::new(
442 offset.x,
443 offset.y,
444 viewport_actual_size.x,
445 viewport_actual_size.y,
446 )
447 }
448
449 pub fn resize(&mut self, physical_size: UVec2) {
450 self.physical_surface_size = physical_size;
451 }
452
453 pub fn resize_virtual(&mut self, virtual_surface_size: UVec2) {
454 if virtual_surface_size == self.virtual_surface_size {
455 return;
456 }
457 self.virtual_surface_size = virtual_surface_size;
458 debug!(?virtual_surface_size, "virtual surface changed");
459 let (virtual_surface_texture, virtual_surface_texture_view, virtual_to_surface_bind_group) =
460 Self::create_virtual_texture(
461 &self.device,
462 self.surface_texture_format,
463 virtual_surface_size,
464 );
465 self.virtual_surface_texture = virtual_surface_texture;
466 self.virtual_surface_texture_view = virtual_surface_texture_view;
467 self.virtual_to_surface_bind_group = virtual_to_surface_bind_group;
468 }
469
470 pub fn sprite_atlas(&mut self, position: Vec3, atlas_rect: URect, material_ref: &MaterialRef) {
471 self.push_sprite(
472 position,
473 material_ref,
474 Sprite {
475 params: SpriteParams {
476 texture_pos: atlas_rect.position,
477 texture_size: atlas_rect.size,
478 ..Default::default()
479 },
480 },
481 );
482 }
483
484 pub fn sprite_atlas_frame(&mut self, position: Vec3, frame: u16, atlas: &impl FrameLookup) {
485 let (material_ref, atlas_rect) = atlas.lookup(frame);
486 self.push_sprite(
487 position,
488 material_ref,
489 Sprite {
490 params: SpriteParams {
491 texture_pos: atlas_rect.position,
492 texture_size: atlas_rect.size,
493 ..Default::default()
494 },
495 },
496 );
497 }
498
499 pub fn sprite_atlas_frame_ex(
500 &mut self,
501 position: Vec3,
502 frame: u16,
503 atlas: &impl FrameLookup,
504 mut params: SpriteParams,
505 ) {
506 let (material_ref, atlas_rect) = atlas.lookup(frame);
507 params.texture_pos = atlas_rect.position;
508 params.texture_size = atlas_rect.size;
509 self.push_sprite(position, material_ref, Sprite { params });
510 }
511
512 pub fn draw_sprite(&mut self, position: Vec3, material: &MaterialRef) {
513 self.push_sprite(
514 position,
515 material,
516 Sprite {
517 params: SpriteParams::default(),
518 },
519 );
520 }
521
522 pub fn draw_sprite_ex(&mut self, position: Vec3, material: &MaterialRef, params: SpriteParams) {
523 self.push_sprite(position, material, Sprite { params });
524 }
525
526 pub fn nine_slice(
527 &mut self,
528 position: Vec3,
529 size: UVec2,
530 color: Color,
531 nine_slice_and_material: &NineSliceAndMaterial,
532 ) {
533 self.push_nine_slice(position, size, color, nine_slice_and_material);
534 }
535
536 pub fn draw_quad(&mut self, position: Vec3, size: UVec2, color: Color) {
537 let material = Material {
538 base: MaterialBase {},
539 kind: MaterialKind::Quad,
540 };
541
542 self.items.push(RenderItem {
543 position,
544 material_ref: MaterialRef::from(material),
545 renderable: Renderable::QuadColor(QuadColor { size, color }),
546 });
547 }
548
549 #[allow(clippy::too_many_arguments)]
550 pub fn draw_nine_slice(
551 &mut self,
552 position: Vec3,
553 size: UVec2,
554 slices: Slices,
555 material_ref: &MaterialRef,
556 color: Color,
557 ) {
558 self.items.push(RenderItem {
559 position,
560 material_ref: material_ref.clone(),
561 renderable: Renderable::NineSlice(NineSlice {
562 size,
563 slices,
564 color,
565 origin_in_atlas: UVec2::new(0, 0),
566 size_inside_atlas: None,
567 }),
568 });
569 }
570
571 pub const fn clear_color(&self) -> wgpu::Color {
572 self.clear_color
573 }
574
575 fn calculate_texture_coords_mul_add(atlas_rect: URect, texture_size: UVec2) -> Vec4 {
577 let x = atlas_rect.position.x as f32 / texture_size.x as f32;
578 let y = atlas_rect.position.y as f32 / texture_size.y as f32;
579 let width = atlas_rect.size.x as f32 / texture_size.x as f32;
580 let height = atlas_rect.size.y as f32 / texture_size.y as f32;
581 Vec4([width, height, x, y])
582 }
583
584 fn order_render_items_in_batches(&mut self) -> Vec<Vec<&RenderItem>> {
585 let mut material_batches: Vec<Vec<&RenderItem>> = Vec::new();
586 let mut current_batch: Vec<&RenderItem> = Vec::new();
587 let mut current_material: Option<MaterialRef> = None;
588
589 for render_item in &self.items {
590 if Some(&render_item.material_ref) != current_material.as_ref() {
591 if !current_batch.is_empty() {
592 material_batches.push(current_batch.clone());
593 current_batch.clear();
594 }
595 current_material = Some(render_item.material_ref.clone());
596 }
597 current_batch.push(render_item);
598 }
599
600 if !current_batch.is_empty() {
601 material_batches.push(current_batch);
602 }
603
604 material_batches
605 }
606
607 #[must_use]
608 pub fn quad_helper_uniform(
609 position: Vec3,
610 quad_size: UVec2,
611 render_atlas: URect,
612 color: Color,
613
614 current_texture_size: UVec2,
615 ) -> SpriteInstanceUniform {
616 let model_matrix = Matrix4::from_translation(position.x as f32, position.y as f32, 0.0)
617 * Matrix4::from_scale(quad_size.x as f32, quad_size.y as f32, 1.0);
618
619 let tex_coords_mul_add =
620 Self::calculate_texture_coords_mul_add(render_atlas, current_texture_size);
621
622 let rotation_value = 0;
623
624 SpriteInstanceUniform::new(
625 model_matrix,
626 tex_coords_mul_add,
627 rotation_value,
628 Vec4(color.to_f32_slice()),
629 )
630 }
631
632 #[allow(clippy::too_many_lines)]
635 pub fn write_vertex_indices_and_uv_to_buffer(
636 &mut self,
637 textures: &Assets<Texture>,
638 fonts: &Assets<Font>,
639 ) {
640 const FLIP_X_MASK: u32 = 0b0000_0100;
641 const FLIP_Y_MASK: u32 = 0b0000_1000;
642
643 let batches = self.sort_and_put_in_batches();
644
645 let mut quad_matrix_and_uv: Vec<SpriteInstanceUniform> = Vec::new();
646 let mut batch_vertex_ranges: Vec<(MaterialRef, u32, u32)> = Vec::new();
647
648 for render_items in batches {
649 let quad_len_before = quad_matrix_and_uv.len();
650
651 let weak_material_ref = render_items
653 .first()
654 .map(|item| {
655 let material_ref: MaterialRef = item.material_ref.clone();
657 material_ref
658 })
659 .expect("Render items batch was empty");
660
661 if !weak_material_ref.is_complete(textures) {
662 trace!(?weak_material_ref, "material is not complete yet");
664 continue;
665 }
666 let material = weak_material_ref.clone();
667
668 let maybe_texture_ref = material.primary_texture();
669 let maybe_texture = maybe_texture_ref.and_then(|found_primary_texture_ref| {
670
671 textures.get(&found_primary_texture_ref)
672 });
673
674 for render_item in render_items {
675 let quad_len_before_inner = quad_matrix_and_uv.len();
676
677 match &render_item.renderable {
678 Renderable::Sprite(sprite) => {
679 let current_texture_size = maybe_texture.unwrap().texture_size;
680
681 let params = &sprite.params;
682 let mut size = params.texture_size;
683 if size.x == 0 && size.y == 0 {
684 size = current_texture_size;
685 }
686
687 let render_atlas = URect {
688 position: params.texture_pos,
689 size,
690 };
691
692 match params.rotation {
693 Rotation::Degrees90 | Rotation::Degrees270 => {
694 swap(&mut size.x, &mut size.y);
695 }
696 _ => {}
697 }
698
699 let model_matrix = Matrix4::from_translation(
700 render_item.position.x as f32,
701 render_item.position.y as f32,
702 0.0,
703 ) * Matrix4::from_scale(
704 (size.x * params.scale as u16) as f32,
705 (size.y * params.scale as u16) as f32,
706 1.0,
707 );
708
709 let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
710 render_atlas,
711 current_texture_size,
712 );
713
714 let mut rotation_value = match params.rotation {
715 Rotation::Degrees0 => 0,
716 Rotation::Degrees90 => 1,
717 Rotation::Degrees180 => 2,
718 Rotation::Degrees270 => 3,
719 };
720
721 if params.flip_x {
722 rotation_value |= FLIP_X_MASK;
723 }
724 if params.flip_y {
725 rotation_value |= FLIP_Y_MASK;
726 }
727
728 let quad_instance = SpriteInstanceUniform::new(
729 model_matrix,
730 tex_coords_mul_add,
731 rotation_value,
732 Vec4(params.color.to_f32_slice()),
733 );
734 quad_matrix_and_uv.push(quad_instance);
735 }
736
737 Renderable::Mask(texture_offset, color) => {
738 let current_texture_size = maybe_texture.unwrap().texture_size;
739 let params = SpriteParams {
740 texture_size: current_texture_size,
741 texture_pos: *texture_offset,
742 scale: 1,
743 rotation: Rotation::default(),
744 flip_x: false,
745 flip_y: false,
746 pivot: Vec2 { x: 0, y: 0 },
747 color: *color,
748 };
749
750 let mut size = params.texture_size;
751 if size.x == 0 && size.y == 0 {
752 size = current_texture_size;
753 }
754
755 let render_atlas = URect {
756 position: params.texture_pos,
757 size,
758 };
759
760 let model_matrix = Matrix4::from_translation(
761 render_item.position.x as f32,
762 render_item.position.y as f32,
763 0.0,
764 ) * Matrix4::from_scale(
765 (size.x * params.scale as u16) as f32,
766 (size.y * params.scale as u16) as f32,
767 1.0,
768 );
769
770 let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
771 render_atlas,
772 current_texture_size,
773 );
774
775 let mut rotation_value = match params.rotation {
776 Rotation::Degrees0 => 0,
777 Rotation::Degrees90 => 1,
778 Rotation::Degrees180 => 2,
779 Rotation::Degrees270 => 3,
780 };
781
782 if params.flip_x {
783 rotation_value |= FLIP_X_MASK;
784 }
785 if params.flip_y {
786 rotation_value |= FLIP_Y_MASK;
787 }
788
789 let quad_instance = SpriteInstanceUniform::new(
790 model_matrix,
791 tex_coords_mul_add,
792 rotation_value,
793 Vec4(params.color.to_f32_slice()),
794 );
795 quad_matrix_and_uv.push(quad_instance);
796 }
797
798 Renderable::NineSlice(nine_slice) => {
799 let current_texture_size = maybe_texture.unwrap().texture_size;
800 Self::prepare_nine_slice(
801 nine_slice,
802 render_item.position,
803 &mut quad_matrix_and_uv,
804 current_texture_size,
805 );
806 }
807
808 Renderable::QuadColor(quad) => {
809 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([
817 0.0, 0.0, 0.0, 0.0,
820 ]);
821 let rotation_value = 0;
822
823 let quad_instance = SpriteInstanceUniform::new(
824 model_matrix,
825 tex_coords_mul_add,
826 rotation_value,
827 Vec4(quad.color.to_f32_slice()),
828 );
829 quad_matrix_and_uv.push(quad_instance);
830 }
831
832 Renderable::Text(text) => {
833 let current_texture_size = maybe_texture.unwrap().texture_size;
834 let result = fonts.get_weak(text.font_ref);
835 if result.is_none() {
836 continue;
837 }
838 let font = result.unwrap();
839
840 let glyphs = font.draw(&text.text);
841 for glyph in glyphs {
842 let pos = render_item.position + Vec3::from(glyph.relative_position);
843 let texture_size = glyph.texture_rectangle.size;
844 let model_matrix =
845 Matrix4::from_translation(pos.x as f32, pos.y as f32, 0.0)
846 * Matrix4::from_scale(
847 texture_size.x as f32,
848 texture_size.y as f32,
849 1.0,
850 );
851 let tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
852 glyph.texture_rectangle,
853 current_texture_size,
854 );
855
856 let quad_instance = SpriteInstanceUniform::new(
857 model_matrix,
858 tex_coords_mul_add,
859 0,
860 Vec4(text.color.to_f32_slice()),
861 );
862 quad_matrix_and_uv.push(quad_instance);
863 }
864 }
865
866 Renderable::TileMap(tile_map) => {
867 for (index, tile) in tile_map.tiles.iter().enumerate() {
868 let cell_pos_x = (index as u16 % tile_map.tiles_data_grid_size.x)
869 * tile_map.one_cell_size.x
870 * tile_map.scale as u16;
871 let cell_pos_y = (index as u16 / tile_map.tiles_data_grid_size.x)
872 * tile_map.one_cell_size.y
873 * tile_map.scale as u16;
874 let cell_x = *tile % tile_map.cell_count_size.x;
875 let cell_y = *tile / tile_map.cell_count_size.x;
876
877 let tex_x = cell_x * tile_map.one_cell_size.x;
878 let tex_y = cell_y * tile_map.one_cell_size.x;
879
880 let cell_texture_area = URect::new(
881 tex_x,
882 tex_y,
883 tile_map.one_cell_size.x,
884 tile_map.one_cell_size.y,
885 );
886
887 let cell_model_matrix = Matrix4::from_translation(
888 (render_item.position.x + cell_pos_x as i16) as f32,
889 (render_item.position.y + cell_pos_y as i16) as f32,
890 0.0,
891 ) * Matrix4::from_scale(
892 (tile_map.one_cell_size.x * tile_map.scale as u16) as f32,
893 (tile_map.one_cell_size.y * tile_map.scale as u16) as f32,
894 1.0,
895 );
896
897 let current_texture_size = maybe_texture.unwrap().texture_size;
898 let cell_tex_coords_mul_add = Self::calculate_texture_coords_mul_add(
899 cell_texture_area,
900 current_texture_size,
901 );
902
903 let quad_instance = SpriteInstanceUniform::new(
904 cell_model_matrix,
905 cell_tex_coords_mul_add,
906 0,
907 Vec4([1.0, 1.0, 1.0, 1.0]),
908 );
909 quad_matrix_and_uv.push(quad_instance);
910 }
911 }
912 }
913
914 let quad_count_for_this_render_item =
915 quad_matrix_and_uv.len() - quad_len_before_inner;
916 assert!(
917 quad_count_for_this_render_item <= MAXIMUM_QUADS_FOR_RENDER_ITEM,
918 "too many quads {quad_count_for_this_render_item} for render item {render_item:?}"
919 );
920 }
921
922 let quad_count_for_this_batch = quad_matrix_and_uv.len() - quad_len_before;
923 assert!(
924 quad_count_for_this_batch <= MAXIMUM_QUADS_IN_A_BATCH,
925 "too many quads {quad_count_for_this_batch} total to render in this batch"
926 );
927
928 assert!(
929 quad_matrix_and_uv.len() <= MAXIMUM_QUADS_IN_ONE_RENDER,
930 "too many quads for whole render {}",
931 quad_matrix_and_uv.len()
932 );
933
934 batch_vertex_ranges.push((
935 weak_material_ref,
936 quad_len_before as u32,
937 quad_count_for_this_batch as u32,
938 ));
939 }
940
941 self.queue.write_buffer(
943 &self.quad_matrix_and_uv_instance_buffer,
944 0,
945 bytemuck::cast_slice(&quad_matrix_and_uv),
946 );
947
948 self.batch_offsets = batch_vertex_ranges;
949 }
950
951 #[allow(clippy::too_many_lines)]
952 #[inline]
953 pub fn prepare_nine_slice(
954 nine_slice: &NineSlice,
955 position_offset: Vec3,
956 quad_matrix_and_uv: &mut Vec<SpriteInstanceUniform>,
957 current_texture_size: UVec2,
958 ) {
959 let world_window_size = nine_slice.size;
960 let slices = &nine_slice.slices;
961
962 assert!(
967 world_window_size.x >= slices.left + slices.right,
968 "NineSlice.width ({}) < slices.left + slices.right ({})",
969 world_window_size.x,
970 slices.left + slices.right
971 );
972 assert!(
973 world_window_size.y >= slices.top + slices.bottom,
974 "NineSlice.height ({}) < slices.top + slices.bottom ({})",
975 world_window_size.y,
976 slices.top + slices.bottom
977 );
978
979 let texture_window_size = nine_slice.size_inside_atlas.unwrap_or(current_texture_size);
980
981 assert!(
983 texture_window_size.x >= slices.left + slices.right,
984 "texture_window_size.width ({}) < slices.left + slices.right ({})",
985 texture_window_size.x,
986 slices.left + slices.right
987 );
988 assert!(
989 texture_window_size.y >= slices.top + slices.bottom,
990 "texture_window_size.height ({}) < slices.top + slices.bottom ({})",
991 texture_window_size.y,
992 slices.top + slices.bottom
993 );
994 let color = nine_slice.color;
997
998 let atlas_origin = nine_slice.origin_in_atlas;
999 let texture_window_size = nine_slice.size_inside_atlas.unwrap_or(current_texture_size);
1000
1001 let world_edge_width = nine_slice.size.x - slices.left - slices.right;
1002 let world_edge_height = nine_slice.size.y - slices.top - slices.bottom;
1003 let texture_edge_width = texture_window_size.x - slices.left - slices.right;
1004 let texture_edge_height = texture_window_size.y - slices.top - slices.bottom;
1005
1006 let lower_left_pos = Vec3::new(position_offset.x, position_offset.y, 0);
1009 let corner_size = UVec2::new(slices.left, slices.bottom);
1010 let lower_left_quad_size = UVec2::new(corner_size.x, corner_size.y);
1012 let lower_left_atlas = URect::new(
1013 atlas_origin.x,
1014 atlas_origin.y + texture_window_size.y - slices.bottom, corner_size.x,
1016 corner_size.y,
1017 );
1018 let lower_left_quad = Self::quad_helper_uniform(
1019 lower_left_pos,
1020 lower_left_quad_size,
1021 lower_left_atlas,
1022 color,
1023 current_texture_size,
1024 );
1025 quad_matrix_and_uv.push(lower_left_quad);
1026
1027 let lower_side_position =
1029 Vec3::new(position_offset.x + slices.left as i16, position_offset.y, 0);
1030 let lower_side_world_quad_size = UVec2::new(world_edge_width, slices.bottom);
1033 let lower_side_texture_size = UVec2::new(texture_edge_width, slices.bottom);
1034 let lower_side_atlas = URect::new(
1036 atlas_origin.x + slices.left,
1037 atlas_origin.y + texture_window_size.y - slices.bottom, lower_side_texture_size.x,
1039 lower_side_texture_size.y,
1040 );
1041 let lower_side_quad = Self::quad_helper_uniform(
1042 lower_side_position,
1043 lower_side_world_quad_size,
1044 lower_side_atlas,
1045 color,
1046 current_texture_size,
1047 );
1048 quad_matrix_and_uv.push(lower_side_quad);
1049
1050 let lower_right_pos = Vec3::new(
1052 position_offset.x + (world_window_size.x - slices.right) as i16,
1053 position_offset.y,
1054 0,
1055 );
1056 let lower_right_corner_size = UVec2::new(slices.right, slices.bottom);
1057 let lower_right_atlas = URect::new(
1058 atlas_origin.x + texture_window_size.x - slices.right,
1059 atlas_origin.y + texture_window_size.y - slices.bottom, lower_right_corner_size.x,
1061 lower_right_corner_size.y,
1062 );
1063 let lower_right_quad = Self::quad_helper_uniform(
1064 lower_right_pos,
1065 lower_right_corner_size,
1066 lower_right_atlas,
1067 color,
1068 current_texture_size,
1069 );
1070 quad_matrix_and_uv.push(lower_right_quad);
1071
1072 let left_edge_pos = Vec3::new(
1074 position_offset.x,
1075 position_offset.y + slices.bottom as i16,
1076 0,
1077 );
1078 let left_edge_world_quad_size = UVec2::new(slices.left, world_edge_height);
1079 let left_edge_texture_size = UVec2::new(slices.left, texture_edge_height);
1080 let left_edge_atlas = URect::new(
1081 atlas_origin.x,
1082 atlas_origin.y + slices.top, left_edge_texture_size.x,
1084 left_edge_texture_size.y,
1085 );
1086 let left_edge_quad = Self::quad_helper_uniform(
1087 left_edge_pos,
1088 left_edge_world_quad_size,
1089 left_edge_atlas,
1090 color,
1091 current_texture_size,
1092 );
1093 quad_matrix_and_uv.push(left_edge_quad);
1094
1095 let base_center_x = atlas_origin.x + slices.left;
1101 let base_center_y = atlas_origin.y + slices.top;
1102
1103 let repeat_x_count = (world_edge_width as f32 / texture_edge_width as f32).ceil() as usize;
1105 let repeat_y_count =
1106 (world_edge_height as f32 / texture_edge_height as f32).ceil() as usize;
1107
1108 for y in 0..repeat_y_count {
1109 for x in 0..repeat_x_count {
1110 let this_quad_width =
1111 if x == repeat_x_count - 1 && world_edge_width % texture_edge_width != 0 {
1112 world_edge_width % texture_edge_width
1113 } else {
1114 texture_edge_width
1115 };
1116
1117 let this_quad_height =
1118 if y == repeat_y_count - 1 && world_edge_height % texture_edge_height != 0 {
1119 world_edge_height % texture_edge_height
1120 } else {
1121 texture_edge_height
1122 };
1123
1124 let quad_pos = Vec3::new(
1125 position_offset.x + slices.left as i16 + (x as u16 * texture_edge_width) as i16,
1126 position_offset.y
1127 + slices.bottom as i16
1128 + (y as u16 * texture_edge_height) as i16,
1129 0,
1130 );
1131
1132 let texture_x = base_center_x;
1133
1134 let texture_y = if y == repeat_y_count - 1 && this_quad_height < texture_edge_height
1135 {
1136 base_center_y + (texture_edge_height - this_quad_height)
1137 } else {
1138 base_center_y
1139 };
1140
1141 let this_texture_region =
1142 URect::new(texture_x, texture_y, this_quad_width, this_quad_height);
1143
1144 let center_quad = Self::quad_helper_uniform(
1145 quad_pos,
1146 UVec2::new(this_quad_width, this_quad_height),
1147 this_texture_region,
1148 color,
1149 current_texture_size,
1150 );
1151
1152 quad_matrix_and_uv.push(center_quad);
1153 }
1154 }
1155 let right_edge_pos = Vec3::new(
1159 position_offset.x + (world_window_size.x - slices.right) as i16,
1160 position_offset.y + slices.bottom as i16,
1161 0,
1162 );
1163 let right_edge_world_quad_size = UVec2::new(slices.right, world_edge_height);
1164 let right_edge_texture_size = UVec2::new(slices.right, texture_edge_height);
1165 let right_edge_atlas = URect::new(
1166 atlas_origin.x + texture_window_size.x - slices.right,
1167 atlas_origin.y + slices.top, right_edge_texture_size.x,
1169 right_edge_texture_size.y,
1170 );
1171
1172 let right_edge_quad = Self::quad_helper_uniform(
1173 right_edge_pos,
1174 right_edge_world_quad_size,
1175 right_edge_atlas,
1176 color,
1177 current_texture_size,
1178 );
1179 quad_matrix_and_uv.push(right_edge_quad);
1180
1181 let top_left_pos = Vec3::new(
1183 position_offset.x,
1184 position_offset.y + (world_window_size.y - slices.top) as i16,
1185 0,
1186 );
1187 let top_left_corner_size = UVec2::new(slices.left, slices.top);
1188 let top_left_atlas = URect::new(
1189 atlas_origin.x,
1190 atlas_origin.y, top_left_corner_size.x,
1192 top_left_corner_size.y,
1193 );
1194 let top_left_quad = Self::quad_helper_uniform(
1195 top_left_pos,
1196 top_left_corner_size,
1197 top_left_atlas,
1198 color,
1199 current_texture_size,
1200 );
1201 quad_matrix_and_uv.push(top_left_quad);
1202
1203 let top_edge_pos = Vec3::new(
1205 position_offset.x + slices.left as i16,
1206 position_offset.y + (world_window_size.y - slices.top) as i16,
1207 0,
1208 );
1209 let top_edge_world_quad_size = UVec2::new(world_edge_width, slices.top);
1210 let top_edge_texture_size = UVec2::new(texture_edge_width, slices.top);
1211 let top_edge_atlas = URect::new(
1212 atlas_origin.x + slices.left,
1213 atlas_origin.y, top_edge_texture_size.x,
1215 top_edge_texture_size.y,
1216 );
1217 let top_edge_quad = Self::quad_helper_uniform(
1218 top_edge_pos,
1219 top_edge_world_quad_size,
1220 top_edge_atlas,
1221 color,
1222 current_texture_size,
1223 );
1224 quad_matrix_and_uv.push(top_edge_quad);
1225
1226 let top_right_pos = Vec3::new(
1228 position_offset.x + (world_window_size.x - slices.right) as i16,
1229 position_offset.y + (world_window_size.y - slices.top) as i16,
1230 0,
1231 );
1232 let top_right_corner_size = UVec2::new(slices.right, slices.top);
1233 let top_right_atlas = URect::new(
1234 atlas_origin.x + texture_window_size.x - slices.right,
1235 atlas_origin.y, top_right_corner_size.x,
1237 top_right_corner_size.y,
1238 );
1239 let top_right_quad = Self::quad_helper_uniform(
1240 top_right_pos,
1241 top_right_corner_size,
1242 top_right_atlas,
1243 color,
1244 current_texture_size,
1245 );
1246 quad_matrix_and_uv.push(top_right_quad);
1247 }
1248
1249 fn sort_and_put_in_batches(&mut self) -> Vec<Vec<&RenderItem>> {
1250 sort_render_items_by_z_and_material(&mut self.items);
1251
1252 self.order_render_items_in_batches()
1253 }
1254
1255 #[allow(clippy::too_many_lines)]
1258 pub fn render(
1259 &mut self,
1260 command_encoder: &mut CommandEncoder,
1261 display_surface_texture_view: &TextureView,
1262 textures: &Assets<Texture>,
1264 fonts: &Assets<Font>,
1265 now: Millis,
1266 ) {
1267 self.debug_tick += 1;
1268 trace!("start render()");
1269 self.last_render_at = now;
1270
1271 self.set_viewport_and_view_projection_matrix();
1272
1273 self.write_vertex_indices_and_uv_to_buffer(textures, fonts);
1274
1275 self.render_batches_to_virtual_texture(command_encoder, textures);
1276
1277 self.render_virtual_texture_to_display(command_encoder, display_surface_texture_view);
1278 }
1279
1280 pub fn set_viewport_and_view_projection_matrix(&mut self) {
1281 let view_proj_matrix = create_view_projection_matrix_from_virtual(
1282 self.virtual_surface_size.x,
1283 self.virtual_surface_size.y,
1284 );
1285
1286 let scale_matrix = Matrix4::from_scale(self.scale, self.scale, 0.0);
1287 let origin_translation_matrix =
1288 Matrix4::from_translation(-self.origin.x as f32, -self.origin.y as f32, 0.0);
1289
1290 let total_matrix = scale_matrix * view_proj_matrix * origin_translation_matrix;
1291
1292 self.queue.write_buffer(
1294 &self.camera_buffer,
1295 0,
1296 bytemuck::cast_slice(&[total_matrix]),
1297 );
1298 }
1299
1300 pub fn render_batches_to_virtual_texture(
1301 &mut self,
1302 command_encoder: &mut CommandEncoder,
1303 textures: &Assets<Texture>,
1304 ) {
1305 let mut render_pass = command_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1306 label: Some("Game Render Pass"),
1307 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1308 view: &self.virtual_surface_texture_view,
1309 resolve_target: None,
1310 ops: wgpu::Operations {
1311 load: wgpu::LoadOp::Clear(self.clear_color),
1312 store: wgpu::StoreOp::Store,
1313 },
1314 })],
1315 depth_stencil_attachment: None,
1316 timestamp_writes: None,
1317 occlusion_query_set: None,
1318 });
1319
1320 render_pass.set_viewport(
1321 0.0,
1322 0.0,
1323 self.virtual_surface_size.x as f32,
1324 self.virtual_surface_size.y as f32,
1325 0.0,
1326 1.0,
1327 );
1328
1329 render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
1331 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1332
1333 render_pass.set_vertex_buffer(1, self.quad_matrix_and_uv_instance_buffer.slice(..));
1335
1336 let num_indices = mireforge_wgpu_sprites::INDICES.len() as u32;
1337
1338 let mut current_pipeline: Option<&MaterialKind> = None;
1339
1340 for &(ref weak_material_ref, start, count) in &self.batch_offsets {
1341 let wgpu_material = weak_material_ref;
1342
1343 let pipeline_kind = &wgpu_material.kind;
1344
1345 if current_pipeline != Some(pipeline_kind) {
1346 let pipeline = match pipeline_kind {
1347 MaterialKind::NormalSprite { .. } => &self.normal_sprite_pipeline.pipeline,
1348 MaterialKind::Quad => &self.quad_shader_info.pipeline,
1349 MaterialKind::AlphaMasker { .. } => &self.mask_shader_info.pipeline,
1350 MaterialKind::LightAdd { .. } => &self.light_shader_info.pipeline,
1351 };
1352 render_pass.set_pipeline(pipeline);
1354 current_pipeline = Some(pipeline_kind);
1357 render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
1358 }
1359
1360 match &wgpu_material.kind {
1361 MaterialKind::NormalSprite { primary_texture }
1362 | MaterialKind::LightAdd { primary_texture } => {
1363 let texture = textures.get(primary_texture).unwrap();
1364 render_pass.set_bind_group(1, &texture.texture_and_sampler_bind_group, &[]);
1366 }
1367 MaterialKind::AlphaMasker {
1368 primary_texture,
1369 alpha_texture,
1370 } => {
1371 let real_diffuse_texture = textures.get(primary_texture).unwrap();
1372 let alpha_texture = textures.get(alpha_texture).unwrap();
1373 render_pass.set_bind_group(
1374 1,
1375 &real_diffuse_texture.texture_and_sampler_bind_group,
1376 &[],
1377 );
1378 render_pass.set_bind_group(
1379 2,
1380 &alpha_texture.texture_and_sampler_bind_group,
1381 &[],
1382 );
1383 }
1384 MaterialKind::Quad => {
1385 }
1387 }
1388 assert!(
1389 count <= MAXIMUM_QUADS_IN_A_BATCH as u32,
1390 "too many instanced draw in a batch {count}"
1391 );
1392
1393 trace!(material=%weak_material_ref, start=%start, count=%count, %num_indices, "draw instanced");
1395 render_pass.draw_indexed(0..num_indices, 0, start..(start + count));
1396 }
1397 self.items.clear();
1398 }
1399
1400 pub fn render_virtual_texture_to_display(
1401 &mut self,
1402 command_encoder: &mut CommandEncoder,
1403 display_surface_texture_view: &TextureView,
1404 ) {
1405 let mut render_pass = command_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1406 label: Some("Screen Render Pass"),
1407 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1408 view: display_surface_texture_view,
1409 resolve_target: None,
1410 ops: wgpu::Operations {
1411 load: wgpu::LoadOp::Clear(self.screen_clear_color),
1412 store: wgpu::StoreOp::Store,
1413 },
1414 })],
1415 depth_stencil_attachment: None,
1416 timestamp_writes: None,
1417 occlusion_query_set: None,
1418 });
1419
1420 self.viewport = match self.viewport_strategy {
1432 ViewportStrategy::FitIntegerScaling => Self::viewport_from_integer_scale(
1433 self.physical_surface_size,
1434 self.virtual_surface_size,
1435 ),
1436 ViewportStrategy::FitFloatScaling => Self::viewport_from_float_scale(
1437 self.physical_surface_size,
1438 self.virtual_surface_size,
1439 ),
1440 ViewportStrategy::MatchPhysicalSize => URect::new(
1441 0,
1442 0,
1443 self.physical_surface_size.x,
1444 self.physical_surface_size.y,
1445 ),
1446 };
1447
1448 render_pass.set_viewport(
1449 self.viewport.position.x as f32,
1450 self.viewport.position.y as f32,
1451 self.viewport.size.x as f32,
1452 self.viewport.size.y as f32,
1453 0.0,
1454 1.0,
1455 );
1456
1457 render_pass.set_pipeline(&self.virtual_to_screen_shader_info.pipeline);
1459 render_pass.set_bind_group(0, &self.virtual_to_surface_bind_group, &[]);
1460 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1461
1462 render_pass.draw(0..6, 0..1);
1463 }
1464
1465 pub fn texture_resource_from_texture(&self, texture: &wgpu::Texture, label: &str) -> Texture {
1466 trace!("load texture from memory with name: '{label}'");
1467 let size = &texture.size();
1468 let texture_and_sampler_bind_group =
1469 mireforge_wgpu_sprites::create_sprite_texture_and_sampler_bind_group(
1470 &self.device,
1471 &self.texture_sampler_bind_group_layout,
1472 texture,
1473 &self.sampler,
1474 label,
1475 );
1476
1477 let texture_size = UVec2::new(size.width as u16, size.height as u16);
1478
1479 Texture {
1480 texture_and_sampler_bind_group,
1481 texture_size,
1482 }
1483 }
1484}
1485
1486fn create_view_projection_matrix_from_virtual(virtual_width: u16, virtual_height: u16) -> Matrix4 {
1487 OrthoInfo {
1488 left: 0.0,
1489 right: virtual_width as f32,
1490 bottom: 0.0,
1491 top: virtual_height as f32,
1492 near: 1.0, far: -1.0, }
1495 .into()
1496}
1497
1498fn create_view_uniform_view_projection_matrix(viewport_size: UVec2) -> Matrix4 {
1499 let viewport_width = viewport_size.x as f32;
1500 let viewport_height = viewport_size.y as f32;
1501
1502 let viewport_aspect_ratio = viewport_width / viewport_height;
1503
1504 let scale_x = 1.0;
1505 let scale_y = viewport_aspect_ratio; let view_projection_matrix = [
1508 [scale_x, 0.0, 0.0, 0.0],
1509 [0.0, scale_y, 0.0, 0.0],
1510 [0.0, 0.0, -1.0, 0.0],
1511 [0.0, 0.0, 0.0, 1.0],
1512 ];
1513
1514 view_projection_matrix.into()
1515}
1516
1517fn sort_render_items_by_z_and_material(items: &mut [RenderItem]) {
1518 items.sort_by_key(|item| (item.position.z, item.material_ref.clone()));
1519}
1520
1521#[derive(Debug, Clone, Copy, Default)]
1522pub enum Rotation {
1523 #[default]
1524 Degrees0,
1525 Degrees90,
1526 Degrees180,
1527 Degrees270,
1528}
1529
1530#[derive(Debug, Copy, Clone)]
1531pub struct SpriteParams {
1532 pub texture_size: UVec2,
1533 pub texture_pos: UVec2,
1534 pub scale: u8,
1535 pub rotation: Rotation,
1536 pub flip_x: bool,
1537 pub flip_y: bool,
1538 pub pivot: Vec2,
1539 pub color: Color,
1540}
1541
1542impl Default for SpriteParams {
1543 fn default() -> Self {
1544 Self {
1545 texture_size: UVec2::new(0, 0),
1546 texture_pos: UVec2::new(0, 0),
1547 pivot: Vec2::new(0, 0),
1548 flip_x: false,
1549 flip_y: false,
1550 color: Color::from_octet(255, 255, 255, 255),
1551 scale: 1,
1552 rotation: Rotation::Degrees0,
1553 }
1554 }
1555}
1556
1557pub type BindGroupRef = Arc<BindGroup>;
1558
1559#[derive(Debug, PartialEq, Eq, Asset)]
1560pub struct Texture {
1561 pub texture_and_sampler_bind_group: BindGroup,
1562 pub texture_size: UVec2,
1564}
1565
1566impl Display for Texture {
1567 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
1568 write!(f, "{:?}", self.texture_size)
1569 }
1570}
1571
1572impl PartialOrd<Self> for Texture {
1573 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1574 Some(
1575 self.texture_and_sampler_bind_group
1576 .cmp(&other.texture_and_sampler_bind_group),
1577 )
1578 }
1579}
1580
1581impl Ord for Texture {
1582 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1583 self.texture_and_sampler_bind_group
1584 .cmp(&other.texture_and_sampler_bind_group)
1585 }
1586}
1587
1588#[derive(Debug, Ord, PartialOrd, PartialEq, Eq)]
1589pub struct MaterialBase {
1590 }
1592
1593#[derive(Debug, Ord, PartialOrd, PartialEq, Eq)]
1594pub struct Material {
1595 pub base: MaterialBase,
1596 pub kind: MaterialKind,
1597}
1598
1599impl Material {
1600 #[inline]
1601 #[must_use]
1602 pub fn primary_texture(&self) -> Option<TextureRef> {
1603 self.kind.primary_texture()
1604 }
1605
1606 #[inline]
1607 #[must_use]
1608 pub fn is_complete(&self, textures: &Assets<Texture>) -> bool {
1609 self.kind.is_complete(textures)
1610 }
1611}
1612
1613impl Display for Material {
1614 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1615 write!(f, "{}", self.kind)
1616 }
1617}
1618
1619#[derive(Debug, Ord, PartialOrd, PartialEq, Eq)]
1620pub enum MaterialKind {
1621 NormalSprite {
1622 primary_texture: Id<Texture>,
1623 },
1624 AlphaMasker {
1625 primary_texture: Id<Texture>,
1626 alpha_texture: Id<Texture>,
1627 },
1628 Quad,
1629 LightAdd {
1630 primary_texture: Id<Texture>,
1631 },
1632}
1633
1634impl MaterialKind {}
1635
1636impl MaterialKind {
1637 pub fn primary_texture(&self) -> Option<Id<Texture>> {
1638 match &self {
1639 Self::NormalSprite {
1640 primary_texture, ..
1641 }
1642 | Self::LightAdd { primary_texture }
1643 | Self::AlphaMasker {
1644 primary_texture, ..
1645 } => Some(primary_texture.clone()),
1646 Self::Quad => None,
1647 }
1648 }
1649
1650 pub(crate) fn is_complete(&self, textures: &Assets<Texture>) -> bool {
1651 match &self {
1652 Self::NormalSprite { primary_texture } | Self::LightAdd { primary_texture } => {
1653 textures.contains(primary_texture)
1654 }
1655 Self::AlphaMasker {
1656 primary_texture,
1657 alpha_texture,
1658 } => textures.contains(primary_texture) && textures.contains(alpha_texture),
1659 Self::Quad => true,
1660 }
1661 }
1662}
1663
1664impl Display for MaterialKind {
1665 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1666 let texture_name = self
1667 .primary_texture()
1668 .map_or_else(String::new, |x| x.to_string());
1669
1670 let kind_name = match self {
1671 Self::NormalSprite { .. } => "NormalSprite",
1672 Self::LightAdd { .. } => "Light (Add)",
1673 Self::Quad => "Quad",
1674 Self::AlphaMasker { .. } => "AlphaMasker",
1675 };
1676
1677 write!(f, "{kind_name} texture {texture_name}")
1678 }
1679}
1680
1681#[derive(Debug)]
1682pub struct Sprite {
1683 pub params: SpriteParams,
1684}
1685
1686#[derive(Debug)]
1687pub struct QuadColor {
1688 pub size: UVec2,
1689 pub color: Color,
1690}
1691
1692#[derive(Debug, Copy, Clone)]
1693pub struct Slices {
1694 pub left: u16,
1695 pub top: u16,
1696 pub right: u16, pub bottom: u16, }
1699
1700#[derive(Debug)]
1701pub struct NineSlice {
1702 pub size: UVec2, pub slices: Slices,
1704 pub color: Color, pub origin_in_atlas: UVec2,
1706 pub size_inside_atlas: Option<UVec2>,
1707}
1708
1709#[derive(Debug)]
1710pub struct TileMap {
1711 pub tiles_data_grid_size: UVec2,
1712 pub cell_count_size: UVec2,
1713 pub one_cell_size: UVec2,
1714 pub tiles: Vec<u16>,
1715 pub scale: u8,
1716}
1717
1718#[derive(PartialEq, Debug, Eq, Ord, PartialOrd)]
1719pub struct Pipeline {
1720 name: String,
1721 render_pipeline: RenderPipeline,
1722}
1723
1724impl Display for Pipeline {
1725 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1726 write!(f, "pipeline: {}", self.name)
1727 }
1728}
1729
1730pub type PipelineRef = Arc<Pipeline>;