Refactor display lists to use less memory.

This commit is contained in:
Patrick Walton 2013-05-28 18:51:57 -07:00
parent 0b22336c45
commit 6012a3e13e
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
* 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 render_context::RenderContext;
use text::SendableTextRun;
use clone_arc = std::arc::clone;
use geom::Rect;
use geom::{Point2D, Size2D};
use std::arc::ARC;
use geom::{Point2D, Rect, Size2D};
use servo_net::image::base::Image;
use servo_util::range::Range;
use std::arc::ARC;
use std::arc;
struct DisplayItemData {
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.
/// A list of rendering operations to be performed.
pub struct DisplayList {
list: ~[~DisplayItem]
priv list: ~[DisplayItem]
}
pub impl DisplayList {
fn new() -> DisplayList {
DisplayList { list: ~[] }
impl DisplayList {
/// Creates a new display 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
//debug!("Adding display item %u: %?", self.len(), item);
self.list.push(item);
self.list.push(item)
}
fn draw_into_context(&self, ctx: &RenderContext) {
debug!("beginning display list");
/// Draws the display list into the given render context.
pub fn draw_into_context(&self, render_context: &RenderContext) {
debug!("Beginning display list.");
for self.list.each |item| {
// FIXME(Issue #150): crashes
//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::managed;
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::geometry::Au;
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::local_image_cache::LocalImageCache;
use servo_util::range::*;
use std::arc;
use std::net::url::Url;
/// 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 color = nearest_ancestor_element.style().color().to_gfx_color();
// FIXME: This should use `with_mut_ref` when that appears.
let mut this_list = list.take();
this_list.append_item(~DisplayItem::new_Text(&absolute_box_bounds,
~text_box.run.serialize(),
text_box.range,
color));
list.put_back(this_list);
// Create the text box.
do list.with_mut_ref |list| {
let text_display_item = ~TextDisplayItem {
base: BaseDisplayItem {
bounds: absolute_box_bounds,
},
// 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.
//
// FIXME(pcwalton): This is a bit of an abuse of the logging infrastructure. We
// should have a real `SERVO_DEBUG` system.
debug!("%?", {
// Compute the text box bounds.
//
// FIXME: This should use `with_mut_ref` when that appears.
let mut this_list = list.take();
this_list.append_item(~DisplayItem::new_Border(&absolute_box_bounds,
Au::from_px(1),
rgb(0, 0, 200).to_gfx_color()));
debug!("%?", {
// Compute the text box bounds and draw a border surrounding them.
do list.with_mut_ref |list| {
let border_display_item = ~BorderDisplayItem {
base: BaseDisplayItem {
bounds: absolute_box_bounds,
},
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.
//
@ -596,10 +609,17 @@ pub impl RenderBox {
let baseline = Rect(absolute_box_bounds.origin + Point2D(Au(0), ascent),
Size2D(absolute_box_bounds.size.width, Au(0)));
this_list.append_item(~DisplayItem::new_Border(&baseline,
Au::from_px(1),
rgb(0, 200, 0).to_gfx_color()));
list.put_back(this_list);
do list.with_mut_ref |list| {
let border_display_item = ~BorderDisplayItem {
base: BaseDisplayItem {
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) => {
debug!("(building display list) building image box");
// FIXME: This should use `with_mut_ref` when that appears.
let mut this_list = list.take();
this_list.append_item(~DisplayItem::new_Image(&absolute_box_bounds,
arc::clone(&image)));
list.put_back(this_list);
// Place the image into the display list.
do list.with_mut_ref |list| {
let image_display_item = ~ImageDisplayItem {
base: BaseDisplayItem {
bounds: absolute_box_bounds,
},
image: image.clone(),
};
list.append_item(ImageDisplayItemClass(image_display_item))
}
}
None => {
// No image data at all? Do nothing.
@ -644,11 +669,18 @@ pub impl RenderBox {
// doesn't have a render box".
let nearest_ancestor_element = self.nearest_ancestor_element();
let bgcolor = nearest_ancestor_element.style().background_color();
if !bgcolor.alpha.approx_eq(&0.0) {
let mut l = list.take(); // FIXME: use with_mut_ref when available
l.append_item(~DisplayItem::new_SolidColor(absolute_bounds, bgcolor.to_gfx_color()));
list.put_back(l);
let background_color = nearest_ancestor_element.style().background_color();
if !background_color.alpha.approx_eq(&0.0) {
do list.with_mut_ref |list| {
let solid_color_display_item = ~SolidColorDisplayItem {
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 color = top_color.to_gfx_color(); // FIXME
// FIXME: Use `with_mut_ref` when that works.
let mut this_list = list.take();
this_list.append_item(~DisplayItem::new_Border(&bounds, border_width, color));
list.put_back(this_list);
// Append the border to the display list.
do list.with_mut_ref |list| {
let border_display_item = ~BorderDisplayItem {
base: BaseDisplayItem {
bounds: bounds,
},
width: border_width,
color: color,
};
list.append_item(BorderDisplayItemClass(border_display_item))
}
} else {
warn!("ignoring unimplemented border widths");
}