1use core::{ops::Deref, sync::atomic::AtomicUsize};
2use std::sync::{Arc, Mutex, RwLock};
3
4use craballoc::{
5 prelude::{Hybrid, SlabAllocator, WeakHybrid},
6 runtime::WgpuRuntime,
7 slab::SlabBuffer,
8};
9use crabslab::Id;
10use glam::{UVec2, UVec3};
11use image::RgbaImage;
12use snafu::{prelude::*, OptionExt};
13
14use crate::{
15 atlas::{
16 shader::{AtlasBlittingDescriptor, AtlasDescriptor, AtlasTextureDescriptor},
17 TextureModes,
18 },
19 bindgroup::ManagedBindGroup,
20 texture::{self, CopiedTextureBuffer, Texture},
21};
22
23use super::atlas_image::{convert_pixels, AtlasImage};
24
25pub(crate) const ATLAS_SUGGESTED_SIZE: u32 = 2048;
26pub(crate) const ATLAS_SUGGESTED_LAYERS: u32 = 8;
27
28#[derive(Debug, Snafu)]
29pub enum AtlasError {
30 #[snafu(display("Cannot pack textures.\natlas_size: {size:#?}"))]
31 CannotPackTextures { size: wgpu::Extent3d },
32
33 #[snafu(display("Missing layer {index}"))]
34 MissingLayer { index: u32, images: Vec<AtlasImage> },
35
36 #[snafu(display("Atlas size is invalid: {size:?}"))]
37 Size { size: wgpu::Extent3d },
38
39 #[snafu(display("Missing slab during staging"))]
40 StagingMissingSlab,
41
42 #[snafu(display("Missing bindgroup {layer}"))]
43 MissingBindgroup { layer: u32 },
44
45 #[snafu(display("{source}"))]
46 Texture {
47 source: crate::texture::TextureError,
48 },
49}
50
51#[derive(Clone)]
70pub struct AtlasTexture {
71 pub(crate) descriptor: Hybrid<AtlasTextureDescriptor>,
72}
73
74impl AtlasTexture {
75 pub fn id(&self) -> Id<AtlasTextureDescriptor> {
79 self.descriptor.id()
80 }
81
82 pub fn descriptor(&self) -> AtlasTextureDescriptor {
84 self.descriptor.get()
85 }
86
87 pub fn modes(&self) -> TextureModes {
89 self.descriptor.get().modes
90 }
91
92 pub fn set_modes(&self, modes: TextureModes) {
98 self.descriptor.modify(|d| d.modes = modes);
99 }
100}
101
102#[derive(Clone, Debug)]
113struct InternalAtlasTexture {
114 cache: AtlasTextureDescriptor,
116 weak: WeakHybrid<AtlasTextureDescriptor>,
117}
118
119impl InternalAtlasTexture {
120 fn from_hybrid(hat: &Hybrid<AtlasTextureDescriptor>) -> Self {
121 InternalAtlasTexture {
122 cache: hat.get(),
123 weak: WeakHybrid::from_hybrid(hat),
124 }
125 }
126
127 fn has_external_references(&self) -> bool {
128 self.weak.has_external_references()
129 }
130
131 fn set(&mut self, at: AtlasTextureDescriptor) {
132 self.cache = at;
133 if let Some(hy) = self.weak.upgrade() {
134 hy.set(at);
135 } else if let Some(gpu) = self.weak.weak_gpu().upgrade() {
136 gpu.set(at)
137 } else {
138 log::warn!("could not set atlas texture, lost");
139 }
140 }
141}
142
143pub(crate) fn check_size(size: wgpu::Extent3d) {
144 let conditions = size.depth_or_array_layers >= 2
145 && size.width == size.height
146 && (size.width & (size.width - 1)) == 0;
147 if !conditions {
148 log::error!("{}", AtlasError::Size { size });
149 }
150}
151
152fn fan_split_n<T>(n: usize, input: impl IntoIterator<Item = T>) -> Vec<Vec<T>> {
153 if n == 0 {
154 return vec![];
155 }
156 let mut output = vec![];
157 for _ in 0..n {
158 output.push(vec![]);
159 }
160 let mut i = 0;
161 for item in input.into_iter() {
162 output
164 .get_mut(i)
165 .unwrap_or_else(|| panic!("could not unwrap i:{i} n:{n}"))
166 .push(item);
167 i = (i + 1) % n;
168 }
169 output
170}
171
172#[derive(Clone)]
173enum AnotherPacking<'a> {
174 Img {
175 original_index: usize,
176 image: &'a AtlasImage,
177 },
178 Internal(InternalAtlasTexture),
179}
180
181impl AnotherPacking<'_> {
182 fn size(&self) -> UVec2 {
183 match self {
184 AnotherPacking::Img {
185 original_index: _,
186 image,
187 } => image.size,
188 AnotherPacking::Internal(tex) => tex.cache.size_px,
189 }
190 }
191}
192
193#[derive(Clone, Default, Debug)]
194pub struct Layer {
195 frames: Vec<InternalAtlasTexture>,
196}
197
198#[derive(Clone)]
202pub struct Atlas {
203 pub(crate) slab: SlabAllocator<WgpuRuntime>,
204 texture_array: Arc<RwLock<Texture>>,
205 layers: Arc<RwLock<Vec<Layer>>>,
206 label: Option<String>,
207 descriptor: Hybrid<AtlasDescriptor>,
208 blitter: AtlasBlitter,
210}
211
212impl Atlas {
213 const LABEL: Option<&str> = Some("atlas-texture");
214
215 pub fn device(&self) -> &wgpu::Device {
216 self.slab.device()
217 }
218
219 fn create_texture(
221 runtime: impl AsRef<WgpuRuntime>,
222 size: wgpu::Extent3d,
223 format: Option<wgpu::TextureFormat>,
224 label: Option<&str>,
225 usage: Option<wgpu::TextureUsages>,
226 ) -> Texture {
227 let device = &runtime.as_ref().device;
228 let queue = &runtime.as_ref().queue;
229 check_size(size);
230 let usage = usage.unwrap_or(wgpu::TextureUsages::empty());
231 let texture = device.create_texture(&wgpu::TextureDescriptor {
232 label: Some(label.unwrap_or(Self::LABEL.unwrap())),
233 size,
234 mip_level_count: 1,
235 sample_count: 1,
236 dimension: wgpu::TextureDimension::D2,
237 format: format.unwrap_or(wgpu::TextureFormat::Rgba8Unorm),
238 usage: usage
239 | wgpu::TextureUsages::TEXTURE_BINDING
240 | wgpu::TextureUsages::COPY_DST
241 | wgpu::TextureUsages::COPY_SRC,
242 view_formats: &[],
243 });
244
245 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
246 label: Some("create atlas texture array"),
247 });
248 if device.features().contains(wgpu::Features::CLEAR_TEXTURE) {
249 encoder.clear_texture(
250 &texture,
251 &wgpu::ImageSubresourceRange {
252 aspect: wgpu::TextureAspect::All,
253 base_mip_level: 0,
254 mip_level_count: None,
255 base_array_layer: 0,
256 array_layer_count: None,
257 },
258 );
259 }
260 queue.submit(Some(encoder.finish()));
261
262 let sampler_desc = wgpu::SamplerDescriptor {
263 address_mode_u: wgpu::AddressMode::ClampToEdge,
264 address_mode_v: wgpu::AddressMode::ClampToEdge,
265 address_mode_w: wgpu::AddressMode::ClampToEdge,
266 mag_filter: wgpu::FilterMode::Nearest,
267 min_filter: wgpu::FilterMode::Nearest,
268 mipmap_filter: wgpu::FilterMode::Nearest,
269 ..Default::default()
270 };
271
272 Texture::from_wgpu_tex(device, texture, Some(sampler_desc), None)
273 }
274
275 pub fn new(
282 slab: &SlabAllocator<WgpuRuntime>,
283 size: wgpu::Extent3d,
284 format: Option<wgpu::TextureFormat>,
285 label: Option<&str>,
286 usage: Option<wgpu::TextureUsages>,
287 ) -> Self {
288 let texture = Self::create_texture(slab.runtime(), size, format, label, usage);
289 let num_layers = texture.texture.size().depth_or_array_layers as usize;
290 let layers = vec![Layer::default(); num_layers];
291 log::trace!("creating new atlas with dimensions {size:?}, {num_layers} layers");
292 let descriptor = slab.new_value(AtlasDescriptor {
293 size: UVec3::new(size.width, size.height, size.depth_or_array_layers),
294 });
295 let label = label.map(|s| s.to_owned());
296 let blitter = AtlasBlitter::new(
297 slab.device(),
298 texture.texture.format(),
299 wgpu::FilterMode::Linear,
300 );
301 Atlas {
302 slab: slab.clone(),
303 layers: Arc::new(RwLock::new(layers)),
304 descriptor,
305 label,
306 blitter,
307 texture_array: Arc::new(RwLock::new(texture)),
308 }
309 }
310
311 pub fn descriptor_id(&self) -> Id<AtlasDescriptor> {
312 self.descriptor.id()
313 }
314
315 pub fn len(&self) -> usize {
316 let layers = self.layers.read().unwrap();
318 layers.iter().map(|layer| layer.frames.len()).sum::<usize>()
319 }
320
321 pub fn is_empty(&self) -> bool {
322 self.len() == 0
324 }
325
326 pub fn get_texture(&self) -> impl Deref<Target = Texture> + '_ {
328 self.texture_array.read().unwrap()
330 }
331
332 pub fn get_layers(&self) -> impl Deref<Target = Vec<Layer>> + '_ {
333 self.layers.read().unwrap()
335 }
336
337 pub fn set_images(&self, images: &[AtlasImage]) -> Result<Vec<AtlasTexture>, AtlasError> {
341 log::debug!("setting images");
342 {
343 let texture = self.texture_array.read().unwrap();
345 let mut guard = self.layers.write().unwrap();
346 let layers: &mut Vec<_> = guard.as_mut();
347 let new_layers =
348 vec![Layer::default(); texture.texture.size().depth_or_array_layers as usize];
349 let _old_layers = std::mem::replace(layers, new_layers);
350 }
351 self.add_images(images)
352 }
353
354 pub fn get_size(&self) -> wgpu::Extent3d {
355 self.texture_array.read().unwrap().texture.size()
357 }
358
359 pub fn add_images<'a>(
361 &self,
362 images: impl IntoIterator<Item = &'a AtlasImage>,
363 ) -> Result<Vec<AtlasTexture>, AtlasError> {
364 let mut layers = self.layers.write().unwrap();
366 let mut texture_array = self.texture_array.write().unwrap();
367 let extent = texture_array.texture.size();
368
369 let newly_packed_layers = pack_images(&layers, images, extent)
370 .context(CannotPackTexturesSnafu { size: extent })?;
371
372 let mut staged = StagedResources::try_staging(
373 self.slab.runtime(),
374 extent,
375 newly_packed_layers,
376 Some(&self.slab),
377 &texture_array,
378 self.label.as_deref(),
379 )?;
380
381 *texture_array = staged.texture;
383 *layers = staged.layers;
384
385 staged.image_additions.sort_by_key(|a| a.0);
386 Ok(staged
387 .image_additions
388 .into_iter()
389 .map(|a| AtlasTexture { descriptor: a.1 })
390 .collect())
391 }
392
393 pub fn add_image(&self, image: &AtlasImage) -> Result<AtlasTexture, AtlasError> {
398 Ok(self.add_images(Some(image))?.pop().unwrap())
400 }
401
402 pub fn resize(
410 &self,
411 runtime: impl AsRef<WgpuRuntime>,
412 extent: wgpu::Extent3d,
413 ) -> Result<(), AtlasError> {
414 let mut layers = self.layers.write().unwrap();
415 let mut texture_array = self.texture_array.write().unwrap();
416
417 let newly_packed_layers =
418 pack_images(&layers, &[], extent).context(CannotPackTexturesSnafu { size: extent })?;
419
420 let staged = StagedResources::try_staging(
421 runtime,
422 extent,
423 newly_packed_layers,
424 None::<&SlabAllocator<WgpuRuntime>>,
425 &texture_array,
426 self.label.as_deref(),
427 )?;
428
429 *texture_array = staged.texture;
431 *layers = staged.layers;
432
433 Ok(())
434 }
435
436 #[must_use]
443 pub fn upkeep(&self, runtime: impl AsRef<WgpuRuntime>) -> bool {
444 let mut total_dropped = 0;
445 {
446 let mut layers = self.layers.write().unwrap();
447 for (i, layer) in layers.iter_mut().enumerate() {
448 let mut dropped = 0;
449 layer.frames.retain(|entry| {
450 if entry.has_external_references() {
451 true
452 } else {
453 dropped += 1;
454 false
455 }
456 });
457 total_dropped += dropped;
458 if dropped > 0 {
459 log::trace!("removed {dropped} frames from layer {i}");
460 }
461 }
462
463 layers.len()
464 };
465
466 if total_dropped > 0 {
467 log::trace!("repacking after dropping {total_dropped} frames from the atlas");
468 self.resize(runtime.as_ref(), self.get_size()).unwrap();
471 true
472 } else {
473 false
474 }
475 }
476
477 pub fn atlas_img_buffer(
484 &self,
485 runtime: impl AsRef<WgpuRuntime>,
486 layer: u32,
487 ) -> CopiedTextureBuffer {
488 let runtime = runtime.as_ref();
489 let tex = self.get_texture();
490 let size = tex.texture.size();
491 let (channels, subpixel_bytes) =
492 crate::texture::wgpu_texture_format_channels_and_subpixel_bytes_todo(
493 tex.texture.format(),
494 );
495 log::info!("atlas_texture_format: {:#?}", tex.texture.format());
496 log::info!("atlas_texture_channels: {channels:#?}");
497 log::info!("atlas_texture_subpixel_bytes: {subpixel_bytes:#?}");
498 CopiedTextureBuffer::read_from(
499 runtime,
500 &tex.texture,
501 size.width as usize,
502 size.height as usize,
503 channels as usize,
504 subpixel_bytes as usize,
505 0,
506 Some(wgpu::Origin3d {
507 x: 0,
508 y: 0,
509 z: layer,
510 }),
511 )
512 }
513
514 pub async fn atlas_img(&self, runtime: impl AsRef<WgpuRuntime>, layer: u32) -> RgbaImage {
524 let runtime = runtime.as_ref();
525 let buffer = self.atlas_img_buffer(runtime, layer);
526 buffer.into_linear_rgba(&runtime.device).await.unwrap()
527 }
528
529 #[allow(clippy::await_holding_lock)]
531 pub async fn read_images(&self, runtime: impl AsRef<WgpuRuntime>) -> Vec<RgbaImage> {
532 let mut images = vec![];
533 for i in 0..self.layers.read().unwrap().len() {
534 images.push(self.atlas_img(runtime.as_ref(), i as u32).await);
535 }
536 images
537 }
538
539 pub fn update_texture(
546 &self,
547 atlas_texture: &AtlasTexture,
548 source_texture: &texture::Texture,
549 ) -> Result<wgpu::SubmissionIndex, AtlasError> {
550 self.update_textures(Some((atlas_texture, source_texture)))
551 }
552
553 pub fn update_textures<'a>(
560 &self,
561 updates: impl IntoIterator<Item = (&'a AtlasTexture, &'a texture::Texture)>,
562 ) -> Result<wgpu::SubmissionIndex, AtlasError> {
563 let updates = updates.into_iter().collect::<Vec<_>>();
564 let op = AtlasBlittingOperation::new(&self.blitter, self, updates.len());
565 let runtime = self.slab.runtime();
566 let mut encoder = runtime
567 .device
568 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
569 label: Some("Atlas::update_texture"),
570 });
571 for (i, (atlas_texture, source_texture)) in updates.into_iter().enumerate() {
572 op.run(
573 runtime,
574 &mut encoder,
575 source_texture,
576 i as u32,
577 self,
578 atlas_texture,
579 )?;
580 }
581 Ok(runtime.queue.submit(Some(encoder.finish())))
582 }
583
584 pub fn update_images<'a>(
591 &self,
592 updates: impl IntoIterator<Item = (&'a AtlasTexture, impl Into<AtlasImage>)>,
593 ) -> Result<wgpu::SubmissionIndex, AtlasError> {
594 let (atlas_textures, images): (Vec<_>, Vec<_>) = updates.into_iter().unzip();
595 let mut textures = vec![];
596 for image in images.into_iter() {
597 let image: AtlasImage = image.into();
598 let atlas_format = self.get_texture().texture.format();
599 let bytes = super::atlas_image::convert_pixels(
600 image.pixels,
601 image.format,
602 atlas_format,
603 image.apply_linear_transfer,
604 );
605 let (channels, subpixel_bytes) =
606 texture::wgpu_texture_format_channels_and_subpixel_bytes(atlas_format)
607 .context(TextureSnafu)?;
608 let texture = texture::Texture::new_with(
609 self.slab.runtime(),
610 Some("atlas-image-update"),
611 Some(wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST),
612 None,
613 atlas_format,
614 channels,
615 subpixel_bytes,
616 image.size.x,
617 image.size.y,
618 1,
619 &bytes,
620 );
621 textures.push(texture);
622 }
623 self.update_textures(atlas_textures.into_iter().zip(textures.iter()))
624 }
625
626 pub fn update_image(
633 &self,
634 atlas_texture: &AtlasTexture,
635 source_image: impl Into<AtlasImage>,
636 ) -> Result<wgpu::SubmissionIndex, AtlasError> {
637 self.update_images(Some((atlas_texture, source_image)))
638 }
639}
640
641fn pack_images<'a>(
642 layers: &[Layer],
643 images: impl IntoIterator<Item = &'a AtlasImage>,
644 extent: wgpu::Extent3d,
645) -> Option<Vec<crunch::PackedItems<AnotherPacking<'a>>>> {
646 let mut new_packing: Vec<AnotherPacking> = {
647 let layers: Vec<_> = layers.to_vec();
648 layers
649 .into_iter()
650 .flat_map(|layer| layer.frames)
651 .filter_map(|tex| {
654 if tex.has_external_references() {
655 Some(AnotherPacking::Internal(tex))
656 } else {
657 None
658 }
659 })
660 .chain(
661 images
662 .into_iter()
663 .enumerate()
664 .map(|(i, image)| AnotherPacking::Img {
665 original_index: i,
666 image,
667 }),
668 )
669 .collect()
670 };
671 new_packing.sort_by_key(|a| (a.size().length_squared()));
672 let total_images = new_packing.len();
673 let new_packing_layers: Vec<Vec<AnotherPacking>> =
674 fan_split_n(extent.depth_or_array_layers as usize, new_packing);
675 log::trace!(
676 "packing {total_images} textures into {} layers",
677 new_packing_layers.len()
678 );
679 let mut newly_packed_layers: Vec<crunch::PackedItems<_>> = vec![];
680 for (i, new_layer) in new_packing_layers.into_iter().enumerate() {
681 log::trace!(" packing layer {i} into power of 2 {}", extent.width);
682 let packed = crunch::pack_into_po2(
683 extent.width as usize,
684 new_layer.into_iter().map(|p| {
685 let size = p.size();
686 crunch::Item::new(p, size.x as usize, size.y as usize, crunch::Rotation::None)
687 }),
688 )
689 .ok()?;
690 log::trace!(" layer {i} packed with {} textures", packed.items.len());
691 newly_packed_layers.push(packed);
692 }
693 Some(newly_packed_layers)
694}
695
696struct StagedResources {
698 texture: Texture,
699 image_additions: Vec<(usize, Hybrid<AtlasTextureDescriptor>)>,
700 layers: Vec<Layer>,
701}
702
703impl StagedResources {
704 fn try_staging(
706 runtime: impl AsRef<WgpuRuntime>,
707 extent: wgpu::Extent3d,
708 newly_packed_layers: Vec<crunch::PackedItems<AnotherPacking>>,
709 slab: Option<&SlabAllocator<WgpuRuntime>>,
710 old_texture_array: &Texture,
711 label: Option<&str>,
712 ) -> Result<Self, AtlasError> {
713 let runtime = runtime.as_ref();
714 let new_texture_array = Atlas::create_texture(
715 runtime,
716 extent,
717 Some(old_texture_array.texture.format()),
718 label,
719 Some(old_texture_array.texture.usage()),
720 );
721 let mut output = vec![];
722 let mut encoder = runtime
723 .device
724 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
725 label: Some("atlas staging"),
726 });
727 let mut temporary_layers = vec![Layer::default(); extent.depth_or_array_layers as usize];
728 for (layer_index, packed_items) in newly_packed_layers.into_iter().enumerate() {
729 if packed_items.items.is_empty() {
730 continue;
731 }
732 let layer = temporary_layers.get_mut(layer_index).unwrap();
734 for (frame_index, crunch::PackedItem { data: item, rect }) in
735 packed_items.items.into_iter().enumerate()
736 {
737 let offset_px = UVec2::new(rect.x as u32, rect.y as u32);
738 let size_px = UVec2::new(rect.w as u32, rect.h as u32);
739
740 match item {
741 AnotherPacking::Img {
742 original_index,
743 image,
744 } => {
745 let atlas_texture = AtlasTextureDescriptor {
746 offset_px,
747 size_px,
748 frame_index: frame_index as u32,
749 layer_index: layer_index as u32,
750 ..Default::default()
751 };
752 let texture = slab
753 .context(StagingMissingSlabSnafu)?
754 .new_value(atlas_texture);
755 layer
756 .frames
757 .push(InternalAtlasTexture::from_hybrid(&texture));
758 output.push((original_index, texture));
759
760 let bytes = convert_pixels(
761 image.pixels.clone(),
762 image.format,
763 old_texture_array.texture.format(),
764 image.apply_linear_transfer,
765 );
766
767 let origin = wgpu::Origin3d {
768 x: offset_px.x,
769 y: offset_px.y,
770 z: layer_index as u32,
771 };
772 let size = wgpu::Extent3d {
773 width: size_px.x,
774 height: size_px.y,
775 depth_or_array_layers: 1,
776 };
777 log::trace!(
778 " writing image data to frame {frame_index} in layer {layer_index}"
779 );
780 log::trace!(" frame: {atlas_texture:?}");
781 log::trace!(" origin: {origin:?}");
782 log::trace!(" size: {size:?}");
783
784 runtime.queue.write_texture(
786 wgpu::TexelCopyTextureInfo {
787 texture: &new_texture_array.texture,
788 mip_level: 0,
789 origin,
790 aspect: wgpu::TextureAspect::All,
791 },
792 &bytes,
793 wgpu::TexelCopyBufferLayout {
794 offset: 0,
795 bytes_per_row: Some(4 * size_px.x),
796 rows_per_image: Some(size_px.y),
797 },
798 size,
799 );
800 }
801 AnotherPacking::Internal(mut texture) => {
802 let prev_t = texture.cache;
803 let mut t = texture.cache;
804 debug_assert_eq!(t.size_px, size_px);
805 encoder.copy_texture_to_texture(
808 wgpu::TexelCopyTextureInfo {
809 texture: &old_texture_array.texture,
810 mip_level: 0,
811 origin: t.origin(),
812 aspect: wgpu::TextureAspect::All,
813 },
814 wgpu::TexelCopyTextureInfo {
815 texture: &new_texture_array.texture,
816 mip_level: 0,
817 origin: wgpu::Origin3d {
818 x: offset_px.x,
819 y: offset_px.y,
820 z: layer_index as u32,
821 },
822 aspect: wgpu::TextureAspect::All,
823 },
824 t.size_as_extent(),
825 );
826
827 t.layer_index = layer_index as u32;
828 t.frame_index = frame_index as u32;
829 t.offset_px = offset_px;
830
831 log::trace!(
832 " copied previous frame {}",
833 pretty_assertions::Comparison::new(&prev_t, &t)
834 );
835
836 texture.set(t);
837 layer.frames.push(texture);
838 }
839 }
840 }
841 }
842 runtime.queue.submit(Some(encoder.finish()));
843
844 Ok(Self {
845 texture: new_texture_array,
846 image_additions: output,
847 layers: temporary_layers,
848 })
849 }
850}
851
852#[derive(Clone)]
855pub struct AtlasBlittingOperation {
856 atlas_slab_buffer: Arc<Mutex<SlabBuffer<wgpu::Buffer>>>,
857 pipeline: Arc<wgpu::RenderPipeline>,
858 bindgroups: Arc<Vec<ManagedBindGroup>>,
859 bindgroup_layout: Arc<wgpu::BindGroupLayout>,
860 sampler: Arc<wgpu::Sampler>,
861 from_texture_id: Arc<AtomicUsize>,
862 pub(crate) desc: Hybrid<AtlasBlittingDescriptor>,
863}
864
865impl AtlasBlittingOperation {
866 pub fn new(
867 blitter: &AtlasBlitter,
868 into_atlas: &Atlas,
869 source_layers: usize,
870 ) -> AtlasBlittingOperation {
871 AtlasBlittingOperation {
872 desc: into_atlas
873 .slab
874 .new_value(AtlasBlittingDescriptor::default()),
875 atlas_slab_buffer: Arc::new(Mutex::new(into_atlas.slab.commit())),
876 bindgroups: {
877 let mut bgs = vec![];
878 for _ in 0..source_layers {
879 bgs.push(ManagedBindGroup::default());
880 }
881 Arc::new(bgs)
882 },
883 pipeline: blitter.pipeline.clone(),
884 sampler: blitter.sampler.clone(),
885 bindgroup_layout: blitter.bind_group_layout.clone(),
886 from_texture_id: Default::default(),
887 }
888 }
889
890 pub fn run(
896 &self,
897 runtime: impl AsRef<WgpuRuntime>,
898 encoder: &mut wgpu::CommandEncoder,
899 from_texture: &crate::texture::Texture,
900 from_layer: u32,
901 to_atlas: &Atlas,
902 atlas_texture: &AtlasTexture,
903 ) -> Result<(), AtlasError> {
904 let runtime = runtime.as_ref();
905
906 self.desc.set(AtlasBlittingDescriptor {
908 atlas_texture_id: atlas_texture.id(),
909 atlas_desc_id: to_atlas.descriptor_id(),
910 });
911 let _ = to_atlas.slab.commit();
913
914 let to_atlas_texture = to_atlas.get_texture();
915 let mut atlas_slab_buffer = self.atlas_slab_buffer.lock().unwrap();
916 let atlas_slab_invalid = atlas_slab_buffer.update_if_invalid();
917 let from_texture_has_been_replaced = {
918 let prev_id = self
919 .from_texture_id
920 .swap(from_texture.id(), std::sync::atomic::Ordering::Relaxed);
921 from_texture.id() != prev_id
922 };
923 let should_invalidate = atlas_slab_invalid || from_texture_has_been_replaced;
924 let view = from_texture
925 .texture
926 .create_view(&wgpu::TextureViewDescriptor {
927 label: Some("atlas-blitting"),
928 base_array_layer: from_layer,
929 array_layer_count: Some(1),
930 dimension: Some(wgpu::TextureViewDimension::D2),
931 ..Default::default()
932 });
933 let bindgroup = self
934 .bindgroups
935 .get(from_layer as usize)
936 .context(MissingBindgroupSnafu { layer: from_layer })?
937 .get(should_invalidate, || {
938 runtime
939 .device
940 .create_bind_group(&wgpu::BindGroupDescriptor {
941 label: Some("atlas-blitting"),
942 layout: &self.bindgroup_layout,
943 entries: &[
944 wgpu::BindGroupEntry {
945 binding: 0,
946 resource: wgpu::BindingResource::Buffer(
947 atlas_slab_buffer.deref().as_entire_buffer_binding(),
948 ),
949 },
950 wgpu::BindGroupEntry {
951 binding: 1,
952 resource: wgpu::BindingResource::TextureView(&view),
953 },
954 wgpu::BindGroupEntry {
955 binding: 2,
956 resource: wgpu::BindingResource::Sampler(&self.sampler),
957 },
958 ],
959 })
960 });
961
962 let atlas_texture = atlas_texture.descriptor();
963 let atlas_view = to_atlas_texture
964 .texture
965 .create_view(&wgpu::TextureViewDescriptor {
966 label: Some("atlas-blitting"),
967 format: None,
968 dimension: Some(wgpu::TextureViewDimension::D2),
969 usage: None,
970 aspect: wgpu::TextureAspect::All,
971 base_mip_level: 0,
972 mip_level_count: None,
973 base_array_layer: atlas_texture.layer_index,
974 array_layer_count: Some(1),
975 });
976 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
977 label: Some("atlas-blitter"),
978 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
979 view: &atlas_view,
980 resolve_target: None,
981 ops: wgpu::Operations {
982 load: wgpu::LoadOp::Load,
983 store: wgpu::StoreOp::Store,
984 },
985 depth_slice: None,
986 })],
987 depth_stencil_attachment: None,
988 timestamp_writes: None,
989 occlusion_query_set: None,
990 });
991 pass.set_pipeline(&self.pipeline);
992 pass.set_bind_group(0, Some(bindgroup.as_ref()), &[]);
993 let id = self.desc.id();
994 pass.draw(0..6, id.inner()..id.inner() + 1);
995 Ok(())
996 }
997}
998
999#[derive(Clone)]
1003pub struct AtlasBlitter {
1004 pipeline: Arc<wgpu::RenderPipeline>,
1005 bind_group_layout: Arc<wgpu::BindGroupLayout>,
1006 sampler: Arc<wgpu::Sampler>,
1007}
1008
1009impl AtlasBlitter {
1010 pub fn new(
1018 device: &wgpu::Device,
1019 format: wgpu::TextureFormat,
1020 mag_filter: wgpu::FilterMode,
1021 ) -> Self {
1022 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1023 label: Some("atlas-blitter"),
1024 address_mode_u: wgpu::AddressMode::ClampToEdge,
1025 address_mode_v: wgpu::AddressMode::ClampToEdge,
1026 address_mode_w: wgpu::AddressMode::ClampToEdge,
1027 mag_filter,
1028 ..Default::default()
1029 });
1030
1031 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1032 label: Some("atlas-blitter"),
1033 entries: &[
1034 wgpu::BindGroupLayoutEntry {
1035 binding: 0,
1036 visibility: wgpu::ShaderStages::VERTEX,
1037 ty: wgpu::BindingType::Buffer {
1038 ty: wgpu::BufferBindingType::Storage { read_only: true },
1039 has_dynamic_offset: false,
1040 min_binding_size: None,
1041 },
1042 count: None,
1043 },
1044 wgpu::BindGroupLayoutEntry {
1045 binding: 1,
1046 visibility: wgpu::ShaderStages::FRAGMENT,
1047 ty: wgpu::BindingType::Texture {
1048 sample_type: wgpu::TextureSampleType::Float {
1049 filterable: mag_filter == wgpu::FilterMode::Linear,
1050 },
1051 view_dimension: wgpu::TextureViewDimension::D2,
1052 multisampled: false,
1053 },
1054 count: None,
1055 },
1056 wgpu::BindGroupLayoutEntry {
1057 binding: 2,
1058 visibility: wgpu::ShaderStages::FRAGMENT,
1059 ty: wgpu::BindingType::Sampler(if mag_filter == wgpu::FilterMode::Linear {
1060 wgpu::SamplerBindingType::Filtering
1061 } else {
1062 wgpu::SamplerBindingType::NonFiltering
1063 }),
1064 count: None,
1065 },
1066 ],
1067 });
1068
1069 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1070 label: Some("atlas-blitter"),
1071 bind_group_layouts: &[&bind_group_layout],
1072 push_constant_ranges: &[],
1073 });
1074
1075 let vertex = crate::linkage::atlas_blit_vertex::linkage(device);
1076 let fragment = crate::linkage::atlas_blit_fragment::linkage(device);
1077 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1078 label: Some("atlas-blitter"),
1079 layout: Some(&pipeline_layout),
1080 vertex: wgpu::VertexState {
1081 module: &vertex.module,
1082 entry_point: Some(vertex.entry_point),
1083 compilation_options: wgpu::PipelineCompilationOptions::default(),
1084 buffers: &[],
1085 },
1086 primitive: wgpu::PrimitiveState {
1087 topology: wgpu::PrimitiveTopology::TriangleList,
1088 strip_index_format: None,
1089 front_face: wgpu::FrontFace::Ccw,
1090 cull_mode: None,
1091 unclipped_depth: false,
1092 polygon_mode: wgpu::PolygonMode::Fill,
1093 conservative: false,
1094 },
1095 depth_stencil: None,
1096 multisample: wgpu::MultisampleState::default(),
1097 fragment: Some(wgpu::FragmentState {
1098 module: &fragment.module,
1099 entry_point: Some(fragment.entry_point),
1100 compilation_options: wgpu::PipelineCompilationOptions::default(),
1101 targets: &[Some(wgpu::ColorTargetState {
1102 format,
1103 blend: None,
1104 write_mask: wgpu::ColorWrites::ALL,
1105 })],
1106 }),
1107 multiview: None,
1108 cache: None,
1109 });
1110
1111 Self {
1112 pipeline: pipeline.into(),
1113 bind_group_layout: bind_group_layout.into(),
1114 sampler: sampler.into(),
1115 }
1116 }
1117}
1118
1119#[cfg(test)]
1120mod test {
1121 use crate::{
1122 atlas::{shader::AtlasTextureDescriptor, TextureAddressMode},
1123 context::Context,
1124 geometry::Vertex,
1125 material::Materials,
1126 test::BlockOnFuture,
1127 };
1128 use glam::{UVec3, Vec2, Vec3, Vec4};
1129
1130 use super::*;
1131
1132 #[test]
1133 fn atlas_uv_mapping() {
1135 log::info!("{:?}", std::env::current_dir());
1136 let ctx = Context::headless(32, 32)
1137 .block()
1138 .with_default_atlas_texture_size(UVec3::new(1024, 1024, 2));
1139 let stage = ctx
1140 .new_stage()
1141 .with_background_color(Vec3::splat(0.0).extend(1.0))
1142 .with_bloom(false);
1143 let (projection, view) = crate::camera::default_ortho2d(32.0, 32.0);
1144 let _camera = stage
1145 .new_camera()
1146 .with_projection_and_view(projection, view);
1147 let dirt = AtlasImage::from_path("../../img/dirt.jpg").unwrap();
1148 let sandstone = AtlasImage::from_path("../../img/sandstone.png").unwrap();
1149 let texels = AtlasImage::from_path("../../test_img/atlas/uv_mapping.png").unwrap();
1150 log::info!("setting images");
1151 let atlas_entries = stage.set_images([dirt, sandstone, texels]).unwrap();
1152 log::info!(" done setting images");
1153
1154 let texels_entry = &atlas_entries[2];
1155
1156 let _rez = stage
1157 .new_primitive()
1158 .with_material(
1159 stage
1160 .new_material()
1161 .with_albedo_texture(texels_entry)
1162 .with_has_lighting(false),
1163 )
1164 .with_vertices(stage.new_vertices({
1165 let tl = Vertex::default()
1166 .with_position(Vec3::ZERO)
1167 .with_uv0(Vec2::ZERO);
1168 let tr = Vertex::default()
1169 .with_position(Vec3::new(1.0, 0.0, 0.0))
1170 .with_uv0(Vec2::new(1.0, 0.0));
1171 let bl = Vertex::default()
1172 .with_position(Vec3::new(0.0, 1.0, 0.0))
1173 .with_uv0(Vec2::new(0.0, 1.0));
1174 let br = Vertex::default()
1175 .with_position(Vec3::new(1.0, 1.0, 0.0))
1176 .with_uv0(Vec2::splat(1.0));
1177 [tl, bl, br, tl, br, tr]
1178 }))
1179 .with_transform(stage.new_transform().with_scale(Vec3::new(32.0, 32.0, 1.0)));
1180
1181 log::info!("rendering");
1182 let frame = ctx.get_next_frame().unwrap();
1183 stage.render(&frame.view());
1184 let img = frame.read_image().block().unwrap();
1185 img_diff::assert_img_eq("atlas/uv_mapping.png", img);
1186 }
1187
1188 #[test]
1189 fn uv_wrapping() {
1191 let icon_w = 32;
1192 let icon_h = 41;
1193 let sheet_w = icon_w * 3;
1194 let sheet_h = icon_h * 3;
1195 let w = sheet_w * 3 + 2;
1196 let h = sheet_h;
1197 let ctx = Context::headless(w, h).block();
1198 let stage = ctx
1199 .new_stage()
1200 .with_background_color(Vec4::new(1.0, 1.0, 0.0, 1.0));
1201 let (projection, view) = crate::camera::default_ortho2d(w as f32, h as f32);
1202 let _camera = stage
1203 .new_camera()
1204 .with_projection_and_view(projection, view);
1205 let texels = AtlasImage::from_path("../../img/happy_mac.png").unwrap();
1206 let entries = stage.set_images(std::iter::repeat_n(texels, 3)).unwrap();
1207 let clamp_tex = &entries[0];
1208 let repeat_tex = &entries[1];
1209 repeat_tex.set_modes(TextureModes {
1210 s: TextureAddressMode::Repeat,
1211 t: TextureAddressMode::Repeat,
1212 });
1213 let mirror_tex = &entries[2];
1214 mirror_tex.set_modes(TextureModes {
1215 s: TextureAddressMode::MirroredRepeat,
1216 t: TextureAddressMode::MirroredRepeat,
1217 });
1218
1219 let sheet_w = sheet_w as f32;
1220 let sheet_h = sheet_h as f32;
1221 let geometry = stage.new_vertices({
1222 let tl = Vertex::default()
1223 .with_position(Vec3::ZERO)
1224 .with_uv0(Vec2::ZERO);
1225 let tr = Vertex::default()
1226 .with_position(Vec3::new(sheet_w, 0.0, 0.0))
1227 .with_uv0(Vec2::new(3.0, 0.0));
1228 let bl = Vertex::default()
1229 .with_position(Vec3::new(0.0, sheet_h, 0.0))
1230 .with_uv0(Vec2::new(0.0, 3.0));
1231 let br = Vertex::default()
1232 .with_position(Vec3::new(sheet_w, sheet_h, 0.0))
1233 .with_uv0(Vec2::splat(3.0));
1234 [tl, bl, br, tl, br, tr]
1235 });
1236 let _clamp_rez = stage
1237 .new_primitive()
1238 .with_vertices(&geometry)
1239 .with_material(
1240 stage
1241 .new_material()
1242 .with_albedo_texture(clamp_tex)
1243 .with_has_lighting(false),
1244 );
1245
1246 let _repeat_rez = stage
1247 .new_primitive()
1248 .with_transform(stage.new_transform().with_translation(Vec3::new(
1249 sheet_w + 1.0,
1250 0.0,
1251 0.0,
1252 )))
1253 .with_material(
1254 stage
1255 .new_material()
1256 .with_albedo_texture(repeat_tex)
1257 .with_has_lighting(false),
1258 )
1259 .with_vertices(&geometry);
1260
1261 let _mirror_rez = stage
1262 .new_primitive()
1263 .with_transform(stage.new_transform().with_translation(Vec3::new(
1264 sheet_w * 2.0 + 2.0,
1265 0.0,
1266 0.0,
1267 )))
1268 .with_material(
1269 stage
1270 .new_material()
1271 .with_albedo_texture(mirror_tex)
1272 .with_has_lighting(false),
1273 )
1274 .with_vertices(geometry);
1275
1276 let frame = ctx.get_next_frame().unwrap();
1277 stage.render(&frame.view());
1278 let img = frame.read_image().block().unwrap();
1279 img_diff::assert_img_eq("atlas/uv_wrapping.png", img);
1280 }
1281
1282 #[test]
1283 fn negative_uv_wrapping() {
1285 let icon_w = 32;
1286 let icon_h = 41;
1287 let sheet_w = icon_w * 3;
1288 let sheet_h = icon_h * 3;
1289 let w = sheet_w * 3 + 2;
1290 let h = sheet_h;
1291 let ctx = Context::headless(w, h).block();
1292 let stage = ctx
1293 .new_stage()
1294 .with_background_color(Vec4::new(1.0, 1.0, 0.0, 1.0));
1295
1296 let (projection, view) = crate::camera::default_ortho2d(w as f32, h as f32);
1297 let _camera = stage
1298 .new_camera()
1299 .with_projection_and_view(projection, view);
1300
1301 let texels = AtlasImage::from_path("../../img/happy_mac.png").unwrap();
1302 let entries = stage.set_images(std::iter::repeat_n(texels, 3)).unwrap();
1303
1304 let clamp_tex = &entries[0];
1305 let repeat_tex = &entries[1];
1306 repeat_tex.set_modes(TextureModes {
1307 s: TextureAddressMode::Repeat,
1308 t: TextureAddressMode::Repeat,
1309 });
1310
1311 let mirror_tex = &entries[2];
1312 mirror_tex.set_modes(TextureModes {
1313 s: TextureAddressMode::MirroredRepeat,
1314 t: TextureAddressMode::MirroredRepeat,
1315 });
1316
1317 let sheet_w = sheet_w as f32;
1318 let sheet_h = sheet_h as f32;
1319 let geometry = stage.new_vertices({
1320 let tl = Vertex::default()
1321 .with_position(Vec3::ZERO)
1322 .with_uv0(Vec2::ZERO);
1323 let tr = Vertex::default()
1324 .with_position(Vec3::new(sheet_w, 0.0, 0.0))
1325 .with_uv0(Vec2::new(-3.0, 0.0));
1326 let bl = Vertex::default()
1327 .with_position(Vec3::new(0.0, sheet_h, 0.0))
1328 .with_uv0(Vec2::new(0.0, -3.0));
1329 let br = Vertex::default()
1330 .with_position(Vec3::new(sheet_w, sheet_h, 0.0))
1331 .with_uv0(Vec2::splat(-3.0));
1332 [tl, bl, br, tl, br, tr]
1333 });
1334 let _clamp_prim = stage
1335 .new_primitive()
1336 .with_vertices(&geometry)
1337 .with_material(
1338 stage
1339 .new_material()
1340 .with_albedo_texture(clamp_tex)
1341 .with_has_lighting(false),
1342 );
1343
1344 let _repeat_rez = stage
1345 .new_primitive()
1346 .with_material(
1347 stage
1348 .new_material()
1349 .with_albedo_texture(repeat_tex)
1350 .with_has_lighting(false),
1351 )
1352 .with_transform(stage.new_transform().with_translation(Vec3::new(
1353 sheet_w + 1.0,
1354 0.0,
1355 0.0,
1356 )))
1357 .with_vertices(&geometry);
1358
1359 let _mirror_rez = stage
1360 .new_primitive()
1361 .with_material(
1362 stage
1363 .new_material()
1364 .with_albedo_texture(mirror_tex)
1365 .with_has_lighting(false),
1366 )
1367 .with_transform(stage.new_transform().with_translation(Vec3::new(
1368 sheet_w * 2.0 + 2.0,
1369 0.0,
1370 0.0,
1371 )))
1372 .with_vertices(&geometry);
1373
1374 let frame = ctx.get_next_frame().unwrap();
1375 stage.render(&frame.view());
1376 let img = frame.read_image().block().unwrap();
1377 img_diff::assert_img_eq("atlas/negative_uv_wrapping.png", img);
1378 }
1379
1380 #[test]
1381 fn transform_uvs_for_atlas() {
1382 let mut tex = AtlasTextureDescriptor {
1383 offset_px: UVec2::ZERO,
1384 size_px: UVec2::ONE,
1385 ..Default::default()
1386 };
1387 assert_eq!(Vec3::ZERO, tex.uv(Vec2::ZERO, UVec2::splat(100)));
1388 assert_eq!(Vec3::ZERO, tex.uv(Vec2::ZERO, UVec2::splat(1)));
1389 assert_eq!(Vec3::ZERO, tex.uv(Vec2::ZERO, UVec2::splat(256)));
1390 tex.offset_px = UVec2::splat(10);
1391 assert_eq!(
1392 Vec2::splat(0.1).extend(0.0),
1393 tex.uv(Vec2::ZERO, UVec2::splat(100))
1394 );
1395 }
1396
1397 #[test]
1398 fn can_load_and_read_atlas_texture_array() {
1399 let ctx = Context::headless(100, 100)
1401 .block()
1402 .with_default_atlas_texture_size(UVec3::new(512, 512, 2));
1403 let stage = ctx.new_stage();
1404 let dirt = AtlasImage::from_path("../../img/dirt.jpg").unwrap();
1405 let sandstone = AtlasImage::from_path("../../img/sandstone.png").unwrap();
1406 let cheetah = AtlasImage::from_path("../../img/cheetah.jpg").unwrap();
1407 let texels = AtlasImage::from_path("../../img/happy_mac.png").unwrap();
1408 let _frames = stage
1409 .set_images([dirt, sandstone, cheetah, texels])
1410 .unwrap();
1411 let materials: &Materials = stage.as_ref();
1412 let img = materials.atlas().atlas_img(&ctx, 0).block();
1413 img_diff::assert_img_eq("atlas/array0.png", img);
1414 let img = materials.atlas().atlas_img(&ctx, 1).block();
1415 img_diff::assert_img_eq("atlas/array1.png", img);
1416 }
1417
1418 #[test]
1419 fn upkeep_trims_the_atlas() {
1420 let ctx = Context::headless(100, 100)
1422 .block()
1423 .with_default_atlas_texture_size(UVec3::new(512, 512, 2));
1424 let stage = ctx.new_stage();
1425 let dirt = AtlasImage::from_path("../../img/dirt.jpg").unwrap();
1426 let sandstone = AtlasImage::from_path("../../img/sandstone.png").unwrap();
1427 let cheetah = AtlasImage::from_path("../../img/cheetah.jpg").unwrap();
1428 let texels = AtlasImage::from_path("../../img/happy_mac.png").unwrap();
1429 let mut frames = stage
1430 .add_images([
1431 dirt,
1432 sandstone,
1433 cheetah,
1434 texels.clone(),
1435 texels.clone(),
1436 texels.clone(),
1437 texels.clone(),
1438 texels,
1439 ])
1440 .unwrap();
1441 let materials: &Materials = stage.as_ref();
1442 assert_eq!(8, materials.atlas().len());
1443
1444 frames.pop();
1445 frames.pop();
1446 frames.pop();
1447 frames.pop();
1448
1449 let _ = materials.atlas().upkeep(&ctx);
1450 assert_eq!(4, materials.atlas().len());
1451 }
1452}