--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -74,9 +74,9 @@ there is another crate in m-c called moz
the same folder to store its rust dependencies. If one of the libraries that is
required by both mozjs_sys and webrender is updated without updating the other
project's Cargo.lock file, that results in build bustage.
This means that any time you do this sort of manual update of packages, you need
to make sure that mozjs_sys also has its Cargo.lock file updated if needed, hence
the need to run the cargo update command in js/src as well. Hopefully this will
be resolved soon.
-Latest Commit: e68c8acb021656440d26ac46e705e7ceb31891e6
+Latest Commit: 101c69db1a989fe89c308dabd53cf50aedfe4a96
--- a/gfx/webrender/examples/animation.rs
+++ b/gfx/webrender/examples/animation.rs
@@ -1,81 +1,88 @@
/* 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;
-#[macro_use]
-extern crate lazy_static;
-
#[path="common/boilerplate.rs"]
mod boilerplate;
-use boilerplate::HandyDandyRectBuilder;
-use std::sync::Mutex;
+use boilerplate::{Example, HandyDandyRectBuilder};
use webrender::api::*;
// This example creates a 100x100 white rect and allows the user to move it
// around by using the arrow keys. It does this by using the animation API.
-fn body(_api: &RenderApi,
- _document_id: &DocumentId,
- builder: &mut DisplayListBuilder,
- _resouces: &mut ResourceUpdates,
- _pipeline_id: &PipelineId,
- _layout_size: &LayoutSize) {
- // Create a 100x100 stacking context with an animatable transform property.
- // Note the magic "42" we use as the animation key. That is used to update
- // the transform in the keyboard event handler code.
- let bounds = (0,0).to(100, 100);
- builder.push_stacking_context(ScrollPolicy::Scrollable,
- bounds,
- Some(PropertyBinding::Binding(PropertyBindingKey::new(42))),
- TransformStyle::Flat,
- None,
- MixBlendMode::Normal,
- Vec::new());
-
- // Fill it with a white rect
- builder.push_rect(bounds, None, ColorF::new(1.0, 1.0, 1.0, 1.0));
-
- builder.pop_stacking_context();
+struct App {
+ transform: LayoutTransform,
}
-lazy_static! {
- static ref TRANSFORM: Mutex<LayoutTransform> = Mutex::new(LayoutTransform::identity());
-}
+impl Example for App {
+ fn render(&mut self,
+ _api: &RenderApi,
+ builder: &mut DisplayListBuilder,
+ _resources: &mut ResourceUpdates,
+ _layout_size: LayoutSize,
+ _pipeline_id: PipelineId,
+ _document_id: DocumentId) {
+ // Create a 100x100 stacking context with an animatable transform property.
+ // Note the magic "42" we use as the animation key. That is used to update
+ // the transform in the keyboard event handler code.
+ let bounds = (0,0).to(100, 100);
+ builder.push_stacking_context(ScrollPolicy::Scrollable,
+ bounds,
+ Some(PropertyBinding::Binding(PropertyBindingKey::new(42))),
+ TransformStyle::Flat,
+ None,
+ MixBlendMode::Normal,
+ Vec::new());
+
+ // Fill it with a white rect
+ builder.push_rect(bounds, None, ColorF::new(1.0, 1.0, 1.0, 1.0));
+
+ builder.pop_stacking_context();
+ }
-fn event_handler(event: &glutin::Event, document_id: DocumentId, api: &RenderApi) {
- match *event {
- glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
- let offset = match key {
- glutin::VirtualKeyCode::Down => (0.0, 10.0),
- glutin::VirtualKeyCode::Up => (0.0, -10.0),
- glutin::VirtualKeyCode::Right => (10.0, 0.0),
- glutin::VirtualKeyCode::Left => (-10.0, 0.0),
- _ => return,
- };
- // 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.
- let new_transform = TRANSFORM.lock().unwrap().post_translate(LayoutVector3D::new(offset.0, offset.1, 0.0));
- api.generate_frame(document_id, Some(DynamicProperties {
- transforms: vec![
- PropertyValue {
- key: PropertyBindingKey::new(42),
- value: new_transform,
- },
- ],
- floats: vec![],
- }));
- *TRANSFORM.lock().unwrap() = new_transform;
+ fn on_event(&mut self,
+ event: glutin::Event,
+ api: &RenderApi,
+ document_id: DocumentId) -> bool {
+ match event {
+ glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
+ let offset = match key {
+ glutin::VirtualKeyCode::Down => (0.0, 10.0),
+ glutin::VirtualKeyCode::Up => (0.0, -10.0),
+ glutin::VirtualKeyCode::Right => (10.0, 0.0),
+ glutin::VirtualKeyCode::Left => (-10.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.
+ let new_transform = self.transform.post_translate(LayoutVector3D::new(offset.0, offset.1, 0.0));
+ api.generate_frame(document_id, Some(DynamicProperties {
+ transforms: vec![
+ PropertyValue {
+ key: PropertyBindingKey::new(42),
+ value: new_transform,
+ },
+ ],
+ floats: vec![],
+ }));
+ self.transform = new_transform;
+ }
+ _ => ()
}
- _ => ()
+
+ false
}
}
fn main() {
- boilerplate::main_wrapper(body, event_handler, None);
+ let mut app = App {
+ transform: LayoutTransform::identity(),
+ };
+ boilerplate::main_wrapper(&mut app, None);
}
--- a/gfx/webrender/examples/basic.rs
+++ b/gfx/webrender/examples/basic.rs
@@ -3,30 +3,26 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
extern crate app_units;
extern crate euclid;
extern crate gleam;
extern crate glutin;
extern crate webrender;
-#[macro_use]
-extern crate lazy_static;
-
#[path="common/boilerplate.rs"]
mod boilerplate;
use app_units::Au;
-use boilerplate::HandyDandyRectBuilder;
+use boilerplate::{Example, HandyDandyRectBuilder};
use euclid::vec2;
use glutin::TouchPhase;
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
-use std::sync::Mutex;
use webrender::api::*;
#[derive(Debug)]
enum Gesture {
None,
Pan,
Zoom,
}
@@ -165,182 +161,193 @@ impl TouchState {
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
}
fn main() {
- boilerplate::main_wrapper(body, event_handler, None);
+ let mut app = App {
+ touch_state: TouchState::new(),
+ };
+ boilerplate::main_wrapper(&mut app, None);
+}
+
+struct App {
+ touch_state: TouchState,
}
-fn body(api: &RenderApi,
- _document_id: &DocumentId,
- builder: &mut DisplayListBuilder,
- resources: &mut ResourceUpdates,
- _pipeline_id: &PipelineId,
- layout_size: &LayoutSize) {
- let bounds = LayoutRect::new(LayoutPoint::zero(), *layout_size);
- builder.push_stacking_context(ScrollPolicy::Scrollable,
- bounds,
- None,
- TransformStyle::Flat,
- None,
- MixBlendMode::Normal,
- Vec::new());
+impl Example for App {
+ fn render(&mut self,
+ api: &RenderApi,
+ builder: &mut DisplayListBuilder,
+ resources: &mut ResourceUpdates,
+ layout_size: LayoutSize,
+ _pipeline_id: PipelineId,
+ _document_id: DocumentId) {
+ let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
+ builder.push_stacking_context(ScrollPolicy::Scrollable,
+ bounds,
+ None,
+ TransformStyle::Flat,
+ None,
+ MixBlendMode::Normal,
+ Vec::new());
- let image_mask_key = api.generate_image_key();
- resources.add_image(
- image_mask_key,
- ImageDescriptor::new(2, 2, ImageFormat::A8, true),
- 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));
- let id = builder.define_clip(None, bounds, vec![complex], Some(mask));
- builder.push_clip_id(id);
+ let image_mask_key = api.generate_image_key();
+ resources.add_image(
+ image_mask_key,
+ ImageDescriptor::new(2, 2, ImageFormat::A8, true),
+ 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));
+ let id = builder.define_clip(None, bounds, vec![complex], Some(mask));
+ builder.push_clip_id(id);
- let bounds = (100, 100).to(200, 200);
- builder.push_rect(bounds, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
+ let bounds = (100, 100).to(200, 200);
+ builder.push_rect(bounds, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
- let bounds = (250, 100).to(350, 200);
- builder.push_rect(bounds, None, 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 = BorderWidths {
- top: 10.0,
- left: 10.0,
- bottom: 10.0,
- right: 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),
- });
+ let bounds = (250, 100).to(350, 200);
+ builder.push_rect(bounds, None, 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 = BorderWidths {
+ top: 10.0,
+ left: 10.0,
+ bottom: 10.0,
+ right: 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),
+ });
- let bounds = (100, 100).to(200, 200);
- builder.push_border(bounds, None, border_widths, border_details);
+ let bounds = (100, 100).to(200, 200);
+ builder.push_border(bounds, None, border_widths, border_details);
- if false { // draw text?
- let font_key = api.generate_font_key();
- let font_bytes = load_file("res/FreeSans.ttf");
- resources.add_raw_font(font_key, font_bytes, 0);
+ if false { // draw text?
+ let font_key = api.generate_font_key();
+ let font_bytes = load_file("res/FreeSans.ttf");
+ resources.add_raw_font(font_key, font_bytes, 0);
- let text_bounds = (100, 200).by(700, 300);
- 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),
- },
- ];
+ let text_bounds = (100, 200).by(700, 300);
+ 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(text_bounds,
- None,
- &glyphs,
- font_key,
- ColorF::new(1.0, 1.0, 0.0, 1.0),
- Au::from_px(32),
- None);
+ builder.push_text(text_bounds,
+ None,
+ &glyphs,
+ font_key,
+ ColorF::new(1.0, 1.0, 0.0, 1.0),
+ Au::from_px(32),
+ None);
+ }
+
+ if false { // draw box shadow?
+ let rect = LayoutRect::zero();
+ 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(rect,
+ Some(LocalClip::from(bounds)),
+ simple_box_bounds,
+ offset,
+ color,
+ blur_radius,
+ spread_radius,
+ simple_border_radius,
+ box_shadow_type);
+ }
+
+ builder.pop_clip_id();
+ builder.pop_stacking_context();
}
- if false { // draw box shadow?
- let rect = LayoutRect::zero();
- 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(rect,
- Some(LocalClip::from(bounds)),
- simple_box_bounds,
- offset,
- color,
- blur_radius,
- spread_radius,
- simple_border_radius,
- box_shadow_type);
- }
+ fn on_event(&mut self,
+ event: glutin::Event,
+ api: &RenderApi,
+ document_id: DocumentId) -> bool {
+ match event {
+ glutin::Event::Touch(touch) => {
+ match self.touch_state.handle_event(touch) {
+ TouchResult::Pan(pan) => {
+ api.set_pan(document_id, pan);
+ api.generate_frame(document_id, None);
+ }
+ TouchResult::Zoom(zoom) => {
+ api.set_pinch_zoom(document_id, ZoomFactor::new(zoom));
+ api.generate_frame(document_id, None);
+ }
+ TouchResult::None => {}
+ }
+ }
+ _ => ()
+ }
- builder.pop_clip_id();
- builder.pop_stacking_context();
-}
-
-lazy_static! {
- static ref TOUCH_STATE: Mutex<TouchState> = Mutex::new(TouchState::new());
-}
-
-fn event_handler(event: &glutin::Event, document_id: DocumentId, api: &RenderApi) {
- match *event {
- glutin::Event::Touch(touch) => {
- match TOUCH_STATE.lock().unwrap().handle_event(touch) {
- TouchResult::Pan(pan) => {
- api.set_pan(document_id, pan);
- api.generate_frame(document_id, None);
- }
- TouchResult::Zoom(zoom) => {
- api.set_pinch_zoom(document_id, ZoomFactor::new(zoom));
- api.generate_frame(document_id, None);
- }
- TouchResult::None => {}
- }
- }
- _ => ()
+ false
}
}
--- a/gfx/webrender/examples/blob.rs
+++ b/gfx/webrender/examples/blob.rs
@@ -7,24 +7,24 @@ extern crate euclid;
extern crate gleam;
extern crate glutin;
extern crate webrender;
extern crate rayon;
#[path="common/boilerplate.rs"]
mod boilerplate;
-use boilerplate::HandyDandyRectBuilder;
+use boilerplate::{Example, HandyDandyRectBuilder};
use rayon::ThreadPool;
use rayon::Configuration as ThreadPoolConfig;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::sync::Arc;
use std::sync::mpsc::{channel, Sender, Receiver};
-use webrender::api;
+use webrender::api::{self, RenderApi, DisplayListBuilder, ResourceUpdates, LayoutSize, PipelineId, DocumentId};
// This example shows how to implement a very basic BlobImageRenderer 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/deserialze the blob.
@@ -207,69 +207,80 @@ impl api::BlobImageRenderer for Checkerb
}
// If we break out of the loop above it means the channel closed unexpectedly.
Err(api::BlobImageError::Other("Channel closed".into()))
}
fn delete_font(&mut self, _font: api::FontKey) { }
}
-fn body(api: &api::RenderApi,
- _document_id: &api::DocumentId,
- builder: &mut api::DisplayListBuilder,
- resources: &mut api::ResourceUpdates,
- _pipeline_id: &api::PipelineId,
- layout_size: &api::LayoutSize) {
- let blob_img1 = api.generate_image_key();
- resources.add_image(
- blob_img1,
- api::ImageDescriptor::new(500, 500, api::ImageFormat::BGRA8, true),
- api::ImageData::new_blob_image(serialize_blob(api::ColorU::new(50, 50, 150, 255))),
- Some(128),
- );
-
- let blob_img2 = api.generate_image_key();
- resources.add_image(
- blob_img2,
- api::ImageDescriptor::new(200, 200, api::ImageFormat::BGRA8, true),
- api::ImageData::new_blob_image(serialize_blob(api::ColorU::new(50, 150, 50, 255))),
- None,
- );
+struct App {
- let bounds = api::LayoutRect::new(api::LayoutPoint::zero(), *layout_size);
- builder.push_stacking_context(api::ScrollPolicy::Scrollable,
- bounds,
- None,
- api::TransformStyle::Flat,
- None,
- api::MixBlendMode::Normal,
- Vec::new());
-
- builder.push_image(
- (30, 30).by(500, 500),
- Some(api::LocalClip::from(bounds)),
- api::LayoutSize::new(500.0, 500.0),
- api::LayoutSize::new(0.0, 0.0),
- api::ImageRendering::Auto,
- blob_img1,
- );
-
- builder.push_image(
- (600, 600).by(200, 200),
- Some(api::LocalClip::from(bounds)),
- api::LayoutSize::new(200.0, 200.0),
- api::LayoutSize::new(0.0, 0.0),
- api::ImageRendering::Auto,
- blob_img2,
- );
-
- builder.pop_stacking_context();
}
-fn event_handler(_event: &glutin::Event, _document_id: api::DocumentId, _api: &api::RenderApi) {
+impl Example for App {
+ fn render(&mut self,
+ api: &RenderApi,
+ builder: &mut DisplayListBuilder,
+ resources: &mut ResourceUpdates,
+ layout_size: LayoutSize,
+ _pipeline_id: PipelineId,
+ _document_id: DocumentId) {
+ let blob_img1 = api.generate_image_key();
+ resources.add_image(
+ blob_img1,
+ api::ImageDescriptor::new(500, 500, api::ImageFormat::BGRA8, true),
+ api::ImageData::new_blob_image(serialize_blob(api::ColorU::new(50, 50, 150, 255))),
+ Some(128),
+ );
+
+ let blob_img2 = api.generate_image_key();
+ resources.add_image(
+ blob_img2,
+ api::ImageDescriptor::new(200, 200, api::ImageFormat::BGRA8, true),
+ api::ImageData::new_blob_image(serialize_blob(api::ColorU::new(50, 150, 50, 255))),
+ None,
+ );
+
+ let bounds = api::LayoutRect::new(api::LayoutPoint::zero(), layout_size);
+ builder.push_stacking_context(api::ScrollPolicy::Scrollable,
+ bounds,
+ None,
+ api::TransformStyle::Flat,
+ None,
+ api::MixBlendMode::Normal,
+ Vec::new());
+
+ builder.push_image(
+ (30, 30).by(500, 500),
+ Some(api::LocalClip::from(bounds)),
+ api::LayoutSize::new(500.0, 500.0),
+ api::LayoutSize::new(0.0, 0.0),
+ api::ImageRendering::Auto,
+ blob_img1,
+ );
+
+ builder.push_image(
+ (600, 600).by(200, 200),
+ Some(api::LocalClip::from(bounds)),
+ api::LayoutSize::new(200.0, 200.0),
+ api::LayoutSize::new(0.0, 0.0),
+ api::ImageRendering::Auto,
+ blob_img2,
+ );
+
+ builder.pop_stacking_context();
+ }
+
+ fn on_event(&mut self,
+ _event: glutin::Event,
+ _api: &RenderApi,
+ _document_id: DocumentId) -> bool {
+ false
+ }
}
fn main() {
let worker_config = ThreadPoolConfig::new().thread_name(|idx|{
format!("WebRender:Worker#{}", idx)
});
let workers = Arc::new(ThreadPool::new(worker_config).unwrap());
@@ -277,10 +288,12 @@ fn main() {
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_renderer: Some(Box::new(CheckerboardRenderer::new(Arc::clone(&workers)))),
.. Default::default()
};
- boilerplate::main_wrapper(body, event_handler, Some(opts));
+ let mut app = App {};
+
+ boilerplate::main_wrapper(&mut app, Some(opts));
}
--- a/gfx/webrender/examples/common/boilerplate.rs
+++ b/gfx/webrender/examples/common/boilerplate.rs
@@ -47,25 +47,31 @@ impl HandyDandyRectBuilder for (i32, i32
}
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 fn main_wrapper(builder_callback: fn(&RenderApi,
- &DocumentId,
- &mut DisplayListBuilder,
- &mut ResourceUpdates,
- &PipelineId,
- &LayoutSize) -> (),
- event_handler: fn(&glutin::Event,
- DocumentId,
- &RenderApi) -> (),
+pub trait Example {
+ fn render(&mut self,
+ api: &RenderApi,
+ builder: &mut DisplayListBuilder,
+ resources: &mut ResourceUpdates,
+ layout_size: LayoutSize,
+ pipeline_id: PipelineId,
+ document_id: DocumentId);
+ fn on_event(&mut self,
+ event: glutin::Event,
+ api: &RenderApi,
+ document_id: DocumentId) -> bool;
+}
+
+pub fn main_wrapper(example: &mut Example,
options: Option<webrender::RendererOptions>)
{
let args: Vec<String> = env::args().collect();
let res_path = if args.len() > 1 {
Some(PathBuf::from(&args[1]))
} else {
None
};
@@ -113,18 +119,17 @@ pub fn main_wrapper(builder_callback: fn
let epoch = Epoch(0);
let root_background_color = ColorF::new(0.3, 0.0, 0.0, 1.0);
let pipeline_id = PipelineId(0, 0);
let layout_size = LayoutSize::new(width as f32, height as f32);
let mut builder = DisplayListBuilder::new(pipeline_id, layout_size);
let mut resources = ResourceUpdates::new();
- builder_callback(&api, &document_id, &mut builder, &mut resources, &pipeline_id, &layout_size);
-
+ example.render(&api, &mut builder, &mut resources, layout_size, pipeline_id, document_id);
api.set_display_list(
document_id,
epoch,
Some(root_background_color),
LayoutSize::new(width as f32, height as f32),
builder.finalize(),
true,
resources
@@ -146,31 +151,51 @@ pub fn main_wrapper(builder_callback: fn
glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Escape)) |
glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Q)) => break 'outer,
glutin::Event::KeyboardInput(glutin::ElementState::Pressed,
_, Some(glutin::VirtualKeyCode::P)) => {
let mut flags = renderer.get_debug_flags();
flags.toggle(PROFILER_DBG);
renderer.set_debug_flags(flags);
- },
+ }
glutin::Event::KeyboardInput(glutin::ElementState::Pressed,
_, Some(glutin::VirtualKeyCode::O)) => {
let mut flags = renderer.get_debug_flags();
flags.toggle(RENDER_TARGET_DBG);
renderer.set_debug_flags(flags);
- },
+ }
glutin::Event::KeyboardInput(glutin::ElementState::Pressed,
_, Some(glutin::VirtualKeyCode::I)) => {
let mut flags = renderer.get_debug_flags();
flags.toggle(TEXTURE_CACHE_DBG);
renderer.set_debug_flags(flags);
- },
+ }
+ glutin::Event::KeyboardInput(glutin::ElementState::Pressed,
+ _, Some(glutin::VirtualKeyCode::M)) => {
+ api.notify_memory_pressure();
+ }
+ _ => {
+ if example.on_event(event, &api, document_id) {
+ let mut builder = DisplayListBuilder::new(pipeline_id, layout_size);
+ let mut resources = ResourceUpdates::new();
- _ => event_handler(&event, document_id, &api),
+ example.render(&api, &mut builder, &mut resources, layout_size, pipeline_id, document_id);
+ api.set_display_list(
+ document_id,
+ epoch,
+ Some(root_background_color),
+ LayoutSize::new(width as f32, height as f32),
+ builder.finalize(),
+ true,
+ resources
+ );
+ api.generate_frame(document_id, None);
+ }
+ }
}
}
renderer.update();
renderer.render(DeviceUintSize::new(width, height));
window.swap_buffers().ok();
}
--- a/gfx/webrender/examples/iframe.rs
+++ b/gfx/webrender/examples/iframe.rs
@@ -4,71 +4,82 @@
extern crate gleam;
extern crate glutin;
extern crate webrender;
#[path="common/boilerplate.rs"]
mod boilerplate;
-use boilerplate::HandyDandyRectBuilder;
+use boilerplate::{Example, HandyDandyRectBuilder};
use webrender::api::*;
// 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.
-fn body(api: &RenderApi,
- document_id: &DocumentId,
- builder: &mut DisplayListBuilder,
- _resources: &mut ResourceUpdates,
- pipeline_id: &PipelineId,
- _layout_size: &LayoutSize) {
+struct App {
+
+}
+
+impl Example for App {
+ fn render(&mut self,
+ api: &RenderApi,
+ builder: &mut DisplayListBuilder,
+ _resources: &mut ResourceUpdates,
+ _layout_size: LayoutSize,
+ pipeline_id: PipelineId,
+ document_id: DocumentId) {
+ // All the sub_* things are for the nested pipeline
+ let sub_size = DeviceUintSize::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);
- // All the sub_* things are for the nested pipeline
- let sub_size = DeviceUintSize::new(100, 100);
- let sub_bounds = (0,0).to(sub_size.width as i32, sub_size.height as i32);
+ sub_builder.push_stacking_context(ScrollPolicy::Scrollable,
+ sub_bounds,
+ None,
+ TransformStyle::Flat,
+ None,
+ MixBlendMode::Normal,
+ Vec::new());
+ // green rect visible == success
+ sub_builder.push_rect(sub_bounds, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
+ sub_builder.pop_stacking_context();
- let sub_pipeline_id = PipelineId(pipeline_id.0, 42);
- let mut sub_builder = DisplayListBuilder::new(sub_pipeline_id, sub_bounds.size);
+ api.set_display_list(
+ document_id,
+ Epoch(0),
+ None,
+ sub_bounds.size,
+ sub_builder.finalize(),
+ true,
+ ResourceUpdates::new(),
+ );
- sub_builder.push_stacking_context(ScrollPolicy::Scrollable,
- sub_bounds,
- None,
+ let bounds = sub_bounds;
+ // And this is for the root pipeline
+ builder.push_stacking_context(ScrollPolicy::Scrollable,
+ bounds,
+ Some(PropertyBinding::Binding(PropertyBindingKey::new(42))),
TransformStyle::Flat,
None,
MixBlendMode::Normal,
Vec::new());
- // green rect visible == success
- sub_builder.push_rect(sub_bounds, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
- sub_builder.pop_stacking_context();
-
- api.set_display_list(
- *document_id,
- Epoch(0),
- None,
- sub_bounds.size,
- sub_builder.finalize(),
- true,
- ResourceUpdates::new(),
- );
+ // red rect under the iframe: if this is visible, things have gone wrong
+ builder.push_rect(bounds, None, ColorF::new(1.0, 0.0, 0.0, 1.0));
+ builder.push_iframe(bounds, None, sub_pipeline_id);
+ builder.pop_stacking_context();
+ }
- let bounds = sub_bounds;
- // And this is for the root pipeline
- builder.push_stacking_context(ScrollPolicy::Scrollable,
- bounds,
- Some(PropertyBinding::Binding(PropertyBindingKey::new(42))),
- TransformStyle::Flat,
- None,
- MixBlendMode::Normal,
- Vec::new());
- // red rect under the iframe: if this is visible, things have gone wrong
- builder.push_rect(bounds, None, ColorF::new(1.0, 0.0, 0.0, 1.0));
- builder.push_iframe(bounds, None, sub_pipeline_id);
- builder.pop_stacking_context();
-}
-
-fn event_handler(_event: &glutin::Event, _document_id: DocumentId, _api: &RenderApi) {
+ fn on_event(&mut self,
+ _event: glutin::Event,
+ _api: &RenderApi,
+ _document_id: DocumentId) -> bool {
+ false
+ }
}
fn main() {
- boilerplate::main_wrapper(body, event_handler, None);
+ let mut app = App {};
+ boilerplate::main_wrapper(&mut app, None);
}
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/examples/image_resize.rs
@@ -0,0 +1,114 @@
+/* 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;
+
+#[path="common/boilerplate.rs"]
+mod boilerplate;
+
+use boilerplate::{Example, HandyDandyRectBuilder};
+use webrender::api::*;
+
+struct App {
+ image_key: ImageKey,
+}
+
+impl Example for App {
+ fn render(&mut self,
+ _api: &RenderApi,
+ builder: &mut DisplayListBuilder,
+ resources: &mut ResourceUpdates,
+ _layout_size: LayoutSize,
+ _pipeline_id: PipelineId,
+ _document_id: DocumentId) {
+ let mut image_data = Vec::new();
+ for y in 0..32 {
+ for x in 0..32 {
+ let lum = 255 * (((x & 8) == 0) ^ ((y & 8) == 0)) as u8;
+ image_data.extend_from_slice(&[lum, lum, lum, 0xff]);
+ }
+ }
+
+ resources.add_image(
+ self.image_key,
+ ImageDescriptor::new(32, 32, ImageFormat::BGRA8, true),
+ ImageData::new(image_data),
+ None,
+ );
+
+ let bounds = (0,0).to(512, 512);
+ builder.push_stacking_context(ScrollPolicy::Scrollable,
+ bounds,
+ None,
+ TransformStyle::Flat,
+ None,
+ MixBlendMode::Normal,
+ Vec::new());
+
+ let image_size = LayoutSize::new(100.0, 100.0);
+
+ builder.push_image(
+ LayoutRect::new(LayoutPoint::new(100.0, 100.0), image_size),
+ Some(LocalClip::from(bounds)),
+ image_size,
+ LayoutSize::zero(),
+ ImageRendering::Auto,
+ self.image_key
+ );
+
+ builder.push_image(
+ LayoutRect::new(LayoutPoint::new(250.0, 100.0), image_size),
+ Some(LocalClip::from(bounds)),
+ image_size,
+ LayoutSize::zero(),
+ ImageRendering::Pixelated,
+ self.image_key
+ );
+
+ builder.pop_stacking_context();
+ }
+
+ fn on_event(&mut self,
+ event: glutin::Event,
+ api: &RenderApi,
+ document_id: DocumentId) -> bool {
+ match event {
+ glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
+ match key {
+ glutin::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 updates = ResourceUpdates::new();
+ updates.update_image(self.image_key,
+ ImageDescriptor::new(64, 64, ImageFormat::BGRA8, true),
+ ImageData::new(image_data),
+ None);
+ api.update_resources(updates);
+ api.generate_frame(document_id, None);
+ }
+ _ => {}
+ }
+ }
+ _ => {}
+ }
+
+ false
+ }
+}
+
+fn main() {
+ let mut app = App {
+ image_key: ImageKey(IdNamespace(0), 0),
+ };
+ boilerplate::main_wrapper(&mut app, None);
+}
--- a/gfx/webrender/examples/nested_display_list.rs
+++ b/gfx/webrender/examples/nested_display_list.rs
@@ -1,136 +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 gleam;
extern crate glutin;
extern crate webrender;
-#[macro_use]
-extern crate lazy_static;
-
#[path="common/boilerplate.rs"]
mod boilerplate;
-use boilerplate::HandyDandyRectBuilder;
-use std::sync::Mutex;
+use boilerplate::{Example, HandyDandyRectBuilder};
use webrender::api::*;
-fn body(_api: &RenderApi,
- _document_id: &DocumentId,
- builder: &mut DisplayListBuilder,
- _resources: &mut ResourceUpdates,
- pipeline_id: &PipelineId,
- layout_size: &LayoutSize) {
- let bounds = LayoutRect::new(LayoutPoint::zero(), *layout_size);
- builder.push_stacking_context(ScrollPolicy::Scrollable,
- bounds,
- None,
- TransformStyle::Flat,
- None,
- MixBlendMode::Normal,
- Vec::new());
-
- let outer_scroll_frame_rect = (100, 100).to(600, 400);
- builder.push_rect(outer_scroll_frame_rect, None, ColorF::new(1.0, 1.0, 1.0, 1.0));
-
- let nested_clip_id = builder.define_scroll_frame(None,
- (100, 100).to(1000, 1000),
- outer_scroll_frame_rect,
- vec![],
- None,
- ScrollSensitivity::ScriptAndInputEvents);
- builder.push_clip_id(nested_clip_id);
-
- let mut builder2 = DisplayListBuilder::new(*pipeline_id, *layout_size);
- let mut builder3 = DisplayListBuilder::new(*pipeline_id, *layout_size);
-
- let rect = (110, 110).to(210, 210);
- builder3.push_rect(rect, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
-
- // A fixed position rectangle should be fixed to the reference frame that starts
- // in the outer display list.
- builder3.push_stacking_context(ScrollPolicy::Fixed,
- (220, 110).to(320, 210),
- None,
- TransformStyle::Flat,
- None,
- MixBlendMode::Normal,
- Vec::new());
- let rect = (0, 0).to(100, 100);
- builder3.push_rect(rect, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
- builder3.pop_stacking_context();
-
- // Now we push an inner scroll frame that should have the same id as the outer one,
- // but the WebRender nested display list replacement code should convert it into
- // a unique ClipId.
- let inner_scroll_frame_rect = (330, 110).to(530, 360);
- builder3.push_rect(inner_scroll_frame_rect, None, ColorF::new(1.0, 0.0, 1.0, 0.5));
- let inner_nested_clip_id =
- builder3.define_scroll_frame(None,
- (330, 110).to(2000, 2000),
- inner_scroll_frame_rect,
- vec![],
- None,
- ScrollSensitivity::ScriptAndInputEvents);
- builder3.push_clip_id(inner_nested_clip_id);
- let rect = (340, 120).to(440, 220);
- builder3.push_rect(rect, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
- builder3.pop_clip_id();
-
- let (_, _, built_list) = builder3.finalize();
- builder2.push_nested_display_list(&built_list);
- let (_, _, built_list) = builder2.finalize();
- builder.push_nested_display_list(&built_list);
-
- builder.pop_clip_id();
-
- builder.pop_stacking_context();
+struct App {
+ cursor_position: WorldPoint,
}
-lazy_static! {
- static ref CURSOR_POSITION: Mutex<WorldPoint> = Mutex::new(WorldPoint::zero());
-}
+impl Example for App {
+ fn render(&mut self,
+ _api: &RenderApi,
+ builder: &mut DisplayListBuilder,
+ _resources: &mut ResourceUpdates,
+ layout_size: LayoutSize,
+ pipeline_id: PipelineId,
+ _document_id: DocumentId) {
+ let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
+ builder.push_stacking_context(ScrollPolicy::Scrollable,
+ bounds,
+ None,
+ TransformStyle::Flat,
+ None,
+ MixBlendMode::Normal,
+ Vec::new());
+
+ let outer_scroll_frame_rect = (100, 100).to(600, 400);
+ builder.push_rect(outer_scroll_frame_rect, None, ColorF::new(1.0, 1.0, 1.0, 1.0));
+
+ let nested_clip_id = builder.define_scroll_frame(None,
+ (100, 100).to(1000, 1000),
+ outer_scroll_frame_rect,
+ vec![],
+ None,
+ ScrollSensitivity::ScriptAndInputEvents);
+ builder.push_clip_id(nested_clip_id);
+
+ let mut builder2 = DisplayListBuilder::new(pipeline_id, layout_size);
+ let mut builder3 = DisplayListBuilder::new(pipeline_id, layout_size);
+
+ let rect = (110, 110).to(210, 210);
+ builder3.push_rect(rect, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
+
+ // A fixed position rectangle should be fixed to the reference frame that starts
+ // in the outer display list.
+ builder3.push_stacking_context(ScrollPolicy::Fixed,
+ (220, 110).to(320, 210),
+ None,
+ TransformStyle::Flat,
+ None,
+ MixBlendMode::Normal,
+ Vec::new());
+ let rect = (0, 0).to(100, 100);
+ builder3.push_rect(rect, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
+ builder3.pop_stacking_context();
-fn event_handler(event: &glutin::Event, document_id: DocumentId, api: &RenderApi) {
- match *event {
- glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
- let offset = match key {
- glutin::VirtualKeyCode::Down => (0.0, -10.0),
- glutin::VirtualKeyCode::Up => (0.0, 10.0),
- glutin::VirtualKeyCode::Right => (-10.0, 0.0),
- glutin::VirtualKeyCode::Left => (10.0, 0.0),
- _ => return,
- };
+ // Now we push an inner scroll frame that should have the same id as the outer one,
+ // but the WebRender nested display list replacement code should convert it into
+ // a unique ClipId.
+ let inner_scroll_frame_rect = (330, 110).to(530, 360);
+ builder3.push_rect(inner_scroll_frame_rect, None, ColorF::new(1.0, 0.0, 1.0, 0.5));
+ let inner_nested_clip_id =
+ builder3.define_scroll_frame(None,
+ (330, 110).to(2000, 2000),
+ inner_scroll_frame_rect,
+ vec![],
+ None,
+ ScrollSensitivity::ScriptAndInputEvents);
+ builder3.push_clip_id(inner_nested_clip_id);
+ let rect = (340, 120).to(440, 220);
+ builder3.push_rect(rect, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
+ builder3.pop_clip_id();
+
+ let (_, _, built_list) = builder3.finalize();
+ builder2.push_nested_display_list(&built_list);
+ let (_, _, built_list) = builder2.finalize();
+ builder.push_nested_display_list(&built_list);
+
+ builder.pop_clip_id();
+
+ builder.pop_stacking_context();
+ }
- api.scroll(document_id,
- ScrollLocation::Delta(LayoutVector2D::new(offset.0, offset.1)),
- *CURSOR_POSITION.lock().unwrap(),
- ScrollEventPhase::Start);
+ fn on_event(&mut self,
+ event: glutin::Event,
+ api: &RenderApi,
+ document_id: DocumentId) -> bool {
+ match event {
+ glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
+ let offset = match key {
+ glutin::VirtualKeyCode::Down => (0.0, -10.0),
+ glutin::VirtualKeyCode::Up => (0.0, 10.0),
+ glutin::VirtualKeyCode::Right => (-10.0, 0.0),
+ glutin::VirtualKeyCode::Left => (10.0, 0.0),
+ _ => return false,
+ };
+
+ api.scroll(document_id,
+ ScrollLocation::Delta(LayoutVector2D::new(offset.0, offset.1)),
+ self.cursor_position,
+ ScrollEventPhase::Start);
+ }
+ glutin::Event::MouseMoved(x, y) => {
+ self.cursor_position = WorldPoint::new(x as f32, y as f32);
+ }
+ glutin::Event::MouseWheel(delta, _, event_cursor_position) => {
+ if let Some((x, y)) = event_cursor_position {
+ self.cursor_position = WorldPoint::new(x as f32, y as f32);
+ }
+
+ const LINE_HEIGHT: f32 = 38.0;
+ let (dx, dy) = match delta {
+ glutin::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
+ glutin::MouseScrollDelta::PixelDelta(dx, dy) => (dx, dy),
+ };
+
+ api.scroll(document_id,
+ ScrollLocation::Delta(LayoutVector2D::new(dx, dy)),
+ self.cursor_position,
+ ScrollEventPhase::Start);
+ }
+ _ => ()
}
- glutin::Event::MouseMoved(x, y) => {
- *CURSOR_POSITION.lock().unwrap() = WorldPoint::new(x as f32, y as f32);
- }
- glutin::Event::MouseWheel(delta, _, event_cursor_position) => {
- if let Some((x, y)) = event_cursor_position {
- *CURSOR_POSITION.lock().unwrap() = WorldPoint::new(x as f32, y as f32);
- }
- const LINE_HEIGHT: f32 = 38.0;
- let (dx, dy) = match delta {
- glutin::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
- glutin::MouseScrollDelta::PixelDelta(dx, dy) => (dx, dy),
- };
-
- api.scroll(document_id,
- ScrollLocation::Delta(LayoutVector2D::new(dx, dy)),
- *CURSOR_POSITION.lock().unwrap(),
- ScrollEventPhase::Start);
- }
- _ => ()
+ false
}
}
fn main() {
- boilerplate::main_wrapper(body, event_handler, None);
+ let mut app = App {
+ cursor_position: WorldPoint::zero(),
+ };
+ boilerplate::main_wrapper(&mut app, None);
}
--- a/gfx/webrender/examples/scrolling.rs
+++ b/gfx/webrender/examples/scrolling.rs
@@ -1,145 +1,152 @@
/* 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;
-#[macro_use]
-extern crate lazy_static;
-
#[path="common/boilerplate.rs"]
mod boilerplate;
-use boilerplate::HandyDandyRectBuilder;
-use std::sync::Mutex;
+use boilerplate::{Example, HandyDandyRectBuilder};
use webrender::api::*;
-fn body(_api: &RenderApi,
- _document_id: &DocumentId,
- builder: &mut DisplayListBuilder,
- _resources: &mut ResourceUpdates,
- _pipeline_id: &PipelineId,
- layout_size: &LayoutSize) {
- let bounds = LayoutRect::new(LayoutPoint::zero(), *layout_size);
- builder.push_stacking_context(ScrollPolicy::Scrollable,
- bounds,
- None,
- TransformStyle::Flat,
- None,
- MixBlendMode::Normal,
- Vec::new());
+struct App {
+ cursor_position: WorldPoint,
+}
- if true { // scrolling and clips stuff
- // let's make a scrollbox
- let scrollbox = (0, 0).to(300, 400);
+impl Example for App {
+ fn render(&mut self,
+ _api: &RenderApi,
+ builder: &mut DisplayListBuilder,
+ _resources: &mut ResourceUpdates,
+ layout_size: LayoutSize,
+ _pipeline_id: PipelineId,
+ _document_id: DocumentId) {
+ let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
builder.push_stacking_context(ScrollPolicy::Scrollable,
- LayoutRect::new(LayoutPoint::new(10.0, 10.0),
- LayoutSize::zero()),
+ bounds,
None,
TransformStyle::Flat,
None,
MixBlendMode::Normal,
Vec::new());
- // set the scrolling clip
- let clip_id = builder.define_scroll_frame(None,
- (0, 0).by(1000, 1000),
- scrollbox,
- vec![],
- None,
- ScrollSensitivity::ScriptAndInputEvents);
- builder.push_clip_id(clip_id);
- // now put some content into it.
- // start with a white background
- builder.push_rect((0, 0).to(1000, 1000), None, ColorF::new(1.0, 1.0, 1.0, 1.0));
+ if true { // scrolling and clips stuff
+ // let's make a scrollbox
+ let scrollbox = (0, 0).to(300, 400);
+ builder.push_stacking_context(ScrollPolicy::Scrollable,
+ LayoutRect::new(LayoutPoint::new(10.0, 10.0),
+ LayoutSize::zero()),
+ None,
+ TransformStyle::Flat,
+ None,
+ MixBlendMode::Normal,
+ Vec::new());
+ // set the scrolling clip
+ let clip_id = builder.define_scroll_frame(None,
+ (0, 0).by(1000, 1000),
+ scrollbox,
+ vec![],
+ None,
+ ScrollSensitivity::ScriptAndInputEvents);
+ builder.push_clip_id(clip_id);
- // let's make a 50x50 blue square as a visual reference
- builder.push_rect((0, 0).to(50, 50), None, ColorF::new(0.0, 0.0, 1.0, 1.0));
+ // now put some content into it.
+ // start with a white background
+ builder.push_rect((0, 0).to(1000, 1000), None, ColorF::new(1.0, 1.0, 1.0, 1.0));
- // and a 50x50 green square next to it with an offset clip
- // to see what that looks like
- builder.push_rect((50, 0).to(100, 50),
- Some(LocalClip::from((60, 10).to(110, 60))),
- ColorF::new(0.0, 1.0, 0.0, 1.0));
+ // let's make a 50x50 blue square as a visual reference
+ builder.push_rect((0, 0).to(50, 50), None, ColorF::new(0.0, 0.0, 1.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 nested_clip_id = builder.define_scroll_frame(None,
- (0, 100).to(300, 400),
- (0, 100).to(200, 300),
- vec![],
- None,
- ScrollSensitivity::ScriptAndInputEvents);
- builder.push_clip_id(nested_clip_id);
+ // and a 50x50 green square next to it with an offset clip
+ // to see what that looks like
+ builder.push_rect((50, 0).to(100, 50),
+ Some(LocalClip::from((60, 10).to(110, 60))),
+ 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 nested_clip_id = builder.define_scroll_frame(None,
+ (0, 100).to(300, 400),
+ (0, 100).to(200, 300),
+ vec![],
+ None,
+ ScrollSensitivity::ScriptAndInputEvents);
+ builder.push_clip_id(nested_clip_id);
- // give it a giant gray background just to distinguish it and to easily
- // visually identify the nested scrollbox
- builder.push_rect((-1000, -1000).to(5000, 5000), None, ColorF::new(0.5, 0.5, 0.5, 1.0));
+ // give it a giant gray background just to distinguish it and to easily
+ // visually identify the nested scrollbox
+ builder.push_rect((-1000, -1000).to(5000, 5000), None, 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 with WASD keys
- builder.push_rect((0, 100).to(50, 150), None, ColorF::new(0.0, 1.0, 1.0, 1.0));
+ // add a teal square to visualize the scrolling/clipping behaviour
+ // as you scroll the nested scrollbox with WASD keys
+ builder.push_rect((0, 100).to(50, 150), None, ColorF::new(0.0, 1.0, 1.0, 1.0));
- // just for good measure add another teal square in the bottom-right
- // corner of the nested scrollframe content, which can be scrolled into
- // view by the user
- builder.push_rect((250, 350).to(300, 400), None, ColorF::new(0.0, 1.0, 1.0, 1.0));
+ // just for good measure add another teal square in the bottom-right
+ // corner of the nested scrollframe content, which can be scrolled into
+ // view by the user
+ builder.push_rect((250, 350).to(300, 400), None, ColorF::new(0.0, 1.0, 1.0, 1.0));
- builder.pop_clip_id(); // nested_clip_id
+ builder.pop_clip_id(); // nested_clip_id
- builder.pop_clip_id(); // clip_id
+ builder.pop_clip_id(); // clip_id
+ builder.pop_stacking_context();
+ }
+
builder.pop_stacking_context();
}
- builder.pop_stacking_context();
-}
-
-lazy_static! {
- static ref CURSOR_POSITION: Mutex<WorldPoint> = Mutex::new(WorldPoint::zero());
-}
-
-fn event_handler(event: &glutin::Event, document_id: DocumentId, api: &RenderApi) {
- match *event {
- glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
- let offset = match key {
- glutin::VirtualKeyCode::Down => (0.0, -10.0),
- glutin::VirtualKeyCode::Up => (0.0, 10.0),
- glutin::VirtualKeyCode::Right => (-10.0, 0.0),
- glutin::VirtualKeyCode::Left => (10.0, 0.0),
- _ => return,
- };
+ fn on_event(&mut self,
+ event: glutin::Event,
+ api: &RenderApi,
+ document_id: DocumentId) -> bool {
+ match event {
+ glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
+ let offset = match key {
+ glutin::VirtualKeyCode::Down => (0.0, -10.0),
+ glutin::VirtualKeyCode::Up => (0.0, 10.0),
+ glutin::VirtualKeyCode::Right => (-10.0, 0.0),
+ glutin::VirtualKeyCode::Left => (10.0, 0.0),
+ _ => return false,
+ };
- api.scroll(document_id,
- ScrollLocation::Delta(LayoutVector2D::new(offset.0, offset.1)),
- *CURSOR_POSITION.lock().unwrap(),
- ScrollEventPhase::Start);
- }
- glutin::Event::MouseMoved(x, y) => {
- *CURSOR_POSITION.lock().unwrap() = WorldPoint::new(x as f32, y as f32);
- }
- glutin::Event::MouseWheel(delta, _, event_cursor_position) => {
- if let Some((x, y)) = event_cursor_position {
- *CURSOR_POSITION.lock().unwrap() = WorldPoint::new(x as f32, y as f32);
+ api.scroll(document_id,
+ ScrollLocation::Delta(LayoutVector2D::new(offset.0, offset.1)),
+ self.cursor_position,
+ ScrollEventPhase::Start);
}
+ glutin::Event::MouseMoved(x, y) => {
+ self.cursor_position = WorldPoint::new(x as f32, y as f32);
+ }
+ glutin::Event::MouseWheel(delta, _, event_cursor_position) => {
+ if let Some((x, y)) = event_cursor_position {
+ self.cursor_position = WorldPoint::new(x as f32, y as f32);
+ }
- const LINE_HEIGHT: f32 = 38.0;
- let (dx, dy) = match delta {
- glutin::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
- glutin::MouseScrollDelta::PixelDelta(dx, dy) => (dx, dy),
- };
+ const LINE_HEIGHT: f32 = 38.0;
+ let (dx, dy) = match delta {
+ glutin::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
+ glutin::MouseScrollDelta::PixelDelta(dx, dy) => (dx, dy),
+ };
- api.scroll(document_id,
- ScrollLocation::Delta(LayoutVector2D::new(dx, dy)),
- *CURSOR_POSITION.lock().unwrap(),
- ScrollEventPhase::Start);
+ api.scroll(document_id,
+ ScrollLocation::Delta(LayoutVector2D::new(dx, dy)),
+ self.cursor_position,
+ ScrollEventPhase::Start);
+ }
+ _ => ()
}
- _ => ()
+
+ false
}
}
fn main() {
- boilerplate::main_wrapper(body, event_handler, None);
+ let mut app = App {
+ cursor_position: WorldPoint::zero(),
+ };
+ boilerplate::main_wrapper(&mut app, None);
}
--- a/gfx/webrender/examples/yuv.rs
+++ b/gfx/webrender/examples/yuv.rs
@@ -3,25 +3,22 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
extern crate app_units;
extern crate euclid;
extern crate gleam;
extern crate glutin;
extern crate webrender;
-#[macro_use]
-extern crate lazy_static;
-
#[path="common/boilerplate.rs"]
mod boilerplate;
+use boilerplate::Example;
use glutin::TouchPhase;
use std::collections::HashMap;
-use std::sync::Mutex;
use webrender::api::*;
#[derive(Debug)]
enum Gesture {
None,
Pan,
Zoom,
}
@@ -152,98 +149,108 @@ impl TouchState {
self.current_gesture = Gesture::None;
}
}
TouchResult::None
}
}
-fn main() {
- boilerplate::main_wrapper(body, event_handler, None);
+struct App {
+ touch_state: TouchState,
}
-fn body(api: &RenderApi,
- _document_id: &DocumentId,
- builder: &mut DisplayListBuilder,
- resources: &mut ResourceUpdates,
- _pipeline_id: &PipelineId,
- layout_size: &LayoutSize) {
- let bounds = LayoutRect::new(LayoutPoint::zero(), *layout_size);
- builder.push_stacking_context(ScrollPolicy::Scrollable,
- bounds,
- None,
- TransformStyle::Flat,
- None,
- MixBlendMode::Normal,
- Vec::new());
+impl Example for App {
+ fn render(&mut self,
+ api: &RenderApi,
+ builder: &mut DisplayListBuilder,
+ resources: &mut ResourceUpdates,
+ layout_size: LayoutSize,
+ _pipeline_id: PipelineId,
+ _document_id: DocumentId) {
+ let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
+ builder.push_stacking_context(ScrollPolicy::Scrollable,
+ bounds,
+ None,
+ TransformStyle::Flat,
+ None,
+ MixBlendMode::Normal,
+ Vec::new());
- 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();
- resources.add_image(
- yuv_chanel1,
- ImageDescriptor::new(100, 100, ImageFormat::A8, true),
- ImageData::new(vec![127; 100 * 100]),
- None,
- );
- resources.add_image(
- yuv_chanel2,
- ImageDescriptor::new(100, 100, ImageFormat::RG8, true),
- ImageData::new(vec![0; 100 * 100 * 2]),
- None,
- );
- resources.add_image(
- yuv_chanel2_1,
- ImageDescriptor::new(100, 100, ImageFormat::A8, true),
- ImageData::new(vec![127; 100 * 100]),
- None,
- );
- resources.add_image(
- yuv_chanel3,
- ImageDescriptor::new(100, 100, ImageFormat::A8, true),
- ImageData::new(vec![127; 100 * 100]),
- None,
- );
+ 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();
+ resources.add_image(
+ yuv_chanel1,
+ ImageDescriptor::new(100, 100, ImageFormat::A8, true),
+ ImageData::new(vec![127; 100 * 100]),
+ None,
+ );
+ resources.add_image(
+ yuv_chanel2,
+ ImageDescriptor::new(100, 100, ImageFormat::RG8, true),
+ ImageData::new(vec![0; 100 * 100 * 2]),
+ None,
+ );
+ resources.add_image(
+ yuv_chanel2_1,
+ ImageDescriptor::new(100, 100, ImageFormat::A8, true),
+ ImageData::new(vec![127; 100 * 100]),
+ None,
+ );
+ resources.add_image(
+ yuv_chanel3,
+ ImageDescriptor::new(100, 100, ImageFormat::A8, true),
+ ImageData::new(vec![127; 100 * 100]),
+ None,
+ );
- builder.push_yuv_image(
- LayoutRect::new(LayoutPoint::new(100.0, 0.0), LayoutSize::new(100.0, 100.0)),
- Some(LocalClip::from(bounds)),
- YuvData::NV12(yuv_chanel1, yuv_chanel2),
- YuvColorSpace::Rec601,
- ImageRendering::Auto,
- );
+ builder.push_yuv_image(
+ LayoutRect::new(LayoutPoint::new(100.0, 0.0), LayoutSize::new(100.0, 100.0)),
+ Some(LocalClip::from(bounds)),
+ YuvData::NV12(yuv_chanel1, yuv_chanel2),
+ YuvColorSpace::Rec601,
+ ImageRendering::Auto,
+ );
+
+ builder.push_yuv_image(
+ LayoutRect::new(LayoutPoint::new(300.0, 0.0), LayoutSize::new(100.0, 100.0)),
+ Some(LocalClip::from(bounds)),
+ YuvData::PlanarYCbCr(yuv_chanel1, yuv_chanel2_1, yuv_chanel3),
+ YuvColorSpace::Rec601,
+ ImageRendering::Auto,
+ );
+
+ builder.pop_stacking_context();
+ }
- builder.push_yuv_image(
- LayoutRect::new(LayoutPoint::new(300.0, 0.0), LayoutSize::new(100.0, 100.0)),
- Some(LocalClip::from(bounds)),
- YuvData::PlanarYCbCr(yuv_chanel1, yuv_chanel2_1, yuv_chanel3),
- YuvColorSpace::Rec601,
- ImageRendering::Auto,
- );
+ fn on_event(&mut self,
+ event: glutin::Event,
+ api: &RenderApi,
+ document_id: DocumentId) -> bool {
+ match event {
+ glutin::Event::Touch(touch) => {
+ match self.touch_state.handle_event(touch) {
+ TouchResult::Pan(pan) => {
+ api.set_pan(document_id, pan);
+ api.generate_frame(document_id, None);
+ }
+ TouchResult::Zoom(zoom) => {
+ api.set_pinch_zoom(document_id, ZoomFactor::new(zoom));
+ api.generate_frame(document_id, None);
+ }
+ TouchResult::None => {}
+ }
+ }
+ _ => ()
+ }
- builder.pop_stacking_context();
+ false
+ }
}
-lazy_static! {
- static ref TOUCH_STATE: Mutex<TouchState> = Mutex::new(TouchState::new());
+fn main() {
+ let mut app = App {
+ touch_state: TouchState::new(),
+ };
+ boilerplate::main_wrapper(&mut app, None);
}
-
-
-fn event_handler(event: &glutin::Event, document_id: DocumentId, api: &RenderApi) {
- match *event {
- glutin::Event::Touch(touch) => {
- match TOUCH_STATE.lock().unwrap().handle_event(touch) {
- TouchResult::Pan(pan) => {
- api.set_pan(document_id, pan);
- api.generate_frame(document_id, None);
- }
- TouchResult::Zoom(zoom) => {
- api.set_pinch_zoom(document_id, ZoomFactor::new(zoom));
- api.generate_frame(document_id, None);
- }
- TouchResult::None => {}
- }
- }
- _ => ()
- }
-}
--- a/gfx/webrender/res/ps_line.vs.glsl
+++ b/gfx/webrender/res/ps_line.vs.glsl
@@ -58,21 +58,21 @@ void main(void) {
center_line,
0.0);
break;
}
case LINE_STYLE_WAVY: {
// Choose some arbitrary values to scale thickness,
// wave period etc.
// TODO(gw): Tune these to get closer to what Gecko uses.
- float thickness = 0.2 * size.y;
+ float thickness = 0.15 * size.y;
vParams = vec4(thickness,
size.y * 0.5,
size.y * 0.75,
- max(3.0, thickness * 2.0));
+ size.y * 0.5);
break;
}
}
#ifdef WR_FEATURE_CACHE
int text_shadow_address = prim.user_data0;
PrimitiveGeometry shadow_geom = fetch_primitive_geometry(text_shadow_address);
TextShadow shadow = fetch_text_shadow(text_shadow_address + VECS_PER_PRIM_HEADER);
--- a/gfx/webrender/src/freelist.rs
+++ b/gfx/webrender/src/freelist.rs
@@ -1,105 +1,106 @@
/* 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/. */
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-pub struct FreeListItemId(u32);
+use std::marker::PhantomData;
+use std::mem;
+
+// TODO(gw): Add a weak free list handle. This is like a strong
+// free list handle below, but will contain an epoch
+// field. Weak handles will use a get_opt style API
+// which returns an Option<T> instead of T.
-impl FreeListItemId {
- #[inline]
- pub fn new(value: u32) -> FreeListItemId {
- FreeListItemId(value)
- }
+// TODO(gw): Add an occupied list head, for fast
+// iteration of the occupied list to implement
+// retain() style functionality.
- #[inline]
- pub fn value(&self) -> u32 {
- self.0
- }
+#[derive(Debug)]
+pub struct FreeListHandle<T> {
+ index: u32,
+ _marker: PhantomData<T>,
}
-pub trait FreeListItem {
- fn take(&mut self) -> Self;
- fn next_free_id(&self) -> Option<FreeListItemId>;
- fn set_next_free_id(&mut self, id: Option<FreeListItemId>);
+enum SlotValue<T> {
+ Free,
+ Occupied(T),
}
-struct FreeListIter<'a, T: 'a> {
- items: &'a [T],
- cur_index: Option<FreeListItemId>,
-}
-
-impl<'a, T: FreeListItem> Iterator for FreeListIter<'a, T> {
- type Item = FreeListItemId;
- fn next(&mut self) -> Option<Self::Item> {
- self.cur_index.map(|free_id| {
- self.cur_index = self.items[free_id.0 as usize].next_free_id();
- free_id
- })
+impl<T> SlotValue<T> {
+ fn take(&mut self) -> T {
+ match mem::replace(self, SlotValue::Free) {
+ SlotValue::Free => unreachable!(),
+ SlotValue::Occupied(data) => data,
+ }
}
}
+struct Slot<T> {
+ next: Option<u32>,
+ value: SlotValue<T>,
+}
+
pub struct FreeList<T> {
- items: Vec<T>,
- first_free_index: Option<FreeListItemId>,
- alloc_count: usize,
+ slots: Vec<Slot<T>>,
+ free_list_head: Option<u32>,
}
-impl<T: FreeListItem> FreeList<T> {
+impl<T> FreeList<T> {
pub fn new() -> FreeList<T> {
FreeList {
- items: Vec::new(),
- first_free_index: None,
- alloc_count: 0,
+ slots: Vec::new(),
+ free_list_head: None,
+ }
+ }
+
+ pub fn get(&self, id: &FreeListHandle<T>) -> &T {
+ match self.slots[id.index as usize].value {
+ SlotValue::Free => unreachable!(),
+ SlotValue::Occupied(ref data) => data,
}
}
- fn free_iter(&self) -> FreeListIter<T> {
- FreeListIter {
- items: &self.items,
- cur_index: self.first_free_index,
+ pub fn get_mut(&mut self, id: &FreeListHandle<T>) -> &mut T {
+ match self.slots[id.index as usize].value {
+ SlotValue::Free => unreachable!(),
+ SlotValue::Occupied(ref mut data) => data,
}
}
- pub fn insert(&mut self, item: T) -> FreeListItemId {
- self.alloc_count += 1;
- match self.first_free_index {
+ pub fn insert(&mut self, item: T) -> FreeListHandle<T> {
+ match self.free_list_head {
Some(free_index) => {
- let FreeListItemId(index) = free_index;
- let free_item = &mut self.items[index as usize];
- self.first_free_index = free_item.next_free_id();
- *free_item = item;
- free_index
+ let slot = &mut self.slots[free_index as usize];
+
+ // Remove from free list.
+ self.free_list_head = slot.next;
+ slot.next = None;
+ slot.value = SlotValue::Occupied(item);
+
+ FreeListHandle {
+ index: free_index,
+ _marker: PhantomData,
+ }
}
None => {
- let item_id = FreeListItemId(self.items.len() as u32);
- self.items.push(item);
- item_id
+ let index = self.slots.len() as u32;
+
+ self.slots.push(Slot {
+ next: None,
+ value: SlotValue::Occupied(item),
+ });
+
+ FreeListHandle {
+ index,
+ _marker: PhantomData,
+ }
}
}
}
- pub fn get(&self, id: FreeListItemId) -> &T {
- debug_assert_eq!(self.free_iter().find(|&fid| fid==id), None);
- &self.items[id.0 as usize]
- }
-
- pub fn get_mut(&mut self, id: FreeListItemId) -> &mut T {
- debug_assert_eq!(self.free_iter().find(|&fid| fid==id), None);
- &mut self.items[id.0 as usize]
- }
-
- #[allow(dead_code)]
- pub fn len(&self) -> usize {
- self.alloc_count
- }
-
- pub fn free(&mut self, id: FreeListItemId) -> T {
- self.alloc_count -= 1;
- let FreeListItemId(index) = id;
- let item = &mut self.items[index as usize];
- let data = item.take();
- item.set_next_free_id(self.first_free_index);
- self.first_free_index = Some(id);
- data
+ pub fn free(&mut self, id: FreeListHandle<T>) -> T {
+ let slot = &mut self.slots[id.index as usize];
+ slot.next = self.free_list_head;
+ self.free_list_head = Some(id.index);
+ slot.value.take()
}
}
--- a/gfx/webrender/src/glyph_cache.rs
+++ b/gfx/webrender/src/glyph_cache.rs
@@ -10,31 +10,31 @@ use resource_cache::{Resource, ResourceC
use texture_cache::{TextureCache, TextureCacheItemId};
pub struct CachedGlyphInfo {
pub texture_cache_id: Option<TextureCacheItemId>,
pub last_access: FrameId,
}
impl Resource for CachedGlyphInfo {
- fn free(&self, texture_cache: &mut TextureCache) {
+ fn free(self, texture_cache: &mut TextureCache) {
if let Some(id) = self.texture_cache_id {
texture_cache.free(id);
}
}
fn get_last_access_time(&self) -> FrameId {
self.last_access
}
fn set_last_access_time(&mut self, frame_id: FrameId) {
self.last_access = frame_id;
}
fn add_to_gpu_cache(&self,
texture_cache: &mut TextureCache,
gpu_cache: &mut GpuCache) {
- if let Some(texture_cache_id) = self.texture_cache_id {
+ if let Some(texture_cache_id) = self.texture_cache_id.as_ref() {
let item = texture_cache.get_mut(texture_cache_id);
if let Some(mut request) = gpu_cache.request(&mut item.uv_rect_handle) {
request.push(item.uv_rect);
request.push([item.user_data[0], item.user_data[1], 0.0, 0.0]);
}
}
}
}
@@ -84,16 +84,25 @@ impl GlyphCache {
}
}
for key in caches_to_remove {
self.glyph_key_caches.remove(&key).unwrap();
}
}
+ pub fn clear(&mut self, texture_cache: &mut TextureCache) {
+ for (_, glyph_key_cache) in &mut self.glyph_key_caches {
+ glyph_key_cache.clear(texture_cache)
+ }
+ // We use this in on_memory_pressure where retaining memory allocations
+ // isn't desirable, so we completely remove the hash map instead of clearing it.
+ self.glyph_key_caches = FastHashMap::default();
+ }
+
pub fn clear_fonts<F>(&mut self, texture_cache: &mut TextureCache, key_fun: F)
where for<'r> F: Fn(&'r &FontInstanceKey) -> bool
{
let caches_to_destroy = self.glyph_key_caches.keys()
.filter(&key_fun)
.cloned()
.collect::<Vec<_>>();
for key in caches_to_destroy {
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -291,17 +291,17 @@ impl GlyphRasterizer {
}
}
}
impl FontContext {
fn add_font(&mut self, font_key: &FontKey, template: &FontTemplate) {
match template {
&FontTemplate::Raw(ref bytes, index) => {
- self.add_raw_font(&font_key, &**bytes, index);
+ self.add_raw_font(&font_key, bytes.clone(), index);
}
&FontTemplate::Native(ref native_font_handle) => {
self.add_native_font(&font_key, (*native_font_handle).clone());
}
}
}
}
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -185,16 +185,17 @@ impl RendererFrame {
frame,
}
}
}
pub enum ResultMsg {
RefreshShader(PathBuf),
NewFrame(DocumentId, RendererFrame, TextureUpdateList, BackendProfileCounters),
+ UpdateResources { updates: TextureUpdateList, cancel_rendering: bool },
}
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
pub struct StackingContextIndex(pub usize);
#[derive(Clone, Copy, Debug)]
pub struct UvRect {
pub uv0: DevicePoint,
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -70,16 +70,17 @@ mod prim_store;
mod print_tree;
mod profiler;
mod record;
mod render_backend;
mod render_task;
mod resource_cache;
mod scene;
mod spring;
+mod texture_allocator;
mod texture_cache;
mod tiling;
mod util;
#[doc(hidden)] // for benchmarks
pub use texture_cache::TexturePage;
#[cfg(feature = "webgl")]
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -11,20 +11,21 @@ use core_graphics::data_provider::CGData
use core_graphics::font::{CGFont, CGGlyph};
use core_graphics::geometry::{CGPoint, CGSize, CGRect};
use core_text::font::CTFont;
use core_text::font_descriptor::kCTFontDefaultOrientation;
use core_text;
use internal_types::FastHashMap;
use std::collections::hash_map::Entry;
use api::{ColorU, FontKey, FontRenderMode, GlyphDimensions};
-use api::{GlyphKey, SubpixelDirection};
+use api::{GlyphKey};
use api::{FontInstanceKey, NativeFontHandle};
use gamma_lut::{GammaLut, Color as ColorLut};
use std::ptr;
+use std::sync::Arc;
pub struct FontContext {
cg_fonts: FastHashMap<FontKey, CGFont>,
ct_fonts: FastHashMap<(FontKey, Au), CTFont>,
gamma_lut: GammaLut,
}
// core text is safe to use on multiple threads and non-shareable resources are
@@ -158,23 +159,23 @@ impl FontContext {
gamma_lut: GammaLut::new(contrast, gamma, gamma),
}
}
pub fn has_font(&self, font_key: &FontKey) -> bool {
self.cg_fonts.contains_key(font_key)
}
- pub fn add_raw_font(&mut self, font_key: &FontKey, bytes: &[u8], index: u32) {
+ pub fn add_raw_font(&mut self, font_key: &FontKey, bytes: Arc<Vec<u8>>, index: u32) {
if self.cg_fonts.contains_key(font_key) {
return
}
assert_eq!(index, 0);
- let data_provider = CGDataProvider::from_buffer(bytes);
+ let data_provider = CGDataProvider::from_buffer(&**bytes);
let cg_font = match CGFont::from_data_provider(data_provider) {
Err(_) => return,
Ok(cg_font) => cg_font,
};
self.cg_fonts.insert((*font_key).clone(), cg_font);
}
pub fn add_native_font(&mut self, font_key: &FontKey, native_font_handle: NativeFontHandle) {
--- a/gfx/webrender/src/platform/unix/font.rs
+++ b/gfx/webrender/src/platform/unix/font.rs
@@ -11,30 +11,34 @@ use freetype::freetype::{FT_Render_Mode,
use freetype::freetype::{FT_Done_FreeType, FT_Library_SetLcdFilter, FT_Pos};
use freetype::freetype::{FT_Library, FT_Set_Char_Size, FT_Outline_Get_CBox};
use freetype::freetype::{FT_Face, FT_Long, FT_UInt, FT_F26Dot6, FT_Glyph_Format};
use freetype::freetype::{FT_Init_FreeType, FT_Load_Glyph, FT_Render_Glyph};
use freetype::freetype::{FT_New_Memory_Face, FT_GlyphSlot, FT_LcdFilter};
use freetype::freetype::{FT_Done_Face, FT_Error, FT_Int32, FT_Get_Char_Index};
use std::{mem, ptr, slice};
+use std::sync::Arc;
// This constant is not present in the freetype
// bindings due to bindgen not handling the way
// the macro is defined.
const FT_LOAD_TARGET_LIGHT: FT_Int32 = 1 << 16;
// Default to slight hinting, which is what most
// Linux distros use by default, and is a better
// default than no hinting.
// TODO(gw): Make this configurable.
const GLYPH_LOAD_FLAGS: FT_Int32 = FT_LOAD_TARGET_LIGHT;
struct Face {
face: FT_Face,
+ // Raw byte data has to live until the font is deleted, according to
+ // https://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_New_Memory_Face
+ _bytes: Arc<Vec<u8>>,
}
pub struct FontContext {
lib: FT_Library,
faces: FastHashMap<FontKey, Face>,
lcd_extra_pixels: i64,
}
@@ -79,30 +83,30 @@ impl FontContext {
lcd_extra_pixels: lcd_extra_pixels,
}
}
pub fn has_font(&self, font_key: &FontKey) -> bool {
self.faces.contains_key(font_key)
}
- pub fn add_raw_font(&mut self, font_key: &FontKey, bytes: &[u8], index: u32) {
+ pub fn add_raw_font(&mut self, font_key: &FontKey, bytes: Arc<Vec<u8>>, index: u32) {
if !self.faces.contains_key(&font_key) {
let mut face: FT_Face = ptr::null_mut();
let result = unsafe {
FT_New_Memory_Face(self.lib,
bytes.as_ptr(),
bytes.len() as FT_Long,
index as FT_Long,
&mut face)
};
if result.succeeded() && !face.is_null() {
self.faces.insert(*font_key, Face {
face,
- //_bytes: bytes
+ _bytes: bytes,
});
} else {
println!("WARN: webrender failed to load font {:?}", font_key);
}
}
}
pub fn add_native_font(&mut self, _font_key: &FontKey, _native_font_handle: NativeFontHandle) {
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -3,16 +3,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{FontKey, FontRenderMode, GlyphDimensions};
use api::{FontInstanceKey, GlyphKey, GlyphOptions, SubpixelDirection};
use gamma_lut::{GammaLut, Color as ColorLut};
use internal_types::FastHashMap;
use dwrote;
+use std::sync::Arc;
lazy_static! {
static ref DEFAULT_FONT_DESCRIPTOR: dwrote::FontDescriptor = dwrote::FontDescriptor {
family_name: "Arial".to_owned(),
weight: dwrote::FontWeight::Regular,
stretch: dwrote::FontStretch::Normal,
style: dwrote::FontStyle::Normal,
};
@@ -100,22 +101,22 @@ impl FontContext {
gdi_gamma_lut: GammaLut::new(contrast, gdi_gamma, gdi_gamma),
}
}
pub fn has_font(&self, font_key: &FontKey) -> bool {
self.fonts.contains_key(font_key)
}
- pub fn add_raw_font(&mut self, font_key: &FontKey, data: &[u8], index: u32) {
+ pub fn add_raw_font(&mut self, font_key: &FontKey, data: Arc<Vec<u8>>, index: u32) {
if self.fonts.contains_key(font_key) {
return
}
- if let Some(font_file) = dwrote::FontFile::new_from_data(data) {
+ if let Some(font_file) = dwrote::FontFile::new_from_data(&**data) {
let face = font_file.create_face(index, dwrote::DWRITE_FONT_SIMULATIONS_NONE);
self.fonts.insert((*font_key).clone(), face);
} else {
// XXX add_raw_font needs to have a way to return an error
debug!("DWrite WR failed to load font from data, using Arial instead");
self.add_native_font(font_key, DEFAULT_FONT_DESCRIPTOR.clone());
}
}
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -7,16 +7,17 @@ use frame_builder::FrameBuilderConfig;
use gpu_cache::GpuCache;
use internal_types::{FastHashMap, SourceTexture, ResultMsg, RendererFrame};
use profiler::{BackendProfileCounters, ResourceProfileCounters};
use record::ApiRecordingReceiver;
use resource_cache::ResourceCache;
use scene::Scene;
use std::sync::{Arc, Mutex};
use std::sync::mpsc::Sender;
+use std::u32;
use texture_cache::TextureCache;
use time::precise_time_ns;
use thread_profiler::register_thread_with_profiler;
use rayon::ThreadPool;
use webgl_types::{GLContextHandleWrapper, GLContextWrapper};
use api::channel::{MsgReceiver, PayloadReceiver, PayloadReceiverHelperMethods};
use api::channel::{PayloadSender, PayloadSenderHelperMethods};
use api::{ApiMsg, BlobImageRenderer, BuiltDisplayList, DeviceIntPoint};
@@ -579,16 +580,27 @@ impl RenderBackend {
let document_ids = self.documents.keys()
.filter(|did| did.0 == namespace_id)
.cloned()
.collect::<Vec<_>>();
for document in document_ids {
self.documents.remove(&document);
}
}
+ ApiMsg::MemoryPressure => {
+ self.resource_cache.on_memory_pressure();
+
+ let pending_update = self.resource_cache.pending_updates();
+ let msg = ResultMsg::UpdateResources { updates: pending_update, cancel_rendering: true };
+ self.result_tx.send(msg).unwrap();
+ // We use new_frame_ready to wake up the renderer and get the
+ // resource updates processed, but the UpdateResources message
+ // will cancel rendering the frame.
+ self.notifier.lock().unwrap().as_mut().unwrap().new_frame_ready();
+ }
ApiMsg::ShutDown => {
let notifier = self.notifier.lock();
notifier.unwrap()
.as_mut()
.unwrap()
.shut_down();
break;
}
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -805,16 +805,17 @@ pub struct Renderer {
cpu_profiles: VecDeque<CpuProfile>,
gpu_profiles: VecDeque<GpuProfile>,
}
#[derive(Debug)]
pub enum InitError {
Shader(ShaderError),
Thread(std::io::Error),
+ MaxTextureSize,
}
impl From<ShaderError> for InitError {
fn from(err: ShaderError) -> Self { InitError::Shader(err) }
}
impl From<std::io::Error> for InitError {
fn from(err: std::io::Error) -> Self { InitError::Thread(err) }
@@ -834,33 +835,51 @@ impl Renderer {
/// device_pixel_ratio: 1.0,
/// resource_override_path: None,
/// enable_aa: false,
/// };
/// let (renderer, sender) = Renderer::new(opts);
/// ```
/// [rendereroptions]: struct.RendererOptions.html
pub fn new(gl: Rc<gl::Gl>, mut options: RendererOptions) -> Result<(Renderer, RenderApiSender), InitError> {
+
let (api_tx, api_rx) = try!{ channel::msg_channel() };
let (payload_tx, payload_rx) = try!{ channel::payload_channel() };
let (result_tx, result_rx) = channel();
let gl_type = gl.get_type();
- register_thread_with_profiler("Compositor".to_owned());
-
let notifier = Arc::new(Mutex::new(None));
let file_watch_handler = FileWatcher {
result_tx: result_tx.clone(),
notifier: Arc::clone(¬ifier),
};
- let mut device = Device::new(gl,
- options.resource_override_path.clone(),
- Box::new(file_watch_handler));
+ let mut device = Device::new(
+ gl,
+ options.resource_override_path.clone(),
+ Box::new(file_watch_handler)
+ );
+
+ let device_max_size = device.max_texture_size();
+ // 512 is the minimum that the texture cache can work with.
+ // Broken GL contexts can return a max texture size of zero (See #1260). Better to
+ // gracefully fail now than panic as soon as a texture is allocated.
+ let min_texture_size = 512;
+ if device_max_size < min_texture_size {
+ println!("Device reporting insufficient max texture size ({})", device_max_size);
+ return Err(InitError::MaxTextureSize);
+ }
+ let max_device_size = cmp::max(
+ cmp::min(device_max_size, options.max_texture_size.unwrap_or(device_max_size)),
+ min_texture_size
+ );
+
+ register_thread_with_profiler("Compositor".to_owned());
+
// device-pixel ratio doesn't matter here - we are just creating resources.
device.begin_frame(1.0);
let cs_box_shadow = try!{
LazilyCompiledShader::new(ShaderKind::Cache(VertexFormat::PrimitiveInstances),
"cs_box_shadow",
&[],
&mut device,
@@ -1107,20 +1126,19 @@ impl Renderer {
let ps_split_composite = try!{
LazilyCompiledShader::new(ShaderKind::Primitive,
"ps_split_composite",
&[],
&mut device,
options.precache_shaders)
};
- let device_max_size = device.max_texture_size();
- let max_texture_size = cmp::min(device_max_size, options.max_texture_size.unwrap_or(device_max_size));
+ let texture_cache = TextureCache::new(max_device_size);
+ let max_texture_size = texture_cache.max_texture_size();
- let texture_cache = TextureCache::new(max_texture_size);
let backend_profile_counters = BackendProfileCounters::new();
let dummy_cache_texture_id = device.create_texture_ids(1, TextureTarget::Array)[0];
device.init_texture(dummy_cache_texture_id,
1,
1,
ImageFormat::BGRA8,
TextureFilter::Linear,
@@ -1404,16 +1422,27 @@ impl Renderer {
// Update the list of available epochs for use during reftests.
// This is a workaround for https://github.com/servo/servo/issues/13149.
for (pipeline_id, epoch) in &frame.pipeline_epoch_map {
self.pipeline_epoch_map.insert(*pipeline_id, *epoch);
}
self.current_frame = Some(frame);
}
+ ResultMsg::UpdateResources { updates, cancel_rendering } => {
+ self.pending_texture_updates.push(updates);
+ self.update_texture_cache();
+ // If we receive a NewFrame message followed by this one within
+ // the same update we need ot cancel the frame because we might
+ // have deleted the resources in use in the frame dut to a memory
+ // pressure event.
+ if cancel_rendering {
+ self.current_frame = None;
+ }
+ }
ResultMsg::RefreshShader(path) => {
self.pending_shader_updates.push(path);
}
}
}
}
// Get the real (OpenGL) texture ID for a given source texture.
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -548,17 +548,19 @@ impl ResourceCache {
mut f: F) -> SourceTexture where F: FnMut(usize, &GpuCacheHandle) {
debug_assert_eq!(self.state, State::QueryResources);
let mut texture_id = None;
let glyph_key_cache = self.cached_glyphs.get_glyph_key_cache_for_font(&font);
for (loop_index, key) in glyph_keys.iter().enumerate() {
let glyph = glyph_key_cache.get(key, self.current_frame_id);
- let cache_item = glyph.texture_cache_id.map(|image_id| self.texture_cache.get(image_id));
+ let cache_item = glyph.texture_cache_id
+ .as_ref()
+ .map(|image_id| self.texture_cache.get(image_id));
if let Some(cache_item) = cache_item {
f(loop_index, &cache_item.uv_rect_handle);
debug_assert!(texture_id == None ||
texture_id == Some(cache_item.texture_id));
texture_id = Some(cache_item.texture_id);
}
}
@@ -589,17 +591,17 @@ impl ResourceCache {
tile: Option<TileOffset>) -> CacheItem {
debug_assert_eq!(self.state, State::QueryResources);
let key = ImageRequest {
key: image_key,
rendering: image_rendering,
tile,
};
let image_info = &self.cached_images.get(&key, self.current_frame_id);
- let item = self.texture_cache.get(image_info.texture_cache_id);
+ let item = self.texture_cache.get(&image_info.texture_cache_id);
CacheItem {
texture_id: SourceTexture::TextureCache(item.texture_id),
uv_rect_handle: item.uv_rect_handle,
}
}
pub fn get_image_properties(&self, image_key: ImageKey) -> ImageProperties {
let image_template = &self.resources.image_templates.get(image_key).unwrap();
@@ -751,35 +753,31 @@ impl ResourceCache {
format: image_descriptor.format,
is_opaque: image_descriptor.is_opaque,
}
} else {
image_template.descriptor.clone()
};
match self.cached_images.entry(request, self.current_frame_id) {
- Occupied(entry) => {
- let image_id = entry.get().texture_cache_id;
+ Occupied(mut entry) => {
+ let entry = entry.get_mut();
// We should only get to this code path if the image
// definitely needs to be updated.
- debug_assert!(entry.get().epoch != image_template.epoch);
- self.texture_cache.update(image_id,
+ debug_assert!(entry.epoch != image_template.epoch);
+ self.texture_cache.update(&entry.texture_cache_id,
descriptor,
filter,
image_data,
image_template.dirty_rect);
// Update the cached epoch
- debug_assert_eq!(self.current_frame_id, entry.get().last_access);
- *entry.into_mut() = CachedImageInfo {
- texture_cache_id: image_id,
- epoch: image_template.epoch,
- last_access: self.current_frame_id,
- };
+ debug_assert_eq!(self.current_frame_id, entry.last_access);
+ entry.epoch = image_template.epoch;
image_template.dirty_rect = None;
}
Vacant(entry) => {
let image_id = self.texture_cache.insert(descriptor,
filter,
image_data,
[0.0; 2],
texture_cache_profile);
@@ -794,16 +792,29 @@ impl ResourceCache {
}
}
pub fn end_frame(&mut self) {
debug_assert_eq!(self.state, State::QueryResources);
self.state = State::Idle;
}
+ pub fn on_memory_pressure(&mut self) {
+ // This is drastic. It will basically flush everything out of the cache,
+ // and the next frame will have to rebuild all of its resources.
+ // We may want to look into something less extreme, but on the other hand this
+ // should only be used in situations where are running low enough on memory
+ // that we risk crashing if we don't do something about it.
+ // The advantage of clearing the cache completely is that it gets rid of any
+ // remaining fragmentation that could have persisted if we kept around the most
+ // recently used resources.
+ self.cached_images.clear(&mut self.texture_cache);
+ self.cached_glyphs.clear(&mut self.texture_cache);
+ }
+
pub fn clear_namespace(&mut self, namespace: IdNamespace) {
//TODO: use `retain` when we are on Rust-1.18
let image_keys: Vec<_> = self.resources.image_templates.images.keys()
.filter(|&key| key.0 == namespace)
.cloned()
.collect();
for key in &image_keys {
self.resources.image_templates.images.remove(key);
@@ -818,38 +829,38 @@ impl ResourceCache {
}
self.cached_images.clear_keys(&mut self.texture_cache, |request| request.key.0 == namespace);
self.cached_glyphs.clear_fonts(&mut self.texture_cache, |font| font.font_key.0 == namespace);
}
}
pub trait Resource {
- fn free(&self, texture_cache: &mut TextureCache);
+ fn free(self, texture_cache: &mut TextureCache);
fn get_last_access_time(&self) -> FrameId;
fn set_last_access_time(&mut self, frame_id: FrameId);
fn add_to_gpu_cache(&self,
texture_cache: &mut TextureCache,
gpu_cache: &mut GpuCache);
}
impl Resource for CachedImageInfo {
- fn free(&self, texture_cache: &mut TextureCache) {
+ fn free(self, texture_cache: &mut TextureCache) {
texture_cache.free(self.texture_cache_id);
}
fn get_last_access_time(&self) -> FrameId {
self.last_access
}
fn set_last_access_time(&mut self, frame_id: FrameId) {
self.last_access = frame_id;
}
fn add_to_gpu_cache(&self,
texture_cache: &mut TextureCache,
gpu_cache: &mut GpuCache) {
- let item = texture_cache.get_mut(self.texture_cache_id);
+ let item = texture_cache.get_mut(&self.texture_cache_id);
if let Some(mut request) = gpu_cache.request(&mut item.uv_rect_handle) {
request.push(item.uv_rect);
}
}
}
// Compute the width and height of a tile depending on its position in the image.
pub fn compute_tile_size(descriptor: &ImageDescriptor,
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/texture_allocator.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/. */
+
+use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize};
+use std::slice::Iter;
+use util;
+
+/// The minimum number of pixels on each side that we require for rects to be classified as
+/// "medium" within the free list.
+const MINIMUM_MEDIUM_RECT_SIZE: u32 = 16;
+
+/// The minimum number of pixels on each side that we require for rects to be classified as
+/// "large" within the free list.
+const MINIMUM_LARGE_RECT_SIZE: u32 = 32;
+
+/// A texture allocator using the guillotine algorithm with the rectangle merge improvement. See
+/// sections 2.2 and 2.2.5 in "A Thousand Ways to Pack the Bin - A Practical Approach to Two-
+/// Dimensional Rectangle Bin Packing":
+///
+/// http://clb.demon.fi/files/RectangleBinPack.pdf
+///
+/// This approach was chosen because of its simplicity, good performance, and easy support for
+/// dynamic texture deallocation.
+pub struct GuillotineAllocator {
+ texture_size: DeviceUintSize,
+ free_list: FreeRectList,
+ allocations: u32,
+ dirty: bool,
+}
+
+impl GuillotineAllocator {
+ pub fn new(texture_size: DeviceUintSize) -> GuillotineAllocator {
+ let mut page = GuillotineAllocator {
+ texture_size,
+ free_list: FreeRectList::new(),
+ allocations: 0,
+ dirty: false,
+ };
+ page.clear();
+ page
+ }
+
+ fn find_index_of_best_rect_in_bin(&self, bin: FreeListBin, requested_dimensions: &DeviceUintSize)
+ -> Option<FreeListIndex> {
+ let mut smallest_index_and_area = None;
+ for (candidate_index, candidate_rect) in self.free_list.iter(bin).enumerate() {
+ if !requested_dimensions.fits_inside(&candidate_rect.size) {
+ continue
+ }
+
+ let candidate_area = candidate_rect.size.width * candidate_rect.size.height;
+ smallest_index_and_area = Some((candidate_index, candidate_area));
+ break
+ }
+
+ smallest_index_and_area.map(|(index, _)| FreeListIndex(bin, index))
+ }
+
+ /// Find a suitable rect in the free list. We choose the smallest such rect
+ /// in terms of area (Best-Area-Fit, BAF).
+ fn find_index_of_best_rect(&self, requested_dimensions: &DeviceUintSize)
+ -> Option<FreeListIndex> {
+ let bin = FreeListBin::for_size(requested_dimensions);
+ for &target_bin in &[FreeListBin::Small, FreeListBin::Medium, FreeListBin::Large] {
+ if bin <= target_bin {
+ if let Some(index) = self.find_index_of_best_rect_in_bin(target_bin,
+ requested_dimensions) {
+ return Some(index);
+ }
+ }
+ }
+ None
+ }
+
+ pub fn allocate(&mut self, requested_dimensions: &DeviceUintSize) -> Option<DeviceUintPoint> {
+ if requested_dimensions.width == 0 || requested_dimensions.height == 0 {
+ return Some(DeviceUintPoint::new(0, 0))
+ }
+ let index = match self.find_index_of_best_rect(requested_dimensions) {
+ None => return None,
+ Some(index) => index,
+ };
+
+ // Remove the rect from the free list and decide how to guillotine it. We choose the split
+ // that results in the single largest area (Min Area Split Rule, MINAS).
+ let chosen_rect = self.free_list.remove(index);
+ let candidate_free_rect_to_right =
+ DeviceUintRect::new(
+ DeviceUintPoint::new(chosen_rect.origin.x + requested_dimensions.width, chosen_rect.origin.y),
+ DeviceUintSize::new(chosen_rect.size.width - requested_dimensions.width, requested_dimensions.height));
+ let candidate_free_rect_to_bottom =
+ DeviceUintRect::new(
+ DeviceUintPoint::new(chosen_rect.origin.x, chosen_rect.origin.y + requested_dimensions.height),
+ DeviceUintSize::new(requested_dimensions.width, chosen_rect.size.height - requested_dimensions.height));
+ let candidate_free_rect_to_right_area = candidate_free_rect_to_right.size.width *
+ candidate_free_rect_to_right.size.height;
+ let candidate_free_rect_to_bottom_area = candidate_free_rect_to_bottom.size.width *
+ candidate_free_rect_to_bottom.size.height;
+
+ // Guillotine the rectangle.
+ let new_free_rect_to_right;
+ let new_free_rect_to_bottom;
+ if candidate_free_rect_to_right_area > candidate_free_rect_to_bottom_area {
+ new_free_rect_to_right = DeviceUintRect::new(
+ candidate_free_rect_to_right.origin,
+ DeviceUintSize::new(candidate_free_rect_to_right.size.width,
+ chosen_rect.size.height));
+ new_free_rect_to_bottom = candidate_free_rect_to_bottom
+ } else {
+ new_free_rect_to_right = candidate_free_rect_to_right;
+ new_free_rect_to_bottom =
+ DeviceUintRect::new(candidate_free_rect_to_bottom.origin,
+ DeviceUintSize::new(chosen_rect.size.width,
+ candidate_free_rect_to_bottom.size.height))
+ }
+
+ // Add the guillotined rects back to the free list. If any changes were made, we're now
+ // dirty since coalescing might be able to defragment.
+ if !util::rect_is_empty(&new_free_rect_to_right) {
+ self.free_list.push(&new_free_rect_to_right);
+ self.dirty = true
+ }
+ if !util::rect_is_empty(&new_free_rect_to_bottom) {
+ self.free_list.push(&new_free_rect_to_bottom);
+ self.dirty = true
+ }
+
+ // Bump the allocation counter.
+ self.allocations += 1;
+
+ // Return the result.
+ Some(chosen_rect.origin)
+ }
+
+ fn clear(&mut self) {
+ self.free_list = FreeRectList::new();
+ self.free_list.push(&DeviceUintRect::new(
+ DeviceUintPoint::zero(),
+ self.texture_size));
+ self.allocations = 0;
+ self.dirty = false;
+ }
+}
+
+/// A binning free list. Binning is important to avoid sifting through lots of small strips when
+/// allocating many texture items.
+struct FreeRectList {
+ small: Vec<DeviceUintRect>,
+ medium: Vec<DeviceUintRect>,
+ large: Vec<DeviceUintRect>,
+}
+
+impl FreeRectList {
+ fn new() -> FreeRectList {
+ FreeRectList {
+ small: vec![],
+ medium: vec![],
+ large: vec![],
+ }
+ }
+
+ fn push(&mut self, rect: &DeviceUintRect) {
+ match FreeListBin::for_size(&rect.size) {
+ FreeListBin::Small => self.small.push(*rect),
+ FreeListBin::Medium => self.medium.push(*rect),
+ FreeListBin::Large => self.large.push(*rect),
+ }
+ }
+
+ fn remove(&mut self, index: FreeListIndex) -> DeviceUintRect {
+ match index.0 {
+ FreeListBin::Small => self.small.swap_remove(index.1),
+ FreeListBin::Medium => self.medium.swap_remove(index.1),
+ FreeListBin::Large => self.large.swap_remove(index.1),
+ }
+ }
+
+ fn iter(&self, bin: FreeListBin) -> Iter<DeviceUintRect> {
+ match bin {
+ FreeListBin::Small => self.small.iter(),
+ FreeListBin::Medium => self.medium.iter(),
+ FreeListBin::Large => self.large.iter(),
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+struct FreeListIndex(FreeListBin, usize);
+
+#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
+enum FreeListBin {
+ Small,
+ Medium,
+ Large,
+}
+
+impl FreeListBin {
+ fn for_size(size: &DeviceUintSize) -> FreeListBin {
+ if size.width >= MINIMUM_LARGE_RECT_SIZE && size.height >= MINIMUM_LARGE_RECT_SIZE {
+ FreeListBin::Large
+ } else if size.width >= MINIMUM_MEDIUM_RECT_SIZE &&
+ size.height >= MINIMUM_MEDIUM_RECT_SIZE {
+ FreeListBin::Medium
+ } else {
+ debug_assert!(size.width > 0 && size.height > 0);
+ FreeListBin::Small
+ }
+ }
+}
+
+trait FitsInside {
+ fn fits_inside(&self, other: &Self) -> bool;
+}
+
+impl FitsInside for DeviceUintSize {
+ fn fits_inside(&self, other: &DeviceUintSize) -> bool {
+ self.width <= other.width && self.height <= other.height
+ }
+}
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -1,14 +1,14 @@
/* 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 device::TextureFilter;
-use freelist::{FreeList, FreeListItem, FreeListItemId};
+use freelist::{FreeList, FreeListHandle};
use gpu_cache::GpuCacheHandle;
use internal_types::{FastHashMap, TextureUpdate, TextureUpdateOp, UvRect};
use internal_types::{CacheTextureId, RenderTargetMode, TextureUpdateList};
use profiler::TextureCacheProfileCounters;
use std::cmp;
use std::collections::hash_map::Entry;
use std::mem;
use std::slice::Iter;
@@ -41,17 +41,17 @@ const MINIMUM_LARGE_RECT_SIZE: u32 = 32;
/// The amount of time in milliseconds we give ourselves to coalesce rects before giving up.
const COALESCING_TIMEOUT: u64 = 100;
/// The number of items that we process in the coalescing work list before checking whether we hit
/// the timeout.
const COALESCING_TIMEOUT_CHECKING_INTERVAL: usize = 256;
-pub type TextureCacheItemId = FreeListItemId;
+pub type TextureCacheItemId = FreeListHandle<TextureCacheItem>;
enum CoalescingStatus {
Changed,
Unchanged,
Timeout,
}
/// A texture allocator using the guillotine algorithm with the rectangle merge improvement. See
@@ -67,17 +67,17 @@ pub struct TexturePage {
texture_size: DeviceUintSize,
free_list: FreeRectList,
coalesce_vec: Vec<DeviceUintRect>,
allocations: u32,
dirty: bool,
}
impl TexturePage {
- pub fn new(texture_id: CacheTextureId, texture_size: DeviceUintSize) -> TexturePage {
+ fn new(texture_id: CacheTextureId, texture_size: DeviceUintSize) -> TexturePage {
let mut page = TexturePage {
texture_id,
texture_size,
free_list: FreeRectList::new(),
coalesce_vec: Vec::new(),
allocations: 0,
dirty: false,
};
@@ -112,17 +112,17 @@ impl TexturePage {
requested_dimensions) {
return Some(index);
}
}
}
None
}
- pub fn can_allocate(&self, requested_dimensions: &DeviceUintSize) -> bool {
+ fn can_allocate(&self, requested_dimensions: &DeviceUintSize) -> bool {
self.find_index_of_best_rect(requested_dimensions).is_some()
}
pub fn allocate(&mut self,
requested_dimensions: &DeviceUintSize) -> Option<DeviceUintPoint> {
if requested_dimensions.width == 0 || requested_dimensions.height == 0 {
return Some(DeviceUintPoint::new(0, 0))
}
@@ -275,17 +275,17 @@ impl TexturePage {
if changed {
self.free_list.init_from_slice(&self.coalesce_vec);
}
self.dirty = changed;
changed
}
- pub fn clear(&mut self) {
+ fn clear(&mut self) {
self.free_list = FreeRectList::new();
self.free_list.push(&DeviceUintRect::new(
DeviceUintPoint::zero(),
self.texture_size));
self.allocations = 0;
self.dirty = false;
}
@@ -452,48 +452,16 @@ pub struct TextureCacheItem {
pub format: ImageFormat,
// Some arbitrary data associated with this item.
// In the case of glyphs, it is the top / left offset
// from the rasterized glyph.
pub user_data: [f32; 2],
}
-// Structure squat the width/height fields to maintain the free list information :)
-impl FreeListItem for TextureCacheItem {
- fn take(&mut self) -> Self {
- let data = self.clone();
- self.texture_id = CacheTextureId(0);
- data
- }
-
- fn next_free_id(&self) -> Option<FreeListItemId> {
- if self.allocated_rect.size.width == 0 {
- debug_assert_eq!(self.allocated_rect.size.height, 0);
- None
- } else {
- debug_assert_eq!(self.allocated_rect.size.width, 1);
- Some(FreeListItemId::new(self.allocated_rect.size.height))
- }
- }
-
- fn set_next_free_id(&mut self, id: Option<FreeListItemId>) {
- match id {
- Some(id) => {
- self.allocated_rect.size.width = 1;
- self.allocated_rect.size.height = id.value();
- }
- None => {
- self.allocated_rect.size.width = 0;
- self.allocated_rect.size.height = 0;
- }
- }
- }
-}
-
impl TextureCacheItem {
fn new(texture_id: CacheTextureId,
rect: DeviceUintRect,
format: ImageFormat,
user_data: [f32; 2])
-> TextureCacheItem {
TextureCacheItem {
texture_id,
@@ -582,17 +550,17 @@ pub struct TextureCache {
#[derive(PartialEq, Eq, Debug)]
pub enum AllocationKind {
TexturePage,
Standalone,
}
#[derive(Debug)]
pub struct AllocationResult {
- image_id: TextureCacheItemId,
+ new_image_id: Option<TextureCacheItemId>,
kind: AllocationKind,
item: TextureCacheItem,
}
impl TextureCache {
pub fn new(mut max_texture_size: u32) -> TextureCache {
if max_texture_size * max_texture_size > MAX_RGBA_PIXELS_PER_TEXTURE {
max_texture_size = SQRT_MAX_RGBA_PIXELS_PER_TEXTURE;
@@ -631,53 +599,70 @@ impl TextureCache {
format,
filter,
user_data,
profile,
None,
)
}
- // If item_id is None, create a new id, otherwise reuse it.
+ // If existing_item_id is None, create a new id, otherwise reuse it.
fn allocate_impl(
&mut self,
requested_width: u32,
requested_height: u32,
format: ImageFormat,
filter: TextureFilter,
user_data: [f32; 2],
profile: &mut TextureCacheProfileCounters,
- item_id: Option<TextureCacheItemId>
+ existing_item_id: Option<&TextureCacheItemId>
) -> AllocationResult {
let requested_size = DeviceUintSize::new(requested_width, requested_height);
// TODO(gw): For now, anything that requests nearest filtering
// just fails to allocate in a texture page, and gets a standalone
// texture. This isn't ideal, as it causes lots of batch breaks,
// but is probably rare enough that it can be fixed up later (it's also
// fairly trivial to implement, just tedious).
if filter == TextureFilter::Nearest {
// Fall back to standalone texture allocation.
let texture_id = self.cache_id_list.allocate();
+
+ let update_op = TextureUpdate {
+ id: texture_id,
+ op: texture_create_op(DeviceUintSize::new(requested_width,
+ requested_height),
+ format,
+ RenderTargetMode::None,
+ filter),
+ };
+ self.pending_updates.push(update_op);
+
let cache_item = TextureCacheItem::new(
texture_id,
DeviceUintRect::new(DeviceUintPoint::zero(), requested_size),
format,
user_data
);
- let image_id = match item_id {
- Some(id) => id,
- None => self.items.insert(cache_item.clone()),
+ let new_image_id = match existing_item_id {
+ Some(id) => {
+ *self.items.get_mut(id) = cache_item.clone();
+ None
+ }
+ None => {
+ let id = self.items.insert(cache_item.clone());
+ Some(id)
+ }
};
return AllocationResult {
- item: self.items.get(image_id).clone(),
+ item: cache_item,
kind: AllocationKind::Standalone,
- image_id,
+ new_image_id,
}
}
let mode = RenderTargetMode::SimpleRenderTarget;
let (page_list, page_profile) = match format {
ImageFormat::A8 => (&mut self.arena.pages_a8, &mut profile.pages_a8),
ImageFormat::BGRA8 => (&mut self.arena.pages_rgba8, &mut profile.pages_rgba8),
ImageFormat::RGB8 => (&mut self.arena.pages_rgb8, &mut profile.pages_rgb8),
@@ -746,17 +731,20 @@ impl TextureCache {
Entry::Vacant(entry) => entry.insert(Vec::new()),
Entry::Occupied(entry) => entry.into_mut(),
};
if free_texture_levels.is_empty() {
let texture_id = self.cache_id_list.allocate();
let update_op = TextureUpdate {
id: texture_id,
- op: texture_create_op(texture_size, format, mode),
+ op: texture_create_op(texture_size,
+ format,
+ mode,
+ filter),
};
self.pending_updates.push(update_op);
free_texture_levels.push(FreeTextureLevel {
texture_id,
});
}
let free_texture_level = free_texture_levels.pop().unwrap();
@@ -772,31 +760,37 @@ impl TextureCache {
.expect("All the checks have passed till now, there is no way back.");
let cache_item = TextureCacheItem::new(
page.texture_id,
DeviceUintRect::new(location, requested_size),
format,
user_data
);
- let image_id = match item_id {
- Some(id) => id,
- None => self.items.insert(cache_item.clone()),
+ let new_image_id = match existing_item_id {
+ Some(id) => {
+ *self.items.get_mut(id) = cache_item.clone();
+ None
+ }
+ None => {
+ let id = self.items.insert(cache_item.clone());
+ Some(id)
+ }
};
AllocationResult {
item: cache_item,
kind: AllocationKind::TexturePage,
- image_id,
+ new_image_id,
}
}
pub fn update(
&mut self,
- image_id: TextureCacheItemId,
+ image_id: &TextureCacheItemId,
descriptor: ImageDescriptor,
filter: TextureFilter,
data: ImageData,
mut dirty_rect: Option<DeviceUintRect>,
) {
let mut existing_item = self.items.get(image_id).clone();
if existing_item.allocated_rect.size.width != descriptor.width ||
@@ -997,24 +991,24 @@ impl TextureCache {
};
self.pending_updates.push(update_op);
}
}
}
}
- result.image_id
+ result.new_image_id.unwrap()
}
- pub fn get(&self, id: TextureCacheItemId) -> &TextureCacheItem {
+ pub fn get(&self, id: &TextureCacheItemId) -> &TextureCacheItem {
self.items.get(id)
}
- pub fn get_mut(&mut self, id: TextureCacheItemId) -> &mut TextureCacheItem {
+ pub fn get_mut(&mut self, id: &TextureCacheItemId) -> &mut TextureCacheItem {
self.items.get_mut(id)
}
pub fn free(&mut self, id: TextureCacheItemId) {
let item = self.items.free(id);
self.free_item_rect(item);
}
@@ -1029,23 +1023,26 @@ impl TextureCache {
op: TextureUpdateOp::Free,
});
self.cache_id_list.free(item.texture_id);
}
}
}
}
-fn texture_create_op(texture_size: DeviceUintSize, format: ImageFormat, mode: RenderTargetMode)
+fn texture_create_op(texture_size: DeviceUintSize,
+ format: ImageFormat,
+ mode: RenderTargetMode,
+ filter: TextureFilter)
-> TextureUpdateOp {
TextureUpdateOp::Create {
width: texture_size.width,
height: texture_size.height,
format,
- filter: TextureFilter::Linear,
+ filter,
mode,
data: None,
}
}
fn texture_grow_op(texture_size: DeviceUintSize,
format: ImageFormat,
mode: RenderTargetMode)
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -1,29 +1,29 @@
/* 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 border::{BorderCornerInstance, BorderCornerSide};
use device::TextureId;
use gpu_cache::{GpuCache, GpuCacheHandle, GpuCacheUpdateList};
-use internal_types::{BatchTextures, CacheTextureId};
+use internal_types::BatchTextures;
use internal_types::{FastHashMap, SourceTexture};
use mask_cache::MaskCacheInfo;
use prim_store::{CLIP_DATA_GPU_BLOCKS, DeferredResolve, ImagePrimitiveKind, PrimitiveCacheKey};
use prim_store::{PrimitiveIndex, PrimitiveKind, PrimitiveMetadata, PrimitiveStore};
use profiler::FrameProfileCounters;
use render_task::{AlphaRenderItem, MaskGeometryKind, MaskSegment, RenderTask, RenderTaskData};
use render_task::{RenderTaskId, RenderTaskIndex, RenderTaskKey, RenderTaskKind};
use render_task::RenderTaskLocation;
use renderer::BlendMode;
use renderer::ImageBufferKind;
use resource_cache::ResourceCache;
use std::{f32, i32, mem, usize};
-use texture_cache::TexturePage;
+use texture_allocator::GuillotineAllocator;
use util::{TransformedRect, TransformedRectKind};
use api::{BuiltDisplayList, ClipAndScrollInfo, ClipId, ColorF, DeviceIntPoint, ImageKey};
use api::{DeviceIntRect, DeviceIntSize, DeviceUintPoint, DeviceUintSize, FontInstanceKey};
use api::{ExternalImageType, FilterOp, FontRenderMode, ImageRendering, LayerRect};
use api::{LayerToWorldTransform, MixBlendMode, PipelineId, PropertyBinding, TransformStyle};
use api::{TileOffset, WorldToLayerTransform, YuvColorSpace, YuvFormat, LayerVector2D};
// Special sentinel value recognized by the shader. It is considered to be
@@ -795,34 +795,34 @@ pub struct RenderTargetContext<'a> {
pub resource_cache: &'a ResourceCache,
}
struct TextureAllocator {
// TODO(gw): Replace this with a simpler allocator for
// render target allocation - this use case doesn't need
// to deal with coalescing etc that the general texture
// cache allocator requires.
- page_allocator: TexturePage,
+ allocator: GuillotineAllocator,
// Track the used rect of the render target, so that
// we can set a scissor rect and only clear to the
// used portion of the target as an optimization.
used_rect: DeviceIntRect,
}
impl TextureAllocator {
fn new(size: DeviceUintSize) -> TextureAllocator {
TextureAllocator {
- page_allocator: TexturePage::new(CacheTextureId(0), size),
+ allocator: GuillotineAllocator::new(size),
used_rect: DeviceIntRect::zero(),
}
}
fn allocate(&mut self, size: &DeviceUintSize) -> Option<DeviceUintPoint> {
- let origin = self.page_allocator.allocate(size);
+ let origin = self.allocator.allocate(size);
if let Some(origin) = origin {
// TODO(gw): We need to make all the device rects
// be consistent in the use of the
// DeviceIntRect and DeviceUintRect types!
let origin = DeviceIntPoint::new(origin.x as i32,
origin.y as i32);
let size = DeviceIntSize::new(size.width as i32,
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -165,16 +165,18 @@ pub enum ApiMsg {
// WebVR commands that must be called in the WebGL render thread.
VRCompositorCommand(WebGLContextId, VRCompositorCommand),
/// An opaque handle that must be passed to the render notifier. It is used by Gecko
/// to forward gecko-specific messages to the render thread preserving the ordering
/// within the other messages.
ExternalEvent(ExternalEvent),
/// Removes all resources associated with a namespace.
ClearNamespace(IdNamespace),
+ /// Flush from the caches anything that isn't necessary, to free some memory.
+ MemoryPressure,
ShutDown,
}
impl fmt::Debug for ApiMsg {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match *self {
ApiMsg::UpdateResources(..) => "ApiMsg::UpdateResources",
ApiMsg::GetGlyphDimensions(..) => "ApiMsg::GetGlyphDimensions",
@@ -184,16 +186,17 @@ impl fmt::Debug for ApiMsg {
ApiMsg::UpdateDocument(..) => "ApiMsg::UpdateDocument",
ApiMsg::DeleteDocument(..) => "ApiMsg::DeleteDocument",
ApiMsg::RequestWebGLContext(..) => "ApiMsg::RequestWebGLContext",
ApiMsg::ResizeWebGLContext(..) => "ApiMsg::ResizeWebGLContext",
ApiMsg::WebGLCommand(..) => "ApiMsg::WebGLCommand",
ApiMsg::VRCompositorCommand(..) => "ApiMsg::VRCompositorCommand",
ApiMsg::ExternalEvent(..) => "ApiMsg::ExternalEvent",
ApiMsg::ClearNamespace(..) => "ApiMsg::ClearNamespace",
+ ApiMsg::MemoryPressure => "ApiMsg::MemoryPressure",
ApiMsg::ShutDown => "ApiMsg::ShutDown",
})
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct Epoch(pub u32);
@@ -394,16 +397,20 @@ impl RenderApi {
self.api_sender.send(msg).unwrap();
}
pub fn send_external_event(&self, evt: ExternalEvent) {
let msg = ApiMsg::ExternalEvent(evt);
self.api_sender.send(msg).unwrap();
}
+ pub fn notify_memory_pressure(&self) {
+ self.api_sender.send(ApiMsg::MemoryPressure).unwrap();
+ }
+
pub fn shut_down(&self) {
self.api_sender.send(ApiMsg::ShutDown).unwrap();
}
/// Create a new unique key that can be used for
/// animated property bindings.
pub fn generate_property_binding_key<T: Copy>(&self) -> PropertyBindingKey<T> {
let new_id = self.next_unique_id();