Bug 1686500 - Update etagere to 0.2.4 r=gfx-reviewers,kvark

The new version contains
 - A bug fix for the bucketed allocator (we don't currently use it)
 - A few fixes that can happen when requesting large enough allocation sizes to cause integer overflows. At the moment we never request an allocation larger than 512px so we are safe but it's still good to stay up to date.

Differential Revision: https://phabricator.services.mozilla.com/D101608
This commit is contained in:
Nicolas Silva 2021-01-14 08:31:40 +00:00
parent 40965f4cef
commit be6b2bc8af
7 changed files with 209 additions and 43 deletions

4
Cargo.lock generated
View File

@ -1364,9 +1364,9 @@ dependencies = [
[[package]]
name = "etagere"
version = "0.2.3"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08b0c1a67320904a4d6e37931945c17447eb94057c8ebcc8d3ffd2789b6fb7b"
checksum = "520d7de540904fd09b11c03a47d50a7ce4ff37d1aa763f454fa60d9088ef8356"
dependencies = [
"euclid",
"serde",

20
gfx/wr/Cargo.lock generated
View File

@ -510,7 +510,7 @@ dependencies = [
[[package]]
name = "etagere"
version = "0.2.3"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"euclid 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -961,8 +961,8 @@ dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
"miow 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"net2 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -980,11 +980,11 @@ dependencies = [
[[package]]
name = "miow"
version = "0.2.1"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
"net2 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -1002,7 +1002,7 @@ dependencies = [
[[package]]
name = "net2"
version = "0.2.33"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1800,7 +1800,7 @@ dependencies = [
"cstr 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)",
"dwrote 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"etagere 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"etagere 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
"euclid 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)",
"freetype 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2104,7 +2104,7 @@ dependencies = [
"checksum dwrote 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b"
"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
"checksum env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)" = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38"
"checksum etagere 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c08b0c1a67320904a4d6e37931945c17447eb94057c8ebcc8d3ffd2789b6fb7b"
"checksum etagere 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "520d7de540904fd09b11c03a47d50a7ce4ff37d1aa763f454fa60d9088ef8356"
"checksum euclid 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7ab0e07e345fb061928646949fdf5fb888e5d75a57385e7f5856e45be289e745"
"checksum expat-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "658f19728920138342f68408b7cf7644d90d4784353d8ebc32e7e8663dbe45fa"
"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
@ -2160,9 +2160,9 @@ dependencies = [
"checksum minidl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "647da2489d438eb707e9bcffe0e47fb6f3f355ed288120740fc1e614cfadb9e9"
"checksum mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)" = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f"
"checksum mio-extras 2.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19"
"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
"checksum miow 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
"checksum mozangle 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d77438925edbca894202489c32351b3cce7e0ab25ae7358af83824440ef5e8e2"
"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
"checksum net2 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)" = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae"
"checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce"
"checksum nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6"
"checksum num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e"

View File

@ -56,7 +56,7 @@ ws = { optional = true, version = "0.9" }
svg_fmt = "0.4"
tracy-rs = "0.1.2"
derive_more = "0.99"
etagere = "0.2.3"
etagere = "0.2.4"
[dev-dependencies]
mozangle = "0.3.2"

View File

@ -1 +1 @@
{"files":{"Cargo.toml":"c482ce00fdf4bf21d1f2772c958503682f77f668c099772c54dffdb620c567bd","LICENSE":"739e55c73735c9733d8be0e3ab1c3bdb2240df594602c18eefbf8c851a83a734","README.md":"c33b45963747bb52370df2d4b44ef8ed6073e1874c1394b09ccdf33cb7c8c606","src/allocator.rs":"726263eee78504ea01aa96aadd0d0efd3754cdba121c8cfe3b2a4a58b0aed25a","src/bucketed.rs":"56d9759fe3ac52829b9eaff2d031447ae4598d1b7d50ac3e9f2d878f1106ec69","src/lib.rs":"f0ed9bd3c748de24cb9bdeeaf5ca6665e04882787db579fa18aa1e57481a51fb"},"package":"c08b0c1a67320904a4d6e37931945c17447eb94057c8ebcc8d3ffd2789b6fb7b"}
{"files":{"Cargo.toml":"d35f36bc8f1841ebfdfefdf89472a9778c79d52e4fd72babc7fc63b83b719fcc","LICENSE":"739e55c73735c9733d8be0e3ab1c3bdb2240df594602c18eefbf8c851a83a734","README.md":"c33b45963747bb52370df2d4b44ef8ed6073e1874c1394b09ccdf33cb7c8c606","src/allocator.rs":"1f2d80519804b553c011a9eec757e4a7ddb2475b55a1dd829a7cabb15ca1a22b","src/bucketed.rs":"caad4039803df2eaf0b326c900b608f5707dda9066a72c87f95c98de87f96e0c","src/lib.rs":"f0ed9bd3c748de24cb9bdeeaf5ca6665e04882787db579fa18aa1e57481a51fb"},"package":"520d7de540904fd09b11c03a47d50a7ce4ff37d1aa763f454fa60d9088ef8356"}

View File

@ -13,14 +13,14 @@
[package]
edition = "2018"
name = "etagere"
version = "0.2.3"
version = "0.2.4"
authors = ["Nicolas Silva <nical@fastmail.com>"]
exclude = [".backup*"]
description = "Dynamic 2D texture atlas allocation using the shelf packing algorithm."
documentation = "https://docs.rs/etagere/"
keywords = ["2d"]
license = "MIT/Apache-2.0"
repository = "https://github.com/nical/lyon"
repository = "https://github.com/nical/etagere"
[profile.release]
debug = true
[dependencies.euclid]

View File

@ -119,6 +119,11 @@ impl AtlasAllocator {
assert!(self.size.height > 0);
assert!(self.size.width <= std::u16::MAX as i32);
assert!(self.size.height <= std::u16::MAX as i32);
assert!(
self.size.width.checked_mul(self.size.height).is_some(),
"The area of the atlas must fit in a i32 value"
);
assert!(self.alignment.width > 0);
assert!(self.alignment.height > 0);
@ -172,20 +177,25 @@ impl AtlasAllocator {
/// Allocate a rectangle in the atlas.
pub fn allocate(&mut self, mut size: Size) -> Option<Allocation> {
if size.is_empty() {
if size.is_empty()
|| size.width > std::u16::MAX as i32
|| size.height > std::u16::MAX as i32 {
return None;
}
adjust_size(self.alignment.width, &mut size.width);
adjust_size(self.alignment.height, &mut size.height);
let (width, height) = convert_coordinates(self.flip_xy, size.width as u16, size.height as u16);
let mut height = shelf_height(height);
let (width, height) = convert_coordinates(self.flip_xy, size.width, size.height);
let height = shelf_height(height);
if width > self.shelf_width || height > self.size.height as u16 {
if width > self.shelf_width as i32 || height > self.size.height {
return None;
}
let mut width = width as u16;
let mut height = height as u16;
let mut selected_shelf_height = std::u16::MAX;
let mut selected_shelf = ShelfIndex::NONE;
let mut selected_item = ItemIndex::NONE;
@ -288,6 +298,8 @@ impl AtlasAllocator {
if item.next.is_some() {
self.items[item.next.index()].prev = new_item_idx;
}
} else {
width = item.width;
}
self.items[selected_item.index()].allocated = true;
@ -297,14 +309,14 @@ impl AtlasAllocator {
let x1 = x0 + width;
let y1 = y0 + height;
let (x0, y0) = convert_coordinates(self.flip_xy, x0, y0);
let (x1, y1) = convert_coordinates(self.flip_xy, x1, y1);
let (x0, y0) = convert_coordinates(self.flip_xy, x0 as i32, y0 as i32);
let (x1, y1) = convert_coordinates(self.flip_xy, x1 as i32, y1 as i32);
self.check();
let rectangle = Rectangle {
min: point2(x0 as i32, y0 as i32),
max: point2(x1 as i32, y1 as i32),
min: point2(x0, y0),
max: point2(x1, y1),
};
self.allocated_space += rectangle.area();
@ -625,7 +637,7 @@ fn adjust_size(alignment: i32, size: &mut i32) {
}
}
fn convert_coordinates(flip_xy: bool, x: u16, y: u16) -> (u16, u16) {
fn convert_coordinates(flip_xy: bool, x: i32, y: i32) -> (i32, i32) {
if flip_xy {
(y, x)
} else {
@ -633,7 +645,7 @@ fn convert_coordinates(flip_xy: bool, x: u16, y: u16) -> (u16, u16) {
}
}
fn shelf_height(mut size: u16) -> u16 {
fn shelf_height(mut size: i32) -> i32 {
let alignment = match size {
0 ..= 31 => 8,
32 ..= 127 => 16,
@ -651,7 +663,15 @@ fn shelf_height(mut size: u16) -> u16 {
#[test]
fn test_simple() {
let mut atlas = AtlasAllocator::new(size2(1000, 1000));
let mut atlas = AtlasAllocator::with_options(
size2(2048, 2048),
&AllocatorOptions {
alignment: size2(4, 8),
vertical_shelves: false,
num_columns: 2,
},
);
assert!(atlas.is_empty());
assert_eq!(atlas.allocated_space(), 0);
@ -839,4 +859,92 @@ fn clear() {
atlas.allocate(size2(29, 28)).unwrap();
atlas.allocate(size2(32, 32)).unwrap();
}
}
}
#[test]
fn fuzz_01() {
let s = 65472;
let mut atlas = AtlasAllocator::new(size2(s, 64));
let alloc = atlas.allocate(size2(s, 64)).unwrap();
assert_eq!(alloc.rectangle.size().width, s);
assert_eq!(alloc.rectangle.size().height, 64);
let mut atlas = AtlasAllocator::new(size2(64, s));
let alloc = atlas.allocate(size2(64, s)).unwrap();
assert_eq!(alloc.rectangle.size().width, 64);
assert_eq!(alloc.rectangle.size().height, s);
let mut atlas = AtlasAllocator::new(size2(s, 64));
let alloc = atlas.allocate(size2(s - 1, 64)).unwrap();
assert_eq!(alloc.rectangle.size().width, s);
assert_eq!(alloc.rectangle.size().height, 64);
let mut atlas = AtlasAllocator::new(size2(64, s));
let alloc = atlas.allocate(size2(64, s - 1)).unwrap();
assert_eq!(alloc.rectangle.size().width, 64);
assert_eq!(alloc.rectangle.size().height, s);
// Because of potential alignment we won't necessarily
// succeed at allocation something this big
let s = std::u16::MAX as i32;
let mut atlas = AtlasAllocator::new(size2(s, 64));
if let Some(alloc) = atlas.allocate(size2(s, 64)) {
assert_eq!(alloc.rectangle.size().width, s);
assert_eq!(alloc.rectangle.size().height, 64);
}
let mut atlas = AtlasAllocator::new(size2(64, s));
if let Some(alloc) = atlas.allocate(size2(64, s)) {
assert_eq!(alloc.rectangle.size().width, 64);
assert_eq!(alloc.rectangle.size().height, s);
}
}
#[test]
fn fuzz_02() {
let mut atlas = AtlasAllocator::new(size2(1000, 1000));
assert!(atlas.allocate(size2(255, 65599)).is_none());
}
#[test]
fn fuzz_03() {
let mut atlas = AtlasAllocator::new(size2(1000, 1000));
let sizes = &[
size2(999, 128),
size2(168492810, 10),
size2(45, 96),
size2(-16711926, 0),
];
let mut allocations = Vec::new();
let mut allocated_space = 0;
for size in sizes {
if let Some(alloc) = atlas.allocate(*size) {
allocations.push(alloc);
allocated_space += alloc.rectangle.area();
assert_eq!(allocated_space, atlas.allocated_space());
}
}
for alloc in &allocations {
atlas.deallocate(alloc.id);
allocated_space -= alloc.rectangle.area();
assert_eq!(allocated_space, atlas.allocated_space());
}
assert_eq!(atlas.allocated_space(), 0);
}
#[test]
fn fuzz_04() {
let mut atlas = AtlasAllocator::new(size2(1000, 1000));
assert!(atlas.allocate(size2(2560, 2147483647)).is_none());
}

View File

@ -141,7 +141,9 @@ impl BucketedAtlasAllocator {
/// Allocate a rectangle in the atlas.
pub fn allocate(&mut self, mut requested_size: Size) -> Option<Allocation> {
if requested_size.is_empty() {
if requested_size.is_empty()
|| requested_size.width > std::u16::MAX as i32
|| requested_size.height > std::u16::MAX as i32 {
return None;
}
@ -348,6 +350,7 @@ impl BucketedAtlasAllocator {
/// The squashed shelves are not removed, their height is just set to zero so no item
/// can go in, and they will be garbage-collected whenever there's no shelf above them.
/// For simplicity, the bucket width is not modified.
fn coalesce_shelves(&mut self, w: u16, h: u16) -> (usize, BucketIndex) {
let len = self.shelves.len();
let mut coalesce_range = None;
@ -386,7 +389,9 @@ impl BucketedAtlasAllocator {
}
if let Some(range) = coalesce_range {
let y_top = self.shelves[range.start].y + coalesced_height;
for i in range.start + 1 .. range.end {
self.shelves[i].y = y_top;
self.shelves[i].height = 0;
}
@ -403,10 +408,9 @@ impl BucketedAtlasAllocator {
fn num_buckets(&self, width: u16, height: u16) -> u16 {
match self.column_width / u16::max(width, height) {
0 ..= 4 => 1,
5 ..= 15 => 2,
16 ..= 64 => 4,
65 ..= 256 => 8,
_ => 16,
5 ..= 16 => 2,
17 ..= 32 => 4,
n => (n /16 - 1).next_power_of_two(),
}.min((MAX_BIN_COUNT - self.buckets.len()) as u16)
}
@ -468,7 +472,7 @@ impl BucketedAtlasAllocator {
let prev_shelf = &self.shelves[self.shelves.len() - 2];
self.available_height = self.height - (prev_shelf.y + prev_shelf.height);
} else {
// Reclaim the height of the bucket.
// Reclaim the height of the shelf.
self.available_height += shelf.height;
}
}
@ -673,14 +677,6 @@ fn atlas_basic() {
atlas.deallocate(full);
}
#[test]
fn fuzz_01() {
let mut atlas = BucketedAtlasAllocator::new(size2(1000, 1000));
assert!(atlas.allocate(size2(65280, 1)).is_none());
assert!(atlas.allocate(size2(1, 65280)).is_none());
}
#[test]
fn test_coalesce_shelves() {
let mut atlas = BucketedAtlasAllocator::new(size2(256, 256));
@ -891,4 +887,66 @@ fn clear() {
atlas.allocate(size2(29, 28)).unwrap();
atlas.allocate(size2(32, 32)).unwrap();
}
}
}
#[test]
fn fuzz_01() {
let mut atlas = BucketedAtlasAllocator::new(size2(1000, 1000));
assert!(atlas.allocate(size2(65280, 1)).is_none());
assert!(atlas.allocate(size2(1, 65280)).is_none());
}
#[test]
fn fuzz_02() {
let mut atlas = BucketedAtlasAllocator::new(size2(1000, 1000));
assert!(atlas.allocate(size2(255, 65599)).is_none());
}
#[test]
fn fuzz_03() {
let mut atlas = BucketedAtlasAllocator::new(size2(1000, 1000));
let sizes = &[
size2(999, 128),
size2(168492810, 10),
size2(45, 96),
size2(-16711926, 0),
];
let mut allocations = Vec::new();
let mut allocated_space = 0;
for size in sizes {
if let Some(alloc) = atlas.allocate(*size) {
allocations.push(alloc);
allocated_space += alloc.rectangle.area();
assert_eq!(allocated_space, atlas.allocated_space());
}
}
for alloc in &allocations {
atlas.deallocate(alloc.id);
allocated_space -= alloc.rectangle.area();
assert_eq!(allocated_space, atlas.allocated_space());
}
assert_eq!(atlas.allocated_space(), 0);
}
#[test]
fn fuzz_04() {
let mut atlas = BucketedAtlasAllocator::new(size2(1000, 1000));
assert!(atlas.allocate(size2(2560, 2147483647)).is_none());
}
#[test]
fn fuzz_05() {
let mut atlas = BucketedAtlasAllocator::new(size2(2048, 2048));
assert!(atlas.allocate(size2(0, -1978597547)).is_none());
}