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,
211}
212
213impl Atlas {
214 const LABEL: Option<&str> = Some("atlas-texture");
215
216 pub fn device(&self) -> &wgpu::Device {
217 self.slab.device()
218 }
219
220 fn create_texture(
222 runtime: impl AsRef<WgpuRuntime>,
223 size: wgpu::Extent3d,
224 format: Option<wgpu::TextureFormat>,
225 label: Option<&str>,
226 usage: Option<wgpu::TextureUsages>,
227 ) -> Texture {
228 let device = &runtime.as_ref().device;
229 let queue = &runtime.as_ref().queue;
230 check_size(size);
231 let usage = usage.unwrap_or(wgpu::TextureUsages::empty());
232 let texture = device.create_texture(&wgpu::TextureDescriptor {
233 label: Some(label.unwrap_or(Self::LABEL.unwrap())),
234 size,
235 mip_level_count: 1,
236 sample_count: 1,
237 dimension: wgpu::TextureDimension::D2,
238 format: format.unwrap_or(wgpu::TextureFormat::Rgba8Unorm),
239 usage: usage
240 | wgpu::TextureUsages::TEXTURE_BINDING
241 | wgpu::TextureUsages::COPY_DST
242 | wgpu::TextureUsages::COPY_SRC,
243 view_formats: &[],
244 });
245
246 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
247 label: Some("create atlas texture array"),
248 });
249 if device.features().contains(wgpu::Features::CLEAR_TEXTURE) {
250 encoder.clear_texture(
251 &texture,
252 &wgpu::ImageSubresourceRange {
253 aspect: wgpu::TextureAspect::All,
254 base_mip_level: 0,
255 mip_level_count: None,
256 base_array_layer: 0,
257 array_layer_count: None,
258 },
259 );
260 }
261 queue.submit(Some(encoder.finish()));
262
263 let sampler_desc = wgpu::SamplerDescriptor {
264 address_mode_u: wgpu::AddressMode::ClampToEdge,
265 address_mode_v: wgpu::AddressMode::ClampToEdge,
266 address_mode_w: wgpu::AddressMode::ClampToEdge,
267 mag_filter: wgpu::FilterMode::Nearest,
268 min_filter: wgpu::FilterMode::Nearest,
269 mipmap_filter: wgpu::FilterMode::Nearest,
270 ..Default::default()
271 };
272
273 Texture::from_wgpu_tex(device, texture, Some(sampler_desc), None)
274 }
275
276 pub fn new(
283 slab: &SlabAllocator<WgpuRuntime>,
284 size: wgpu::Extent3d,
285 format: Option<wgpu::TextureFormat>,
286 label: Option<&str>,
287 usage: Option<wgpu::TextureUsages>,
288 ) -> Self {
289 let texture = Self::create_texture(slab.runtime(), size, format, label, usage);
290 let num_layers = texture.texture.size().depth_or_array_layers as usize;
291 let layers = vec![Layer::default(); num_layers];
292 log::trace!("creating new atlas with dimensions {size:?}, {num_layers} layers");
293 let descriptor = slab.new_value(AtlasDescriptor {
294 size: UVec3::new(size.width, size.height, size.depth_or_array_layers),
295 });
296 let label = label.map(|s| s.to_owned());
297 let blitter = AtlasBlitter::new(
298 slab.device(),
299 texture.texture.format(),
300 wgpu::FilterMode::Linear,
301 );
302 Atlas {
303 slab: slab.clone(),
304 layers: Arc::new(RwLock::new(layers)),
305 descriptor,
306 label,
307 blitter,
308 texture_array: Arc::new(RwLock::new(texture)),
309 }
310 }
311
312 pub fn descriptor_id(&self) -> Id<AtlasDescriptor> {
313 self.descriptor.id()
314 }
315
316 pub fn len(&self) -> usize {
317 let layers = self.layers.read().expect("atlas layers read");
319 layers.iter().map(|layer| layer.frames.len()).sum::<usize>()
320 }
321
322 pub fn is_empty(&self) -> bool {
323 self.len() == 0
325 }
326
327 pub fn get_texture(&self) -> impl Deref<Target = Texture> + '_ {
329 self.texture_array.read().expect("atlas texture_array read")
331 }
332
333 pub fn get_layers(&self) -> impl Deref<Target = Vec<Layer>> + '_ {
334 self.layers.read().expect("atlas layers read")
336 }
337
338 pub fn set_images(&self, images: &[AtlasImage]) -> Result<Vec<AtlasTexture>, AtlasError> {
342 log::debug!("setting images");
343 {
344 let texture = self.texture_array.read().expect("atlas texture_array read");
346 let mut guard = self.layers.write().expect("atlas layers write");
347 let layers: &mut Vec<_> = guard.as_mut();
348 let new_layers =
349 vec![Layer::default(); texture.texture.size().depth_or_array_layers as usize];
350 let _old_layers = std::mem::replace(layers, new_layers);
351 }
352 self.add_images(images)
353 }
354
355 pub fn get_size(&self) -> wgpu::Extent3d {
356 self.texture_array
358 .read()
359 .expect("atlas texture_array read")
360 .texture
361 .size()
362 }
363
364 pub fn add_images<'a>(
366 &self,
367 images: impl IntoIterator<Item = &'a AtlasImage>,
368 ) -> Result<Vec<AtlasTexture>, AtlasError> {
369 let mut layers = self.layers.write().expect("atlas layers write");
371 let mut texture_array = self
372 .texture_array
373 .write()
374 .expect("atlas texture_array write");
375 let extent = texture_array.texture.size();
376
377 let newly_packed_layers = pack_images(&layers, images, extent)
378 .context(CannotPackTexturesSnafu { size: extent })?;
379
380 let mut staged = StagedResources::try_staging(
381 self.slab.runtime(),
382 extent,
383 newly_packed_layers,
384 Some(&self.slab),
385 &texture_array,
386 self.label.as_deref(),
387 )?;
388
389 *texture_array = staged.texture;
391 *layers = staged.layers;
392
393 staged.image_additions.sort_by_key(|a| a.0);
394 Ok(staged
395 .image_additions
396 .into_iter()
397 .map(|a| AtlasTexture { descriptor: a.1 })
398 .collect())
399 }
400
401 pub fn add_image(&self, image: &AtlasImage) -> Result<AtlasTexture, AtlasError> {
407 Ok(self.add_images(Some(image))?.pop().unwrap())
409 }
410
411 pub fn resize(
420 &self,
421 runtime: impl AsRef<WgpuRuntime>,
422 extent: wgpu::Extent3d,
423 ) -> Result<(), AtlasError> {
424 let mut layers = self.layers.write().expect("atlas layers write");
425 let mut texture_array = self
426 .texture_array
427 .write()
428 .expect("atlas texture_array write");
429
430 let newly_packed_layers =
431 pack_images(&layers, &[], extent).context(CannotPackTexturesSnafu { size: extent })?;
432
433 let staged = StagedResources::try_staging(
434 runtime,
435 extent,
436 newly_packed_layers,
437 None::<&SlabAllocator<WgpuRuntime>>,
438 &texture_array,
439 self.label.as_deref(),
440 )?;
441
442 *texture_array = staged.texture;
444 *layers = staged.layers;
445
446 Ok(())
447 }
448
449 #[must_use]
456 pub fn upkeep(&self, runtime: impl AsRef<WgpuRuntime>) -> bool {
457 let mut total_dropped = 0;
458 {
459 let mut layers = self.layers.write().expect("atlas layers write");
460 for (i, layer) in layers.iter_mut().enumerate() {
461 let mut dropped = 0;
462 layer.frames.retain(|entry| {
463 if entry.has_external_references() {
464 true
465 } else {
466 dropped += 1;
467 false
468 }
469 });
470 total_dropped += dropped;
471 if dropped > 0 {
472 log::trace!("removed {dropped} frames from layer {i}");
473 }
474 }
475
476 layers.len()
477 };
478
479 if total_dropped > 0 {
480 log::trace!("repacking after dropping {total_dropped} frames from the atlas");
481 self.resize(runtime.as_ref(), self.get_size()).unwrap();
484 true
485 } else {
486 false
487 }
488 }
489
490 pub fn atlas_img_buffer(
497 &self,
498 runtime: impl AsRef<WgpuRuntime>,
499 layer: u32,
500 ) -> CopiedTextureBuffer {
501 let runtime = runtime.as_ref();
502 let tex = self.get_texture();
503 let size = tex.texture.size();
504 let (channels, subpixel_bytes) =
505 crate::texture::wgpu_texture_format_channels_and_subpixel_bytes_todo(
506 tex.texture.format(),
507 );
508 log::info!("atlas_texture_format: {:#?}", tex.texture.format());
509 log::info!("atlas_texture_channels: {channels:#?}");
510 log::info!("atlas_texture_subpixel_bytes: {subpixel_bytes:#?}");
511 CopiedTextureBuffer::read_from(
512 runtime,
513 &tex.texture,
514 size.width as usize,
515 size.height as usize,
516 channels as usize,
517 subpixel_bytes as usize,
518 0,
519 Some(wgpu::Origin3d {
520 x: 0,
521 y: 0,
522 z: layer,
523 }),
524 )
525 }
526
527 pub async fn atlas_img(&self, runtime: impl AsRef<WgpuRuntime>, layer: u32) -> RgbaImage {
537 let runtime = runtime.as_ref();
538 let buffer = self.atlas_img_buffer(runtime, layer);
539 buffer.into_linear_rgba(&runtime.device).await.unwrap()
540 }
541
542 #[allow(clippy::await_holding_lock)]
544 pub async fn read_images(&self, runtime: impl AsRef<WgpuRuntime>) -> Vec<RgbaImage> {
545 let mut images = vec![];
546 for i in 0..self.layers.read().expect("atlas layers read").len() {
547 images.push(self.atlas_img(runtime.as_ref(), i as u32).await);
548 }
549 images
550 }
551
552 pub fn update_texture(
561 &self,
562 atlas_texture: &AtlasTexture,
563 source_texture: &texture::Texture,
564 ) -> Result<wgpu::SubmissionIndex, AtlasError> {
565 self.update_textures(Some((atlas_texture, source_texture)))
566 }
567
568 pub fn update_textures<'a>(
577 &self,
578 updates: impl IntoIterator<Item = (&'a AtlasTexture, &'a texture::Texture)>,
579 ) -> Result<wgpu::SubmissionIndex, AtlasError> {
580 let updates = updates.into_iter().collect::<Vec<_>>();
581 let op = AtlasBlittingOperation::new(&self.blitter, self, updates.len());
582 let runtime = self.slab.runtime();
583 let mut encoder = runtime
584 .device
585 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
586 label: Some("Atlas::update_texture"),
587 });
588 for (i, (atlas_texture, source_texture)) in updates.into_iter().enumerate() {
589 op.run(
590 runtime,
591 &mut encoder,
592 source_texture,
593 i as u32,
594 self,
595 atlas_texture,
596 )?;
597 }
598 Ok(runtime.queue.submit(Some(encoder.finish())))
599 }
600
601 pub fn update_images<'a>(
609 &self,
610 updates: impl IntoIterator<Item = (&'a AtlasTexture, impl Into<AtlasImage>)>,
611 ) -> Result<wgpu::SubmissionIndex, AtlasError> {
612 let (atlas_textures, images): (Vec<_>, Vec<_>) = updates.into_iter().unzip();
613 let mut textures = vec![];
614 for image in images.into_iter() {
615 let image: AtlasImage = image.into();
616 let atlas_format = self.get_texture().texture.format();
617 let bytes = super::atlas_image::convert_pixels(
618 image.pixels,
619 image.format,
620 atlas_format,
621 image.apply_linear_transfer,
622 );
623 let (channels, subpixel_bytes) =
624 texture::wgpu_texture_format_channels_and_subpixel_bytes(atlas_format)
625 .context(TextureSnafu)?;
626 let texture = texture::Texture::new_with(
627 self.slab.runtime(),
628 Some("atlas-image-update"),
629 Some(wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST),
630 None,
631 atlas_format,
632 channels,
633 subpixel_bytes,
634 image.size.x,
635 image.size.y,
636 1,
637 &bytes,
638 );
639 textures.push(texture);
640 }
641 self.update_textures(atlas_textures.into_iter().zip(textures.iter()))
642 }
643
644 pub fn update_image(
652 &self,
653 atlas_texture: &AtlasTexture,
654 source_image: impl Into<AtlasImage>,
655 ) -> Result<wgpu::SubmissionIndex, AtlasError> {
656 self.update_images(Some((atlas_texture, source_image)))
657 }
658}
659
660fn pack_images<'a>(
661 layers: &[Layer],
662 images: impl IntoIterator<Item = &'a AtlasImage>,
663 extent: wgpu::Extent3d,
664) -> Option<Vec<crunch::PackedItems<AnotherPacking<'a>>>> {
665 let mut new_packing: Vec<AnotherPacking> = {
666 let layers: Vec<_> = layers.to_vec();
667 layers
668 .into_iter()
669 .flat_map(|layer| layer.frames)
670 .filter_map(|tex| {
673 if tex.has_external_references() {
674 Some(AnotherPacking::Internal(tex))
675 } else {
676 None
677 }
678 })
679 .chain(
680 images
681 .into_iter()
682 .enumerate()
683 .map(|(i, image)| AnotherPacking::Img {
684 original_index: i,
685 image,
686 }),
687 )
688 .collect()
689 };
690 new_packing.sort_by_key(|a| a.size().length_squared());
691 let total_images = new_packing.len();
692 let new_packing_layers: Vec<Vec<AnotherPacking>> =
693 fan_split_n(extent.depth_or_array_layers as usize, new_packing);
694 log::trace!(
695 "packing {total_images} textures into {} layers",
696 new_packing_layers.len()
697 );
698 let mut newly_packed_layers: Vec<crunch::PackedItems<_>> = vec![];
699 for (i, new_layer) in new_packing_layers.into_iter().enumerate() {
700 log::trace!(" packing layer {i} into power of 2 {}", extent.width);
701 let packed = crunch::pack_into_po2(
702 extent.width as usize,
703 new_layer.into_iter().map(|p| {
704 let size = p.size();
705 crunch::Item::new(p, size.x as usize, size.y as usize, crunch::Rotation::None)
706 }),
707 )
708 .ok()?;
709 log::trace!(" layer {i} packed with {} textures", packed.items.len());
710 newly_packed_layers.push(packed);
711 }
712 Some(newly_packed_layers)
713}
714
715struct StagedResources {
717 texture: Texture,
718 image_additions: Vec<(usize, Hybrid<AtlasTextureDescriptor>)>,
719 layers: Vec<Layer>,
720}
721
722impl StagedResources {
723 fn try_staging(
725 runtime: impl AsRef<WgpuRuntime>,
726 extent: wgpu::Extent3d,
727 newly_packed_layers: Vec<crunch::PackedItems<AnotherPacking>>,
728 slab: Option<&SlabAllocator<WgpuRuntime>>,
729 old_texture_array: &Texture,
730 label: Option<&str>,
731 ) -> Result<Self, AtlasError> {
732 let runtime = runtime.as_ref();
733 let new_texture_array = Atlas::create_texture(
734 runtime,
735 extent,
736 Some(old_texture_array.texture.format()),
737 label,
738 Some(old_texture_array.texture.usage()),
739 );
740 let mut output = vec![];
741 let mut encoder = runtime
742 .device
743 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
744 label: Some("atlas staging"),
745 });
746 let mut temporary_layers = vec![Layer::default(); extent.depth_or_array_layers as usize];
747 for (layer_index, packed_items) in newly_packed_layers.into_iter().enumerate() {
748 if packed_items.items.is_empty() {
749 continue;
750 }
751 let layer = temporary_layers.get_mut(layer_index).unwrap();
754 for (frame_index, crunch::PackedItem { data: item, rect }) in
755 packed_items.items.into_iter().enumerate()
756 {
757 let offset_px = UVec2::new(rect.x as u32, rect.y as u32);
758 let size_px = UVec2::new(rect.w as u32, rect.h as u32);
759
760 match item {
761 AnotherPacking::Img {
762 original_index,
763 image,
764 } => {
765 let atlas_texture = AtlasTextureDescriptor {
766 offset_px,
767 size_px,
768 frame_index: frame_index as u32,
769 layer_index: layer_index as u32,
770 ..Default::default()
771 };
772 let texture = slab
773 .context(StagingMissingSlabSnafu)?
774 .new_value(atlas_texture);
775 layer
776 .frames
777 .push(InternalAtlasTexture::from_hybrid(&texture));
778 output.push((original_index, texture));
779
780 let bytes = convert_pixels(
781 image.pixels.clone(),
782 image.format,
783 old_texture_array.texture.format(),
784 image.apply_linear_transfer,
785 );
786
787 let origin = wgpu::Origin3d {
788 x: offset_px.x,
789 y: offset_px.y,
790 z: layer_index as u32,
791 };
792 let size = wgpu::Extent3d {
793 width: size_px.x,
794 height: size_px.y,
795 depth_or_array_layers: 1,
796 };
797 log::trace!(
798 " writing image data to frame {frame_index} in layer {layer_index}"
799 );
800 log::trace!(" frame: {atlas_texture:?}");
801 log::trace!(" origin: {origin:?}");
802 log::trace!(" size: {size:?}");
803
804 runtime.queue.write_texture(
806 wgpu::TexelCopyTextureInfo {
807 texture: &new_texture_array.texture,
808 mip_level: 0,
809 origin,
810 aspect: wgpu::TextureAspect::All,
811 },
812 &bytes,
813 wgpu::TexelCopyBufferLayout {
814 offset: 0,
815 bytes_per_row: Some(4 * size_px.x),
816 rows_per_image: Some(size_px.y),
817 },
818 size,
819 );
820 }
821 AnotherPacking::Internal(mut texture) => {
822 let prev_t = texture.cache;
823 let mut t = texture.cache;
824 debug_assert_eq!(t.size_px, size_px);
825 encoder.copy_texture_to_texture(
828 wgpu::TexelCopyTextureInfo {
829 texture: &old_texture_array.texture,
830 mip_level: 0,
831 origin: t.origin(),
832 aspect: wgpu::TextureAspect::All,
833 },
834 wgpu::TexelCopyTextureInfo {
835 texture: &new_texture_array.texture,
836 mip_level: 0,
837 origin: wgpu::Origin3d {
838 x: offset_px.x,
839 y: offset_px.y,
840 z: layer_index as u32,
841 },
842 aspect: wgpu::TextureAspect::All,
843 },
844 t.size_as_extent(),
845 );
846
847 t.layer_index = layer_index as u32;
848 t.frame_index = frame_index as u32;
849 t.offset_px = offset_px;
850
851 log::trace!(
852 " copied previous frame {}",
853 pretty_assertions::Comparison::new(&prev_t, &t)
854 );
855
856 texture.set(t);
857 layer.frames.push(texture);
858 }
859 }
860 }
861 }
862 runtime.queue.submit(Some(encoder.finish()));
863
864 Ok(Self {
865 texture: new_texture_array,
866 image_additions: output,
867 layers: temporary_layers,
868 })
869 }
870}
871
872#[derive(Clone)]
875pub struct AtlasBlittingOperation {
876 atlas_slab_buffer: Arc<Mutex<SlabBuffer<wgpu::Buffer>>>,
877 pipeline: Arc<wgpu::RenderPipeline>,
878 bindgroups: Arc<Vec<ManagedBindGroup>>,
879 bindgroup_layout: Arc<wgpu::BindGroupLayout>,
880 sampler: Arc<wgpu::Sampler>,
881 from_texture_id: Arc<AtomicUsize>,
882 pub(crate) desc: Hybrid<AtlasBlittingDescriptor>,
883}
884
885impl AtlasBlittingOperation {
886 pub fn new(
887 blitter: &AtlasBlitter,
888 into_atlas: &Atlas,
889 source_layers: usize,
890 ) -> AtlasBlittingOperation {
891 AtlasBlittingOperation {
892 desc: into_atlas
893 .slab
894 .new_value(AtlasBlittingDescriptor::default()),
895 atlas_slab_buffer: Arc::new(Mutex::new(into_atlas.slab.commit())),
896 bindgroups: {
897 let mut bgs = vec![];
898 for _ in 0..source_layers {
899 bgs.push(ManagedBindGroup::default());
900 }
901 Arc::new(bgs)
902 },
903 pipeline: blitter.pipeline.clone(),
904 sampler: blitter.sampler.clone(),
905 bindgroup_layout: blitter.bind_group_layout.clone(),
906 from_texture_id: Default::default(),
907 }
908 }
909
910 pub fn run(
917 &self,
918 runtime: impl AsRef<WgpuRuntime>,
919 encoder: &mut wgpu::CommandEncoder,
920 from_texture: &crate::texture::Texture,
921 from_layer: u32,
922 to_atlas: &Atlas,
923 atlas_texture: &AtlasTexture,
924 ) -> Result<(), AtlasError> {
925 let runtime = runtime.as_ref();
926
927 self.desc.set(AtlasBlittingDescriptor {
929 atlas_texture_id: atlas_texture.id(),
930 atlas_desc_id: to_atlas.descriptor_id(),
931 });
932 let _ = to_atlas.slab.commit();
934
935 let to_atlas_texture = to_atlas.get_texture();
936 let mut atlas_slab_buffer = self
937 .atlas_slab_buffer
938 .lock()
939 .expect("atlas slab buffer lock");
940 let atlas_slab_invalid = atlas_slab_buffer.update_if_invalid();
941 let from_texture_has_been_replaced = {
942 let prev_id = self
943 .from_texture_id
944 .swap(from_texture.id(), std::sync::atomic::Ordering::Relaxed);
945 from_texture.id() != prev_id
946 };
947 let should_invalidate = atlas_slab_invalid || from_texture_has_been_replaced;
948 let view = from_texture
949 .texture
950 .create_view(&wgpu::TextureViewDescriptor {
951 label: Some("atlas-blitting"),
952 base_array_layer: from_layer,
953 array_layer_count: Some(1),
954 dimension: Some(wgpu::TextureViewDimension::D2),
955 ..Default::default()
956 });
957 let bindgroup = self
958 .bindgroups
959 .get(from_layer as usize)
960 .context(MissingBindgroupSnafu { layer: from_layer })?
961 .get(should_invalidate, || {
962 runtime
963 .device
964 .create_bind_group(&wgpu::BindGroupDescriptor {
965 label: Some("atlas-blitting"),
966 layout: &self.bindgroup_layout,
967 entries: &[
968 wgpu::BindGroupEntry {
969 binding: 0,
970 resource: wgpu::BindingResource::Buffer(
971 atlas_slab_buffer.deref().as_entire_buffer_binding(),
972 ),
973 },
974 wgpu::BindGroupEntry {
975 binding: 1,
976 resource: wgpu::BindingResource::TextureView(&view),
977 },
978 wgpu::BindGroupEntry {
979 binding: 2,
980 resource: wgpu::BindingResource::Sampler(&self.sampler),
981 },
982 ],
983 })
984 });
985
986 let atlas_texture = atlas_texture.descriptor();
987 let atlas_view = to_atlas_texture
988 .texture
989 .create_view(&wgpu::TextureViewDescriptor {
990 label: Some("atlas-blitting"),
991 format: None,
992 dimension: Some(wgpu::TextureViewDimension::D2),
993 usage: None,
994 aspect: wgpu::TextureAspect::All,
995 base_mip_level: 0,
996 mip_level_count: None,
997 base_array_layer: atlas_texture.layer_index,
998 array_layer_count: Some(1),
999 });
1000 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1001 label: Some("atlas-blitter"),
1002 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1003 view: &atlas_view,
1004 resolve_target: None,
1005 ops: wgpu::Operations {
1006 load: wgpu::LoadOp::Load,
1007 store: wgpu::StoreOp::Store,
1008 },
1009 depth_slice: None,
1010 })],
1011 depth_stencil_attachment: None,
1012 timestamp_writes: None,
1013 occlusion_query_set: None,
1014 });
1015 pass.set_pipeline(&self.pipeline);
1016 pass.set_bind_group(0, Some(bindgroup.as_ref()), &[]);
1017 let id = self.desc.id();
1018 pass.draw(0..6, id.inner()..id.inner() + 1);
1019 Ok(())
1020 }
1021}
1022
1023#[derive(Clone)]
1028pub struct AtlasBlitter {
1029 pipeline: Arc<wgpu::RenderPipeline>,
1030 bind_group_layout: Arc<wgpu::BindGroupLayout>,
1031 sampler: Arc<wgpu::Sampler>,
1032}
1033
1034impl AtlasBlitter {
1035 pub fn new(
1043 device: &wgpu::Device,
1044 format: wgpu::TextureFormat,
1045 mag_filter: wgpu::FilterMode,
1046 ) -> Self {
1047 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1048 label: Some("atlas-blitter"),
1049 address_mode_u: wgpu::AddressMode::ClampToEdge,
1050 address_mode_v: wgpu::AddressMode::ClampToEdge,
1051 address_mode_w: wgpu::AddressMode::ClampToEdge,
1052 mag_filter,
1053 ..Default::default()
1054 });
1055
1056 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1057 label: Some("atlas-blitter"),
1058 entries: &[
1059 wgpu::BindGroupLayoutEntry {
1060 binding: 0,
1061 visibility: wgpu::ShaderStages::VERTEX,
1062 ty: wgpu::BindingType::Buffer {
1063 ty: wgpu::BufferBindingType::Storage { read_only: true },
1064 has_dynamic_offset: false,
1065 min_binding_size: None,
1066 },
1067 count: None,
1068 },
1069 wgpu::BindGroupLayoutEntry {
1070 binding: 1,
1071 visibility: wgpu::ShaderStages::FRAGMENT,
1072 ty: wgpu::BindingType::Texture {
1073 sample_type: wgpu::TextureSampleType::Float {
1074 filterable: mag_filter == wgpu::FilterMode::Linear,
1075 },
1076 view_dimension: wgpu::TextureViewDimension::D2,
1077 multisampled: false,
1078 },
1079 count: None,
1080 },
1081 wgpu::BindGroupLayoutEntry {
1082 binding: 2,
1083 visibility: wgpu::ShaderStages::FRAGMENT,
1084 ty: wgpu::BindingType::Sampler(
1085 if mag_filter == wgpu::FilterMode::Linear {
1086 wgpu::SamplerBindingType::Filtering
1087 } else {
1088 wgpu::SamplerBindingType::NonFiltering
1089 },
1090 ),
1091 count: None,
1092 },
1093 ],
1094 });
1095
1096 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1097 label: Some("atlas-blitter"),
1098 bind_group_layouts: &[&bind_group_layout],
1099 push_constant_ranges: &[],
1100 });
1101
1102 let vertex = crate::linkage::atlas_blit_vertex::linkage(device);
1103 let fragment = crate::linkage::atlas_blit_fragment::linkage(device);
1104 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1105 label: Some("atlas-blitter"),
1106 layout: Some(&pipeline_layout),
1107 vertex: wgpu::VertexState {
1108 module: &vertex.module,
1109 entry_point: Some(vertex.entry_point),
1110 compilation_options: wgpu::PipelineCompilationOptions::default(),
1111 buffers: &[],
1112 },
1113 primitive: wgpu::PrimitiveState {
1114 topology: wgpu::PrimitiveTopology::TriangleList,
1115 strip_index_format: None,
1116 front_face: wgpu::FrontFace::Ccw,
1117 cull_mode: None,
1118 unclipped_depth: false,
1119 polygon_mode: wgpu::PolygonMode::Fill,
1120 conservative: false,
1121 },
1122 depth_stencil: None,
1123 multisample: wgpu::MultisampleState::default(),
1124 fragment: Some(wgpu::FragmentState {
1125 module: &fragment.module,
1126 entry_point: Some(fragment.entry_point),
1127 compilation_options: wgpu::PipelineCompilationOptions::default(),
1128 targets: &[Some(wgpu::ColorTargetState {
1129 format,
1130 blend: None,
1131 write_mask: wgpu::ColorWrites::ALL,
1132 })],
1133 }),
1134 multiview: None,
1135 cache: None,
1136 });
1137
1138 Self {
1139 pipeline: pipeline.into(),
1140 bind_group_layout: bind_group_layout.into(),
1141 sampler: sampler.into(),
1142 }
1143 }
1144}
1145
1146#[cfg(test)]
1147mod test {
1148 use crate::{
1149 atlas::{shader::AtlasTextureDescriptor, TextureAddressMode},
1150 context::Context,
1151 geometry::Vertex,
1152 material::Materials,
1153 test::BlockOnFuture,
1154 };
1155 use glam::{UVec3, Vec2, Vec3, Vec4};
1156
1157 use super::*;
1158
1159 #[test]
1160 fn atlas_uv_mapping() {
1162 log::info!("{:?}", std::env::current_dir());
1163 let ctx = Context::headless(32, 32)
1164 .block()
1165 .with_default_atlas_texture_size(UVec3::new(1024, 1024, 2));
1166 let stage = ctx
1167 .new_stage()
1168 .with_background_color(Vec3::splat(0.0).extend(1.0))
1169 .with_bloom(false);
1170 let (projection, view) = crate::camera::default_ortho2d(32.0, 32.0);
1171 let _camera = stage
1172 .new_camera()
1173 .with_projection_and_view(projection, view);
1174 let dirt = AtlasImage::from_path("../../img/dirt.jpg").unwrap();
1175 let sandstone = AtlasImage::from_path("../../img/sandstone.png").unwrap();
1176 let texels = AtlasImage::from_path("../../test_img/atlas/uv_mapping.png").unwrap();
1177 log::info!("setting images");
1178 let atlas_entries = stage.set_images([dirt, sandstone, texels]).unwrap();
1179 log::info!(" done setting images");
1180
1181 let texels_entry = &atlas_entries[2];
1182
1183 let _rez = stage
1184 .new_primitive()
1185 .with_material(
1186 stage
1187 .new_material()
1188 .with_albedo_texture(texels_entry)
1189 .with_has_lighting(false),
1190 )
1191 .with_vertices(stage.new_vertices({
1192 let tl = Vertex::default()
1193 .with_position(Vec3::ZERO)
1194 .with_uv0(Vec2::ZERO);
1195 let tr = Vertex::default()
1196 .with_position(Vec3::new(1.0, 0.0, 0.0))
1197 .with_uv0(Vec2::new(1.0, 0.0));
1198 let bl = Vertex::default()
1199 .with_position(Vec3::new(0.0, 1.0, 0.0))
1200 .with_uv0(Vec2::new(0.0, 1.0));
1201 let br = Vertex::default()
1202 .with_position(Vec3::new(1.0, 1.0, 0.0))
1203 .with_uv0(Vec2::splat(1.0));
1204 [tl, bl, br, tl, br, tr]
1205 }))
1206 .with_transform(stage.new_transform().with_scale(Vec3::new(32.0, 32.0, 1.0)));
1207
1208 log::info!("rendering");
1209 let frame = ctx.get_next_frame().unwrap();
1210 stage.render(&frame.view());
1211 let img = frame.read_image().block().unwrap();
1212 img_diff::assert_img_eq("atlas/uv_mapping.png", img);
1213 }
1214
1215 #[test]
1216 fn uv_wrapping() {
1219 let icon_w = 32;
1220 let icon_h = 41;
1221 let sheet_w = icon_w * 3;
1222 let sheet_h = icon_h * 3;
1223 let w = sheet_w * 3 + 2;
1224 let h = sheet_h;
1225 let ctx = Context::headless(w, h).block();
1226 let stage = ctx
1227 .new_stage()
1228 .with_background_color(Vec4::new(1.0, 1.0, 0.0, 1.0));
1229 let (projection, view) = crate::camera::default_ortho2d(w as f32, h as f32);
1230 let _camera = stage
1231 .new_camera()
1232 .with_projection_and_view(projection, view);
1233 let texels = AtlasImage::from_path("../../img/happy_mac.png").unwrap();
1234 let entries = stage.set_images(std::iter::repeat_n(texels, 3)).unwrap();
1235 let clamp_tex = &entries[0];
1236 let repeat_tex = &entries[1];
1237 repeat_tex.set_modes(TextureModes {
1238 s: TextureAddressMode::Repeat,
1239 t: TextureAddressMode::Repeat,
1240 });
1241 let mirror_tex = &entries[2];
1242 mirror_tex.set_modes(TextureModes {
1243 s: TextureAddressMode::MirroredRepeat,
1244 t: TextureAddressMode::MirroredRepeat,
1245 });
1246
1247 let sheet_w = sheet_w as f32;
1248 let sheet_h = sheet_h as f32;
1249 let geometry = stage.new_vertices({
1250 let tl = Vertex::default()
1251 .with_position(Vec3::ZERO)
1252 .with_uv0(Vec2::ZERO);
1253 let tr = Vertex::default()
1254 .with_position(Vec3::new(sheet_w, 0.0, 0.0))
1255 .with_uv0(Vec2::new(3.0, 0.0));
1256 let bl = Vertex::default()
1257 .with_position(Vec3::new(0.0, sheet_h, 0.0))
1258 .with_uv0(Vec2::new(0.0, 3.0));
1259 let br = Vertex::default()
1260 .with_position(Vec3::new(sheet_w, sheet_h, 0.0))
1261 .with_uv0(Vec2::splat(3.0));
1262 [tl, bl, br, tl, br, tr]
1263 });
1264 let _clamp_rez = stage
1265 .new_primitive()
1266 .with_vertices(&geometry)
1267 .with_material(
1268 stage
1269 .new_material()
1270 .with_albedo_texture(clamp_tex)
1271 .with_has_lighting(false),
1272 );
1273
1274 let _repeat_rez = stage
1275 .new_primitive()
1276 .with_transform(stage.new_transform().with_translation(Vec3::new(
1277 sheet_w + 1.0,
1278 0.0,
1279 0.0,
1280 )))
1281 .with_material(
1282 stage
1283 .new_material()
1284 .with_albedo_texture(repeat_tex)
1285 .with_has_lighting(false),
1286 )
1287 .with_vertices(&geometry);
1288
1289 let _mirror_rez = stage
1290 .new_primitive()
1291 .with_transform(stage.new_transform().with_translation(Vec3::new(
1292 sheet_w * 2.0 + 2.0,
1293 0.0,
1294 0.0,
1295 )))
1296 .with_material(
1297 stage
1298 .new_material()
1299 .with_albedo_texture(mirror_tex)
1300 .with_has_lighting(false),
1301 )
1302 .with_vertices(geometry);
1303
1304 let frame = ctx.get_next_frame().unwrap();
1305 stage.render(&frame.view());
1306 let img = frame.read_image().block().unwrap();
1307 img_diff::assert_img_eq("atlas/uv_wrapping.png", img);
1308 }
1309
1310 #[test]
1311 fn negative_uv_wrapping() {
1313 let icon_w = 32;
1314 let icon_h = 41;
1315 let sheet_w = icon_w * 3;
1316 let sheet_h = icon_h * 3;
1317 let w = sheet_w * 3 + 2;
1318 let h = sheet_h;
1319 let ctx = Context::headless(w, h).block();
1320 let stage = ctx
1321 .new_stage()
1322 .with_background_color(Vec4::new(1.0, 1.0, 0.0, 1.0));
1323
1324 let (projection, view) = crate::camera::default_ortho2d(w as f32, h as f32);
1325 let _camera = stage
1326 .new_camera()
1327 .with_projection_and_view(projection, view);
1328
1329 let texels = AtlasImage::from_path("../../img/happy_mac.png").unwrap();
1330 let entries = stage.set_images(std::iter::repeat_n(texels, 3)).unwrap();
1331
1332 let clamp_tex = &entries[0];
1333 let repeat_tex = &entries[1];
1334 repeat_tex.set_modes(TextureModes {
1335 s: TextureAddressMode::Repeat,
1336 t: TextureAddressMode::Repeat,
1337 });
1338
1339 let mirror_tex = &entries[2];
1340 mirror_tex.set_modes(TextureModes {
1341 s: TextureAddressMode::MirroredRepeat,
1342 t: TextureAddressMode::MirroredRepeat,
1343 });
1344
1345 let sheet_w = sheet_w as f32;
1346 let sheet_h = sheet_h as f32;
1347 let geometry = stage.new_vertices({
1348 let tl = Vertex::default()
1349 .with_position(Vec3::ZERO)
1350 .with_uv0(Vec2::ZERO);
1351 let tr = Vertex::default()
1352 .with_position(Vec3::new(sheet_w, 0.0, 0.0))
1353 .with_uv0(Vec2::new(-3.0, 0.0));
1354 let bl = Vertex::default()
1355 .with_position(Vec3::new(0.0, sheet_h, 0.0))
1356 .with_uv0(Vec2::new(0.0, -3.0));
1357 let br = Vertex::default()
1358 .with_position(Vec3::new(sheet_w, sheet_h, 0.0))
1359 .with_uv0(Vec2::splat(-3.0));
1360 [tl, bl, br, tl, br, tr]
1361 });
1362 let _clamp_prim = stage
1363 .new_primitive()
1364 .with_vertices(&geometry)
1365 .with_material(
1366 stage
1367 .new_material()
1368 .with_albedo_texture(clamp_tex)
1369 .with_has_lighting(false),
1370 );
1371
1372 let _repeat_rez = stage
1373 .new_primitive()
1374 .with_material(
1375 stage
1376 .new_material()
1377 .with_albedo_texture(repeat_tex)
1378 .with_has_lighting(false),
1379 )
1380 .with_transform(stage.new_transform().with_translation(Vec3::new(
1381 sheet_w + 1.0,
1382 0.0,
1383 0.0,
1384 )))
1385 .with_vertices(&geometry);
1386
1387 let _mirror_rez = stage
1388 .new_primitive()
1389 .with_material(
1390 stage
1391 .new_material()
1392 .with_albedo_texture(mirror_tex)
1393 .with_has_lighting(false),
1394 )
1395 .with_transform(stage.new_transform().with_translation(Vec3::new(
1396 sheet_w * 2.0 + 2.0,
1397 0.0,
1398 0.0,
1399 )))
1400 .with_vertices(&geometry);
1401
1402 let frame = ctx.get_next_frame().unwrap();
1403 stage.render(&frame.view());
1404 let img = frame.read_image().block().unwrap();
1405 img_diff::assert_img_eq("atlas/negative_uv_wrapping.png", img);
1406 }
1407
1408 #[test]
1409 fn transform_uvs_for_atlas() {
1410 let mut tex = AtlasTextureDescriptor {
1411 offset_px: UVec2::ZERO,
1412 size_px: UVec2::ONE,
1413 ..Default::default()
1414 };
1415 assert_eq!(Vec3::ZERO, tex.uv(Vec2::ZERO, UVec2::splat(100)));
1416 assert_eq!(Vec3::ZERO, tex.uv(Vec2::ZERO, UVec2::splat(1)));
1417 assert_eq!(Vec3::ZERO, tex.uv(Vec2::ZERO, UVec2::splat(256)));
1418 tex.offset_px = UVec2::splat(10);
1419 assert_eq!(
1420 Vec2::splat(0.1).extend(0.0),
1421 tex.uv(Vec2::ZERO, UVec2::splat(100))
1422 );
1423 }
1424
1425 #[test]
1426 fn can_load_and_read_atlas_texture_array() {
1427 let ctx = Context::headless(100, 100)
1429 .block()
1430 .with_default_atlas_texture_size(UVec3::new(512, 512, 2));
1431 let stage = ctx.new_stage();
1432 let dirt = AtlasImage::from_path("../../img/dirt.jpg").unwrap();
1433 let sandstone = AtlasImage::from_path("../../img/sandstone.png").unwrap();
1434 let cheetah = AtlasImage::from_path("../../img/cheetah.jpg").unwrap();
1435 let texels = AtlasImage::from_path("../../img/happy_mac.png").unwrap();
1436 let _frames = stage
1437 .set_images([dirt, sandstone, cheetah, texels])
1438 .unwrap();
1439 let materials: &Materials = stage.as_ref();
1440 let img = materials.atlas().atlas_img(&ctx, 0).block();
1441 img_diff::assert_img_eq("atlas/array0.png", img);
1442 let img = materials.atlas().atlas_img(&ctx, 1).block();
1443 img_diff::assert_img_eq("atlas/array1.png", img);
1444 }
1445
1446 #[test]
1447 fn upkeep_trims_the_atlas() {
1448 let ctx = Context::headless(100, 100)
1451 .block()
1452 .with_default_atlas_texture_size(UVec3::new(512, 512, 2));
1453 let stage = ctx.new_stage();
1454 let dirt = AtlasImage::from_path("../../img/dirt.jpg").unwrap();
1455 let sandstone = AtlasImage::from_path("../../img/sandstone.png").unwrap();
1456 let cheetah = AtlasImage::from_path("../../img/cheetah.jpg").unwrap();
1457 let texels = AtlasImage::from_path("../../img/happy_mac.png").unwrap();
1458 let mut frames = stage
1459 .add_images([
1460 dirt,
1461 sandstone,
1462 cheetah,
1463 texels.clone(),
1464 texels.clone(),
1465 texels.clone(),
1466 texels.clone(),
1467 texels,
1468 ])
1469 .unwrap();
1470 let materials: &Materials = stage.as_ref();
1471 assert_eq!(8, materials.atlas().len());
1472
1473 frames.pop();
1474 frames.pop();
1475 frames.pop();
1476 frames.pop();
1477
1478 let _ = materials.atlas().upkeep(&ctx);
1479 assert_eq!(4, materials.atlas().len());
1480 }
1481}