mirror of
https://github.com/servo/servo.git
synced 2025-08-06 22:15:33 +01:00
gfx: Rewrite display list construction to make stacking-contexts more
first-class. This implements the scheme described here: https://groups.google.com/forum/#!topic/mozilla.dev.servo/sZVPSfPVfkg This commit changes Servo to generate one display list per stacking context instead of one display list per layer. This is purely a refactoring; there are no functional changes. Performance is essentially the same as before. However, there should be numerous future benefits that this is intended to allow for: * It makes the code simpler to understand because the "new layer needed" vs. "no new layer needed" code paths are more consolidated. * It makes it easy to support CSS properties that did not fit into our previous flat display list model (without unconditionally layerizing them): o `opacity` should be easy to support because the stacking context provides the higher-level grouping of display items to which opacity is to be applied. o `transform` can be easily supported because the stacking context provides a place to stash the transformation matrix. This has the side benefit of nicely separating the transformation matrix from the clipping regions. * The `flatten` logic is now O(1) instead of O(n) and now only needs to be invoked for pseudo-stacking contexts (right now: just floats), instead of for every stacking context. * Layers are now a proper tree instead of a flat list as far as layout is concerned, bringing us closer to a production-quality compositing/layers framework. * This commit opens the door to incremental display list construction at the level of stacking contexts. Future performance improvements could come from optimizing allocation of display list items, and, of course, incremental display list construction.
This commit is contained in:
parent
0ab70dd539
commit
a4a9a46a87
13 changed files with 812 additions and 550 deletions
|
@ -15,7 +15,8 @@
|
|||
//! low-level drawing primitives.
|
||||
|
||||
use color::Color;
|
||||
use render_context::RenderContext;
|
||||
use display_list::optimizer::DisplayListOptimizer;
|
||||
use render_context::{RenderContext, ToAzureRect};
|
||||
use text::glyph::CharIndex;
|
||||
use text::TextRun;
|
||||
|
||||
|
@ -23,11 +24,16 @@ use azure::azure::AzFloat;
|
|||
use collections::dlist::{mod, DList};
|
||||
use geom::{Point2D, Rect, SideOffsets2D, Size2D, Matrix2D};
|
||||
use libc::uintptr_t;
|
||||
use render_task::RenderLayer;
|
||||
use script_traits::UntrustedNodeAddress;
|
||||
use servo_msg::compositor_msg::LayerId;
|
||||
use servo_net::image::base::Image;
|
||||
use servo_util::dlist as servo_dlist;
|
||||
use servo_util::geometry::Au;
|
||||
use servo_util::geometry::{mod, Au};
|
||||
use servo_util::range::Range;
|
||||
use servo_util::smallvec::{SmallVec, SmallVec8};
|
||||
use std::fmt;
|
||||
use std::mem;
|
||||
use std::slice::Items;
|
||||
use style::computed_values::border_style;
|
||||
use sync::Arc;
|
||||
|
@ -55,240 +61,308 @@ impl OpaqueNode {
|
|||
}
|
||||
}
|
||||
|
||||
/// "Steps" as defined by CSS 2.1 § E.2.
|
||||
#[deriving(Clone, PartialEq, Show)]
|
||||
pub enum StackingLevel {
|
||||
/// The border and backgrounds for the root of this stacking context: steps 1 and 2.
|
||||
BackgroundAndBordersStackingLevel,
|
||||
/// Borders and backgrounds for block-level descendants: step 4.
|
||||
BlockBackgroundsAndBordersStackingLevel,
|
||||
/// Floats: step 5. These are treated as pseudo-stacking contexts.
|
||||
FloatStackingLevel,
|
||||
/// All other content.
|
||||
ContentStackingLevel,
|
||||
/// Positioned descendant stacking contexts, along with their `z-index` levels.
|
||||
///
|
||||
/// TODO(pcwalton): `z-index` should be the actual CSS property value in order to handle
|
||||
/// `auto`, not just an integer.
|
||||
PositionedDescendantStackingLevel(i32)
|
||||
}
|
||||
|
||||
impl StackingLevel {
|
||||
#[inline]
|
||||
pub fn from_background_and_border_level(level: BackgroundAndBorderLevel) -> StackingLevel {
|
||||
match level {
|
||||
RootOfStackingContextLevel => BackgroundAndBordersStackingLevel,
|
||||
BlockLevel => BlockBackgroundsAndBordersStackingLevel,
|
||||
ContentLevel => ContentStackingLevel,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct StackingContext {
|
||||
/// The border and backgrounds for the root of this stacking context: steps 1 and 2.
|
||||
pub background_and_borders: DisplayList,
|
||||
/// Borders and backgrounds for block-level descendants: step 4.
|
||||
pub block_backgrounds_and_borders: DisplayList,
|
||||
/// Floats: step 5. These are treated as pseudo-stacking contexts.
|
||||
pub floats: DisplayList,
|
||||
/// All other content.
|
||||
pub content: DisplayList,
|
||||
/// Positioned descendant stacking contexts, along with their `z-index` levels.
|
||||
pub positioned_descendants: Vec<(i32, DisplayList)>,
|
||||
}
|
||||
|
||||
impl StackingContext {
|
||||
/// Creates a new empty stacking context.
|
||||
#[inline]
|
||||
fn new() -> StackingContext {
|
||||
StackingContext {
|
||||
background_and_borders: DisplayList::new(),
|
||||
block_backgrounds_and_borders: DisplayList::new(),
|
||||
floats: DisplayList::new(),
|
||||
content: DisplayList::new(),
|
||||
positioned_descendants: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes a stacking context from a display list, consuming that display list in the
|
||||
/// process.
|
||||
fn init_from_list(&mut self, list: &mut DisplayList) {
|
||||
while !list.list.is_empty() {
|
||||
let mut head = DisplayList::from_list(servo_dlist::split(&mut list.list));
|
||||
match head.front().unwrap().base().level {
|
||||
BackgroundAndBordersStackingLevel => {
|
||||
self.background_and_borders.append_from(&mut head)
|
||||
}
|
||||
BlockBackgroundsAndBordersStackingLevel => {
|
||||
self.block_backgrounds_and_borders.append_from(&mut head)
|
||||
}
|
||||
FloatStackingLevel => self.floats.append_from(&mut head),
|
||||
ContentStackingLevel => self.content.append_from(&mut head),
|
||||
PositionedDescendantStackingLevel(z_index) => {
|
||||
match self.positioned_descendants.iter_mut().find(|& &(z, _)| z_index == z) {
|
||||
Some(&(_, ref mut my_list)) => {
|
||||
my_list.append_from(&mut head);
|
||||
continue
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
self.positioned_descendants.push((z_index, head))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Which level to place backgrounds and borders in.
|
||||
pub enum BackgroundAndBorderLevel {
|
||||
RootOfStackingContextLevel,
|
||||
BlockLevel,
|
||||
ContentLevel,
|
||||
}
|
||||
|
||||
/// A list of rendering operations to be performed.
|
||||
#[deriving(Clone, Show)]
|
||||
/// Display items that make up a stacking context. "Steps" here refer to the steps in CSS 2.1
|
||||
/// Appendix E.
|
||||
///
|
||||
/// TODO(pcwalton): We could reduce the size of this structure with a more "skip list"-like
|
||||
/// structure, omitting several pointers and lengths.
|
||||
pub struct DisplayList {
|
||||
pub list: DList<DisplayItem>,
|
||||
}
|
||||
|
||||
pub enum DisplayListIterator<'a> {
|
||||
EmptyDisplayListIterator,
|
||||
ParentDisplayListIterator(Items<'a,DisplayList>),
|
||||
}
|
||||
|
||||
impl<'a> Iterator<&'a DisplayList> for DisplayListIterator<'a> {
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<&'a DisplayList> {
|
||||
match *self {
|
||||
EmptyDisplayListIterator => None,
|
||||
ParentDisplayListIterator(ref mut subiterator) => subiterator.next(),
|
||||
}
|
||||
}
|
||||
/// The border and backgrounds for the root of this stacking context: steps 1 and 2.
|
||||
pub background_and_borders: DList<DisplayItem>,
|
||||
/// Borders and backgrounds for block-level descendants: step 4.
|
||||
pub block_backgrounds_and_borders: DList<DisplayItem>,
|
||||
/// Floats: step 5. These are treated as pseudo-stacking contexts.
|
||||
pub floats: DList<DisplayItem>,
|
||||
/// All other content.
|
||||
pub content: DList<DisplayItem>,
|
||||
/// Child stacking contexts.
|
||||
pub children: DList<Arc<StackingContext>>,
|
||||
}
|
||||
|
||||
impl DisplayList {
|
||||
/// Creates a new display list.
|
||||
/// Creates a new, empty display list.
|
||||
#[inline]
|
||||
pub fn new() -> DisplayList {
|
||||
DisplayList {
|
||||
list: DList::new(),
|
||||
background_and_borders: DList::new(),
|
||||
block_backgrounds_and_borders: DList::new(),
|
||||
floats: DList::new(),
|
||||
content: DList::new(),
|
||||
children: DList::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new display list from the given list of display items.
|
||||
fn from_list(list: DList<DisplayItem>) -> DisplayList {
|
||||
DisplayList {
|
||||
list: list,
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends the given item to the display list.
|
||||
#[inline]
|
||||
pub fn push(&mut self, item: DisplayItem) {
|
||||
self.list.push_back(item);
|
||||
}
|
||||
|
||||
/// Appends the items in the given display list to this one, removing them in the process.
|
||||
/// Appends all display items from `other` into `self`, preserving stacking order and emptying
|
||||
/// `other` in the process.
|
||||
#[inline]
|
||||
pub fn append_from(&mut self, other: &mut DisplayList) {
|
||||
servo_dlist::append_from(&mut self.list, &mut other.list)
|
||||
servo_dlist::append_from(&mut self.background_and_borders,
|
||||
&mut other.background_and_borders);
|
||||
servo_dlist::append_from(&mut self.block_backgrounds_and_borders,
|
||||
&mut other.block_backgrounds_and_borders);
|
||||
servo_dlist::append_from(&mut self.floats, &mut other.floats);
|
||||
servo_dlist::append_from(&mut self.content, &mut other.content);
|
||||
servo_dlist::append_from(&mut self.children, &mut other.children);
|
||||
}
|
||||
|
||||
/// Returns the first display item in this list.
|
||||
/// Merges all display items from all non-float stacking levels to the `float` stacking level.
|
||||
#[inline]
|
||||
fn front(&self) -> Option<&DisplayItem> {
|
||||
self.list.front()
|
||||
pub fn form_float_pseudo_stacking_context(&mut self) {
|
||||
servo_dlist::prepend_from(&mut self.floats, &mut self.content);
|
||||
servo_dlist::prepend_from(&mut self.floats, &mut self.block_backgrounds_and_borders);
|
||||
servo_dlist::prepend_from(&mut self.floats, &mut self.background_and_borders);
|
||||
}
|
||||
|
||||
pub fn debug(&self) {
|
||||
for item in self.list.iter() {
|
||||
item.debug_with_level(0);
|
||||
/// Returns a list of all items in this display list concatenated together. This is extremely
|
||||
/// inefficient and should only be used for debugging.
|
||||
pub fn all_display_items(&self) -> Vec<DisplayItem> {
|
||||
let mut result = Vec::new();
|
||||
for display_item in self.background_and_borders.iter() {
|
||||
result.push((*display_item).clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws the display list into the given render context. The display list must be flattened
|
||||
/// first for correct painting.
|
||||
pub fn draw_into_context(&self,
|
||||
render_context: &mut RenderContext,
|
||||
current_transform: &Matrix2D<AzFloat>,
|
||||
current_clip_stack: &mut Vec<Rect<Au>>) {
|
||||
debug!("Beginning display list.");
|
||||
for item in self.list.iter() {
|
||||
item.draw_into_context(render_context, current_transform, current_clip_stack)
|
||||
for display_item in self.block_backgrounds_and_borders.iter() {
|
||||
result.push((*display_item).clone())
|
||||
}
|
||||
debug!("Ending display list.");
|
||||
for display_item in self.floats.iter() {
|
||||
result.push((*display_item).clone())
|
||||
}
|
||||
for display_item in self.content.iter() {
|
||||
result.push((*display_item).clone())
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a preorder iterator over the given display list.
|
||||
#[inline]
|
||||
pub fn iter<'a>(&'a self) -> DisplayItemIterator<'a> {
|
||||
ParentDisplayItemIterator(self.list.iter())
|
||||
}
|
||||
/// Represents one CSS stacking context, which may or may not have a hardware layer.
|
||||
pub struct StackingContext {
|
||||
/// The display items that make up this stacking context.
|
||||
pub display_list: Box<DisplayList>,
|
||||
/// The layer for this stacking context, if there is one.
|
||||
pub layer: Option<Arc<RenderLayer>>,
|
||||
/// The position and size of this stacking context.
|
||||
pub bounds: Rect<Au>,
|
||||
/// The clipping rect for this stacking context, in the coordinate system of the *parent*
|
||||
/// stacking context.
|
||||
pub clip_rect: Rect<Au>,
|
||||
/// The `z-index` for this stacking context.
|
||||
pub z_index: i32,
|
||||
}
|
||||
|
||||
/// Flattens a display list into a display list with a single stacking level according to the
|
||||
/// steps in CSS 2.1 § E.2.
|
||||
impl StackingContext {
|
||||
/// Creates a new stacking context.
|
||||
///
|
||||
/// This must be called before `draw_into_context()` is for correct results.
|
||||
pub fn flatten(&mut self, resulting_level: StackingLevel) {
|
||||
// Fast paths:
|
||||
if self.list.len() == 0 {
|
||||
return
|
||||
}
|
||||
if self.list.len() == 1 {
|
||||
self.set_stacking_level(resulting_level);
|
||||
return
|
||||
/// TODO(pcwalton): Stacking contexts should not always be clipped to their bounds, to handle
|
||||
/// overflow properly.
|
||||
#[inline]
|
||||
pub fn new(display_list: Box<DisplayList>,
|
||||
bounds: Rect<Au>,
|
||||
z_index: i32,
|
||||
layer: Option<Arc<RenderLayer>>)
|
||||
-> StackingContext {
|
||||
StackingContext {
|
||||
display_list: display_list,
|
||||
layer: layer,
|
||||
bounds: bounds,
|
||||
clip_rect: bounds,
|
||||
z_index: z_index,
|
||||
}
|
||||
}
|
||||
|
||||
let mut stacking_context = StackingContext::new();
|
||||
stacking_context.init_from_list(self);
|
||||
debug_assert!(self.list.is_empty());
|
||||
|
||||
// Steps 1 and 2: Borders and background for the root.
|
||||
self.append_from(&mut stacking_context.background_and_borders);
|
||||
/// Draws the stacking context in the proper order according to the steps in CSS 2.1 § E.2.
|
||||
pub fn optimize_and_draw_into_context(&self,
|
||||
render_context: &mut RenderContext,
|
||||
tile_bounds: &Rect<AzFloat>,
|
||||
current_transform: &Matrix2D<AzFloat>,
|
||||
current_clip_stack: &mut Vec<Rect<Au>>) {
|
||||
// Optimize the display list to throw out out-of-bounds display items and so forth.
|
||||
let display_list = DisplayListOptimizer::new(tile_bounds).optimize(&*self.display_list);
|
||||
|
||||
// Sort positioned children according to z-index.
|
||||
stacking_context.positioned_descendants.sort_by(|&(z_index_a, _), &(z_index_b, _)| {
|
||||
z_index_a.cmp(&z_index_b)
|
||||
});
|
||||
let mut positioned_children = SmallVec8::new();
|
||||
for kid in display_list.children.iter() {
|
||||
positioned_children.push((*kid).clone());
|
||||
}
|
||||
positioned_children.as_slice_mut().sort_by(|this, other| this.z_index.cmp(&other.z_index));
|
||||
|
||||
// Steps 1 and 2: Borders and background for the root.
|
||||
for display_item in display_list.background_and_borders.iter() {
|
||||
display_item.draw_into_context(render_context, current_transform, current_clip_stack)
|
||||
}
|
||||
|
||||
// Step 3: Positioned descendants with negative z-indices.
|
||||
for &(ref mut z_index, ref mut list) in stacking_context.positioned_descendants.iter_mut() {
|
||||
if *z_index < 0 {
|
||||
self.append_from(list)
|
||||
for positioned_kid in positioned_children.iter() {
|
||||
if positioned_kid.z_index >= 0 {
|
||||
break
|
||||
}
|
||||
if positioned_kid.layer.is_none() {
|
||||
let new_transform =
|
||||
current_transform.translate(positioned_kid.bounds.origin.x.to_nearest_px()
|
||||
as AzFloat,
|
||||
positioned_kid.bounds.origin.y.to_nearest_px()
|
||||
as AzFloat);
|
||||
let new_tile_rect =
|
||||
self.compute_tile_rect_for_child_stacking_context(tile_bounds,
|
||||
&**positioned_kid);
|
||||
positioned_kid.optimize_and_draw_into_context(render_context,
|
||||
&new_tile_rect,
|
||||
&new_transform,
|
||||
current_clip_stack);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Block backgrounds and borders.
|
||||
self.append_from(&mut stacking_context.block_backgrounds_and_borders);
|
||||
for display_item in display_list.block_backgrounds_and_borders.iter() {
|
||||
display_item.draw_into_context(render_context, current_transform, current_clip_stack)
|
||||
}
|
||||
|
||||
// Step 5: Floats.
|
||||
self.append_from(&mut stacking_context.floats);
|
||||
for display_item in display_list.floats.iter() {
|
||||
display_item.draw_into_context(render_context, current_transform, current_clip_stack)
|
||||
}
|
||||
|
||||
// TODO(pcwalton): Step 6: Inlines that generate stacking contexts.
|
||||
|
||||
// Step 7: Content.
|
||||
self.append_from(&mut stacking_context.content);
|
||||
for display_item in display_list.content.iter() {
|
||||
display_item.draw_into_context(render_context, current_transform, current_clip_stack)
|
||||
}
|
||||
|
||||
// Steps 8 and 9: Positioned descendants with nonnegative z-indices.
|
||||
for &(ref mut z_index, ref mut list) in stacking_context.positioned_descendants.iter_mut() {
|
||||
if *z_index >= 0 {
|
||||
self.append_from(list)
|
||||
for positioned_kid in positioned_children.iter() {
|
||||
if positioned_kid.z_index < 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if positioned_kid.layer.is_none() {
|
||||
let new_transform =
|
||||
current_transform.translate(positioned_kid.bounds.origin.x.to_nearest_px()
|
||||
as AzFloat,
|
||||
positioned_kid.bounds.origin.y.to_nearest_px()
|
||||
as AzFloat);
|
||||
let new_tile_rect =
|
||||
self.compute_tile_rect_for_child_stacking_context(tile_bounds,
|
||||
&**positioned_kid);
|
||||
positioned_kid.optimize_and_draw_into_context(render_context,
|
||||
&new_tile_rect,
|
||||
&new_transform,
|
||||
current_clip_stack);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(pcwalton): Step 10: Outlines.
|
||||
|
||||
self.set_stacking_level(resulting_level);
|
||||
}
|
||||
|
||||
/// Sets the stacking level for this display list and all its subitems.
|
||||
fn set_stacking_level(&mut self, new_level: StackingLevel) {
|
||||
for item in self.list.iter_mut() {
|
||||
item.mut_base().level = new_level;
|
||||
/// Translate the given tile rect into the coordinate system of a child stacking context.
|
||||
fn compute_tile_rect_for_child_stacking_context(&self,
|
||||
tile_bounds: &Rect<AzFloat>,
|
||||
child_stacking_context: &StackingContext)
|
||||
-> Rect<AzFloat> {
|
||||
static ZERO_AZURE_RECT: Rect<f32> = Rect {
|
||||
origin: Point2D {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
},
|
||||
size: Size2D {
|
||||
width: 0.0,
|
||||
height: 0.0
|
||||
}
|
||||
};
|
||||
|
||||
let child_stacking_context_bounds = child_stacking_context.bounds.to_azure_rect();
|
||||
let tile_subrect = tile_bounds.intersection(&child_stacking_context_bounds)
|
||||
.unwrap_or(ZERO_AZURE_RECT);
|
||||
let offset = tile_subrect.origin - child_stacking_context_bounds.origin;
|
||||
Rect(offset, tile_subrect.size)
|
||||
}
|
||||
|
||||
/// Places all nodes containing the point of interest into `result`, topmost first. If
|
||||
/// `topmost_only` is true, stops after placing one node into the list. `result` must be empty
|
||||
/// upon entry to this function.
|
||||
pub fn hit_test(&self,
|
||||
point: Point2D<Au>,
|
||||
result: &mut Vec<UntrustedNodeAddress>,
|
||||
topmost_only: bool) {
|
||||
fn hit_test_in_list<'a,I>(point: Point2D<Au>,
|
||||
result: &mut Vec<UntrustedNodeAddress>,
|
||||
topmost_only: bool,
|
||||
mut iterator: I)
|
||||
where I: Iterator<&'a DisplayItem> {
|
||||
for item in iterator {
|
||||
if geometry::rect_contains_point(item.base().clip_rect, point) &&
|
||||
geometry::rect_contains_point(item.bounds(), point) {
|
||||
result.push(item.base().node.to_untrusted_node_address());
|
||||
if topmost_only {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug_assert!(!topmost_only || result.is_empty());
|
||||
|
||||
// Iterate through display items in reverse stacking order. Steps here refer to the
|
||||
// painting steps in CSS 2.1 Appendix E.
|
||||
//
|
||||
// Steps 9 and 8: Positioned descendants with nonnegative z-indices.
|
||||
for kid in self.display_list.children.iter().rev() {
|
||||
if kid.z_index < 0 {
|
||||
continue
|
||||
}
|
||||
kid.hit_test(point, result, topmost_only);
|
||||
if topmost_only && !result.is_empty() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Steps 7, 5, and 4: Content, floats, and block backgrounds and borders.
|
||||
//
|
||||
// TODO(pcwalton): Step 6: Inlines that generate stacking contexts.
|
||||
for display_list in [
|
||||
&self.display_list.content,
|
||||
&self.display_list.floats,
|
||||
&self.display_list.block_backgrounds_and_borders,
|
||||
].iter() {
|
||||
hit_test_in_list(point, result, topmost_only, display_list.iter().rev());
|
||||
if topmost_only && !result.is_empty() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Positioned descendants with negative z-indices.
|
||||
for kid in self.display_list.children.iter().rev() {
|
||||
if kid.z_index >= 0 {
|
||||
continue
|
||||
}
|
||||
kid.hit_test(point, result, topmost_only);
|
||||
if topmost_only && !result.is_empty() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Steps 2 and 1: Borders and background for the root.
|
||||
hit_test_in_list(point,
|
||||
result,
|
||||
topmost_only,
|
||||
self.display_list.background_and_borders.iter().rev())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the stacking context in the given tree of stacking contexts with a specific layer ID.
|
||||
pub fn find_stacking_context_with_layer_id(this: &Arc<StackingContext>, layer_id: LayerId)
|
||||
-> Option<Arc<StackingContext>> {
|
||||
match this.layer {
|
||||
Some(ref layer) if layer.id == layer_id => return Some((*this).clone()),
|
||||
Some(_) | None => {}
|
||||
}
|
||||
|
||||
for kid in this.display_list.children.iter() {
|
||||
match find_stacking_context_with_layer_id(kid, layer_id) {
|
||||
Some(stacking_context) => return Some(stacking_context),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// One drawing command in the list.
|
||||
|
@ -318,9 +392,6 @@ pub struct BaseDisplayItem {
|
|||
/// The originating DOM node.
|
||||
pub node: OpaqueNode,
|
||||
|
||||
/// The stacking level in which this display item lives.
|
||||
pub level: StackingLevel,
|
||||
|
||||
/// The rectangle to clip to.
|
||||
///
|
||||
/// TODO(pcwalton): Eventually, to handle `border-radius`, this will (at least) need to grow
|
||||
|
@ -330,12 +401,10 @@ pub struct BaseDisplayItem {
|
|||
|
||||
impl BaseDisplayItem {
|
||||
#[inline(always)]
|
||||
pub fn new(bounds: Rect<Au>, node: OpaqueNode, level: StackingLevel, clip_rect: Rect<Au>)
|
||||
-> BaseDisplayItem {
|
||||
pub fn new(bounds: Rect<Au>, node: OpaqueNode, clip_rect: Rect<Au>) -> BaseDisplayItem {
|
||||
BaseDisplayItem {
|
||||
bounds: bounds,
|
||||
node: node,
|
||||
level: level,
|
||||
clip_rect: clip_rect,
|
||||
}
|
||||
}
|
||||
|
@ -450,9 +519,6 @@ impl DisplayItem {
|
|||
render_context: &mut RenderContext,
|
||||
current_transform: &Matrix2D<AzFloat>,
|
||||
current_clip_stack: &mut Vec<Rect<Au>>) {
|
||||
// This should have been flattened to the content stacking level first.
|
||||
assert!(self.base().level == ContentStackingLevel);
|
||||
|
||||
// TODO(pcwalton): This will need some tweaking to deal with more complex clipping regions.
|
||||
let clip_rect = &self.base().clip_rect;
|
||||
if current_clip_stack.len() == 0 || current_clip_stack.last().unwrap() != clip_rect {
|
||||
|
@ -464,6 +530,8 @@ impl DisplayItem {
|
|||
current_clip_stack.push(*clip_rect);
|
||||
}
|
||||
|
||||
render_context.draw_target.set_transform(current_transform);
|
||||
|
||||
match *self {
|
||||
SolidColorDisplayItemClass(ref solid_color) => {
|
||||
render_context.draw_solid_color(&solid_color.base.bounds, solid_color.color)
|
||||
|
@ -558,7 +626,7 @@ impl DisplayItem {
|
|||
|
||||
impl fmt::Show for DisplayItem {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{} @ {} ({:x}) [{}]",
|
||||
write!(f, "{} @ {} ({:x})",
|
||||
match *self {
|
||||
SolidColorDisplayItemClass(_) => "SolidColor",
|
||||
TextDisplayItemClass(_) => "Text",
|
||||
|
@ -569,9 +637,25 @@ impl fmt::Show for DisplayItem {
|
|||
PseudoDisplayItemClass(_) => "Pseudo",
|
||||
},
|
||||
self.base().bounds,
|
||||
self.base().node.id(),
|
||||
self.base().level
|
||||
self.base().node.id()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait OpaqueNodeMethods {
|
||||
/// Converts this node to an `UntrustedNodeAddress`. An `UntrustedNodeAddress` is just the type
|
||||
/// of node that script expects to receive in a hit test.
|
||||
fn to_untrusted_node_address(&self) -> UntrustedNodeAddress;
|
||||
}
|
||||
|
||||
|
||||
impl OpaqueNodeMethods for OpaqueNode {
|
||||
fn to_untrusted_node_address(&self) -> UntrustedNodeAddress {
|
||||
unsafe {
|
||||
let OpaqueNode(addr) = *self;
|
||||
let addr: UntrustedNodeAddress = mem::transmute(addr);
|
||||
addr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,52 +2,66 @@
|
|||
* 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 display_list::{DisplayItem, DisplayList};
|
||||
//! Transforms a display list to produce a visually-equivalent, but cheaper-to-render, one.
|
||||
|
||||
use display_list::{DisplayItem, DisplayList, StackingContext};
|
||||
|
||||
use collections::dlist::DList;
|
||||
use geom::rect::Rect;
|
||||
use servo_util::geometry::Au;
|
||||
use servo_util::geometry::{mod, Au};
|
||||
use sync::Arc;
|
||||
|
||||
/// Transforms a display list to produce a visually-equivalent, but cheaper-to-render, one.
|
||||
pub struct DisplayListOptimizer {
|
||||
display_list: Arc<DisplayList>,
|
||||
/// The visible rect in page coordinates.
|
||||
visible_rect: Rect<Au>,
|
||||
}
|
||||
|
||||
impl DisplayListOptimizer {
|
||||
/// `visible_rect` specifies the visible rect in page coordinates.
|
||||
pub fn new(display_list: Arc<DisplayList>, visible_rect: Rect<Au>) -> DisplayListOptimizer {
|
||||
/// Creates a new display list optimizer object. `visible_rect` specifies the visible rect in
|
||||
/// page coordinates.
|
||||
pub fn new(visible_rect: &Rect<f32>) -> DisplayListOptimizer {
|
||||
DisplayListOptimizer {
|
||||
display_list: display_list,
|
||||
visible_rect: visible_rect,
|
||||
visible_rect: geometry::f32_rect_to_au_rect(*visible_rect),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn optimize(self) -> DisplayList {
|
||||
self.process_display_list(&*self.display_list)
|
||||
/// Optimizes the given display list, returning an equivalent, but cheaper-to-paint, one.
|
||||
pub fn optimize(self, display_list: &DisplayList) -> DisplayList {
|
||||
let mut result = DisplayList::new();
|
||||
self.add_in_bounds_display_items(&mut result.background_and_borders,
|
||||
display_list.background_and_borders.iter());
|
||||
self.add_in_bounds_display_items(&mut result.block_backgrounds_and_borders,
|
||||
display_list.block_backgrounds_and_borders.iter());
|
||||
self.add_in_bounds_display_items(&mut result.floats, display_list.floats.iter());
|
||||
self.add_in_bounds_display_items(&mut result.content, display_list.content.iter());
|
||||
self.add_in_bounds_stacking_contexts(&mut result.children, display_list.children.iter());
|
||||
result
|
||||
}
|
||||
|
||||
fn process_display_list(&self, display_list: &DisplayList) -> DisplayList {
|
||||
let mut result = DList::new();
|
||||
for item in display_list.iter() {
|
||||
match self.process_display_item(item) {
|
||||
None => {}
|
||||
Some(display_item) => result.push_back(display_item),
|
||||
/// Adds display items that intersect the visible rect to `result_list`.
|
||||
fn add_in_bounds_display_items<'a,I>(&self,
|
||||
result_list: &mut DList<DisplayItem>,
|
||||
mut display_items: I)
|
||||
where I: Iterator<&'a DisplayItem> {
|
||||
for display_item in display_items {
|
||||
if self.visible_rect.intersects(&display_item.base().bounds) &&
|
||||
self.visible_rect.intersects(&display_item.base().clip_rect) {
|
||||
result_list.push_back((*display_item).clone())
|
||||
}
|
||||
}
|
||||
DisplayList {
|
||||
list: result,
|
||||
}
|
||||
}
|
||||
|
||||
fn process_display_item(&self, display_item: &DisplayItem) -> Option<DisplayItem> {
|
||||
// Eliminate display items outside the visible region.
|
||||
if !self.visible_rect.intersects(&display_item.base().bounds) ||
|
||||
!self.visible_rect.intersects(&display_item.base().clip_rect) {
|
||||
None
|
||||
} else {
|
||||
Some((*display_item).clone())
|
||||
/// Adds child stacking contexts whose boundaries intersect the visible rect to `result_list`.
|
||||
fn add_in_bounds_stacking_contexts<'a,I>(&self,
|
||||
result_list: &mut DList<Arc<StackingContext>>,
|
||||
mut stacking_contexts: I)
|
||||
where I: Iterator<&'a Arc<StackingContext>> {
|
||||
for stacking_context in stacking_contexts {
|
||||
if self.visible_rect.intersects(&stacking_context.bounds) &&
|
||||
self.visible_rect.intersects(&stacking_context.clip_rect) {
|
||||
result_list.push_back((*stacking_context).clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ extern crate native;
|
|||
extern crate rustrt;
|
||||
extern crate stb_image;
|
||||
extern crate png;
|
||||
extern crate script_traits;
|
||||
extern crate serialize;
|
||||
extern crate unicode;
|
||||
#[phase(plugin)]
|
||||
|
|
|
@ -467,7 +467,7 @@ impl<'a> RenderContext<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
trait ToAzurePoint {
|
||||
pub trait ToAzurePoint {
|
||||
fn to_azure_point(&self) -> Point2D<AzFloat>;
|
||||
}
|
||||
|
||||
|
@ -477,7 +477,7 @@ impl ToAzurePoint for Point2D<Au> {
|
|||
}
|
||||
}
|
||||
|
||||
trait ToAzureRect {
|
||||
pub trait ToAzureRect {
|
||||
fn to_azure_rect(&self) -> Rect<AzFloat>;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
//! The task that handles all rendering/painting.
|
||||
|
||||
use buffer_map::BufferMap;
|
||||
use display_list::optimizer::DisplayListOptimizer;
|
||||
use display_list::DisplayList;
|
||||
use display_list::{mod, StackingContext};
|
||||
use font_cache_task::FontCacheTask;
|
||||
use font_context::FontContext;
|
||||
use render_context::RenderContext;
|
||||
|
@ -27,9 +26,9 @@ use servo_msg::compositor_msg::{LayerMetadata, RenderListener, RenderingRenderSt
|
|||
use servo_msg::constellation_msg::{ConstellationChan, Failure, FailureMsg, PipelineId};
|
||||
use servo_msg::constellation_msg::{RendererReadyMsg};
|
||||
use servo_msg::platform::surface::NativeSurfaceAzureMethods;
|
||||
use servo_util::geometry;
|
||||
use servo_util::geometry::{Au, ZERO_POINT};
|
||||
use servo_util::opts;
|
||||
use servo_util::smallvec::{SmallVec, SmallVec1};
|
||||
use servo_util::smallvec::SmallVec;
|
||||
use servo_util::task::spawn_named_with_send_on_failure;
|
||||
use servo_util::task_state;
|
||||
use servo_util::time::{TimeProfilerChan, profile};
|
||||
|
@ -39,21 +38,28 @@ use std::mem;
|
|||
use std::task::TaskBuilder;
|
||||
use sync::Arc;
|
||||
|
||||
/// Information about a layer that layout sends to the painting task.
|
||||
/// Information about a hardware graphics layer that layout sends to the painting task.
|
||||
#[deriving(Clone)]
|
||||
pub struct RenderLayer {
|
||||
/// A per-pipeline ID describing this layer that should be stable across reflows.
|
||||
pub id: LayerId,
|
||||
/// The display list describing the contents of this layer.
|
||||
pub display_list: Arc<DisplayList>,
|
||||
/// The position of the layer in pixels.
|
||||
pub position: Rect<uint>,
|
||||
/// The color of the background in this layer. Used for unrendered content.
|
||||
pub background_color: Color,
|
||||
/// The scrolling policy of this layer.
|
||||
pub scroll_policy: ScrollPolicy,
|
||||
}
|
||||
|
||||
impl RenderLayer {
|
||||
/// Creates a new `RenderLayer`.
|
||||
pub fn new(id: LayerId, background_color: Color, scroll_policy: ScrollPolicy) -> RenderLayer {
|
||||
RenderLayer {
|
||||
id: id,
|
||||
background_color: background_color,
|
||||
scroll_policy: scroll_policy,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RenderRequest {
|
||||
pub buffer_requests: Vec<BufferRequest>,
|
||||
pub scale: f32,
|
||||
|
@ -62,7 +68,7 @@ pub struct RenderRequest {
|
|||
}
|
||||
|
||||
pub enum Msg {
|
||||
RenderInitMsg(SmallVec1<RenderLayer>),
|
||||
RenderInitMsg(Arc<StackingContext>),
|
||||
RenderMsg(Vec<RenderRequest>),
|
||||
UnusedBufferMsg(Vec<Box<LayerBuffer>>),
|
||||
PaintPermissionGranted,
|
||||
|
@ -102,8 +108,8 @@ pub struct RenderTask<C> {
|
|||
/// The native graphics context.
|
||||
native_graphics_context: Option<NativePaintingGraphicsContext>,
|
||||
|
||||
/// The layers to be rendered.
|
||||
render_layers: SmallVec1<RenderLayer>,
|
||||
/// The root stacking context sent to us by the layout thread.
|
||||
root_stacking_context: Option<Arc<StackingContext>>,
|
||||
|
||||
/// Permission to send paint messages to the compositor
|
||||
paint_permission: bool,
|
||||
|
@ -129,17 +135,36 @@ macro_rules! native_graphics_context(
|
|||
fn initialize_layers<C>(compositor: &mut C,
|
||||
pipeline_id: PipelineId,
|
||||
epoch: Epoch,
|
||||
render_layers: &[RenderLayer])
|
||||
root_stacking_context: &StackingContext)
|
||||
where C: RenderListener {
|
||||
let metadata = render_layers.iter().map(|render_layer| {
|
||||
LayerMetadata {
|
||||
id: render_layer.id,
|
||||
position: render_layer.position,
|
||||
background_color: render_layer.background_color,
|
||||
scroll_policy: render_layer.scroll_policy,
|
||||
}
|
||||
}).collect();
|
||||
let mut metadata = Vec::new();
|
||||
build(&mut metadata, root_stacking_context, &ZERO_POINT);
|
||||
compositor.initialize_layers_for_pipeline(pipeline_id, metadata, epoch);
|
||||
|
||||
fn build(metadata: &mut Vec<LayerMetadata>,
|
||||
stacking_context: &StackingContext,
|
||||
page_position: &Point2D<Au>) {
|
||||
let page_position = stacking_context.bounds.origin + *page_position;
|
||||
match stacking_context.layer {
|
||||
None => {}
|
||||
Some(ref render_layer) => {
|
||||
metadata.push(LayerMetadata {
|
||||
id: render_layer.id,
|
||||
position:
|
||||
Rect(Point2D(page_position.x.to_nearest_px() as uint,
|
||||
page_position.y.to_nearest_px() as uint),
|
||||
Size2D(stacking_context.bounds.size.width.to_nearest_px() as uint,
|
||||
stacking_context.bounds.size.height.to_nearest_px() as uint)),
|
||||
background_color: render_layer.background_color,
|
||||
scroll_policy: render_layer.scroll_policy,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for kid in stacking_context.display_list.children.iter() {
|
||||
build(metadata, &**kid, &page_position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> RenderTask<C> where C: RenderListener + Send {
|
||||
|
@ -170,11 +195,8 @@ impl<C> RenderTask<C> where C: RenderListener + Send {
|
|||
compositor: compositor,
|
||||
constellation_chan: constellation_chan,
|
||||
time_profiler_chan: time_profiler_chan,
|
||||
|
||||
native_graphics_context: native_graphics_context,
|
||||
|
||||
render_layers: SmallVec1::new(),
|
||||
|
||||
root_stacking_context: None,
|
||||
paint_permission: false,
|
||||
epoch: Epoch(0),
|
||||
buffer_map: BufferMap::new(10000000),
|
||||
|
@ -205,9 +227,9 @@ impl<C> RenderTask<C> where C: RenderListener + Send {
|
|||
|
||||
loop {
|
||||
match self.port.recv() {
|
||||
RenderInitMsg(render_layers) => {
|
||||
RenderInitMsg(stacking_context) => {
|
||||
self.epoch.next();
|
||||
self.render_layers = render_layers;
|
||||
self.root_stacking_context = Some(stacking_context.clone());
|
||||
|
||||
if !self.paint_permission {
|
||||
debug!("render_task: render ready msg");
|
||||
|
@ -219,7 +241,7 @@ impl<C> RenderTask<C> where C: RenderListener + Send {
|
|||
initialize_layers(&mut self.compositor,
|
||||
self.id,
|
||||
self.epoch,
|
||||
self.render_layers.as_slice());
|
||||
&*stacking_context);
|
||||
}
|
||||
RenderMsg(requests) => {
|
||||
if !self.paint_permission {
|
||||
|
@ -254,15 +276,15 @@ impl<C> RenderTask<C> where C: RenderListener + Send {
|
|||
PaintPermissionGranted => {
|
||||
self.paint_permission = true;
|
||||
|
||||
// Here we assume that the main layer—the layer responsible for the page size—
|
||||
// is the first layer. This is a pretty fragile assumption. It will be fixed
|
||||
// once we use the layers-based scrolling infrastructure for all scrolling.
|
||||
if self.render_layers.len() > 1 {
|
||||
self.epoch.next();
|
||||
initialize_layers(&mut self.compositor,
|
||||
self.id,
|
||||
self.epoch,
|
||||
self.render_layers.as_slice());
|
||||
match self.root_stacking_context {
|
||||
None => {}
|
||||
Some(ref stacking_context) => {
|
||||
self.epoch.next();
|
||||
initialize_layers(&mut self.compositor,
|
||||
self.id,
|
||||
self.epoch,
|
||||
&**stacking_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
PaintPermissionRevoked => {
|
||||
|
@ -327,9 +349,15 @@ impl<C> RenderTask<C> where C: RenderListener + Send {
|
|||
scale: f32,
|
||||
layer_id: LayerId) {
|
||||
time::profile(time::PaintingCategory, None, self.time_profiler_chan.clone(), || {
|
||||
// Bail out if there is no appropriate render layer.
|
||||
let render_layer = match self.render_layers.iter().find(|layer| layer.id == layer_id) {
|
||||
Some(render_layer) => (*render_layer).clone(),
|
||||
// Bail out if there is no appropriate stacking context.
|
||||
let stacking_context = match self.root_stacking_context {
|
||||
Some(ref stacking_context) => {
|
||||
match display_list::find_stacking_context_with_layer_id(stacking_context,
|
||||
layer_id) {
|
||||
Some(stacking_context) => stacking_context,
|
||||
None => return,
|
||||
}
|
||||
}
|
||||
None => return,
|
||||
};
|
||||
|
||||
|
@ -342,7 +370,7 @@ impl<C> RenderTask<C> where C: RenderListener + Send {
|
|||
let layer_buffer = self.find_or_create_layer_buffer_for_tile(&tile, scale);
|
||||
self.worker_threads[thread_id].paint_tile(tile,
|
||||
layer_buffer,
|
||||
render_layer.clone(),
|
||||
stacking_context.clone(),
|
||||
scale);
|
||||
}
|
||||
let new_buffers = Vec::from_fn(tile_count, |i| {
|
||||
|
@ -397,9 +425,9 @@ impl WorkerThreadProxy {
|
|||
fn paint_tile(&mut self,
|
||||
tile: BufferRequest,
|
||||
layer_buffer: Option<Box<LayerBuffer>>,
|
||||
render_layer: RenderLayer,
|
||||
stacking_context: Arc<StackingContext>,
|
||||
scale: f32) {
|
||||
self.sender.send(PaintTileMsgToWorkerThread(tile, layer_buffer, render_layer, scale))
|
||||
self.sender.send(PaintTileMsgToWorkerThread(tile, layer_buffer, stacking_context, scale))
|
||||
}
|
||||
|
||||
fn get_painted_tile_buffer(&mut self) -> Box<LayerBuffer> {
|
||||
|
@ -443,8 +471,8 @@ impl WorkerThread {
|
|||
loop {
|
||||
match self.receiver.recv() {
|
||||
ExitMsgToWorkerThread => break,
|
||||
PaintTileMsgToWorkerThread(tile, layer_buffer, render_layer, scale) => {
|
||||
let draw_target = self.optimize_and_paint_tile(&tile, render_layer, scale);
|
||||
PaintTileMsgToWorkerThread(tile, layer_buffer, stacking_context, scale) => {
|
||||
let draw_target = self.optimize_and_paint_tile(&tile, stacking_context, scale);
|
||||
let buffer = self.create_layer_buffer_for_painted_tile(&tile,
|
||||
layer_buffer,
|
||||
draw_target,
|
||||
|
@ -457,21 +485,9 @@ impl WorkerThread {
|
|||
|
||||
fn optimize_and_paint_tile(&mut self,
|
||||
tile: &BufferRequest,
|
||||
render_layer: RenderLayer,
|
||||
stacking_context: Arc<StackingContext>,
|
||||
scale: f32)
|
||||
-> DrawTarget {
|
||||
// page_rect is in coordinates relative to the layer origin, but all display list
|
||||
// components are relative to the page origin. We make page_rect relative to
|
||||
// the page origin before passing it to the optimizer.
|
||||
let page_rect = tile.page_rect.translate(&Point2D(render_layer.position.origin.x as f32,
|
||||
render_layer.position.origin.y as f32));
|
||||
let page_rect_au = geometry::f32_rect_to_au_rect(page_rect);
|
||||
|
||||
// Optimize the display list for this tile.
|
||||
let optimizer = DisplayListOptimizer::new(render_layer.display_list.clone(),
|
||||
page_rect_au);
|
||||
let display_list = optimizer.optimize();
|
||||
|
||||
let size = Size2D(tile.screen_rect.size.width as i32, tile.screen_rect.size.height as i32);
|
||||
let draw_target = if !opts::get().gpu_painting {
|
||||
DrawTarget::new(SkiaBackend, size, B8G8R8A8)
|
||||
|
@ -496,10 +512,11 @@ impl WorkerThread {
|
|||
};
|
||||
|
||||
// Apply the translation to render the tile we want.
|
||||
let tile_bounds = tile.page_rect;
|
||||
let matrix: Matrix2D<AzFloat> = Matrix2D::identity();
|
||||
let matrix = matrix.scale(scale as AzFloat, scale as AzFloat);
|
||||
let matrix = matrix.translate(-page_rect.origin.x as AzFloat,
|
||||
-page_rect.origin.y as AzFloat);
|
||||
let matrix = matrix.translate(-tile_bounds.origin.x as AzFloat,
|
||||
-tile_bounds.origin.y as AzFloat);
|
||||
|
||||
render_context.draw_target.set_transform(&matrix);
|
||||
|
||||
|
@ -509,7 +526,10 @@ impl WorkerThread {
|
|||
// Draw the display list.
|
||||
profile(time::PaintingPerTileCategory, None, self.time_profiler_sender.clone(), || {
|
||||
let mut clip_stack = Vec::new();
|
||||
display_list.draw_into_context(&mut render_context, &matrix, &mut clip_stack);
|
||||
stacking_context.optimize_and_draw_into_context(&mut render_context,
|
||||
&tile.page_rect,
|
||||
&matrix,
|
||||
&mut clip_stack);
|
||||
render_context.draw_target.flush();
|
||||
});
|
||||
}
|
||||
|
@ -564,7 +584,7 @@ impl WorkerThread {
|
|||
|
||||
enum MsgToWorkerThread {
|
||||
ExitMsgToWorkerThread,
|
||||
PaintTileMsgToWorkerThread(BufferRequest, Option<Box<LayerBuffer>>, RenderLayer, f32),
|
||||
PaintTileMsgToWorkerThread(BufferRequest, Option<Box<LayerBuffer>>, Arc<StackingContext>, f32),
|
||||
}
|
||||
|
||||
enum MsgFromWorkerThread {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue