Skip to content

Commit a86e60f

Browse files
committed
Atlas: update images in place
1 parent 1cdeeb9 commit a86e60f

3 files changed

Lines changed: 176 additions & 40 deletions

File tree

crates/renderling/src/atlas/atlas_image.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,26 @@ pub enum AtlasImageFormat {
4646
D32FLOAT,
4747
}
4848

49+
impl From<AtlasImageFormat> for wgpu::TextureFormat {
50+
fn from(value: AtlasImageFormat) -> Self {
51+
match value {
52+
AtlasImageFormat::R8 => wgpu::TextureFormat::R8Unorm,
53+
AtlasImageFormat::R8G8 => wgpu::TextureFormat::Rg8Unorm,
54+
AtlasImageFormat::R8G8B8 => wgpu::TextureFormat::Rgba8Unorm, // No direct 3-channel format, using 4-channel
55+
AtlasImageFormat::R8G8B8A8 => wgpu::TextureFormat::Rgba8Unorm,
56+
AtlasImageFormat::R16 => wgpu::TextureFormat::R16Unorm,
57+
AtlasImageFormat::R16G16 => wgpu::TextureFormat::Rg16Unorm,
58+
AtlasImageFormat::R16G16B16 => wgpu::TextureFormat::Rgba16Unorm, // No direct 3-channel format, using 4-channel
59+
AtlasImageFormat::R16G16B16A16 => wgpu::TextureFormat::Rgba16Unorm,
60+
AtlasImageFormat::R16G16B16A16FLOAT => wgpu::TextureFormat::Rgba16Float,
61+
AtlasImageFormat::R32FLOAT => wgpu::TextureFormat::R32Float,
62+
AtlasImageFormat::R32G32B32FLOAT => wgpu::TextureFormat::Rgba32Float, // No direct 3-channel format, using 4-channel
63+
AtlasImageFormat::R32G32B32A32FLOAT => wgpu::TextureFormat::Rgba32Float,
64+
AtlasImageFormat::D32FLOAT => wgpu::TextureFormat::Depth32Float,
65+
}
66+
}
67+
}
68+
4969
impl AtlasImageFormat {
5070
pub fn from_wgpu_texture_format(value: wgpu::TextureFormat) -> Option<Self> {
5171
match value {

crates/renderling/src/atlas/cpu.rs

Lines changed: 151 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::{
1717
TextureModes,
1818
},
1919
bindgroup::ManagedBindGroup,
20-
texture::{CopiedTextureBuffer, Texture},
20+
texture::{self, CopiedTextureBuffer, Texture},
2121
};
2222

2323
use super::atlas_image::{convert_pixels, AtlasImage};
@@ -41,6 +41,11 @@ pub enum AtlasError {
4141

4242
#[snafu(display("Missing bindgroup {layer}"))]
4343
MissingBindgroup { layer: u32 },
44+
45+
#[snafu(display("{source}"))]
46+
Texture {
47+
source: crate::texture::TextureError,
48+
},
4449
}
4550

4651
/// A staged texture in the texture atlas.
@@ -200,6 +205,8 @@ pub struct Atlas {
200205
layers: Arc<RwLock<Vec<Layer>>>,
201206
label: Option<String>,
202207
descriptor: Hybrid<AtlasDescriptor>,
208+
/// Used for user updates into the atlas by blit images into specific frames.
209+
blitter: AtlasBlitter,
203210
}
204211

205212
impl Atlas {
@@ -286,13 +293,18 @@ impl Atlas {
286293
size: UVec3::new(size.width, size.height, size.depth_or_array_layers),
287294
});
288295
let label = label.map(|s| s.to_owned());
289-
296+
let blitter = AtlasBlitter::new(
297+
slab.device(),
298+
texture.texture.format(),
299+
wgpu::FilterMode::Linear,
300+
);
290301
Atlas {
291302
slab: slab.clone(),
292303
layers: Arc::new(RwLock::new(layers)),
293-
texture_array: Arc::new(RwLock::new(texture)),
294304
descriptor,
295305
label,
306+
blitter,
307+
texture_array: Arc::new(RwLock::new(texture)),
296308
}
297309
}
298310

@@ -523,6 +535,107 @@ impl Atlas {
523535
}
524536
images
525537
}
538+
539+
/// Update the given [`AtlasTexture`] with a [`Texture`](crate::texture::Texture).
540+
///
541+
/// This will blit the `Texture` into the frame of the [`Atlas`] pointed to by the
542+
/// `AtlasTexture`.
543+
///
544+
/// Returns a submission index that can be polled with [`wgpu::Device::poll`].
545+
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+
/// Update the given [`AtlasTexture`]s with [`Texture`](crate::texture::Texture)s.
554+
///
555+
/// This will blit the `Texture` into the frame of the [`Atlas`] pointed to by the
556+
/// `AtlasTexture`.
557+
///
558+
/// Returns a submission index that can be polled with [`wgpu::Device::poll`].
559+
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+
/// Update the given [`AtlasTexture`]s with new data.
585+
///
586+
/// This will blit the image data into the frame of the [`Atlas`] pointed to by the
587+
/// `AtlasTexture`.
588+
///
589+
/// Returns a submission index that can be polled with [`wgpu::Device::poll`].
590+
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+
/// Update the given [`AtlasTexture`]s with new data.
627+
///
628+
/// This will blit the image data into the frame of the [`Atlas`] pointed to by the
629+
/// `AtlasTexture`.
630+
///
631+
/// Returns a submission index that can be polled with [`wgpu::Device::poll`].
632+
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+
}
526639
}
527640

528641
fn pack_images<'a>(
@@ -750,6 +863,30 @@ pub struct AtlasBlittingOperation {
750863
}
751864

752865
impl 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+
753890
/// Copies the data from texture this [`AtlasBlittingOperation`] was created with
754891
/// into the atlas.
755892
///
@@ -760,7 +897,7 @@ impl AtlasBlittingOperation {
760897
runtime: impl AsRef<WgpuRuntime>,
761898
encoder: &mut wgpu::CommandEncoder,
762899
from_texture: &crate::texture::Texture,
763-
layer: u32,
900+
from_layer: u32,
764901
to_atlas: &Atlas,
765902
atlas_texture: &AtlasTexture,
766903
) -> Result<(), AtlasError> {
@@ -788,15 +925,15 @@ impl AtlasBlittingOperation {
788925
.texture
789926
.create_view(&wgpu::TextureViewDescriptor {
790927
label: Some("atlas-blitting"),
791-
base_array_layer: layer,
928+
base_array_layer: from_layer,
792929
array_layer_count: Some(1),
793930
dimension: Some(wgpu::TextureViewDimension::D2),
794931
..Default::default()
795932
});
796933
let bindgroup = self
797934
.bindgroups
798-
.get(layer as usize)
799-
.context(MissingBindgroupSnafu { layer })?
935+
.get(from_layer as usize)
936+
.context(MissingBindgroupSnafu { layer: from_layer })?
800937
.get(should_invalidate, || {
801938
runtime
802939
.device
@@ -874,19 +1011,20 @@ impl AtlasBlitter {
8741011
///
8751012
/// # Arguments
8761013
/// - `device` - A [`wgpu::Device`]
877-
/// - `format` - The [`wgpu::TextureFormat`] of the texture that will be copied to. This has to be renderable.
878-
/// - `sample_type` - The [`wgpu::Sampler`] Filtering Mode
1014+
/// - `format` - The [`wgpu::TextureFormat`] of the atlas being updated.
1015+
/// - `mag_filter` - The filtering algorithm to use when magnifying.
1016+
/// This is used when the input source is smaller than the destination.
8791017
pub fn new(
8801018
device: &wgpu::Device,
8811019
format: wgpu::TextureFormat,
882-
sample_type: wgpu::FilterMode,
1020+
mag_filter: wgpu::FilterMode,
8831021
) -> Self {
8841022
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
8851023
label: Some("atlas-blitter"),
8861024
address_mode_u: wgpu::AddressMode::ClampToEdge,
8871025
address_mode_v: wgpu::AddressMode::ClampToEdge,
8881026
address_mode_w: wgpu::AddressMode::ClampToEdge,
889-
mag_filter: sample_type,
1027+
mag_filter,
8901028
..Default::default()
8911029
});
8921030

@@ -908,7 +1046,7 @@ impl AtlasBlitter {
9081046
visibility: wgpu::ShaderStages::FRAGMENT,
9091047
ty: wgpu::BindingType::Texture {
9101048
sample_type: wgpu::TextureSampleType::Float {
911-
filterable: sample_type == wgpu::FilterMode::Linear,
1049+
filterable: mag_filter == wgpu::FilterMode::Linear,
9121050
},
9131051
view_dimension: wgpu::TextureViewDimension::D2,
9141052
multisampled: false,
@@ -918,7 +1056,7 @@ impl AtlasBlitter {
9181056
wgpu::BindGroupLayoutEntry {
9191057
binding: 2,
9201058
visibility: wgpu::ShaderStages::FRAGMENT,
921-
ty: wgpu::BindingType::Sampler(if sample_type == wgpu::FilterMode::Linear {
1059+
ty: wgpu::BindingType::Sampler(if mag_filter == wgpu::FilterMode::Linear {
9221060
wgpu::SamplerBindingType::Filtering
9231061
} else {
9241062
wgpu::SamplerBindingType::NonFiltering
@@ -976,30 +1114,6 @@ impl AtlasBlitter {
9761114
sampler: sampler.into(),
9771115
}
9781116
}
979-
980-
pub fn new_blitting_operation(
981-
&self,
982-
into_atlas: &Atlas,
983-
layers: usize,
984-
) -> AtlasBlittingOperation {
985-
AtlasBlittingOperation {
986-
desc: into_atlas
987-
.slab
988-
.new_value(AtlasBlittingDescriptor::default()),
989-
atlas_slab_buffer: Arc::new(Mutex::new(into_atlas.slab.commit())),
990-
bindgroups: {
991-
let mut bgs = vec![];
992-
for _ in 0..layers {
993-
bgs.push(ManagedBindGroup::default());
994-
}
995-
Arc::new(bgs)
996-
},
997-
pipeline: self.pipeline.clone(),
998-
sampler: self.sampler.clone(),
999-
bindgroup_layout: self.bind_group_layout.clone(),
1000-
from_texture_id: Default::default(),
1001-
}
1002-
}
10031117
}
10041118

10051119
#[cfg(test)]

crates/renderling/src/light/shadow_map.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,11 @@ impl ShadowMap {
204204
let atlas_textures_array = lighting
205205
.light_slab
206206
.new_array(atlas_textures.iter().map(|t| t.id()));
207-
let blitting_op = lighting
208-
.shadow_map_update_blitter
209-
.new_blitting_operation(atlas, if is_point_light { 6 } else { 1 });
207+
let blitting_op = AtlasBlittingOperation::new(
208+
&lighting.shadow_map_update_blitter,
209+
atlas,
210+
if is_point_light { 6 } else { 1 },
211+
);
210212
let light_space_transforms =
211213
lighting
212214
.light_slab

0 commit comments

Comments
 (0)