/* 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/. */ extern crate gleam; extern crate glutin; extern crate rayon; extern crate webrender; extern crate winit; #[path = "common/boilerplate.rs"] mod boilerplate; use crate::boilerplate::{Example, HandyDandyRectBuilder}; use rayon::{ThreadPool, ThreadPoolBuilder}; use rayon::prelude::*; use std::collections::HashMap; use std::sync::Arc; use webrender::api::{self, DisplayListBuilder, DocumentId, PipelineId, PrimitiveFlags}; use webrender::api::{ColorF, CommonItemProperties, SpaceAndClipInfo, ImageDescriptorFlags}; use webrender::api::units::*; use webrender::render_api::*; use webrender::euclid::size2; // This example shows how to implement a very basic BlobImageHandler that can only render // a checkerboard pattern. // The deserialized command list internally used by this example is just a color. type ImageRenderingCommands = api::ColorU; // Serialize/deserialize the blob. // For real usecases you should probably use serde rather than doing it by hand. fn serialize_blob(color: api::ColorU) -> Arc> { Arc::new(vec![color.r, color.g, color.b, color.a]) } fn deserialize_blob(blob: &[u8]) -> Result { let mut iter = blob.iter(); return match (iter.next(), iter.next(), iter.next(), iter.next()) { (Some(&r), Some(&g), Some(&b), Some(&a)) => Ok(api::ColorU::new(r, g, b, a)), (Some(&a), None, None, None) => Ok(api::ColorU::new(a, a, a, a)), _ => Err(()), }; } // This is the function that applies the deserialized drawing commands and generates // actual image data. fn render_blob( commands: Arc, descriptor: &api::BlobImageDescriptor, tile: TileOffset, ) -> api::BlobImageResult { let color = *commands; // Note: This implementation ignores the dirty rect which isn't incorrect // but is a missed optimization. // Allocate storage for the result. Right now the resource cache expects the // tiles to have have no stride or offset. let bpp = 4; let mut texels = Vec::with_capacity((descriptor.rect.area() * bpp) 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 [w, h] = descriptor.rect.size().to_array(); let offset = descriptor.rect.min; for y in 0..h { for x in 0..w { // 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 + offset.x; let y2 = y + offset.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 { api::ImageFormat::BGRA8 => { texels.push(color.b * checker + tc); texels.push(color.g * checker + tc); texels.push(color.r * checker + tc); texels.push(color.a * checker + tc); } api::ImageFormat::R8 => { texels.push(color.a * checker + tc); } _ => { return Err(api::BlobImageError::Other( format!("Unsupported image format"), )); } } } } Ok(api::RasterizedBlobImage { data: Arc::new(texels), rasterized_rect: size2(w, h).into(), }) } struct CheckerboardRenderer { // We are going to defer the rendering work to worker threads. // Using a pre-built Arc rather than creating our own threads // makes it possible to share the same thread pool as the glyph renderer (if we // want to). workers: Arc, // The deserialized drawing commands. // In this example we store them in Arcs. This isn't necessary since in this simplified // case the command list is a simple 32 bits value and would be cheap to clone before sending // to the workers. But in a more realistic scenario the commands would typically be bigger // and more expensive to clone, so let's pretend it is also the case here. image_cmds: HashMap>, } impl CheckerboardRenderer { fn new(workers: Arc) -> Self { CheckerboardRenderer { image_cmds: HashMap::new(), workers, } } } impl api::BlobImageHandler for CheckerboardRenderer { fn create_similar(&self) -> Box { Box::new(CheckerboardRenderer::new(Arc::clone(&self.workers))) } fn add(&mut self, key: api::BlobImageKey, cmds: Arc, _visible_rect: &DeviceIntRect, _: api::TileSize) { self.image_cmds .insert(key, Arc::new(deserialize_blob(&cmds[..]).unwrap())); } fn update(&mut self, key: api::BlobImageKey, cmds: Arc, _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 .insert(key, Arc::new(deserialize_blob(&cmds[..]).unwrap())); } fn delete(&mut self, key: api::BlobImageKey) { self.image_cmds.remove(&key); } fn prepare_resources( &mut self, _services: &dyn api::BlobImageResources, _requests: &[api::BlobImageParams], ) {} fn enable_multithreading(&mut self, _: bool) {} fn delete_font(&mut self, _font: api::FontKey) {} fn delete_font_instance(&mut self, _instance: api::FontInstanceKey) {} fn clear_namespace(&mut self, _namespace: api::IdNamespace) {} fn create_blob_rasterizer(&mut self) -> Box { Box::new(Rasterizer { workers: Arc::clone(&self.workers), image_cmds: self.image_cmds.clone(), }) } } struct Rasterizer { workers: Arc, image_cmds: HashMap>, } impl api::AsyncBlobImageRasterizer for Rasterizer { fn rasterize( &mut self, requests: &[api::BlobImageParams], _low_priority: bool ) -> Vec<(api::BlobImageRequest, api::BlobImageResult)> { let requests: Vec<(&api::BlobImageParams, Arc)> = requests.into_iter().map(|params| { (params, Arc::clone(&self.image_cmds[¶ms.request.key])) }).collect(); self.workers.install(|| { requests.into_par_iter().map(|(params, commands)| { (params.request, render_blob(commands, ¶ms.descriptor, params.request.tile)) }).collect() }) } } struct App {} impl Example for App { fn render( &mut self, api: &mut RenderApi, builder: &mut DisplayListBuilder, txn: &mut Transaction, _device_size: DeviceIntSize, pipeline_id: PipelineId, _document_id: DocumentId, ) { let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id); builder.push_simple_stacking_context( LayoutPoint::zero(), space_and_clip.spatial_id, PrimitiveFlags::IS_BACKFACE_VISIBLE, ); let size1 = DeviceIntSize::new(500, 500); let blob_img1 = api.generate_blob_image_key(); txn.add_blob_image( blob_img1, api::ImageDescriptor::new( size1.width, size1.height, api::ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE, ), serialize_blob(api::ColorU::new(50, 50, 150, 255)), size1.into(), Some(128), ); let bounds = (30, 30).by(size1.width, size1.height); builder.push_image( &CommonItemProperties::new(bounds, space_and_clip), bounds, api::ImageRendering::Auto, api::AlphaType::PremultipliedAlpha, blob_img1.as_image(), ColorF::WHITE, ); let size2 = DeviceIntSize::new(256, 256); let blob_img2 = api.generate_blob_image_key(); txn.add_blob_image( blob_img2, api::ImageDescriptor::new( size2.width, size2.height, api::ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE, ), serialize_blob(api::ColorU::new(50, 150, 50, 255)), size2.into(), None, ); let bounds = (600, 600).by(size2.width, size2.height); builder.push_image( &CommonItemProperties::new(bounds, space_and_clip), bounds, api::ImageRendering::Auto, api::AlphaType::PremultipliedAlpha, blob_img2.as_image(), ColorF::WHITE, ); builder.pop_stacking_context(); } } fn main() { let workers = ThreadPoolBuilder::new().thread_name(|idx| format!("WebRender:Worker#{}", idx)) .build(); let workers = Arc::new(workers.unwrap()); let opts = webrender::WebRenderOptions { workers: Some(Arc::clone(&workers)), // Register our blob renderer, so that WebRender integrates it in the resource cache.. // Share the same pool of worker threads between WebRender and our blob renderer. blob_image_handler: Some(Box::new(CheckerboardRenderer::new(Arc::clone(&workers)))), ..Default::default() }; let mut app = App {}; boilerplate::main_wrapper(&mut app, Some(opts)); }