auto merge of #476 : pcwalton/servo/display-list-refactor, r=pcwalton

r? @metajack
This commit is contained in:
bors-servo 2013-05-30 00:42:39 -07:00
commit 273b6cfe29
2 changed files with 201 additions and 139 deletions

View file

@ -2,129 +2,151 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use color::{Color, rgb}; //! Servo heavily uses display lists, which are retained-mode lists of rendering commands to
/// perform. Using a list instead of rendering elements in immediate mode allows transforms, hit
/// testing, and invalidation to be performed using the same primitives as painting. It also allows
/// Servo to aggressively cull invisible and out-of-bounds rendering elements, to reduce overdraw.
/// Finally, display lists allow tiles to be farmed out onto multiple CPUs and rendered in
/// parallel (although this benefit does not apply to GPU-based rendering).
///
/// Display items describe relatively high-level drawing operations (for example, entire borders
/// and shadows instead of lines and blur operations), to reduce the amount of allocation required.
/// They are therefore not exactly analogous to constructs like Skia pictures, which consist of
/// low-level drawing primitives.
use color::Color;
use geometry::Au; use geometry::Au;
use render_context::RenderContext; use render_context::RenderContext;
use text::SendableTextRun; use text::SendableTextRun;
use clone_arc = std::arc::clone; use geom::{Point2D, Rect, Size2D};
use geom::Rect;
use geom::{Point2D, Size2D};
use std::arc::ARC;
use servo_net::image::base::Image; use servo_net::image::base::Image;
use servo_util::range::Range; use servo_util::range::Range;
use std::arc::ARC;
use std::arc;
struct DisplayItemData { /// A list of rendering operations to be performed.
bounds : Rect<Au>, // TODO: whose coordinate system should this use?
}
pub impl DisplayItemData {
fn new(bounds: &Rect<Au>) -> DisplayItemData {
DisplayItemData { bounds: copy *bounds }
}
}
pub enum DisplayItem {
SolidColor(DisplayItemData, Color),
// TODO: need to provide spacing data for text run.
// (i.e, to support rendering of CSS 'word-spacing' and 'letter-spacing')
// TODO: don't copy text runs, ever.
Text(DisplayItemData, ~SendableTextRun, Range, Color),
Image(DisplayItemData, ARC<~Image>),
Border(DisplayItemData, Au, Color)
}
pub impl<'self> DisplayItem {
fn d(&'self self) -> &'self DisplayItemData {
match *self {
SolidColor(ref d, _) => d,
Text(ref d, _, _, _) => d,
Image(ref d, _) => d,
Border(ref d, _, _) => d
}
}
fn draw_into_context(&self, ctx: &RenderContext) {
match self {
&SolidColor(_, color) => {
ctx.draw_solid_color(&self.d().bounds, color)
}
&Text(_, ref run, ref range, color) => {
debug!("drawing text at %?", self.d().bounds);
let new_run = @run.deserialize(ctx.font_ctx);
let font = new_run.font;
let origin = self.d().bounds.origin;
let baseline_origin = Point2D(origin.x, origin.y + font.metrics.ascent);
font.draw_text_into_context(ctx, new_run, range, baseline_origin, color);
if(new_run.underline){
//TODO: Use the font metrics to properly position the underline bar
let width = self.d().bounds.size.width;
let u_size = font.metrics.underline_size;
let u_bounds = Rect(
Point2D(baseline_origin.x, baseline_origin.y),
Size2D(width, u_size)
);
ctx.draw_solid_color(&u_bounds, color);
}
},
&Image(_, ref img) => {
debug!("drawing image at %?", self.d().bounds);
ctx.draw_image(self.d().bounds, clone_arc(img));
}
&Border(_, width, color) => {
ctx.draw_border(&self.d().bounds, width, color)
}
}
debug!("%?", {
ctx.draw_border(&self.d().bounds, Au::from_px(1), rgb(150, 150, 150));
() });
}
fn new_SolidColor(bounds: &Rect<Au>, color: Color) -> DisplayItem {
SolidColor(DisplayItemData::new(bounds), color)
}
fn new_Border(bounds: &Rect<Au>, width: Au, color: Color) -> DisplayItem {
Border(DisplayItemData::new(bounds), width, color)
}
fn new_Text(bounds: &Rect<Au>,
run: ~SendableTextRun,
range: Range,
color: Color) -> DisplayItem {
Text(DisplayItemData::new(bounds), run, range, color)
}
// ARC should be cloned into ImageData, but Images are not sendable
fn new_Image(bounds: &Rect<Au>, image: ARC<~Image>) -> DisplayItem {
Image(DisplayItemData::new(bounds), image)
}
}
// Dual-mode/freezable.
pub struct DisplayList { pub struct DisplayList {
list: ~[~DisplayItem] priv list: ~[DisplayItem]
} }
pub impl DisplayList { impl DisplayList {
fn new() -> DisplayList { /// Creates a new display list.
DisplayList { list: ~[] } pub fn new() -> DisplayList {
DisplayList {
list: ~[]
}
} }
fn append_item(&mut self, item: ~DisplayItem) { /// Appends the given item to the display list.
pub fn append_item(&mut self, item: DisplayItem) {
// FIXME(Issue #150): crashes // FIXME(Issue #150): crashes
//debug!("Adding display item %u: %?", self.len(), item); //debug!("Adding display item %u: %?", self.len(), item);
self.list.push(item); self.list.push(item)
} }
fn draw_into_context(&self, ctx: &RenderContext) { /// Draws the display list into the given render context.
debug!("beginning display list"); pub fn draw_into_context(&self, render_context: &RenderContext) {
debug!("Beginning display list.");
for self.list.each |item| { for self.list.each |item| {
// FIXME(Issue #150): crashes // FIXME(Issue #150): crashes
//debug!("drawing %?", *item); //debug!("drawing %?", *item);
item.draw_into_context(ctx); item.draw_into_context(render_context)
} }
debug!("ending display list"); debug!("Ending display list.")
} }
} }
/// One drawing command in the list.
pub enum DisplayItem {
SolidColorDisplayItemClass(~SolidColorDisplayItem),
TextDisplayItemClass(~TextDisplayItem),
ImageDisplayItemClass(~ImageDisplayItem),
BorderDisplayItemClass(~BorderDisplayItem),
}
/// Information common to all display items.
pub struct BaseDisplayItem {
/// The boundaries of the display item.
///
/// TODO: Which coordinate system should this use?
bounds: Rect<Au>,
}
/// Renders a solid color.
pub struct SolidColorDisplayItem {
base: BaseDisplayItem,
color: Color,
}
/// Renders text.
pub struct TextDisplayItem {
base: BaseDisplayItem,
text_run: ~SendableTextRun,
range: Range,
color: Color,
}
/// Renders an image.
pub struct ImageDisplayItem {
base: BaseDisplayItem,
image: ARC<~Image>,
}
/// Renders a border.
pub struct BorderDisplayItem {
base: BaseDisplayItem,
/// The width of the border.
width: Au,
/// The color of the border.
color: Color,
}
impl DisplayItem {
/// Renders this display item into the given render context.
fn draw_into_context(&self, render_context: &RenderContext) {
match *self {
SolidColorDisplayItemClass(ref solid_color) => {
render_context.draw_solid_color(&solid_color.base.bounds, solid_color.color)
}
TextDisplayItemClass(ref text) => {
debug!("Drawing text at %?.", text.base.bounds);
// FIXME(pcwalton): Allocating? Why?
let new_run = @text.text_run.deserialize(render_context.font_ctx);
let font = new_run.font;
let origin = text.base.bounds.origin;
let baseline_origin = Point2D(origin.x, origin.y + font.metrics.ascent);
font.draw_text_into_context(render_context,
new_run,
&text.range,
baseline_origin,
text.color);
if new_run.underline {
// TODO(eatkinson): Use the font metrics to properly position the underline
// bar.
let width = text.base.bounds.size.width;
let underline_size = font.metrics.underline_size;
let underline_bounds = Rect(Point2D(baseline_origin.x, baseline_origin.y),
Size2D(width, underline_size));
render_context.draw_solid_color(&underline_bounds, text.color);
}
}
ImageDisplayItemClass(ref image_item) => {
debug!("Drawing image at %?.", image_item.base.bounds);
render_context.draw_image(image_item.base.bounds, image_item.image.clone())
}
BorderDisplayItemClass(ref border) => {
render_context.draw_border(&border.base.bounds, border.width, border.color)
}
}
}
}

View file

@ -14,7 +14,10 @@ use core::cell::Cell;
use core::cmp::ApproxEq; use core::cmp::ApproxEq;
use core::managed; use core::managed;
use geom::{Point2D, Rect, Size2D}; use geom::{Point2D, Rect, Size2D};
use gfx::display_list::{DisplayItem, DisplayList}; use gfx::display_list::{BaseDisplayItem, BorderDisplayItem, BorderDisplayItemClass};
use gfx::display_list::{DisplayList, ImageDisplayItem, ImageDisplayItemClass};
use gfx::display_list::{SolidColorDisplayItem, SolidColorDisplayItemClass, TextDisplayItem};
use gfx::display_list::{TextDisplayItemClass};
use gfx::font::{FontStyle, FontWeight300}; use gfx::font::{FontStyle, FontWeight300};
use gfx::geometry::Au; use gfx::geometry::Au;
use gfx::text::text_run::TextRun; use gfx::text::text_run::TextRun;
@ -31,7 +34,6 @@ use script::dom::node::{AbstractNode, LayoutView};
use servo_net::image::holder::ImageHolder; use servo_net::image::holder::ImageHolder;
use servo_net::local_image_cache::LocalImageCache; use servo_net::local_image_cache::LocalImageCache;
use servo_util::range::*; use servo_util::range::*;
use std::arc;
use std::net::url::Url; use std::net::url::Url;
/// Render boxes (`struct RenderBox`) are the leaves of the layout tree. They cannot position /// Render boxes (`struct RenderBox`) are the leaves of the layout tree. They cannot position
@ -567,26 +569,37 @@ pub impl RenderBox {
let nearest_ancestor_element = self.nearest_ancestor_element(); let nearest_ancestor_element = self.nearest_ancestor_element();
let color = nearest_ancestor_element.style().color().to_gfx_color(); let color = nearest_ancestor_element.style().color().to_gfx_color();
// FIXME: This should use `with_mut_ref` when that appears. // Create the text box.
let mut this_list = list.take(); do list.with_mut_ref |list| {
this_list.append_item(~DisplayItem::new_Text(&absolute_box_bounds, let text_display_item = ~TextDisplayItem {
~text_box.run.serialize(), base: BaseDisplayItem {
text_box.range, bounds: absolute_box_bounds,
color)); },
list.put_back(this_list); // FIXME(pcwalton): Allocation? Why?!
text_run: ~text_box.run.serialize(),
range: text_box.range,
color: color,
};
list.append_item(TextDisplayItemClass(text_display_item))
}
// Draw debug frames for text bounds. // Draw debug frames for text bounds.
// //
// FIXME(pcwalton): This is a bit of an abuse of the logging infrastructure. We // FIXME(pcwalton): This is a bit of an abuse of the logging infrastructure. We
// should have a real `SERVO_DEBUG` system. // should have a real `SERVO_DEBUG` system.
debug!("%?", { debug!("%?", {
// Compute the text box bounds. // Compute the text box bounds and draw a border surrounding them.
// do list.with_mut_ref |list| {
// FIXME: This should use `with_mut_ref` when that appears. let border_display_item = ~BorderDisplayItem {
let mut this_list = list.take(); base: BaseDisplayItem {
this_list.append_item(~DisplayItem::new_Border(&absolute_box_bounds, bounds: absolute_box_bounds,
Au::from_px(1), },
rgb(0, 0, 200).to_gfx_color())); width: Au::from_px(1),
color: rgb(0, 0, 200).to_gfx_color(),
};
list.append_item(BorderDisplayItemClass(border_display_item))
}
// Draw a rectangle representing the baselines. // Draw a rectangle representing the baselines.
// //
@ -596,10 +609,17 @@ pub impl RenderBox {
let baseline = Rect(absolute_box_bounds.origin + Point2D(Au(0), ascent), let baseline = Rect(absolute_box_bounds.origin + Point2D(Au(0), ascent),
Size2D(absolute_box_bounds.size.width, Au(0))); Size2D(absolute_box_bounds.size.width, Au(0)));
this_list.append_item(~DisplayItem::new_Border(&baseline, do list.with_mut_ref |list| {
Au::from_px(1), let border_display_item = ~BorderDisplayItem {
rgb(0, 200, 0).to_gfx_color())); base: BaseDisplayItem {
list.put_back(this_list); bounds: baseline,
},
width: Au::from_px(1),
color: rgb(0, 200, 0).to_gfx_color(),
};
list.append_item(BorderDisplayItemClass(border_display_item))
}
() ()
}); });
}, },
@ -611,11 +631,16 @@ pub impl RenderBox {
Some(image) => { Some(image) => {
debug!("(building display list) building image box"); debug!("(building display list) building image box");
// FIXME: This should use `with_mut_ref` when that appears. // Place the image into the display list.
let mut this_list = list.take(); do list.with_mut_ref |list| {
this_list.append_item(~DisplayItem::new_Image(&absolute_box_bounds, let image_display_item = ~ImageDisplayItem {
arc::clone(&image))); base: BaseDisplayItem {
list.put_back(this_list); bounds: absolute_box_bounds,
},
image: image.clone(),
};
list.append_item(ImageDisplayItemClass(image_display_item))
}
} }
None => { None => {
// No image data at all? Do nothing. // No image data at all? Do nothing.
@ -644,11 +669,18 @@ pub impl RenderBox {
// doesn't have a render box". // doesn't have a render box".
let nearest_ancestor_element = self.nearest_ancestor_element(); let nearest_ancestor_element = self.nearest_ancestor_element();
let bgcolor = nearest_ancestor_element.style().background_color(); let background_color = nearest_ancestor_element.style().background_color();
if !bgcolor.alpha.approx_eq(&0.0) { if !background_color.alpha.approx_eq(&0.0) {
let mut l = list.take(); // FIXME: use with_mut_ref when available do list.with_mut_ref |list| {
l.append_item(~DisplayItem::new_SolidColor(absolute_bounds, bgcolor.to_gfx_color())); let solid_color_display_item = ~SolidColorDisplayItem {
list.put_back(l); base: BaseDisplayItem {
bounds: *absolute_bounds,
},
color: background_color.to_gfx_color(),
};
list.append_item(SolidColorDisplayItemClass(solid_color_display_item))
}
} }
} }
@ -689,10 +721,18 @@ pub impl RenderBox {
let top_color = self.style().border_top_color(); let top_color = self.style().border_top_color();
let color = top_color.to_gfx_color(); // FIXME let color = top_color.to_gfx_color(); // FIXME
// FIXME: Use `with_mut_ref` when that works. // Append the border to the display list.
let mut this_list = list.take(); do list.with_mut_ref |list| {
this_list.append_item(~DisplayItem::new_Border(&bounds, border_width, color)); let border_display_item = ~BorderDisplayItem {
list.put_back(this_list); base: BaseDisplayItem {
bounds: bounds,
},
width: border_width,
color: color,
};
list.append_item(BorderDisplayItemClass(border_display_item))
}
} else { } else {
warn!("ignoring unimplemented border widths"); warn!("ignoring unimplemented border widths");
} }