diff options
| author | 2020-02-28 14:38:42 +0100 | |
|---|---|---|
| committer | 2020-02-28 14:38:42 +0100 | |
| commit | 4e7159c22c6be90f61aa715d5eb6811f805cb597 (patch) | |
| tree | 77faf86bc65142d3c224b240a0c5922b7f4f9025 /wgpu/src/image | |
| parent | fc55e3a3df9e08d361985b01528d2a6095d98aba (diff) | |
| download | iced-4e7159c22c6be90f61aa715d5eb6811f805cb597.tar.gz iced-4e7159c22c6be90f61aa715d5eb6811f805cb597.tar.bz2 iced-4e7159c22c6be90f61aa715d5eb6811f805cb597.zip  | |
Stop creating image pipeline when unnecessary
Diffstat (limited to 'wgpu/src/image')
| -rw-r--r-- | wgpu/src/image/atlas.rs | 361 | ||||
| -rw-r--r-- | wgpu/src/image/atlas/allocation.rs | 35 | ||||
| -rw-r--r-- | wgpu/src/image/atlas/allocator.rs | 69 | ||||
| -rw-r--r-- | wgpu/src/image/atlas/entry.rs | 26 | ||||
| -rw-r--r-- | wgpu/src/image/atlas/layer.rs | 17 | ||||
| -rw-r--r-- | wgpu/src/image/raster.rs | 2 | ||||
| -rw-r--r-- | wgpu/src/image/vector.rs | 2 | 
7 files changed, 510 insertions, 2 deletions
diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs new file mode 100644 index 00000000..86a5ff49 --- /dev/null +++ b/wgpu/src/image/atlas.rs @@ -0,0 +1,361 @@ +pub mod entry; + +mod allocation; +mod allocator; +mod layer; + +pub use allocation::Allocation; +pub use entry::Entry; +pub use layer::Layer; + +use allocator::Allocator; + +pub const SIZE: u32 = 2048; + +#[derive(Debug)] +pub struct Atlas { +    texture: wgpu::Texture, +    texture_view: wgpu::TextureView, +    layers: Vec<Layer>, +} + +impl Atlas { +    pub fn new(device: &wgpu::Device) -> Self { +        let extent = wgpu::Extent3d { +            width: SIZE, +            height: SIZE, +            depth: 1, +        }; + +        let texture = device.create_texture(&wgpu::TextureDescriptor { +            size: extent, +            array_layer_count: 2, +            mip_level_count: 1, +            sample_count: 1, +            dimension: wgpu::TextureDimension::D2, +            format: wgpu::TextureFormat::Bgra8UnormSrgb, +            usage: wgpu::TextureUsage::COPY_DST +                | wgpu::TextureUsage::COPY_SRC +                | wgpu::TextureUsage::SAMPLED, +        }); + +        let texture_view = texture.create_default_view(); + +        Atlas { +            texture, +            texture_view, +            layers: vec![Layer::Empty, Layer::Empty], +        } +    } + +    pub fn view(&self) -> &wgpu::TextureView { +        &self.texture_view +    } + +    pub fn layer_count(&self) -> usize { +        self.layers.len() +    } + +    pub fn upload<C>( +        &mut self, +        width: u32, +        height: u32, +        data: &[C], +        device: &wgpu::Device, +        encoder: &mut wgpu::CommandEncoder, +    ) -> Option<Entry> +    where +        C: Copy + 'static, +    { +        let entry = { +            let current_size = self.layers.len(); +            let entry = self.allocate(width, height)?; + +            // We grow the internal texture after allocating if necessary +            let new_layers = self.layers.len() - current_size; +            self.grow(new_layers, device, encoder); + +            entry +        }; + +        log::info!("Allocated atlas entry: {:?}", entry); + +        let buffer = device +            .create_buffer_mapped(data.len(), wgpu::BufferUsage::COPY_SRC) +            .fill_from_slice(data); + +        match &entry { +            Entry::Contiguous(allocation) => { +                self.upload_allocation( +                    &buffer, +                    width, +                    height, +                    0, +                    &allocation, +                    encoder, +                ); +            } +            Entry::Fragmented { fragments, .. } => { +                for fragment in fragments { +                    let (x, y) = fragment.position; +                    let offset = (y * width + x) as usize * 4; + +                    self.upload_allocation( +                        &buffer, +                        width, +                        height, +                        offset, +                        &fragment.allocation, +                        encoder, +                    ); +                } +            } +        } + +        log::info!("Current atlas: {:?}", self); + +        Some(entry) +    } + +    pub fn remove(&mut self, entry: &Entry) { +        log::info!("Removing atlas entry: {:?}", entry); + +        match entry { +            Entry::Contiguous(allocation) => { +                self.deallocate(allocation); +            } +            Entry::Fragmented { fragments, .. } => { +                for fragment in fragments { +                    self.deallocate(&fragment.allocation); +                } +            } +        } +    } + +    fn allocate(&mut self, width: u32, height: u32) -> Option<Entry> { +        // Allocate one layer if texture fits perfectly +        if width == SIZE && height == SIZE { +            let mut empty_layers = self +                .layers +                .iter_mut() +                .enumerate() +                .filter(|(_, layer)| layer.is_empty()); + +            if let Some((i, layer)) = empty_layers.next() { +                *layer = Layer::Full; + +                return Some(Entry::Contiguous(Allocation::Full { layer: i })); +            } + +            self.layers.push(Layer::Full); + +            return Some(Entry::Contiguous(Allocation::Full { +                layer: self.layers.len() - 1, +            })); +        } + +        // Split big textures across multiple layers +        if width > SIZE || height > SIZE { +            let mut fragments = Vec::new(); +            let mut y = 0; + +            while y < height { +                let height = std::cmp::min(height - y, SIZE); +                let mut x = 0; + +                while x < width { +                    let width = std::cmp::min(width - x, SIZE); + +                    let allocation = self.allocate(width, height)?; + +                    if let Entry::Contiguous(allocation) = allocation { +                        fragments.push(entry::Fragment { +                            position: (x, y), +                            allocation, +                        }); +                    } + +                    x += width; +                } + +                y += height; +            } + +            return Some(Entry::Fragmented { +                size: (width, height), +                fragments, +            }); +        } + +        // Try allocating on an existing layer +        for (i, layer) in self.layers.iter_mut().enumerate() { +            match layer { +                Layer::Empty => { +                    let mut allocator = Allocator::new(SIZE); + +                    if let Some(region) = allocator.allocate(width, height) { +                        *layer = Layer::Busy(allocator); + +                        return Some(Entry::Contiguous(Allocation::Partial { +                            region, +                            layer: i, +                        })); +                    } +                } +                Layer::Busy(allocator) => { +                    if let Some(region) = allocator.allocate(width, height) { +                        return Some(Entry::Contiguous(Allocation::Partial { +                            region, +                            layer: i, +                        })); +                    } +                } +                _ => {} +            } +        } + +        // Create new layer with atlas allocator +        let mut allocator = Allocator::new(SIZE); + +        if let Some(region) = allocator.allocate(width, height) { +            self.layers.push(Layer::Busy(allocator)); + +            return Some(Entry::Contiguous(Allocation::Partial { +                region, +                layer: self.layers.len() - 1, +            })); +        } + +        // We ran out of memory (?) +        None +    } + +    fn deallocate(&mut self, allocation: &Allocation) { +        log::info!("Deallocating atlas: {:?}", allocation); + +        match allocation { +            Allocation::Full { layer } => { +                self.layers[*layer] = Layer::Empty; +            } +            Allocation::Partial { layer, region } => { +                let layer = &mut self.layers[*layer]; + +                if let Layer::Busy(allocator) = layer { +                    allocator.deallocate(region); + +                    if allocator.is_empty() { +                        *layer = Layer::Empty; +                    } +                } +            } +        } +    } + +    fn upload_allocation( +        &mut self, +        buffer: &wgpu::Buffer, +        image_width: u32, +        image_height: u32, +        offset: usize, +        allocation: &Allocation, +        encoder: &mut wgpu::CommandEncoder, +    ) { +        let (x, y) = allocation.position(); +        let (width, height) = allocation.size(); +        let layer = allocation.layer(); + +        let extent = wgpu::Extent3d { +            width, +            height, +            depth: 1, +        }; + +        encoder.copy_buffer_to_texture( +            wgpu::BufferCopyView { +                buffer, +                offset: offset as u64, +                row_pitch: 4 * image_width, +                image_height, +            }, +            wgpu::TextureCopyView { +                texture: &self.texture, +                array_layer: layer as u32, +                mip_level: 0, +                origin: wgpu::Origin3d { +                    x: x as f32, +                    y: y as f32, +                    z: 0.0, +                }, +            }, +            extent, +        ); +    } + +    fn grow( +        &mut self, +        amount: usize, +        device: &wgpu::Device, +        encoder: &mut wgpu::CommandEncoder, +    ) { +        if amount == 0 { +            return; +        } + +        let new_texture = device.create_texture(&wgpu::TextureDescriptor { +            size: wgpu::Extent3d { +                width: SIZE, +                height: SIZE, +                depth: 1, +            }, +            array_layer_count: self.layers.len() as u32, +            mip_level_count: 1, +            sample_count: 1, +            dimension: wgpu::TextureDimension::D2, +            format: wgpu::TextureFormat::Bgra8UnormSrgb, +            usage: wgpu::TextureUsage::COPY_DST +                | wgpu::TextureUsage::COPY_SRC +                | wgpu::TextureUsage::SAMPLED, +        }); + +        let amount_to_copy = self.layers.len() - amount; + +        for (i, layer) in +            self.layers.iter_mut().take(amount_to_copy).enumerate() +        { +            if layer.is_empty() { +                continue; +            } + +            encoder.copy_texture_to_texture( +                wgpu::TextureCopyView { +                    texture: &self.texture, +                    array_layer: i as u32, +                    mip_level: 0, +                    origin: wgpu::Origin3d { +                        x: 0.0, +                        y: 0.0, +                        z: 0.0, +                    }, +                }, +                wgpu::TextureCopyView { +                    texture: &new_texture, +                    array_layer: i as u32, +                    mip_level: 0, +                    origin: wgpu::Origin3d { +                        x: 0.0, +                        y: 0.0, +                        z: 0.0, +                    }, +                }, +                wgpu::Extent3d { +                    width: SIZE, +                    height: SIZE, +                    depth: 1, +                }, +            ); +        } + +        self.texture = new_texture; +        self.texture_view = self.texture.create_default_view(); +    } +} diff --git a/wgpu/src/image/atlas/allocation.rs b/wgpu/src/image/atlas/allocation.rs new file mode 100644 index 00000000..59b7239f --- /dev/null +++ b/wgpu/src/image/atlas/allocation.rs @@ -0,0 +1,35 @@ +use crate::image::atlas::{self, allocator}; + +#[derive(Debug)] +pub enum Allocation { +    Partial { +        layer: usize, +        region: allocator::Region, +    }, +    Full { +        layer: usize, +    }, +} + +impl Allocation { +    pub fn position(&self) -> (u32, u32) { +        match self { +            Allocation::Partial { region, .. } => region.position(), +            Allocation::Full { .. } => (0, 0), +        } +    } + +    pub fn size(&self) -> (u32, u32) { +        match self { +            Allocation::Partial { region, .. } => region.size(), +            Allocation::Full { .. } => (atlas::SIZE, atlas::SIZE), +        } +    } + +    pub fn layer(&self) -> usize { +        match self { +            Allocation::Partial { layer, .. } => *layer, +            Allocation::Full { layer } => *layer, +        } +    } +} diff --git a/wgpu/src/image/atlas/allocator.rs b/wgpu/src/image/atlas/allocator.rs new file mode 100644 index 00000000..7a4ff5b1 --- /dev/null +++ b/wgpu/src/image/atlas/allocator.rs @@ -0,0 +1,69 @@ +use guillotiere::{AtlasAllocator, Size}; + +pub struct Allocator { +    raw: AtlasAllocator, +    allocations: usize, +} + +impl Allocator { +    pub fn new(size: u32) -> Allocator { +        let raw = AtlasAllocator::new(Size::new(size as i32, size as i32)); + +        Allocator { +            raw, +            allocations: 0, +        } +    } + +    pub fn allocate(&mut self, width: u32, height: u32) -> Option<Region> { +        let allocation = +            self.raw.allocate(Size::new(width as i32, height as i32))?; + +        self.allocations += 1; + +        Some(Region { allocation }) +    } + +    pub fn deallocate(&mut self, region: &Region) { +        self.raw.deallocate(region.allocation.id); + +        self.allocations = self.allocations.saturating_sub(1); +    } + +    pub fn is_empty(&self) -> bool { +        self.allocations == 0 +    } +} + +pub struct Region { +    allocation: guillotiere::Allocation, +} + +impl Region { +    pub fn position(&self) -> (u32, u32) { +        let rectangle = &self.allocation.rectangle; + +        (rectangle.min.x as u32, rectangle.min.y as u32) +    } + +    pub fn size(&self) -> (u32, u32) { +        let size = self.allocation.rectangle.size(); + +        (size.width as u32, size.height as u32) +    } +} + +impl std::fmt::Debug for Allocator { +    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +        write!(f, "Allocator") +    } +} + +impl std::fmt::Debug for Region { +    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +        f.debug_struct("Region") +            .field("id", &self.allocation.id) +            .field("rectangle", &self.allocation.rectangle) +            .finish() +    } +} diff --git a/wgpu/src/image/atlas/entry.rs b/wgpu/src/image/atlas/entry.rs new file mode 100644 index 00000000..0310fc54 --- /dev/null +++ b/wgpu/src/image/atlas/entry.rs @@ -0,0 +1,26 @@ +use crate::image::atlas; + +#[derive(Debug)] +pub enum Entry { +    Contiguous(atlas::Allocation), +    Fragmented { +        size: (u32, u32), +        fragments: Vec<Fragment>, +    }, +} + +impl Entry { +    #[cfg(feature = "image")] +    pub fn size(&self) -> (u32, u32) { +        match self { +            Entry::Contiguous(allocation) => allocation.size(), +            Entry::Fragmented { size, .. } => *size, +        } +    } +} + +#[derive(Debug)] +pub struct Fragment { +    pub position: (u32, u32), +    pub allocation: atlas::Allocation, +} diff --git a/wgpu/src/image/atlas/layer.rs b/wgpu/src/image/atlas/layer.rs new file mode 100644 index 00000000..b1084ed9 --- /dev/null +++ b/wgpu/src/image/atlas/layer.rs @@ -0,0 +1,17 @@ +use crate::image::atlas::Allocator; + +#[derive(Debug)] +pub enum Layer { +    Empty, +    Busy(Allocator), +    Full, +} + +impl Layer { +    pub fn is_empty(&self) -> bool { +        match self { +            Layer::Empty => true, +            _ => false, +        } +    } +} diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 883c32f7..3edec57e 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -1,4 +1,4 @@ -use crate::texture::atlas::{self, Atlas}; +use crate::image::atlas::{self, Atlas};  use iced_native::image;  use std::collections::{HashMap, HashSet}; diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 6b12df54..bae0f82f 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -1,4 +1,4 @@ -use crate::texture::atlas::{self, Atlas}; +use crate::image::atlas::{self, Atlas};  use iced_native::svg;  use std::collections::{HashMap, HashSet};  | 
