From 4e7159c22c6be90f61aa715d5eb6811f805cb597 Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector0193@gmail.com>
Date: Fri, 28 Feb 2020 14:38:42 +0100
Subject: Stop creating image pipeline when unnecessary

---
 wgpu/src/image/atlas.rs            | 361 +++++++++++++++++++++++++++++++++++++
 wgpu/src/image/atlas/allocation.rs |  35 ++++
 wgpu/src/image/atlas/allocator.rs  |  69 +++++++
 wgpu/src/image/atlas/entry.rs      |  26 +++
 wgpu/src/image/atlas/layer.rs      |  17 ++
 wgpu/src/image/raster.rs           |   2 +-
 wgpu/src/image/vector.rs           |   2 +-
 7 files changed, 510 insertions(+), 2 deletions(-)
 create mode 100644 wgpu/src/image/atlas.rs
 create mode 100644 wgpu/src/image/atlas/allocation.rs
 create mode 100644 wgpu/src/image/atlas/allocator.rs
 create mode 100644 wgpu/src/image/atlas/entry.rs
 create mode 100644 wgpu/src/image/atlas/layer.rs

(limited to 'wgpu/src/image')

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};
 
-- 
cgit