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 #![deny(missing_docs)]
5
6 //! Provides the webrender-side implementation of gecko blob images.
7 //!
8 //! Pretty much this is just a shim that calls back into Moz2DImageRenderer, but
9 //! it also handles merging "partial" blob images (see `merge_blob_images`) and
10 //! registering fonts found in the blob (see `prepare_request`).
11
12 use bindings::{
13 gecko_profiler_end_marker, gecko_profiler_start_marker, wr_moz2d_render_cb, ArcVecU8, ByteSlice, MutByteSlice,
14 };
15 use rayon::prelude::*;
16 use rayon::ThreadPool;
17 use webrender::api::units::{BlobDirtyRect, BlobToDeviceTranslation, DeviceIntRect};
18 use webrender::api::*;
19
20 use euclid::{Box2D, Rect};
21 use std;
22 use std::collections::btree_map::BTreeMap;
23 use std::collections::hash_map;
24 use std::collections::hash_map::HashMap;
25 use std::collections::Bound::Included;
26 use std::i32;
27 use std::mem;
28 use std::os::raw::{c_char, c_void};
29 use std::ptr;
30 use std::sync::Arc;
31
32 #[cfg(target_os = "windows")]
33 use dwrote;
34
35 #[cfg(target_os = "macos")]
36 use foreign_types::ForeignType;
37
38 #[cfg(not(any(target_os = "macos", target_os = "windows")))]
39 use std::ffi::CString;
40 #[cfg(not(any(target_os = "macos", target_os = "windows")))]
41 use std::os::unix::ffi::OsStrExt;
42
43 /// Local print-debugging utility
44 macro_rules! dlog {
45 ($($e:expr),*) => { {$(let _ = $e;)*} }
46 //($($t:tt)*) => { println!($($t)*) }
47 }
48
49 /// Debug prints a blob's item bounds, indicating whether the bounds are dirty or not.
dump_bounds(blob: &[u8], dirty_rect: Box2d)50 fn dump_bounds(blob: &[u8], dirty_rect: Box2d) {
51 let mut index = BlobReader::new(blob);
52 while index.reader.has_more() {
53 let e = index.read_entry();
54 dlog!(
55 " {:?} {}",
56 e.bounds,
57 if e.bounds.contained_by(&dirty_rect) { "*" } else { "" }
58 );
59 }
60 }
61
62 /// Debug prints a blob's metadata.
dump_index(blob: &[u8])63 fn dump_index(blob: &[u8]) {
64 let mut index = BlobReader::new(blob);
65 // we might get an empty result here because sub groups are not tightly bound
66 // and we'll sometimes have display items that end up with empty bounds in
67 // the blob image.
68 while index.reader.has_more() {
69 let e = index.read_entry();
70 dlog!("result bounds: {} {} {:?}", e.end, e.extra_end, e.bounds);
71 }
72 }
73
74 /// Handles the interpretation and rasterization of gecko-based (moz2d) WR blob images.
75 pub struct Moz2dBlobImageHandler {
76 workers: Arc<ThreadPool>,
77 workers_low_priority: Arc<ThreadPool>,
78 blob_commands: HashMap<BlobImageKey, BlobCommand>,
79 enable_multithreading: bool,
80 }
81
82 /// Transmute some bytes into a value.
83 ///
84 /// FIXME: kill this with fire and/or do a super robust security audit
convert_from_bytes<T: Copy>(slice: &[u8]) -> T85 unsafe fn convert_from_bytes<T: Copy>(slice: &[u8]) -> T {
86 assert!(mem::size_of::<T>() <= slice.len());
87 ptr::read_unaligned(slice.as_ptr() as *const T)
88 }
89
90 /// Transmute a value into some bytes.
convert_to_bytes<T>(x: &T) -> &[u8]91 fn convert_to_bytes<T>(x: &T) -> &[u8] {
92 unsafe {
93 let ip: *const T = x;
94 let bp: *const u8 = ip as *const _;
95 ::std::slice::from_raw_parts(bp, mem::size_of::<T>())
96 }
97 }
98
99 /// A simple helper for deserializing a bunch of transmuted POD data from bytes.
100 struct BufReader<'a> {
101 /// The buffer to read from.
102 buf: &'a [u8],
103 /// Where we currently are reading from.
104 pos: usize,
105 }
106
107 impl<'a> BufReader<'a> {
108 /// Creates a reader over the given input.
new(buf: &'a [u8]) -> BufReader<'a>109 fn new(buf: &'a [u8]) -> BufReader<'a> {
110 BufReader { buf, pos: 0 }
111 }
112
113 /// Transmute-deserializes a value of type T from the stream.
114 ///
115 /// !!! SUPER DANGEROUS !!!
116 ///
117 /// To limit the scope of this unsafety, please don't call this directly.
118 /// Make a helper method for each whitelisted type.
read<T: Copy>(&mut self) -> T119 unsafe fn read<T: Copy>(&mut self) -> T {
120 let ret = convert_from_bytes(&self.buf[self.pos..]);
121 self.pos += mem::size_of::<T>();
122 ret
123 }
124
125 /// Deserializes a BlobFont.
read_blob_font(&mut self) -> BlobFont126 fn read_blob_font(&mut self) -> BlobFont {
127 unsafe { self.read::<BlobFont>() }
128 }
129
130 /// Deserializes a usize.
read_usize(&mut self) -> usize131 fn read_usize(&mut self) -> usize {
132 unsafe { self.read::<usize>() }
133 }
134
135 /// Deserializes a Box2d.
read_box(&mut self) -> Box2d136 fn read_box(&mut self) -> Box2d {
137 unsafe { self.read::<Box2d>() }
138 }
139
140 /// Returns whether the buffer has more data to deserialize.
has_more(&self) -> bool141 fn has_more(&self) -> bool {
142 self.pos < self.buf.len()
143 }
144 }
145
146 /// Reads the metadata of a blob image.
147 ///
148 /// Blob stream format:
149 /// { data[..], index[..], offset in the stream of the index array }
150 ///
151 /// An 'item' has 'data' and 'extra_data'
152 /// - In our case the 'data' is the stream produced by DrawTargetRecording
153 /// and the 'extra_data' includes things like webrender font keys
154 ///
155 /// The index is an array of entries of the following form:
156 /// { end, extra_end, bounds }
157 ///
158 /// - end is the offset of the end of an item's data
159 /// an item's data goes from the begining of the stream or
160 /// the begining of the last item til end
161 /// - extra_end is the offset of the end of an item's extra data
162 /// an item's extra data goes from 'end' until 'extra_end'
163 /// - bounds is a set of 4 ints {x1, y1, x2, y2 }
164 ///
165 /// The offsets in the index should be monotonically increasing.
166 ///
167 /// Design rationale:
168 /// - the index is smaller so we append it to the end of the data array
169 /// during construction. This makes it more likely that we'll fit inside
170 /// the data Vec
171 /// - we use indices/offsets instead of sizes to avoid having to deal with any
172 /// arithmetic that might overflow.
173 struct BlobReader<'a> {
174 /// The buffer of the blob.
175 reader: BufReader<'a>,
176 /// Where the buffer head is.
177 begin: usize,
178 }
179
180 #[derive(PartialEq, Debug, Eq, Clone, Copy)]
181 struct IntPoint {
182 x: i32,
183 y: i32,
184 }
185
186 /// The metadata for each display item in a blob image (doesn't match the serialized layout).
187 ///
188 /// See BlobReader above for detailed docs of the blob image format.
189 struct Entry {
190 /// The bounds of the display item.
191 bounds: Box2d,
192 /// Where the item's recorded drawing commands start.
193 begin: usize,
194 /// Where the item's recorded drawing commands end, and its extra data starts.
195 end: usize,
196 /// Where the item's extra data ends, and the next item's `begin`.
197 extra_end: usize,
198 }
199
200 impl<'a> BlobReader<'a> {
201 /// Creates a new BlobReader for the given buffer.
new(buf: &'a [u8]) -> BlobReader<'a>202 fn new(buf: &'a [u8]) -> BlobReader<'a> {
203 // The offset of the index is at the end of the buffer.
204 let index_offset_pos = buf.len() - mem::size_of::<usize>();
205 assert!(index_offset_pos < buf.len());
206 let index_offset = unsafe { convert_from_bytes::<usize>(&buf[index_offset_pos..]) };
207
208 BlobReader {
209 reader: BufReader::new(&buf[index_offset..index_offset_pos]),
210 begin: 0,
211 }
212 }
213
214 /// Reads the next display item's metadata.
read_entry(&mut self) -> Entry215 fn read_entry(&mut self) -> Entry {
216 let end = self.reader.read_usize();
217 let extra_end = self.reader.read_usize();
218 let bounds = self.reader.read_box();
219 let ret = Entry {
220 begin: self.begin,
221 end,
222 extra_end,
223 bounds,
224 };
225 self.begin = extra_end;
226 ret
227 }
228 }
229
230 /// Writes new blob images.
231 ///
232 /// In our case this is the result of merging an old one and a new one
233 struct BlobWriter {
234 /// The buffer that the data and extra data for the items is accumulated.
235 data: Vec<u8>,
236 /// The buffer that the metadata for the items is accumulated.
237 index: Vec<u8>,
238 }
239
240 impl BlobWriter {
241 /// Creates an empty BlobWriter.
new() -> BlobWriter242 fn new() -> BlobWriter {
243 BlobWriter {
244 data: Vec::new(),
245 index: Vec::new(),
246 }
247 }
248
249 /// Writes a display item to the blob.
new_entry(&mut self, extra_size: usize, bounds: Box2d, data: &[u8])250 fn new_entry(&mut self, extra_size: usize, bounds: Box2d, data: &[u8]) {
251 self.data.extend_from_slice(data);
252 // Write 'end' to the index: the offset where the regular data ends and the extra data starts.
253 self.index
254 .extend_from_slice(convert_to_bytes(&(self.data.len() - extra_size)));
255 // Write 'extra_end' to the index: the offset where the extra data ends.
256 self.index.extend_from_slice(convert_to_bytes(&self.data.len()));
257 // XXX: we can aggregate these writes
258 // Write the bounds to the index.
259 self.index.extend_from_slice(convert_to_bytes(&bounds.x1));
260 self.index.extend_from_slice(convert_to_bytes(&bounds.y1));
261 self.index.extend_from_slice(convert_to_bytes(&bounds.x2));
262 self.index.extend_from_slice(convert_to_bytes(&bounds.y2));
263 }
264
265 /// Completes the blob image, producing a single buffer containing it.
finish(mut self) -> Vec<u8>266 fn finish(mut self) -> Vec<u8> {
267 // Append the index to the end of the buffer
268 // and then append the offset to the beginning of the index.
269 let index_begin = self.data.len();
270 self.data.extend_from_slice(&self.index);
271 self.data.extend_from_slice(convert_to_bytes(&index_begin));
272 self.data
273 }
274 }
275
276 // TODO: replace with euclid::Box2D
277 #[derive(Debug, Eq, PartialEq, Clone, Copy, Ord, PartialOrd)]
278 #[repr(C)]
279 /// A two-points representation of a rectangle.
280 struct Box2d {
281 /// Top-left x
282 x1: i32,
283 /// Top-left y
284 y1: i32,
285 /// Bottom-right x
286 x2: i32,
287 /// Bottom-right y
288 y2: i32,
289 }
290
291 impl Box2d {
292 /// Returns whether `self` is contained by `other` (inclusive).
293 /// an empty self is contained in every rect
contained_by(&self, other: &Box2d) -> bool294 fn contained_by(&self, other: &Box2d) -> bool {
295 self.is_empty() || (self.x1 >= other.x1 && self.x2 <= other.x2 && self.y1 >= other.y1 && self.y2 <= other.y2)
296 }
297
intersection(&self, other: &Box2d) -> Box2d298 fn intersection(&self, other: &Box2d) -> Box2d {
299 let result = Box2d {
300 x1: self.x1.max(other.x1),
301 y1: self.y1.max(other.y1),
302 x2: self.x2.min(other.x2),
303 y2: self.y2.min(other.y2),
304 };
305 if self.is_empty() || other.is_empty() {
306 assert!(result.is_empty());
307 }
308 result
309 }
310
is_empty(&self) -> bool311 fn is_empty(&self) -> bool {
312 self.x2 <= self.x1 || self.y2 <= self.y1
313 }
314 }
315
316 impl<T> From<Rect<i32, T>> for Box2d {
from(rect: Rect<i32, T>) -> Box2d317 fn from(rect: Rect<i32, T>) -> Box2d {
318 (&rect).into()
319 }
320 }
321
322 impl<T> From<&Rect<i32, T>> for Box2d {
from(rect: &Rect<i32, T>) -> Box2d323 fn from(rect: &Rect<i32, T>) -> Box2d {
324 Box2d {
325 x1: rect.min_x(),
326 y1: rect.min_y(),
327 x2: rect.max_x(),
328 y2: rect.max_y(),
329 }
330 }
331 }
332
333 impl<T> From<Box2D<i32, T>> for Box2d {
from(rect: Box2D<i32, T>) -> Box2d334 fn from(rect: Box2D<i32, T>) -> Box2d {
335 (&rect).into()
336 }
337 }
338
339 impl<T> From<&Box2D<i32, T>> for Box2d {
from(rect: &Box2D<i32, T>) -> Box2d340 fn from(rect: &Box2D<i32, T>) -> Box2d {
341 Box2d {
342 x1: rect.min.x,
343 y1: rect.min.y,
344 x2: rect.max.x,
345 y2: rect.max.y,
346 }
347 }
348 }
349
350 /// Provides an API for looking up the display items in a blob image by bounds, yielding items
351 /// with equal bounds in their original relative ordering.
352 ///
353 /// This is used to implement `merge_blobs_images`.
354 ///
355 /// We use a BTree as a kind of multi-map, by appending an integer "cache_order" to the key.
356 /// This lets us use multiple items with matching bounds in the map and allows
357 /// us to fetch and remove them while retaining the ordering of the original list.
358 struct CachedReader<'a> {
359 /// Wrapped reader.
360 reader: BlobReader<'a>,
361 /// Cached entries that have been read but not yet requested by our consumer.
362 cache: BTreeMap<(Box2d, u32), Entry>,
363 /// The current number of internally read display items, used to preserve list order.
364 cache_index_counter: u32,
365 }
366
367 impl<'a> CachedReader<'a> {
368 /// Creates a new CachedReader.
new(buf: &'a [u8]) -> CachedReader369 pub fn new(buf: &'a [u8]) -> CachedReader {
370 CachedReader {
371 reader: BlobReader::new(buf),
372 cache: BTreeMap::new(),
373 cache_index_counter: 0,
374 }
375 }
376
377 /// Tries to find the given bounds in the cache of internally read items, removing it if found.
take_entry_with_bounds_from_cache(&mut self, bounds: &Box2d) -> Option<Entry>378 fn take_entry_with_bounds_from_cache(&mut self, bounds: &Box2d) -> Option<Entry> {
379 if self.cache.is_empty() {
380 return None;
381 }
382
383 let key_to_delete = match self
384 .cache
385 .range((Included((*bounds, 0u32)), Included((*bounds, std::u32::MAX))))
386 .next()
387 {
388 Some((&key, _)) => key,
389 None => return None,
390 };
391
392 Some(
393 self.cache
394 .remove(&key_to_delete)
395 .expect("We just got this key from range, it needs to be present"),
396 )
397 }
398
399 /// Yields the next item in the blob image with the given bounds.
400 ///
401 /// If the given bounds aren't found in the blob, this panics. `merge_blob_images` should
402 /// avoid this by construction if the blob images are well-formed.
next_entry_with_bounds(&mut self, bounds: &Box2d, ignore_rect: &Box2d) -> Entry403 pub fn next_entry_with_bounds(&mut self, bounds: &Box2d, ignore_rect: &Box2d) -> Entry {
404 if let Some(entry) = self.take_entry_with_bounds_from_cache(bounds) {
405 return entry;
406 }
407
408 loop {
409 // This will panic if we run through the whole list without finding our bounds.
410 let old = self.reader.read_entry();
411 if old.bounds == *bounds {
412 return old;
413 } else if !old.bounds.contained_by(&ignore_rect) {
414 self.cache.insert((old.bounds, self.cache_index_counter), old);
415 self.cache_index_counter += 1;
416 }
417 }
418 }
419 }
420
421 /// Merges a new partial blob image into an existing complete one.
422 ///
423 /// A blob image represents a recording of the drawing commands needed to render
424 /// (part of) a display list. A partial blob image is a diff between the old display
425 /// list and a new one. It contains an entry for every display item in the new list, but
426 /// the actual drawing commands are missing for any item that isn't strictly contained
427 /// in the dirty rect. This is possible because not being contained in the dirty
428 /// rect implies that the item is unchanged between the old and new list, so we can
429 /// just grab the drawing commands from the old list.
430 ///
431 /// The dirty rect strictly contains the bounds of every item that has been inserted
432 /// into or deleted from the old list to create the new list. (For simplicity
433 /// you may think of any other update as deleting and reinserting the item).
434 ///
435 /// Partial blobs are based on gecko's "retained display list" system, and
436 /// in particular rely on one key property: if two items have overlapping bounds
437 /// and *aren't* contained in the dirty rect, then their relative order in both
438 /// the old and new list will not change. This lets us uniquely identify a display
439 /// item using only its bounds and relative order in the list.
440 ///
441 /// That is, the first non-dirty item in the new list with bounds (10, 15, 100, 100)
442 /// is *also* the first non-dirty item in the old list with those bounds.
443 ///
444 /// Note that *every* item contained inside the dirty rect will be fully recorded in
445 /// the new list, even if it is actually unchanged from the old list.
446 ///
447 /// All of this together gives us a fairly simple merging algorithm: all we need
448 /// to do is walk through the new (partial) list, determine which of the two lists
449 /// has the recording for that item, and copy the recording into the result.
450 ///
451 /// If an item is contained in the dirty rect, then the new list contains the
452 /// correct recording for that item, so we always copy it from there. Otherwise, we find
453 /// the first not-yet-copied item with those bounds in the old list and copy that.
454 /// Any items found in the old list but not the new one can be safely assumed to
455 /// have been deleted.
merge_blob_images( old_buf: &[u8], new_buf: &[u8], dirty_rect: Box2d, old_visible_rect: Box2d, new_visible_rect: Box2d, ) -> Vec<u8>456 fn merge_blob_images(
457 old_buf: &[u8],
458 new_buf: &[u8],
459 dirty_rect: Box2d,
460 old_visible_rect: Box2d,
461 new_visible_rect: Box2d,
462 ) -> Vec<u8> {
463 let mut result = BlobWriter::new();
464 dlog!("dirty rect: {:?}", dirty_rect);
465 dlog!("old:");
466 dump_bounds(old_buf, dirty_rect);
467 dlog!("new:");
468 dump_bounds(new_buf, dirty_rect);
469 dlog!("old visibile rect: {:?}", old_visible_rect);
470 dlog!("new visibile rect: {:?}", new_visible_rect);
471
472 let mut old_reader = CachedReader::new(old_buf);
473 let mut new_reader = BlobReader::new(new_buf);
474 let preserved_rect = old_visible_rect.intersection(&new_visible_rect);
475
476 // Loop over both new and old entries merging them.
477 // Both new and old must have the same number of entries that
478 // overlap but are not contained by the dirty rect, and they
479 // must be in the same order.
480 while new_reader.reader.has_more() {
481 let new = new_reader.read_entry();
482 dlog!("bounds: {} {} {:?}", new.end, new.extra_end, new.bounds);
483 let preserved_bounds = new.bounds.intersection(&preserved_rect);
484 if preserved_bounds.contained_by(&dirty_rect) {
485 result.new_entry(new.extra_end - new.end, new.bounds, &new_buf[new.begin..new.extra_end]);
486 } else {
487 let old = old_reader.next_entry_with_bounds(&new.bounds, &dirty_rect);
488 result.new_entry(old.extra_end - old.end, new.bounds, &old_buf[old.begin..old.extra_end])
489 }
490 }
491
492 // XXX: future work: ensure that items that have been deleted but aren't in the blob's visible
493 // rect don't affect the dirty rect -- this allows us to scroll content out of view while only
494 // updating the areas where items have been scrolled *into* view. This is very important for
495 // the performance of blobs that are larger than the viewport. When this is done this
496 // assertion will need to be modified to factor in the visible rect, or removed.
497
498 // Ensure all remaining items will be discarded
499 while old_reader.reader.reader.has_more() {
500 let old = old_reader.reader.read_entry();
501 dlog!("new bounds: {} {} {:?}", old.end, old.extra_end, old.bounds);
502 //assert!(old.bounds.contained_by(&dirty_rect));
503 }
504
505 //assert!(old_reader.cache.is_empty());
506
507 let result = result.finish();
508 dump_index(&result);
509 result
510 }
511
512 /// A font used by a blob image.
513 #[repr(C)]
514 #[derive(Copy, Clone)]
515 struct BlobFont {
516 /// The font key.
517 font_instance_key: FontInstanceKey,
518 /// A pointer to the scaled font.
519 scaled_font_ptr: u64,
520 }
521
522 /// A blob image and extra data provided by webrender on how to rasterize it.
523 #[derive(Clone)]
524 struct BlobCommand {
525 /// The blob.
526 data: Arc<BlobImageData>,
527 /// What part of the blob should be rasterized (visible_rect's top-left corresponds to
528 /// (0,0) in the blob's rasterization)
529 visible_rect: DeviceIntRect,
530 /// The size of the tiles to use in rasterization.
531 tile_size: TileSize,
532 }
533
534 struct Job {
535 request: BlobImageRequest,
536 descriptor: BlobImageDescriptor,
537 commands: Arc<BlobImageData>,
538 dirty_rect: BlobDirtyRect,
539 visible_rect: DeviceIntRect,
540 tile_size: TileSize,
541 }
542
543 /// Rasterizes gecko blob images.
544 struct Moz2dBlobRasterizer {
545 /// Pool of rasterizers.
546 workers: Arc<ThreadPool>,
547 /// Pool of low priority rasterizers.
548 workers_low_priority: Arc<ThreadPool>,
549 /// Blobs to rasterize.
550 blob_commands: HashMap<BlobImageKey, BlobCommand>,
551 ///
552 enable_multithreading: bool,
553 }
554
555 struct GeckoProfilerMarker {
556 name: &'static [u8],
557 }
558
559 impl GeckoProfilerMarker {
new(name: &'static [u8]) -> GeckoProfilerMarker560 pub fn new(name: &'static [u8]) -> GeckoProfilerMarker {
561 unsafe {
562 gecko_profiler_start_marker(name.as_ptr() as *const c_char);
563 }
564 GeckoProfilerMarker { name }
565 }
566 }
567
568 impl Drop for GeckoProfilerMarker {
drop(&mut self)569 fn drop(&mut self) {
570 unsafe {
571 gecko_profiler_end_marker(self.name.as_ptr() as *const c_char);
572 }
573 }
574 }
575
576 impl AsyncBlobImageRasterizer for Moz2dBlobRasterizer {
rasterize( &mut self, requests: &[BlobImageParams], low_priority: bool, ) -> Vec<(BlobImageRequest, BlobImageResult)>577 fn rasterize(
578 &mut self,
579 requests: &[BlobImageParams],
580 low_priority: bool,
581 ) -> Vec<(BlobImageRequest, BlobImageResult)> {
582 // All we do here is spin up our workers to callback into gecko to replay the drawing commands.
583 let _marker = GeckoProfilerMarker::new(b"BlobRasterization\0");
584
585 let requests: Vec<Job> = requests
586 .iter()
587 .map(|params| {
588 let command = &self.blob_commands[¶ms.request.key];
589 let blob = Arc::clone(&command.data);
590 assert!(!params.descriptor.rect.is_empty());
591
592 Job {
593 request: params.request,
594 descriptor: params.descriptor,
595 commands: blob,
596 visible_rect: command.visible_rect,
597 dirty_rect: params.dirty_rect,
598 tile_size: command.tile_size,
599 }
600 })
601 .collect();
602
603 // If we don't have a lot of blobs it is probably not worth the initial cost
604 // of installing work on rayon's thread pool so we do it serially on this thread.
605 let should_parallelize = if !self.enable_multithreading {
606 false
607 } else if low_priority {
608 requests.len() > 2
609 } else {
610 // For high priority requests we don't "risk" the potential priority inversion of
611 // dispatching to a thread pool full of low priority jobs unless it is really
612 // appealing.
613 requests.len() > 4
614 };
615
616 if should_parallelize {
617 // Parallel version synchronously installs a job on the thread pool which will
618 // try to do the work in parallel.
619 // This thread is blocked until the thread pool is done doing the work.
620 let lambda = || requests.into_par_iter().map(rasterize_blob).collect();
621 if low_priority {
622 //TODO --bpe runtime flag to A/B test these two
623 self.workers_low_priority.install(lambda)
624 //self.workers.install(lambda)
625 } else {
626 self.workers.install(lambda)
627 }
628 } else {
629 requests.into_iter().map(rasterize_blob).collect()
630 }
631 }
632 }
633
634 // a cross platform wrapper that creates an autorelease pool
635 // on macOS
autoreleasepool<T, F: FnOnce() -> T>(f: F) -> T636 fn autoreleasepool<T, F: FnOnce() -> T>(f: F) -> T {
637 #[cfg(target_os = "macos")]
638 {
639 objc::rc::autoreleasepool(f)
640 }
641 #[cfg(not(target_os = "macos"))]
642 {
643 f()
644 }
645 }
646
rasterize_blob(job: Job) -> (BlobImageRequest, BlobImageResult)647 fn rasterize_blob(job: Job) -> (BlobImageRequest, BlobImageResult) {
648 let descriptor = job.descriptor;
649 let buf_size = (descriptor.rect.area() * descriptor.format.bytes_per_pixel()) as usize;
650
651 let mut output = vec![0u8; buf_size];
652
653 let dirty_rect = match job.dirty_rect {
654 DirtyRect::Partial(rect) => Some(rect),
655 DirtyRect::All => None,
656 };
657 assert!(!descriptor.rect.is_empty());
658
659 let result = autoreleasepool(|| {
660 unsafe {
661 if wr_moz2d_render_cb(
662 ByteSlice::new(&job.commands[..]),
663 descriptor.format,
664 &descriptor.rect,
665 &job.visible_rect,
666 job.tile_size,
667 &job.request.tile,
668 dirty_rect.as_ref(),
669 MutByteSlice::new(output.as_mut_slice()),
670 ) {
671 // We want the dirty rect local to the tile rather than the whole image.
672 // TODO(nical): move that up and avoid recomupting the tile bounds in the callback
673 let dirty_rect = job.dirty_rect.to_subrect_of(&descriptor.rect);
674 let tx: BlobToDeviceTranslation = (-descriptor.rect.min.to_vector()).into();
675 let rasterized_rect = tx.transform_box(&dirty_rect);
676
677 Ok(RasterizedBlobImage {
678 rasterized_rect,
679 data: Arc::new(output),
680 })
681 } else {
682 panic!("Moz2D replay problem");
683 }
684 }
685 });
686
687 (job.request, result)
688 }
689
690 impl BlobImageHandler for Moz2dBlobImageHandler {
create_similar(&self) -> Box<dyn BlobImageHandler>691 fn create_similar(&self) -> Box<dyn BlobImageHandler> {
692 Box::new(Self::new(
693 Arc::clone(&self.workers),
694 Arc::clone(&self.workers_low_priority),
695 ))
696 }
697
add(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, visible_rect: &DeviceIntRect, tile_size: TileSize)698 fn add(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, visible_rect: &DeviceIntRect, tile_size: TileSize) {
699 {
700 let index = BlobReader::new(&data);
701 assert!(index.reader.has_more());
702 }
703 self.blob_commands.insert(
704 key,
705 BlobCommand {
706 data: Arc::clone(&data),
707 visible_rect: *visible_rect,
708 tile_size,
709 },
710 );
711 }
712
update( &mut self, key: BlobImageKey, data: Arc<BlobImageData>, visible_rect: &DeviceIntRect, dirty_rect: &BlobDirtyRect, )713 fn update(
714 &mut self,
715 key: BlobImageKey,
716 data: Arc<BlobImageData>,
717 visible_rect: &DeviceIntRect,
718 dirty_rect: &BlobDirtyRect,
719 ) {
720 match self.blob_commands.entry(key) {
721 hash_map::Entry::Occupied(mut e) => {
722 let command = e.get_mut();
723 let dirty_rect = if let DirtyRect::Partial(rect) = *dirty_rect {
724 Box2d {
725 x1: rect.min.x,
726 y1: rect.min.y,
727 x2: rect.max.x,
728 y2: rect.max.y,
729 }
730 } else {
731 Box2d {
732 x1: i32::MIN,
733 y1: i32::MIN,
734 x2: i32::MAX,
735 y2: i32::MAX,
736 }
737 };
738 command.data = Arc::new(merge_blob_images(
739 &command.data,
740 &data,
741 dirty_rect,
742 command.visible_rect.into(),
743 visible_rect.into(),
744 ));
745 command.visible_rect = *visible_rect;
746 }
747 _ => {
748 panic!("missing image key");
749 }
750 }
751 }
752
delete(&mut self, key: BlobImageKey)753 fn delete(&mut self, key: BlobImageKey) {
754 self.blob_commands.remove(&key);
755 }
756
create_blob_rasterizer(&mut self) -> Box<dyn AsyncBlobImageRasterizer>757 fn create_blob_rasterizer(&mut self) -> Box<dyn AsyncBlobImageRasterizer> {
758 Box::new(Moz2dBlobRasterizer {
759 workers: Arc::clone(&self.workers),
760 workers_low_priority: Arc::clone(&self.workers_low_priority),
761 blob_commands: self.blob_commands.clone(),
762 enable_multithreading: self.enable_multithreading,
763 })
764 }
765
delete_font(&mut self, font: FontKey)766 fn delete_font(&mut self, font: FontKey) {
767 unsafe {
768 DeleteFontData(font);
769 }
770 }
771
delete_font_instance(&mut self, key: FontInstanceKey)772 fn delete_font_instance(&mut self, key: FontInstanceKey) {
773 unsafe {
774 DeleteBlobFont(key);
775 }
776 }
777
clear_namespace(&mut self, namespace: IdNamespace)778 fn clear_namespace(&mut self, namespace: IdNamespace) {
779 unsafe {
780 ClearBlobImageResources(namespace);
781 }
782 }
783
prepare_resources(&mut self, resources: &dyn BlobImageResources, requests: &[BlobImageParams])784 fn prepare_resources(&mut self, resources: &dyn BlobImageResources, requests: &[BlobImageParams]) {
785 for params in requests {
786 let commands = &self.blob_commands[¶ms.request.key];
787 let blob = Arc::clone(&commands.data);
788 self.prepare_request(&blob, resources);
789 }
790 }
791
enable_multithreading(&mut self, enable: bool)792 fn enable_multithreading(&mut self, enable: bool) {
793 self.enable_multithreading = enable;
794 }
795 }
796
797 use bindings::{WrFontInstanceKey, WrFontKey, WrIdNamespace};
798
799 #[allow(improper_ctypes)] // this is needed so that rustc doesn't complain about passing the &Arc<Vec> to an extern function
800 extern "C" {
HasFontData(key: WrFontKey) -> bool801 fn HasFontData(key: WrFontKey) -> bool;
AddFontData(key: WrFontKey, data: *const u8, size: usize, index: u32, vec: &ArcVecU8)802 fn AddFontData(key: WrFontKey, data: *const u8, size: usize, index: u32, vec: &ArcVecU8);
AddNativeFontHandle(key: WrFontKey, handle: *mut c_void, index: u32)803 fn AddNativeFontHandle(key: WrFontKey, handle: *mut c_void, index: u32);
DeleteFontData(key: WrFontKey)804 fn DeleteFontData(key: WrFontKey);
AddBlobFont( instance_key: WrFontInstanceKey, font_key: WrFontKey, size: f32, options: Option<&FontInstanceOptions>, platform_options: Option<&FontInstancePlatformOptions>, variations: *const FontVariation, num_variations: usize, )805 fn AddBlobFont(
806 instance_key: WrFontInstanceKey,
807 font_key: WrFontKey,
808 size: f32,
809 options: Option<&FontInstanceOptions>,
810 platform_options: Option<&FontInstancePlatformOptions>,
811 variations: *const FontVariation,
812 num_variations: usize,
813 );
DeleteBlobFont(key: WrFontInstanceKey)814 fn DeleteBlobFont(key: WrFontInstanceKey);
ClearBlobImageResources(namespace: WrIdNamespace)815 fn ClearBlobImageResources(namespace: WrIdNamespace);
816
817 }
818
819 impl Moz2dBlobImageHandler {
820 /// Create a new BlobImageHandler with the given thread pool.
new(workers: Arc<ThreadPool>, workers_low_priority: Arc<ThreadPool>) -> Self821 pub fn new(workers: Arc<ThreadPool>, workers_low_priority: Arc<ThreadPool>) -> Self {
822 Moz2dBlobImageHandler {
823 blob_commands: HashMap::new(),
824 workers,
825 workers_low_priority,
826 enable_multithreading: true,
827 }
828 }
829
830 /// Does early preprocessing of a blob's resources.
831 ///
832 /// Currently just sets up fonts found in the blob.
prepare_request(&self, blob: &[u8], resources: &dyn BlobImageResources)833 fn prepare_request(&self, blob: &[u8], resources: &dyn BlobImageResources) {
834 #[cfg(target_os = "windows")]
835 fn process_native_font_handle(key: FontKey, handle: &NativeFontHandle) {
836 let file = dwrote::FontFile::new_from_path(&handle.path).unwrap();
837 let face = file
838 .create_face(handle.index, dwrote::DWRITE_FONT_SIMULATIONS_NONE)
839 .unwrap();
840 unsafe { AddNativeFontHandle(key, face.as_ptr() as *mut c_void, 0) };
841 }
842
843 #[cfg(target_os = "macos")]
844 fn process_native_font_handle(key: FontKey, handle: &NativeFontHandle) {
845 unsafe { AddNativeFontHandle(key, handle.0.as_ptr() as *mut c_void, 0) };
846 }
847
848 #[cfg(not(any(target_os = "macos", target_os = "windows")))]
849 fn process_native_font_handle(key: FontKey, handle: &NativeFontHandle) {
850 let cstr = CString::new(handle.path.as_os_str().as_bytes()).unwrap();
851 unsafe { AddNativeFontHandle(key, cstr.as_ptr() as *mut c_void, handle.index) };
852 }
853
854 fn process_fonts(
855 mut extra_data: BufReader,
856 resources: &dyn BlobImageResources,
857 unscaled_fonts: &mut Vec<FontKey>,
858 scaled_fonts: &mut Vec<FontInstanceKey>,
859 ) {
860 let font_count = extra_data.read_usize();
861 for _ in 0..font_count {
862 let font = extra_data.read_blob_font();
863 if scaled_fonts.contains(&font.font_instance_key) {
864 continue;
865 }
866 scaled_fonts.push(font.font_instance_key);
867 if let Some(instance) = resources.get_font_instance_data(font.font_instance_key) {
868 if !unscaled_fonts.contains(&instance.font_key) {
869 unscaled_fonts.push(instance.font_key);
870 if !unsafe { HasFontData(instance.font_key) } {
871 let template = resources.get_font_data(instance.font_key);
872 match template {
873 FontTemplate::Raw(ref data, ref index) => unsafe {
874 AddFontData(instance.font_key, data.as_ptr(), data.len(), *index, data);
875 },
876 FontTemplate::Native(ref handle) => {
877 process_native_font_handle(instance.font_key, handle);
878 }
879 }
880 }
881 }
882 unsafe {
883 AddBlobFont(
884 font.font_instance_key,
885 instance.font_key,
886 instance.size,
887 instance.options.as_ref(),
888 instance.platform_options.as_ref(),
889 instance.variations.as_ptr(),
890 instance.variations.len(),
891 );
892 }
893 }
894 }
895 }
896
897 {
898 let mut index = BlobReader::new(blob);
899 let mut unscaled_fonts = Vec::new();
900 let mut scaled_fonts = Vec::new();
901 while index.reader.pos < index.reader.buf.len() {
902 let e = index.read_entry();
903 process_fonts(
904 BufReader::new(&blob[e.end..e.extra_end]),
905 resources,
906 &mut unscaled_fonts,
907 &mut scaled_fonts,
908 );
909 }
910 }
911 }
912 }
913