diff options
Diffstat (limited to 'gfx/wr/examples/blob.rs')
-rw-r--r-- | gfx/wr/examples/blob.rs | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/gfx/wr/examples/blob.rs b/gfx/wr/examples/blob.rs new file mode 100644 index 0000000000..65c048c87b --- /dev/null +++ b/gfx/wr/examples/blob.rs @@ -0,0 +1,290 @@ +/* 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<Vec<u8>> { + Arc::new(vec![color.r, color.g, color.b, color.a]) +} + +fn deserialize_blob(blob: &[u8]) -> Result<ImageRenderingCommands, ()> { + 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<ImageRenderingCommands>, + 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.size.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.origin; + + 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<ThreadPool> 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<ThreadPool>, + + // 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<api::BlobImageKey, Arc<ImageRenderingCommands>>, +} + +impl CheckerboardRenderer { + fn new(workers: Arc<ThreadPool>) -> Self { + CheckerboardRenderer { + image_cmds: HashMap::new(), + workers, + } + } +} + +impl api::BlobImageHandler for CheckerboardRenderer { + fn create_similar(&self) -> Box<dyn api::BlobImageHandler> { + Box::new(CheckerboardRenderer::new(Arc::clone(&self.workers))) + } + + fn add(&mut self, key: api::BlobImageKey, cmds: Arc<api::BlobImageData>, + _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<api::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 + .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<dyn api::AsyncBlobImageRasterizer> { + Box::new(Rasterizer { + workers: Arc::clone(&self.workers), + image_cmds: self.image_cmds.clone(), + }) + } +} + +struct Rasterizer { + workers: Arc<ThreadPool>, + image_cmds: HashMap<api::BlobImageKey, Arc<ImageRenderingCommands>>, +} + +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<ImageRenderingCommands>)> = 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::RendererOptions { + 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)); +} |