.
diff --git a/sparse_strips/vello_common/src/multi_atlas.rs b/sparse_strips/vello_common/src/multi_atlas.rs index 5dd493a..c5ec0c1 100644 --- a/sparse_strips/vello_common/src/multi_atlas.rs +++ b/sparse_strips/vello_common/src/multi_atlas.rs
@@ -196,19 +196,10 @@ } } - fn space_diagnostics( - &self, - width: u32, - height: u32, - exclude_atlas_id: Option<AtlasId>, - ) -> AtlasSpaceDiagnostics { + fn space_diagnostics(&self, width: u32, height: u32) -> AtlasSpaceDiagnostics { let mut atlases = Vec::new(); for atlas in &self.atlases { - if Some(atlas.id) == exclude_atlas_id { - continue; - } - let mut free_area = 0_u64; let mut free_rectangle_count = 0; let mut largest_free_width = 0; @@ -248,24 +239,14 @@ } } - fn no_space_available( - &self, - width: u32, - height: u32, - exclude_atlas_id: Option<AtlasId>, - ) -> AtlasError { - AtlasError::NoSpaceAvailable(self.space_diagnostics(width, height, exclude_atlas_id)) + fn no_space_available(&self, width: u32, height: u32) -> AtlasError { + AtlasError::NoSpaceAvailable(self.space_diagnostics(width, height)) } - fn atlas_limit_reached( - &self, - width: u32, - height: u32, - exclude_atlas_id: Option<AtlasId>, - ) -> AtlasError { + fn atlas_limit_reached(&self, width: u32, height: u32) -> AtlasError { AtlasError::AtlasLimitReached { max_atlases: self.config.max_atlases, - diagnostics: self.space_diagnostics(width, height, exclude_atlas_id), + diagnostics: self.space_diagnostics(width, height), } } @@ -291,10 +272,9 @@ // Try creating a new atlas if auto-grow is enabled if self.config.auto_grow { - if self.atlases.len() >= self.config.max_atlases { - return Err(self.atlas_limit_reached(width, height, exclude_atlas_id)); - } - let atlas_id = self.create_atlas()?; + let atlas_id = self + .create_atlas() + .map_err(|_| self.atlas_limit_reached(width, height))?; let atlas = self.atlases.last_mut().unwrap(); if let Some(allocation) = atlas.allocate(width, height) { return Ok(AtlasAllocation { @@ -304,7 +284,7 @@ } } - Err(self.no_space_available(width, height, exclude_atlas_id)) + Err(self.no_space_available(width, height)) } /// Allocate using best-fit strategy: choose the atlas with the smallest remaining space that @@ -417,10 +397,9 @@ // Try creating a new atlas if auto-grow is enabled if self.config.auto_grow { - if self.atlases.len() >= self.config.max_atlases { - return Err(self.atlas_limit_reached(width, height, exclude_atlas_id)); - } - let atlas_id = self.create_atlas()?; + let atlas_id = self + .create_atlas() + .map_err(|_| self.atlas_limit_reached(width, height))?; let atlas = self.atlases.last_mut().unwrap(); if let Some(allocation) = atlas.allocate(width, height) { self.round_robin_counter = self.atlases.len() - 1; @@ -431,7 +410,7 @@ } } - Err(self.no_space_available(width, height, exclude_atlas_id)) + Err(self.no_space_available(width, height)) } /// Deallocate space in the specified atlas.
diff --git a/sparse_strips/vello_hybrid/src/lib.rs b/sparse_strips/vello_hybrid/src/lib.rs index 1483f72..d92463a 100644 --- a/sparse_strips/vello_hybrid/src/lib.rs +++ b/sparse_strips/vello_hybrid/src/lib.rs
@@ -113,17 +113,26 @@ /// Errors that can occur during rendering. #[derive(Error, Debug, Clone)] pub enum RenderError { - /// An atlas allocation failed. + /// An image atlas allocation failed. #[error("Atlas allocation failed: {0}")] AtlasError(#[from] vello_common::multi_atlas::AtlasError), /// A draw referenced a [`TextureId`] that was not provided at render time. #[error("Missing texture binding for {0:?}")] MissingTextureBinding(TextureId), + /// An intermediate texture allocation failed. + #[error(transparent)] + IntermediateTexture(#[from] IntermediateTextureError), + // TODO: Consider expanding `RenderError` to replace some `.unwrap` and `.expect`. +} + +/// Errors that can occur while allocating intermediate textures. +#[derive(Error, Debug, Clone)] +pub enum IntermediateTextureError { /// An intermediate texture allocation exceeds the configured texture dimensions. #[error( "Intermediate texture allocation {width}x{height} exceeds maximum {max_width}x{max_height}" )] - IntermediateTextureTooLarge { + TooLarge { /// The requested allocation width. width: u32, /// The requested allocation height. @@ -137,13 +146,12 @@ #[error( "Render requires {required} intermediate textures, exceeding the configured maximum of {max}" )] - IntermediateTextureLimitReached { + LimitReached { /// The number of intermediate textures required by the render. required: usize, /// The configured maximum number of intermediate textures. max: usize, }, - // TODO: Consider expanding `RenderError` to replace some `.unwrap` and `.expect`. } #[cfg(test)]
diff --git a/sparse_strips/vello_hybrid/src/render/common.rs b/sparse_strips/vello_hybrid/src/render/common.rs index bc33d4a..a5c84cd 100644 --- a/sparse_strips/vello_hybrid/src/render/common.rs +++ b/sparse_strips/vello_hybrid/src/render/common.rs
@@ -8,6 +8,7 @@ reason = "GPU paint structures have small, fixed sizes that fit in u32" )] +use crate::IntermediateTextureError; use crate::blend::GpuBlendInstance; use crate::copy::GpuCopyInstance; use crate::filter::FILTER_ATLAS_PADDING; @@ -15,7 +16,6 @@ use alloc::vec::Vec; use bytemuck::{Pod, Zeroable}; use vello_common::geometry::{SizeU16, SizeU32}; -use vello_common::multi_atlas::AtlasError; use vello_common::record::CommandRecorder; // GPU paint structure sizes in texels (1 texel = 16 bytes for RGBA32Uint texture format). @@ -130,7 +130,7 @@ pub(crate) fn required_intermediate_texture_size( self, recorder: &CommandRecorder<RecordedDraw>, - ) -> Result<SizeU16, AtlasError> { + ) -> Result<SizeU16, IntermediateTextureError> { let min_size = self.min_texture_size; let max_size = self.max_texture_size; @@ -138,7 +138,7 @@ if size.width() > u32::from(max_size.width()) || size.height() > u32::from(max_size.height()) { - return Err(AtlasError::TextureTooLarge { + return Err(IntermediateTextureError::TooLarge { width: size.width(), height: size.height(), max_width: u32::from(max_size.width()), @@ -174,8 +174,8 @@ mod tests { use super::DeviceLimits; use crate::scene::RecordedDraw; - use crate::{LayersConfig, MemorySettings, SizeU16}; - use vello_common::multi_atlas::{AtlasConfig, AtlasError}; + use crate::{IntermediateTextureError, LayersConfig, MemorySettings, SizeU16}; + use vello_common::multi_atlas::AtlasConfig; use vello_common::record::CommandRecorder; fn device_limits(max_texture_dimension_2d: u32, max_texture_array_layers: u32) -> DeviceLimits { @@ -297,7 +297,7 @@ assert!(matches!( config.required_intermediate_texture_size(&recorder), - Err(AtlasError::TextureTooLarge { + Err(IntermediateTextureError::TooLarge { width: 513, height: 10, max_width: 512,
diff --git a/sparse_strips/vello_hybrid/src/schedule/allocate.rs b/sparse_strips/vello_hybrid/src/schedule/allocate.rs index d5bbd69..da4ebd2 100644 --- a/sparse_strips/vello_hybrid/src/schedule/allocate.rs +++ b/sparse_strips/vello_hybrid/src/schedule/allocate.rs
@@ -134,7 +134,7 @@ } pub(super) fn allocation_size(self) -> SizeU32 { - SizeU32::from(self.region.size) + u32::from(self.region.padding) * 2 + self.region.allocation_size() } } @@ -147,6 +147,13 @@ padding: u16, } +impl RegionProps { + /// Size of the atlas allocation needed to hold the region and its padding. + fn allocation_size(self) -> SizeU32 { + SizeU32::from(self.size) + u32::from(self.padding) * 2 + } +} + /// Texture region and allocator metadata needed to release it. #[derive(Debug, Clone, Copy)] pub(super) struct AllocatedTextureRegion { @@ -201,7 +208,7 @@ let padding = u32::from(props.padding); let width = props.size.width(); let height = props.size.height(); - let allocation_size = SizeU32::from(props.size) + padding * 2; + let allocation_size = props.allocation_size(); let allocation = self.allocate(allocation_size.width(), allocation_size.height())?; let x = u16::try_from(allocation.x + padding).unwrap(); let y = u16::try_from(allocation.y + padding).unwrap();
diff --git a/sparse_strips/vello_hybrid/src/schedule/cursor.rs b/sparse_strips/vello_hybrid/src/schedule/cursor.rs index e2df219..f1a78e5 100644 --- a/sparse_strips/vello_hybrid/src/schedule/cursor.rs +++ b/sparse_strips/vello_hybrid/src/schedule/cursor.rs
@@ -8,10 +8,10 @@ //! preferring to advance the base round count in case a requested allocation doesn't fit //! into the current round, and only resorting to adding more textures as a last resort. -use crate::RenderError; use crate::schedule::allocate::{ AllocatedTextureRegion, Allocation, Atlases, LayerAllocationRequest, }; +use crate::{IntermediateTextureError, RenderError}; use alloc::vec::Vec; /// The round cursor. @@ -75,7 +75,7 @@ .allocate_layer(&request) // If we successfully added a new texture but allocation still fails, it means the layer // itself is larger than the maximum texture size, so it cannot possibly fit. - .ok_or(RenderError::IntermediateTextureTooLarge { + .ok_or(IntermediateTextureError::TooLarge { width: requested_size.width(), height: requested_size.height(), max_width: u32::from(texture_size.width()), @@ -139,9 +139,9 @@ #[cfg(test)] mod tests { use super::Cursor; - use crate::RenderError; use crate::schedule::allocate::{Atlases, LayerAllocationRequest}; use crate::target::{LayerTextureId, TextureParity}; + use crate::{IntermediateTextureError, RenderError}; use vello_common::geometry::{RectU16, SizeU16}; use vello_common::record::RecordedLayerKind; @@ -212,12 +212,14 @@ assert!(matches!( cursor.allocate_layer(request(TextureParity::Even, SizeU16::from_wh(9, 8),)), - Err(RenderError::IntermediateTextureTooLarge { - width: 9, - height: 8, - max_width: 8, - max_height: 8, - }) + Err(RenderError::IntermediateTexture( + IntermediateTextureError::TooLarge { + width: 9, + height: 8, + max_width: 8, + max_height: 8, + } + )) )); }
diff --git a/sparse_strips/vello_hybrid/src/schedule/mod.rs b/sparse_strips/vello_hybrid/src/schedule/mod.rs index 5991278..119a298 100644 --- a/sparse_strips/vello_hybrid/src/schedule/mod.rs +++ b/sparse_strips/vello_hybrid/src/schedule/mod.rs
@@ -141,7 +141,7 @@ use crate::target::{ DrawTarget, LayerTextureRegion, RootTarget, RoundBindings, TextureParity, TextureRegion, }; -use crate::{RenderError, Scene, blend::BlendStrip}; +use crate::{IntermediateTextureError, RenderError, Scene, blend::BlendStrip}; use alloc::vec::Vec; use vello_common::filter::FilterLayerPlacement; use vello_common::geometry::{RectU16, SizeU16}; @@ -250,13 +250,13 @@ self, existing: IntermediateTextureAllocations, max_textures: Option<usize>, - ) -> Result<(), RenderError> { + ) -> Result<(), IntermediateTextureError> { let retained_textures = self.allocations.combine(existing).texture_count(); if let Some(max) = max_textures && retained_textures > max { - return Err(RenderError::IntermediateTextureLimitReached { + return Err(IntermediateTextureError::LimitReached { required: retained_textures, max, });
diff --git a/sparse_strips/vello_hybrid/src/schedule/schedule_tests.rs b/sparse_strips/vello_hybrid/src/schedule/schedule_tests.rs index 93887dc..fc085c6 100644 --- a/sparse_strips/vello_hybrid/src/schedule/schedule_tests.rs +++ b/sparse_strips/vello_hybrid/src/schedule/schedule_tests.rs
@@ -3,9 +3,9 @@ use super::test_support::{SceneCase, ScheduledCase}; use super::{IntermediateTextureAllocations, IntermediateTextureRequirements, ScheduleStorage}; -use crate::RenderError; use crate::filter::FILTER_ATLAS_PADDING; use crate::target::{RootTarget, TextureParity}; +use crate::{IntermediateTextureError, RenderError}; use vello_common::filter_effects::{EdgeMode, Filter, FilterPrimitive}; use vello_common::geometry::SizeU16; use vello_common::kurbo::Rect; @@ -36,28 +36,28 @@ assert!(base.validate(allocations([0, 3], false), Some(4)).is_err()); assert!(matches!( base.validate(allocations([2, 0], false), Some(3)), - Err(RenderError::IntermediateTextureLimitReached { + Err(IntermediateTextureError::LimitReached { required: 4, max: 3, }) )); assert!(matches!( base.validate(allocations([1, 0], true), Some(2)), - Err(RenderError::IntermediateTextureLimitReached { + Err(IntermediateTextureError::LimitReached { required: 3, max: 2, }) )); assert!(matches!( base.validate(allocations([0, 2], false), Some(3)), - Err(RenderError::IntermediateTextureLimitReached { + Err(IntermediateTextureError::LimitReached { required: 4, max: 3, }) )); assert!(matches!( base.validate(allocations([2, 2], false), Some(4)), - Err(RenderError::IntermediateTextureLimitReached { + Err(IntermediateTextureError::LimitReached { required: 5, max: 4, }) @@ -370,10 +370,12 @@ ); assert!(matches!( case.schedule(RootTarget::UserSurface, SizeU16::new(16), 2,), - Err(RenderError::IntermediateTextureLimitReached { - required: 3, - max: 2, - }) + Err(RenderError::IntermediateTexture( + IntermediateTextureError::LimitReached { + required: 3, + max: 2, + } + )) )); }