summaryrefslogtreecommitdiffstats
path: root/gfx/wr/examples
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/wr/examples')
-rw-r--r--gfx/wr/examples/Cargo.toml67
-rw-r--r--gfx/wr/examples/README.md8
-rw-r--r--gfx/wr/examples/alpha_perf.rs94
-rw-r--r--gfx/wr/examples/animation.rs220
-rw-r--r--gfx/wr/examples/basic.rs322
-rw-r--r--gfx/wr/examples/blob.rs290
-rw-r--r--gfx/wr/examples/common/boilerplate.rs329
-rw-r--r--gfx/wr/examples/common/image_helper.rs19
-rw-r--r--gfx/wr/examples/document.rs143
-rw-r--r--gfx/wr/examples/iframe.rs96
-rw-r--r--gfx/wr/examples/image_resize.rs122
-rw-r--r--gfx/wr/examples/multiwindow.rs327
-rw-r--r--gfx/wr/examples/scrolling.rs243
-rw-r--r--gfx/wr/examples/texture_cache_stress.rs322
-rw-r--r--gfx/wr/examples/yuv.rs225
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[&params.request.key]))
+ }).collect();
+
+ self.workers.install(|| {
+ requests.into_par_iter().map(|(params, commands)| {
+ (params.request, render_blob(commands, &params.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));
+}