From 6012a3e13e3b912bedd19b479fe2ec8454ebf061 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 28 May 2013 18:51:57 -0700 Subject: [PATCH] Refactor display lists to use less memory. --- src/components/gfx/display_list.rs | 230 ++++++++++++++++------------- src/components/main/layout/box.rs | 110 +++++++++----- 2 files changed, 201 insertions(+), 139 deletions(-) diff --git a/src/components/gfx/display_list.rs b/src/components/gfx/display_list.rs index afda98af08e..6ceeef76a9c 100644 --- a/src/components/gfx/display_list.rs +++ b/src/components/gfx/display_list.rs @@ -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, // TODO: whose coordinate system should this use? -} - -pub impl DisplayItemData { - fn new(bounds: &Rect) -> 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, color: Color) -> DisplayItem { - SolidColor(DisplayItemData::new(bounds), color) - } - - fn new_Border(bounds: &Rect, width: Au, color: Color) -> DisplayItem { - Border(DisplayItemData::new(bounds), width, color) - } - - fn new_Text(bounds: &Rect, - 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, 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, +} + +/// 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) + } + } + } +} + diff --git a/src/components/main/layout/box.rs b/src/components/main/layout/box.rs index a2112882b06..a9f834f4d9c 100644 --- a/src/components/main/layout/box.rs +++ b/src/components/main/layout/box.rs @@ -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"); }