Source code
Revision control
Copy as Markdown
Other Tools
/* 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
// A very basic BlobImageRasterizer that can only render a checkerboard pattern.
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;
use webrender::api::*;
use webrender::api::units::{BlobDirtyRect, BlobToDeviceTranslation, TileOffset};
use webrender::api::units::DeviceIntRect;
// Serialize/deserialize the blob.
pub fn serialize_blob(color: ColorU) -> Arc<Vec<u8>> {
Arc::new(vec![color.r, color.g, color.b, color.a])
}
fn deserialize_blob(blob: &[u8]) -> Result<ColorU, ()> {
let mut iter = blob.iter();
match (iter.next(), iter.next(), iter.next(), iter.next()) {
(Some(&r), Some(&g), Some(&b), Some(&a)) => Ok(ColorU::new(r, g, b, a)),
(Some(&a), None, None, None) => Ok(ColorU::new(a, a, a, a)),
_ => Err(()),
}
}
// perform floor((x * a) / 255. + 0.5) see "Three wrongs make a right" for derivation
fn premul(x: u8, a: u8) -> u8 {
let t = (x as u32) * (a as u32) + 128;
((t + (t >> 8)) >> 8) as u8
}
// This is the function that applies the deserialized drawing commands and generates
// actual image data.
fn render_blob(
color: ColorU,
descriptor: &BlobImageDescriptor,
tile: TileOffset,
_tile_size: TileSize,
dirty_rect: &BlobDirtyRect,
) -> BlobImageResult {
// Allocate storage for the result. Right now the resource cache expects the
// tiles to have have no stride or offset.
let buf_size = descriptor.rect.area() *
descriptor.format.bytes_per_pixel();
let mut texels = vec![0u8; (buf_size) as usize];
// Generate a per-tile pattern to see it in the demo. For a real use case it would not
// make sense for the rendered content to depend on its tile.
let tile_checker = (tile.x % 2 == 0) != (tile.y % 2 == 0);
let dirty_rect = dirty_rect.to_subrect_of(&descriptor.rect);
// We want the dirty rect local to the tile rather than the whole image.
let tx: BlobToDeviceTranslation = (-descriptor.rect.min.to_vector()).into();
let rasterized_rect = tx.transform_box(&dirty_rect);
for y in rasterized_rect.min.y .. rasterized_rect.max.y {
for x in rasterized_rect.min.x .. rasterized_rect.max.x {
// Apply the tile's offset. This is important: all drawing commands should be
// translated by this offset to give correct results with tiled blob images.
let x2 = x + descriptor.rect.min.x;
let y2 = y + descriptor.rect.min.y;
// Render a simple checkerboard pattern
let checker = if (x2 % 20 >= 10) != (y2 % 20 >= 10) {
1
} else {
0
};
// ..nested in the per-tile checkerboard pattern
let tc = if tile_checker { 0 } else { (1 - checker) * 40 };
match descriptor.format {
ImageFormat::BGRA8 => {
let a = color.a * checker + tc;
let pixel_offset = ((y * descriptor.rect.width() + x) * 4) as usize;
texels[pixel_offset ] = premul(color.b * checker + tc, a);
texels[pixel_offset + 1] = premul(color.g * checker + tc, a);
texels[pixel_offset + 2] = premul(color.r * checker + tc, a);
texels[pixel_offset + 3] = a;
}
ImageFormat::R8 => {
texels[(y * descriptor.rect.width() + x) as usize] = color.a * checker + tc;
}
_ => {
return Err(BlobImageError::Other(
format!("Unsupported image format {:?}", descriptor.format),
));
}
}
}
}
Ok(RasterizedBlobImage {
data: Arc::new(texels),
rasterized_rect,
})
}
/// See rawtest.rs. We use this to test that blob images are requested the right
/// amount of times.
pub struct BlobCallbacks {
pub request: Box<dyn Fn(&[BlobImageParams]) + Send + 'static>,
}
impl BlobCallbacks {
pub fn new() -> Self {
BlobCallbacks { request: Box::new(|_|()) }
}
}
pub struct CheckerboardRenderer {
image_cmds: HashMap<BlobImageKey, (ColorU, TileSize)>,
callbacks: Arc<Mutex<BlobCallbacks>>,
}
impl CheckerboardRenderer {
pub fn new(callbacks: Arc<Mutex<BlobCallbacks>>) -> Self {
CheckerboardRenderer {
callbacks,
image_cmds: HashMap::new(),
}
}
}
impl BlobImageHandler for CheckerboardRenderer {
fn create_similar(&self) -> Box<dyn BlobImageHandler> {
Box::new(CheckerboardRenderer::new(Arc::clone(&self.callbacks)))
}
fn add(&mut self, key: BlobImageKey, cmds: Arc<BlobImageData>,
_visible_rect: &DeviceIntRect, tile_size: TileSize) {
self.image_cmds
.insert(key, (deserialize_blob(&cmds[..]).unwrap(), tile_size));
}
fn update(&mut self, key: BlobImageKey, cmds: Arc<BlobImageData>,
_visible_rect: &DeviceIntRect, _dirty_rect: &BlobDirtyRect) {
// Here, updating is just replacing the current version of the commands with
// the new one (no incremental updates).
self.image_cmds.get_mut(&key).unwrap().0 = deserialize_blob(&cmds[..]).unwrap();
}
fn delete(&mut self, key: BlobImageKey) {
self.image_cmds.remove(&key);
}
fn delete_font(&mut self, _key: FontKey) {}
fn delete_font_instance(&mut self, _key: FontInstanceKey) {}
fn clear_namespace(&mut self, _namespace: IdNamespace) {}
fn prepare_resources(
&mut self,
_services: &dyn BlobImageResources,
requests: &[BlobImageParams],
) {
if !requests.is_empty() {
(self.callbacks.lock().unwrap().request)(requests);
}
}
fn create_blob_rasterizer(&mut self) -> Box<dyn AsyncBlobImageRasterizer> {
Box::new(Rasterizer { image_cmds: self.image_cmds.clone() })
}
fn enable_multithreading(&mut self, _enable: bool) {}
}
struct Command {
request: BlobImageRequest,
color: ColorU,
descriptor: BlobImageDescriptor,
tile: TileOffset,
tile_size: TileSize,
dirty_rect: BlobDirtyRect,
}
struct Rasterizer {
image_cmds: HashMap<BlobImageKey, (ColorU, TileSize)>,
}
impl AsyncBlobImageRasterizer for Rasterizer {
fn rasterize(
&mut self,
requests: &[BlobImageParams],
_low_priority: bool,
_tile_pool: &mut BlobTilePool,
) -> Vec<(BlobImageRequest, BlobImageResult)> {
let requests: Vec<Command> = requests.iter().map(
|item| {
let (color, tile_size) = self.image_cmds[&item.request.key];
Command {
request: item.request,
color,
tile_size,
tile: item.request.tile,
descriptor: item.descriptor,
dirty_rect: item.dirty_rect,
}
}
).collect();
requests.iter().map(|cmd| {
(cmd.request, render_blob(cmd.color, &cmd.descriptor, cmd.tile, cmd.tile_size, &cmd.dirty_rect))
}).collect()
}
}