diff options
Diffstat (limited to 'gfx/wr/examples')
-rw-r--r-- | gfx/wr/examples/Cargo.toml | 67 | ||||
-rw-r--r-- | gfx/wr/examples/README.md | 8 | ||||
-rw-r--r-- | gfx/wr/examples/alpha_perf.rs | 94 | ||||
-rw-r--r-- | gfx/wr/examples/animation.rs | 220 | ||||
-rw-r--r-- | gfx/wr/examples/basic.rs | 322 | ||||
-rw-r--r-- | gfx/wr/examples/blob.rs | 290 | ||||
-rw-r--r-- | gfx/wr/examples/common/boilerplate.rs | 329 | ||||
-rw-r--r-- | gfx/wr/examples/common/image_helper.rs | 19 | ||||
-rw-r--r-- | gfx/wr/examples/document.rs | 143 | ||||
-rw-r--r-- | gfx/wr/examples/iframe.rs | 96 | ||||
-rw-r--r-- | gfx/wr/examples/image_resize.rs | 122 | ||||
-rw-r--r-- | gfx/wr/examples/multiwindow.rs | 327 | ||||
-rw-r--r-- | gfx/wr/examples/scrolling.rs | 243 | ||||
-rw-r--r-- | gfx/wr/examples/texture_cache_stress.rs | 322 | ||||
-rw-r--r-- | gfx/wr/examples/yuv.rs | 225 |
15 files changed, 2827 insertions, 0 deletions
diff --git a/gfx/wr/examples/Cargo.toml b/gfx/wr/examples/Cargo.toml new file mode 100644 index 0000000000..3cbcf2ee48 --- /dev/null +++ b/gfx/wr/examples/Cargo.toml @@ -0,0 +1,67 @@ +[package] +name = "webrender-examples" +version = "0.1.0" +authors = ["Glenn Watson <gw@intuitionlibrary.com>"] +license = "MPL-2.0" +repository = "https://github.com/servo/webrender" +edition = "2018" + +[[bin]] +name = "alpha_perf" +path = "alpha_perf.rs" + +[[bin]] +name = "animation" +path = "animation.rs" + +[[bin]] +name = "basic" +path = "basic.rs" + +[[bin]] +name = "blob" +path = "blob.rs" + +[[bin]] +name = "document" +path = "document.rs" + +[[bin]] +name = "iframe" +path = "iframe.rs" + +[[bin]] +name = "image_resize" +path = "image_resize.rs" + +[[bin]] +name = "multiwindow" +path = "multiwindow.rs" + +[[bin]] +name = "scrolling" +path = "scrolling.rs" + +[[bin]] +name = "texture_cache_stress" +path = "texture_cache_stress.rs" + +[[bin]] +name = "yuv" +path = "yuv.rs" + +[features] +debug = ["webrender/capture", "webrender/debugger", "webrender/profiler"] + +[dependencies] +app_units = "0.7" +env_logger = "0.5" +euclid = "0.22" +gleam = "0.13" +glutin = "0.21" +rayon = "1" +webrender = { path = "../webrender" } +winit = "0.19" + +[target.'cfg(target_os = "macos")'.dependencies] +core-foundation = "0.7" diff --git a/gfx/wr/examples/README.md b/gfx/wr/examples/README.md new file mode 100644 index 0000000000..68efd33a89 --- /dev/null +++ b/gfx/wr/examples/README.md @@ -0,0 +1,8 @@ +# Examples + +This directory contains a collection of examples which uses the WebRender API. + +To run an example e.g. `basic`, try: +``` +cargo run --bin basic +``` diff --git a/gfx/wr/examples/alpha_perf.rs b/gfx/wr/examples/alpha_perf.rs new file mode 100644 index 0000000000..3d8d019de1 --- /dev/null +++ b/gfx/wr/examples/alpha_perf.rs @@ -0,0 +1,94 @@ +/* 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 euclid; +extern crate gleam; +extern crate glutin; +extern crate webrender; +extern crate winit; + +#[path = "common/boilerplate.rs"] +mod boilerplate; + +use crate::boilerplate::{Example, HandyDandyRectBuilder}; +use std::cmp; +use webrender::api::*; +use webrender::render_api::*; +use webrender::api::units::DeviceIntSize; + + +struct App { + rect_count: usize, +} + +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 bounds = (0, 0).to(1920, 1080); + let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id); + + builder.push_simple_stacking_context( + bounds.origin, + space_and_clip.spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + + for _ in 0 .. self.rect_count { + builder.push_rect( + &CommonItemProperties::new(bounds, space_and_clip), + bounds, + ColorF::new(1.0, 1.0, 1.0, 0.05) + ); + } + + builder.pop_stacking_context(); + } + + fn on_event( + &mut self, + event: winit::WindowEvent, + _api: &mut RenderApi, + _document_id: DocumentId + ) -> bool { + match event { + winit::WindowEvent::KeyboardInput { + input: winit::KeyboardInput { + state: winit::ElementState::Pressed, + virtual_keycode: Some(key), + .. + }, + .. + } => { + match key { + winit::VirtualKeyCode::Right => { + self.rect_count += 1; + println!("rects = {}", self.rect_count); + } + winit::VirtualKeyCode::Left => { + self.rect_count = cmp::max(self.rect_count, 1) - 1; + println!("rects = {}", self.rect_count); + } + _ => {} + }; + } + _ => (), + } + + true + } +} + +fn main() { + let mut app = App { + rect_count: 1, + }; + boilerplate::main_wrapper(&mut app, None); +} diff --git a/gfx/wr/examples/animation.rs b/gfx/wr/examples/animation.rs new file mode 100644 index 0000000000..b72b90b43e --- /dev/null +++ b/gfx/wr/examples/animation.rs @@ -0,0 +1,220 @@ +/* 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/. */ + +//! This example creates a 200x200 white rect and allows the user to move it +//! around by using the arrow keys and rotate with '<'/'>'. +//! It does this by using the animation API. + +//! The example also features seamless opaque/transparent split of a +//! rounded cornered rectangle, which is done automatically during the +//! scene building for render optimization. + +extern crate euclid; +extern crate gleam; +extern crate glutin; +extern crate webrender; +extern crate winit; + +#[path = "common/boilerplate.rs"] +mod boilerplate; + +use crate::boilerplate::{Example, HandyDandyRectBuilder}; +use euclid::Angle; +use webrender::api::*; +use webrender::render_api::*; +use webrender::api::units::*; + + +struct App { + property_key0: PropertyBindingKey<LayoutTransform>, + property_key1: PropertyBindingKey<LayoutTransform>, + property_key2: PropertyBindingKey<LayoutTransform>, + opacity_key: PropertyBindingKey<f32>, + opacity: f32, + angle0: f32, + angle1: f32, + angle2: f32, +} + +impl App { + fn add_rounded_rect( + &mut self, + bounds: LayoutRect, + color: ColorF, + builder: &mut DisplayListBuilder, + pipeline_id: PipelineId, + property_key: PropertyBindingKey<LayoutTransform>, + opacity_key: Option<PropertyBindingKey<f32>>, + ) { + let filters = match opacity_key { + Some(opacity_key) => { + vec![ + FilterOp::Opacity(PropertyBinding::Binding(opacity_key, self.opacity), self.opacity), + ] + } + None => { + vec![] + } + }; + + let spatial_id = builder.push_reference_frame( + bounds.origin, + SpatialId::root_scroll_node(pipeline_id), + TransformStyle::Flat, + PropertyBinding::Binding(property_key, LayoutTransform::identity()), + ReferenceFrameKind::Transform, + ); + + builder.push_simple_stacking_context_with_filters( + LayoutPoint::zero(), + spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + &filters, + &[], + &[] + ); + + let space_and_clip = SpaceAndClipInfo { + spatial_id, + clip_id: ClipId::root(pipeline_id), + }; + let clip_bounds = LayoutRect::new(LayoutPoint::zero(), bounds.size); + let complex_clip = ComplexClipRegion { + rect: clip_bounds, + radii: BorderRadius::uniform(30.0), + mode: ClipMode::Clip, + }; + let clip_id = builder.define_clip_rounded_rect( + &space_and_clip, + complex_clip, + ); + + // Fill it with a white rect + builder.push_rect( + &CommonItemProperties::new( + LayoutRect::new(LayoutPoint::zero(), bounds.size), + SpaceAndClipInfo { + spatial_id, + clip_id, + } + ), + LayoutRect::new(LayoutPoint::zero(), bounds.size), + color, + ); + + builder.pop_stacking_context(); + builder.pop_reference_frame(); + } +} + +impl Example for App { + const WIDTH: u32 = 2048; + const HEIGHT: u32 = 1536; + + fn render( + &mut self, + _api: &mut RenderApi, + builder: &mut DisplayListBuilder, + _txn: &mut Transaction, + _device_size: DeviceIntSize, + pipeline_id: PipelineId, + _document_id: DocumentId, + ) { + let opacity_key = self.opacity_key; + + let bounds = (150, 150).to(250, 250); + let key0 = self.property_key0; + self.add_rounded_rect(bounds, ColorF::new(1.0, 0.0, 0.0, 0.5), builder, pipeline_id, key0, Some(opacity_key)); + + let bounds = (400, 400).to(600, 600); + let key1 = self.property_key1; + self.add_rounded_rect(bounds, ColorF::new(0.0, 1.0, 0.0, 0.5), builder, pipeline_id, key1, None); + + let bounds = (200, 500).to(350, 580); + let key2 = self.property_key2; + self.add_rounded_rect(bounds, ColorF::new(0.0, 0.0, 1.0, 0.5), builder, pipeline_id, key2, None); + } + + fn on_event(&mut self, win_event: winit::WindowEvent, api: &mut RenderApi, document_id: DocumentId) -> bool { + let mut rebuild_display_list = false; + + match win_event { + winit::WindowEvent::KeyboardInput { + input: winit::KeyboardInput { + state: winit::ElementState::Pressed, + virtual_keycode: Some(key), + .. + }, + .. + } => { + let (delta_angle, delta_opacity) = match key { + winit::VirtualKeyCode::Down => (0.0, -0.1), + winit::VirtualKeyCode::Up => (0.0, 0.1), + winit::VirtualKeyCode::Right => (1.0, 0.0), + winit::VirtualKeyCode::Left => (-1.0, 0.0), + winit::VirtualKeyCode::R => { + rebuild_display_list = true; + (0.0, 0.0) + } + _ => return false, + }; + // Update the transform based on the keyboard input and push it to + // webrender using the generate_frame API. This will recomposite with + // the updated transform. + self.opacity += delta_opacity; + self.angle0 += delta_angle * 0.1; + self.angle1 += delta_angle * 0.2; + self.angle2 -= delta_angle * 0.15; + let xf0 = LayoutTransform::rotation(0.0, 0.0, 1.0, Angle::radians(self.angle0)); + let xf1 = LayoutTransform::rotation(0.0, 0.0, 1.0, Angle::radians(self.angle1)); + let xf2 = LayoutTransform::rotation(0.0, 0.0, 1.0, Angle::radians(self.angle2)); + let mut txn = Transaction::new(); + txn.update_dynamic_properties( + DynamicProperties { + transforms: vec![ + PropertyValue { + key: self.property_key0, + value: xf0, + }, + PropertyValue { + key: self.property_key1, + value: xf1, + }, + PropertyValue { + key: self.property_key2, + value: xf2, + }, + ], + floats: vec![ + PropertyValue { + key: self.opacity_key, + value: self.opacity, + } + ], + colors: vec![], + }, + ); + txn.generate_frame(0); + api.send_transaction(document_id, txn); + } + _ => (), + } + + rebuild_display_list + } +} + +fn main() { + let mut app = App { + property_key0: PropertyBindingKey::new(42), // arbitrary magic number + property_key1: PropertyBindingKey::new(44), // arbitrary magic number + property_key2: PropertyBindingKey::new(45), // arbitrary magic number + opacity_key: PropertyBindingKey::new(43), + opacity: 0.5, + angle0: 0.0, + angle1: 0.0, + angle2: 0.0, + }; + boilerplate::main_wrapper(&mut app, None); +} diff --git a/gfx/wr/examples/basic.rs b/gfx/wr/examples/basic.rs new file mode 100644 index 0000000000..8c7d60c60c --- /dev/null +++ b/gfx/wr/examples/basic.rs @@ -0,0 +1,322 @@ +/* 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 euclid; +extern crate gleam; +extern crate glutin; +extern crate webrender; +extern crate winit; + +#[path = "common/boilerplate.rs"] +mod boilerplate; + +use crate::boilerplate::{Example, HandyDandyRectBuilder}; +use euclid::vec2; +use winit::TouchPhase; +use std::collections::HashMap; +use webrender::ShaderPrecacheFlags; +use webrender::api::*; +use webrender::render_api::*; +use webrender::api::units::*; + + +#[derive(Debug)] +enum Gesture { + None, + Pan, + Zoom, +} + +#[derive(Debug)] +struct Touch { + id: u64, + start_x: f32, + start_y: f32, + current_x: f32, + current_y: f32, +} + +fn dist(x0: f32, y0: f32, x1: f32, y1: f32) -> f32 { + let dx = x0 - x1; + let dy = y0 - y1; + ((dx * dx) + (dy * dy)).sqrt() +} + +impl Touch { + fn distance_from_start(&self) -> f32 { + dist(self.start_x, self.start_y, self.current_x, self.current_y) + } + + fn initial_distance_from_other(&self, other: &Touch) -> f32 { + dist(self.start_x, self.start_y, other.start_x, other.start_y) + } + + fn current_distance_from_other(&self, other: &Touch) -> f32 { + dist( + self.current_x, + self.current_y, + other.current_x, + other.current_y, + ) + } +} + +struct TouchState { + active_touches: HashMap<u64, Touch>, + current_gesture: Gesture, + start_zoom: f32, + current_zoom: f32, + start_pan: DeviceIntPoint, + current_pan: DeviceIntPoint, +} + +enum TouchResult { + None, + Pan(DeviceIntPoint), + Zoom(f32), +} + +impl TouchState { + fn new() -> TouchState { + TouchState { + active_touches: HashMap::new(), + current_gesture: Gesture::None, + start_zoom: 1.0, + current_zoom: 1.0, + start_pan: DeviceIntPoint::zero(), + current_pan: DeviceIntPoint::zero(), + } + } + + fn handle_event(&mut self, touch: winit::Touch) -> TouchResult { + match touch.phase { + TouchPhase::Started => { + debug_assert!(!self.active_touches.contains_key(&touch.id)); + self.active_touches.insert( + touch.id, + Touch { + id: touch.id, + start_x: touch.location.x as f32, + start_y: touch.location.y as f32, + current_x: touch.location.x as f32, + current_y: touch.location.y as f32, + }, + ); + self.current_gesture = Gesture::None; + } + TouchPhase::Moved => { + match self.active_touches.get_mut(&touch.id) { + Some(active_touch) => { + active_touch.current_x = touch.location.x as f32; + active_touch.current_y = touch.location.y as f32; + } + None => panic!("move touch event with unknown touch id!"), + } + + match self.current_gesture { + Gesture::None => { + let mut over_threshold_count = 0; + let active_touch_count = self.active_touches.len(); + + for (_, touch) in &self.active_touches { + if touch.distance_from_start() > 8.0 { + over_threshold_count += 1; + } + } + + if active_touch_count == over_threshold_count { + if active_touch_count == 1 { + self.start_pan = self.current_pan; + self.current_gesture = Gesture::Pan; + } else if active_touch_count == 2 { + self.start_zoom = self.current_zoom; + self.current_gesture = Gesture::Zoom; + } + } + } + Gesture::Pan => { + let keys: Vec<u64> = self.active_touches.keys().cloned().collect(); + debug_assert!(keys.len() == 1); + let active_touch = &self.active_touches[&keys[0]]; + let x = active_touch.current_x - active_touch.start_x; + let y = active_touch.current_y - active_touch.start_y; + self.current_pan.x = self.start_pan.x + x.round() as i32; + self.current_pan.y = self.start_pan.y + y.round() as i32; + return TouchResult::Pan(self.current_pan); + } + Gesture::Zoom => { + let keys: Vec<u64> = self.active_touches.keys().cloned().collect(); + debug_assert!(keys.len() == 2); + let touch0 = &self.active_touches[&keys[0]]; + let touch1 = &self.active_touches[&keys[1]]; + let initial_distance = touch0.initial_distance_from_other(touch1); + let current_distance = touch0.current_distance_from_other(touch1); + self.current_zoom = self.start_zoom * current_distance / initial_distance; + return TouchResult::Zoom(self.current_zoom); + } + } + } + TouchPhase::Ended | TouchPhase::Cancelled => { + self.active_touches.remove(&touch.id).unwrap(); + self.current_gesture = Gesture::None; + } + } + + TouchResult::None + } +} + +fn main() { + let mut app = App { + touch_state: TouchState::new(), + }; + boilerplate::main_wrapper(&mut app, None); +} + +struct App { + touch_state: TouchState, +} + +impl Example for App { + // Make this the only example to test all shaders for compile errors. + const PRECACHE_SHADER_FLAGS: ShaderPrecacheFlags = ShaderPrecacheFlags::FULL_COMPILE; + + fn render( + &mut self, + api: &mut RenderApi, + builder: &mut DisplayListBuilder, + txn: &mut Transaction, + _: DeviceIntSize, + pipeline_id: PipelineId, + _document_id: DocumentId, + ) { + let content_bounds = LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(800.0, 600.0)); + let root_space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id); + let spatial_id = root_space_and_clip.spatial_id; + + builder.push_simple_stacking_context( + content_bounds.origin, + spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + + let image_mask_key = api.generate_image_key(); + txn.add_image( + image_mask_key, + ImageDescriptor::new(2, 2, ImageFormat::R8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::new(vec![0, 80, 180, 255]), + None, + ); + let mask = ImageMask { + image: image_mask_key, + rect: (75, 75).by(100, 100), + repeat: false, + }; + let complex = ComplexClipRegion::new( + (50, 50).to(150, 150), + BorderRadius::uniform(20.0), + ClipMode::Clip + ); + let mask_clip_id = builder.define_clip_image_mask( + &root_space_and_clip, + mask, + ); + let clip_id = builder.define_clip_rounded_rect( + &SpaceAndClipInfo { + spatial_id: root_space_and_clip.spatial_id, + clip_id: mask_clip_id, + }, + complex, + ); + + builder.push_rect( + &CommonItemProperties::new( + (100, 100).to(200, 200), + SpaceAndClipInfo { spatial_id, clip_id }, + ), + (100, 100).to(200, 200), + ColorF::new(0.0, 1.0, 0.0, 1.0), + ); + + builder.push_rect( + &CommonItemProperties::new( + (250, 100).to(350, 200), + SpaceAndClipInfo { spatial_id, clip_id }, + ), + (250, 100).to(350, 200), + ColorF::new(0.0, 1.0, 0.0, 1.0), + ); + let border_side = BorderSide { + color: ColorF::new(0.0, 0.0, 1.0, 1.0), + style: BorderStyle::Groove, + }; + let border_widths = LayoutSideOffsets::new_all_same(10.0); + let border_details = BorderDetails::Normal(NormalBorder { + top: border_side, + right: border_side, + bottom: border_side, + left: border_side, + radius: BorderRadius::uniform(20.0), + do_aa: true, + }); + + let bounds = (100, 100).to(200, 200); + builder.push_border( + &CommonItemProperties::new( + bounds, + SpaceAndClipInfo { spatial_id, clip_id }, + ), + bounds, + border_widths, + border_details, + ); + + if false { + // draw box shadow? + let simple_box_bounds = (20, 200).by(50, 50); + let offset = vec2(10.0, 10.0); + let color = ColorF::new(1.0, 1.0, 1.0, 1.0); + let blur_radius = 0.0; + let spread_radius = 0.0; + let simple_border_radius = 8.0; + let box_shadow_type = BoxShadowClipMode::Inset; + + builder.push_box_shadow( + &CommonItemProperties::new(content_bounds, root_space_and_clip), + simple_box_bounds, + offset, + color, + blur_radius, + spread_radius, + BorderRadius::uniform(simple_border_radius), + box_shadow_type, + ); + } + + builder.pop_stacking_context(); + } + + fn on_event(&mut self, event: winit::WindowEvent, api: &mut RenderApi, document_id: DocumentId) -> bool { + let mut txn = Transaction::new(); + match event { + winit::WindowEvent::Touch(touch) => match self.touch_state.handle_event(touch) { + TouchResult::Pan(pan) => { + txn.set_pan(pan); + } + TouchResult::Zoom(zoom) => { + txn.set_pinch_zoom(ZoomFactor::new(zoom)); + } + TouchResult::None => {} + }, + _ => (), + } + + if !txn.is_empty() { + txn.generate_frame(0); + api.send_transaction(document_id, txn); + } + + false + } +} 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)); +} diff --git a/gfx/wr/examples/common/boilerplate.rs b/gfx/wr/examples/common/boilerplate.rs new file mode 100644 index 0000000000..d07084031a --- /dev/null +++ b/gfx/wr/examples/common/boilerplate.rs @@ -0,0 +1,329 @@ +/* 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 gleam::gl; +use glutin; +use std::env; +use std::path::PathBuf; +use webrender; +use winit; +use webrender::{DebugFlags, ShaderPrecacheFlags}; +use webrender::api::*; +use webrender::render_api::*; +use webrender::api::units::*; + +struct Notifier { + events_proxy: winit::EventsLoopProxy, +} + +impl Notifier { + fn new(events_proxy: winit::EventsLoopProxy) -> Notifier { + Notifier { events_proxy } + } +} + +impl RenderNotifier for Notifier { + fn clone(&self) -> Box<dyn RenderNotifier> { + Box::new(Notifier { + events_proxy: self.events_proxy.clone(), + }) + } + + fn wake_up(&self, _composite_needed: bool) { + #[cfg(not(target_os = "android"))] + let _ = self.events_proxy.wakeup(); + } + + fn new_frame_ready(&self, + _: DocumentId, + _scrolled: bool, + composite_needed: bool, + _render_time: Option<u64>) { + self.wake_up(composite_needed); + } +} + +pub trait HandyDandyRectBuilder { + fn to(&self, x2: i32, y2: i32) -> LayoutRect; + fn by(&self, w: i32, h: i32) -> LayoutRect; +} +// Allows doing `(x, y).to(x2, y2)` or `(x, y).by(width, height)` with i32 +// values to build a f32 LayoutRect +impl HandyDandyRectBuilder for (i32, i32) { + fn to(&self, x2: i32, y2: i32) -> LayoutRect { + LayoutRect::new( + LayoutPoint::new(self.0 as f32, self.1 as f32), + LayoutSize::new((x2 - self.0) as f32, (y2 - self.1) as f32), + ) + } + + fn by(&self, w: i32, h: i32) -> LayoutRect { + LayoutRect::new( + LayoutPoint::new(self.0 as f32, self.1 as f32), + LayoutSize::new(w as f32, h as f32), + ) + } +} + +pub trait Example { + const TITLE: &'static str = "WebRender Sample App"; + const PRECACHE_SHADER_FLAGS: ShaderPrecacheFlags = ShaderPrecacheFlags::EMPTY; + const WIDTH: u32 = 1920; + const HEIGHT: u32 = 1080; + + fn render( + &mut self, + api: &mut RenderApi, + builder: &mut DisplayListBuilder, + txn: &mut Transaction, + device_size: DeviceIntSize, + pipeline_id: PipelineId, + document_id: DocumentId, + ); + fn on_event( + &mut self, + _: winit::WindowEvent, + _: &mut RenderApi, + _: DocumentId, + ) -> bool { + false + } + fn get_image_handler( + &mut self, + _gl: &dyn gl::Gl, + ) -> Option<Box<dyn ExternalImageHandler>> { + None + } + fn draw_custom(&mut self, _gl: &dyn gl::Gl) { + } +} + +pub fn main_wrapper<E: Example>( + example: &mut E, + options: Option<webrender::RendererOptions>, +) { + env_logger::init(); + + #[cfg(target_os = "macos")] + { + use core_foundation::{self as cf, base::TCFType}; + let i = cf::bundle::CFBundle::main_bundle().info_dictionary(); + let mut i = unsafe { i.to_mutable() }; + i.set( + cf::string::CFString::new("NSSupportsAutomaticGraphicsSwitching"), + cf::boolean::CFBoolean::true_value().into_CFType(), + ); + } + + let args: Vec<String> = env::args().collect(); + let res_path = if args.len() > 1 { + Some(PathBuf::from(&args[1])) + } else { + None + }; + + let mut events_loop = winit::EventsLoop::new(); + let window_builder = winit::WindowBuilder::new() + .with_title(E::TITLE) + .with_multitouch() + .with_dimensions(winit::dpi::LogicalSize::new(E::WIDTH as f64, E::HEIGHT as f64)); + let windowed_context = glutin::ContextBuilder::new() + .with_gl(glutin::GlRequest::GlThenGles { + opengl_version: (3, 2), + opengles_version: (3, 0), + }) + .build_windowed(window_builder, &events_loop) + .unwrap(); + + let windowed_context = unsafe { windowed_context.make_current().unwrap() }; + + let gl = match windowed_context.get_api() { + glutin::Api::OpenGl => unsafe { + gl::GlFns::load_with( + |symbol| windowed_context.get_proc_address(symbol) as *const _ + ) + }, + glutin::Api::OpenGlEs => unsafe { + gl::GlesFns::load_with( + |symbol| windowed_context.get_proc_address(symbol) as *const _ + ) + }, + glutin::Api::WebGl => unimplemented!(), + }; + + println!("OpenGL version {}", gl.get_string(gl::VERSION)); + println!("Shader resource path: {:?}", res_path); + let device_pixel_ratio = windowed_context.window().get_hidpi_factor() as f32; + println!("Device pixel ratio: {}", device_pixel_ratio); + + println!("Loading shaders..."); + let mut debug_flags = DebugFlags::ECHO_DRIVER_MESSAGES | DebugFlags::TEXTURE_CACHE_DBG; + let opts = webrender::RendererOptions { + resource_override_path: res_path, + precache_flags: E::PRECACHE_SHADER_FLAGS, + device_pixel_ratio, + clear_color: Some(ColorF::new(0.3, 0.0, 0.0, 1.0)), + debug_flags, + //allow_texture_swizzling: false, + ..options.unwrap_or(webrender::RendererOptions::default()) + }; + + let device_size = { + let size = windowed_context + .window() + .get_inner_size() + .unwrap() + .to_physical(device_pixel_ratio as f64); + DeviceIntSize::new(size.width as i32, size.height as i32) + }; + let notifier = Box::new(Notifier::new(events_loop.create_proxy())); + let (mut renderer, sender) = webrender::Renderer::new( + gl.clone(), + notifier, + opts, + None, + ).unwrap(); + let mut api = sender.create_api(); + let document_id = api.add_document(device_size); + + let external = example.get_image_handler(&*gl); + + if let Some(external_image_handler) = external { + renderer.set_external_image_handler(external_image_handler); + } + + let epoch = Epoch(0); + let pipeline_id = PipelineId(0, 0); + let layout_size = device_size.to_f32() / euclid::Scale::new(device_pixel_ratio); + let mut builder = DisplayListBuilder::new(pipeline_id); + let mut txn = Transaction::new(); + + example.render( + &mut api, + &mut builder, + &mut txn, + device_size, + pipeline_id, + document_id, + ); + txn.set_display_list( + epoch, + Some(ColorF::new(0.3, 0.0, 0.0, 1.0)), + layout_size, + builder.finalize(), + true, + ); + txn.set_root_pipeline(pipeline_id); + txn.generate_frame(0); + api.send_transaction(document_id, txn); + + println!("Entering event loop"); + events_loop.run_forever(|global_event| { + let mut txn = Transaction::new(); + let mut custom_event = true; + + let old_flags = debug_flags; + let win_event = match global_event { + winit::Event::WindowEvent { event, .. } => event, + _ => return winit::ControlFlow::Continue, + }; + match win_event { + winit::WindowEvent::CloseRequested => return winit::ControlFlow::Break, + winit::WindowEvent::AxisMotion { .. } | + winit::WindowEvent::CursorMoved { .. } => { + custom_event = example.on_event( + win_event, + &mut api, + document_id, + ); + // skip high-frequency events from triggering a frame draw. + if !custom_event { + return winit::ControlFlow::Continue; + } + }, + winit::WindowEvent::KeyboardInput { + input: winit::KeyboardInput { + state: winit::ElementState::Pressed, + virtual_keycode: Some(key), + .. + }, + .. + } => match key { + winit::VirtualKeyCode::Escape => return winit::ControlFlow::Break, + winit::VirtualKeyCode::P => debug_flags.toggle(DebugFlags::PROFILER_DBG), + winit::VirtualKeyCode::O => debug_flags.toggle(DebugFlags::RENDER_TARGET_DBG), + winit::VirtualKeyCode::I => debug_flags.toggle(DebugFlags::TEXTURE_CACHE_DBG), + winit::VirtualKeyCode::T => debug_flags.toggle(DebugFlags::PICTURE_CACHING_DBG), + winit::VirtualKeyCode::Q => debug_flags.toggle( + DebugFlags::GPU_TIME_QUERIES | DebugFlags::GPU_SAMPLE_QUERIES + ), + winit::VirtualKeyCode::G => debug_flags.toggle(DebugFlags::GPU_CACHE_DBG), + winit::VirtualKeyCode::Key1 => txn.set_document_view( + device_size.into(), + 1.0 + ), + winit::VirtualKeyCode::Key2 => txn.set_document_view( + device_size.into(), + 2.0 + ), + winit::VirtualKeyCode::M => api.notify_memory_pressure(), + winit::VirtualKeyCode::C => { + let path: PathBuf = "../captures/example".into(); + //TODO: switch between SCENE/FRAME capture types + // based on "shift" modifier, when `glutin` is updated. + let bits = CaptureBits::all(); + api.save_capture(path, bits); + }, + _ => { + custom_event = example.on_event( + win_event, + &mut api, + document_id, + ) + }, + }, + other => custom_event = example.on_event( + other, + &mut api, + document_id, + ), + }; + + if debug_flags != old_flags { + api.send_debug_cmd(DebugCommand::SetFlags(debug_flags)); + } + + if custom_event { + let mut builder = DisplayListBuilder::new(pipeline_id); + + example.render( + &mut api, + &mut builder, + &mut txn, + device_size, + pipeline_id, + document_id, + ); + txn.set_display_list( + epoch, + Some(ColorF::new(0.3, 0.0, 0.0, 1.0)), + layout_size, + builder.finalize(), + true, + ); + txn.generate_frame(0); + } + api.send_transaction(document_id, txn); + + renderer.update(); + renderer.render(device_size, 0).unwrap(); + let _ = renderer.flush_pipeline_info(); + example.draw_custom(&*gl); + windowed_context.swap_buffers().ok(); + + winit::ControlFlow::Continue + }); + + renderer.deinit(); +} diff --git a/gfx/wr/examples/common/image_helper.rs b/gfx/wr/examples/common/image_helper.rs new file mode 100644 index 0000000000..368674c0e1 --- /dev/null +++ b/gfx/wr/examples/common/image_helper.rs @@ -0,0 +1,19 @@ +/* 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 webrender::api::{ImageData, ImageDescriptor, ImageFormat, ImageDescriptorFlags}; + +pub fn make_checkerboard(width: u32, height: u32) -> (ImageDescriptor, ImageData) { + let mut image_data = Vec::new(); + for y in 0 .. height { + for x in 0 .. width { + let lum = 255 * (((x & 8) == 0) ^ ((y & 8) == 0)) as u8; + image_data.extend_from_slice(&[lum, lum, lum, 0xff]); + } + } + ( + ImageDescriptor::new(width as i32, height as i32, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::new(image_data) + ) +} diff --git a/gfx/wr/examples/document.rs b/gfx/wr/examples/document.rs new file mode 100644 index 0000000000..c81a3f5c25 --- /dev/null +++ b/gfx/wr/examples/document.rs @@ -0,0 +1,143 @@ +/* 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 euclid; +extern crate gleam; +extern crate glutin; +extern crate webrender; +extern crate winit; + +#[path = "common/boilerplate.rs"] +mod boilerplate; + +use crate::boilerplate::Example; +use euclid::Scale; +use webrender::api::*; +use webrender::render_api::*; +use webrender::api::units::*; + +// This example creates multiple documents overlapping each other with +// specified layer indices. + +struct Document { + id: DocumentId, + pipeline_id: PipelineId, + content_rect: LayoutRect, + color: ColorF, +} + +struct App { + documents: Vec<Document>, +} + +impl App { + fn init( + &mut self, + api: &mut RenderApi, + device_pixel_ratio: f32, + ) { + let init_data = vec![ + ( + PipelineId(1, 0), + ColorF::new(0.0, 1.0, 0.0, 1.0), + DeviceIntPoint::new(0, 0), + ), + ( + PipelineId(2, 0), + ColorF::new(1.0, 1.0, 0.0, 1.0), + DeviceIntPoint::new(200, 0), + ), + ( + PipelineId(3, 0), + ColorF::new(1.0, 0.0, 0.0, 1.0), + DeviceIntPoint::new(200, 200), + ), + ( + PipelineId(4, 0), + ColorF::new(1.0, 0.0, 1.0, 1.0), + DeviceIntPoint::new(0, 200), + ), + ]; + + for (pipeline_id, color, offset) in init_data { + let size = DeviceIntSize::new(250, 250); + let bounds = DeviceIntRect::new(offset, size); + + let document_id = api.add_document(size); + let mut txn = Transaction::new(); + txn.set_document_view(bounds, device_pixel_ratio); + txn.set_root_pipeline(pipeline_id); + api.send_transaction(document_id, txn); + + self.documents.push(Document { + id: document_id, + pipeline_id, + content_rect: LayoutRect::new( + LayoutPoint::origin(), + bounds.size.to_f32() / Scale::new(device_pixel_ratio), + ), + color, + }); + } + } +} + +impl Example for App { + fn render( + &mut self, + api: &mut RenderApi, + _base_builder: &mut DisplayListBuilder, + _txn: &mut Transaction, + _device_size: DeviceIntSize, + _pipeline_id: PipelineId, + _: DocumentId, + ) { + if self.documents.is_empty() { + // this is the first run, hack around the boilerplate, + // which assumes an example only needs one document + self.init(api, 1.0); + } + + for doc in &self.documents { + let space_and_clip = SpaceAndClipInfo::root_scroll(doc.pipeline_id); + let mut builder = DisplayListBuilder::new( + doc.pipeline_id, + ); + let local_rect = LayoutRect::new( + LayoutPoint::zero(), + doc.content_rect.size, + ); + + builder.push_simple_stacking_context( + doc.content_rect.origin, + space_and_clip.spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + builder.push_rect( + &CommonItemProperties::new(local_rect, space_and_clip), + local_rect, + doc.color, + ); + builder.pop_stacking_context(); + + let mut txn = Transaction::new(); + txn.set_display_list( + Epoch(0), + None, + doc.content_rect.size, + builder.finalize(), + true, + ); + txn.generate_frame(0); + api.send_transaction(doc.id, txn); + } + } +} + +fn main() { + let mut app = App { + documents: Vec::new(), + }; + boilerplate::main_wrapper(&mut app, None); +} diff --git a/gfx/wr/examples/iframe.rs b/gfx/wr/examples/iframe.rs new file mode 100644 index 0000000000..9bd785fbda --- /dev/null +++ b/gfx/wr/examples/iframe.rs @@ -0,0 +1,96 @@ +/* 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 webrender; +extern crate winit; + +#[path = "common/boilerplate.rs"] +mod boilerplate; + +use crate::boilerplate::{Example, HandyDandyRectBuilder}; +use webrender::api::*; +use webrender::render_api::*; +use webrender::api::units::*; + +// This example uses the push_iframe API to nest a second pipeline's displaylist +// inside the root pipeline's display list. When it works, a green square is +// shown. If it fails, a red square is shown. + +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, + ) { + // All the sub_* things are for the nested pipeline + let sub_size = DeviceIntSize::new(100, 100); + let sub_bounds = (0, 0).to(sub_size.width as i32, sub_size.height as i32); + + let sub_pipeline_id = PipelineId(pipeline_id.0, 42); + let mut sub_builder = DisplayListBuilder::new(sub_pipeline_id); + let mut space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id); + + sub_builder.push_simple_stacking_context( + sub_bounds.origin, + space_and_clip.spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + + // green rect visible == success + sub_builder.push_rect( + &CommonItemProperties::new(sub_bounds, space_and_clip), + sub_bounds, + ColorF::new(0.0, 1.0, 0.0, 1.0) + ); + sub_builder.pop_stacking_context(); + + let mut txn = Transaction::new(); + txn.set_display_list( + Epoch(0), + None, + sub_bounds.size, + sub_builder.finalize(), + true, + ); + api.send_transaction(document_id, txn); + + space_and_clip.spatial_id = builder.push_reference_frame( + sub_bounds.origin, + space_and_clip.spatial_id, + TransformStyle::Flat, + PropertyBinding::Binding(PropertyBindingKey::new(42), LayoutTransform::identity()), + ReferenceFrameKind::Transform, + ); + + // And this is for the root pipeline + builder.push_simple_stacking_context( + sub_bounds.origin, + space_and_clip.spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + + // red rect under the iframe: if this is visible, things have gone wrong + builder.push_rect( + &CommonItemProperties::new(sub_bounds, space_and_clip), + sub_bounds, + ColorF::new(1.0, 0.0, 0.0, 1.0) + ); + builder.push_iframe(sub_bounds, sub_bounds, &space_and_clip, sub_pipeline_id, false); + builder.pop_stacking_context(); + builder.pop_reference_frame(); + } +} + +fn main() { + let mut app = App {}; + boilerplate::main_wrapper(&mut app, None); +} diff --git a/gfx/wr/examples/image_resize.rs b/gfx/wr/examples/image_resize.rs new file mode 100644 index 0000000000..b057a4035e --- /dev/null +++ b/gfx/wr/examples/image_resize.rs @@ -0,0 +1,122 @@ +/* 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 webrender; +extern crate winit; + +#[path = "common/boilerplate.rs"] +mod boilerplate; +#[path = "common/image_helper.rs"] +mod image_helper; + +use crate::boilerplate::{Example, HandyDandyRectBuilder}; +use webrender::api::*; +use webrender::render_api::*; +use webrender::api::units::*; + +struct App { + image_key: ImageKey, +} + +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 (image_descriptor, image_data) = image_helper::make_checkerboard(32, 32); + txn.add_image( + self.image_key, + image_descriptor, + image_data, + None, + ); + + let bounds = (0, 0).to(512, 512); + let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id); + + builder.push_simple_stacking_context( + bounds.origin, + space_and_clip.spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + + let image_size = LayoutSize::new(100.0, 100.0); + + builder.push_image( + &CommonItemProperties::new( + LayoutRect::new(LayoutPoint::new(100.0, 100.0), image_size), + space_and_clip, + ), + bounds, + ImageRendering::Auto, + AlphaType::PremultipliedAlpha, + self.image_key, + ColorF::WHITE, + ); + + builder.push_image( + &CommonItemProperties::new( + LayoutRect::new(LayoutPoint::new(250.0, 100.0), image_size), + space_and_clip, + ), + bounds, + ImageRendering::Pixelated, + AlphaType::PremultipliedAlpha, + self.image_key, + ColorF::WHITE, + ); + + builder.pop_stacking_context(); + } + + fn on_event(&mut self, event: winit::WindowEvent, api: &mut RenderApi, document_id: DocumentId) -> bool { + match event { + winit::WindowEvent::KeyboardInput { + input: winit::KeyboardInput { + state: winit::ElementState::Pressed, + virtual_keycode: Some(winit::VirtualKeyCode::Space), + .. + }, + .. + } => { + let mut image_data = Vec::new(); + for y in 0 .. 64 { + for x in 0 .. 64 { + let r = 255 * ((y & 32) == 0) as u8; + let g = 255 * ((x & 32) == 0) as u8; + image_data.extend_from_slice(&[0, g, r, 0xff]); + } + } + + let mut txn = Transaction::new(); + txn.update_image( + self.image_key, + ImageDescriptor::new(64, 64, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::new(image_data), + &DirtyRect::All, + ); + let mut txn = Transaction::new(); + txn.generate_frame(0); + api.send_transaction(document_id, txn); + } + _ => {} + } + + false + } +} + +fn main() { + let mut app = App { + image_key: ImageKey(IdNamespace(0), 0), + }; + boilerplate::main_wrapper(&mut app, None); +} diff --git a/gfx/wr/examples/multiwindow.rs b/gfx/wr/examples/multiwindow.rs new file mode 100644 index 0000000000..aa0ac87151 --- /dev/null +++ b/gfx/wr/examples/multiwindow.rs @@ -0,0 +1,327 @@ +/* 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 euclid; +extern crate gleam; +extern crate glutin; +extern crate webrender; +extern crate winit; + +use gleam::gl; +use glutin::NotCurrent; +use std::fs::File; +use std::io::Read; +use webrender::api::*; +use webrender::api::units::*; +use webrender::render_api::*; +use webrender::DebugFlags; +use winit::dpi::LogicalSize; + +struct Notifier { + events_proxy: winit::EventsLoopProxy, +} + +impl Notifier { + fn new(events_proxy: winit::EventsLoopProxy) -> Notifier { + Notifier { events_proxy } + } +} + +impl RenderNotifier for Notifier { + fn clone(&self) -> Box<dyn RenderNotifier> { + Box::new(Notifier { + events_proxy: self.events_proxy.clone(), + }) + } + + fn wake_up(&self, _composite_needed: bool) { + #[cfg(not(target_os = "android"))] + let _ = self.events_proxy.wakeup(); + } + + fn new_frame_ready(&self, + _: DocumentId, + _scrolled: bool, + composite_needed: bool, + _render_time: Option<u64>) { + self.wake_up(composite_needed); + } +} + +struct Window { + events_loop: winit::EventsLoop, //TODO: share events loop? + context: Option<glutin::WindowedContext<NotCurrent>>, + renderer: webrender::Renderer, + name: &'static str, + pipeline_id: PipelineId, + document_id: DocumentId, + epoch: Epoch, + api: RenderApi, + font_instance_key: FontInstanceKey, +} + +impl Window { + fn new(name: &'static str, clear_color: ColorF) -> Self { + let events_loop = winit::EventsLoop::new(); + let window_builder = winit::WindowBuilder::new() + .with_title(name) + .with_multitouch() + .with_dimensions(LogicalSize::new(800., 600.)); + let context = glutin::ContextBuilder::new() + .with_gl(glutin::GlRequest::GlThenGles { + opengl_version: (3, 2), + opengles_version: (3, 0), + }) + .build_windowed(window_builder, &events_loop) + .unwrap(); + + let context = unsafe { context.make_current().unwrap() }; + + let gl = match context.get_api() { + glutin::Api::OpenGl => unsafe { + gl::GlFns::load_with(|symbol| context.get_proc_address(symbol) as *const _) + }, + glutin::Api::OpenGlEs => unsafe { + gl::GlesFns::load_with(|symbol| context.get_proc_address(symbol) as *const _) + }, + glutin::Api::WebGl => unimplemented!(), + }; + + let device_pixel_ratio = context.window().get_hidpi_factor() as f32; + + let opts = webrender::RendererOptions { + device_pixel_ratio, + clear_color: Some(clear_color), + ..webrender::RendererOptions::default() + }; + + let device_size = { + let size = context + .window() + .get_inner_size() + .unwrap() + .to_physical(device_pixel_ratio as f64); + DeviceIntSize::new(size.width as i32, size.height as i32) + }; + let notifier = Box::new(Notifier::new(events_loop.create_proxy())); + let (renderer, sender) = webrender::Renderer::new(gl.clone(), notifier, opts, None).unwrap(); + let mut api = sender.create_api(); + let document_id = api.add_document(device_size); + + let epoch = Epoch(0); + let pipeline_id = PipelineId(0, 0); + let mut txn = Transaction::new(); + + let font_key = api.generate_font_key(); + let font_bytes = load_file("../wrench/reftests/text/FreeSans.ttf"); + txn.add_raw_font(font_key, font_bytes, 0); + + let font_instance_key = api.generate_font_instance_key(); + txn.add_font_instance(font_instance_key, font_key, 32.0, None, None, Vec::new()); + + api.send_transaction(document_id, txn); + + Window { + events_loop, + context: Some(unsafe { context.make_not_current().unwrap() }), + renderer, + name, + epoch, + pipeline_id, + document_id, + api, + font_instance_key, + } + } + + fn tick(&mut self) -> bool { + let mut do_exit = false; + let my_name = &self.name; + let renderer = &mut self.renderer; + let api = &mut self.api; + + self.events_loop.poll_events(|global_event| match global_event { + winit::Event::WindowEvent { event, .. } => match event { + winit::WindowEvent::CloseRequested | + winit::WindowEvent::KeyboardInput { + input: winit::KeyboardInput { + virtual_keycode: Some(winit::VirtualKeyCode::Escape), + .. + }, + .. + } => { + do_exit = true + } + winit::WindowEvent::KeyboardInput { + input: winit::KeyboardInput { + state: winit::ElementState::Pressed, + virtual_keycode: Some(winit::VirtualKeyCode::P), + .. + }, + .. + } => { + println!("set flags {}", my_name); + api.send_debug_cmd(DebugCommand::SetFlags(DebugFlags::PROFILER_DBG)) + } + _ => {} + } + _ => {} + }); + if do_exit { + return true + } + + let context = unsafe { self.context.take().unwrap().make_current().unwrap() }; + let device_pixel_ratio = context.window().get_hidpi_factor() as f32; + let device_size = { + let size = context + .window() + .get_inner_size() + .unwrap() + .to_physical(device_pixel_ratio as f64); + DeviceIntSize::new(size.width as i32, size.height as i32) + }; + let layout_size = device_size.to_f32() / euclid::Scale::new(device_pixel_ratio); + let mut txn = Transaction::new(); + let mut builder = DisplayListBuilder::new(self.pipeline_id); + let space_and_clip = SpaceAndClipInfo::root_scroll(self.pipeline_id); + + let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size); + builder.push_simple_stacking_context( + bounds.origin, + space_and_clip.spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + + builder.push_rect( + &CommonItemProperties::new( + LayoutRect::new( + LayoutPoint::new(100.0, 200.0), + LayoutSize::new(100.0, 200.0), + ), + space_and_clip, + ), + LayoutRect::new( + LayoutPoint::new(100.0, 200.0), + LayoutSize::new(100.0, 200.0), + ), + ColorF::new(0.0, 1.0, 0.0, 1.0)); + + let text_bounds = LayoutRect::new( + LayoutPoint::new(100.0, 50.0), + LayoutSize::new(700.0, 200.0) + ); + let glyphs = vec![ + GlyphInstance { + index: 48, + point: LayoutPoint::new(100.0, 100.0), + }, + GlyphInstance { + index: 68, + point: LayoutPoint::new(150.0, 100.0), + }, + GlyphInstance { + index: 80, + point: LayoutPoint::new(200.0, 100.0), + }, + GlyphInstance { + index: 82, + point: LayoutPoint::new(250.0, 100.0), + }, + GlyphInstance { + index: 81, + point: LayoutPoint::new(300.0, 100.0), + }, + GlyphInstance { + index: 3, + point: LayoutPoint::new(350.0, 100.0), + }, + GlyphInstance { + index: 86, + point: LayoutPoint::new(400.0, 100.0), + }, + GlyphInstance { + index: 79, + point: LayoutPoint::new(450.0, 100.0), + }, + GlyphInstance { + index: 72, + point: LayoutPoint::new(500.0, 100.0), + }, + GlyphInstance { + index: 83, + point: LayoutPoint::new(550.0, 100.0), + }, + GlyphInstance { + index: 87, + point: LayoutPoint::new(600.0, 100.0), + }, + GlyphInstance { + index: 17, + point: LayoutPoint::new(650.0, 100.0), + }, + ]; + + builder.push_text( + &CommonItemProperties::new( + text_bounds, + space_and_clip, + ), + text_bounds, + &glyphs, + self.font_instance_key, + ColorF::new(1.0, 1.0, 0.0, 1.0), + None, + ); + + builder.pop_stacking_context(); + + txn.set_display_list( + self.epoch, + None, + layout_size, + builder.finalize(), + true, + ); + txn.set_root_pipeline(self.pipeline_id); + txn.generate_frame(0); + api.send_transaction(self.document_id, txn); + + renderer.update(); + renderer.render(device_size, 0).unwrap(); + context.swap_buffers().ok(); + + self.context = Some(unsafe { context.make_not_current().unwrap() }); + + false + } + + fn deinit(self) { + self.renderer.deinit(); + } +} + +fn main() { + let mut win1 = Window::new("window1", ColorF::new(0.3, 0.0, 0.0, 1.0)); + let mut win2 = Window::new("window2", ColorF::new(0.0, 0.3, 0.0, 1.0)); + + loop { + if win1.tick() { + break; + } + if win2.tick() { + break; + } + } + + win1.deinit(); + win2.deinit(); +} + +fn load_file(name: &str) -> Vec<u8> { + let mut file = File::open(name).unwrap(); + let mut buffer = vec![]; + file.read_to_end(&mut buffer).unwrap(); + buffer +} diff --git a/gfx/wr/examples/scrolling.rs b/gfx/wr/examples/scrolling.rs new file mode 100644 index 0000000000..034af95244 --- /dev/null +++ b/gfx/wr/examples/scrolling.rs @@ -0,0 +1,243 @@ +/* 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 euclid; +extern crate gleam; +extern crate glutin; +extern crate webrender; +extern crate winit; + +#[path = "common/boilerplate.rs"] +mod boilerplate; + +use crate::boilerplate::{Example, HandyDandyRectBuilder}; +use euclid::SideOffsets2D; +use webrender::api::*; +use webrender::render_api::*; +use webrender::api::units::*; +use winit::dpi::LogicalPosition; + + +const EXT_SCROLL_ID_ROOT: u64 = 1; +const EXT_SCROLL_ID_CONTENT: u64 = 2; + +struct App { + cursor_position: WorldPoint, + scroll_origin: LayoutPoint, +} + +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 root_space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id); + builder.push_simple_stacking_context( + LayoutPoint::zero(), + root_space_and_clip.spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + + if true { + // scrolling and clips stuff + // let's make a scrollbox + let scrollbox = (0, 0).to(300, 400); + builder.push_simple_stacking_context( + LayoutPoint::new(10., 10.), + root_space_and_clip.spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + // set the scrolling clip + let space_and_clip1 = builder.define_scroll_frame( + &root_space_and_clip, + ExternalScrollId(EXT_SCROLL_ID_ROOT, PipelineId::dummy()), + (0, 0).by(1000, 1000), + scrollbox, + ScrollSensitivity::ScriptAndInputEvents, + LayoutVector2D::zero(), + ); + + // now put some content into it. + // start with a white background + let info = CommonItemProperties::new((0, 0).to(1000, 1000), space_and_clip1); + builder.push_hit_test(&info, (0, 1)); + builder.push_rect(&info, info.clip_rect, ColorF::new(1.0, 1.0, 1.0, 1.0)); + + // let's make a 50x50 blue square as a visual reference + let info = CommonItemProperties::new((0, 0).to(50, 50), space_and_clip1); + builder.push_hit_test(&info, (0, 2)); + builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 0.0, 1.0, 1.0)); + + // and a 50x50 green square next to it with an offset clip + // to see what that looks like + let info = CommonItemProperties::new( + (50, 0).to(100, 50).intersection(&(60, 10).to(110, 60)).unwrap(), + space_and_clip1, + ); + builder.push_hit_test(&info, (0, 3)); + builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 0.0, 1.0)); + + // Below the above rectangles, set up a nested scrollbox. It's still in + // the same stacking context, so note that the rects passed in need to + // be relative to the stacking context. + let space_and_clip2 = builder.define_scroll_frame( + &space_and_clip1, + ExternalScrollId(EXT_SCROLL_ID_CONTENT, PipelineId::dummy()), + (0, 100).to(300, 1000), + (0, 100).to(200, 300), + ScrollSensitivity::ScriptAndInputEvents, + LayoutVector2D::zero(), + ); + + // give it a giant gray background just to distinguish it and to easily + // visually identify the nested scrollbox + let info = CommonItemProperties::new( + (-1000, -1000).to(5000, 5000), + space_and_clip2, + ); + builder.push_hit_test(&info, (0, 4)); + builder.push_rect(&info, info.clip_rect, ColorF::new(0.5, 0.5, 0.5, 1.0)); + + // add a teal square to visualize the scrolling/clipping behaviour + // as you scroll the nested scrollbox + let info = CommonItemProperties::new((0, 200).to(50, 250), space_and_clip2); + builder.push_hit_test(&info, (0, 5)); + builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 1.0, 1.0)); + + // Add a sticky frame. It will "stick" twice while scrolling, once + // at a margin of 10px from the bottom, for 40 pixels of scrolling, + // and once at a margin of 10px from the top, for 60 pixels of + // scrolling. + let sticky_id = builder.define_sticky_frame( + space_and_clip2.spatial_id, + (50, 350).by(50, 50), + SideOffsets2D::new(Some(10.0), None, Some(10.0), None), + StickyOffsetBounds::new(-40.0, 60.0), + StickyOffsetBounds::new(0.0, 0.0), + LayoutVector2D::new(0.0, 0.0) + ); + + let info = CommonItemProperties::new( + (50, 350).by(50, 50), + SpaceAndClipInfo { + spatial_id: sticky_id, + clip_id: space_and_clip2.clip_id, + }, + ); + builder.push_hit_test(&info, (0, 6)); + builder.push_rect( + &info, + info.clip_rect, + ColorF::new(0.5, 0.5, 1.0, 1.0), + ); + + // just for good measure add another teal square further down and to + // the right, which can be scrolled into view by the user + let info = CommonItemProperties::new( + (250, 350).to(300, 400), + space_and_clip2, + ); + builder.push_hit_test(&info, (0, 7)); + builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 1.0, 1.0)); + + builder.pop_stacking_context(); + } + + builder.pop_stacking_context(); + } + + fn on_event(&mut self, event: winit::WindowEvent, api: &mut RenderApi, document_id: DocumentId) -> bool { + let mut txn = Transaction::new(); + match event { + winit::WindowEvent::KeyboardInput { + input: winit::KeyboardInput { + state: winit::ElementState::Pressed, + virtual_keycode: Some(key), + .. + }, + .. + } => { + let offset = match key { + winit::VirtualKeyCode::Down => Some(LayoutVector2D::new(0.0, -10.0)), + winit::VirtualKeyCode::Up => Some(LayoutVector2D::new(0.0, 10.0)), + winit::VirtualKeyCode::Right => Some(LayoutVector2D::new(-10.0, 0.0)), + winit::VirtualKeyCode::Left => Some(LayoutVector2D::new(10.0, 0.0)), + _ => None, + }; + let zoom = match key { + winit::VirtualKeyCode::Key0 => Some(1.0), + winit::VirtualKeyCode::Minus => Some(0.8), + winit::VirtualKeyCode::Equals => Some(1.25), + _ => None, + }; + + if let Some(offset) = offset { + self.scroll_origin += offset; + + txn.scroll_node_with_id( + self.scroll_origin, + ExternalScrollId(EXT_SCROLL_ID_CONTENT, PipelineId::dummy()), + ScrollClamping::ToContentBounds, + ); + txn.generate_frame(0); + } + if let Some(zoom) = zoom { + txn.set_pinch_zoom(ZoomFactor::new(zoom)); + txn.generate_frame(0); + } + } + winit::WindowEvent::CursorMoved { position: LogicalPosition { x, y }, .. } => { + self.cursor_position = WorldPoint::new(x as f32, y as f32); + } + winit::WindowEvent::MouseWheel { delta, .. } => { + const LINE_HEIGHT: f32 = 38.0; + let (dx, dy) = match delta { + winit::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT), + winit::MouseScrollDelta::PixelDelta(pos) => (pos.x as f32, pos.y as f32), + }; + + self.scroll_origin += LayoutVector2D::new(dx, dy); + + txn.scroll_node_with_id( + self.scroll_origin, + ExternalScrollId(EXT_SCROLL_ID_CONTENT, PipelineId::dummy()), + ScrollClamping::ToContentBounds, + ); + + txn.generate_frame(0); + } + winit::WindowEvent::MouseInput { .. } => { + let results = api.hit_test( + document_id, + None, + self.cursor_position, + ); + + println!("Hit test results:"); + for item in &results.items { + println!(" • {:?}", item); + } + println!(""); + } + _ => (), + } + + api.send_transaction(document_id, txn); + + false + } +} + +fn main() { + let mut app = App { + cursor_position: WorldPoint::zero(), + scroll_origin: LayoutPoint::zero(), + }; + boilerplate::main_wrapper(&mut app, None); +} diff --git a/gfx/wr/examples/texture_cache_stress.rs b/gfx/wr/examples/texture_cache_stress.rs new file mode 100644 index 0000000000..9b029fbceb --- /dev/null +++ b/gfx/wr/examples/texture_cache_stress.rs @@ -0,0 +1,322 @@ +/* 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 webrender; +extern crate winit; + +#[path = "common/boilerplate.rs"] +mod boilerplate; + +use crate::boilerplate::{Example, HandyDandyRectBuilder}; +use gleam::gl; +use std::mem; +use webrender::api::*; +use webrender::render_api::*; +use webrender::api::units::*; + + +struct ImageGenerator { + patterns: [[u8; 3]; 6], + next_pattern: usize, + current_image: Vec<u8>, +} + +impl ImageGenerator { + fn new() -> Self { + ImageGenerator { + next_pattern: 0, + patterns: [ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1], + [1, 1, 0], + [0, 1, 1], + [1, 0, 1], + ], + current_image: Vec::new(), + } + } + + fn generate_image(&mut self, size: i32) { + let pattern = &self.patterns[self.next_pattern]; + self.current_image.clear(); + for y in 0 .. size { + for x in 0 .. size { + let lum = 255 * (1 - (((x & 8) == 0) ^ ((y & 8) == 0)) as u8); + self.current_image.extend_from_slice(&[ + lum * pattern[0], + lum * pattern[1], + lum * pattern[2], + 0xff, + ]); + } + } + + self.next_pattern = (self.next_pattern + 1) % self.patterns.len(); + } + + fn take(&mut self) -> Vec<u8> { + mem::replace(&mut self.current_image, Vec::new()) + } +} + +impl ExternalImageHandler for ImageGenerator { + fn lock( + &mut self, + _key: ExternalImageId, + channel_index: u8, + _rendering: ImageRendering + ) -> ExternalImage { + self.generate_image(channel_index as i32); + ExternalImage { + uv: TexelRect::new(0.0, 0.0, 1.0, 1.0), + source: ExternalImageSource::RawData(&self.current_image), + } + } + fn unlock(&mut self, _key: ExternalImageId, _channel_index: u8) {} +} + +struct App { + stress_keys: Vec<ImageKey>, + image_key: Option<ImageKey>, + image_generator: ImageGenerator, + swap_keys: Vec<ImageKey>, + swap_index: usize, +} + +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 bounds = (0, 0).to(512, 512); + let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id); + + builder.push_simple_stacking_context( + bounds.origin, + space_and_clip.spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + + let x0 = 50.0; + let y0 = 50.0; + let image_size = LayoutSize::new(4.0, 4.0); + + if self.swap_keys.is_empty() { + let key0 = api.generate_image_key(); + let key1 = api.generate_image_key(); + + self.image_generator.generate_image(128); + txn.add_image( + key0, + ImageDescriptor::new(128, 128, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::new(self.image_generator.take()), + None, + ); + + self.image_generator.generate_image(128); + txn.add_image( + key1, + ImageDescriptor::new(128, 128, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::new(self.image_generator.take()), + None, + ); + + self.swap_keys.push(key0); + self.swap_keys.push(key1); + } + + for (i, key) in self.stress_keys.iter().enumerate() { + let x = (i % 128) as f32; + let y = (i / 128) as f32; + let info = CommonItemProperties::new( + LayoutRect::new( + LayoutPoint::new(x0 + image_size.width * x, y0 + image_size.height * y), + image_size, + ), + space_and_clip, + ); + + builder.push_image( + &info, + bounds, + ImageRendering::Auto, + AlphaType::PremultipliedAlpha, + *key, + ColorF::WHITE, + ); + } + + if let Some(image_key) = self.image_key { + let image_size = LayoutSize::new(100.0, 100.0); + let info = CommonItemProperties::new( + LayoutRect::new(LayoutPoint::new(100.0, 100.0), image_size), + space_and_clip, + ); + builder.push_image( + &info, + bounds, + ImageRendering::Auto, + AlphaType::PremultipliedAlpha, + image_key, + ColorF::WHITE, + ); + } + + let swap_key = self.swap_keys[self.swap_index]; + let image_size = LayoutSize::new(64.0, 64.0); + let info = CommonItemProperties::new( + LayoutRect::new(LayoutPoint::new(100.0, 400.0), image_size), + space_and_clip, + ); + builder.push_image( + &info, + bounds, + ImageRendering::Auto, + AlphaType::PremultipliedAlpha, + swap_key, + ColorF::WHITE, + ); + self.swap_index = 1 - self.swap_index; + + builder.pop_stacking_context(); + } + + fn on_event( + &mut self, + event: winit::WindowEvent, + api: &mut RenderApi, + document_id: DocumentId, + ) -> bool { + match event { + winit::WindowEvent::KeyboardInput { + input: winit::KeyboardInput { + state: winit::ElementState::Pressed, + virtual_keycode: Some(key), + .. + }, + .. + } => { + let mut txn = Transaction::new(); + + match key { + winit::VirtualKeyCode::S => { + self.stress_keys.clear(); + + for _ in 0 .. 16 { + for _ in 0 .. 16 { + let size = 4; + + let image_key = api.generate_image_key(); + + self.image_generator.generate_image(size); + + txn.add_image( + image_key, + ImageDescriptor::new( + size, + size, + ImageFormat::BGRA8, + ImageDescriptorFlags::IS_OPAQUE, + ), + ImageData::new(self.image_generator.take()), + None, + ); + + self.stress_keys.push(image_key); + } + } + } + winit::VirtualKeyCode::D => if let Some(image_key) = self.image_key.take() { + txn.delete_image(image_key); + }, + winit::VirtualKeyCode::U => if let Some(image_key) = self.image_key { + let size = 128; + self.image_generator.generate_image(size); + + txn.update_image( + image_key, + ImageDescriptor::new(size, size, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::new(self.image_generator.take()), + &DirtyRect::All, + ); + }, + winit::VirtualKeyCode::E => { + if let Some(image_key) = self.image_key.take() { + txn.delete_image(image_key); + } + + let size = 32; + let image_key = api.generate_image_key(); + + let image_data = ExternalImageData { + id: ExternalImageId(0), + channel_index: size as u8, + image_type: ExternalImageType::Buffer, + }; + + txn.add_image( + image_key, + ImageDescriptor::new(size, size, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::External(image_data), + None, + ); + + self.image_key = Some(image_key); + } + winit::VirtualKeyCode::R => { + if let Some(image_key) = self.image_key.take() { + txn.delete_image(image_key); + } + + let image_key = api.generate_image_key(); + let size = 32; + self.image_generator.generate_image(size); + + txn.add_image( + image_key, + ImageDescriptor::new(size, size, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::new(self.image_generator.take()), + None, + ); + + self.image_key = Some(image_key); + } + _ => {} + } + + api.send_transaction(document_id, txn); + return true; + } + _ => {} + } + + false + } + + fn get_image_handler( + &mut self, + _gl: &dyn gl::Gl, + ) -> Option<Box<dyn ExternalImageHandler>> { + Some(Box::new(ImageGenerator::new())) + } +} + +fn main() { + let mut app = App { + image_key: None, + stress_keys: Vec::new(), + image_generator: ImageGenerator::new(), + swap_keys: Vec::new(), + swap_index: 0, + }; + boilerplate::main_wrapper(&mut app, None); +} diff --git a/gfx/wr/examples/yuv.rs b/gfx/wr/examples/yuv.rs new file mode 100644 index 0000000000..eacd97daa9 --- /dev/null +++ b/gfx/wr/examples/yuv.rs @@ -0,0 +1,225 @@ +/* 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 webrender; +extern crate winit; + +#[path = "common/boilerplate.rs"] +mod boilerplate; + +use crate::boilerplate::Example; +use gleam::gl; +use webrender::api::*; +use webrender::render_api::*; +use webrender::api::units::*; + + +fn init_gl_texture( + id: gl::GLuint, + internal: gl::GLenum, + external: gl::GLenum, + bytes: &[u8], + gl: &dyn gl::Gl, +) { + gl.bind_texture(gl::TEXTURE_2D, id); + gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as gl::GLint); + gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as gl::GLint); + gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as gl::GLint); + gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as gl::GLint); + gl.tex_image_2d( + gl::TEXTURE_2D, + 0, + internal as gl::GLint, + 100, + 100, + 0, + external, + gl::UNSIGNED_BYTE, + Some(bytes), + ); + gl.bind_texture(gl::TEXTURE_2D, 0); +} + +struct YuvImageProvider { + texture_ids: Vec<gl::GLuint>, +} + +impl YuvImageProvider { + fn new(gl: &dyn gl::Gl) -> Self { + let texture_ids = gl.gen_textures(4); + + init_gl_texture(texture_ids[0], gl::RED, gl::RED, &[127; 100 * 100], gl); + init_gl_texture(texture_ids[1], gl::RG8, gl::RG, &[0; 100 * 100 * 2], gl); + init_gl_texture(texture_ids[2], gl::RED, gl::RED, &[127; 100 * 100], gl); + init_gl_texture(texture_ids[3], gl::RED, gl::RED, &[127; 100 * 100], gl); + + YuvImageProvider { + texture_ids + } + } +} + +impl ExternalImageHandler for YuvImageProvider { + fn lock( + &mut self, + key: ExternalImageId, + _channel_index: u8, + _rendering: ImageRendering + ) -> ExternalImage { + let id = self.texture_ids[key.0 as usize]; + ExternalImage { + uv: TexelRect::new(0.0, 0.0, 1.0, 1.0), + source: ExternalImageSource::NativeTexture(id), + } + } + fn unlock(&mut self, _key: ExternalImageId, _channel_index: u8) { + } +} + +struct App { + texture_id: gl::GLuint, + current_value: u8, +} + +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 bounds = LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(500.0, 500.0)); + let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id); + + builder.push_simple_stacking_context( + bounds.origin, + space_and_clip.spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + + let yuv_chanel1 = api.generate_image_key(); + let yuv_chanel2 = api.generate_image_key(); + let yuv_chanel2_1 = api.generate_image_key(); + let yuv_chanel3 = api.generate_image_key(); + txn.add_image( + yuv_chanel1, + ImageDescriptor::new(100, 100, ImageFormat::R8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::External(ExternalImageData { + id: ExternalImageId(0), + channel_index: 0, + image_type: ExternalImageType::TextureHandle( + ImageBufferKind::Texture2D, + ), + }), + None, + ); + txn.add_image( + yuv_chanel2, + ImageDescriptor::new(100, 100, ImageFormat::RG8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::External(ExternalImageData { + id: ExternalImageId(1), + channel_index: 0, + image_type: ExternalImageType::TextureHandle( + ImageBufferKind::Texture2D, + ), + }), + None, + ); + txn.add_image( + yuv_chanel2_1, + ImageDescriptor::new(100, 100, ImageFormat::R8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::External(ExternalImageData { + id: ExternalImageId(2), + channel_index: 0, + image_type: ExternalImageType::TextureHandle( + ImageBufferKind::Texture2D, + ), + }), + None, + ); + txn.add_image( + yuv_chanel3, + ImageDescriptor::new(100, 100, ImageFormat::R8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::External(ExternalImageData { + id: ExternalImageId(3), + channel_index: 0, + image_type: ExternalImageType::TextureHandle( + ImageBufferKind::Texture2D, + ), + }), + None, + ); + + let info = CommonItemProperties::new( + LayoutRect::new(LayoutPoint::new(100.0, 0.0), LayoutSize::new(100.0, 100.0)), + space_and_clip, + ); + builder.push_yuv_image( + &info, + bounds, + YuvData::NV12(yuv_chanel1, yuv_chanel2), + ColorDepth::Color8, + YuvColorSpace::Rec601, + ColorRange::Limited, + ImageRendering::Auto, + ); + + let info = CommonItemProperties::new( + LayoutRect::new(LayoutPoint::new(300.0, 0.0), LayoutSize::new(100.0, 100.0)), + space_and_clip, + ); + builder.push_yuv_image( + &info, + bounds, + YuvData::PlanarYCbCr(yuv_chanel1, yuv_chanel2_1, yuv_chanel3), + ColorDepth::Color8, + YuvColorSpace::Rec601, + ColorRange::Limited, + ImageRendering::Auto, + ); + + builder.pop_stacking_context(); + } + + fn on_event( + &mut self, + _event: winit::WindowEvent, + _api: &mut RenderApi, + _document_id: DocumentId, + ) -> bool { + false + } + + fn get_image_handler( + &mut self, + gl: &dyn gl::Gl, + ) -> Option<Box<dyn ExternalImageHandler>> { + let provider = YuvImageProvider::new(gl); + self.texture_id = provider.texture_ids[0]; + Some(Box::new(provider)) + } + + fn draw_custom(&mut self, gl: &dyn gl::Gl) { + init_gl_texture(self.texture_id, gl::RED, gl::RED, &[self.current_value; 100 * 100], gl); + self.current_value = self.current_value.wrapping_add(1); + } +} + +fn main() { + let mut app = App { + texture_id: 0, + current_value: 0, + }; + + let opts = webrender::RendererOptions { + ..Default::default() + }; + + boilerplate::main_wrapper(&mut app, Some(opts)); +} |