1use core::ops::Deref;
2use glam::UVec2;
3use image::RgbaImage;
4use snafu::prelude::*;
5use std::sync::{Arc, RwLock};
6
7use crate::{
8 slab::{Hybrid, IsBuffer, SlabAllocator, UpdatesSlab},
9 texture::Texture,
10};
11
12use super::{
13 atlas_image::{convert_to_rgba8_bytes, AtlasImage},
14 AtlasTexture,
15};
16
17pub(crate) const ATLAS_SUGGESTED_SIZE: u32 = 2048;
18pub(crate) const ATLAS_SUGGESTED_LAYERS: u32 = 8;
19
20pub const ENTRY_STRONG_COUNT_LOWER_BOUND: usize = 2;
27
28#[derive(Debug, Snafu)]
29pub enum AtlasError {
30 #[snafu(display(
31 "Cannot pack textures.\natlas_size: {size:#?}\nimage_sizes: {image_sizes:#?}"
32 ))]
33 CannotPackTextures {
34 size: wgpu::Extent3d,
35 image_sizes: Vec<UVec2>,
36 },
37
38 #[snafu(display("Missing layer {index}"))]
39 MissingLayer { index: u32, images: Vec<AtlasImage> },
40
41 #[snafu(display("Atlas size is invalid: {size:?}"))]
42 Size { size: wgpu::Extent3d },
43}
44
45pub(crate) fn check_size(size: wgpu::Extent3d) -> Result<(), AtlasError> {
46 let conditions = size.depth_or_array_layers >= 2
47 && size.width == size.height
48 && (size.width & (size.width - 1)) == 0;
49 if !conditions {
50 return SizeSnafu { size }.fail();
51 }
52
53 Ok(())
54}
55
56fn fan_split_n<T>(n: usize, input: impl IntoIterator<Item = T>) -> Vec<Vec<T>> {
57 if n == 0 {
58 return vec![];
59 }
60 let mut output = vec![];
61 for _ in 0..n {
62 output.push(vec![]);
63 }
64 let mut i = 0;
65 for item in input.into_iter() {
66 output
68 .get_mut(i)
69 .unwrap_or_else(|| panic!("could not unwrap i:{i} n:{n}"))
70 .push(item);
71 i = (i + 1) % n;
72 }
73 output
74}
75
76#[derive(Clone)]
78enum Packing<'a> {
79 Img {
83 layer_index: u32,
85 frame_index: u32,
87 image: &'a AtlasImage,
89 },
90 GpuImg(Hybrid<AtlasTexture>),
94}
95
96impl<'a> Packing<'a> {
97 pub fn size(&self) -> UVec2 {
98 match self {
99 Packing::Img { image, .. } => image.size,
100 Packing::GpuImg(entry) => entry.get().size_px,
101 }
102 }
103}
104
105#[derive(Clone, Default, Debug)]
106pub struct Layer {
107 pub frames: Vec<Hybrid<AtlasTexture>>,
108}
109
110#[derive(Clone)]
114pub struct Atlas {
115 texture_array: Arc<RwLock<Texture>>,
116 layers: Arc<RwLock<Vec<Layer>>>,
117}
118
119impl Atlas {
120 fn new_with_texture(texture: Texture) -> Self {
127 let num_layers = texture.texture.size().depth_or_array_layers as usize;
128 let layers = vec![Layer::default(); num_layers];
129 log::trace!("created atlas with {num_layers} layers");
130 Atlas {
131 layers: Arc::new(RwLock::new(layers)),
132 texture_array: Arc::new(RwLock::new(texture)),
133 }
134 }
135
136 fn create_texture(
138 device: &wgpu::Device,
139 queue: &wgpu::Queue,
140 size: wgpu::Extent3d,
141 ) -> Result<Texture, AtlasError> {
142 check_size(size)?;
143 let texture = device.create_texture(&wgpu::TextureDescriptor {
144 label: Some("atlas texture"),
145 size,
146 mip_level_count: 1,
147 sample_count: 1,
148 dimension: wgpu::TextureDimension::D2,
149 format: wgpu::TextureFormat::Rgba8Unorm,
150 usage: wgpu::TextureUsages::TEXTURE_BINDING
151 | wgpu::TextureUsages::COPY_DST
152 | wgpu::TextureUsages::COPY_SRC,
153 view_formats: &[],
154 });
155
156 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
157 label: Some("create atlas texture array"),
158 });
159 if device.features().contains(wgpu::Features::CLEAR_TEXTURE) {
160 encoder.clear_texture(
161 &texture,
162 &wgpu::ImageSubresourceRange {
163 aspect: wgpu::TextureAspect::All,
164 base_mip_level: 0,
165 mip_level_count: None,
166 base_array_layer: 0,
167 array_layer_count: None,
168 },
169 );
170 }
171 queue.submit(Some(encoder.finish()));
172
173 let sampler_desc = wgpu::SamplerDescriptor {
174 address_mode_u: wgpu::AddressMode::ClampToEdge,
175 address_mode_v: wgpu::AddressMode::ClampToEdge,
176 address_mode_w: wgpu::AddressMode::ClampToEdge,
177 mag_filter: wgpu::FilterMode::Nearest,
178 min_filter: wgpu::FilterMode::Nearest,
179 mipmap_filter: wgpu::FilterMode::Nearest,
180 ..Default::default()
181 };
182
183 Ok(Texture::from_wgpu_tex(
184 device,
185 texture,
186 Some(sampler_desc),
187 None,
188 ))
189 }
190
191 pub fn new(
198 device: &wgpu::Device,
199 queue: &wgpu::Queue,
200 size: wgpu::Extent3d,
201 ) -> Result<Self, AtlasError> {
202 log::trace!("creating new atlas with dimensions {size:?}");
203 let texture = Self::create_texture(device, queue, size)?;
204 Ok(Self::new_with_texture(texture))
205 }
206
207 pub fn len(&self) -> usize {
208 let layers = self.layers.read().unwrap();
210 layers.iter().map(|layer| layer.frames.len()).sum::<usize>()
211 }
212
213 pub fn is_empty(&self) -> bool {
214 self.len() == 0
216 }
217
218 pub fn get_texture(&self) -> Texture {
220 self.texture_array.read().unwrap().clone()
222 }
223
224 pub fn get_layers(&self) -> impl Deref<Target = Vec<Layer>> + '_ {
225 self.layers.read().unwrap()
227 }
228
229 pub fn set_images(
233 &self,
234 device: &wgpu::Device,
235 queue: &wgpu::Queue,
236 slab: &SlabAllocator<impl IsBuffer>,
237 images: impl IntoIterator<Item = impl Into<AtlasImage>>,
238 ) -> Result<Vec<Hybrid<AtlasTexture>>, AtlasError> {
239 log::debug!("setting images");
240 {
241 let texture = self.texture_array.read().unwrap();
243 let mut guard = self.layers.write().unwrap();
244 let layers: &mut Vec<_> = guard.as_mut();
245 let new_layers =
246 vec![Layer::default(); texture.texture.size().depth_or_array_layers as usize];
247 let _old_layers = std::mem::replace(layers, new_layers);
248 }
249 self.add_images(device, queue, slab, images)
250 }
251
252 pub fn get_size(&self) -> wgpu::Extent3d {
253 self.texture_array.read().unwrap().texture.size()
255 }
256
257 pub fn add_images(
258 &self,
259 device: &wgpu::Device,
260 queue: &wgpu::Queue,
261 slab: &SlabAllocator<impl IsBuffer>,
262 images: impl IntoIterator<Item = impl Into<AtlasImage>>,
263 ) -> Result<Vec<Hybrid<AtlasTexture>>, AtlasError> {
264 let mut layers = self.layers.write().unwrap();
266
267 let mut images = images
268 .into_iter()
269 .enumerate()
270 .map(|(i, img)| {
271 let img = img.into();
272 log::trace!("adding image{i}: {:?}", img.size);
273 (i, img)
274 })
275 .collect::<Vec<_>>();
276 images.sort_by(|a, b| (a.1.size.x * a.1.size.y).cmp(&(b.1.size.x * b.1.size.y)));
277
278 log::trace!("adding {} images to {} layers", images.len(), layers.len());
279 let layer_additions: Vec<Vec<(usize, AtlasImage)>> = fan_split_n(layers.len(), images);
280 log::trace!(
281 "extending the atlas by '{}'",
282 layer_additions
283 .iter()
284 .map(|v| format!("{}", v.len()))
285 .collect::<Vec<_>>()
286 .join(",")
287 );
288
289 let mut texture_array = self.texture_array.write().unwrap();
291 let extent = texture_array.texture.size();
292 let new_texture_array = Self::create_texture(device, queue, extent)?;
293
294 let mut output = vec![];
295 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
296 label: Some("atlas add images"),
297 });
298
299 for (i, additions) in layer_additions.into_iter().enumerate() {
301 if additions.is_empty() {
302 continue;
303 }
304 log::trace!("repacking layer {i} and adding {} images", additions.len());
305 let layer = layers.get_mut(i).unwrap();
307 let images_starting_texture_index = layer.frames.len() as u32;
308 let maybe_items = crunch::pack_into_po2(
309 extent.width as usize,
310 layer
311 .frames
312 .iter()
313 .map(|entry| Packing::GpuImg(entry.clone()))
314 .chain(additions.iter().map(|(j, image)| Packing::Img {
315 layer_index: i as u32,
316 frame_index: images_starting_texture_index + *j as u32,
317 image,
318 }))
319 .map(|packing: Packing| {
320 let size = packing.size();
321 crunch::Item::new(
322 packing,
323 size.x as usize,
324 size.y as usize,
325 crunch::Rotation::None,
326 )
327 }),
328 )
329 .ok();
330 if let Some(items) = maybe_items {
331 for crunch::PackedItem { data: item, rect } in items.items.into_iter() {
332 let offset_px = UVec2::new(rect.x as u32, rect.y as u32);
333 let size_px = UVec2::new(rect.w as u32, rect.h as u32);
334 match item {
336 Packing::Img {
337 layer_index,
338 frame_index,
339 image,
340 } => {
341 let atlas_texture = AtlasTexture {
342 offset_px,
343 size_px,
344 layer_index,
345 frame_index,
346 ..Default::default()
347 };
348 let texture = slab.new_value(atlas_texture);
349 output.push(texture.clone());
350 layer.frames.push(texture);
351
352 let bytes = convert_to_rgba8_bytes(
353 image.pixels.clone(),
354 image.format,
355 image.apply_linear_transfer,
356 );
357
358 let origin = wgpu::Origin3d {
359 x: offset_px.x,
360 y: offset_px.y,
361 z: layer_index,
362 };
363 let size = wgpu::Extent3d {
364 width: size_px.x,
365 height: size_px.y,
366 depth_or_array_layers: 1,
367 };
368 log::trace!(" writing image data to frame {frame_index} in layer {layer_index}");
369 log::trace!(" frame: {atlas_texture:?}");
370 log::trace!(" origin: {origin:?}");
371 log::trace!(" size: {size:?}");
372
373 queue.write_texture(
375 wgpu::ImageCopyTextureBase {
376 texture: &new_texture_array.texture,
377 mip_level: 0,
378 origin,
379 aspect: wgpu::TextureAspect::All,
380 },
381 &bytes,
382 wgpu::ImageDataLayout {
383 offset: 0,
384 bytes_per_row: Some(4 * size_px.x),
385 rows_per_image: Some(size_px.y),
386 },
387 size,
388 );
389 }
390 Packing::GpuImg(texture) => texture.modify(|t| {
391 debug_assert_eq!(t.size_px, size_px);
392 log::trace!(" add_images: copying previous frame {t:?}",);
393 encoder.copy_texture_to_texture(
396 wgpu::ImageCopyTexture {
397 texture: &texture_array.texture,
398 mip_level: 0,
399 origin: wgpu::Origin3d {
400 x: t.offset_px.x,
401 y: t.offset_px.y,
402 z: t.layer_index,
403 },
404 aspect: wgpu::TextureAspect::All,
405 },
406 wgpu::ImageCopyTexture {
407 texture: &new_texture_array.texture,
408 mip_level: 0,
409 origin: wgpu::Origin3d {
410 x: offset_px.x,
411 y: offset_px.y,
412 z: t.layer_index,
413 },
414 aspect: wgpu::TextureAspect::All,
415 },
416 wgpu::Extent3d {
417 width: size_px.x,
418 height: size_px.y,
419 depth_or_array_layers: 1,
420 },
421 );
422
423 t.offset_px = offset_px;
424 }),
425 }
426 }
427 } else {
428 return CannotPackTexturesSnafu {
430 size: texture_array.texture.size(),
431 image_sizes: additions
432 .iter()
433 .map(|(_, img)| img.size)
434 .collect::<Vec<_>>(),
435 }
436 .fail();
437 }
438 }
439 queue.submit(Some(encoder.finish()));
440
441 *texture_array = new_texture_array;
442
443 output.sort_by_key(|a| {
444 a.get().frame_index
446 });
447 Ok(output)
448 }
449
450 pub fn resize(
458 &self,
459 device: &wgpu::Device,
460 queue: &wgpu::Queue,
461 size: wgpu::Extent3d,
462 ) -> Result<(), AtlasError> {
463 let new_texture_array = Self::create_texture(device, queue, size)?;
464 let mut texture_array = self.texture_array.write().unwrap();
466 let mut layers = self.layers.write().unwrap();
468 let old_layers = std::mem::replace(
469 layers.as_mut(),
470 vec![Layer::default(); size.depth_or_array_layers as usize],
471 );
472 log::trace!("repacking all layers due to a resize");
473 let mut all_frames = old_layers
474 .into_iter()
475 .flat_map(|layer| layer.frames)
476 .collect::<Vec<_>>();
477 all_frames.sort_by(|a, b| {
478 let a = a.get();
479 let b = b.get();
480 (a.size_px.x * a.size_px.y).cmp(&(b.size_px.x * b.size_px.y))
481 });
482
483 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
484 label: Some("atlas resize"),
485 });
486 for (i, entries) in fan_split_n(size.depth_or_array_layers as usize, all_frames)
487 .into_iter()
488 .enumerate()
489 {
490 log::trace!("repacking layer {i} with {} frames", entries.len());
491 let items = crunch::pack_into_po2(
492 size.width as usize,
493 entries.iter().map(|atlas_texture| {
494 let size = atlas_texture.get().size_px;
495 crunch::Item::new(
496 atlas_texture,
497 size.x as usize,
498 size.y as usize,
499 crunch::Rotation::None,
500 )
501 }),
502 )
503 .ok()
504 .context(CannotPackTexturesSnafu {
505 size,
506 image_sizes: entries.iter().map(|t| t.get().size_px).collect::<Vec<_>>(),
507 })?;
508 log::trace!(" packed!");
509
510 let layer = layers.get_mut(i).unwrap();
512 for (j, crunch::PackedItem { data: entry, rect }) in items.items.into_iter().enumerate()
513 {
514 let offset_px = UVec2::new(rect.x as u32, rect.y as u32);
515 let size_px = UVec2::new(rect.w as u32, rect.h as u32);
516
517 entry.modify(|t| {
518 debug_assert_eq!(t.size_px, size_px);
519
520 log::trace!(
521 " resize: copying previous texture {} in layer {}",
522 t.frame_index,
523 t.layer_index
524 );
525 encoder.copy_texture_to_texture(
527 wgpu::ImageCopyTexture {
528 texture: &texture_array.texture,
529 mip_level: 0,
530 origin: wgpu::Origin3d {
531 x: t.offset_px.x,
532 y: t.offset_px.y,
533 z: t.layer_index,
534 },
535 aspect: wgpu::TextureAspect::All,
536 },
537 wgpu::ImageCopyTexture {
538 texture: &new_texture_array.texture,
539 mip_level: 0,
540 origin: wgpu::Origin3d {
541 x: offset_px.x,
542 y: offset_px.y,
543 z: t.layer_index,
544 },
545 aspect: wgpu::TextureAspect::All,
546 },
547 wgpu::Extent3d {
548 width: size_px.x,
549 height: size_px.y,
550 depth_or_array_layers: 1,
551 },
552 );
553
554 t.offset_px = offset_px;
555 t.layer_index = i as u32;
556 t.frame_index = j as u32;
557 });
558
559 layer.frames.push(entry.clone());
560 }
561 }
562 queue.submit(Some(encoder.finish()));
563
564 *texture_array = new_texture_array;
565
566 Ok(())
567 }
568
569 pub fn upkeep(&self, device: &wgpu::Device, queue: &wgpu::Queue) {
574 let mut total_dropped = 0;
575 {
576 let mut layers = self.layers.write().unwrap();
577 for (i, layer) in layers.iter_mut().enumerate() {
578 let mut dropped = 0;
579 layer.frames.retain(|entry| {
580 let tex_has_references = entry.strong_count() > ENTRY_STRONG_COUNT_LOWER_BOUND;
581 if tex_has_references {
582 true
583 } else {
584 dropped += 1;
585 false
586 }
587 });
588 total_dropped += dropped;
589 if dropped > 0 {
590 log::trace!("removed {dropped} frames from layer {i}");
591 }
592 }
593
594 layers.len()
595 };
596
597 if total_dropped > 0 {
598 log::trace!("repacking after dropping {total_dropped} frames from the atlas");
599 self.resize(device, queue, self.get_size()).unwrap();
602 }
603 }
604
605 pub fn atlas_img(&self, device: &wgpu::Device, queue: &wgpu::Queue, layer: u32) -> RgbaImage {
615 let tex = self.get_texture();
616 let size = tex.texture.size();
617 let buffer = Texture::read_from(
618 &tex.texture,
619 device,
620 queue,
621 size.width as usize,
622 size.height as usize,
623 4,
624 1,
625 0,
626 Some(wgpu::Origin3d {
627 x: 0,
628 y: 0,
629 z: layer,
630 }),
631 );
632 buffer.into_linear_rgba(device).unwrap()
633 }
634}
635
636#[cfg(test)]
637mod test {
638 use crate::{
639 atlas::{AtlasTexture, TextureAddressMode},
640 camera::Camera,
641 pbr::Material,
642 stage::{Renderlet, Vertex},
643 transform::Transform,
644 Context,
645 };
646 use glam::{UVec3, Vec2, Vec3, Vec4};
647
648 use super::*;
649
650 #[test]
651 fn atlas_uv_mapping() {
653 log::info!("{:?}", std::env::current_dir());
654 let ctx =
655 Context::headless(32, 32).with_default_atlas_texture_size(UVec3::new(1024, 1024, 2));
656 let stage = ctx
657 .new_stage()
658 .with_background_color(Vec3::splat(0.0).extend(1.0))
659 .with_bloom(false);
660 let (projection, view) = crate::camera::default_ortho2d(32.0, 32.0);
661 let camera = stage.new_value(Camera::new(projection, view));
662 let dirt = AtlasImage::from_path("../../img/dirt.jpg").unwrap();
663 let sandstone = AtlasImage::from_path("../../img/sandstone.png").unwrap();
664 let texels = AtlasImage::from_path("../../test_img/atlas/uv_mapping.png").unwrap();
665 log::info!("setting images");
666 let atlas_entries = stage.set_images([dirt, sandstone, texels]).unwrap();
667 log::info!(" done setting images");
668
669 let texels_entry = &atlas_entries[2];
670
671 let material = stage.new_value(Material {
672 albedo_texture_id: texels_entry.id(),
673 has_lighting: false,
674 ..Default::default()
675 });
676 let geometry = stage.new_array({
677 let tl = Vertex::default()
678 .with_position(Vec3::ZERO)
679 .with_uv0(Vec2::ZERO);
680 let tr = Vertex::default()
681 .with_position(Vec3::new(1.0, 0.0, 0.0))
682 .with_uv0(Vec2::new(1.0, 0.0));
683 let bl = Vertex::default()
684 .with_position(Vec3::new(0.0, 1.0, 0.0))
685 .with_uv0(Vec2::new(0.0, 1.0));
686 let br = Vertex::default()
687 .with_position(Vec3::new(1.0, 1.0, 0.0))
688 .with_uv0(Vec2::splat(1.0));
689 [tl, bl, br, tl, br, tr]
690 });
691 let transform = stage.new_value(Transform {
692 scale: Vec3::new(32.0, 32.0, 1.0),
693 ..Default::default()
694 });
695 let renderlet = stage.new_value(Renderlet {
696 camera_id: camera.id(),
697 vertices_array: geometry.array(),
698 transform_id: transform.id(),
699 material_id: material.id(),
700 ..Default::default()
701 });
702 stage.add_renderlet(&renderlet);
703
704 log::info!("rendering");
705 let frame = ctx.get_next_frame().unwrap();
706 stage.render(&frame.view());
707 let img = frame.read_image().unwrap();
708 img_diff::assert_img_eq("atlas/uv_mapping.png", img);
709 }
710
711 #[test]
712 fn uv_wrapping() {
714 let icon_w = 32;
715 let icon_h = 41;
716 let sheet_w = icon_w * 3;
717 let sheet_h = icon_h * 3;
718 let w = sheet_w * 3 + 2;
719 let h = sheet_h;
720 let ctx = Context::headless(w, h);
721 let stage = ctx
722 .new_stage()
723 .with_background_color(Vec4::new(1.0, 1.0, 0.0, 1.0));
724 let (projection, view) = crate::camera::default_ortho2d(w as f32, h as f32);
725 let camera = stage.new_value(Camera::new(projection, view));
726 let texels = AtlasImage::from_path("../../img/happy_mac.png").unwrap();
727 let entries = stage.set_images(std::iter::repeat(texels).take(3)).unwrap();
728 let clamp_tex = &entries[0];
729 let repeat_tex = &entries[1];
730 repeat_tex.modify(|t| {
731 t.modes.s = TextureAddressMode::Repeat;
732 t.modes.t = TextureAddressMode::Repeat;
733 });
734 let mirror_tex = &entries[2];
735 mirror_tex.modify(|t| {
736 t.modes.s = TextureAddressMode::MirroredRepeat;
737 t.modes.t = TextureAddressMode::MirroredRepeat;
738 });
739
740 let clamp_material = stage.new_value(Material {
741 albedo_texture_id: clamp_tex.id(),
742 has_lighting: false,
743 ..Default::default()
744 });
745 let repeat_material = stage.new_value(Material {
746 albedo_texture_id: repeat_tex.id(),
747 has_lighting: false,
748 ..Default::default()
749 });
750 let mirror_material = stage.new_value(Material {
751 albedo_texture_id: mirror_tex.id(),
752 has_lighting: false,
753 ..Default::default()
754 });
755
756 let sheet_w = sheet_w as f32;
757 let sheet_h = sheet_h as f32;
758 let geometry = stage.new_array({
759 let tl = Vertex::default()
760 .with_position(Vec3::ZERO)
761 .with_uv0(Vec2::ZERO);
762 let tr = Vertex::default()
763 .with_position(Vec3::new(sheet_w, 0.0, 0.0))
764 .with_uv0(Vec2::new(3.0, 0.0));
765 let bl = Vertex::default()
766 .with_position(Vec3::new(0.0, sheet_h, 0.0))
767 .with_uv0(Vec2::new(0.0, 3.0));
768 let br = Vertex::default()
769 .with_position(Vec3::new(sheet_w, sheet_h, 0.0))
770 .with_uv0(Vec2::splat(3.0));
771 [tl, bl, br, tl, br, tr]
772 });
773 let clamp_prim = stage.new_value(Renderlet {
774 camera_id: camera.id(),
775 vertices_array: geometry.array(),
776 material_id: clamp_material.id(),
777 ..Default::default()
778 });
779 stage.add_renderlet(&clamp_prim);
780
781 let repeat_transform = stage.new_value(Transform {
782 translation: Vec3::new(sheet_w + 1.0, 0.0, 0.0),
783 ..Default::default()
784 });
785 let repeat_prim = stage.new_value(Renderlet {
786 camera_id: camera.id(),
787 vertices_array: geometry.array(),
788 material_id: repeat_material.id(),
789 transform_id: repeat_transform.id(),
790 ..Default::default()
791 });
792 stage.add_renderlet(&repeat_prim);
793
794 let mirror_transform = stage.new_value(Transform {
795 translation: Vec3::new(sheet_w * 2.0 + 2.0, 0.0, 0.0),
796 ..Default::default()
797 });
798 let mirror_prim = stage.new_value(Renderlet {
799 camera_id: camera.id(),
800 vertices_array: geometry.array(),
801 material_id: mirror_material.id(),
802 transform_id: mirror_transform.id(),
803 ..Default::default()
804 });
805 stage.add_renderlet(&mirror_prim);
806
807 let frame = ctx.get_next_frame().unwrap();
808 stage.render(&frame.view());
809 let img = frame.read_image().unwrap();
810 img_diff::assert_img_eq("atlas/uv_wrapping.png", img);
811 }
812
813 #[test]
814 fn negative_uv_wrapping() {
816 let icon_w = 32;
817 let icon_h = 41;
818 let sheet_w = icon_w * 3;
819 let sheet_h = icon_h * 3;
820 let w = sheet_w * 3 + 2;
821 let h = sheet_h;
822 let ctx = Context::headless(w, h);
823 let stage = ctx
824 .new_stage()
825 .with_background_color(Vec4::new(1.0, 1.0, 0.0, 1.0));
826
827 let (projection, view) = crate::camera::default_ortho2d(w as f32, h as f32);
828 let camera = stage.new_value(Camera {
829 projection,
830 view,
831 ..Default::default()
832 });
833
834 let texels = AtlasImage::from_path("../../img/happy_mac.png").unwrap();
835 let entries = stage.set_images(std::iter::repeat(texels).take(3)).unwrap();
836
837 let clamp_tex = &entries[0];
838 let repeat_tex = &entries[1];
839 repeat_tex.modify(|t| {
840 t.modes.s = TextureAddressMode::Repeat;
841 t.modes.t = TextureAddressMode::Repeat;
842 });
843
844 let mirror_tex = &entries[2];
845 mirror_tex.modify(|t| {
846 t.modes.s = TextureAddressMode::MirroredRepeat;
847 t.modes.t = TextureAddressMode::MirroredRepeat;
848 });
849
850 let clamp_material = stage.new_value(Material {
851 albedo_texture_id: clamp_tex.id(),
852 has_lighting: false,
853 ..Default::default()
854 });
855
856 let repeat_material = stage.new_value(Material {
857 albedo_texture_id: repeat_tex.id(),
858 has_lighting: false,
859 ..Default::default()
860 });
861
862 let mirror_material = stage.new_value(Material {
863 albedo_texture_id: mirror_tex.id(),
864 has_lighting: false,
865 ..Default::default()
866 });
867
868 let sheet_w = sheet_w as f32;
869 let sheet_h = sheet_h as f32;
870 let geometry = {
871 let tl = Vertex::default()
872 .with_position(Vec3::ZERO)
873 .with_uv0(Vec2::ZERO);
874 let tr = Vertex::default()
875 .with_position(Vec3::new(sheet_w, 0.0, 0.0))
876 .with_uv0(Vec2::new(-3.0, 0.0));
877 let bl = Vertex::default()
878 .with_position(Vec3::new(0.0, sheet_h, 0.0))
879 .with_uv0(Vec2::new(0.0, -3.0));
880 let br = Vertex::default()
881 .with_position(Vec3::new(sheet_w, sheet_h, 0.0))
882 .with_uv0(Vec2::splat(-3.0));
883 stage.new_array([tl, bl, br, tl, br, tr])
884 };
885
886 let clamp_prim = stage.new_value(Renderlet {
887 camera_id: camera.id(),
888 vertices_array: geometry.array(),
889 material_id: clamp_material.id(),
890 ..Default::default()
891 });
892 stage.add_renderlet(&clamp_prim);
893
894 let repeat_transform = stage.new_value(Transform {
895 translation: Vec3::new(sheet_w + 1.0, 0.0, 0.0),
896 ..Default::default()
897 });
898 let repeat_prim = stage.new_value(Renderlet {
899 camera_id: camera.id(),
900 vertices_array: geometry.array(),
901 material_id: repeat_material.id(),
902 transform_id: repeat_transform.id(),
903 ..Default::default()
904 });
905 stage.add_renderlet(&repeat_prim);
906
907 let mirror_transform = stage.new_value(Transform {
908 translation: Vec3::new(sheet_w * 2.0 + 2.0, 0.0, 0.0),
909 ..Default::default()
910 });
911 let mirror_prim = stage.new_value(Renderlet {
912 camera_id: camera.id(),
913 vertices_array: geometry.array(),
914 material_id: mirror_material.id(),
915 transform_id: mirror_transform.id(),
916 ..Default::default()
917 });
918 stage.add_renderlet(&mirror_prim);
919
920 let frame = ctx.get_next_frame().unwrap();
921 stage.render(&frame.view());
922 let img = frame.read_image().unwrap();
923 img_diff::assert_img_eq("atlas/negative_uv_wrapping.png", img);
924 }
925
926 #[test]
927 fn transform_uvs_for_atlas() {
928 let mut tex = AtlasTexture {
929 offset_px: UVec2::ZERO,
930 size_px: UVec2::ONE,
931 ..Default::default()
932 };
933 assert_eq!(Vec3::ZERO, tex.uv(Vec2::ZERO, UVec2::splat(100)));
934 assert_eq!(Vec3::ZERO, tex.uv(Vec2::ZERO, UVec2::splat(1)));
935 assert_eq!(Vec3::ZERO, tex.uv(Vec2::ZERO, UVec2::splat(256)));
936 tex.offset_px = UVec2::splat(10);
937 assert_eq!(
938 Vec2::splat(0.1).extend(0.0),
939 tex.uv(Vec2::ZERO, UVec2::splat(100))
940 );
941 }
942
943 #[test]
944 fn can_load_and_read_atlas_texture_array() {
945 let ctx =
947 Context::headless(100, 100).with_default_atlas_texture_size(UVec3::new(512, 512, 2));
948 let stage = ctx.new_stage();
949 let dirt = AtlasImage::from_path("../../img/dirt.jpg").unwrap();
950 let sandstone = AtlasImage::from_path("../../img/sandstone.png").unwrap();
951 let cheetah = AtlasImage::from_path("../../img/cheetah.jpg").unwrap();
952 let texels = AtlasImage::from_path("../../img/happy_mac.png").unwrap();
953 let _frames = stage
954 .set_images([dirt, sandstone, cheetah, texels])
955 .unwrap();
956
957 let img = stage.atlas.atlas_img(&stage.device, &stage.queue, 0);
958 img_diff::assert_img_eq("atlas/array0.png", img);
959 let img = stage.atlas.atlas_img(&stage.device, &stage.queue, 1);
960 img_diff::assert_img_eq("atlas/array1.png", img);
961 }
962
963 #[test]
964 fn upkeep_trims_the_atlas() {
965 let ctx =
967 Context::headless(100, 100).with_default_atlas_texture_size(UVec3::new(512, 512, 2));
968 let stage = ctx.new_stage();
969 let dirt = AtlasImage::from_path("../../img/dirt.jpg").unwrap();
970 let sandstone = AtlasImage::from_path("../../img/sandstone.png").unwrap();
971 let cheetah = AtlasImage::from_path("../../img/cheetah.jpg").unwrap();
972 let texels = AtlasImage::from_path("../../img/happy_mac.png").unwrap();
973 let mut frames = stage
974 .set_images([
975 dirt,
976 sandstone,
977 cheetah,
978 texels.clone(),
979 texels.clone(),
980 texels.clone(),
981 texels.clone(),
982 texels,
983 ])
984 .unwrap();
985 assert_eq!(8, stage.atlas.len());
986
987 frames.pop();
988 frames.pop();
989 frames.pop();
990 frames.pop();
991
992 stage.atlas.upkeep(&stage.device, &stage.queue);
993 assert_eq!(4, stage.atlas.len());
994 }
995}