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