Vendor the current version of WebRender

This is a step toward upgrading WebRender, which will be upgraded and
patched in the `third_party` directory. This change vendors the current
private branch of WebRender that we use and adds a `patches` directory
which tracks the changes on top of the upstream WebRender commit
described by third_party/webrender/patches/head.
This commit is contained in:
Martin Robinson 2023-07-03 17:43:57 +02:00
parent c19eb800de
commit 49277f5c3f
No known key found for this signature in database
GPG key ID: D56AA4FA55EFE6F8
1215 changed files with 185677 additions and 34 deletions

View file

@ -0,0 +1,71 @@
[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 = "frame_output"
path = "frame_output.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.12"
glutin = "0.21"
rayon = "1"
webrender = { path = "../webrender" }
winit = "0.19"
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation = "0.7"

View file

@ -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
```

View file

@ -0,0 +1,93 @@
/* 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::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);
}

View file

@ -0,0 +1,219 @@
/* 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::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();
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);
}

321
third_party/webrender/examples/basic.rs vendored Normal file
View file

@ -0,0 +1,321 @@
/* 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::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(), builder.content_size());
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();
api.send_transaction(document_id, txn);
}
false
}
}

289
third_party/webrender/examples/blob.rs vendored Normal file
View file

@ -0,0 +1,289 @@
/* 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, RenderApi, Transaction};
use webrender::api::{ColorF, CommonItemProperties, SpaceAndClipInfo, ImageDescriptorFlags};
use webrender::api::units::*;
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));
}

View file

@ -0,0 +1,338 @@
/* 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::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) {
#[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();
}
}
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_handlers(
&mut self,
_gl: &dyn gl::Gl,
) -> (Option<Box<dyn ExternalImageHandler>>,
Option<Box<dyn OutputImageHandler>>) {
(None, 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,
device_size,
).unwrap();
let mut api = sender.create_api();
let document_id = api.add_document(device_size, 0);
let (external, output) = example.get_image_handlers(&*gl);
if let Some(output_image_handler) = output {
renderer.set_output_image_handler(output_image_handler);
}
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, layout_size);
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();
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::S => debug_flags.toggle(DebugFlags::COMPACT_PROFILER),
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::F => debug_flags.toggle(
DebugFlags::NEW_FRAME_INDICATOR | DebugFlags::NEW_SCENE_INDICATOR
),
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, layout_size);
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();
}
api.send_transaction(document_id, txn);
renderer.update();
renderer.render(device_size).unwrap();
let _ = renderer.flush_pipeline_info();
example.draw_custom(&*gl);
windowed_context.swap_buffers().ok();
winit::ControlFlow::Continue
});
renderer.deinit();
}

View file

@ -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)
)
}

View file

@ -0,0 +1,149 @@
/* 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::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),
-2,
ColorF::new(0.0, 1.0, 0.0, 1.0),
DeviceIntPoint::new(0, 0),
),
(
PipelineId(2, 0),
-1,
ColorF::new(1.0, 1.0, 0.0, 1.0),
DeviceIntPoint::new(200, 0),
),
(
PipelineId(3, 0),
0,
ColorF::new(1.0, 0.0, 0.0, 1.0),
DeviceIntPoint::new(200, 200),
),
(
PipelineId(4, 0),
1,
ColorF::new(1.0, 0.0, 1.0, 1.0),
DeviceIntPoint::new(0, 200),
),
];
for (pipeline_id, layer, color, offset) in init_data {
let size = DeviceIntSize::new(250, 250);
let bounds = DeviceIntRect::new(offset, size);
let document_id = api.add_document(size, layer);
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() {
let device_pixel_ratio = device_size.width as f32 /
base_builder.content_size().width;
// this is the first run, hack around the boilerplate,
// which assumes an example only needs one document
self.init(api, device_pixel_ratio);
}
for doc in &self.documents {
let space_and_clip = SpaceAndClipInfo::root_scroll(doc.pipeline_id);
let mut builder = DisplayListBuilder::new(
doc.pipeline_id,
doc.content_rect.size,
);
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();
api.send_transaction(doc.id, txn);
}
}
}
fn main() {
let mut app = App {
documents: Vec::new(),
};
boilerplate::main_wrapper(&mut app, None);
}

View file

@ -0,0 +1,238 @@
/* 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::Scale;
use gleam::gl;
use webrender::api::*;
use webrender::api::units::*;
// This example demonstrates using the frame output feature to copy
// the output of a WR framebuffer to a custom texture.
#[derive(Debug)]
struct Document {
id: DocumentId,
pipeline_id: PipelineId,
content_rect: LayoutRect,
color: ColorF,
}
struct App {
external_image_key: Option<ImageKey>,
output_document: Option<Document>
}
struct OutputHandler {
texture_id: gl::GLuint
}
struct ExternalHandler {
texture_id: gl::GLuint
}
impl OutputImageHandler for OutputHandler {
fn lock(&mut self, _id: PipelineId) -> Option<(u32, FramebufferIntSize)> {
Some((self.texture_id, FramebufferIntSize::new(500, 500)))
}
fn unlock(&mut self, _id: PipelineId) {}
}
impl ExternalImageHandler for ExternalHandler {
fn lock(
&mut self,
_key: ExternalImageId,
_channel_index: u8,
_rendering: ImageRendering
) -> ExternalImage {
ExternalImage {
uv: TexelRect::new(0.0, 0.0, 1.0, 1.0),
source: ExternalImageSource::NativeTexture(self.texture_id),
}
}
fn unlock(&mut self, _key: ExternalImageId, _channel_index: u8) {}
}
impl App {
fn init_output_document(
&mut self,
api: &mut RenderApi,
device_size: DeviceIntSize,
device_pixel_ratio: f32,
) {
// Generate the external image key that will be used to render the output document to the root document.
self.external_image_key = Some(api.generate_image_key());
let pipeline_id = PipelineId(1, 0);
let layer = 1;
let color = ColorF::new(1., 1., 0., 1.);
let document_id = api.add_document(device_size, layer);
api.enable_frame_output(document_id, pipeline_id, true);
api.set_document_view(
document_id,
device_size.into(),
device_pixel_ratio,
);
let document = Document {
id: document_id,
pipeline_id,
content_rect: LayoutRect::new(
LayoutPoint::zero(),
device_size.to_f32() / Scale::new(device_pixel_ratio),
),
color,
};
let mut txn = Transaction::new();
txn.add_image(
self.external_image_key.unwrap(),
ImageDescriptor::new(100, 100, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
ImageData::External(ExternalImageData {
id: ExternalImageId(0),
channel_index: 0,
image_type: ExternalImageType::TextureHandle(TextureTarget::Default),
}),
None,
);
let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
let mut builder = DisplayListBuilder::new(
document.pipeline_id,
document.content_rect.size,
);
builder.push_simple_stacking_context(
document.content_rect.origin,
space_and_clip.spatial_id,
PrimitiveFlags::IS_BACKFACE_VISIBLE,
);
builder.push_rect(
&CommonItemProperties::new(document.content_rect, space_and_clip),
document.content_rect,
ColorF::new(1.0, 1.0, 0.0, 1.0)
);
builder.pop_stacking_context();
txn.set_root_pipeline(pipeline_id);
txn.set_display_list(
Epoch(0),
Some(document.color),
document.content_rect.size,
builder.finalize(),
true,
);
txn.generate_frame();
api.send_transaction(document.id, txn);
self.output_document = Some(document);
}
}
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,
) {
if self.output_document.is_none() {
let device_pixel_ratio = device_size.width as f32 /
builder.content_size().width;
self.init_output_document(api, DeviceIntSize::new(200, 200), device_pixel_ratio);
}
let bounds = (100, 100).to(200, 200);
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,
);
builder.push_image(
&CommonItemProperties::new(bounds, space_and_clip),
bounds,
ImageRendering::Auto,
AlphaType::PremultipliedAlpha,
self.external_image_key.unwrap(),
ColorF::WHITE,
);
builder.pop_stacking_context();
}
fn get_image_handlers(
&mut self,
gl: &dyn gl::Gl,
) -> (Option<Box<dyn ExternalImageHandler>>,
Option<Box<dyn OutputImageHandler>>) {
let texture_id = gl.gen_textures(1)[0];
gl.bind_texture(gl::TEXTURE_2D, texture_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,
gl::RGBA as gl::GLint,
100,
100,
0,
gl::BGRA,
gl::UNSIGNED_BYTE,
None,
);
gl.bind_texture(gl::TEXTURE_2D, 0);
(
Some(Box::new(ExternalHandler { texture_id })),
Some(Box::new(OutputHandler { texture_id }))
)
}
}
fn main() {
let mut app = App {
external_image_key: None,
output_document: None
};
boilerplate::main_wrapper(&mut app, None);
}

View file

@ -0,0 +1,95 @@
/* 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::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, sub_bounds.size);
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);
}

View file

@ -0,0 +1,121 @@
/* 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::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();
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);
}

View file

@ -0,0 +1,326 @@
/* 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::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) {
#[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();
}
}
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, device_size).unwrap();
let mut api = sender.create_api();
let document_id = api.add_document(device_size, 0);
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, layout_size);
let space_and_clip = SpaceAndClipInfo::root_scroll(self.pipeline_id);
let bounds = LayoutRect::new(LayoutPoint::zero(), builder.content_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();
api.send_transaction(self.document_id, txn);
renderer.update();
renderer.render(device_size).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
}

View file

@ -0,0 +1,231 @@
/* 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::api::units::*;
use winit::dpi::LogicalPosition;
struct App {
cursor_position: WorldPoint,
}
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,
None,
(0, 0).by(1000, 1000),
scrollbox,
ScrollSensitivity::ScriptAndInputEvents,
LayoutVector2D::zero(),
);
// now put some content into it.
// start with a white background
let mut info = CommonItemProperties::new((0, 0).to(1000, 1000), space_and_clip1);
info.hit_info = Some((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 mut info = CommonItemProperties::new((0, 0).to(50, 50), space_and_clip1);
info.hit_info = Some((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 mut info = CommonItemProperties::new(
(50, 0).to(100, 50).intersection(&(60, 10).to(110, 60)).unwrap(),
space_and_clip1,
);
info.hit_info = Some((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,
None,
(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 mut info = CommonItemProperties::new(
(-1000, -1000).to(5000, 5000),
space_and_clip2,
);
info.hit_info = Some((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 mut info = CommonItemProperties::new((0, 200).to(50, 250), space_and_clip2);
info.hit_info = Some((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 mut info = CommonItemProperties::new(
(50, 350).by(50, 50),
SpaceAndClipInfo {
spatial_id: sticky_id,
clip_id: space_and_clip2.clip_id,
},
);
info.hit_info = Some((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 mut info = CommonItemProperties::new(
(250, 350).to(300, 400),
space_and_clip2,
);
info.hit_info = Some((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((0.0, -10.0)),
winit::VirtualKeyCode::Up => Some((0.0, 10.0)),
winit::VirtualKeyCode::Right => Some((-10.0, 0.0)),
winit::VirtualKeyCode::Left => Some((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 {
txn.scroll(
ScrollLocation::Delta(LayoutVector2D::new(offset.0, offset.1)),
self.cursor_position,
);
txn.generate_frame();
}
if let Some(zoom) = zoom {
txn.set_pinch_zoom(ZoomFactor::new(zoom));
txn.generate_frame();
}
}
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),
};
txn.scroll(
ScrollLocation::Delta(LayoutVector2D::new(dx, dy)),
self.cursor_position,
);
txn.generate_frame();
}
winit::WindowEvent::MouseInput { .. } => {
let results = api.hit_test(
document_id,
None,
self.cursor_position,
HitTestFlags::FIND_ALL
);
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(),
};
boilerplate::main_wrapper(&mut app, None);
}

View file

@ -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::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_handlers(
&mut self,
_gl: &dyn gl::Gl,
) -> (Option<Box<dyn ExternalImageHandler>>,
Option<Box<dyn OutputImageHandler>>) {
(Some(Box::new(ImageGenerator::new())), None)
}
}
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);
}

226
third_party/webrender/examples/yuv.rs vendored Normal file
View file

@ -0,0 +1,226 @@
/* 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::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(), builder.content_size());
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(
TextureTarget::Default,
),
}),
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(
TextureTarget::Default,
),
}),
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(
TextureTarget::Default,
),
}),
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(
TextureTarget::Default,
),
}),
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_handlers(
&mut self,
gl: &dyn gl::Gl,
) -> (Option<Box<dyn ExternalImageHandler>>,
Option<Box<dyn OutputImageHandler>>) {
let provider = YuvImageProvider::new(gl);
self.texture_id = provider.texture_ids[0];
(Some(Box::new(provider)), None)
}
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 {
debug_flags: webrender::DebugFlags::NEW_FRAME_INDICATOR | webrender::DebugFlags::NEW_SCENE_INDICATOR,
..Default::default()
};
boilerplate::main_wrapper(&mut app, Some(opts));
}