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 {}