Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 5 additions & 11 deletions crates/example/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,16 @@ pub struct App {

impl App {
pub fn new(ctx: &Context, camera_control: CameraControl) -> Self {
// Use library defaults for atlas size and MSAA -- the Context
// auto-detects appropriate settings based on GPU capabilities.
let stage = ctx
.new_stage()
.with_background_color(DARK_BLUE_BG_COLOR)
.with_bloom_mix_strength(0.5)
.with_bloom_filter_radius(4.0)
.with_msaa_sample_count(4);
.with_bloom_filter_radius(4.0);
let size = ctx.get_size();
let (proj, view) = renderling::camera::default_perspective(size.x as f32, size.y as f32);
let (proj, view) =
renderling::camera::default_perspective(size.x as f32, size.y as f32);
let camera = stage.new_camera().with_projection_and_view(proj, view);

let sunlight = stage
Expand All @@ -152,14 +154,6 @@ impl App {
.with_color(renderling::math::hex_to_vec4(0xFDFBD3FF))
.with_intensity(Lux::OUTDOOR_SUNSET);

stage
.set_atlas_size(wgpu::Extent3d {
width: 2048,
height: 2048,
depth_or_array_layers: 32,
})
.unwrap();

let ui = Ui::new(ctx).with_background_color(Vec4::ZERO);
let _ = ui.add_font(FontArc::try_from_slice(FONT_BYTES).unwrap());
let fps_counter = FPSCounter::default();
Expand Down
103 changes: 96 additions & 7 deletions crates/renderling/src/atlas/cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@ pub struct Atlas {
descriptor: Hybrid<AtlasDescriptor>,
/// Used for user updates into the atlas by blit images into specific frames.
blitter: AtlasBlitter,
/// Maximum size the atlas is allowed to grow to.
/// When `None`, the atlas will not auto-grow.
max_size: Arc<RwLock<Option<wgpu::Extent3d>>>,
}

impl Atlas {
Expand Down Expand Up @@ -305,9 +308,27 @@ impl Atlas {
label,
blitter,
texture_array: Arc::new(RwLock::new(texture)),
max_size: Arc::new(RwLock::new(None)),
}
}

/// Set the maximum size the atlas is allowed to auto-grow to.
///
/// When the atlas cannot pack new images into its current size,
/// it will attempt to grow (doubling dimensions, then adding
/// layers) up to this limit before returning an error.
///
/// Pass `None` to disable auto-growing.
pub fn set_max_size(&self, max_size: Option<wgpu::Extent3d>) {
*self.max_size.write().expect("atlas max_size write") = max_size;
}

/// Builder-style setter for the maximum auto-grow size.
pub fn with_max_size(self, max_size: wgpu::Extent3d) -> Self {
self.set_max_size(Some(max_size));
self
}

pub fn descriptor_id(&self) -> Id<AtlasDescriptor> {
self.descriptor.id()
}
Expand Down Expand Up @@ -356,18 +377,77 @@ impl Atlas {
self.texture_array.read().expect("atlas texture_array read").texture.size()
}

/// Add the given images
/// Compute the next larger extent for auto-growing.
///
/// Strategy: first double the width/height, then add layers.
fn next_grow_extent(
current: wgpu::Extent3d,
max: wgpu::Extent3d,
) -> Option<wgpu::Extent3d> {
// Try doubling dimensions first (if below max)
if current.width * 2 <= max.width
&& current.height * 2 <= max.height
{
return Some(wgpu::Extent3d {
width: current.width * 2,
height: current.height * 2,
depth_or_array_layers: current.depth_or_array_layers,
});
}
// Try adding a layer
if current.depth_or_array_layers
< max.depth_or_array_layers
{
return Some(wgpu::Extent3d {
width: current.width,
height: current.height,
depth_or_array_layers: current.depth_or_array_layers + 1,
});
}
// Already at max
None
}

/// Add the given images, auto-growing the atlas if necessary.
pub fn add_images<'a>(
&self,
images: impl IntoIterator<Item = &'a AtlasImage>,
) -> Result<Vec<AtlasTexture>, AtlasError> {
let images: Vec<&AtlasImage> = images.into_iter().collect();
// UNWRAP: POP
let mut layers = self.layers.write().expect("atlas layers write");
let mut texture_array = self.texture_array.write().expect("atlas texture_array write");
let extent = texture_array.texture.size();

let newly_packed_layers = pack_images(&layers, images, extent)
.context(CannotPackTexturesSnafu { size: extent })?;
let mut layers =
self.layers.write().expect("atlas layers write");
let mut texture_array = self
.texture_array
.write()
.expect("atlas texture_array write");
let mut extent = texture_array.texture.size();
let max_size =
*self.max_size.read().expect("atlas max_size read");

// Try packing, auto-growing if a max_size is configured.
let newly_packed_layers = loop {
match pack_images(&layers, images.iter().copied(), extent) {
Some(packed) => break packed,
None => {
if let Some(max) = max_size {
if let Some(bigger) =
Self::next_grow_extent(extent, max)
{
log::info!(
"atlas auto-growing from {extent:?} \
to {bigger:?}"
);
extent = bigger;
continue;
}
}
return Err(AtlasError::CannotPackTextures {
size: extent,
});
}
}
};

let mut staged = StagedResources::try_staging(
self.slab.runtime(),
Expand All @@ -378,6 +458,15 @@ impl Atlas {
self.label.as_deref(),
)?;

// Update the descriptor with the new size.
self.descriptor.set(AtlasDescriptor {
size: UVec3::new(
extent.width,
extent.height,
extent.depth_or_array_layers,
),
});

// Commit our newly staged values, now that everything is done.
*texture_array = staged.texture;
*layers = staged.layers;
Expand Down
34 changes: 16 additions & 18 deletions crates/renderling/src/bloom/cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -553,17 +553,18 @@ impl Bloom {
.clone()
}

pub(crate) fn render_downsamples(&self, device: &wgpu::Device, queue: &wgpu::Queue) {
fn encode_downsamples(&self, encoder: &mut wgpu::CommandEncoder) {
struct DownsampleItem<'a> {
view: &'a wgpu::TextureView,
bindgroup: &'a wgpu::BindGroup,
pixel_size: Id<Vec2>,
}
// Get all the bindgroups (which are what we're reading from),
// starting with the hdr frame.
// Since `bindgroups` are one element greater (we pushed `hdr_texture_bindgroup`
// to the front) the last bindgroup will not be used, which is good - we
// don't need to read from the smallest texture during downsampling.
// Since `bindgroups` are one element greater (we pushed
// `hdr_texture_bindgroup` to the front) the last bindgroup will not
// be used, which is good - we don't need to read from the smallest
// texture during downsampling.
// UNWRAP: not safe but we want to panic
let textures_guard = self.textures.read().expect("bloom textures read");
let hdr_texture_downsample_bindgroup_guard = self
Expand Down Expand Up @@ -595,8 +596,6 @@ impl Bloom {
{
let title = format!("bloom downsample {i}");
let label = Some(title.as_str());
let mut encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label });
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label,
Expand All @@ -618,11 +617,10 @@ impl Bloom {
let id = pixel_size.into();
render_pass.draw(0..6, id..id + 1);
}
queue.submit(std::iter::once(encoder.finish()));
}
}

fn render_upsamples(&self, device: &wgpu::Device, queue: &wgpu::Queue) {
fn encode_upsamples(&self, encoder: &mut wgpu::CommandEncoder) {
struct UpsampleItem<'a> {
view: &'a wgpu::TextureView,
bindgroup: &'a wgpu::BindGroup,
Expand All @@ -642,8 +640,6 @@ impl Bloom {
for (i, UpsampleItem { view, bindgroup }) in items.enumerate() {
let title = format!("bloom upsample {}", textures_guard.len() - i - 1);
let label = Some(title.as_str());
let mut encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label });
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label,
Expand All @@ -665,16 +661,14 @@ impl Bloom {
let id = self.upsample_filter_radius.id().into();
render_pass.draw(0..6, id..id + 1);
}
queue.submit(std::iter::once(encoder.finish()));
}
}

fn render_mix(&self, device: &wgpu::Device, queue: &wgpu::Queue) {
fn encode_mix(&self, encoder: &mut wgpu::CommandEncoder) {
let label = Some("bloom mix");
// UNWRAP: not safe but we want to panic
let mix_texture = self.mix_texture.read().expect("bloom mix_texture read");
let mix_bindgroup = self.mix_bindgroup.read().expect("bloom mix_bindgroup read");
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label });
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label,
Expand All @@ -696,19 +690,23 @@ impl Bloom {
let id = self.mix_strength.id().into();
render_pass.draw(0..6, id..id + 1);
}

queue.submit(std::iter::once(encoder.finish()));
}

/// Run the full bloom pipeline (downsample, upsample, mix) using a
/// single command encoder and a single queue submission.
pub fn bloom(&self, device: &wgpu::Device, queue: &wgpu::Queue) {
self.slab.commit();
assert!(
self.slab_buffer.is_valid(),
"bloom slab buffer should never resize"
);
self.render_downsamples(device, queue);
self.render_upsamples(device, queue);
self.render_mix(device, queue);
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("bloom"),
});
self.encode_downsamples(&mut encoder);
self.encode_upsamples(&mut encoder);
self.encode_mix(&mut encoder);
queue.submit(std::iter::once(encoder.finish()));
}
}

Expand Down
Loading
Loading