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