1755 lines
60 KiB
Rust
1755 lines
60 KiB
Rust
use crate::{Rectangle, Size};
|
|
use euclid::{vec2, point2, size2};
|
|
|
|
use std::num::Wrapping;
|
|
|
|
const LARGE_BUCKET: usize = 2;
|
|
const MEDIUM_BUCKET: usize = 1;
|
|
const SMALL_BUCKET: usize = 0;
|
|
const NUM_BUCKETS: usize = 3;
|
|
|
|
fn free_list_for_size(small_threshold: i32, large_threshold: i32, size: &Size) -> usize {
|
|
if size.width >= large_threshold || size.height >= large_threshold {
|
|
LARGE_BUCKET
|
|
} else if size.width >= small_threshold || size.height >= small_threshold {
|
|
MEDIUM_BUCKET
|
|
} else {
|
|
SMALL_BUCKET
|
|
}
|
|
}
|
|
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
|
struct AllocIndex(u32);
|
|
impl AllocIndex {
|
|
const NONE: AllocIndex = AllocIndex(std::u32::MAX);
|
|
|
|
fn index(self) -> usize {
|
|
self.0 as usize
|
|
}
|
|
|
|
fn is_none(self) -> bool {
|
|
self == AllocIndex::NONE
|
|
}
|
|
|
|
fn is_some(self) -> bool {
|
|
self != AllocIndex::NONE
|
|
}
|
|
}
|
|
|
|
/// ID referring to an allocated rectangle.
|
|
#[repr(C)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
|
pub struct AllocId(pub(crate) u32);
|
|
|
|
impl AllocId {
|
|
pub fn serialize(&self) -> u32 {
|
|
self.0
|
|
}
|
|
|
|
pub fn deserialize(bytes: u32) -> Self {
|
|
AllocId(bytes)
|
|
}
|
|
}
|
|
|
|
const GEN_MASK: u32 = 0xFF000000;
|
|
const IDX_MASK: u32 = 0x00FFFFFF;
|
|
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
|
enum Orientation {
|
|
Vertical,
|
|
Horizontal,
|
|
}
|
|
|
|
impl Orientation {
|
|
fn flipped(self) -> Self {
|
|
match self {
|
|
Orientation::Vertical => Orientation::Horizontal,
|
|
Orientation::Horizontal => Orientation::Vertical,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
|
pub enum NodeKind {
|
|
Container,
|
|
Alloc,
|
|
Free,
|
|
Unused,
|
|
}
|
|
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[derive(Clone, Debug)]
|
|
struct Node {
|
|
parent: AllocIndex,
|
|
next_sibling: AllocIndex,
|
|
prev_sibling: AllocIndex,
|
|
kind: NodeKind,
|
|
orientation: Orientation,
|
|
rect: Rectangle,
|
|
}
|
|
|
|
/// Options to tweak the behavior of the atlas allocator.
|
|
#[repr(C)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
|
pub struct AllocatorOptions {
|
|
/// Round the rectangle sizes up to a multiple of this value.
|
|
///
|
|
/// Width and height alignments must be superior to zero.
|
|
///
|
|
/// Default value: (1, 1),
|
|
pub alignment: Size,
|
|
|
|
/// Value below which a size is considered small.
|
|
///
|
|
/// This is value is used to speed up the storage and lookup of free rectangles.
|
|
/// This value must be inferior or equal to `large_size_threshold`
|
|
///
|
|
/// Default value: 32,
|
|
pub small_size_threshold: i32,
|
|
|
|
/// Value above which a size is considered large.
|
|
///
|
|
/// This is value is used to speed up the storage and lookup of free rectangles.
|
|
/// This value must be inferior or equal to `large_size_threshold`
|
|
///
|
|
/// Default value: 256,
|
|
pub large_size_threshold: i32,
|
|
}
|
|
|
|
pub const DEFAULT_OPTIONS: AllocatorOptions = AllocatorOptions {
|
|
alignment: size2(1, 1),
|
|
large_size_threshold: 256,
|
|
small_size_threshold: 32,
|
|
};
|
|
|
|
impl Default for AllocatorOptions {
|
|
fn default() -> Self {
|
|
DEFAULT_OPTIONS
|
|
}
|
|
}
|
|
|
|
/// A dynamic texture atlas allocator using the guillotine algorithm.
|
|
///
|
|
/// The guillotine algorithm is assisted by a data structure that keeps track of
|
|
/// neighboring rectangles to provide fast deallocation and coalescing.
|
|
///
|
|
/// ## Goals
|
|
///
|
|
/// Coalescing free rectangles, in the context of dynamic atlas allocation can be
|
|
/// prohibitively expensive under real-time constraints if the algorithm needs to
|
|
/// visit a large amount of free rectangles to find merge candidates.
|
|
///
|
|
/// This implementation proposes a compromise with fast (constant time) search
|
|
/// for merge candidates at the expense of some (constant time) bookkeeping overhead
|
|
/// when allocating and removing rectangles and imperfect defragmentation (see the
|
|
/// "Limitations" section below.
|
|
///
|
|
/// The subdivision scheme uses the worst fit variant of the guillotine algorithm
|
|
/// for its simplicity and CPU efficiency.
|
|
///
|
|
/// ## The data structure
|
|
///
|
|
/// We maintain a tree with allocated and free rectangles as leaf nodes and
|
|
/// containers as non-leaf nodes.
|
|
///
|
|
/// The direct children of a Containers's form an ordered horizontal or vertical
|
|
/// sequence of rectangles that cover exactly their parent container's area.
|
|
///
|
|
/// For example, a subdivision such as this one:
|
|
///
|
|
/// ```ascii
|
|
/// +-----------+----------+---+---+--+---------+---+
|
|
/// | | | C | D |E | F | G |
|
|
/// | | +---+---+--+---------+---+
|
|
/// | A | B | |
|
|
/// | | | H |
|
|
/// | | | |
|
|
/// +------+----+----------+-+----------------------+
|
|
/// | | J | |
|
|
/// | I +-----------------+ L |
|
|
/// | | K | |
|
|
/// +------+-----------------+----------------------+
|
|
/// ```
|
|
///
|
|
/// Would have a tree of the form:
|
|
///
|
|
/// ```ascii
|
|
///
|
|
/// Tree | Layout
|
|
/// ---------------------+------------
|
|
/// |
|
|
/// # |
|
|
/// | |
|
|
/// +----+----+. . .|. vertical
|
|
/// | | |
|
|
/// # # |
|
|
/// | | |
|
|
/// +-+-+ . . +-+-+. .|. horizontal
|
|
/// | | | | | | |
|
|
/// A B # I # L |
|
|
/// | | |
|
|
/// +-+-+ . +-+-+. .|. vertical
|
|
/// | | | | |
|
|
/// # H J K |
|
|
/// | |
|
|
/// +-+-+-+-+. . . . . .|. horizontal
|
|
/// | | | | | |
|
|
/// C D E F G |
|
|
/// ```
|
|
///
|
|
/// Where container nodes are represented with "#".
|
|
///
|
|
/// Note that if a horizontal container is the direct child of another
|
|
/// horizontal container, we can merge the two into a single horizontal
|
|
/// sequence.
|
|
/// We use this property to always keep the tree in its simplest form.
|
|
/// In practice this means that the orientation of a container is always
|
|
/// the opposite of the orientation of its parent, if any.
|
|
///
|
|
/// The goal of this data structure is to quickly find neighboring free
|
|
/// rectangles that can be coalesced into fewer rectangles.
|
|
/// This structure guarantees that two consecutive children of the same
|
|
/// container, if both rectangles are free, can be coalesced into a single
|
|
/// one.
|
|
///
|
|
/// An important thing to note about this tree structure is that we only
|
|
/// use it to visit neighbor and parent nodes. As a result we don't care
|
|
/// about whether the tree is balanced, although flat sequences of children
|
|
/// tend to offer more opportunity for coalescing than deeply nested structures
|
|
/// Either way, the cost of finding potential merges is the same because
|
|
/// each node stores the indices of their siblings, and we never have to
|
|
/// traverse any global list of free rectangle nodes.
|
|
///
|
|
/// ### Merging siblings
|
|
///
|
|
/// As soon as two consecutive sibling nodes are marked as "free", they are coalesced
|
|
/// into a single node.
|
|
///
|
|
/// In the example below, we just deallocated the rectangle `B`, which is a sibling of
|
|
/// `A` which is free and `C` which is still allocated. `A` and `B` are merged and this
|
|
/// change is reflected on the tree as shown below:
|
|
///
|
|
/// ```ascii
|
|
/// +---+---+---+ # +-------+---+ #
|
|
/// | | |///| | | |///| |
|
|
/// | A | B |/C/| +---+---+ | AB |/C/| +---+---+
|
|
/// | | |///| | | | |///| | |
|
|
/// +---+---+---+ # D +-------+---+ # D
|
|
/// | D | | -> | D | |
|
|
/// | | +-+-+ | | +-+-+
|
|
/// | | | | | | | | |
|
|
/// +-----------+ A B C +-----------+ AB C
|
|
/// ```
|
|
///
|
|
/// ### Merging unique children with their parents
|
|
///
|
|
/// In the previous example `C` was an allocated slot. Let's now deallocate it:
|
|
///
|
|
/// ```ascii
|
|
/// +-------+---+ # +-----------+ # #
|
|
/// | | | | | | | |
|
|
/// | AB | C | +---+---+ | ABC | +---+---+ +---+---+
|
|
/// | | | | | | | | | | |
|
|
/// +-------+---+ # D +-----------+ # D ABC D
|
|
/// | D | | -> | D | | ->
|
|
/// | | +-+-+ | | +
|
|
/// | | | | | | |
|
|
/// +-----------+ AB C +-----------+ ABC
|
|
/// ```
|
|
///
|
|
/// Deallocating `C` allowed it to merge with the free rectangle `AB`, making the
|
|
/// resulting node `ABC` the only child of its parent container. As a result the
|
|
/// node `ABC` was lifted up the tree to replace its parent.
|
|
///
|
|
/// In this example, assuming `D` to also be a free rectangle, `ABC` and `D` would
|
|
/// be immediately merged and the resulting node `ABCD`, also being only child of
|
|
/// its parent container, would replace its parent, turning the tree into a single
|
|
/// node `ABCD`.
|
|
///
|
|
/// ### Limitations
|
|
///
|
|
/// This strategy can miss some opportunities for coalescing free rectangles
|
|
/// when the two sibling containers are split exactly the same way.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```ascii
|
|
/// +---------+------+
|
|
/// | A | B |
|
|
/// | | |
|
|
/// +---------+------+
|
|
/// | C | D |
|
|
/// | | |
|
|
/// +---------+------+
|
|
/// ```
|
|
///
|
|
/// Could be the result of either a vertical followed with two horizontal splits,
|
|
/// or an horizontal then two vertical splits.
|
|
///
|
|
/// ```ascii
|
|
/// Tree | Layout Tree | Layout
|
|
/// -----------------+------------ -----------------+------------
|
|
/// # | # |
|
|
/// | | | |
|
|
/// +---+---+ . .|. Vertical +---+---+ . .|. Horizontal
|
|
/// | | | | | |
|
|
/// # # | or # # |
|
|
/// | | | | | |
|
|
/// +-+-+ . +-+-+ .|. Horizontal +-+-+ . +-+-+ .|. Vertical
|
|
/// | | | | | | | | | |
|
|
/// A B C D | A C B D |
|
|
/// ```
|
|
///
|
|
/// In the former case A can't be merged with C nor B with D because they are not siblings.
|
|
///
|
|
/// For a lot of workloads it is rather rare for two consecutive sibling containers to be
|
|
/// subdivided exactly the same way. In this situation losing the ability to merge rectangles
|
|
/// that aren't under the same container is good compromise between the CPU cost of coalescing
|
|
/// and the fragmentation of the atlas.
|
|
///
|
|
/// This algorithm is, however, not the best solution for very "structured" grid-like
|
|
/// subdivision patterns where the ability to merge across containers would have provided
|
|
/// frequent defragmentation opportunities.
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[derive(Clone)]
|
|
pub struct AtlasAllocator {
|
|
nodes: Vec<Node>,
|
|
/// Free lists are split into a small a medium and a large bucket for faster lookups.
|
|
free_lists: [Vec<AllocIndex>; NUM_BUCKETS],
|
|
|
|
/// Index of the first element of an intrusive linked list of unused nodes.
|
|
/// The `next_sibling` member of unused node serves as the linked list link.
|
|
unused_nodes: AllocIndex,
|
|
|
|
/// We keep a per-node generation counter to reduce the likelihood of ID reuse bugs
|
|
/// going unnoticed.
|
|
generations: Vec<Wrapping<u8>>,
|
|
|
|
/// See `AllocatorOptions`.
|
|
alignment: Size,
|
|
|
|
/// See `AllocatorOptions`.
|
|
small_size_threshold: i32,
|
|
|
|
/// See `AllocatorOptions`.
|
|
large_size_threshold: i32,
|
|
|
|
/// Total size of the atlas.
|
|
size: Size,
|
|
|
|
/// Index of one of the top-level nodes in the tree.
|
|
root_node: AllocIndex,
|
|
}
|
|
|
|
// Some notes about the atlas's tree data structure:
|
|
//
|
|
// (AllocIndex::NONE) (AllocIndex::NONE)
|
|
// ^ ^
|
|
// | parent | parent
|
|
// +---------+ next sibling +---------+ next sibling
|
|
// ... ------|Container|---------------------->|Free |---> (AllocIndex::NONE)
|
|
// ----->| |<----------------------| |
|
|
// +---------+ previous sibling +---------+
|
|
// ^ ^
|
|
// | \____________________________
|
|
// | \
|
|
// | parent \ parent
|
|
// +---------+ next sibling +---------+ next sibling
|
|
// ... ------|Alloc |---------------------->|Container|---> (AllocIndex::NONE)
|
|
// ----->| |<----------------------| |
|
|
// +---------+ previous sibling +---------+
|
|
// ^ ^ ^
|
|
// / | \
|
|
// ...
|
|
//
|
|
// - Nodes are stored in a contiguous vector.
|
|
// - Links between the nodes are indices in the vector (AllocIndex), with a magic value
|
|
// AllocIndex::NONE that means no link.
|
|
// - Nodes have a link to their parent, but parents do not have a link to any of its children because
|
|
// we never need to traverse the structure from parent to child.
|
|
// - All nodes with the same parent are "siblings". An intrusive linked list allows traversing siblings
|
|
// in order. Consecutive siblings share an edge and can be merged if they are both "free".
|
|
// - There isn't necessarily a single root node. The top-most level of the tree can have several siblings
|
|
// and their parent index is equal to AllocIndex::NONE. AtlasAllocator::root_node only needs to refer
|
|
// to one of these top-level nodes.
|
|
// - After a rectangle has been deallocated, the slot for its node in the vector is not part of the
|
|
// tree anymore in the sense that no node from the tree points to it with its sibling list or parent
|
|
// index. This unused node is available for reuse in a future allocation, and is placed in another
|
|
// linked list (also using AllocIndex), a singly linked list this time, which reuses the next_sibling
|
|
// member of the node. So depending on whether the node kind is Unused or not, the next_sibling
|
|
// member is used different things.
|
|
// - We reuse nodes aggressively to avoid growing the vector whenever possible. This is important because
|
|
// the memory footprint of this data structure depends on the capacity of its vectors which don't
|
|
// get deallocated during the lifetime of the atlas.
|
|
// - Because nodes are aggressively reused, the same node indices will come up often. To avoid id reuse
|
|
// bugs, a parallel vector of generation counters is maintained.
|
|
// - The difference between AllocIndex and AllocId is that the latter embeds a generation ID to help
|
|
// finding id reuse bugs. AllocIndex however only contains the node offset. Internal links in the
|
|
// data structure use AllocIndex, and external users of the data structure only get to see AllocId.
|
|
|
|
impl AtlasAllocator {
|
|
/// Create an atlas allocator.
|
|
pub fn new(size: Size) -> Self {
|
|
AtlasAllocator::with_options(size, &DEFAULT_OPTIONS)
|
|
}
|
|
|
|
/// Create an atlas allocator with the provided options.
|
|
pub fn with_options(size: Size, options: &AllocatorOptions) -> Self {
|
|
assert!(options.alignment.width > 0);
|
|
assert!(options.alignment.height > 0);
|
|
assert!(size.width > 0);
|
|
assert!(size.height > 0);
|
|
assert!(options.large_size_threshold >= options.small_size_threshold);
|
|
|
|
let mut free_lists = [Vec::new(), Vec::new(), Vec::new()];
|
|
let bucket = free_list_for_size(
|
|
options.small_size_threshold,
|
|
options.large_size_threshold,
|
|
&size,
|
|
);
|
|
free_lists[bucket].push(AllocIndex(0));
|
|
|
|
AtlasAllocator {
|
|
nodes: vec![Node {
|
|
parent: AllocIndex::NONE,
|
|
next_sibling: AllocIndex::NONE,
|
|
prev_sibling: AllocIndex::NONE,
|
|
rect: size.into(),
|
|
kind: NodeKind::Free,
|
|
orientation: Orientation::Vertical,
|
|
}],
|
|
free_lists,
|
|
generations: vec![Wrapping(0)],
|
|
unused_nodes: AllocIndex::NONE,
|
|
alignment: options.alignment,
|
|
small_size_threshold: options.small_size_threshold,
|
|
large_size_threshold: options.large_size_threshold,
|
|
size,
|
|
root_node: AllocIndex(0),
|
|
}
|
|
}
|
|
|
|
/// The total size of the atlas.
|
|
pub fn size(&self) -> Size {
|
|
self.size
|
|
}
|
|
|
|
/// Allocate a rectangle in the atlas.
|
|
pub fn allocate(&mut self, mut requested_size: Size) -> Option<Allocation> {
|
|
if requested_size.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
adjust_size(self.alignment.width, &mut requested_size.width);
|
|
adjust_size(self.alignment.height, &mut requested_size.height);
|
|
|
|
// Find a suitable free rect.
|
|
let chosen_id = self.find_suitable_rect(&requested_size);
|
|
|
|
if chosen_id.is_none() {
|
|
//println!("failed to allocate {:?}", requested_size);
|
|
//self.print_free_rects();
|
|
|
|
// No suitable free rect!
|
|
return None;
|
|
}
|
|
|
|
let chosen_node = self.nodes[chosen_id.index()].clone();
|
|
let chosen_rect = chosen_node.rect;
|
|
let allocated_rect = Rectangle {
|
|
min: chosen_rect.min,
|
|
max: chosen_rect.min + requested_size.to_vector(),
|
|
};
|
|
let current_orientation = chosen_node.orientation;
|
|
assert_eq!(chosen_node.kind, NodeKind::Free);
|
|
|
|
let (split_rect, leftover_rect, orientation) =
|
|
guillotine_rect(&chosen_node.rect, requested_size, current_orientation);
|
|
|
|
// Update the tree.
|
|
|
|
let allocated_id;
|
|
let split_id;
|
|
let leftover_id;
|
|
|
|
//println!("{:?} -> {:?}", current_orientation, orientation);
|
|
if orientation == current_orientation {
|
|
if !split_rect.is_empty() {
|
|
let next_sibling = chosen_node.next_sibling;
|
|
|
|
split_id = self.new_node();
|
|
self.nodes[split_id.index()] = Node {
|
|
parent: chosen_node.parent,
|
|
next_sibling,
|
|
prev_sibling: chosen_id,
|
|
rect: split_rect,
|
|
kind: NodeKind::Free,
|
|
orientation: current_orientation,
|
|
};
|
|
|
|
self.nodes[chosen_id.index()].next_sibling = split_id;
|
|
if next_sibling.is_some() {
|
|
self.nodes[next_sibling.index()].prev_sibling = split_id;
|
|
}
|
|
} else {
|
|
split_id = AllocIndex::NONE;
|
|
}
|
|
|
|
if !leftover_rect.is_empty() {
|
|
self.nodes[chosen_id.index()].kind = NodeKind::Container;
|
|
|
|
allocated_id = self.new_node();
|
|
leftover_id = self.new_node();
|
|
|
|
self.nodes[allocated_id.index()] = Node {
|
|
parent: chosen_id,
|
|
next_sibling: leftover_id,
|
|
prev_sibling: AllocIndex::NONE,
|
|
rect: allocated_rect,
|
|
kind: NodeKind::Alloc,
|
|
orientation: current_orientation.flipped(),
|
|
};
|
|
|
|
self.nodes[leftover_id.index()] = Node {
|
|
parent: chosen_id,
|
|
next_sibling: AllocIndex::NONE,
|
|
prev_sibling: allocated_id,
|
|
rect: leftover_rect,
|
|
kind: NodeKind::Free,
|
|
orientation: current_orientation.flipped(),
|
|
};
|
|
} else {
|
|
// No need to split for the leftover area, we can allocate directly in the chosen node.
|
|
allocated_id = chosen_id;
|
|
let node = &mut self.nodes[chosen_id.index()];
|
|
node.kind = NodeKind::Alloc;
|
|
node.rect = allocated_rect;
|
|
|
|
leftover_id = AllocIndex::NONE
|
|
}
|
|
} else {
|
|
self.nodes[chosen_id.index()].kind = NodeKind::Container;
|
|
|
|
if !split_rect.is_empty() {
|
|
split_id = self.new_node();
|
|
self.nodes[split_id.index()] = Node {
|
|
parent: chosen_id,
|
|
next_sibling: AllocIndex::NONE,
|
|
prev_sibling: AllocIndex::NONE,
|
|
rect: split_rect,
|
|
kind: NodeKind::Free,
|
|
orientation: current_orientation.flipped(),
|
|
};
|
|
} else {
|
|
split_id = AllocIndex::NONE;
|
|
}
|
|
|
|
if !leftover_rect.is_empty() {
|
|
let container_id = self.new_node();
|
|
self.nodes[container_id.index()] = Node {
|
|
parent: chosen_id,
|
|
next_sibling: split_id,
|
|
prev_sibling: AllocIndex::NONE,
|
|
rect: Rectangle::zero(),
|
|
kind: NodeKind::Container,
|
|
orientation: current_orientation.flipped(),
|
|
};
|
|
|
|
self.nodes[split_id.index()].prev_sibling = container_id;
|
|
|
|
allocated_id = self.new_node();
|
|
leftover_id = self.new_node();
|
|
|
|
self.nodes[allocated_id.index()] = Node {
|
|
parent: container_id,
|
|
next_sibling: leftover_id,
|
|
prev_sibling: AllocIndex::NONE,
|
|
rect: allocated_rect,
|
|
kind: NodeKind::Alloc,
|
|
orientation: current_orientation,
|
|
};
|
|
|
|
self.nodes[leftover_id.index()] = Node {
|
|
parent: container_id,
|
|
next_sibling: AllocIndex::NONE,
|
|
prev_sibling: allocated_id,
|
|
rect: leftover_rect,
|
|
kind: NodeKind::Free,
|
|
orientation: current_orientation,
|
|
};
|
|
} else {
|
|
allocated_id = self.new_node();
|
|
self.nodes[allocated_id.index()] = Node {
|
|
parent: chosen_id,
|
|
next_sibling: split_id,
|
|
prev_sibling: AllocIndex::NONE,
|
|
rect: allocated_rect,
|
|
kind: NodeKind::Alloc,
|
|
orientation: current_orientation.flipped(),
|
|
};
|
|
|
|
self.nodes[split_id.index()].prev_sibling = allocated_id;
|
|
|
|
leftover_id = AllocIndex::NONE;
|
|
}
|
|
}
|
|
|
|
assert_eq!(self.nodes[allocated_id.index()].kind, NodeKind::Alloc);
|
|
|
|
if split_id.is_some() {
|
|
self.add_free_rect(split_id, &split_rect.size());
|
|
}
|
|
|
|
if leftover_id.is_some() {
|
|
self.add_free_rect(leftover_id, &leftover_rect.size());
|
|
}
|
|
|
|
//println!("allocated {:?} split: {:?} leftover: {:?}", allocated_rect, split_rect, leftover_rect);
|
|
//self.print_free_rects();
|
|
|
|
#[cfg(feature = "checks")]
|
|
self.check_tree();
|
|
|
|
Some(Allocation {
|
|
id: self.alloc_id(allocated_id),
|
|
rectangle: allocated_rect,
|
|
})
|
|
}
|
|
|
|
/// Deallocate a rectangle in the atlas.
|
|
pub fn deallocate(&mut self, node_id: AllocId) {
|
|
let mut node_id = self.get_index(node_id);
|
|
|
|
assert!(node_id.index() < self.nodes.len());
|
|
assert_eq!(self.nodes[node_id.index()].kind, NodeKind::Alloc);
|
|
|
|
self.nodes[node_id.index()].kind = NodeKind::Free;
|
|
|
|
loop {
|
|
let orientation = self.nodes[node_id.index()].orientation;
|
|
|
|
let next = self.nodes[node_id.index()].next_sibling;
|
|
let prev = self.nodes[node_id.index()].prev_sibling;
|
|
|
|
// Try to merge with the next node.
|
|
if next.is_some() && self.nodes[next.index()].kind == NodeKind::Free {
|
|
self.merge_siblings(node_id, next, orientation);
|
|
}
|
|
|
|
// Try to merge with the previous node.
|
|
if prev.is_some() && self.nodes[prev.index()].kind == NodeKind::Free {
|
|
self.merge_siblings(prev, node_id, orientation);
|
|
node_id = prev;
|
|
}
|
|
|
|
// If this node is now a unique child. We collapse it into its parent and try to merge
|
|
// again at the parent level.
|
|
let parent = self.nodes[node_id.index()].parent;
|
|
if self.nodes[node_id.index()].prev_sibling.is_none()
|
|
&& self.nodes[node_id.index()].next_sibling.is_none()
|
|
&& parent.is_some()
|
|
{
|
|
debug_assert_eq!(self.nodes[parent.index()].kind, NodeKind::Container);
|
|
|
|
self.mark_node_unused(node_id);
|
|
|
|
// Replace the parent container with a free node.
|
|
self.nodes[parent.index()].rect = self.nodes[node_id.index()].rect;
|
|
self.nodes[parent.index()].kind = NodeKind::Free;
|
|
|
|
// Start again at the parent level.
|
|
node_id = parent;
|
|
} else {
|
|
let size = self.nodes[node_id.index()].rect.size();
|
|
self.add_free_rect(node_id, &size);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "checks")]
|
|
self.check_tree();
|
|
}
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
let root = &self.nodes[self.root_node.index()];
|
|
|
|
root.kind == NodeKind::Free && root.next_sibling.is_none()
|
|
}
|
|
|
|
/// Drop all rectangles, clearing the atlas to its initial state.
|
|
pub fn clear(&mut self) {
|
|
self.nodes.clear();
|
|
self.nodes.push(Node {
|
|
parent: AllocIndex::NONE,
|
|
next_sibling: AllocIndex::NONE,
|
|
prev_sibling: AllocIndex::NONE,
|
|
rect: self.size.into(),
|
|
kind: NodeKind::Free,
|
|
orientation: Orientation::Vertical,
|
|
});
|
|
|
|
self.root_node = AllocIndex(0);
|
|
|
|
self.generations.clear();
|
|
self.generations.push(Wrapping(0));
|
|
|
|
self.unused_nodes = AllocIndex::NONE;
|
|
|
|
let bucket = free_list_for_size(
|
|
self.small_size_threshold,
|
|
self.large_size_threshold,
|
|
&self.size,
|
|
);
|
|
for i in 0..NUM_BUCKETS {
|
|
self.free_lists[i].clear();
|
|
}
|
|
self.free_lists[bucket].push(AllocIndex(0));
|
|
}
|
|
|
|
/// Clear the allocator and reset its size and options.
|
|
pub fn reset(&mut self, size: Size, options: &AllocatorOptions) {
|
|
self.alignment = options.alignment;
|
|
self.small_size_threshold = options.small_size_threshold;
|
|
self.large_size_threshold = options.large_size_threshold;
|
|
self.size = size;
|
|
|
|
self.clear();
|
|
}
|
|
|
|
/// Recompute the allocations in the atlas and returns a list of the changes.
|
|
///
|
|
/// Previous ids and rectangles are not valid anymore after this operation as each id/rectangle
|
|
/// pair is assigned to new values which are communicated in the returned change list.
|
|
/// Rearranging the atlas can help reduce fragmentation.
|
|
pub fn rearrange(&mut self) -> ChangeList {
|
|
let size = self.size;
|
|
self.resize_and_rearrange(size)
|
|
}
|
|
|
|
/// Identical to `AtlasAllocator::rearrange`, also allowing to change the size of the atlas.
|
|
pub fn resize_and_rearrange(&mut self, new_size: Size) -> ChangeList {
|
|
let mut allocs = Vec::with_capacity(self.nodes.len());
|
|
for (i, node) in self.nodes.iter().enumerate() {
|
|
if node.kind != NodeKind::Alloc {
|
|
continue;
|
|
}
|
|
let id = self.alloc_id(AllocIndex(i as u32));
|
|
allocs.push(Allocation {
|
|
id,
|
|
rectangle: node.rect,
|
|
});
|
|
}
|
|
|
|
allocs.sort_by_key(|alloc| safe_area(&alloc.rectangle));
|
|
allocs.reverse();
|
|
|
|
self.size = new_size;
|
|
self.clear();
|
|
|
|
let mut changes = Vec::new();
|
|
let mut failures = Vec::new();
|
|
|
|
for old in allocs {
|
|
let size = old.rectangle.size();
|
|
if let Some(new) = self.allocate(size) {
|
|
changes.push(Change { old, new });
|
|
} else {
|
|
failures.push(old);
|
|
}
|
|
}
|
|
|
|
ChangeList { changes, failures }
|
|
}
|
|
|
|
/// Resize the atlas without changing the allocations.
|
|
///
|
|
/// This method is not allowed to shrink the width or height of the atlas.
|
|
pub fn grow(&mut self, new_size: Size) {
|
|
assert!(new_size.width >= self.size.width);
|
|
assert!(new_size.height >= self.size.height);
|
|
|
|
let old_size = self.size;
|
|
self.size = new_size;
|
|
|
|
let dx = new_size.width - old_size.width;
|
|
let dy = new_size.height - old_size.height;
|
|
|
|
// If there is only one node and it is free, just grow it.
|
|
let root = &mut self.nodes[self.root_node.index()];
|
|
if root.kind == NodeKind::Free && root.rect.size() == old_size {
|
|
root.rect.max = root.rect.min + new_size.to_vector();
|
|
return;
|
|
}
|
|
|
|
let root_orientation = root.orientation;
|
|
let grows_in_root_orientation = match root_orientation {
|
|
Orientation::Horizontal => dx > 0,
|
|
Orientation::Vertical => dy > 0,
|
|
};
|
|
|
|
// If growing along the orientation of the root node, find the right-or-bottom-most sibling
|
|
// and either grow it (if it is free) or append a free node next.
|
|
if grows_in_root_orientation {
|
|
let mut sibling = self.root_node;
|
|
while self.nodes[sibling.index()].next_sibling != AllocIndex::NONE {
|
|
sibling = self.nodes[sibling.index()].next_sibling;
|
|
}
|
|
let node = &mut self.nodes[sibling.index()];
|
|
if node.kind == NodeKind::Free {
|
|
node.rect.max += match root_orientation {
|
|
Orientation::Horizontal => vec2(dx, 0),
|
|
Orientation::Vertical => vec2(0, dy),
|
|
};
|
|
} else {
|
|
let rect = match root_orientation {
|
|
Orientation::Horizontal => {
|
|
let min = point2(node.rect.max.x, node.rect.min.y);
|
|
let max = min + vec2(dx, node.rect.height());
|
|
Rectangle { min, max }
|
|
}
|
|
Orientation::Vertical => {
|
|
let min = point2(node.rect.min.x, node.rect.max.y);
|
|
let max = min + vec2(node.rect.width(), dy);
|
|
Rectangle { min, max }
|
|
}
|
|
};
|
|
|
|
let next = self.new_node();
|
|
self.nodes[sibling.index()].next_sibling = next;
|
|
self.nodes[next.index()] = Node {
|
|
kind: NodeKind::Free,
|
|
rect,
|
|
prev_sibling: sibling,
|
|
next_sibling: AllocIndex::NONE,
|
|
parent: AllocIndex::NONE,
|
|
orientation: root_orientation,
|
|
};
|
|
|
|
self.add_free_rect(next, &rect.size());
|
|
}
|
|
}
|
|
|
|
let grows_in_opposite_orientation = match root_orientation {
|
|
Orientation::Horizontal => dy > 0,
|
|
Orientation::Vertical => dx > 0,
|
|
};
|
|
|
|
if grows_in_opposite_orientation {
|
|
let free_node = self.new_node();
|
|
let new_root = self.new_node();
|
|
|
|
let old_root = self.root_node;
|
|
self.root_node = new_root;
|
|
|
|
let new_root_orientation = root_orientation.flipped();
|
|
|
|
let min = match new_root_orientation {
|
|
Orientation::Horizontal => point2(old_size.width, 0),
|
|
Orientation::Vertical => point2(0, old_size.height),
|
|
};
|
|
let max = point2(new_size.width, new_size.height);
|
|
let rect = Rectangle { min, max };
|
|
|
|
self.nodes[free_node.index()] = Node {
|
|
parent: AllocIndex::NONE,
|
|
prev_sibling: new_root,
|
|
next_sibling: AllocIndex::NONE,
|
|
kind: NodeKind::Free,
|
|
rect,
|
|
orientation: new_root_orientation,
|
|
};
|
|
|
|
self.nodes[new_root.index()] = Node {
|
|
parent: AllocIndex::NONE,
|
|
prev_sibling: AllocIndex::NONE,
|
|
next_sibling: free_node,
|
|
kind: NodeKind::Container,
|
|
rect: Rectangle::zero(),
|
|
orientation: new_root_orientation,
|
|
};
|
|
|
|
self.add_free_rect(free_node, &rect.size());
|
|
|
|
// Update the nodes that need to be re-parented to the new-root.
|
|
|
|
let mut iter = old_root;
|
|
while iter != AllocIndex::NONE {
|
|
self.nodes[iter.index()].parent = new_root;
|
|
iter = self.nodes[iter.index()].next_sibling;
|
|
}
|
|
|
|
// That second loop might not be necessary, I think that the root is always the first
|
|
// sibling.
|
|
let mut iter = self.nodes[old_root.index()].next_sibling;
|
|
while iter != AllocIndex::NONE {
|
|
self.nodes[iter.index()].parent = new_root;
|
|
iter = self.nodes[iter.index()].prev_sibling;
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "checks")]
|
|
self.check_tree();
|
|
}
|
|
|
|
/// Invoke a callback for each free rectangle in the atlas.
|
|
pub fn for_each_free_rectangle<F>(&self, mut callback: F)
|
|
where
|
|
F: FnMut(&Rectangle),
|
|
{
|
|
for node in &self.nodes {
|
|
if node.kind == NodeKind::Free {
|
|
callback(&node.rect);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Invoke a callback for each allocated rectangle in the atlas.
|
|
pub fn for_each_allocated_rectangle<F>(&self, mut callback: F)
|
|
where
|
|
F: FnMut(AllocId, &Rectangle),
|
|
{
|
|
for (i, node) in self.nodes.iter().enumerate() {
|
|
if node.kind != NodeKind::Alloc {
|
|
continue;
|
|
}
|
|
|
|
let id = self.alloc_id(AllocIndex(i as u32));
|
|
|
|
callback(id, &node.rect);
|
|
}
|
|
}
|
|
|
|
fn find_suitable_rect(&mut self, requested_size: &Size) -> AllocIndex {
|
|
let ideal_bucket = free_list_for_size(
|
|
self.small_size_threshold,
|
|
self.large_size_threshold,
|
|
requested_size,
|
|
);
|
|
|
|
let use_worst_fit = ideal_bucket == LARGE_BUCKET;
|
|
for bucket in ideal_bucket..NUM_BUCKETS {
|
|
let mut candidate_score = if use_worst_fit { 0 } else { std::i32::MAX };
|
|
let mut candidate = None;
|
|
|
|
let mut freelist_idx = 0;
|
|
while freelist_idx < self.free_lists[bucket].len() {
|
|
let id = self.free_lists[bucket][freelist_idx];
|
|
|
|
// During tree simplification we don't remove merged nodes from the free list, so we have
|
|
// to handle it here.
|
|
// This is a tad awkward, but lets us avoid having to maintain a doubly linked list for
|
|
// the free list (which would be needed to remove nodes during tree simplification).
|
|
if self.nodes[id.index()].kind != NodeKind::Free {
|
|
// remove the element from the free list
|
|
self.free_lists[bucket].swap_remove(freelist_idx);
|
|
continue;
|
|
}
|
|
|
|
let size = self.nodes[id.index()].rect.size();
|
|
let dx = size.width - requested_size.width;
|
|
let dy = size.height - requested_size.height;
|
|
|
|
if dx >= 0 && dy >= 0 {
|
|
if dx == 0 || dy == 0 {
|
|
// Perfect fit!
|
|
candidate = Some((id, freelist_idx));
|
|
break;
|
|
}
|
|
|
|
// Favor the largest minimum dimension, except for small
|
|
// allocations.
|
|
let score = i32::min(dx, dy);
|
|
if (use_worst_fit && score > candidate_score)
|
|
|| (!use_worst_fit && score < candidate_score)
|
|
{
|
|
candidate_score = score;
|
|
candidate = Some((id, freelist_idx));
|
|
}
|
|
}
|
|
|
|
freelist_idx += 1;
|
|
}
|
|
|
|
if let Some((id, freelist_idx)) = candidate {
|
|
self.free_lists[bucket].swap_remove(freelist_idx);
|
|
return id;
|
|
}
|
|
}
|
|
|
|
AllocIndex::NONE
|
|
}
|
|
|
|
fn new_node(&mut self) -> AllocIndex {
|
|
let idx = self.unused_nodes;
|
|
if idx.index() < self.nodes.len() {
|
|
self.unused_nodes = self.nodes[idx.index()].next_sibling;
|
|
self.generations[idx.index()] += Wrapping(1);
|
|
debug_assert_eq!(self.nodes[idx.index()].kind, NodeKind::Unused);
|
|
return idx;
|
|
}
|
|
|
|
self.nodes.push(Node {
|
|
parent: AllocIndex::NONE,
|
|
next_sibling: AllocIndex::NONE,
|
|
prev_sibling: AllocIndex::NONE,
|
|
rect: Rectangle::zero(),
|
|
kind: NodeKind::Unused,
|
|
orientation: Orientation::Horizontal,
|
|
});
|
|
|
|
self.generations.push(Wrapping(0));
|
|
|
|
AllocIndex(self.nodes.len() as u32 - 1)
|
|
}
|
|
|
|
fn mark_node_unused(&mut self, id: AllocIndex) {
|
|
debug_assert!(self.nodes[id.index()].kind != NodeKind::Unused);
|
|
self.nodes[id.index()].kind = NodeKind::Unused;
|
|
self.nodes[id.index()].next_sibling = self.unused_nodes;
|
|
self.unused_nodes = id;
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn print_free_rects(&self) {
|
|
println!("Large:");
|
|
for &id in &self.free_lists[LARGE_BUCKET] {
|
|
if self.nodes[id.index()].kind == NodeKind::Free {
|
|
println!(" - {:?} #{:?}", self.nodes[id.index()].rect, id);
|
|
}
|
|
}
|
|
println!("Medium:");
|
|
for &id in &self.free_lists[MEDIUM_BUCKET] {
|
|
if self.nodes[id.index()].kind == NodeKind::Free {
|
|
println!(" - {:?} #{:?}", self.nodes[id.index()].rect, id);
|
|
}
|
|
}
|
|
println!("Small:");
|
|
for &id in &self.free_lists[SMALL_BUCKET] {
|
|
if self.nodes[id.index()].kind == NodeKind::Free {
|
|
println!(" - {:?} #{:?}", self.nodes[id.index()].rect, id);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "checks")]
|
|
fn check_siblings(&self, id: AllocIndex, next: AllocIndex, orientation: Orientation) {
|
|
if next.is_none() {
|
|
return;
|
|
}
|
|
|
|
if self.nodes[next.index()].prev_sibling != id {
|
|
panic!("error: #{:?}'s next sibling #{:?} has prev sibling #{:?}", id, next, self.nodes[next.index()].prev_sibling);
|
|
}
|
|
assert_eq!(self.nodes[next.index()].prev_sibling, id);
|
|
|
|
match self.nodes[id.index()].kind {
|
|
NodeKind::Container | NodeKind::Unused => {
|
|
return;
|
|
}
|
|
_ => {}
|
|
}
|
|
match self.nodes[next.index()].kind {
|
|
NodeKind::Container | NodeKind::Unused => {
|
|
return;
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
let r1 = self.nodes[id.index()].rect;
|
|
let r2 = self.nodes[next.index()].rect;
|
|
match orientation {
|
|
Orientation::Horizontal => {
|
|
assert_eq!(r1.min.y, r2.min.y);
|
|
assert_eq!(r1.max.y, r2.max.y);
|
|
}
|
|
Orientation::Vertical => {
|
|
assert_eq!(r1.min.x, r2.min.x);
|
|
assert_eq!(r1.max.x, r2.max.x);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "checks")]
|
|
fn check_tree(&self) {
|
|
for node_idx in 0..self.nodes.len() {
|
|
let node = &self.nodes[node_idx];
|
|
|
|
if node.kind == NodeKind::Unused {
|
|
if node.next_sibling.is_some() {
|
|
assert_eq!(self.nodes[node.next_sibling.index()].kind, NodeKind::Unused);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
let mut iter = node.next_sibling;
|
|
while iter.is_some() {
|
|
assert_eq!(self.nodes[iter.index()].orientation, node.orientation);
|
|
assert_eq!(self.nodes[iter.index()].parent, node.parent);
|
|
assert!(self.nodes[iter.index()].kind != NodeKind::Unused);
|
|
let next = self.nodes[iter.index()].next_sibling;
|
|
|
|
#[cfg(feature = "checks")]
|
|
self.check_siblings(iter, next, node.orientation);
|
|
|
|
iter = next;
|
|
}
|
|
|
|
if node.parent.is_some() {
|
|
if self.nodes[node.parent.index()].kind != NodeKind::Container {
|
|
panic!("error: child: {:?} parent: {:?}", node_idx, node.parent);
|
|
}
|
|
assert_eq!(
|
|
self.nodes[node.parent.index()].orientation,
|
|
node.orientation.flipped()
|
|
);
|
|
assert_eq!(self.nodes[node.parent.index()].kind, NodeKind::Container);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn add_free_rect(&mut self, id: AllocIndex, size: &Size) {
|
|
debug_assert_eq!(self.nodes[id.index()].kind, NodeKind::Free);
|
|
let bucket = free_list_for_size(self.small_size_threshold, self.large_size_threshold, size);
|
|
//println!("add free rect #{:?} size {} bucket {}", id, size, bucket);
|
|
self.free_lists[bucket].push(id);
|
|
}
|
|
|
|
// Merge `next` into `node` and append `next` to a list of available `nodes`vector slots.
|
|
fn merge_siblings(&mut self, node: AllocIndex, next: AllocIndex, orientation: Orientation) {
|
|
debug_assert_eq!(self.nodes[node.index()].kind, NodeKind::Free);
|
|
debug_assert_eq!(self.nodes[next.index()].kind, NodeKind::Free);
|
|
let r1 = self.nodes[node.index()].rect;
|
|
let r2 = self.nodes[next.index()].rect;
|
|
//println!("merge {} #{:?} and {} #{:?} {:?}", r1, node, r2, next, orientation);
|
|
let merge_size = self.nodes[next.index()].rect.size();
|
|
match orientation {
|
|
Orientation::Horizontal => {
|
|
debug_assert_eq!(r1.min.y, r2.min.y);
|
|
debug_assert_eq!(r1.max.y, r2.max.y);
|
|
self.nodes[node.index()].rect.max.x += merge_size.width;
|
|
}
|
|
Orientation::Vertical => {
|
|
debug_assert_eq!(r1.min.x, r2.min.x);
|
|
debug_assert_eq!(r1.max.x, r2.max.x);
|
|
self.nodes[node.index()].rect.max.y += merge_size.height;
|
|
}
|
|
}
|
|
|
|
// Remove the merged node from the sibling list.
|
|
let next_next = self.nodes[next.index()].next_sibling;
|
|
self.nodes[node.index()].next_sibling = next_next;
|
|
if next_next.is_some() {
|
|
self.nodes[next_next.index()].prev_sibling = node;
|
|
}
|
|
|
|
// Add the merged node to the list of available slots in the nodes vector.
|
|
self.mark_node_unused(next);
|
|
}
|
|
|
|
fn alloc_id(&self, index: AllocIndex) -> AllocId {
|
|
let generation = self.generations[index.index()].0 as u32;
|
|
debug_assert!(index.0 & IDX_MASK == index.0);
|
|
AllocId(index.0 + (generation << 24))
|
|
}
|
|
|
|
fn get_index(&self, id: AllocId) -> AllocIndex {
|
|
let idx = id.0 & IDX_MASK;
|
|
let expected_generation = (self.generations[idx as usize].0 as u32) << 24;
|
|
assert_eq!(id.0 & GEN_MASK, expected_generation);
|
|
AllocIndex(idx)
|
|
}
|
|
}
|
|
|
|
impl std::ops::Index<AllocId> for AtlasAllocator {
|
|
type Output = Rectangle;
|
|
fn index(&self, index: AllocId) -> &Rectangle {
|
|
let idx = self.get_index(index);
|
|
|
|
&self.nodes[idx.index()].rect
|
|
}
|
|
}
|
|
|
|
/// A simpler atlas allocator implementation that can allocate rectangles but not deallocate them.
|
|
pub struct SimpleAtlasAllocator {
|
|
free_rects: [Vec<Rectangle>; 3],
|
|
alignment: Size,
|
|
small_size_threshold: i32,
|
|
large_size_threshold: i32,
|
|
size: Size,
|
|
}
|
|
|
|
impl SimpleAtlasAllocator {
|
|
/// Create a simple atlas allocator with default options.
|
|
pub fn new(size: Size) -> Self {
|
|
Self::with_options(size, &DEFAULT_OPTIONS)
|
|
}
|
|
|
|
/// Create a simple atlas allocator with the provided options.
|
|
pub fn with_options(size: Size, options: &AllocatorOptions) -> Self {
|
|
let bucket = free_list_for_size(
|
|
options.small_size_threshold,
|
|
options.large_size_threshold,
|
|
&size,
|
|
);
|
|
|
|
let mut free_rects = [Vec::new(), Vec::new(), Vec::new()];
|
|
free_rects[bucket].push(size.into());
|
|
|
|
SimpleAtlasAllocator {
|
|
free_rects,
|
|
alignment: options.alignment,
|
|
small_size_threshold: options.small_size_threshold,
|
|
large_size_threshold: options.large_size_threshold,
|
|
size,
|
|
}
|
|
}
|
|
|
|
/// Drop all rectangles, clearing the atlas to its initial state.
|
|
pub fn clear(&mut self) {
|
|
|
|
for i in 0..NUM_BUCKETS {
|
|
self.free_rects[i].clear();
|
|
}
|
|
|
|
let bucket = free_list_for_size(
|
|
self.small_size_threshold,
|
|
self.large_size_threshold,
|
|
&self.size,
|
|
);
|
|
|
|
self.free_rects[bucket].push(self.size.into());
|
|
}
|
|
|
|
/// Clear the allocator and reset its size and options.
|
|
pub fn reset(&mut self, size: Size, options: &AllocatorOptions) {
|
|
self.alignment = options.alignment;
|
|
self.small_size_threshold = options.small_size_threshold;
|
|
self.large_size_threshold = options.large_size_threshold;
|
|
self.size = size;
|
|
|
|
self.clear();
|
|
}
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
for b in 0..NUM_BUCKETS {
|
|
for rect in &self.free_rects[b] {
|
|
return rect.size() == self.size;
|
|
}
|
|
}
|
|
|
|
// This should be unreachable.
|
|
return false;
|
|
}
|
|
|
|
/// The total size of the atlas.
|
|
pub fn size(&self) -> Size {
|
|
self.size
|
|
}
|
|
|
|
/// Allocate a rectangle in the atlas.
|
|
pub fn allocate(&mut self, mut requested_size: Size) -> Option<Rectangle> {
|
|
if requested_size.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
adjust_size(self.alignment.width, &mut requested_size.width);
|
|
adjust_size(self.alignment.height, &mut requested_size.height);
|
|
|
|
let ideal_bucket = free_list_for_size(
|
|
self.small_size_threshold,
|
|
self.large_size_threshold,
|
|
&requested_size,
|
|
);
|
|
|
|
let use_worst_fit = ideal_bucket == LARGE_BUCKET;
|
|
|
|
let mut chosen_rect = None;
|
|
for bucket in ideal_bucket..NUM_BUCKETS {
|
|
let mut candidate_score = if use_worst_fit { 0 } else { std::i32::MAX };
|
|
let mut candidate = None;
|
|
|
|
for (index, rect) in self.free_rects[bucket].iter().enumerate() {
|
|
let dx = rect.width() - requested_size.width;
|
|
let dy = rect.height() - requested_size.height;
|
|
|
|
if dx >= 0 && dy >= 0 {
|
|
if dx == 0 || dy == 0 {
|
|
// Perfect fit!
|
|
candidate = Some(index);
|
|
break;
|
|
}
|
|
|
|
let score = i32::min(dx, dy);
|
|
if (use_worst_fit && score > candidate_score)
|
|
|| (!use_worst_fit && score < candidate_score)
|
|
{
|
|
candidate_score = score;
|
|
candidate = Some(index);
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(index) = candidate {
|
|
let rect = self.free_rects[bucket].remove(index);
|
|
chosen_rect = Some(rect);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if let Some(rect) = chosen_rect {
|
|
let (split_rect, leftover_rect, _) =
|
|
guillotine_rect(&rect, requested_size, Orientation::Vertical);
|
|
self.add_free_rect(&split_rect);
|
|
self.add_free_rect(&leftover_rect);
|
|
|
|
return Some(Rectangle {
|
|
min: rect.min,
|
|
max: rect.min + requested_size.to_vector(),
|
|
});
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
/// Resize the atlas without changing the allocations.
|
|
///
|
|
/// This method is not allowed to shrink the width or height of the atlas.
|
|
pub fn grow(&mut self, new_size: Size) {
|
|
assert!(new_size.width >= self.size.width);
|
|
assert!(new_size.height >= self.size.height);
|
|
|
|
let (split_rect, leftover_rect, _) =
|
|
guillotine_rect(&new_size.into(), self.size, Orientation::Vertical);
|
|
|
|
self.size = new_size;
|
|
|
|
self.add_free_rect(&split_rect);
|
|
self.add_free_rect(&leftover_rect);
|
|
}
|
|
|
|
/// Initialize this simple allocator with the content of an atlas allocator.
|
|
pub fn init_from_allocator(&mut self, src: &AtlasAllocator) {
|
|
self.size = src.size;
|
|
self.small_size_threshold = src.small_size_threshold;
|
|
self.large_size_threshold = src.large_size_threshold;
|
|
|
|
for bucket in 0..NUM_BUCKETS {
|
|
for id in src.free_lists[bucket].iter() {
|
|
// During tree simplification we don't remove merged nodes from the free list, so we have
|
|
// to handle it here.
|
|
// This is a tad awkward, but lets us avoid having to maintain a doubly linked list for
|
|
// the free list (which would be needed to remove nodes during tree simplification).
|
|
if src.nodes[id.index()].kind != NodeKind::Free {
|
|
continue;
|
|
}
|
|
|
|
self.free_rects[bucket].push(src.nodes[id.index()].rect);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn add_free_rect(&mut self, rect: &Rectangle) {
|
|
if rect.width() < self.alignment.width || rect.height() < self.alignment.height {
|
|
return;
|
|
}
|
|
|
|
let bucket = free_list_for_size(
|
|
self.small_size_threshold,
|
|
self.large_size_threshold,
|
|
&rect.size(),
|
|
);
|
|
|
|
self.free_rects[bucket].push(*rect);
|
|
}
|
|
}
|
|
|
|
fn adjust_size(alignment: i32, size: &mut i32) {
|
|
let rem = *size % alignment;
|
|
if rem > 0 {
|
|
*size += alignment - rem;
|
|
}
|
|
}
|
|
|
|
/// Compute the area, saturating at i32::MAX instead of overflowing.
|
|
fn safe_area(rect: &Rectangle) -> i32 {
|
|
rect.width().checked_mul(rect.height()).unwrap_or(std::i32::MAX)
|
|
}
|
|
|
|
fn guillotine_rect(
|
|
chosen_rect: &Rectangle,
|
|
requested_size: Size,
|
|
default_orientation: Orientation,
|
|
) -> (Rectangle, Rectangle, Orientation) {
|
|
// Decide whether to split horizontally or vertically.
|
|
//
|
|
// If the chosen free rectangle is bigger than the requested size, we subdivide it
|
|
// into an allocated rectangle, a split rectangle and a leftover rectangle:
|
|
//
|
|
// +-----------+-------------+
|
|
// |///////////| |
|
|
// |/allocated/| |
|
|
// |///////////| |
|
|
// +-----------+ |
|
|
// | |
|
|
// | chosen |
|
|
// | |
|
|
// +-------------------------+
|
|
//
|
|
// Will be split into either:
|
|
//
|
|
// +-----------+-------------+
|
|
// |///////////| |
|
|
// |/allocated/| leftover |
|
|
// |///////////| |
|
|
// +-----------+-------------+
|
|
// | |
|
|
// | split |
|
|
// | |
|
|
// +-------------------------+
|
|
//
|
|
// or:
|
|
//
|
|
// +-----------+-------------+
|
|
// |///////////| |
|
|
// |/allocated/| |
|
|
// |///////////| split |
|
|
// +-----------+ |
|
|
// | | |
|
|
// | leftover | |
|
|
// | | |
|
|
// +-----------+-------------+
|
|
|
|
let candidate_leftover_rect_to_right = Rectangle {
|
|
min: chosen_rect.min + vec2(requested_size.width, 0),
|
|
max: point2(chosen_rect.max.x, chosen_rect.min.y + requested_size.height),
|
|
};
|
|
let candidate_leftover_rect_to_bottom = Rectangle {
|
|
min: chosen_rect.min + vec2(0, requested_size.height),
|
|
max: point2(chosen_rect.min.x + requested_size.width, chosen_rect.max.y),
|
|
};
|
|
|
|
let split_rect;
|
|
let leftover_rect;
|
|
let orientation;
|
|
if requested_size == chosen_rect.size() {
|
|
// Perfect fit.
|
|
orientation = default_orientation;
|
|
split_rect = Rectangle::zero();
|
|
leftover_rect = Rectangle::zero();
|
|
} else if safe_area(&candidate_leftover_rect_to_right) > safe_area(&candidate_leftover_rect_to_bottom) {
|
|
leftover_rect = candidate_leftover_rect_to_bottom;
|
|
split_rect = Rectangle {
|
|
min: candidate_leftover_rect_to_right.min,
|
|
max: point2(candidate_leftover_rect_to_right.max.x, chosen_rect.max.y),
|
|
};
|
|
orientation = Orientation::Horizontal;
|
|
} else {
|
|
leftover_rect = candidate_leftover_rect_to_right;
|
|
split_rect = Rectangle {
|
|
min: candidate_leftover_rect_to_bottom.min,
|
|
max: point2(chosen_rect.max.x, candidate_leftover_rect_to_bottom.max.y),
|
|
};
|
|
orientation = Orientation::Vertical;
|
|
}
|
|
|
|
(split_rect, leftover_rect, orientation)
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
pub struct Allocation {
|
|
pub id: AllocId,
|
|
pub rectangle: Rectangle,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
pub struct Change {
|
|
pub old: Allocation,
|
|
pub new: Allocation,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct ChangeList {
|
|
pub changes: Vec<Change>,
|
|
pub failures: Vec<Allocation>,
|
|
}
|
|
|
|
impl ChangeList {
|
|
pub fn empty() -> Self {
|
|
ChangeList {
|
|
changes: Vec::new(),
|
|
failures: Vec::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Dump a visual representation of the atlas in SVG format.
|
|
pub fn dump_svg(atlas: &AtlasAllocator, output: &mut dyn std::io::Write) -> std::io::Result<()> {
|
|
use svg_fmt::*;
|
|
|
|
writeln!(
|
|
output,
|
|
"{}",
|
|
BeginSvg {
|
|
w: atlas.size.width as f32,
|
|
h: atlas.size.height as f32
|
|
}
|
|
)?;
|
|
|
|
dump_into_svg(atlas, None, output)?;
|
|
|
|
writeln!(output, "{}", EndSvg)
|
|
}
|
|
|
|
/// Dump a visual representation of the atlas in SVG, omitting the beginning and end of the
|
|
/// SVG document, so that it can be included in a larger document.
|
|
///
|
|
/// If a rectange is provided, translate and scale the output to fit it.
|
|
pub fn dump_into_svg(atlas: &AtlasAllocator, rect: Option<&Rectangle>, output: &mut dyn std::io::Write) -> std::io::Result<()> {
|
|
use svg_fmt::*;
|
|
|
|
let (sx, sy, tx, ty) = if let Some(rect) = rect {
|
|
(
|
|
rect.width() as f32 / atlas.size.width as f32,
|
|
rect.height() as f32 / atlas.size.height as f32,
|
|
rect.min.x as f32,
|
|
rect.min.y as f32,
|
|
)
|
|
} else {
|
|
(1.0, 1.0, 0.0, 0.0)
|
|
};
|
|
|
|
for node in &atlas.nodes {
|
|
let color = match node.kind {
|
|
NodeKind::Free => rgb(50, 50, 50),
|
|
NodeKind::Alloc => rgb(70, 70, 180),
|
|
_ => {
|
|
continue;
|
|
}
|
|
};
|
|
|
|
let (x, y) = node.rect.min.to_f32().to_tuple();
|
|
let (w, h) = node.rect.size().to_f32().to_tuple();
|
|
|
|
writeln!(
|
|
output,
|
|
r#" {}"#,
|
|
rectangle(tx + x * sx, ty + y * sy, w * sx, h * sy)
|
|
.fill(color)
|
|
.stroke(Stroke::Color(black(), 1.0))
|
|
)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn atlas_basic() {
|
|
let mut atlas = AtlasAllocator::new(size2(1000, 1000));
|
|
|
|
let full = atlas.allocate(size2(1000, 1000)).unwrap().id;
|
|
assert!(atlas.allocate(size2(1, 1)).is_none());
|
|
|
|
atlas.deallocate(full);
|
|
|
|
let a = atlas.allocate(size2(100, 1000)).unwrap().id;
|
|
let b = atlas.allocate(size2(900, 200)).unwrap().id;
|
|
let c = atlas.allocate(size2(300, 200)).unwrap().id;
|
|
let d = atlas.allocate(size2(200, 300)).unwrap().id;
|
|
let e = atlas.allocate(size2(100, 300)).unwrap().id;
|
|
let f = atlas.allocate(size2(100, 300)).unwrap().id;
|
|
let g = atlas.allocate(size2(100, 300)).unwrap().id;
|
|
|
|
atlas.deallocate(b);
|
|
atlas.deallocate(f);
|
|
atlas.deallocate(c);
|
|
atlas.deallocate(e);
|
|
let h = atlas.allocate(size2(500, 200)).unwrap().id;
|
|
atlas.deallocate(a);
|
|
let i = atlas.allocate(size2(500, 200)).unwrap().id;
|
|
atlas.deallocate(g);
|
|
atlas.deallocate(h);
|
|
atlas.deallocate(d);
|
|
atlas.deallocate(i);
|
|
|
|
let full = atlas.allocate(size2(1000, 1000)).unwrap().id;
|
|
assert!(atlas.allocate(size2(1, 1)).is_none());
|
|
atlas.deallocate(full);
|
|
}
|
|
|
|
#[test]
|
|
fn atlas_random_test() {
|
|
let mut atlas = AtlasAllocator::with_options(
|
|
size2(1000, 1000),
|
|
&AllocatorOptions {
|
|
alignment: size2(5, 2),
|
|
..DEFAULT_OPTIONS
|
|
},
|
|
);
|
|
|
|
let a = 1103515245;
|
|
let c = 12345;
|
|
let m = usize::pow(2, 31);
|
|
let mut seed: usize = 37;
|
|
|
|
let mut rand = || {
|
|
seed = (a * seed + c) % m;
|
|
seed
|
|
};
|
|
|
|
let mut n: usize = 0;
|
|
let mut misses: usize = 0;
|
|
|
|
let mut allocated = Vec::new();
|
|
for _ in 0..500000 {
|
|
if rand() % 5 > 2 && !allocated.is_empty() {
|
|
// deallocate something
|
|
let nth = rand() % allocated.len();
|
|
let id = allocated[nth];
|
|
allocated.remove(nth);
|
|
|
|
atlas.deallocate(id);
|
|
} else {
|
|
// allocate something
|
|
let size = size2((rand() % 300) as i32 + 5, (rand() % 300) as i32 + 5);
|
|
|
|
if let Some(alloc) = atlas.allocate(size) {
|
|
allocated.push(alloc.id);
|
|
n += 1;
|
|
} else {
|
|
misses += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
while let Some(id) = allocated.pop() {
|
|
atlas.deallocate(id);
|
|
}
|
|
|
|
println!("added/removed {} rectangles, {} misses", n, misses);
|
|
println!(
|
|
"nodes.cap: {}, free_list.cap: {}/{}/{}",
|
|
atlas.nodes.capacity(),
|
|
atlas.free_lists[LARGE_BUCKET].capacity(),
|
|
atlas.free_lists[MEDIUM_BUCKET].capacity(),
|
|
atlas.free_lists[SMALL_BUCKET].capacity(),
|
|
);
|
|
|
|
let full = atlas.allocate(size2(1000, 1000)).unwrap().id;
|
|
assert!(atlas.allocate(size2(1, 1)).is_none());
|
|
atlas.deallocate(full);
|
|
}
|
|
|
|
#[test]
|
|
fn test_grow() {
|
|
let mut atlas = AtlasAllocator::new(size2(1000, 1000));
|
|
|
|
atlas.grow(size2(2000, 2000));
|
|
|
|
let full = atlas.allocate(size2(2000, 2000)).unwrap().id;
|
|
assert!(atlas.allocate(size2(1, 1)).is_none());
|
|
atlas.deallocate(full);
|
|
|
|
let a = atlas.allocate(size2(100, 100)).unwrap().id;
|
|
|
|
atlas.grow(size2(3000, 3000));
|
|
|
|
let b = atlas.allocate(size2(1000, 2900)).unwrap().id;
|
|
|
|
atlas.grow(size2(4000, 4000));
|
|
|
|
atlas.deallocate(b);
|
|
atlas.deallocate(a);
|
|
|
|
let full = atlas.allocate(size2(4000, 4000)).unwrap().id;
|
|
assert!(atlas.allocate(size2(1, 1)).is_none());
|
|
atlas.deallocate(full);
|
|
}
|
|
|
|
#[test]
|
|
fn clear_empty() {
|
|
let mut atlas = AtlasAllocator::new(size2(1000, 1000));
|
|
|
|
assert!(atlas.is_empty());
|
|
|
|
assert!(atlas.allocate(size2(10, 10)).is_some());
|
|
assert!(!atlas.is_empty());
|
|
|
|
atlas.clear();
|
|
assert!(atlas.is_empty());
|
|
|
|
let a = atlas.allocate(size2(10, 10)).unwrap().id;
|
|
let b = atlas.allocate(size2(20, 20)).unwrap().id;
|
|
assert!(!atlas.is_empty());
|
|
|
|
atlas.deallocate(b);
|
|
atlas.deallocate(a);
|
|
assert!(atlas.is_empty());
|
|
|
|
atlas.clear();
|
|
assert!(atlas.is_empty());
|
|
|
|
atlas.clear();
|
|
assert!(atlas.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn simple_atlas() {
|
|
let mut atlas = SimpleAtlasAllocator::new(size2(1000, 1000));
|
|
|
|
assert!(atlas.allocate(size2(1, 1001)).is_none());
|
|
assert!(atlas.allocate(size2(1001, 1)).is_none());
|
|
|
|
let mut rectangles = Vec::new();
|
|
rectangles.push(atlas.allocate(size2(100, 1000)).unwrap());
|
|
rectangles.push(atlas.allocate(size2(900, 200)).unwrap());
|
|
rectangles.push(atlas.allocate(size2(300, 200)).unwrap());
|
|
rectangles.push(atlas.allocate(size2(200, 300)).unwrap());
|
|
rectangles.push(atlas.allocate(size2(100, 300)).unwrap());
|
|
rectangles.push(atlas.allocate(size2(100, 300)).unwrap());
|
|
rectangles.push(atlas.allocate(size2(100, 300)).unwrap());
|
|
assert!(atlas.allocate(size2(800, 800)).is_none());
|
|
|
|
for i in 0..rectangles.len() {
|
|
for j in 0..rectangles.len() {
|
|
if i == j {
|
|
continue;
|
|
}
|
|
|
|
assert!(!rectangles[i].intersects(&rectangles[j]));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn allocate_zero() {
|
|
let mut atlas = SimpleAtlasAllocator::new(size2(1000, 1000));
|
|
|
|
assert!(atlas.allocate(size2(0, 0)).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn allocate_negative() {
|
|
let mut atlas = SimpleAtlasAllocator::new(size2(1000, 1000));
|
|
|
|
assert!(atlas.allocate(size2(-1, 1)).is_none());
|
|
assert!(atlas.allocate(size2(1, -1)).is_none());
|
|
assert!(atlas.allocate(size2(-1, -1)).is_none());
|
|
|
|
assert!(atlas.allocate(size2(-167114179, -718142)).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn issue_25() {
|
|
let mut allocator = AtlasAllocator::new(Size::new(65536, 65536));
|
|
allocator.allocate(Size::new(2,2));
|
|
allocator.allocate(Size::new(65500,2));
|
|
allocator.allocate(Size::new(2, 65500));
|
|
}
|