Add a simple quadtree-based texture allocator
This commit is contained in:
parent
2db43797c3
commit
b8f622203a
|
@ -0,0 +1,229 @@
|
||||||
|
// pathfinder/renderer/src/allocator.rs
|
||||||
|
//
|
||||||
|
// Copyright © 2020 The Pathfinder Project Developers.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
//! A simple quadtree-based texture allocator.
|
||||||
|
|
||||||
|
use pathfinder_geometry::rect::RectI;
|
||||||
|
use pathfinder_geometry::vector::Vector2I;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TextureAllocator {
|
||||||
|
root: TreeNode,
|
||||||
|
size: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
|
pub struct TextureLocation {
|
||||||
|
pub rect: RectI,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum TreeNode {
|
||||||
|
EmptyLeaf,
|
||||||
|
FullLeaf,
|
||||||
|
// Top left, top right, bottom left, and bottom right, in that order.
|
||||||
|
Parent([Box<TreeNode>; 4]),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextureAllocator {
|
||||||
|
#[inline]
|
||||||
|
pub fn new(size: u32) -> TextureAllocator {
|
||||||
|
// Make sure that the size is a power of two.
|
||||||
|
debug_assert_eq!(size & (size - 1), 0);
|
||||||
|
TextureAllocator { root: TreeNode::EmptyLeaf, size }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn allocate(&mut self, requested_size: Vector2I) -> Option<TextureLocation> {
|
||||||
|
let requested_length = requested_size.x().max(requested_size.y()) as u32;
|
||||||
|
self.root.allocate(Vector2I::default(), self.size, requested_length)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn free(&mut self, location: TextureLocation) {
|
||||||
|
let requested_length = location.rect.width() as u32;
|
||||||
|
self.root.free(Vector2I::default(), self.size, location.rect.origin(), requested_length)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
match self.root {
|
||||||
|
TreeNode::EmptyLeaf => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TreeNode {
|
||||||
|
// Invariant: `requested_size` must be a power of two.
|
||||||
|
fn allocate(&mut self, this_origin: Vector2I, this_size: u32, requested_size: u32)
|
||||||
|
-> Option<TextureLocation> {
|
||||||
|
if let TreeNode::FullLeaf = *self {
|
||||||
|
// No room here.
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if this_size < requested_size {
|
||||||
|
// Doesn't fit.
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate here or split, as necessary.
|
||||||
|
if let TreeNode::EmptyLeaf = *self {
|
||||||
|
// Do we have a perfect fit?
|
||||||
|
if this_size == requested_size {
|
||||||
|
*self = TreeNode::FullLeaf;
|
||||||
|
return Some(TextureLocation {
|
||||||
|
rect: RectI::new(this_origin, Vector2I::splat(this_size as i32)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split.
|
||||||
|
*self = TreeNode::Parent([
|
||||||
|
Box::new(TreeNode::EmptyLeaf),
|
||||||
|
Box::new(TreeNode::EmptyLeaf),
|
||||||
|
Box::new(TreeNode::EmptyLeaf),
|
||||||
|
Box::new(TreeNode::EmptyLeaf),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recurse into children.
|
||||||
|
match *self {
|
||||||
|
TreeNode::Parent(ref mut kids) => {
|
||||||
|
let kid_size = this_size / 2;
|
||||||
|
if let Some(origin) = kids[0].allocate(this_origin, kid_size, requested_size) {
|
||||||
|
return Some(origin);
|
||||||
|
}
|
||||||
|
if let Some(origin) =
|
||||||
|
kids[1].allocate(this_origin + Vector2I::new(kid_size as i32, 0),
|
||||||
|
kid_size,
|
||||||
|
requested_size) {
|
||||||
|
return Some(origin);
|
||||||
|
}
|
||||||
|
if let Some(origin) =
|
||||||
|
kids[2].allocate(this_origin + Vector2I::new(0, kid_size as i32),
|
||||||
|
kid_size,
|
||||||
|
requested_size) {
|
||||||
|
return Some(origin);
|
||||||
|
}
|
||||||
|
if let Some(origin) =
|
||||||
|
kids[3].allocate(this_origin + Vector2I::splat(kid_size as i32),
|
||||||
|
kid_size,
|
||||||
|
requested_size) {
|
||||||
|
return Some(origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.merge_if_necessary();
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
TreeNode::EmptyLeaf | TreeNode::FullLeaf => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free(&mut self,
|
||||||
|
this_origin: Vector2I,
|
||||||
|
this_size: u32,
|
||||||
|
requested_origin: Vector2I,
|
||||||
|
requested_size: u32) {
|
||||||
|
if this_size <= requested_size {
|
||||||
|
if this_size == requested_size && this_origin == requested_origin {
|
||||||
|
*self = TreeNode::EmptyLeaf;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let child_size = this_size / 2;
|
||||||
|
let this_center = this_origin + Vector2I::splat(child_size as i32);
|
||||||
|
|
||||||
|
let child_index;
|
||||||
|
let mut child_origin = this_origin;
|
||||||
|
|
||||||
|
if requested_origin.y() < this_center.y() {
|
||||||
|
if requested_origin.x() < this_center.x() {
|
||||||
|
child_index = 0;
|
||||||
|
} else {
|
||||||
|
child_index = 1;
|
||||||
|
child_origin = child_origin + Vector2I::new(child_size as i32, 0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if requested_origin.x() < this_center.x() {
|
||||||
|
child_index = 2;
|
||||||
|
child_origin = child_origin + Vector2I::new(0, child_size as i32);
|
||||||
|
} else {
|
||||||
|
child_index = 3;
|
||||||
|
child_origin = this_center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match *self {
|
||||||
|
TreeNode::Parent(ref mut kids) => {
|
||||||
|
kids[child_index].free(child_origin, child_size, requested_origin, requested_size);
|
||||||
|
self.merge_if_necessary();
|
||||||
|
}
|
||||||
|
TreeNode::EmptyLeaf | TreeNode::FullLeaf => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn merge_if_necessary(&mut self) {
|
||||||
|
match *self {
|
||||||
|
TreeNode::Parent(ref mut kids) => {
|
||||||
|
if kids.iter().all(|kid| {
|
||||||
|
match **kid {
|
||||||
|
TreeNode::EmptyLeaf => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
*self = TreeNode::EmptyLeaf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use pathfinder_geometry::vector::Vector2I;
|
||||||
|
use quickcheck;
|
||||||
|
use std::u32;
|
||||||
|
|
||||||
|
use super::TextureAllocator;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_allocation_and_freeing() {
|
||||||
|
quickcheck::quickcheck(prop_allocation_and_freeing_work as
|
||||||
|
fn(u32, Vec<(u32, u32)>) -> bool);
|
||||||
|
|
||||||
|
fn prop_allocation_and_freeing_work(mut length: u32, mut sizes: Vec<(u32, u32)>) -> bool {
|
||||||
|
length = u32::next_power_of_two(length).max(1);
|
||||||
|
|
||||||
|
for &mut (ref mut width, ref mut height) in &mut sizes {
|
||||||
|
*width = (*width).min(length).max(1);
|
||||||
|
*height = (*height).min(length).max(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut allocator = TextureAllocator::new(length);
|
||||||
|
let mut locations = vec![];
|
||||||
|
for &(width, height) in &sizes {
|
||||||
|
let size = Vector2I::new(width as i32, height as i32);
|
||||||
|
if let Some(location) = allocator.allocate(size) {
|
||||||
|
locations.push(location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for location in locations {
|
||||||
|
allocator.free(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(allocator.is_empty());
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ pub mod paint;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod scene;
|
pub mod scene;
|
||||||
|
|
||||||
|
mod allocator;
|
||||||
mod builder;
|
mod builder;
|
||||||
mod sorted_vector;
|
mod sorted_vector;
|
||||||
mod tile_map;
|
mod tile_map;
|
||||||
|
|
Loading…
Reference in New Issue