webrender_api/tile_pool.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::sync::Arc;
const NUM_TILE_BUCKETS: usize = 6;
/// A pool of blob tile buffers to mitigate the overhead of
/// allocating and deallocating blob tiles.
///
/// The pool keeps a strong reference to each allocated buffers and
/// reuses the ones with a strong count of 1.
pub struct BlobTilePool {
largest_size_class: usize,
buckets: [Vec<Arc<Vec<u8>>>; NUM_TILE_BUCKETS],
}
impl BlobTilePool {
pub fn new() -> Self {
// The default max tile size is actually 256, using 512 here
// so that this still works when experimenting with larger
// tile sizes. If we ever make larger adjustments, the buckets
// should be changed accordingly.
let max_tile_size = 512;
BlobTilePool {
largest_size_class: max_tile_size * max_tile_size * 4,
buckets: [
Vec::with_capacity(32),
Vec::with_capacity(32),
Vec::with_capacity(32),
Vec::with_capacity(32),
Vec::with_capacity(32),
Vec::with_capacity(32),
],
}
}
/// Get or allocate a tile buffer of the requested size.
///
/// The returned buffer is zero-inizitalized.
/// The length of the returned buffer is equal to the requested size,
/// however the buffer may be allocated with a larger capacity to
/// conform to the pool's corresponding bucket tile size.
pub fn get_buffer(&mut self, requested_size: usize) -> MutableTileBuffer {
if requested_size > self.largest_size_class {
// If the requested size is larger than the largest size class,
// simply return a MutableBuffer that isn't tracked/recycled by
// the pool.
// In Firefox this should only happen in pathological cases
// where the blob visible area ends up so large that the tile
// size is increased to avoid producing too many tiles.
// See wr_resource_updates_add_blob_image.
let mut buf = vec![0; requested_size];
return MutableTileBuffer {
ptr: buf.as_mut_ptr(),
strong_ref: Arc::new(buf),
};
}
let (bucket_idx, cap) = self.bucket_and_size(requested_size);
let bucket = &mut self.buckets[bucket_idx];
let mut selected_idx = None;
for (buf_idx, buffer) in bucket.iter().enumerate() {
if Arc::strong_count(buffer) == 1 {
selected_idx = Some(buf_idx);
break;
}
}
let ptr;
let strong_ref;
if let Some(idx) = selected_idx {
{
// This works because we just ensured the pool has the only strong
// ref to the buffer.
let buffer = Arc::get_mut(&mut bucket[idx]).unwrap();
debug_assert!(buffer.capacity() >= requested_size);
// Ensure the length is equal to the requested size. It's not
// strictly necessay for the tile pool but the texture upload
// code relies on it.
unsafe { buffer.set_len(requested_size); }
// zero-initialize
buffer.fill(0);
ptr = buffer.as_mut_ptr();
}
strong_ref = Arc::clone(&bucket[idx]);
} else {
// Allocate a buffer with the adequate capacity for the requested
// size's bucket.
let mut buf = vec![0; cap];
// Force the length to be the requested size.
unsafe { buf.set_len(requested_size) };
ptr = buf.as_mut_ptr();
strong_ref = Arc::new(buf);
// Track the new buffer.
bucket.push(Arc::clone(&strong_ref));
};
MutableTileBuffer {
ptr,
strong_ref,
}
}
fn bucket_and_size(&self, size: usize) -> (usize, usize) {
let mut next_size_class = self.largest_size_class / 4;
let mut idx = 0;
while size < next_size_class && idx < NUM_TILE_BUCKETS - 1 {
next_size_class /= 4;
idx += 1;
}
(idx, next_size_class * 4)
}
/// Go over all allocated tile buffers. For each bucket, deallocate some buffers
/// until the number of unused buffer is more than half of the buffers for that
/// bucket.
///
/// In practice, if called regularly, this gradually lets go of blob tiles when
/// they are not used.
pub fn cleanup(&mut self) {
for bucket in &mut self.buckets {
let threshold = bucket.len() / 2;
let mut num_available = 0;
bucket.retain(&mut |buffer: &Arc<Vec<u8>>| {
if Arc::strong_count(buffer) > 1 {
return true;
}
num_available += 1;
num_available < threshold
});
}
}
}
// The role of tile buffer is to encapsulate an Arc to the underlying buffer
// with a reference count of at most 2 and a way to view the buffer's content
// as a mutable slice, even though the reference count may be more than 1.
// The safety of this relies on the other strong reference being held by the
// tile pool which never accesses the buffer's content, so the only reference
// that can access it is the `TileBuffer` itself.
pub struct MutableTileBuffer {
strong_ref: Arc<Vec<u8>>,
ptr: *mut u8,
}
impl MutableTileBuffer {
pub fn as_mut_slice(&mut self) -> &mut[u8] {
unsafe { std::slice::from_raw_parts_mut(self.ptr, self.strong_ref.len()) }
}
pub fn into_arc(self) -> Arc<Vec<u8>> {
self.strong_ref
}
}
unsafe impl Send for MutableTileBuffer {}